1.ALSA声卡驱动中的DAPM详解之一:kcontrol
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结构,为了方便讨论,这里再次给出它的定义: [cpp] view plaincopy
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const unsigned char *name; /* ASCII name of item */
unsigned int index; /* index of item */ unsigned int access; /* access rights */ unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info; snd_kcontrol_get_t *get; snd_kcontrol_put_t *put; union {
1
snd_kcontrol_tlv_rw_t *c; const unsigned int *p; } tlv;
unsigned long private_value; };
回到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大小等等)。我们看看这个宏是如何定义的: [cpp] view plaincopy
#define SOC_SINGLE(xname, reg, shift, max, invert) \\
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \\ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\\ .put = snd_soc_put_volsw, \\
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
宏定义的参数分别是:xname(该控件的名字),reg(该控件对应的寄存器的地址),shift(控制位在寄存器中的位移),max(控件可设置的最大值),invert(设定值是否逻辑取反)。这里又使用了一个宏来定义private_value字段:SOC_SINGLE_VALUE,我们看看它的定义: [cpp] view plaincopy
#define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert) \\
((unsigned long)&(struct soc_mixer_control) \\
{.reg = xreg, .rreg = xreg, .shift = shift_left, \\
.rshift = shift_right, .max = xmax, .platform_max = xmax, \\
.invert = xinvert})
#define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert) \\
2
SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert) 这里实际上是定义了一个soc_mixer_control结构,然后把该结构的地址赋值给了private_value字段,soc_mixer_control结构是这样的: [cpp] view plaincopy /* mixer control */
struct soc_mixer_control {
int min, max, platform_max;
unsigned int reg, rreg, shift, rshift, invert; };
看来soc_mixer_control是控件特征的真正描述者,它确定了该控件对应寄存器的地址,位移值,最大值和是否逻辑取反等特性,控件的put回调函数和get回调函数需要借助该结构来访问实际的寄存器。我们看看这get回调函数的定义: [cpp] view plaincopy
int snd_soc_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) {
struct soc_mixer_control *mc =
(struct soc_mixer_control *)kcontrol->private_value;
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
unsigned int reg = mc->reg; unsigned int reg2 = mc->rreg; unsigned int shift = mc->shift; unsigned int rshift = mc->rshift; int max = mc->max;
unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert;
ucontrol->value.integer.value[0] =
(snd_soc_read(codec, reg) >> shift) & mask; if (invert)
ucontrol->value.integer.value[0] =
max - ucontrol->value.integer.value[0];
if (snd_soc_volsw_is_stereo(mc)) { if (reg == reg2)
ucontrol->value.integer.value[1] =
(snd_soc_read(codec, reg) >> rshift) & mask;
else
ucontrol->value.integer.value[1] =
(snd_soc_read(codec, reg2) >> shift) & mask;
3
if (invert)
ucontrol->value.integer.value[1] = max - ucontrol->value.integer.value[1]; }
return 0; }
上述代码一目了然,从private_value字段取出soc_mixer_control结构,利用该结构的信息,访问对应的寄存器,返回相应的值。
SOC_SINGLE_TLV SOC_SINGLE_TLV是SOC_SINGLE的一种扩展,主要用于定义那些有增益控制的控件,例如音量控制器,EQ均衡器等等。 [cpp] view plaincopy
#define SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \\
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \\ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\\ SNDRV_CTL_ELEM_ACCESS_READWRITE,\\ .tlv.p = (tlv_array), \\
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\\ .put = snd_soc_put_volsw, \\
.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值之间的映射关系,请看以下例子:
[cpp] view plaincopy
static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0);
static const struct snd_kcontrol_new wm1811_snd_controls[] = { SOC_SINGLE_TLV(\, WM8994_INPUT_MIXER_1, 7, 1, 0,
mixin_boost_tlv),
4
SOC_SINGLE_TLV(\, WM8994_INPUT_MIXER_1, 8, 1, 0,
mixin_boost_tlv), };
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位移值, [cpp] view plaincopy
#define SOC_DOUBLE(xname, reg, shift_left, shift_right, max, invert) \\
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\\ .info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \\ .put = snd_soc_put_volsw, \\
.private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \\
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控件用于音频通道的路由控制,由多个输入和一个输出组成,多个输入可以自由地混合在一起,形成混合后的输出:
5