DAPM是Dynamic Audio Power Management的缩写,直译过来就是动态音频电源管理的意思,DAPM是为了使基于linux的移动设备上的音频子系统,在任何时候都工作在最小功耗状态下。DAPM对用户空间的应用程序来说是透明的,所有与电源相关的开关都在ASoc core中完成。用户空间的应用程序无需对代码做出修改,也无需重新编译,DAPM根据当前激活的音频流(playback/capture)和声卡中的mixer等的配置来决定那些音频控件的电源开关被打开或关闭。

/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

DAPM控件是由普通的soc音频控件演变而来的,所以本章的内容我们先从普通的soc音频控件开始。

snd_kcontrol_new结构


在正式讨论DAPM之前,我们需要先搞清楚ASoc中的一个重要的概念:kcontrol,不熟悉的读者需要浏览一下我之前的文章:Linux ALSA声卡驱动之四:Control设备的创建。通常,一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。 从上述文章中我们知道,定义一个kcontrol主要就是定义一个snd_kcontrol_new结构,为了方便讨论,这里再次给出它的定义:

  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. const 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. };

回到Linux ALSA声卡驱动之四:Control设备的创建中,我们知道,对于每个控件,我们需要定义一个和他对应的snd_kcontrol_new结构,这些snd_kcontrol_new结构会在声卡的初始化阶段,通过snd_soc_add_codec_controls函数注册到系统中,用户空间就可以通过amixer或alsamixer等工具查看和设定这些控件的状态。

snd_kcontrol_new结构中,几个主要的字段是get,put,private_value,get回调函数用于获取该控件当前的状态值,而put回调函数则用于设置控件的状态值,而private_value字段则根据不同的控件类型有不同的意义,比如对于普通的控件,private_value字段可以用来定义该控件所对应的寄存器的地址以及对应的控制位在寄存器中的位置信息。值得庆幸的是,ASoc系统已经为我们准备了大量的宏定义,用于定义常用的控件,这些宏定义位于include/sound/soc.h中。下面我们分别讨论一下如何用这些预设的宏定义来定义一些常用的控件。

简单型的控件


SOC_SINGLE    SOC_SINGLE应该算是最简单的控件了,这种控件只有一个控制量,比如一个开关,或者是一个数值变量(比如Codec中某个频率,FIFO大小等等)。我们看看这个宏是如何定义的:

  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) }

宏定义的参数分别是:xname(该控件的名字),reg(该控件对应的寄存器的地址),shift(控制位在寄存器中的位移),max(控件可设置的最大值),invert(设定值是否逻辑取反)。这里又使用了一个宏来定义private_value字段:SOC_SINGLE_VALUE,我们看看它的定义:

  1. #define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert) \
  2. ((unsigned long)&(struct soc_mixer_control) \
  3. {.reg = xreg, .rreg = xreg, .shift = shift_left, \
  4. .rshift = shift_right, .max = xmax, .platform_max = xmax, \
  5. .invert = xinvert})
  6. #define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) \
  7. SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert)

这里实际上是定义了一个soc_mixer_control结构,然后把该结构的地址赋值给了private_value字段,soc_mixer_control结构是这样的:

  1. /* mixer control */
  2. struct soc_mixer_control {
  3. int min, max, platform_max;
  4. unsigned int reg, rreg, shift, rshift, invert;
  5. };

看来soc_mixer_control是控件特征的真正描述者,它确定了该控件对应寄存器的地址,位移值,最大值和是否逻辑取反等特性,控件的put回调函数和get回调函数需要借助该结构来访问实际的寄存器。我们看看这get回调函数的定义:

  1. int snd_soc_get_volsw(struct snd_kcontrol *kcontrol,
  2. struct snd_ctl_elem_value *ucontrol)
  3. {
  4. struct soc_mixer_control *mc =
  5. (struct soc_mixer_control *)kcontrol->private_value;
  6. struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
  7. unsigned int reg = mc->reg;
  8. unsigned int reg2 = mc->rreg;
  9. unsigned int shift = mc->shift;
  10. unsigned int rshift = mc->rshift;
  11. int max = mc->max;
  12. unsigned int mask = (1 << fls(max)) - 1;
  13. unsigned int invert = mc->invert;
  14. ucontrol->value.integer.value[0] =
  15. (snd_soc_read(codec, reg) >> shift) & mask;
  16. if (invert)
  17. ucontrol->value.integer.value[0] =
  18. max - ucontrol->value.integer.value[0];
  19. if (snd_soc_volsw_is_stereo(mc)) {
  20. if (reg == reg2)
  21. ucontrol->value.integer.value[1] =
  22. (snd_soc_read(codec, reg) >> rshift) & mask;
  23. else
  24. ucontrol->value.integer.value[1] =
  25. (snd_soc_read(codec, reg2) >> shift) & mask;
  26. if (invert)
  27. ucontrol->value.integer.value[1] =
  28. max - ucontrol->value.integer.value[1];
  29. }
  30. return 0;
  31. }

