Linux ALSA驅動之四:Control裝置建立流程原始碼分析(5.18)

語言: CN / TW / HK

開啟掘金成長之旅!這是我參與「掘金日新計劃 · 2 月更文挑戰」的第 4 天,點選檢視活動詳情

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

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

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

1、snd_kcontrol_new

C++ struct snd_kcontrol_new { snd_ctl_elem_iface_t iface; /* interface identifier */ unsigned int device; /* device/client number */ unsigned int subdevice; /* subdevice (substream) number */ const char *name; /* ASCII name of item */ unsigned int index; /* index of item */ unsigned int access; /* access rights */ unsigned int count; /* count of same elements */ snd_kcontrol_info_t *info; snd_kcontrol_get_t *get; snd_kcontrol_put_t *put; union { snd_kcontrol_tlv_rw_t *c; const unsigned int *p; } tlv; unsigned long private_value; };

  iface: 表示control的型別,用SNDRV_CTL_ELEM_IFACE_XXX來定義。通常使用MIXER,也可以定於屬於全域性的CARD型別,如果定義為屬於莫雷裝置的型別,例如HWDEP、PCMRAWMIDI、TIMER等,此時必須在device和subdevice欄位中支出卡的裝置邏輯編號。

  name: 表示control的名字,使用者層可以通過這個名字訪問這個control,後續會細聊

  index: 存放這個 control 的索引號。如果音效卡下不止一個codec。每個codec有相同的名字的control。此時就需要通過index來區分這些controls,當index為0,則可以忽略這種區分策略

  access: 訪問許可權的控制,READ,WRITE,READWRITE等。每一個bit代表一種訪問型別,這些訪問型別可以多個或運算組合在一起使用。

  private_value: 包含了一個人員的長整數型別的值,該值可以通過info、get、put這幾個回撥函式訪問。

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

2、control的名字

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

  源: 可以理解為該control的輸入端,alsa已經預定義了一些常用的源,例如:Master,PCM,CD,Line等等。

  方向: 代表該control的資料流向,例如:Playback,Capture,Bypass,Bypass Capture等等,也可以不定義方向,這時表示該Control是雙向的(playback和capture)。

  功能: 根據control的功能,可以是以下字串:Switch,Volume,Route等等。

  也有一些命名上的特例:

    1、全域性的capture和playback: "Capture Source",“Capture Volume”,“Capture Switch”,他們用於全域性的capture source、switch和volume。同樣的“Playback Volume”,“Playeback Switch”,它們用於全域性的輸出switch和volume。

    2、Tone-controles: 音調控制的開關和音量命名為:Tomw Control-XXX,例如,“Tone-Control-Switch”,“Tone Control-Bass”,“Tone Control-Center”。

    3、3D controls: 3D控制元件的命名規則:“3D Control-Switch”,“3D Control-Center”,“3D Control-Space”。

    4、MIC boost: 麥克風音量加強空間命名為:“MIC Boost”或“MIC Bosst(6dB)”。

