snd_kcontrol_new名称中的SOURCE字段

本文深入探讨snd_kcontrol的工作原理及应用,特别是其在Android音频控制接口中的作用。通过对kcontrol名称规范的理解,帮助读者更好地掌握如何配置和使用音频控制接口。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前些日子写了一篇snd_kcontrol探究,该文主要从内核源码出发简单讲述一下kcontrol接口的始末。这几天因为要在Android里面添加一些音频控制接口,配合alsa_amixer scontents分析,对此有了更深的体会,记录于此。因为这方面的资料实在太少,很多东西都是自我理解的,如有错误请见谅并指出。


name字段是名称标识,这个字段非常重要,因为kcontrol的作用由名称来区分,对于名称相同的kcontrol,则使用index区分。name定义的标准是“SOURCE DIRECTION FUNCTION”即“源 方向 功能”,SOURCE定义了kcontrol的源,如“Master”、“PCM”等;DIRECTION 则为“Playback”、“Capture”等,如果DIRECTION忽略,意味着Playback和capture双向;FUNCTION则可以是“Switch”、“Volume”和“Route”等。

以上的说法比较简略,下面会较详细补充一下。

先内核源码中的文档ControlNames.txt:

This document describes standard names of mixer controls.

Syntax: SOURCE [DIRECTION] FUNCTION

DIRECTION:
  <nothing>	(both directions)
  Playback
  Capture
  Bypass Playback
  Bypass Capture

FUNCTION:
  Switch	(on/off switch)
  Volume
  Route		(route control, hardware specific)

SOURCE:
  Master
  Master Mono
  Hardware Master
  Headphone
  PC Speaker
  Phone
  Phone Input
  Phone Output
  Synth
  FM
  Mic
  Line
  CD
  Video
  Zoom Video
  Aux
  PCM
  PCM Front
  PCM Rear
  PCM Pan
  Loopback
  Analog Loopback	(D/A -> A/D loopback)
  Digital Loopback	(playback -> capture loopback - without analog path)
  Mono
  Mono Output
  Multi
  ADC
  Wave
  Music
  I2S
  IEC958

Exceptions:
  [Digital] Capture Source
  [Digital] Capture Switch	(aka input gain switch)
  [Digital] Capture Volume	(aka input gain volume)
  [Digital] Playback Switch	(aka output gain switch)
  [Digital] Playback Volume	(aka output gain volume)
  Tone Control - Switch
  Tone Control - Bass
  Tone Control - Treble
  3D Control - Switch
  3D Control - Center
  3D Control - Depth
  3D Control - Wide
  3D Control - Space
  3D Control - Level
  Mic Boost [(?dB)]

PCM interface:

  Sample Clock Source	{ "Word", "Internal", "AutoSync" }
  Clock Sync Status	{ "Lock", "Sync", "No Lock" }
  External Rate		/* external capture rate */
  Capture Rate		/* capture rate taken from external source */

IEC958 (S/PDIF) interface:

  IEC958 [...] [Playback|Capture] Switch	/* turn on/off the IEC958 interface */
  IEC958 [...] [Playback|Capture] Volume	/* digital volume control */
  IEC958 [...] [Playback|Capture] Default	/* default or global value - read/write */
  IEC958 [...] [Playback|Capture] Mask		/* consumer and professional mask */
  IEC958 [...] [Playback|Capture] Con Mask	/* consumer mask */
  IEC958 [...] [Playback|Capture] Pro Mask	/* professional mask */
  IEC958 [...] [Playback|Capture] PCM Stream	/* the settings assigned to a PCM stream */
  IEC958 Q-subcode [Playback|Capture] Default	/* Q-subcode bits */
  IEC958 Preamble [Playback|Capture] Default	/* burst preamble words (4*16bits) */
可以看到表示方向DIRECTION有五种,其中DIRECTION为空时则表示Playback、Capture双向。

一个标准的kcontrol如:SOC_DOUBLE("PCM Playback Volume", AC97_PHONE, 8, 0, 31, 1),其中“PCM”为源SOURCE、“Playback”为方向DIRECTION、“Volume”为功能FUNCTION--音量调节,这个kcontrol是调节放音PCM的Volume的。

又如:SOC_SINGLE("Mic Switch", AC97_CD, 15, 1, 1),其中“Mic”为源SOURCE、方向DIRECTION为空表示Playback和Capture双向、“Switch”为功能FUNCTION--开关切换,这个kcontrol是用于切换Mic开关的。

实际应用中,并不是所有的kcontrol命名都符合ControlNames.txt规则。如:SOC_SINGLE("ALC Hold Time", AC97_CODEC_CLASS_REV, 8, 15, 0),这个kcontrol的name只有名为“ALC Hold Time”的SOURCE字段,DIRECTION和FUNCTION都欠缺。


alsa_amixer命令可以输出这些kcontrol的名称、numid和当前值等信息。而选项sconctols/controls、scontents/contents、sset/cset、sget/cget有所不同,前者是mixer simple control信息,后者是control信息;前者的handle通过snd_mixer_open取得,后者的handle通过snd_ctl_open取得。

