1. 程式人生 > >Linux ALSA音效卡驅動之四:Control裝置的建立

Linux ALSA音效卡驅動之四:Control裝置的建立

Control介面

Control介面主要讓使用者空間的應用程式(alsa-lib)可以訪問和控制音訊codec晶片中的多路開關,滑動控制元件等。對於Mixer(混音)來說,Control介面顯得尤為重要,從ALSA 0.9.x版本開始,所有的mixer工作都是通過control介面的API來實現的。

ALSA已經為AC97定義了完整的控制介面模型,如果你的Codec晶片只支援AC97介面,你可以不用關心本節的內容。

<sound/control.h>定義了所有的Control API。如果你要為你的codec實現自己的controls,請在程式碼中包含該標頭檔案。

Controls的定義

要自定義一個Control,我們首先要定義3各回調函式:info,get和put。然後,定義一個snd_kcontrol_new結構:

[c-sharp] view plain copy print?
  1. staticstruct snd_kcontrol_new my_control __devinitdata = { 
  2.     .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 
  3.     .name = "PCM Playback Switch"
  4.     .index = 0, 
  5.     .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, 
  6.     .private_value = 0xffff, 
  7.     .info = my_control_info, 
  8.     .get = my_control_get, 
  9.     .put = my_control_put 
  10. }; 

iface欄位指出了control的型別,alsa定義了幾種型別(SNDDRV_CTL_ELEM_IFACE_XXX),常用的型別是MIXER,當然也可以定義屬於全域性的CARD型別,也可以定義屬於某類裝置的型別,例如HWDEP,PCMRAWMIDI,TIMER等,這時需要在device和subdevice欄位中指出卡的裝置邏輯編號。

name欄位是該control的名字,從ALSA 0.9.x開始,control的名字是變得比較重要,因為control的作用是按名字來歸類的。ALSA已經預定義了一些control的名字,我們再Control Name一節詳細討論。

index欄位用於儲存該control的在該卡中的編號。如果音效卡中有不止一個codec,每個codec中有相同名字的control,這時我們可以通過index來區分這些controls。當index為0時,則可以忽略這種區分策略。

access欄位包含了該control的訪問型別。每一個bit代表一種訪問型別,這些訪問型別可以多個“或”運算組合在一起。

private_value欄位包含了一個任意的長整數型別值。該值可以通過info,get,put這幾個回撥函式訪問。你可以自己決定如何使用該欄位,例如可以把它拆分成多個位域,又或者是一個指標,指向某一個數據結構。

tlv欄位為該control提供元資料。

Control的名字

control的名字需要遵循一些標準,通常可以分成3部分來定義control的名字:源--方向--功能。

  • 源,可以理解為該control的輸入端,alsa已經預定義了一些常用的源,例如:Master,PCM,CD,Line等等。 
  • 方向,代表該control的資料流向,例如:Playback,Capture,Bypass,Bypass Capture等等,也可以不定義方向,這時表示該Control是雙向的(playback和capture)。
  • 功能,根據control的功能,可以是以下字串:Switch,Volume,Route等等。

也有一些命名上的特例:

  • 全域性的capture和playback    "Capture Source","Capture Volume","Capture Switch",它們用於全域性的capture source,switch和volume。同理,"Playback Volume","Playback Switch",它們用於全域性的輸出switch和volume。
  • Tone-controles    音調控制的開關和音量命名為:Tone Control - XXX,例如,"Tone Control - Switch","Tone Control - Bass","Tone Control - Center"。
  • 3D controls    3D控制元件的命名規則:,"3D Control - Switch","3D Control - Center","3D Control - Space"。
  • Mic boost    麥克風音量加強控制元件命名為:"Mic Boost"或"Mic Boost(6dB)"。

訪問標誌(ACCESS Flags)

Access欄位是一個bitmask,它儲存了改control的訪問型別。預設的訪問型別是:SNDDRV_CTL_ELEM_ACCESS_READWRITE,表明該control支援讀和寫操作。如果access欄位沒有定義(.access==0),此時也認為是READWRITE型別。

如果是一個只讀control,access應該設定為:SNDDRV_CTL_ELEM_ACCESS_READ,這時,我們不必定義put回撥函式。類似地,如果是隻寫control,access應該設定為:SNDDRV_CTL_ELEM_ACCESS_WRITE,這時,我們不必定義get回撥函式。

如果control的值會頻繁地改變(例如:電平表),我們可以使用VOLATILE型別,這意味著該control會在沒有通知的情況下改變,應用程式應該定時地查詢該control的值。

回撥函式

info回撥函式

