snd_kcontrol探究

本文深入探讨ALSA中的control控制接口原理,包括snd_kcontrol_new结构体各字段含义、kcontrol宏定义及其触发过程,帮助读者理解音频控制的底层机制。

control控制接口


控制接口对于许多开关(switch)和调节器(slider)应用广泛,它能被用户空间存取,从而读写CODEC相关寄存器。control的主要用于mixer。它用snd_kcontrol_new结构体描述。


snd_kcontrol_new


[cpp]  view plain copy
  1. struct snd_kcontrol_new {    
  2.     snd_ctl_elem_iface_t iface; /* interface identifier */    
  3.     unsigned int device;        /* device/client number */    
  4.     unsigned int subdevice;     /* subdevice (substream) number */    
  5.     unsigned char *name;        /* ASCII name of item */    
  6.     unsigned int index;     /* index of item */    
  7.     unsigned int access;        /* access rights */    
  8.     unsigned int count;     /* count of same elements */    
  9.     snd_kcontrol_info_t *info;    
  10.     snd_kcontrol_get_t *get;    
  11.     snd_kcontrol_put_t *put;    
  12.     union {    
  13.         snd_kcontrol_tlv_rw_t *c;    
  14.         const unsigned int *p;    
  15.     } tlv;    
  16.     unsigned long private_value;    
  17. };  


iface 字段定义了control的类型,形式为SNDRV_CTL_ELEM_IFACE_XXX,对于mixer是SNDRV_CTL_ELEM_IFACE_MIXER,对于不属于mixer的全局控制,使用CARD;如果关联到某类设备,则是PCM、RAWMIDI、TIMER或SEQUENCER。在这里,我们主要关注mixer。

name字段是名称标识,这个字段非常重要,因为control的作用由名称来区分,对于名称相同的control,则使用index区分。下面会详细介绍上层应用如何根据name名称标识来找到底层相应的control。

name定义的标准是“SOURCE DIRECTION FUNCTION”即“源 方向 功能”,SOURCE定义了control的源,如“Master”、“PCM”等;DIRECTION 则为“Playback”、“Capture”等,如果DIRECTION忽略,意味着Playback和capture双向;FUNCTION则可以是“Switch”、“Volume”和“Route”等。

上层也可以根据numid来找到对应的control,snd_ctl_find_id()也是优先判断上层是否传递了numid,是则直接返回这个numid对应的control。用户层设置numid和control的关联时,可用alsa-lib的snd_mixer_selem_set_enum_item()函数。snd_kcontrol_new结构体并没有numid这个成员,是因为numid是系统自动管理的,原则是该control的注册次序,保存到snd_ctl_elem_value结构体中。

access字段是访问控制权限。SNDRV_CTL_ELEM_ACCESS_READ意味着只读,这时put()函数不必实现;SNDRV_CTL_ELEM_ACCESS_WRITE意味着只写,这时get()函数不必实现。若control值频繁变化,则需定义 VOLATILE标志。当control处于非激活状态时,应设置INACTIVE标志。

private_value字段包含1个长整型值,可以通过它给info()、get()和put()函数传递参数。


kcontrol宏


在早期的ALSA创建一个新的control需要实现snd_kcontrol_new中的info、get和put这三个成员函数。现在较新版本的ALSA均定义了一些宏,如:

[cpp]  view plain copy
  1. #define SOC_SINGLE(xname, reg, shift, max, invert) /    
  2. {   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, /    
  3.     .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,/    
  4.     .put = snd_soc_put_volsw, /    
  5.     .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }   
这个宏的对象是MIXER,对寄存器reg的位偏移shift可以设置0-max的数值。

又如:

[cpp]  view plain copy
  1. #define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) /    
  2. {   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),/    
  3.     .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |/    
  4.          SNDRV_CTL_ELEM_ACCESS_READWRITE,/    
  5.     .tlv.p = (tlv_array), /    
  6.     .info = snd_soc_info_volsw_2r, /    
  7.     .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, /    
  8.     .private_value = (unsigned long)&(struct soc_mixer_control) /    
  9.         {.reg = reg_left, .rreg = reg_right, .shift = xshift, /    
  10.         .max = xmax, .invert = xinvert} }  
这个宏与刚才类似,但是它是对两个寄存器reg_left和reg_right进行同一操作,Codec芯片中左右声道的寄存器配置一般来说是差不多的,这就是这个宏存在的意义。

例如我们一个Playback Volume的kcontrol接口这样定义:

SOC_DOUBLE_R_TLV("Playback Volume", REG_VOL_L, REG_VOL_R, 0, 192, 0, digital_tlv)