两个kcontrol:1、SOC_SINGLE("Capture Switch", AC97_CD, 15, 1, 1), 2、SOC_DOUBLE("Capture Volume", AC97_CD, 8, 0, 31, 0):

在alsa_amixer scontrols来看,显示一个名称为“Capture”的mixer simple control,根据我的结果,一般这个mixer simple control对应的是后者【猜测:之所以有这样的结果,是因为两个kcontrol的SOURCE名称相同,而mixer simple control是匹配kcontrol的SOURCE字段的;因此SOURCE字段区别体现在mixer simple control上,而name区别体现在control上,待验证。】

在alsa_amixer controls来看,列表会显示所有的kcontrol,因此会显示“Capture Switch”和“Capture Volume”这两个kcontrol。这不难理解,在snd_kcontrol探究中就提到,上层是根据name来找到底层的kcontrol的,这个name包含了SOURCE、DIRECTION和FUNCTION这三个字段。

【建议:编写snd_kcontrol时,尽量使用标准的ControlName,留意SOURCE字段是否有重复。在上层应用中,很多都是使用snd_mixer_open得到的mixer simple control,一个典型的例子就是Android2.2的AlsaMixer.cpp。之前我在为Android添加mic mute接口时,发现无论如何都找不到“Capture Switch”的kcontrol,因为它与“Capture Volume”的kcontrol的SOURCE字段重名了。将其改名为“Mic Switch”就行了。】


2011/4/16

找到一个alsa基本架构图:


