1. 程式人生 > >DAPM之二:audio paths與dapm kcontrol

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

:可選擇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的路徑選擇。要開啟紅色通路,則進行如下操作:

  1. alsa_amixer cset numid=3,iface=MIXER,name='Left Input PGA Switch' 1  
  2. alsa_amixer cset numid=73,iface=MIXER,name='Left Input PGA LINPUT1 Switch' 1  
  3. alsa_amixer cset numid=69,iface=MIXER,name='Left Input Mixer Input PGA Switch' 1  
  4. alsa_amixer cset numid=58,iface=MIXER,name='Left Output Mixer Left Input Mixer Switch' 1  
  5. 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的輸入源選擇:

  1. staticconststruct snd_kcontrol_new wm8900_loutmix_controls[] = {  
  2. SOC_DAPM_SINGLE("LINPUT3 Bypass Switch", WM8900_REG_LOUTMIXCTL1, 7, 1, 0),  
  3. SOC_DAPM_SINGLE("AUX Bypass Switch", WM8900_REG_AUXOUT_CTL, 7, 1, 0),  
  4. SOC_DAPM_SINGLE("Left Input Mixer Switch", WM8900_REG_BYPASS1, 7, 1, 0),  
  5. SOC_DAPM_SINGLE("Right Input Mixer Switch", WM8900_REG_BYPASS2, 3, 1, 0),  
  6. SOC_DAPM_SINGLE("DACL Switch", WM8900_REG_LOUTMIXCTL1, 8, 1, 0),  
  7. };  

2、新增名為Left Output Mixer的widget:

  1. staticconststruct snd_soc_dapm_widget wm8900_dapm_widgets[] = {  
  2. /* Externally visible pins */
  3. ......省略......  
  4. /* Input */
  5. ......省略......  
  6. SND_SOC_DAPM_MIXER("Left Input Mixer", WM8900_REG_POWER2, 5, 0,  
  7.            wm8900_linmix_controls,  
  8.            ARRAY_SIZE(wm8900_linmix_controls)),  
  9. ......省略......  
  10. /* Output */
  11. ......省略......  
  12. SND_SOC_DAPM_MIXER("Left Output Mixer", WM8900_REG_POWER3, 3, 0,  
  13.            wm8900_loutmix_controls,  
  14.            ARRAY_SIZE(wm8900_loutmix_controls)),  
  15. ......省略......  
  16. };  

【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、搭建音訊路徑:

  1. staticconststruct snd_soc_dapm_route audio_map[] = {  
  2. /* Inputs */
  3. ......省略......  
  4. /* Outputs */
  5. ......省略......  
  6. {"Left Output Mixer""Left Input Mixer Switch""Left Input Mixer"},  
  7. ......省略......  
  8. };  

前面的"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更清晰一點:

  1. e.g., from the WM8731 output mixer (wm8731.c)  
  2. The WM8731 output mixer has 3 inputs (sources)  
  3.  1. Line Bypass Input  
  4.  2. DAC (HiFi playback)  
  5.  3. Mic Sidetone Input  
  6. Each input inthis example has a kcontrol associated with it (defined in example  
  7. above) and is connected to the output mixer via it's kcontrol name. We can now  
  8. connect the destination widget (wrt audio signal) with it's source widgets.  
  9.     /* output mixer */
  10.     {"Output Mixer""Line Bypass Switch""Line Input"},  
  11.     {"Output Mixer""HiFi Playback Switch""DAC"},  
  12.     {"Output Mixer""Mic Sidetone Switch""Mic Bias"},  
  13. So we have :-  
  14.     Destination Widget  <=== Path Name <=== Source Widget  
  15. Or:-  
  16.     Sink, Path, Source  
  17. Or :-  
  18.     "Output Mixer"is connected to the "DAC" via the "HiFi Playback Switch".  
  19. When there is no path name connecting widgets (e.g. a direct connection) we  
  20. pass NULL for the path name.  

