16-控制接口
17.4.4 控制接口
1.control
控制接口对于许多开关(switch)和调节器(slider)而言应用相当广泛,它能从用户空间被存取。control的最主要用途是mixer,所有的mixer元素基于control内核API实现,在ALSA中,control用snd_kcontrol结构体描述。
ALSA有一个定义很好的AC97控制模块,对于仅支持AC97的芯片而言,不必实现本节的内容。
创建一个新的control至少需要实现snd_kcontrol_new中的info()、get()和put()这3个成员函数,snd_kcontrol_new结构体的定义如代码清单17.16所示。
代码清单17.16 snd_kcontrol_new结构体
1 struct snd_kcontrol_new {
2 snd_ctl_elem_iface_t iface; /接口ID,SNDRV_CTL_ELEM_IFACE_XXX /
3 unsigned int device; / 设备号 /
4 unsigned int subdevice; / 子流(子设备)号 /
5 unsigned char name; / 名称(ASCII格式) */
6 unsigned int index; / 索引 /
7 unsigned int access; / 访问权限 /
8 unsigned int count; / 享用元素的数量 /
9 snd_kcontrol_info_t *info;
10 snd_kcontrol_get_t *get;
11 snd_kcontrol_put_t *put;
12 unsigned long private_value;
13 };
iface字段定义了control的类型,形式为SNDRV_CTL_ELEM_IFACE_XXX,通常是MIXER,对于不属于mixer的全局控制,使用CARD。如果关联于某类设备,则使用HWDEP、PCM、RAWMIDI、TIMER或SEQUENCER。
name是名称标识字符串,control的名称非常重要,因为control的作用由名称来区分。对于名称相同的control,则使用index区分。name定义的标准是“SOURCE DIRECTION FUNCTION”即“源 方向 功能”,SOURCE定义了control的源,如“Master”、“PCM”、“CD”和“Line”,方向则为“Playback”、“Capture”、“Bypass Playback”或“Bypass Capture”,如果方向省略,意味着playback和capture双向,第3个参数可以是“Switch”、“Volume”和“Route”等。
“SOURCE DIRECTION FUNCTION”格式的名称例子如“Master Capture Switch”、“PCM Playback Volume”。
下面几种control的命名不采用“SOURCE DIRECTION FUNCTION”格式,属于例外。
(1)全局控制。
“Capture Source”、“Capture Switch”和“Capture Volume”用于全局录音源、输入开关和录音音量控制;“Playback Switch”、“Playback Volume”用于全局输出开关和音量控制。
(2)音调控制。
音调控制名称的形式为“Tone Control – XXX”,例如“Tone Control – Switch”、“Tone Control – Bas”和“Tone Control – Center”。
(3)3D控制。
3D控制名称的形式为“3D Control – XXX”,例如“3D Control – Switch”、“3D Control – Center”和“3D Control – Space”。
(4)麦克风增益(Mic boost)。
麦克风增益被设置为“Mic Boost”或“Mic Boost (6dB)”。
snd_kcontrol_new结构体的access字段是访问控制权限,形式如SNDRV_CTLELEM ACCESS_XXX。SNDRV_CTL_ELEM_ACCESS_READ意味着只读,这时put()函数不必实现;SNDRV_CTL_ELEM_ACCESS_WRITE意味着只写,这时get()函数不必实现。若control值频繁变化,则需定义VOLATILE标志。当control处于非激活状态时,应设置INACTIVE标志。
private_value字段包含一个长整型值,可以通过它给info()、get()和put()函数传递参数。
2.info()函数
snd_kcontrol_new结构体中的info()函数用于获得该control的详细信息,该函数必须填充传递给它的第二个参数snd_ctl_elem_info结构体,info()函数的形式如下:
static int snd_xxxctl_info(struct snd_kcontrol kcontrol, struct snd_ctl_elem_info uinfo);
snd_ctl_elem_info结构体的定义如代码清单17.17所示。
代码清单17.17 snd_ctl_elem_info结构体
1 struct snd_ctl_elem_info
2 {
3 struct snd_ctl_elem_id id; / W: 元素ID /
4 snd_ctl_elem_type_t type; / R: 值类型 - SNDRV_CTL_ELEMTYPE */
5 unsigned int access; / R: 值访问权限(位掩码) - SNDRV_CTL_ELEMACCESS */
6 unsigned int count; / 值的计数 /
7 pid_t owner; / 该control的拥有者PID /
8 union {
9 struct {
10 long min; / R: 最小值 /
11 long max; / R: 最大值 /
12 long step; / R: 值步进 (0 可变的) /
13 } integer;
14 struct {
15 long long min; / R: 最小值 /
16 long long max; / R: 最大值 /
17 long long step; / R: 值步进 (0 可变的) /
18 } integer64;
19 struct {
20 unsigned int items; / R: 项目数 /
21 unsigned int item; / W: 项目号 /
22 char name[64]; / R: 值名称 /
23 } enumerated; / 枚举 /
24 unsigned char reserved[128];
25 }
26 value;
27 union {
28 unsigned short d[4];
29 unsigned short *d_ptr;
30 } dimen;
31 unsigned char reserved[64-4 * sizeof(unsigned short)];
32 };
snd_ctl_elem_info结构体的type字段定义了control的类型,包括BOOLEAN、INTEGER、ENUMERATED、BYTES、IEC958和INTEGER64。count字段定义了这个control中包含的元素的数量,例如一个立体声音量control的count = 2。value是一个联合体,其所存储的值的具体类型依赖于type。代码清单17.18所示为一个info()函数填充snd_ctl_elem_info结构体的范例。
代码清单17.18 snd_ctl_elem_info结构体中的info()函数范例
1 static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct
2 snd_ctl_elem_info *uinfo)
3 {
4 uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;/ 类型为BOOLEAN /
5 uinfo->count = 1;/ 数量为1 /
6 uinfo->value.integer.min = 0;/ 最小值为0 /
7 uinfo->value.integer.max = 1;/ 最大值为1 /
8 return 0;
9 }
枚举类型和其他类型略有不同,对枚举类型,应为目前项目索引设置名称字符串,如代码清单17.19所示。
代码清单17.19 填充snd_ctl_elem_info结构体中的权举类型值
1 static int snd_xxxctl_info(struct snd_kcontrol *kcontrol, struct
2 snd_ctl_elem_info *uinfo)
3 {
4 / 值名称字符串/
5 static char *texts[4] = {
6 "First", "Second", "Third", "Fourth"
7 };
8 uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;/ 枚举类型/
9 uinfo->count = 1;/ 数量为1 /
10 uinfo->value.enumerated.items = 4;/ 项目数量为1 /
11 / 超过3的项目号改为3 /
12 if (uinfo->value.enumerated.item > 3)
13 uinfo->value.enumerated.item = 3;
14 / 为目前项目索引复制名称字符串/
15 strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
16 return 0;
17 }
3.get()函数
get()函数用于得到control的目前值并返回用户空间,代码清单17.20所示为get()函数的范例。
代码清单17.20 snd_ctl_elem_info结构体中的get()函数范例
1 static int snd_xxxctl_get(struct snd_kcontrol *kcontrol, struct
2 snd_ctl_elem_value *ucontrol)
3 {
4 / 从snd_kcontrol获得xxxchip指针/
5 struct xxxchip *chip = snd_kcontrol_chip(kcontrol);
6 / 从xxxchip获得值并写入snd_ctl_elem_value /
7 ucontrol->value.integer.value[0] = get_some_value(chip);
8 return 0;
9 }
get()函数的第二个参数的类型为snd_ctl_elem_value,其定义如代码清单10.21所示。sndctl elem_ value结构体的内部也包含一个由integer、integer64、enumerated等组成的值联合体,它的具体类型依赖于control的类型和info()函数。
代码清单17.21 snd_ctl_elem_value结构体
1 struct snd_ctl_elem_value
2 {
3 struct snd_ctl_elem_id id; / W: 元素ID /
4 unsigned int indirect: 1; / W: 使用间接指针(xxx_ptr成员) /
5 / 值联合体/
6 union {
7 union {
8 long value[128];
9 long *value_ptr;
10 } integer;
11 union {
12 long long value[64];
13 long long *value_ptr;
14 } integer64;
15 union {
16 unsigned int item[128];
17 unsigned int *item_ptr;
18 } enumerated;
19 union {
20 unsigned char data[512];
21 unsigned char *data_ptr;
22 } bytes;
23 struct snd_aes_iec958 iec958;
24 }
25 value; / 只读 /
26 struct timespec tstamp;
27 unsigned char reserved[128-sizeof(struct timespec)];
28 };
4.put()函数
put()用于从用户空间写入值,如果值被改变,该函数返回1,否则返回0;如果发生错误,该函数返回一个错误码。代码清单17.22所示为一个put()函数的范例。
代码清单17.22 snd_ctl_elem_info结构体中的put()函数范例
1 static int snd_xxxctl_put(struct snd_kcontrol *kcontrol, struct
2 snd_ctl_elem_value *ucontrol)
3 {
4 / 从snd_kcontrol获得xxxchip指针/
5 struct xxxchip *chip = snd_kcontrol_chip(kcontrol);
6 int changed = 0;/ 默认返回值为0 /
7 / 值被改变/
8 if (chip->current_value != ucontrol->value.integer.value[0]) {
9 change_current_value(chip, ucontrol->value.integer.value[0]);
10 changed = 1;/ 返回值为1 /
11 }
12 return changed;
13 }
对于get()和put()函数而言,如果control有多于一个元素,即count >1,则每个元素都需要被返回或写入。
5.构造control
当所有事情准备好后,我们需要创建一个control,调用snd_ctl_add()和snd_ctl_new1()这两个函数来完成,这两个函数的原型为:
int snd_ctl_add(struct snd_card card, struct snd_kcontrol kcontrol);
struct snd_kcontrol snd_ctl_new1(const struct snd_kcontrol_new ncontrol,
void *private_data);
snd_ctl_new1()函数用于创建一个snd_kcontrol并返回其指针,snd_ctl_add()函数用于将创建的snd_kcontrol添加到对应的card中。
6.变更通知
如果驱动中需要在中断服务程序中改变或更新一个control,可以调用snd_ctl_notify()函数,此函数原型为:
void snd_ctl_notify(struct snd_card card, unsigned int mask, struct snd_ctl_elem_id id);
该函数的第二个参数为事件掩码(event-mask),第三个参数为该通知的control元素id指针。
例如,如下语句定义的事件掩码SNDRV_CTL_EVENT_MASK_VALUE意味着control值的改变被通知:
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, id_pointer);