linux-alsa詳解3 control設備


1 control設備簡介

Control接口主要讓用戶空間的應用程序(alsa-lib)可以訪問和控制音頻codec芯片中的多路開關,滑動控件等.對於Mixer(混音)來說,Control接口顯得尤為重要,從ALSA 0.9.x版本開始,所有的mixer工作都是通過control接口的API來實現的。其實通俗的理解control設備的作用如音量的調節,開關等。<sound/control.h>定義了所有的Control API.如果你要為你的codec實現自己的controls,請在代碼中包含該頭文件。

2 control設備的建立

control設備和PCM設備一樣,都屬於聲卡下的邏輯設備.用戶空間的應用程序通過alsa-lib訪問該Control設備,讀取或控制control的控制狀態,從而達到控制音頻Codec進行各種Mixer等控制操作。

control設備創建函數snd_ctl_create,定義位於\sound\core\control.c

 1 /*
 2  * create control core:
 3  * called from init.c
 4  */
 5 int snd_ctl_create(struct snd_card *card)
 6 {
 7     static struct snd_device_ops ops = {
 8         .dev_free = snd_ctl_dev_free,
 9         .dev_register =    snd_ctl_dev_register,
10         .dev_disconnect = snd_ctl_dev_disconnect,
11     };
12     int err;
13 
14     if (snd_BUG_ON(!card))
15         return -ENXIO;
16     if (snd_BUG_ON(card->number < 0 || card->number >= SNDRV_CARDS))
17         return -ENXIO;
18 
19     snd_device_initialize(&card->ctl_dev, card);
20     dev_set_name(&card->ctl_dev, "controlC%d", card->number);
21 
22     err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
23     if (err < 0)
24         put_device(&card->ctl_dev);
25     return err;
26 }

 control設備的創建過程大體上和pcm設備的創建過程相同,最終調用函數snd_device_new,拿到聲卡邏輯設備的設備操作結構體(設備操作結構體定義以上函數中,最重要的還是注冊函數),將該設備邏輯設備加入聲卡結構體devices鏈表中。詳細的創建過程可以參考linux-alsa詳解2 pcm設備

3 control設備的注冊

control設備注冊函數snd_ctl_dev_register,

 1 /*
 2  * registration of the control device
 3  */
 4 static int snd_ctl_dev_register(struct snd_device *device)
 5 {
 6     struct snd_card *card = device->device_data;
 7 
 8     return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
 9                    &snd_ctl_f_ops, card, &card->ctl_dev);
10 }

還是和pcm設備注冊一樣,最終調用函數snd_register_device。

(1)獲取聲卡次設備snd_minor ,並將其賦值給全局聲卡次設備變量snd_minor中 

(2)獲取次設備pcm的文件操作結構體,供用戶層使用的api回調函數

control的文件操作結構體定義如下:

 1 static const struct file_operations snd_ctl_f_ops =
 2 {
 3     .owner =    THIS_MODULE,
 4     .read =        snd_ctl_read,
 5     .open =        snd_ctl_open,
 6     .release =    snd_ctl_release,
 7     .llseek =    no_llseek,
 8     .poll =        snd_ctl_poll,
 9     .unlocked_ioctl =    snd_ctl_ioctl,
10     .compat_ioctl =    snd_ctl_ioctl_compat,
11     .fasync =    snd_ctl_fasync,
12 };

4 control設備的打開

用戶程序需要打開control設備時,驅動程序通過snd_minors[]全局數組和此設備號,可以獲得snd_ctl_f_ops結構中的各個回調函數,然后通過這些回調函數訪問control中的信息和數據(最終會調用control的幾個回調函數get,put,info)。具體參考pcm設備的打開。

5 controls的定義

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

 1 static struct 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提供元數據.

6 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)".

7 訪問標志(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的值.

8 回調函數

8.1 info回調函數

info回調函數用於獲取control的詳細信息.它的主要工作就是填充通過參數傳入的snd_ctl_elem_info對象,以下例子是一個具有單個元素的boolean型control的info回調:

1 static int snd_myctl_mono_info(struct snd_kcontrol *kcontrol,
2     struct snd_ctl_elem_info *uinfo)
3 {
4     uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
5     uinfo->count = 1;
6     uinfo->value.integer.min = 0;
7     uinfo->value.integer.max = 1;
8     return 0;
9 }

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

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

 1 static int snd_myctl_enum_info(struct snd_kcontrol *kcontrol,
 2 struct snd_ctl_elem_info *uinfo)
 3 {
 4     static char *texts[4] = {
 5         "First", "Second", "Third", "Fourth"
 6     };
 7     uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
 8     uinfo->count = 1;
 9     uinfo->value.enumerated.items = 4;
10     if (uinfo->value.enumerated.item > 3)
11         uinfo->value.enumerated.item = 3;
12     strcpy(uinfo->value.enumerated.name,
13         texts[uinfo->value.enumerated.item]);
14     return 0;
15 }

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

8.2 get回調函數

該回調函數用於讀取control的當前值,並返回給用戶空間的應用程序.

1 static int snd_myctl_get(struct snd_kcontrol *kcontrol,
2     struct snd_ctl_elem_value *ucontrol)
3 {
4     struct mychip *chip = snd_kcontrol_chip(kcontrol);
5     ucontrol->value.integer.value[0] = get_some_value(chip);
6     return 0;
7 }

value字段的賦值依賴於control的類型(如同info回調).很多聲卡的驅動利用它存儲硬件寄存器的地址、bit-shift和bit-mask,這時,private_value字段可以按以下例子進行設置:

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

然后,get回調函數可以這樣實現:

 1 static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol,
 2     struct snd_ctl_elem_value *ucontrol)
 3 
 4 {
 5     int reg = kcontrol->private_value & 0xff;
 6     int shift = (kcontrol->private_value >> 16) & 0xff;
 7     int mask = (kcontrol->private_value >> 24) & 0xff;
 8     ....
 9 
10     //根據以上的值讀取相應寄存器的值並填入value中
11 }

如果control的count字段大於1,表示control有多個元素單元,get回調函數也應該為value填充多個數值

8.3 put回調函數

put回調函數用於把應用程序的控制值設置到control中.

 1 static int snd_myctl_put(struct snd_kcontrol *kcontrol,
 2     struct snd_ctl_elem_value *ucontrol)
 3 {
 4     struct mychip *chip = snd_kcontrol_chip(kcontrol);
 5     int changed = 0;
 6     if (chip->current_value !=
 7         ucontrol->value.integer.value[0]) {
 8         change_current_value(chip,
 9         ucontrol->value.integer.value[0]);
10         changed = 1;
11     }
12     return changed;
13 }

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

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

9 創建Controls

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

snd_ctl_new1()

snd_ctl_add()

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

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當中.

10 元數據(Metadata)

很多mixer control需要提供以dB為單位的信息,我們可以使用DECLARE_TLV_xxx宏來定義一些包含這種信息的變量,然后把control的tlv.p字段指向這些變量,最后,在access字段中加上SNDRV_CTL_ELEM_ACCESS_TLV_READ標志,就像這樣:

 1 static DECLARE_TLV_DB_SCALE(db_scale_my_control, -4050, 150, 0);
 2 
 3 
 4 static struct snd_kcontrol_new my_control __devinitdata = {
 5     ...
 6     .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
 7             SNDRV_CTL_ELEM_ACCESS_TLV_READ,
 8     ...
 9     .tlv.p = db_scale_my_control,
10 };

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個元素代表數據的長度,第三個元素和之后的元素保存該變量的數據.

參考博文:https://www.cnblogs.com/jason-lu/archive/2013/06/07/3123917.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM