图1 Mixer混音器
对于Mixer控件,我们可以认为是多个简单控件的组合,通常,我们会为mixer的每个输入端都单独定义一个简单控件来控制该路输入的开启和关闭,反应在代码上,就是定义一个soc_kcontrol_new数组: [cpp] view plaincopy
static const struct snd_kcontrol_new left_speaker_mixer[] = { SOC_SINGLE(\, WM8993_SPEAKER_MIXER, 7, 1, 0), SOC_SINGLE(\, WM8993_SPEAKER_MIXER, 5, 1, 0), SOC_SINGLE(\, WM8993_SPEAKER_MIXER, 3, 1, 0), SOC_SINGLE(\, WM8993_SPEAKER_MIXER, 6, 1, 0), };
以上这个mixer使用寄存器WM8993_SPEAKER_MIXER的第3,5,6,7位来分别控制4个输入端的开启和关闭。 Mux控件
mux控件与mixer控件类似,也是多个输入端和一个输出端的组合控件,与mixer控件不同的是,mux控件的多个输入端同时只能有一个被选中。因此,mux控件所对应的寄存器,通常可以设定一段连续的数值,每个不同的数值对应不同的输入端被打开,与上述的mixer控件不同,ASoc用soc_enum结构来描述mux控件的寄存器信息:
[cpp] view plaincopy
/* enumerated kcontrol */ struct soc_enum {
unsigned short reg; unsigned short reg2; unsigned char shift_l; unsigned char shift_r; unsigned int max; unsigned int mask;
const char * const *texts; const unsigned int *values; };
两个寄存器地址和位移字段:reg,reg2,shift_l,shift_r,用于描述左右声道的控制寄存器信息。字符串数组指针用于描述每个输入端对应的名字,value字段则指
6
向一个数组,该数组定义了寄存器可以选择的值,每个值对应一个输入端,如果value是一组连续的值,通常我们可以忽略values参数。下面我们先看看如何定义一个mux控件:
第一步,定义字符串和values数组,以下的例子因为values是连续的,所以不用定义:
[cpp] view plaincopy
static const char *drc_path_text[] = { \, \ };
第二步,利用ASoc提供的辅助宏定义soc_enum结构,用于描述寄存器: [cpp] view plaincopy
static const struct soc_enum drc_path =
SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 14, 2, drc_path_text);
第三步,利痛ASoc提供的辅助宏,定义soc_kcontrol_new结构,该结构最后用于注册该mux控件: [cpp] view plaincopy
static const struct snd_kcontrol_new wm8993_snd_controls[] = { SOC_DOUBLE_TLV(......), ......
SOC_ENUM(\, drc_path), ...... }
以上几步定义了一个叫DRC PATH的mux控件,该控件具有两个输入选择,分别是来自ADC和DAC,用寄存器WM8993_DRC_CONTROL_1控制。其中,soc_enum结构使用了辅助宏SOC_ENUM_SINGLE来定义,该宏的声明如下: [cpp] view plaincopy
#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \\
{ .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \\
.max = xmax, .texts = xtexts, \\
.mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0} #define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts) \\
SOC_ENUM_DOUBLE(xreg, xshift, xshift, xmax, xtexts) 定义soc_kcontrol_new结构时使用了SOC_ENUM,列出它的定义如下: [cpp] view plaincopy
#define SOC_ENUM(xname, xenum) \\
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\\ .info = snd_soc_info_enum_double, \\
.get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \\
7
.private_value = (unsigned long)&xenum }
思想如此统一,依然是使用private_value字段记录soc_enum结构,不过几个回调函数变了,我们看看get回调对应的snd_soc_get_enum_double函数: [cpp] view plaincopy
int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) {
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int val;
val = snd_soc_read(codec, e->reg); ucontrol->value.enumerated.item[0]
= (val >> e->shift_l) & e->mask; if (e->shift_l != e->shift_r)
ucontrol->value.enumerated.item[1] = (val >> e->shift_r) & e->mask;
return 0; }
通过info回调函数则可以获取某个输入端所对应的名字,其实就是从soc_enum结构的texts数组中获得: [cpp] view plaincopy
int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) {
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = e->shift_l == e->shift_r ? 1 : 2; uinfo->value.enumerated.items = e->max;
if (uinfo->value.enumerated.item > e->max - 1)
uinfo->value.enumerated.item = e->max - 1; strcpy(uinfo->value.enumerated.name,
e->texts[uinfo->value.enumerated.item]); return 0; }
以下是另外几个常用于定义mux控件的宏:
SOC_VALUE_ENUM_SINGLE 用于定义带values字段的soc_enum结构。
8
SOC_VALUE_ENUM_DOUBLE SOC_VALUE_ENUM_SINGLE的立体声版本。
SOC_VALUE_ENUM 用于定义带values字段snd_kcontrol_new结构,这个有点特别,我们还是看看它的定义: [cpp] view plaincopy
#define SOC_VALUE_ENUM(xname, xenum) \\
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\\ .info = snd_soc_info_enum_double, \\
.get = snd_soc_get_value_enum_double, \\ .put = snd_soc_put_value_enum_double, \\ .private_value = (unsigned long)&xenum } 从定义可以看出来,回调函数被换掉了,我们看看他的get回调: [cpp] view plaincopy
int snd_soc_get_value_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol) {
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
unsigned int reg_val, val, mux;
reg_val = snd_soc_read(codec, e->reg); val = (reg_val >> e->shift_l) & e->mask; for (mux = 0; mux < e->max; mux++) { if (val == e->values[mux]) break; }
ucontrol->value.enumerated.item[0] = mux; if (e->shift_l != e->shift_r) {
val = (reg_val >> e->shift_r) & e->mask; for (mux = 0; mux < e->max; mux++) { if (val == e->values[mux]) break; }
ucontrol->value.enumerated.item[1] = mux; }
return 0; }
与SOC_ENUM定义的mux不同,它没有直接返回寄存器的设定值,而是通过soc_enum结构中的values字段做了一次转换,与values数组中查找和寄存器相
9
等的值,然后返回他在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 1.ALSA声卡驱动中的DAPM详解之二:widget-具备路径和电源管理信息的kcontrol 上一篇文章中,我们介绍了音频驱动中对基本控制单元的封装:kcontrol。利用kcontrol,我们可以完成对音频系统中的mixer,mux,音量控制,音效控制,以及各种开关量的控制,通过对各种kcontrol的控制,使得音频硬件能够按照我们预想的结果进行工作。同时我们可以看到,kcontrol还是有以下几点不足: 只能描述自身,无法描述各个kcontrol之间的连接关系; 没有相应的电源管理机制;
没有相应的时间处理机制来响应播放、停止、上电、下电等音频事件;
为了防止pop-pop声,需要用户程序关注各个kcontrol上电和下电的顺序; 当一个音频路径不再有效时,不能自动关闭该路径上的所有的kcontrol;
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
10