info回撥函式用於獲取control的詳細資訊。它的主要工作就是填充通過引數傳入的snd_ctl_elem_info物件,以下例子是一個具有單個元素的boolean型control的info回撥:

[c-sharp] view plain copy print?
  1. staticint snd_myctl_mono_info(struct snd_kcontrol *kcontrol, 
  2.     struct snd_ctl_elem_info *uinfo) 
  3.     uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; 
  4.     uinfo->count = 1; 
  5.     uinfo->value.integer.min = 0; 
  6.     uinfo->value.integer.max = 1; 
  7.     return 0; 

type欄位指出該control的值型別,值型別可以是BOOLEAN, INTEGER, ENUMERATED, BYTES,IEC958和INTEGER64之一。count欄位指出了改control中包含有多少個元素單元,比如,立體聲的音量control左右兩個聲道的音量值,它的count欄位等於2。value欄位是一個聯合體(union),value的內容和control的型別有關。其中,boolean和integer型別是相同的。

ENUMERATED型別有些特殊。它的value需要設定一個字串和字串的索引,請看以下例子:

[c-sharp] view plain copy print?
  1. staticint snd_myctl_enum_info(struct snd_kcontrol *kcontrol, 
  2. struct snd_ctl_elem_info *uinfo) 
  3.     staticchar *texts[4] = { 
  4.         "First", "Second", "Third", "Fourth"
  5.     }; 
  6.     uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; 
  7.     uinfo->count = 1; 
  8.     uinfo->value.enumerated.items = 4; 
  9.     if (uinfo->value.enumerated.item > 3) 
  10.         uinfo->value.enumerated.item = 3; 
  11.     strcpy(uinfo->value.enumerated.name, 
  12.         texts[uinfo->value.enumerated.item]); 
  13.     return 0; 

alsa已經為我們實現了一些通用的info回撥函式,例如:snd_ctl_boolean_mono_info(),snd_ctl_boolean_stereo_info()等等。

get回撥函式

該回調函式用於讀取control的當前值,並返回給使用者空間的應用程式。

[c-sharp] view plain copy print?
  1. staticint snd_myctl_get(struct snd_kcontrol *kcontrol, 
  2.     struct snd_ctl_elem_value *ucontrol) 
  3.     struct mychip *chip = snd_kcontrol_chip(kcontrol); 
  4.     ucontrol->value.integer.value[0] = get_some_value(chip); 
  5.     return 0; 

value欄位的賦值依賴於control的型別(如同info回撥)。很多音效卡的驅動利用它儲存硬體暫存器的地址、bit-shift和bit-mask,這時,private_value欄位可以按以下例子進行設定:

.private_value = reg | (shift << 16) | (mask << 24);

然後,get回撥函式可以這樣實現:

static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)

{
    int reg = kcontrol->private_value & 0xff;
    int shift = (kcontrol->private_value >> 16) & 0xff;
    int mask = (kcontrol->private_value >> 24) & 0xff;
    ....

    //根據以上的值讀取相應暫存器的值並填入value中
}

如果control的count欄位大於1,表示control有多個元素單元,get回撥函式也應該為value填充多個數值。

put回撥函式

put回撥函式用於把應用程式的控制值設定到control中。

[c-sharp] view plain copy print?
  1. staticint snd_myctl_put(struct snd_kcontrol *kcontrol, 
  2.     struct snd_ctl_elem_value *ucontrol) 
  3.     struct mychip *chip = snd_kcontrol_chip(kcontrol); 
  4.     int changed = 0; 
  5.     if (chip->current_value != 
  6.         ucontrol->value.integer.value[0]) { 
  7.         change_current_value(chip, 
  8.         ucontrol->value.integer.value[0]); 
  9.         changed = 1; 
  10.     } 
  11.     return changed; 

如上述例子所示,當control的值被改變時,put回撥必須要返回1,如果值沒有被改變,則返回0。如果發生了錯誤,則返回一個負數的錯誤號。

和get回撥一樣,當control的count大於1時,put回撥也要處理多個control中的元素值。

建立Controls

當把以上討論的內容都準備好了以後,我們就可以建立我們自己的control了。alsa-driver為我們提供了兩個用於建立control的API:

  • snd_ctl_new1()
  • snd_ctl_add()

我們可以用以下最簡單的方式建立control:

[c-sharp] view plain copy print?
  1. err = snd_ctl_add(card, snd_ctl_new1(&my_control, chip)); 
  2. if (err < 0) 
  3.     return err; 