我们仅仅需要将Volume寄存器地址及位偏移,最大值填进去即可,当然这些数据要从Codec的datasheet取得。这里Volume寄存器地址是REG_VOL_L(左声道)和REG_VOL_R(右声道),位偏移为0,DAC Digital Gain范围是0-192(steps)。


触发过程


为了探讨这些kcontrol是如何触发的,我们以SOC_DOUBLE_R_TLV的put函数为例说明:

[cpp]  view plain copy
  1. /**  
  2.  * snd_soc_put_volsw_2r - double mixer set callback  
  3.  * @kcontrol: mixer control  
  4.  * @ucontrol: control element information  
  5.  *  
  6.  * Callback to set the value of a double mixer control that spans 2 registers.  
  7.  *  
  8.  * Returns 0 for success.  
  9.  */    
  10. int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol,    
  11.     struct snd_ctl_elem_value *ucontrol)    
  12. {    
  13.     struct soc_mixer_control *mc =    
  14.         (struct soc_mixer_control *)kcontrol->private_value;    
  15.     struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);    
  16.     unsigned int reg = mc->reg;    
  17.     unsigned int reg2 = mc->rreg;    
  18.     unsigned int shift = mc->shift;    
  19.     int max = mc->max;    
  20.     unsigned int mask = (1 << fls(max)) - 1;    
  21.     unsigned int invert = mc->invert;    
  22.     int err;    
  23.     unsigned int val, val2, val_mask;    
  24.     
  25.     val_mask = mask << shift;    
  26.     val = (ucontrol->value.integer.value[0] & mask);    
  27.     val2 = (ucontrol->value.integer.value[1] & mask);    
  28.      
  29.     if (invert) {    
  30.         val = max - val;    
  31.         val2 = max - val2;    
  32.     }     
  33.     
  34.     val = val << shift;    
  35.     val2 = val2 << shift;    
  36.     
  37.     err = snd_soc_update_bits(codec, reg, val_mask, val);    
  38.     if (err < 0)    
  39.         return err;    
  40.     
  41.     err = snd_soc_update_bits(codec, reg2, val_mask, val2);    
  42.     return err;    
  43. }  
struct snd_ctl_elem_value *ucontrol:从用户层传递下来的,这个也可以从命名看出来(kcontrol-kernel control,ucontrol-user control);

shift是位偏移,而位掩码mask是通过宏SOC_DOUBLE_R_TLV中的xmax运算得到:unsigned int mask = (1 << fls(max)) - 1;

调用snd_soc_update_bits()->snd_soc_write()将ucontrol的value送到CODEC的寄存器上。

snd_soc_put_volsw_2r()作为一个callback函数,用户层要设置某些功能时,如改变Playback Volume:

#amixer cset numid=3,iface=MIXER,name='Playback Volume' 100

注:amixer相关用法见:http://hi.baidu.com/serial_story/blog/item/c4e826d82a562f3f32fa1c31.html


到内核层时,会遍历一个节点类型为struct snd_kcontrol *的链表,找到kcontrol.id.numid与3相匹配的kctl(这个过程见snd_ctl_find_id()函数),然后调用kctl.put()函数将100写到Playback Volume寄存器中。当然如果上层没有提供numid,则可根据name找到kcontrol.id.name相匹配的kctl。详细见snd_ctl_find_id函数:

[cpp]  view plain copy
  1. /**  
  2.  * snd_ctl_find_id - find the control instance with the given id  
  3.  * @card: the card instance  
  4.  * @id: the id to search  
  5.  *  
  6.  * Finds the control instance with the given id from the card.  
  7.  *  
  8.  * Returns the pointer of the instance if found, or NULL if not.  
  9.  *  
  10.  * The caller must down card->controls_rwsem before calling this function  
  11.  * (if the race condition can happen).  
  12.  */    
  13. struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card,    
  14.                      struct snd_ctl_elem_id *id)    
  15. {    
  16.     struct snd_kcontrol *kctl;    
  17.     
  18.     if (snd_BUG_ON(!card || !id))    
  19.         return NULL;    
  20.     if (id->numid != 0)    
  21.         return snd_ctl_find_numid(card, id->numid);    
  22.     list_for_each_entry(kctl, &card->controls, list) {    
  23.         if (kctl->id.iface != id->iface)    
  24.             continue;    
  25.         if (kctl->id.device != id->device)    
  26.             continue;    
  27.         if (kctl->id.subdevice != id->subdevice)    
  28.             continue;    
  29.         if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)))    
  30.             continue;    
  31.         if (kctl->id.index > id->index)    
  32.             continue;    
  33.         if (kctl->id.index + kctl->count <= id->index)    
  34.             continue;    
  35.         return kctl;    
  36.     }    
  37.     return NULL;    
  38. }   