上述代码一目了然,从private_value字段取出soc_mixer_control结构,利用该结构的信息,访问对应的寄存器,返回相应的值。

SOC_SINGLE_TLV    SOC_SINGLE_TLV是SOC_SINGLE的一种扩展,主要用于定义那些有增益控制的控件,例如音量控制器,EQ均衡器等等。

  1. #define SOC_SINGLE_TLV(xname, reg, shift, max, invert, 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, .get = snd_soc_get_volsw,\
  7. .put = snd_soc_put_volsw, \
  8. .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }

从他的定义可以看出,用于设定寄存器信息的private_value字段的定义和SOC_SINGLE是一样的,甚至put、get回调函数也是使用同一套,唯一不同的是增加了一个tlv_array参数,并把它赋值给了tlv.p字段。关于tlv,已经在Linux ALSA声卡驱动之四:Control设备的创建中进行了阐述。用户空间可以通过对声卡的control设备发起以下两种ioctl来访问tlv字段所指向的数组:

  • SNDRV_CTL_IOCTL_TLV_READ
  • SNDRV_CTL_IOCTL_TLV_WRITE
  • SNDRV_CTL_IOCTL_TLV_COMMAND
通常,tlv_array用来描述寄存器的设定值与它所代表的实际意义之间的映射关系,最常用的就是用于音量控件时,设定值与对应的dB值之间的映射关系,请看以下例子:
  1. static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0);
  2. static const struct snd_kcontrol_new wm1811_snd_controls[] = {
  3. SOC_SINGLE_TLV("MIXINL IN1LP Boost Volume", WM8994_INPUT_MIXER_1, 7, 1, 0,
  4. mixin_boost_tlv),
  5. SOC_SINGLE_TLV("MIXINL IN1RP Boost Volume", WM8994_INPUT_MIXER_1, 8, 1, 0,
  6. mixin_boost_tlv),
  7. };

DECLARE_TLV_DB_SCALE用于定义一个dB值映射的tlv_array,上述的例子表明,该tlv的类型是SNDRV_CTL_TLVT_DB_SCALE,寄存器的最小值对应是0dB,寄存器每增加一个单位值,对应的dB数增加是9dB(0.01dB*900),而由接下来的两组SOC_SINGLE_TLV定义可以看出,我们定义了两个boost控件,寄存器的地址都是WM8994_INPUT_MIXER_1,控制位分别是第7bit和第8bit,最大值是1,所以,该控件只能设定两个数值0和1,对应的dB值就是0dB和9dB。

SOC_DOUBLE    与SOC_SINGLE相对应,区别是SOC_SINGLE只控制一个变量,而SOC_DOUBLE则可以同时在一个寄存器中控制两个相似的变量,最常用的就是用于一些立体声的控件,我们需要同时对左右声道进行控制,因为多了一个声道,参数也就相应地多了一个shift位移值,

  1. #define SOC_DOUBLE(xname, reg, shift_left, shift_right, 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_DOUBLE_VALUE(reg, shift_left, shift_right, \
  6. max, invert) }

SOC_DOUBLE_R    与SOC_DOUBLE类似,对于左右声道的控制寄存器不一样的情况,使用SOC_DOUBLE_R来定义,参数中需要指定两个寄存器地址。

SOC_DOUBLE_TLV    与SOC_SINGLE_TLV对应的立体声版本,通常用于立体声音量控件的定义。

SOC_DOUBLE_R_TLV    左右声道有独立寄存器控制的SOC_DOUBLE_TLV版本

