当前位置: 首页 > article >正文

Linux: ASoC 声卡硬件参数的设置过程简析

文章目录

  • 1. 前言
  • 2. ASoC 声卡设备硬件参数
    • 2.1 将 DAI、Machine 平台的硬件参数添加到声卡
    • 2.2 打开 PCM 流时将声卡硬件参数配置到 PCM 流
    • 2.3 应用程序对 PCM 流参数进行修改调整

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. ASoC 声卡设备硬件参数

ASoC(ALSA System-on-Chip) 声卡驱动框架如下图所示:
在这里插入图片描述
整个声卡驱动由 CPU DAI 驱动CODEC DAI 驱动Machine 驱动(胶水粘合驱动)3 者拼接构成,所以声卡硬件参数,自然也同时受这 3 者的共同约束。

声卡的硬件参数管理过程包括以下两步:

1. 将 DAI (CPU 侧 + CODEC 侧)、Machine 平台的 声卡硬件参数 添加到声卡
2. 打开 PCM 流时,将 声卡的硬件参数 配置到 PCM 流
3. 应用程序在硬件参数允许的范围内,对 PCM 流参数进行修改调整

2.1 将 DAI、Machine 平台的硬件参数添加到声卡

声卡硬件参数主要包括:通道数,数据格式(S16_LE,S32_LE,...),采样率,period 大小,FIFO 大小 等。这些声卡硬件参数来自于 DAI 接口驱动、Machine 平台驱动。这里以 CPU 侧 DAI 驱动硬件参数为例,来剖析声卡硬件参数的添加过程。如:

static struct snd_soc_dai_driver sun4i_i2s_dai = {
	.probe = sun4i_i2s_dai_probe,
	.capture = {
		.stream_name = "Capture",
		.channels_min = 2,
		.channels_max = 2,
		.rates = SNDRV_PCM_RATE_8000_192000,
		.formats = SNDRV_PCM_FMTBIT_S16_LE,
	},
	.playback = {
		.stream_name = "Playback",
		.channels_min = 2,
		.channels_max = 2,
		.rates = SNDRV_PCM_RATE_8000_192000,
		.formats = SNDRV_PCM_FMTBIT_S16_LE,
	},
	.ops = &sun4i_i2s_dai_ops,
	.symmetric_rates = 1,
};

上面的示例代码定义了 SoC CPU 一侧 DAI 接口 I2S 支持的通道数、数据格式、采样率,然后通过接口 snd_soc_register_component() ,将声卡 DAI 组件注册到系统,即添加到声卡组件对象(struct snd_soc_component)全局列表 component_list

devm_snd_soc_register_component(&pdev->dev,
					      &sun4i_i2s_component,
					      &sun4i_i2s_dai, 1)
	snd_soc_register_component()
		struct snd_soc_component *component;
		...
		component = kzalloc(sizeof(*component), GFP_KERNEL);
		...
		ret = snd_soc_component_initialize(component, component_driver, dev);
		...
		snd_soc_component_add(component)
			...
			list_add(&component->list, &component_list); /* (1) 添加到声卡组件全局列表 @component_list */

然后在注册声卡时,将声卡组件对象(struct snd_soc_component)添加到声卡(声卡组件对象(struct snd_soc_component)作为声卡的一部分),这一过程也将包含在 DAI 驱动内的 DAI 硬件参数添加到了声卡:

snd_soc_register_card()
	snd_soc_instantiate_card()
		soc_bind_dai_link()

static int soc_bind_dai_link(struct snd_soc_card *card,
	struct snd_soc_dai_link *dai_link)
{
	...
	/* 集成 CPU 一侧 DAI (驱动) 到声卡 */
	rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
	snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
	...
	/* Find CODEC from registered CODECs */
	codec_dais = rtd->codec_dais;
	for (i = 0; i < rtd->num_codecs; i++) { /* 集成 CODEC 一侧 DAI (驱动) 到声卡 */
		codec_dais[i] = snd_soc_find_dai(&codecs[i]); // 查找 DAI
		...
		snd_soc_rtdcom_add(rtd, codec_dais[i]->component); // 添加 DAI 到声卡(包括 DAI 携带的硬件参数)
	}
	...
}

