.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
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. }
36
如上述例子所示,当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
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当中。
37
元数据(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个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据。
38
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
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. int err, cardnum; 8. char name[16]; 9.
10. if (snd_BUG_ON(!card)) 11. return -ENXIO;
12. cardnum = card->number;
13. if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
14. return -ENXIO;
15. /* control设备的名字 */
16. sprintf(name, \, cardnum);
17. if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
39
18. &snd_ctl_f_ops, card, name)) < 0)
19. return err; 20. return 0; 21. }
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。
5. Linux ALSA声卡驱动之五:移动设备中的ALSA(ASoC)
40