Mixer控件


Mixer控件用于音频通道的路由控制,由多个输入和一个输出组成,多个输入可以自由地混合在一起,形成混合后的输出:

图1     Mixer混音器

对于Mixer控件,我们可以认为是多个简单控件的组合,通常,我们会为mixer的每个输入端都单独定义一个简单控件来控制该路输入的开启和关闭,反应在代码上,就是定义一个soc_kcontrol_new数组:

  1. static const struct snd_kcontrol_new left_speaker_mixer[] = {
  2. SOC_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0),
  3. SOC_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0),
  4. SOC_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0),
  5. SOC_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0),
  6. };

以上这个mixer使用寄存器WM8993_SPEAKER_MIXER的第3,5,6,7位来分别控制4个输入端的开启和关闭。

Mux控件


mux控件与mixer控件类似,也是多个输入端和一个输出端的组合控件,与mixer控件不同的是,mux控件的多个输入端同时只能有一个被选中。因此,mux控件所对应的寄存器,通常可以设定一段连续的数值,每个不同的数值对应不同的输入端被打开,与上述的mixer控件不同,ASoc用soc_enum结构来描述mux控件的寄存器信息:

  1. /* enumerated kcontrol */
  2. struct soc_enum {
  3. unsigned short reg;
  4. unsigned short reg2;
  5. unsigned char shift_l;
  6. unsigned char shift_r;
  7. unsigned int max;
  8. unsigned int mask;
  9. const char * const *texts;
  10. const unsigned int *values;
  11. };

两个寄存器地址和位移字段:reg,reg2,shift_l,shift_r,用于描述左右声道的控制寄存器信息。字符串数组指针用于描述每个输入端对应的名字,value字段则指向一个数组,该数组定义了寄存器可以选择的值,每个值对应一个输入端,如果value是一组连续的值,通常我们可以忽略values参数。下面我们先看看如何定义一个mux控件:

第一步,定义字符串和values数组,以下的例子因为values是连续的,所以不用定义:

  1. static const char *drc_path_text[] = {
  2. "ADC",
  3. "DAC"
  4. };

第二步,利用ASoc提供的辅助宏定义soc_enum结构,用于描述寄存器:

  1. static const struct soc_enum drc_path =
  2. SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 14, 2, drc_path_text);

第三步,利痛ASoc提供的辅助宏,定义soc_kcontrol_new结构,该结构最后用于注册该mux控件:

  1. static const struct snd_kcontrol_new wm8993_snd_controls[] = {
  2. SOC_DOUBLE_TLV(......),
  3. ......
  4. SOC_ENUM("DRC Path", drc_path),
  5. ......
  6. }

以上几步定义了一个叫DRC PATH的mux控件,该控件具有两个输入选择,分别是来自ADC和DAC,用寄存器WM8993_DRC_CONTROL_1控制。其中,soc_enum结构使用了辅助宏SOC_ENUM_SINGLE来定义,该宏的声明如下:

  1. #define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \
  2. {       .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
  3. .max = xmax, .texts = xtexts, \
  4. .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0}
  5. #define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts) \
  6. SOC_ENUM_DOUBLE(xreg, xshift, xshift, xmax, xtexts)

定义soc_kcontrol_new结构时使用了SOC_ENUM,列出它的定义如下:

  1. #define SOC_ENUM(xname, xenum) \
  2. {       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
  3. .info = snd_soc_info_enum_double, \
  4. .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \
  5. .private_value = (unsigned long)&xenum }

思想如此统一,依然是使用private_value字段记录soc_enum结构,不过几个回调函数变了,我们看看get回调对应的snd_soc_get_enum_double函数:

  1. int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
  2. struct snd_ctl_elem_value *ucontrol)
  3. {
  4. struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
  5. struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
  6. unsigned int val;
  7. val = snd_soc_read(codec, e->reg);
  8. ucontrol->value.enumerated.item[0]
  9. = (val >> e->shift_l) & e->mask;
  10. if (e->shift_l != e->shift_r)
  11. ucontrol->value.enumerated.item[1] =
  12. (val >> e->shift_r) & e->mask;
  13. return 0;
  14. }

