DAPM之二:audio paths與dapm kcontrol

在用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探究一致。

發佈了59 篇原創文章 · 獲贊 62 · 訪問量 64萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章