在用alsa_amixer controls时,除了我们之前提到的snd_soc_add_controls添加的kcontrols外,还有一些多出来的controls。其实多出来的那些都是属于dapm kcontrol,主要用于切换音频路径。
一、AUDIO PATHS OVERVIEW
以标准内核2.6.32的wm8900 codec为例。先看AUDIO PATHS OVERVIEW,红色线路是LINPUT1(Left Input) -> LEFT INPUT PGA -> LEFT INPUT MIXER -> LEFT OUTPUT MIXER -> LINEOUT1L,表示从LINPUT输入的信号通过这条路径送到LINEOUT输出,再具现一点,就是录音信号直接放到SPK播出。在这条路径上,有三个带+号的圆圈,那就是多路混合器mixer,用于切换输入源或将多个输入源混合输出。
土黄色部分为LEFT INPUT PGA:可选择LINPUT1、LINPUT2和LINPUT3;
绿色部分是LEFT INPUT MIXER:可选择INPUTPGA、LINPUT2、LINPUT3和AUX/LCOM;
蓝色部分是LEFT OUTPUT MIXER:可选择INPUTMIXER、LINPUT3、AUX/LCOM和LEFT DAC等。
配置声音通路时,主要是对mixer做切换输入源操作。如要实现Playback,则需要打通DAC -> OUTPUT MIXER -> LINEOUT通路。
二、配置声音通路
这里先绕开代码,先用alsa_amixer实际操作切换声音路径(以上图的红色路径为例),有个直观印象。
~ # alsa_amixer controls
numid=1,iface=MIXER,name='Mic Bias Level'
......省略......
numid=68,iface=MIXER,name='Left Input Mixer AUX Switch'
numid=69,iface=MIXER,name='Left Input Mixer Input PGA Switch'
numid=66,iface=MIXER,name='Left Input Mixer LINPUT2 Switch'
numid=67,iface=MIXER,name='Left Input Mixer LINPUT3 Switch'
numid=73,iface=MIXER,name='Left Input PGA LINPUT1 Switch'
numid=74,iface=MIXER,name='Left Input PGA LINPUT2 Switch'
numid=75,iface=MIXER,name='Left Input PGA LINPUT3 Switch'
numid=3,iface=MIXER,name='Left Input PGA Switch'
numid=2,iface=MIXER,name='Left Input PGA Volume'
numid=4,iface=MIXER,name='Left Input PGA ZC Switch'
numid=57,iface=MIXER,name='Left Output Mixer AUX Bypass Switch'
numid=60,iface=MIXER,name='Left Output Mixer DACL Switch'
numid=56,iface=MIXER,name='Left Output Mixer LINPUT3 Bypass Switch'
numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch'
numid=59,iface=MIXER,name='Left Output Mixer Right Input Mixer Switch'
......省略......
~ #
以上打印出所有的codec kcontrols,以之前对应的颜色区分了INPUT PGA、INPUT MIXER、OUTPUT MIXER三个mixer的路径选择。要打开红色通路,则进行如下操作:
- alsa_amixer cset numid=3,iface=MIXER,name='Left Input PGA Switch' 1
- alsa_amixer cset numid=73,iface=MIXER,name='Left Input PGA LINPUT1 Switch' 1
- alsa_amixer cset numid=69,iface=MIXER,name='Left Input Mixer Input PGA Switch' 1
- alsa_amixer cset numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch' 1
- alsa_amixer cset numid=43,iface=MIXER,name='LINEOUT1 Switch' 1
numid3 : 使能Left Input PGA;
numid73: 令Left Input PGA选择LINPUT1输入源;
numid69: 令Left Input Mixer选择Left Input PGA输入源;
numid58: 令Left Output Mixer选择Left Input Mixer输入源;
numid43: 使能LINEOUT1。
【这里仅以最基本的alsa-util来配置回路,在实际应用中,可自己实现alsa mixer或编写asound.conf虚拟出不同的devices,前者较典型见Android2.2的ALSAControl.cpp,后者在之后的章节会提一下。我偏向于asound.conf实现,灵活性较高,不同的codec改动asound.conf就行了。】
三、AUDIO PATHS代码实现
知道声音通路如何配置后,回到驱动代码实现。音频路径功能与普通的snd_kcontrol差不多,但写法稍微复杂一点,以'Left Output Mixer Left Input Mixer Switch'为例说明。
1、实现Left Output Mixer的输入源选择:
- static const struct snd_kcontrol_new wm8900_loutmix_controls[] = {
- SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),
- SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),
- SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),
- SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),
- SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),
- };
2、添加名为Left Output Mixer的widget:
- static const struct snd_soc_dapm_widget wm8900_dapm_widgets[] = {
- /* Externally visible pins */
- ......省略......
- /* Input */
- ......省略......
- SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0,
- wm8900_linmix_controls,
- ARRAY_SIZE(wm8900_linmix_controls)),
- ......省略......
- /* Output */
- ......省略......
- SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,
- wm8900_loutmix_controls,
- ARRAY_SIZE(wm8900_loutmix_controls)),
- ......省略......
- };
【MIXER:多个输入源混合成一个输出,用SND_SOC_DAPM_MIXER定义这个widget,类型为snd_soc_dapm_mixer;
MUX:多路选择器,多路输入,但只能选择一路作为输出,用SND_SOC_DAPM_MUX定义这个widget,类型为snd_soc_dapm_mux;
PGA:单路输入,单路输出,带gain调整的部件,用SND_SOC_DAPM_PGA定义这个widget,类型为snd_soc_dapm_pga。】
3、搭建音频路径:
- static const struct snd_soc_dapm_route audio_map[] = {
- /* Inputs */
- ......省略......
- /* Outputs */
- ......省略......
- {"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"},
- ......省略......
- };
前面的"Left Output Mixer"表示操作目的对象sink,对应widgets中的名为Left Output Mixer的widget;
中间的"Left Input Mixer Switch"表示操作行为control,对应wm8900_loutmix_controls中的名为Left Input Mixer Switch的kcontrol;
后面的"Left Input Mixer"表示操作源对象source,对应widgets中的名为Left Input Mixer的widget。
合起来的意思:通过动作"Left Input Mixer Switch"将输入源"Left Input Mixer"送到目的混合器"Left Output Mixer"输出。
这里我的解析有点拗口,直接看dapm.txt更清晰一点:
- e.g., from the WM8731 output mixer (wm8731.c)
- The WM8731 output mixer has 3 inputs (sources)
- 1. Line Bypass Input
- 2. DAC (HiFi playback)
- 3. Mic Sidetone Input
- Each input in this example has a kcontrol associated with it (defined in example
- above) and is connected to the output mixer via it's kcontrol name. We can now
- connect the destination widget (wrt audio signal) with it's source widgets.
- /* output mixer */
- {"Output Mixer", "Line Bypass Switch", "Line Input"},
- {"Output Mixer", "HiFi Playback Switch", "DAC"},
- {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"},
- So we have :-
- Destination Widget <=== Path Name <=== Source Widget
- Or:-
- Sink, Path, Source
- Or :-
- "Output Mixer" is connected to the "DAC" via the "HiFi Playback Switch".
- When there is no path name connecting widgets (e.g. a direct connection) we
- pass NULL for the path name.
注意:dapm kcontrol名称 = 目的对象sink名称 + 操作行为control名称,即'Left Output Mixer Left Input Mixer Switch',操作源对象source名称被忽略。之后深入源码分析。
4、将kcontrols、widgets和route串联起来:
- static int wm8900_add_widgets(struct snd_soc_codec *codec)
- {
- snd_soc_dapm_new_controls(codec, wm8900_dapm_widgets,
- ARRAY_SIZE(wm8900_dapm_widgets));
- snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
- snd_soc_dapm_new_widgets(codec);
- return 0;
- }
四、dapm kcontrol
好了,如果仅仅是为了实现audio paths的话,了解以上应该是足够的了。但人生的追求不应该那么简单,我们要从更根源处剖析问题,以后再审视相似的问题时,站的高度也不同。
snd_soc_dapm_route的定义:
- /*
- * DAPM audio route definition.
- *
- * Defines an audio route originating at source via control and finishing
- * at sink.
- */
- struct snd_soc_dapm_route {
- const char *sink;
- const char *control;
- const char *source;
- };
sink为目的对象名称,control为操作行为名称,source为源对象名称。
为方便起见,先定义一些名词:source为输入源,在widgets中定义,如"Left Input Mixer";sources为输入源选择,如wm8900_loutmix_controls;control为操作行为,具体在sources数组中定义,如"Left Input Mixer Switch";sink为目的混合器,在widgets中定义,如"Left Output Mixer";route为音频路径,连接source、control和sink,如{"Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer"}。
1、snd_soc_dapm_new_controls()函数进行widget内存分配、链表初始化工作,比较简单,按下不表。
2、snd_soc_dapm_add_routes(),该函数的注释值得一看:snd_soc_dapm_add_routes - Add routes between DAPM widgets. Connects 2 dapm widgets together via a named audio path. The sink is the widget receiving the audio signal, whilst the source is the sender of the audio signal.
以MIXER类型snd_soc_dapm_mixer为例,简单介绍调用过程:
- snd_soc_dapm_add_routes
- -->snd_soc_dapm_add_route [添加一个snd_soc_dapm_mixer类型的route]
- -->分配snd_soc_dapm_path内存,初始化这个path,令path->sink=sink,path->source=source
- dapm_connect_mixer
- -->检查操作行为control是否存在:从sources中找到name匹配的control
- path->name = control.name;接着将这个path的sink、source和list插入到各自的链表中
- (注:path三大要素中的两个都已经得到即sink和source,差kcontrol。)
- dapm_set_path_status
- -->判定指定source是否被选择了,是则置p->connect = 1;否则置p->connect = 0
以上的核心部分是path的建立和链表插入操作,如果在sink->kcontrols中找不到name相匹配的kcontrol,则这个route无效,不创建path。这里path->name为找到的sink->kcontrol.name。
【其实dapm_set_path_status挺困惑的,path的connect理应包含两个操作:1是指定source的选择,2是sink本身的使能。但这里connect状态仅仅是检查source是否选择而已,不检查指定sink是否使能,看起来不太合理。后面会解决这个疑问。这里我理解可能还有些偏差或有遗漏的地方。】
3、snd_soc_dapm_new_widgets()
- snd_soc_dapm_new_widgets
- -->遍历dapm_widgets链表,找到为switch、mixer类型的widget
- w->power_check = dapm_generic_check_power;
- dapm_new_mixer
- -->/* add dapm control with long name.
- * for dapm_mixer this is the concatenation of the
- * mixer and kcontrol name.
- * for dapm_mixer_named_ctl this is simply the
- * kcontrol name.
- */
- snd_soc_dapm_mixer dapm kcontrol:path->long_name = sink->name + kcontrol->name
- snd_soc_dapm_mixer_named_ctl dapm kcontrol:path->long_name = kcontrol->name
- snd_soc_cnew为path分配一个kcontrol,置这个kcontrol的name为path->long_name
- snd_ctl_add添加这个dapm kcontrol
- dapm_power_widgets
- -->Scan each dapm widget for complete audio path.A complete path is a route that has valid endpoints i.e.:-
经过snd_soc_dapm_new_widgets(),终于为snd_soc_dapm_mixer类型的widget建立用于route切换的dapm kcontrol,使得alsa_amixer可以通过控制这些dapm kcontrol来达到音频通路切换的目的。
注:SND_SOC_DAPM_MIXER和SND_SOC_DAPM_MIXER_NAMED_CTL建立的widget仅体现在dapm kcontrol的名字上面,前者为sink->name + kcontrol->name,后者简单的为kcontrol->name。
4、snd_soc_dapm_path[补充]
- /* dapm audio path between two widgets */
- struct snd_soc_dapm_path {
- char *name;
- char *long_name;
- /* source (input) and sink (output) widgets */
- struct snd_soc_dapm_widget *source;
- struct snd_soc_dapm_widget *sink;
- struct snd_kcontrol *kcontrol;
- /* status */
- u32 connect:1; /* source and sink widgets are connected */
- u32 walked:1; /* path has been walked */
- struct list_head list_source;
- struct list_head list_sink;
- struct list_head list;
- };
以上的过程分析非常简略,其实一切都是围绕path展开的。可以把重点放在path的分析上面,搞懂path数据,基本就能理解这个dapm kcontrol的一切了。总的来说:
path->name = sink->kcontrol[i].name,上例是"Left Input Mixer Switch";
path->long_name = sink->name + sink->kcontrol[i].name,上例是"Left Output Mixer Left Input Mixer Switch";
path->source = source,上例是名为"Left Input Mixer"的widget;
path->sink = sink,上例是名为"Left Output Mixer"的widget;
path->connect:通道connect状态,根据sink->kcontrol[i]判断。
path->kcontrol = snd_soc_cnew(&w->kcontrols[i], w, path->long_name);可见path->kcontrol对应sink->kcontrol[i],但名为path->long_name。因此上层可以通过path->long_name找到对应的sink->kcontrol[i]。
五、如何触发path connect
dapm kcontrol的触发很大程度上可以参考snd_kcontrol探究,但更为复杂。当上层触发dapm kcontrol时,会做两个重要动作:1是切换音频通路,这与普通的kcontrol做法基本一致;2是使能dapm widget(power up/down),这就是分歧之处。
回到<三、AUDIO PATHS代码实现>复习一下"Left Input Mixer Switch"的kcontrol写法:SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0)。
1、SOC_DAPM_SINGLE宏定义:
- /* dapm kcontrol types */
- #define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) /
- { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, /
- .info = snd_soc_info_volsw, /
- .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, /
- .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
SOC_SINGLE宏定义:
- #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) }
从这里就可以看出,dapm kcontrol跟普通的kcontrol不同之处,以put为例:
dapm kcontrol:.put = snd_soc_dapm_put_volsw
kcontrol:.put = snd_soc_put_volsw
2、snd_soc_dapm_put_volsw
- snd_soc_dapm_put_volsw
- -->dapm_mixer_update_power
- -->snd_kcontrol_chip
- -->找到dapm kcontrol所在的widget(也就是操作目的对象sink)
- -->snd_soc_test_bits
- -->Tests a register with a new value and checks if the new value is different from the old value.
- dapm_power_widgets
- -->power up/down对象widget,更底层可追溯到dapm_seq_run_coalesced
- 检查是否有widget->event [这里不分析Event的情况,继续往下走]
- snd_soc_update_bits
- -->根据dapm kcontrol:SOC_DAPM_SINGLE定义的reg、shift和max设置音频通路,方法与普通的kcontrol一样
这是底层方法差异,往上应该没必要说了,与snd_kcontrol探究一致。