通过info回调函数则可以获取某个输入端所对应的名字,其实就是从soc_enum结构的texts数组中获得:

  1. int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
  2. struct snd_ctl_elem_info *uinfo)
  3. {
  4. struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
  5. uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
  6. uinfo->count = e->shift_l == e->shift_r ? 1 : 2;
  7. uinfo->value.enumerated.items = e->max;
  8. if (uinfo->value.enumerated.item > e->max - 1)
  9. uinfo->value.enumerated.item = e->max - 1;
  10. strcpy(uinfo->value.enumerated.name,
  11. e->texts[uinfo->value.enumerated.item]);
  12. return 0;
  13. }

以下是另外几个常用于定义mux控件的宏:

SOC_VALUE_ENUM_SINGLE    用于定义带values字段的soc_enum结构。

SOC_VALUE_ENUM_DOUBLE    SOC_VALUE_ENUM_SINGLE的立体声版本。

SOC_VALUE_ENUM    用于定义带values字段snd_kcontrol_new结构,这个有点特别,我们还是看看它的定义:

  1. #define SOC_VALUE_ENUM(xname, xenum) \
  2. {       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
  3. .info = snd_soc_info_enum_double, \
  4. .get = snd_soc_get_value_enum_double, \
  5. .put = snd_soc_put_value_enum_double, \
  6. .private_value = (unsigned long)&xenum }

从定义可以看出来,回调函数被换掉了,我们看看他的get回调:

  1. int snd_soc_get_value_enum_double(struct snd_kcontrol *kcontrol,
  2. struct snd_ctl_elem_value *ucontrol)
  3. {
  4. struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
  5. struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
  6. unsigned int reg_val, val, mux;
  7. reg_val = snd_soc_read(codec, e->reg);
  8. val = (reg_val >> e->shift_l) & e->mask;
  9. for (mux = 0; mux < e->max; mux++) {
  10. if (val == e->values[mux])
  11. break;
  12. }
  13. ucontrol->value.enumerated.item[0] = mux;
  14. if (e->shift_l != e->shift_r) {
  15. val = (reg_val >> e->shift_r) & e->mask;
  16. for (mux = 0; mux < e->max; mux++) {
  17. if (val == e->values[mux])
  18. break;
  19. }
  20. ucontrol->value.enumerated.item[1] = mux;
  21. }
  22. return 0;
  23. }

与SOC_ENUM定义的mux不同,它没有直接返回寄存器的设定值,而是通过soc_enum结构中的values字段做了一次转换,与values数组中查找和寄存器相等的值,然后返回他在values数组中的索引值,所以,尽管寄存器的值可能是不连续的,但返回的值是连续的。

通常,我们还可以用以下几个辅助宏定义soc_enum结构,其实和上面所说的没什么区别,只是可以偷一下懒,省掉struct soc_enum xxxx=几个单词而已:

  • SOC_ENUM_SINGLE_DECL
  • SOC_ENUM_DOUBLE_DECL
  • SOC_VALUE_ENUM_SINGLE_DECL  
  • SOC_VALUE_ENUM_DOUBLE_DECL

其它控件


其实,除了以上介绍的几种常用的控件,ASoc还为我们提供了另外一些控件定义辅助宏,详细的请读者参考include/sound/soc.h。这里列举几个:

需要自己定义get和put回调时,可以使用以下这些带EXT的版本:

  • SOC_SINGLE_EXT    
  • SOC_DOUBLE_EXT
  • SOC_SINGLE_EXT_TLV
  • SOC_DOUBLE_EXT_TLV
  • SOC_DOUBLE_R_EXT_TLV
  • SOC_ENUM_EXT