3、訪問標誌(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的值。

4、元資料(METADATA)

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

```C++ static const DECLARE_TLV_DB_SCALE(snd_cx88_db_scale, -6300, 100, 0);

static const struct snd_kcontrol_new snd_cx88_volume = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, .name = "Analog-TV Volume", .info = snd_cx88_volume_info, .get = snd_cx88_volume_get, .put = snd_cx88_volume_put, .tlv.p = snd_cx88_db_scale, }; ```

5、函式詳解

5.1、snd_ctl_new1函式

```C++ / * snd_ctl_new1 - create a control instance from the template * @ncontrol: the initialization record * @private_data: the private data to set * * Allocates a new struct snd_kcontrol instance and initialize from the given * template. When the access field of ncontrol is 0, it's assumed as * READWRITE access. When the count field is 0, it's assumes as one. * * Return: The pointer of the newly generated instance, or %NULL on failure. / struct snd_kcontrol snd_ctl_new1(const struct snd_kcontrol_new ncontrol, void private_data) { struct snd_kcontrol *kctl; unsigned int count; unsigned int access; int err;

if (snd_BUG_ON(!ncontrol || !ncontrol->info))
    return NULL;

count = ncontrol->count;
if (count == 0)
    count = 1;

access = ncontrol->access;
if (access == 0)
    access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
access &= (SNDRV_CTL_ELEM_ACCESS_READWRITE |
           SNDRV_CTL_ELEM_ACCESS_VOLATILE |
           SNDRV_CTL_ELEM_ACCESS_INACTIVE |
           SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
           SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
           SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK |
           SNDRV_CTL_ELEM_ACCESS_LED_MASK |
           SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK);

/* 建立snd_kcontrol */
err = snd_ctl_new(&kctl, count, access, NULL);
if (err < 0)
    return NULL;

/* 根據snd_kcontrol_new初始化snd_kcontrol */
/* The 'numid' member is decided when calling snd_ctl_add(). */
kctl->id.iface = ncontrol->iface;
kctl->id.device = ncontrol->device;
kctl->id.subdevice = ncontrol->subdevice;
if (ncontrol->name) {
    strscpy(kctl->id.name, ncontrol->name, sizeof(kctl->id.name));
    if (strcmp(ncontrol->name, kctl->id.name) != 0)
        pr_warn("ALSA: Control name '%s' truncated to '%s'\n",
            ncontrol->name, kctl->id.name);
}
kctl->id.index = ncontrol->index;

kctl->info = ncontrol->info;
kctl->get = ncontrol->get;
kctl->put = ncontrol->put;
kctl->tlv.p = ncontrol->tlv.p;

kctl->private_value = ncontrol->private_value;
kctl->private_data = private_data;

return kctl;

} ```

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

```C++ struct snd_kcontrol { struct list_head list; / list of controls / struct snd_ctl_elem_id id; unsigned int count; / count of same elements / snd_kcontrol_info_t info; snd_kcontrol_get_t get; snd_kcontrol_put_t put; union { snd_kcontrol_tlv_rw_t c; const unsigned int p; } tlv; unsigned long private_value; void private_data; void (private_free)(struct snd_kcontrol kcontrol); struct snd_kcontrol_volatile vd[]; / volatile data / };

define snd_kcontrol(n) list_entry(n, struct snd_kcontrol, list)

```

5.2、 snd_ctl_add函式

```C++ / add/replace a new kcontrol object; call with card->controls_rwsem locked / static int __snd_ctl_add_replace(struct snd_card card, struct snd_kcontrol kcontrol, enum snd_ctl_add_mode mode) { struct snd_ctl_elem_id id; unsigned int idx; struct snd_kcontrol *old; int err;

id = kcontrol->id;
if (id.index > UINT_MAX - kcontrol->count)
    return -EINVAL;

old = snd_ctl_find_id(card, &id);
if (!old) {
    if (mode == CTL_REPLACE)
        return -EINVAL;
} else {
    if (mode == CTL_ADD_EXCLUSIVE) {
        dev_err(card->dev,
            "control %i:%i:%i:%s:%i is already present\n",
            id.iface, id.device, id.subdevice, id.name,
            id.index);
        return -EBUSY;
    }

    err = snd_ctl_remove(card, old);
    if (err < 0)
        return err;
}

if (snd_ctl_find_hole(card, kcontrol->count) < 0)
    return -ENOMEM;

/* 把snd_kcontrol掛入snd_card的controls連結串列 */
list_add_tail(&kcontrol->list, &card->controls);
card->controls_count += kcontrol->count;
/* 設定元素ID */
kcontrol->id.numid = card->last_numid + 1;
card->last_numid += kcontrol->count;

for (idx = 0; idx < kcontrol->count; idx++)
    snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx);

return 0;

}

static int snd_ctl_add_replace(struct snd_card card, struct snd_kcontrol kcontrol, enum snd_ctl_add_mode mode) { int err = -EINVAL;

if (! kcontrol)
    return err;
if (snd_BUG_ON(!card || !kcontrol->info))
    goto error;

down_write(&card->controls_rwsem);
err = __snd_ctl_add_replace(card, kcontrol, mode);
up_write(&card->controls_rwsem);
if (err < 0)
    goto error;
return 0;

error: snd_ctl_free_one(kcontrol); return err; }

/ * snd_ctl_add - add the control instance to the card * @card: the card instance * @kcontrol: the control instance to add * * Adds the control instance created via snd_ctl_new() or * snd_ctl_new1() to the given card. Assigns also an unique * numid used for fast search. * * It frees automatically the control which cannot be added. * * Return: Zero if successful, or a negative error code on failure. * / int snd_ctl_add(struct snd_card card, struct snd_kcontrol *kcontrol) { return snd_ctl_add_replace(card, kcontrol, CTL_ADD_EXCLUSIVE); } ```

5.3、info回撥函式

  用於得到對應control的詳細資訊,需要把資訊存入snd_ctl_elem_info 物件中。

C++ struct snd_ctl_elem_info { struct snd_ctl_elem_id id; /* W: element ID */ snd_ctl_elem_type_t type; /* R: value type - SNDRV_CTL_ELEM_TYPE_* */ unsigned int access; /* R: value access (bitmask) - SNDRV_CTL_ELEM_ACCESS_* */ unsigned int count; /* count of values */ __kernel_pid_t owner; /* owner's PID of this control */ union { struct { long min; /* R: minimum value */ long max; /* R: maximum value */ long step; /* R: step (0 variable) */ } integer; struct { long long min; /* R: minimum value */ long long max; /* R: maximum value */ long long step; /* R: step (0 variable) */ } integer64; struct { unsigned int items; /* R: number of items */ unsigned int item; /* W: item number */ char name[64]; /* R: value name */ __u64 names_ptr; /* W: names list (ELEM_ADD only) */ unsigned int names_length; } enumerated; unsigned char reserved[128]; } value; unsigned char reserved[64]; };   其中的value是個一個共用體,需要根據control的型別,確定值的型別,control type包括如下幾類:

```C++ typedef int __bitwise snd_ctl_elem_type_t;

define SNDRV_CTL_ELEM_TYPE_NONE ((__force snd_ctl_elem_type_t) 0) / invalid /

define SNDRV_CTL_ELEM_TYPE_BOOLEAN ((__force snd_ctl_elem_type_t) 1) / boolean type /

define SNDRV_CTL_ELEM_TYPE_INTEGER ((__force snd_ctl_elem_type_t) 2) / integer type /

define SNDRV_CTL_ELEM_TYPE_ENUMERATED ((__force snd_ctl_elem_type_t) 3) / enumerated type /

define SNDRV_CTL_ELEM_TYPE_BYTES ((__force snd_ctl_elem_type_t) 4) / byte array /

define SNDRV_CTL_ELEM_TYPE_IEC958 ((__force snd_ctl_elem_type_t) 5) / IEC958 (S/PDIF) setup /

define SNDRV_CTL_ELEM_TYPE_INTEGER64 ((__force snd_ctl_elem_type_t) 6) / 64-bit integer type /

define SNDRV_CTL_ELEM_TYPE_LAST SNDRV_CTL_ELEM_TYPE_INTEGER64

```

  下面是以SNDRV_CTL_ELEM_TYPE_INTEGER和以SNDRV_CTL_ELEM_TYPE_BOOLEAN為例定義的info回撥函式:

``` static int snd_cx88_volume_info(struct snd_kcontrol kcontrol, struct snd_ctl_elem_info info) { info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; info->count = 2; info->value.integer.min = 0; info->value.integer.max = 0x3f;

return 0;

}

static int snd_saa7134_capsrc_info(struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; uinfo->count = 2; uinfo->value.integer.min = 0; uinfo->value.integer.max = 1; return 0; } ```

5.4、get回撥函式

這個函式用來讀取當前 control 的值並返回到使用者空間,需要把值放在snd_ctl_elem_value結構體中,與info結構體類似,value欄位是一個共用體,與型別相關。如果value的cont大於1, 需要把值全部放入到 value[]陣列中。

``` static int snd_cx88_volume_info(struct snd_kcontrol kcontrol, struct snd_ctl_elem_info info) { info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; info->count = 2; info->value.integer.min = 0; info->value.integer.max = 0x3f;

return 0;

}

static int snd_cx88_volume_get(struct snd_kcontrol kcontrol, struct snd_ctl_elem_value value) { struct cx88_audio_dev chip = snd_kcontrol_chip(kcontrol); struct cx88_core core = chip->core; int vol = 0x3f - (cx_read(AUD_VOL_CTL) & 0x3f), bal = cx_read(AUD_BAL_CTL);

value->value.integer.value[(bal & 0x40) ? 0 : 1] = vol;
vol -= (bal & 0x3f);
value->value.integer.value[(bal & 0x40) ? 1 : 0] = vol < 0 ? 0 : vol;

return 0;

} ```

5.5、put回撥函式

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

```C++ static void snd_cx88_wm8775_volume_put(struct snd_kcontrol kcontrol, struct snd_ctl_elem_value value) { struct cx88_audio_dev chip = snd_kcontrol_chip(kcontrol); struct cx88_core core = chip->core; u16 left = value->value.integer.value[0]; u16 right = value->value.integer.value[1]; int v, b;

/* Pass volume & balance onto any WM8775 */
if (left >= right) {
    v = left << 10;
    b = left ? (0x8000 * right) / left : 0x8000;
} else {
    v = right << 10;
    b = right ? 0xffff - (0x8000 * left) / right : 0x8000;
}
wm8775_s_ctrl(core, V4L2_CID_AUDIO_VOLUME, v);
wm8775_s_ctrl(core, V4L2_CID_AUDIO_BALANCE, b);

}

/ OK - TODO: test it / static int snd_cx88_volume_put(struct snd_kcontrol kcontrol, struct snd_ctl_elem_value value) { struct cx88_audio_dev chip = snd_kcontrol_chip(kcontrol); struct cx88_core core = chip->core; int left, right, v, b; int changed = 0; u32 old;

if (core->sd_wm8775)
        snd_cx88_wm8775_volume_put(kcontrol, value);

left = value->value.integer.value[0] & 0x3f;
right = value->value.integer.value[1] & 0x3f;
b = right - left;
if (b < 0) {
    v = 0x3f - left;
    b = (-b) | 0x40;
} else {
    v = 0x3f - right;
}
/* Do we really know this will always be called with IRQs on? */
spin_lock_irq(&chip->reg_lock);
old = cx_read(AUD_VOL_CTL);
if (v != (old & 0x3f)) {
    cx_swrite(SHADOW_AUD_VOL_CTL, AUD_VOL_CTL, (old & ~0x3f) | v);
    changed = 1;
}
if ((cx_read(AUD_BAL_CTL) & 0x7f) != b) {
    cx_write(AUD_BAL_CTL, b);
    changed = 1;
}
spin_unlock_irq(&chip->reg_lock);

return changed;

} ```

6、Control裝置建立流程

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

  Control裝置的建立過程大體上和PCM裝置的建立過程相同。詳細的建立過程可以參考下方時序圖。

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

  和pcm裝置一樣,control裝置的名字遵循一定的規則:controlCxx,這裡的xx代表音效卡的編號。

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