DAPM之二:audio paths與dapm kcontrol
一、AUDIO PATHS OVERVIEW
以標準核心2.6.32的wm8900 codec為例。先看AUDIO PATHS OVERVIEW,紅色線路是LINPUT1(Left Input) ->LEFTINPUT PGA ->LEFTINPUT MIXER -> LEFTOUTPUT MIXER -> LINEOUT1L,表示從LINPUT輸入的訊號通過這條路徑送到LINEOUT輸出,再具現一點,就是錄音訊號直接放到SPK播出。在這條路徑上,有三個帶+號的圓圈,那就是多路混合器mixer,用於切換輸入源或將多個輸入源混合輸出。
土黃色部分為LEFT INPUT PGA
綠色部分是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=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的輸入源選擇:
- staticconststruct 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:
- staticconststruct 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、搭建音訊路徑:
- staticconststruct 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 inthis 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串聯起來:
- staticint 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 {
- constchar *sink;
- constchar *control;
- constchar *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) /