ALSA声卡驱动中的DAPM详解之一:kcontrol的更多相关文章

  1. ALSA声卡驱动中的DAPM详解之七:dapm事件机制(dapm event)

    前面的六篇文章,我们已经讨论了dapm关于动态电源管理的有关知识,包括widget的创建和初始化,widget之间的连接以及widget的上下电顺序等等.本章我们准备讨论dapm框架中的另一个机制:事 ...

  2. ALSA声卡驱动中的DAPM详解之六:精髓所在,牵一发而动全身

    设计dapm的主要目的之一,就是希望声卡上的各种部件的电源按需分配,需要的就上电,不需要的就下电,使得整个音频系统总是处于最小的耗电状态,最主要的就是,这一切对用户空间的应用程序是透明的,也就是说,用 ...

  3. ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

    前几篇文章我们从dapm的数据结构入手,了解了代表音频控件的widget,代表连接路径的route以及用于连接两个widget的path.之前都是一些概念的讲解以及对数据结构中各个字段的说明,从本章开 ...

  4. ALSA声卡驱动中的DAPM详解之三:如何定义各种widget

    上一节中,介绍了DAPM框架中几个重要的数据结构:snd_soc_dapm_widget,snd_soc_dapm_path,snd_soc_dapm_route.其中snd_soc_dapm_pat ...

  5. ALSA声卡驱动中的DAPM详解之五:建立widget之间的连接关系

    前面我们主要着重于codec.platform.machine驱动程序中如何使用和建立dapm所需要的widget,route,这些是音频驱动开发人员必须要了解的内容,经过前几章的介绍,我们应该知道如 ...

  6. ALSA声卡驱动中的DAPM详解之二:widget-具备路径和电源管理信息的kcontrol

    上一篇文章中,我们介绍了音频驱动中对基本控制单元的封装:kcontrol.利用kcontrol,我们可以完成对音频系统中的mixer,mux,音量控制,音效控制,以及各种开关量的控制,通过对各种kco ...

  7. Linux ALSA声卡驱动之八:ASoC架构中的Platform

    1.  Platform驱动在ASoC中的作用 前面几章内容已经说过,ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过C ...

  8. Linux驱动开发必看详解神秘内核(完全转载)

    Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html   IT168 技术文档]在开始步入L ...

  9. Maven依赖中的scope详解,在eclipse里面用maven install可以编程成功,到服务器上用命令执行报VM crash错误

    Maven依赖中的scope详解 项目中用了<scope>test</scope>在eclipse里面用maven install可以编译成功,到服务器上用命令执行报VM cr ...

随机推荐

  1. creator游戏开发基本语法

    写的比较杂乱,类似随笔,随时可能往里面添加修改给lable文本赋值: this.ScoreNumber.getComponent(cc.Label).string = GAME_DATE.MMscor ...

  2. JavaScript 中实现 sleep

    来自推特上 Windows 故障分析的笑话 图片来源:me.me 推上看到的笑话,Windows 故障分析的实现. 然后想起来 JavaScript 中如何实现这个 sleep() 函数让代码暂停指定 ...

  3. lombok无法解析log

    首先确认开发工具是否安装lombok,已安装的话打开lombok插件页,选择update, 然后重启idea.

  4. 巧用TWaver 3D 矢量图形功能

    的确,提起TWaver,大家想到的首先是“电信拓扑图组件”.其实,由于其灵活的MVC架构.矢量化设计.方便定制等特点,TWaver可以做的还有很多.例如房地产行业常见到的“户型图”. 户型推荐是销售接 ...

  5. CentOS7.x的DNS服务的基础配置

    一.bind服务器安装 bind:开源.稳定.应用广泛的DNS服务.bind的软件包名bind,服务名称named. 查看是否安装bind, 安装bind包: rpm -qa bind yum -y ...

  6. 简述Centos系统启动流程

    1. Centos5 POST开机自检 运行CMOS中的BIOS程序,加载第一个启动磁盘的Bootloader 由Bootloader读取kernel 通过挂载临时根目录initramfs加载核心模块 ...

  7. 常用的HTTP测试工具谷歌浏览器插件汇总

    网页的开发和测试时最常见的测试就是HTTP测试,作为曾经的测试人员在这方面还是略知一二的.其实做网页测试工作是非常繁琐的时期,有时候甚至是无聊重复的,如果没有网页测试工具的帮助的话,测试人员会越做越怀 ...

  8. Jmeter BeanShell PreProcessor使用笔记

    打印log log.info("content:" + content); 将字符串转化为JsonString import com.alibaba.fastjson.JSON; ...

  9. A - Restaurant

    UVA 1468 Description   Mr. Kim is planning to open a new restaurant. His city is laid out as a grid ...

  10. saltstack(二) master、minion常用配置选项

    master常用配置选项: interface: 指定bind的地址(默认0.) publish_port:指定发布端口(默认4505) ret_port: 指定结果返回端口,与minion配置文件的 ...