struct snd_soc_dai *snd_soc_find_dai(
	const struct snd_soc_dai_link_component *dlc)
{
	...
	/* Find CPU DAI from registered DAIs*/
	list_for_each_entry(component, &component_list, list) {
		// 匹配 DAI
		...
		list_for_each_entry(dai, &component->dai_list, list) {
			...
			return dai; // 返回匹配的 DAI
		}
	}
	...
}

static int snd_soc_rtdcom_add(struct snd_soc_pcm_runtime *rtd,
			      struct snd_soc_component *component)
{
	...
	list_add_tail(&new_rtdcom->list, &rtd->component_list);
	...
}

2.2 打开 PCM 流时将声卡硬件参数配置到 PCM 流

open("/dev/snd/pcmC%dD%dp", ...)
	...
	snd_pcm_playback_open() / snd_pcm_capture_open()
		snd_pcm_open()
			snd_pcm_open_file()
				snd_pcm_open_substream()

struct snd_pcm_runtime {
	...
	struct snd_pcm_hardware hw;
	struct snd_pcm_hw_constraints hw_constraints;
	...
};

int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,
			   struct file *file,
			   struct snd_pcm_substream **rsubstream)
{
	...
	// 将 DAI 等的硬件参数 配置到 PCM 流
	if ((err = substream->ops->open(substream)) < 0) // ASoC 架构下: soc_pcm_open() 
		goto error;
	...
	// 将 参数 转换为 约束参数形式
	err = snd_pcm_hw_constraints_complete(substream);
	...
}

// 将 DAI 等的硬件参数 配置到 PCM 流
soc_pcm_open() // ASoC 架构下
	soc_pcm_init_runtime_hw()

static void soc_pcm_init_runtime_hw(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_pcm_hardware *hw = &runtime->hw;
	...
	struct snd_soc_pcm_stream *codec_stream;
	struct snd_soc_pcm_stream *cpu_stream;
	...

	// CPU DAI
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		cpu_stream = &cpu_dai_drv->playback; // &sun4i_i2s_dai.playback
	else
		cpu_stream = &cpu_dai_drv->capture; // &sun4i_i2s_dai.capture

	...

	/* 配置声卡 DAI 的参数到 PCM 流 */
	hw->channels_min = max(chan_min, cpu_stream->channels_min);
	hw->channels_max = min(chan_max, cpu_stream->channels_max);
	if (hw->formats)
		hw->formats &= formats & cpu_stream->formats;
	else
		hw->formats = formats & cpu_stream->formats;
	
	...
}

int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_pcm_hardware *hw = &runtime->hw;
	...

	...
	err = snd_pcm_hw_constraint_mask(runtime, SNDRV_PCM_HW_PARAM_ACCESS, mask);
	...
	err = snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, hw->formats);
	...
	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
					   hw->channels_min, hw->channels_max);
	...
	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIODS,
					   hw->periods_min, hw->periods_max);
	...
	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
					   hw->period_bytes_min, hw->buffer_bytes_max);
	...
}

int snd_pcm_hw_constraint_minmax(struct snd_pcm_runtime *runtime, snd_pcm_hw_param_t var,
				 unsigned int min, unsigned int max)
{
	struct snd_pcm_hw_constraints *constrs = &runtime->hw_constraints;
	struct snd_interval t;
	t.min = min;
	t.max = max;
	t.openmin = t.openmax = 0;
	t.integer = 0;
	return snd_interval_refine(constrs_interval(constrs, var), &t);
}

2.3 应用程序对 PCM 流参数进行修改调整

2.2 小节中,在 open("/dev/snd/pcmC%dD%dp", ...) 打开 PCM 流时,设置了 PCM 的默认硬件参数,但这并不一定符合用户的预期,譬如采样率,通道数,数据格式等,这时候用户空间可以通过 ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &hwparams) 来进行调整:

sys_ioctl()
	...
	snd_pcm_ioctl()
		snd_pcm_common_ioctl()
			snd_pcm_hw_params_user()
				snd_pcm_hw_params()

static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream,
				  struct snd_pcm_hw_params __user * _params)
{
	struct snd_pcm_hw_params *params;
	...
	
	/* 复制用户空间参数到内核空间 */
	params = memdup_user(_params, sizeof(*params));
	...

