L i nu x ALSA声卡驱动之四:control设备的创建

转自:http://blog.csdn.net/droidphone

Control接口

control接口主要让用户空间的应用程序(alsa-lib)可以访问和控制音频的codec芯片中的多路开关,滑动控件等。对于Mixer(混音)来说,control接口显得尤为重要,从ALSA0.9.x版本开始,所有的mixer工作都是通过control接口的API来实现的。

 

/sound/control.h定义了所有的control API。如果你要为你的codec实现自己的control,请在代码中包含该头文件。

Control的定义:

要定义一个control,我们首先要定义3个回调函数:info,get,put、然后,定义一个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 unsigned 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的类型,alsa定义了几种类型(SNDDRV_CTL_ELEM_IFACE_XXX),常用的类型是MIXER,当然也可以定义属于全局的CARD类型,也可以定义属于某类设备的类型,例如HWDEP,PCMRAWMIDI,TIMER等,这时需要device和subdevice字段中指出卡的设备逻辑编号。

name字段是该control的名字,从ALSA0.9.x开始,control的名字是变得比较重要,因为control的作用是按名字来归类的,ALSA已经预定义了一些control的名字,我们再control name一节详细讨论。

index字段用于保存该control在该卡中的编号,如果声卡中不止有一个codec,每个codec中有相同名字的control,这时 我们可以通过index来区分这些control,当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,等等,也可以不定义方向,这时表示该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-xx ,例如,“Tone Control-Switch”,“Tone Control-Bass”

3D controls 3D控件的命名规则: 3D Control-switch 3D Control-center 3DControl-Space

Mic boost 麦克风音量加强空间命名为:mic boost或mic boost

访问标志(ACCESS Flags)

Access字段是一个bitmask,它保存了该Control的访问类型,默认的访问类型是SNDDRV_CTL_ELEM_ACCESS_READWRITE,表明该Control支持读写和操作。如果access字段没有定义,此时也认为是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回调:

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

type字段指出该control的值类型,值类型可以是BOOLEAN,INTEGER,ENUMERATED,BYTES,IEC958和INTEGER64之一。count字段指出了该control中包含多少个元素单元,比如立体声的音量control左右两个声道的音量值,它的的count字段等于2。value 字段是一个联合体,value的内容和control的类型有关,其中,boolean和integer类型是相同的。

ENUMERATEC类型有些特殊,它的value需要设定一个字符串和字符串的索引,请看一下例子:

static int snd_myctl_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
    static char *texts[4] = {
        "First", "Second", "Third", "Fourth"
    };
    uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
    uinfo->count = 1;
    uinfo->value.enumerated.items = 4;
    if (uinfo->value.enumerated.item > 3)
        uinfo->value.enumerated.item = 3;
    strcpy(uinfo->value.enumerated.name,
        texts[uinfo->value.enumerated.item]);
    return 0;
}

get回调函数

该回调函数用于读取control的当前值,并返回给用户空间的应用程序。

static int snd_myctl_get(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct mychip *chip = snd_kcontrol_chip(kcontrol);
    ucontrol->value.integer.value[0] = get_some_value(chip);
    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中。

static int snd_myctl_put(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct mychip *chip = snd_kcontrol_chip(kcontrol);
    int changed = 0;
    if (chip->current_value !=
        ucontrol->value.integer.value[0]) {
        change_current_value(chip,
        ucontrol->value.integer.value[0]);
        changed = 1;
    }
    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:

err = snd_ctl_add(card, snd_ctl_new1(&my_control, chip));
if (err < 0)
    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()函数的代码:

/*
 * registration of the control device
 */
static int snd_ctl_dev_register(struct snd_device *device)
{
	struct snd_card *card = device->device_data;
	int err, cardnum;
	char name[16];

	if (snd_BUG_ON(!card))
		return -ENXIO;
	cardnum = card->number;
	if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
		return -ENXIO;
    //control设备的命名
	sprintf(name, "controlC%i", cardnum);
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
				       &snd_ctl_f_ops, card, name)) < 0)
		return err;
	return 0;
}

snd_ctl_dev_register()函数会在snd_card_register()中,即声卡的注册阶段被调用,注册完成后,control设备的相关信息被保存在snd_minors[]数组中,用control设备的次设备号作索引,即可在snd_minors[]数组找出相关的信息。注册完成后的数据结构关系可以用下图进行表述:

用户程序需要打开control设备时,驱动程序通过snd_minors[]全局数组和次设备号,可以获得snd_ctl_f_ops结构体中的各个回调函数,然后通过这些回调函数访问control中的信息和数据(最终会调用control的几个回调函数get ,put,info)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值