开启成长之旅!这是我参与「日新方案 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

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标志,例如:

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函数

/**
 * 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傍边。

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函数

/* 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 目标中。

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包含如下几类:

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中。

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[]数组中找出相关的信息。注册完成后的数据结构关系能够用下图进行表述: ​

Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)
Linux ALSA驱动之四:Control设备创建流程源码分析(5.18)​修改​