分享

snd_kcontrol的分析

 arm_embed 2012-11-23

用snd_kcontrol_new结构体了codec可供控制的部分,包括:通道切换(switch/mixer),音量调整(volume)。

下面根据具体代码分析一下snd_kcontrol_new的构建和使用。

  1. //snd_kcontrol_new的定义:  
  2. 在sound/soc/codec/twl4030.c中:  
  3. static const struct snd_kcontrol_new twl4030_snd_controls[] 中定义了此codec相关的控制接口;  
  4. 在驱动初始化的时候通过  
  5. snd_soc_add_controls(codec, twl4030_snd_controls,ARRAY_SIZE(twl4030_snd_controls));  
  6. |-->snd_soc_cnew()--->snd_ctl_new1()-->snd_ctl_new() //逐个取出snd_kcontrol_new的信息,构建一个snd_kcontrol元素  
  7. |-->int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol) /*将上面构建的snd_kcontrol添加到struct snd_card的成员[struct list_head controls]上,此时动态的设置kcontrol->id->numid,也就是我们使用alsa_amixer controls命令列出的numid*/  
  8. //结构体成员介绍:  
  9. struct snd_kcontrol_new {    
  10.     snd_ctl_elem_iface_t iface; /* interface identifier */    
  11.     unsigned int device;        /* device/client number */    
  12.     unsigned int subdevice;     /* subdevice (substream) number */    
  13.     unsigned char *name;        /* ASCII name of item */    
  14.     unsigned int index;     /* index of item */    
  15.     unsigned int access;        /* access rights */    
  16.     unsigned int count;     /* count of same elements */    
  17.     snd_kcontrol_info_t *info;    
  18.     snd_kcontrol_get_t *get;    
  19.     snd_kcontrol_put_t *put;    
  20.     union {    
  21.         snd_kcontrol_tlv_rw_t *c;    
  22.         const unsigned int *p;    
  23.     } tlv;    
  24.     unsigned long private_value;    
  25. };    
 

TLV:具体指的是什么? 是Tag,Length,Value(标记、长度、值)吗?

 

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

 

name字段是名称标识:name定义的标准是“SOURCE DIRECTION FUNCTION”即“源 方向 功能”:

SOURCE定义了control的源,如“Master”、“PCM”等;

DIRECTION 则为“Playback”、“Capture”等,如果DIRECTION忽略,意味着Playback和capture双向;

FUNCTION则可以是“Switch”、“Volume”和“Route”等。

【???】名字这样设置的原因??难道alsa_lib中做了名字的解析?

 

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均定义了一些宏(可能是因为寄存器的操作有一定的共性,抽象出来比较简炼),如:

  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) }    
  6. //这个宏的对象是MIXER,对寄存器reg的偏移shift,设置0-max的数值。SINGLE是对单个寄存器的操作。  
  7. //又如:  
  8. #define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) /    
  9. {   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),/    
  10.     .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |/    
  11.          SNDRV_CTL_ELEM_ACCESS_READWRITE,/    
  12.     .tlv.p = (tlv_array), /    
  13.     .info = snd_soc_info_volsw_2r, /    
  14.     .get = snd_soc_get_volsw_2r, .put = snd_soc_put_volsw_2r, /    
  15.     .private_value = (unsigned long)&(struct soc_mixer_control) /    
  16.         {.reg = reg_left, .rreg = reg_right, .shift = xshift, /    
  17.         .max = xmax, .invert = xinvert} }    
  18. //这个宏与刚才类似,DOUBLE是指对两个寄存器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)

 

以这个control为例,改变Playback Volume到100,使用一下命令:

[c-sharp] view plaincopy
  1. amixer cset numid=3,iface=MIXER,name='Playback Volume' 100  
 

通过上面给定的Volume寄存器地址及位偏移,以及音量值,填写寄存器。

 

从数据手册得知:Volume寄存器地址REG_VOL_L(左声道)和REG_VOL_R(右声道),音量bit filed的位偏移(shift)是0,DAC Digital Gain范围是0-192(steps).

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

 

 

到内核层时,会遍历一个节点类型为struct snd_kcontrol *的链表,找到kcontrol.id.numid与3相匹配的kctl(这个过程见snd_ctl_find_id()函数),然后调用

kctl.put()--snd_soc_put_volsw_2r用snd_soc_update_bits()->snd_soc_write()--->codec->write

将100写到Playback Volume寄存器中。

 

从上往下的大致流程:

amixer-用户层  

  |->snd_ctl_ioctl-系统调用  

       |->snd_ctl_elem_write_user-内核钩子函数  

            |->snd_ctl_elem_wirte-  

                 |->snd_ctl_find_id-遍历kcontrol链表找到与给定id相匹配的kctl  

                 |->kctl->put()-调用kctl的成员函数put()  

                      |->snd_soc_put_volsw_2r  

 

 

总结:

从上面可以看出,alsa drvier 到alsa_lib和alsa_utils,它用了很多心思,一大堆代码,希望使用者知道的尽量少,做的尽量少。

但结构的过于复杂和不直观,实际上会让使用者花费大量精力来理解它的思路。

据传google正在重做中间层的那些库,包括alsa_lib。

 

但alsa依然潇洒的活在linux内核里。这个世界如此的寂寞。别人无法理解,是因为它不懂欣赏。那就孤独的前行吧。

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多