<think>我们面对的是一个Linux内核音频子系统(ALSA)中的关键结构体 `struct snd_kcontrol_new`,用于定义音频控制元素。下面逐字段解析其功能: ### 结构体字段解析 ```c struct snd_kcontrol_new { snd_ctl_elem_iface_t iface; /* 控制元素接口类型 */ unsigned int device; /* 设备/客户端编号 */ unsigned int subdevice; /* 子设备(子流)编号 */ const unsigned char *name; /* 控制项名称(ASCII字符串) */ unsigned int index; /* 控制项索引号 */ unsigned int access; /* 访问权限标志 */ unsigned int count; /* 相同元素的数量(用于多通道) */ snd_kcontrol_info_t *info; /* 回调:获取控制项元信息 */ snd_kcontrol_get_t *get; /* 回调:读取当前值 */ snd_kcontrol_put_t *put; /* 回调:写入新值(需加锁) */ union { /* TLV(Type-Length-Value)数据操作 */ snd_kcontrol_tlv_rw_t *c; /* 读写TLV的回调函数 */ const unsigned int *p; /* 静态TLV数据指针 */ } tlv; unsigned long private_value; /* 驱动私有数据(传递额外参数) */ }; ``` ### 核心字段详解 1. **接口类型 (`iface`)** 定义控制项所属硬件接口,通过 `snd_ctl_elem_iface_t` 枚举指定: - `SNDRV_CTL_ELEM_IFACE_CARD`:声卡级全局控制 - `SNDRV_CTL_ELEM_IFACE_HWDEP`:硬件依赖控制 - `SNDRV_CTL_ELEM_IFACE_MIXER`:混音器通道控制(最常见) - `SNDRV_CTL_ELEM_IFACE_PCM`:PCM流控制 2. **访问权限 (`access`)** 按位组合标志,控制读写行为: - `SNDRV_CTL_ELEM_ACCESS_READ`:只读 - `SNDRV_CTL_ELEM_ACCESS_WRITE`:只写 - `SNDRV_CTL_ELEM_ACCESS_VOLATILE`:值易变(不缓存) - `SNDRV_CTL_ELEM_ACCESS_TLV_READ`:允许读取TLV数据 3. **回调函数三剑客** - `info`:返回控制项元数据(名称、值范围、枚举项等) - `get`:读取当前值到用户缓冲区(需将硬件值转换为用户空间格式) - `put`:将用户空间传入的值写入硬件(返回实际变更状态) 4. **TLV 操作联合体** 用于传递音频参数(如音量缩放表): - 动态模式:设置 `.c` 回调函数指针,实现 `snd_kcontrol_tlv_rw_t` - 静态模式:直接设置 `.p` 指向静态TLV数组(如 `const unsigned int db_scale[]`) 5. **私有数据 (`private_value`)** 驱动自定义数据传递通道,典型用法: - 存储寄存器偏移地址 - 传递位掩码或通道映射 - 保存多路复用控制项的分组索引 ### 典型驱动实现示例 ```c // 定义音量控制项 static struct snd_kcontrol_new volume_ctl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Master Playback Volume", .index = 0, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, .info = snd_mychip_volume_info, // 实现元信息回调 .get = snd_mychip_volume_get, // 实现读回调 .put = snd_mychip_volume_put, // 实现写回调 .tlv = { .p = db_scale }, // 静态音量步进表 .private_value = REG_VOL_OFFSET, // 寄存器地址 }; // 注册控制项 snd_ctl_add(card, snd_ctl_new1(&volume_ctl, chip)); ``` ### 关键设计思想 - **硬件抽象**:通过回调函数隔离驱动实现细节 - **零拷贝传递**:`private_value` 避免为每个控制项创建专用结构体 - **TLV标准化**:统一处理参数线性化(如分贝值转换) §§相关问题§§ 1. ALSA驱动中`snd_kcontrol_new`结构体的`count`字段在哪些场景下需要设置为大于1的值? 2. 如何通过`private_value`字段实现一个控制项同时操作多个硬件寄存器? 3. 在`put`回调函数中,为什么必须检测值是否实际变更?不检测会导致什么问题? 4. TLV数据中的`SNDRV_CTL_TLVT_DB_SCALE`类型如何描述音量缩放规则? 5. 当控制项需要跨多个子设备(subdevice)生效时,`subdevice`字段应如何设置?</think>### `struct snd_kcontrol_new` 深度解析 这是 Linux ALSA(Advanced Linux Sound Architecture)驱动开发中的核心结构体,用于定义音频控制元素(如音量、开关、路由等)。每个字段对应硬件控制项的抽象描述: #### 字段详解(按功能分组) ```c struct snd_kcontrol_new { /* 标识控制项位置 */ snd_ctl_elem_iface_t iface; // 接口类型(如混音器/MIXER, PCM流) unsigned int device; // 物理设备编号(声卡内区分多个设备) unsigned int subdevice; // 子设备号(区分多路音频流) /* 控制项元信息 */ const unsigned char *name; // 用户可见名称(如 "Master Volume") unsigned int index; // 同名控制项的索引(用于区分重复项) unsigned int access; // 访问权限(如读/写/易失性标志位组合) unsigned int count; // 控制项值数量(如立体声音量需2个值) /* 核心回调函数 */ snd_kcontrol_info_t *info; // 获取控制项元数据(值范围/类型等) snd_kcontrol_get_t *get; // 读取当前值(用户空间->驱动) snd_kcontrol_put_t *put; // 写入新值(驱动->硬件寄存器) /* TLV(Type-Length-Value)数据 */ union { snd_kcontrol_tlv_rw_t *c; // 动态TLV回调(如音量分贝映射) const unsigned int *p; // 静态TLV数据指针 } tlv; /* 驱动私有数据 */ unsigned long private_value; // 传递寄存器地址/位掩码等硬件参数 }; ``` --- ### 关键字段深入说明 1. **`access` 权限标志** 位掩码组合,控制交互行为: ```c #define SNDRV_CTL_ELEM_ACCESS_READ (1<<0) // 可读 #define SNDRV_CTL_ELEM_ACCESS_WRITE (1<<1) // 可写 #define SNDRV_CTL_ELEM_ACCESS_VOLATILE (1<<2) // 值易变(不缓存) #define SNDRV_CTL_ELEM_ACCESS_TLV_READ (1<<6) // 支持TLV读取 ``` 2. **回调函数原型** ```c // 元数据回调 typedef int (*snd_kcontrol_info_t)(struct snd_kcontrol *, struct snd_ctl_elem_info *); // 值获取回调 typedef int (*snd_kcontrol_get_t)(struct snd_kcontrol *, struct snd_ctl_elem_value *); // 值设置回调 typedef int (*snd_kcontrol_put_t)(struct snd_kcontrol *, struct snd_ctl_elem_value *); ``` 3. **`private_value` 典型用法** 驱动通过此字段传递硬件参数: ```c // 示例:将寄存器地址和位掩码合并传递 #define CODEC_REG_SHIFT 16 unsigned long reg = 0x4A; // I2C寄存器地址 unsigned long mask = 0x0F; // 有效位掩码 ctrl.private_value = (reg << CODEC_REG_SHIFT) | mask; // 在回调函数中解析: unsigned int reg = (kcontrol->private_value >> CODEC_REG_SHIFT) & 0xFF; unsigned int mask = kcontrol->private_value & 0xFF; ``` --- ### 典型应用场景 1. **定义音量控制** ```c static struct snd_kcontrol_new volume_ctrl = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Volume", .index = 0, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, .count = 2, // 立体声 .info = snd_pcm_volume_info, .get = snd_pcm_volume_get, .put = snd_pcm_volume_put, .tlv = { .p = db_scale }, // 分贝刻度静态数据 .private_value = (unsigned long)&hw_regs, // 硬件寄存器结构体 }; ``` 2. **注册控制项** 在驱动初始化时添加到声卡: ```c snd_ctl_add(card, snd_ctl_new1(&volume_ctrl, chip)); // card: snd_card实例, chip: 驱动私有数据 ``` --- ### 设计哲学 - **硬件抽象**:通过回调函数隔离硬件操作 - **动态绑定**:`private_value` 实现通用结构体适配不同硬件 - **用户空间接口**:通过 `/proc/asound/cardX/control*` 暴露控制项
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值