从上往下的大致流程:

[cpp]  view plain copy
  1. amixer-用户层    
  2.   |->snd_ctl_ioctl-系统调用    
  3.        |->snd_ctl_elem_write_user-内核钩子函数    
  4.             |->snd_ctl_elem_wirte-    
  5.                  |->snd_ctl_find_id-遍历kcontrol链表找到name字段匹配的kctl    
  6.                  |->kctl->put()-调用kctl的成员函数put()    
  7.                       |->snd_soc_put_volsw_2r   
PS:上层如何设置kctl的numid,可参考: http://blog.youkuaiyun.com/cpuwolf/archive/2009/10/17/4686830.aspx
<think> 我们正在查找`snd_kcontrol_chip`的替代方案。首先,需要了解`snd_kcontrol_chip`的作用:它用于从`snd_kcontrol`结构中获取芯片特定的私有数据。在ALSA驱动开发中,当我们需要在控制元素的回调函数(如`get`、`put`)中访问芯片特定的数据时,通常会使用`snd_kcontrol_chip`。 然而,随着内核的更新,ALSA框架也在变化。特别是在较新的内核中,`snd_kcontrol`结构的使用方式可能发生了变化。根据引用[3]和[4],我们知道在Machine驱动层面,现在主要使用`snd_soc_card`和`snd_soc_dai_link`等结构。此外,引用[1]中提到了`snd_device_new`用于创建ALSA设备,包括编解码器(CODEC)设备。 在较新的ALSA SoC框架中,控制元素的创建通常通过`snd_soc_add_component_controls`或`snd_soc_add_card_controls`等函数完成。这些函数会将控制元素与特定的组件(component)或声卡(card)关联,然后在回调函数中,我们可以通过`snd_soc_component_get_drvdata`或`snd_soc_kcontrol_component`来获取组件,进而获取私有数据。 因此,替代`snd_kcontrol_chip`的方法可能是使用`snd_soc_kcontrol_component`获取组件,然后通过`component->dev->driver_data`或`snd_soc_component_get_drvdata`获取私有数据。 具体步骤: 1. 在控制元素的回调函数中,使用`snd_soc_kcontrol_component(kcontrol)`获取`struct snd_soc_component *`。 2. 然后,使用`snd_soc_component_get_drvdata(component)`获取芯片特定的私有数据。 示例代码: ```c static int my_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); struct my_chip_data *chip = snd_soc_component_get_drvdata(component); // 然后就可以使用chip来访问私有数据 ... } ``` 注意:这种方法要求控制元素是由SoC框架添加的,即通过`snd_soc_add_component_controls`或`snd_soc_add_card_controls`等函数添加的。如果控制元素是通过传统的ALSA方式添加(例如直接调用`snd_ctl_add`),则可能无法使用`snd_soc_kcontrol_component`。 另外,如果是在编解码器驱动中,我们通常将控制元素添加到编解码器组件上,这样在回调函数中就可以使用上述方法。 如果是在Machine驱动中添加的控制元素,通常添加到声卡上,那么获取私有数据的方式可能略有不同。在这种情况下,我们可以通过`snd_kcontrol_chip`来获取声卡的私有数据(如果Machine驱动将声卡的私有数据设置为了芯片数据)?但是,在SoC框架中,我们更倾向于将控制元素与组件关联。 总结:在ALSA SoC框架中,推荐使用组件(component)的方式来管理控制元素,并通过`snd_soc_kcontrol_component`和`snd_soc_component_get_drvdata`来获取私有数据。因此,替代`snd_kcontrol_chip`的主要方案是使用上述方法。 注意:`snd_kcontrol_chip`是一个宏,定义在`sound/core/control.h`中: ```c #define snd_kcontrol_chip(kcontrol) ((kcontrol)->private_data) ``` 它直接返回`private_data`。在传统的ALSA驱动中,我们在创建控制元素时会设置`private_data`为芯片数据。而在SoC框架中,这个`private_data`通常由框架设置为组件,因此我们通过`snd_soc_kcontrol_component`来获取组件(它内部也是访问`private_data`,但进行了类型转换和检查)。 因此,对于SoC框架下的驱动,我们不再推荐直接使用`snd_kcontrol_chip`来获取芯片数据,而是通过组件来获取。 如果我们的驱动不是基于SoC框架的,那么仍然可以使用`snd_kcontrol_chip`。 所以,替代方案取决于上下文: - 如果是SoC框架的驱动,使用`snd_soc_kcontrol_component`和`snd_soc_component_get_drvdata`。 - 如果是传统的ALSA驱动,仍然可以使用`snd_kcontrol_chip`。 但是,由于用户提到了“替代方案”,可能是在新的内核中`snd_kcontrol_chip`不再适用,或者用户正在迁移到SoC框架。因此,我们重点给出SoC框架下的替代方法。 参考文献: [^1]: 传统ALSA设备创建函数,如`snd_pcm_new`等。 [^3][^4]: 描述了SoC框架下Machine驱动的注册方式,使用`snd_soc_card`。 </think> # snd_kcontrol_chip 替代方案指南 在 Linux ALSA 驱动开发中,`snd_kcontrol_chip` 是用于从 `snd_kcontrol` 结构中获取芯片私有数据的宏。但随着内核更新,特别是在 ASoC(ALSA System on Chip)框架中,推荐使用更现代的替代方案。 ## ✅ 推荐替代方案 ### 1. 使用 `snd_soc_component_get_drvdata` 这是当前 ASoC 框架推荐的方式: ```c static int my_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { // 获取关联的组件 struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); // 获取芯片私有数据 struct my_chip_data *chip = snd_soc_component_get_drvdata(component); // 使用 chip 访问硬件寄存器 ... } ``` ### 2. 使用 `snd_kcontrol_chip` 的替代宏 对于非 ASoC 的传统驱动,内核提供了更安全的替代宏: ```c // 替代 snd_kcontrol_chip #define snd_kcontrol_chip(kctrl) ((kctrl)->private_data) // 使用示例 static int my_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct my_chip_data *chip = snd_kcontrol_chip(kcontrol); ... } ``` ## 迁移步骤 1. **确认上下文**: - 对于 ASoC 组件驱动:使用 `snd_soc_component_get_drvdata` - 对于传统 ALSA 驱动:使用 `snd_kcontrol_chip` 宏 2. **更新控件注册**: 使用组件注册方式替代旧方法: ```c // 旧方法 snd_ctl_add(card, snd_ctl_new1(&my_control, chip)); // 新方法 (ASoC) int snd_soc_add_component_controls(struct snd_soc_component *component, const struct snd_kcontrol_new *controls, unsigned int num_controls); ``` 3. **数据类型转换**: 确保私有数据正确设置: ```c // 在组件探测函数中 snd_soc_component_set_drvdata(component, chip); ``` ## 示例对比 ### 传统方式 ```c static struct snd_kcontrol_new my_control = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "My Control", .info = my_control_info, .get = my_control_get, .put = my_control_put, }; // 在驱动初始化中 snd_ctl_add(card, snd_ctl_new1(&my_control, chip)); // 在回调函数中 static int my_control_get(...) { struct my_chip *chip = snd_kcontrol_chip(kcontrol); } ``` ### 现代 ASoC 方式 ```c static const struct snd_kcontrol_new my_controls[] = { SOC_SINGLE("My Control", REG_ADDR, REG_SHIFT, MAX_VAL, DEFAULT), }; // 在组件探测中 snd_soc_add_component_controls(component, my_controls, ARRAY_SIZE(my_controls)); // 在回调函数中 (如需要自定义处理) static int my_control_get(...) { struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); struct my_chip *chip = snd_soc_component_get_drvdata(comp); } ``` ## 注意事项 1. **内核版本兼容**: - ASoC 组件接口自 Linux 3.16 起稳定 - 旧版内核 (3.x 之前) 可能需要使用 `snd_soc_codec` 替代 `snd_soc_component` 2. **数据结构变化**: ```c // 旧版 (Linux < 4.xx) struct snd_soc_codec *codec = snd_kcontrol_codec(kcontrol); struct my_chip *chip = snd_soc_codec_get_drvdata(codec); // 新版 (Linux >= 4.xx) struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); struct my_chip *chip = snd_soc_component_get_drvdata(comp); ``` 3. **调试技巧**: - 使用 `dev_dbg(component->dev, "Control access...")` 添加调试信息 - 检查 `component->name` 确认正确的组件被访问 [^1]: ALSA 框架通过 `snd_pcm_new()` 等封装函数简化了设备创建 [^3]: Machine 驱动通过 `snd_soc_register_card()` 注册声卡 [^4]: 电路板厂商需要填充 `struct snd_soc_dai_link` 结构 ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值