DAPM之二:audio paths与dapm kcontrol
2011年04月20日 02:11:00 sepnic 阅读数:27066更多
个人分类: ALSA
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sepnic/article/details/6334922
在用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探究一致。