- 论坛徽章:
- 0
|
第六章
控制接口
翻译:creator
sz111@126.com
概要
控制接口非常广泛的应用在许多转换,变调等场合,可以从用户空间进行控制。混音器接口是一个最重要的接口。换句话说,在ALSA0.9.x版本,所有的混音器的工作都是通过控制接口API实现的(在0.5.x版本混音器内核API是独立出来的)。
ALSA有一个定义很好的AC97的控制模块。如果你的声卡仅仅支持AC97,你可以忽略这章。
控制接口定义
为了创建一个新的控制接口,需要定义三个函数:info,get和put。然后定义一个snd_kcontrol_new类型的记录,例如:
Example6-1.定义一个控制接口
static struct
snd_kcontrol_new my_control __devinitdata = {
.face =
SNDRV_CTL_ELEM_IFACE_MIXER,
.name =
“PCM Playback Switch”,
.index =
0,
.access =
SNDRV_CTL_ELEM_ACCESS_READWRITE,
.private_value
= 0xffff,
.info =
my_control_info,
.get =
my_control_get,
.put =
my_control_put
};
大部分情况是通过snd_ctl_new1()来创建control,这种情况下,你可以像上面一样,在定义的前面加上__devinitdata前缀.
iface字段表示control的类型,如:SNDRV_CTL_ELEM_IFACE_XXX,通常情况都是MIXER。CARD表示一个全局控制,而不是混音器的一部分。假如control和声卡设备联系的非常紧密,如HWDEP,RAMDIDI,TIMER或SEQUENCER,需要特别通过device和subdevice字段标识出。
name字段是表示名称的标识符。在ALSA0.9.X,控制单元名字是非常重要的,因为的角色就是通过它的名字进行分类。有一些预定义的标准的control名字。详细描述请参考下节的“控制单元名字”。
index字段存放这个control的索引号。假如一个名字下面有多个不同的control,就要通过index(索引)来区分了。这是当一个声卡拥有多个解码的时候才会用到。加入索引设定为零,就可以忽略定义。
access字段包括了控制接口的存取控制。提供了一些位的组合,如:SNDRV_CTL_ELEM_XXX。详细描述请参考“接口标志位”一节。
private_value字段这个记录的一个专有的的长整型变量。当调用info,get和put函数的时候,可以通过这个字段传递一些参数值。如果参数都是些很小的数,可以把它们通过移位来组合,也可以存放一个指向一个记录的指针(因为它是长整型的)。
另外三个在回调函数一节介绍。
控制接口名字
有一些control名字的标准。一个control通常根据“源,方向,功能”三部分来命名。
首先,SOURCE定义了control的源,是一个字符串,如:“Master”,“PCM”,“CD”,“Line”。已经有很多预定义好的“源”了。
第二,“方向”则为“Playbck”,“Capture”,“Bypass
Playback”,“Bypass
Capture”。或者,它如果省略,那就表示播放和录音双向。
第三个,“功能”。根据控制接口的功能不同有下面三个:“Switch”,“volume”,“route”。
一些控制接口名字的范例如下:“Master
Capture Switch”,“PCM
Playback Volume”.
也有一些不是采用“源,方向,功能”三部分来命名方式:
全局录音和播放
“Capture
Source”,“Capture
Switch”和“Capture
Volume”用来做全局录音(输入)源,开关,和音量的控制。“Playback
Switch”和“Playback
Volume”用来做全局的输出开关和音量控制。
音调控制
音调开关和音量名称形式为“Tone
Control-XXX”。如:“Tone
Control-Switch”,“Tone
Control – Bass”,“Tone
Control – Center”。
3D控制
3D控制开关和音量命名形式为“3D
Control – XXX”。如:“3D
Control – Switch”,“3D
Control – Switch”,“3D
Control – Center”,“3D Control – Space”。
麦克风增益
麦克风增益命名如“Mic
Boost”或“Mic
Boost(6dB)”。
更精确的信息请参考文档(Documentation/sound/alsa/ControlNames.txt)
存取标志
存取标志是一个位标志,主要是区分给定的control的存取类型。缺省的存取类型是SNDRV_CTL_ELEM_ACCESS_READWRITE,意思是允许对control进行读写控制。当这个标志位被忽略的时候(为0),被认为是缺省读写(READWRITE)。
当这个control是只读的时候,需要传递SNDRV_CTL_ELEM_ACCESS_READ。这时候,可以不定义put函数。类似的,如果control是只写的话(虽然这种可能性很低),要设定为WRITE标志,也可以不必定义get函数。
加入control的值是经常改变的,应该加上VOLATILE标志,这意味着control可以不用显式通知就可以改变。应用程序应经常查询control。
当一个control是不活动的话,设定INACTIVE标志。还有LOCK和OWNER标志用来改变写权限。
回调函数
info函数
info函数可以得到对应control的详细信息。它必须存到一个给定的snd_ctl_elem_info对象中。例如,对于一个拥有一个元素的布尔型control的如下:
Example6-2.info函数示例
static int
snd_myctl_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的类型。有如下几种:布尔型,整形,枚举型,BYTES型,IEC958,64位整形。count字段表示这个control里面的元素的数量。如:一个立体声音量拥有的count为2。value字段是一个union(联合)类型。value的存储依赖于类型。布尔型和整形是一样的。
枚举型和其他类型有一点不同,需要为当前给定的索引项设定字符串。
Static int
snd_myctl_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_ENUMRATED;
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的值并返回到用户空间。
例如:
Example6-3.get函数示例
static int
snd_myctl_get(struct snd_kcontrol *kcontrol,
struct
snd_ctl_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类似。例如,子驱动和用这个字段来存储一些寄存器的偏移,位的屏蔽。private_value设定如下:
.private_value
= reg | (shift
可以通过以下函数重新得到:
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;
....
}
在get函数中,加入control拥有超过一个的元素。如count大于1,就必须填充所有的元素。上面的例子中,因为我们假定count为1,所以我们仅仅填充了一个元素(value.integer.value[0])。
put函数
这个函数主要是从用户空间写一个值
例如:
Example6-4.put函数示例
static int
snd_myctrl_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;
}
如上,假如value被改变要返回1,没有改变返回0。假如有错误发生,通常返回一个带错误码的负数。
像get函数一样,如果control拥有超过一个的元素,在put函数中所有的元素都要比较一下。
回调函数不是原子的。
所有上面的3个回调函数都是非原子的。
构造器
当所有事情都准备好的时候,我们就可以创建一个新的control了。为了创建它,首先要调用两个函数,snd_ctl_new1()和snd_ctl_add()。
一个简单的方式如下:
if ((err =
snd_ctl_add(card, snd_ctl_new1(&my_control, chip)))
return
err;
my_control是前面定义好的snd_kcontrol_new类型的对象,chip是一个指向kcontrol->private_data的指针,可以被回调函数调用。
snd_ctl_new1()分配了一个新的snd_kcontrol实例(这也是为何my_control可以带有__devinitdata前缀的原因了),snd_ctl_add会把给定的control组件添加到card里面。
更改通知
假如你需要在中断程序中改变或更新一个control,你需要调用snd_ctl_notify()。
例如:
snd_ctl_notify(card,
SNDRV_CTL_EVENT_MASK_VALUE,id_pointer);
这个函数需要card指针,event_mask,和control
id指针作为参数。event-mask表示notification的类型,如上述示例,是通知改变control的值。id指针是指向一个snd_ctl_elem_id的结构体。在es1938.c或es1968.c中关于硬件的卷的中断部分有相关示例。
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/49088/showart_1018357.html |
|