	/* 按用户请求参数 @_params,对当前设置的参数做可能的调整 */
	err = snd_pcm_hw_params(substream, params);
	...

	/* 修改后的参数,从 @_params 返回用户空间 */
	if (copy_to_user(_params, params, sizeof(*params)))
		err = -EFAULT;
end:
	kfree(params);
	return err;
}

static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
			     struct snd_pcm_hw_params *params)
{
	struct snd_pcm_runtime *runtime;
	...

	params->rmask = ~0U;
	err = snd_pcm_hw_refine(substream, params); /* 对 所有 硬件参数,按用户请求参数 @params,做可能的调整 */
	...

	/* 更新 用户参数 @params 到 PCM 流 */
	runtime->access = params_access(params);
	runtime->format = params_format(params);
	runtime->subformat = params_subformat(params);
	runtime->channels = params_channels(params);
	runtime->rate = params_rate(params);
	runtime->period_size = params_period_size(params);
	runtime->periods = params_periods(params);
	runtime->buffer_size = params_buffer_size(params);
	runtime->info = params->info;
	runtime->rate_num = params->rate_num;
	runtime->rate_den = params->rate_den;
	...

	return 0;
 _error:
 	...
}

int snd_pcm_hw_refine(struct snd_pcm_substream *substream,
		      struct snd_pcm_hw_params *params)
{
	...

	/* 对所有 掩码类型参数 做可能的调整 */
	err = constrain_mask_params(substream, params);
	...

	/* 对所有 区间类型参数 做可能的调整 */
	err = constrain_interval_params(substream, params);
	...

	/* 对 所有类型(掩码类 + 区间类) 的所有参数 按 约束规则 做可能的调整 */
	err = constrain_params_by_rules(substream, params);
	...
}

这列只看 constrain_interval_params() 的调整参数的过程,其它的类似:

static int constrain_interval_params(struct snd_pcm_substream *substream,
				     struct snd_pcm_hw_params *params)
{
	// 这里的 ,正是前面 2.2 分析 PCM 打开过程中, snd_pcm_hw_constraint_minmax() 设置的
	struct snd_pcm_hw_constraints *constrs =
					&substream->runtime->hw_constraints;
	struct snd_interval *i;
	unsigned int k;
	...

	for (k = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL; k <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; k++) {
		i = hw_param_interval(params, k);
		...
		/* 对区间类型参数做可能的调整 */
		changed = snd_interval_refine(i, constrs_interval(constrs, k));
		...
	}

	return 0;
}

到此,对声卡硬件参数管理设置过程分析已毕。对应硬件参数,声卡还有软件参数,对这部分感兴趣的读者,可自行阅读相关源码。


http://www.kler.cn/a/540666.html

相关文章:

  • 防火墙术语大全( Firewalld Glossary of Terms)
  • 基于SpringBoot的校园社交平台
  • [笔记] 汇编杂记(持续更新)
  • graylog初体验
  • .net一些知识点5
  • 嵌入式AI革命:DeepSeek开源如何终结GPU霸权,开启单片机智能新时代?
  • 协议-ACLLite-ffmpeg
  • C++ STL算法总结
  • salesforce 中 Account 转移给新 Owner 后如何仅转移 Case,而不转移 Opportunity
  • 怎么编写AI模型prompt(提问,表达需求)
  • ZooKeeper Watcher 机制详解:从注册到回调的全过程
  • Vue07
  • vi 是 Unix 和 Linux 系统中常用的文本编辑器
  • 易仓与金蝶云星空无缝集成:实现高效R调拨入库
  • 如何在浏览器中搭建开源Web操作系统Puter的本地与远程环境
  • Python 高阶函数(详解)
  • 主机安全:数字时代的基石
  • harmonyOS的路由跳转及数据请求
  • UNet-二维全景X射线图像牙齿分割(代码和模型修改)
  • DeepSeek神经网络:技术架构与实现原理探析
  • Harmony os router 使用详解
  • 基于UVM搭验证环境
  • 代码随想录_二叉树
  • 【多模态大模型】系列4:目标检测(ViLD、GLIP)
  • 因果推断与机器学习—特定领域的机器学习
  • 如何在 CSS Modules 中使用 Sass 或 Less?