注意:dapm kcontrol名稱 = 目的物件sink名稱 + 操作行為control名稱,即'Left Output Mixer Left Input Mixer Switch',操作源物件source名稱被忽略。之後深入原始碼分析。

4、將kcontrols、widgets和route串聯起來:

  1. staticint wm8900_add_widgets(struct snd_soc_codec *codec)  
  2. {  
  3.     snd_soc_dapm_new_controls(codec, wm8900_dapm_widgets,  
  4.                   ARRAY_SIZE(wm8900_dapm_widgets));  
  5.     snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));  
  6.     snd_soc_dapm_new_widgets(codec);  
  7.     return 0;  
  8. }  

四、dapm kcontrol

好了,如果僅僅是為了實現audio paths的話,瞭解以上應該是足夠的了。但人生的追求不應該那麼簡單,我們要從更根源處剖析問題,以後再審視相似的問題時,站的高度也不同。

snd_soc_dapm_route的定義:

  1. /* 
  2.  * DAPM audio route definition. 
  3.  * 
  4.  * Defines an audio route originating at source via control and finishing 
  5.  * at sink. 
  6.  */
  7. struct snd_soc_dapm_route {  
  8.     constchar *sink;  
  9.     constchar *control;  
  10.     constchar *source;  
  11. };  

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為例,簡單介紹呼叫過程:

  1. snd_soc_dapm_add_routes  
  2.   -->snd_soc_dapm_add_route [新增一個snd_soc_dapm_mixer型別的route]  
  3.        -->分配snd_soc_dapm_path記憶體,初始化這個path,令path->sink=sink,path->source=source  
  4.           dapm_connect_mixer  
  5.             -->檢查操作行為control是否存在:從sources中找到name匹配的control  
  6.                path->name = control.name;接著將這個path的sink、source和list插入到各自的連結串列中  
  7.                (注:path三大要素中的兩個都已經得到即sink和source,差kcontrol。)  
  8.                dapm_set_path_status  
  9.                  -->判定指定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()

  1. snd_soc_dapm_new_widgets  
  2.   -->遍歷dapm_widgets連結串列,找到為switch、mixer型別的widget  
  3.      w->power_check = dapm_generic_check_power;  
  4.      dapm_new_mixer  
  5.        -->/* add dapm control with long name. 
  6.            * for dapm_mixer this is the concatenation of the 
  7.            * mixer and kcontrol name. 
  8.            * for dapm_mixer_named_ctl this is simply the 
  9.            * kcontrol name. 
  10.            */
  11.           snd_soc_dapm_mixer dapm kcontrol:path->long_name = sink->name + kcontrol->name  
  12.           snd_soc_dapm_mixer_named_ctl dapm kcontrol:path->long_name = kcontrol->name  
  13.           snd_soc_cnew為path分配一個kcontrol,置這個kcontrol的name為path->long_name  
  14.           snd_ctl_add新增這個dapm kcontrol  
  15.           dapm_power_widgets  
  16.             -->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[補充]

  1. /* dapm audio path between two widgets */
  2. struct snd_soc_dapm_path {  
  3.     char *name;  
  4.     char *long_name;  
  5.     /* source (input) and sink (output) widgets */
  6.     struct snd_soc_dapm_widget *source;  
  7.     struct snd_soc_dapm_widget *sink;  
  8.     struct snd_kcontrol *kcontrol;  
  9.     /* status */
  10.     u32 connect:1;  /* source and sink widgets are connected */
  11.     u32 walked:1;   /* path has been walked */
  12.     struct list_head list_source;  
  13.     struct list_head list_sink;  
  14.     struct list_head list;  
  15. };  

以上的過程分析非常簡略,其實一切都是圍繞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巨集定義:

  1. /* dapm kcontrol types */
  2. #define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) /
  3. {   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, /  
  4.     .info = snd_soc_info_volsw, /  
  5.     .get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, /  
  6.     .private_value =  SOC_SINGLE_VALUE(reg, shift, max, invert) }  

SOC_SINGLE巨集定義:

  1. #define SOC_SINGLE(xname, reg, shift, max, invert) /