在這裡,my_control是一個之前定義好的snd_kcontrol_new物件,chip物件將會被賦值在kcontrol->private_data欄位,該欄位可以在回撥函式中訪問。

snd_ctl_new1()會分配一個新的snd_kcontrol例項,並把my_control中相應的值複製到該例項中,所以,在定義my_control時,通常我們可以加上__devinitdata字首。snd_ctl_add則把該control繫結到音效卡物件card當中。

元資料(Metadata)

很多mixer control需要提供以dB為單位的資訊,我們可以使用DECLARE_TLV_xxx巨集來定義一些包含這種資訊的變數,然後把control的tlv.p欄位指向這些變數,最後,在access欄位中加上SNDRV_CTL_ELEM_ACCESS_TLV_READ標誌,就像這樣:

static DECLARE_TLV_DB_SCALE(db_scale_my_control, -4050, 150, 0);


static struct snd_kcontrol_new my_control __devinitdata = {
    ...
    .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
            SNDRV_CTL_ELEM_ACCESS_TLV_READ,
    ...
    .tlv.p = db_scale_my_control,
};

DECLARE_TLV_DB_SCALE巨集定義的mixer control,它所代表的值按一個固定的dB值的步長變化。該巨集的第一個引數是要定義變數的名字,第二個引數是最小值,以0.01dB為單位。第三個引數是變化的步長,也是以0.01dB為單位。如果該control處於最小值時會做出mute時,需要把第四個引數設為1。

DECLARE_TLV_DB_LINEAR巨集定義的mixer control,它的輸出隨值的變化而線性變化。 該巨集的第一個引數是要定義變數的名字,第二個引數是最小值,以0.01dB為單位。第二個引數是最大值,以0.01dB為單位。如果該control處於最小值時會做出mute時,需要把第二個引數設為TLV_DB_GAIN_MUTE。

這兩個巨集實際上就是定義一個整形陣列,所謂tlv,就是Type-Lenght-Value的意思,陣列的第0各元素代表資料的型別,第1個元素代表資料的長度,第三個元素和之後的元素儲存該變數的資料。

Control裝置的建立

Control裝置和PCM裝置一樣,都屬於音效卡下的邏輯裝置。使用者空間的應用程式通過alsa-lib訪問該Control裝置,讀取或控制control的控制狀態,從而達到控制音訊Codec進行各種Mixer等控制操作。

Control裝置的建立過程大體上和PCM裝置的建立過程相同。詳細的建立過程可以參考本博的另一篇文章:Linux音訊驅動之三:PCM裝置的建立。下面我們只討論有區別的地方。

我們需要在我們的驅動程式初始化時主動呼叫snd_pcm_new()函式建立pcm裝置,而control裝置則在snd_card_create()內被建立,snd_card_create()通過呼叫snd_ctl_create()函式建立control裝置節點。所以我們無需顯式地建立control裝置,只要建立音效卡,control裝置被自動地建立。

和pcm裝置一樣,control裝置的名字遵循一定的規則:controlCxx,這裡的xx代表音效卡的編號。我們也可以通過程式碼正是這一點,下面的是snd_ctl_dev_register()函式的程式碼:

[c-sharp] view plain copy print?
  1. /*
  2. * registration of the control device
  3. */
  4. staticint snd_ctl_dev_register(struct snd_device *device) 
  5.     struct snd_card *card = device->device_data; 
  6.     int err, cardnum; 
  7.     char name[16]; 
  8.     if (snd_BUG_ON(!card)) 
  9.         return -ENXIO; 
  10.     cardnum = card->number; 
  11.     if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS)) 
  12.         return -ENXIO; 
  13.         /* control裝置的名字 */
  14.     sprintf(name, "controlC%i", cardnum); 
  15.     if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, 
  16.                        &snd_ctl_f_ops, card, name)) < 0) 
  17.         return err; 
  18.     return 0; 

snd_ctl_dev_register()函式會在snd_card_register()中,即音效卡的註冊階段被呼叫。註冊完成後,control裝置的相關資訊被儲存在snd_minors[]陣列中,用control裝置的此裝置號作索引,即可在snd_minors[]陣列中找出相關的資訊。註冊完成後的資料結構關係可以用下圖進行表述:

                                                    control裝置的操作函式入口

使用者程式需要開啟control裝置時,驅動程式通過snd_minors[]全域性陣列和此裝置號,可以獲得snd_ctl_f_ops結構中的各個回撥函式,然後通過這些回撥函式訪問control中的資訊和資料(最終會呼叫control的幾個回撥函式get,put,info)。詳細的程式碼我就不貼了,大家可以讀一下程式碼:/sound/core/control.c。