Control接口
Linux ALSA驱动框架分析之(一):架构介绍
Linux ALSA驱动框架分析之(二):pcm逻辑设备的创建
Linux ALSA驱动框架分析之(三):Control逻辑设备的创建
Control接口主要让用户空间的应用程序可以访问和控制音频codec芯片中的多路开关,滑动控件等,进行音量控制、静音等。
在ALSA中,control用snd_kcontrol结构体描述。创建一个新的control至少需要实现 snd_kcontrol_new中的info、get和put这3个成员函数,snd_kcontrol_new结构体的定义如下:
struct snd_kcontrol_new {
/* 控制类型,如:
SNDRV_CTL_ELEM_IFACE_CARD
SNDRV_CTL_ELEM_IFACE_MIXER
*/
snd_ctl_elem_iface_t iface;
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const unsigned char *name; /* 名称 */
unsigned int index; /* 同名control,通过index区分 */
unsigned int access; /* 访问权限 */
unsigned int count; /* 元素的数量 */
//用于获得该control的详细信息
snd_kcontrol_info_t *info;
//获取control的目前值
snd_kcontrol_get_t *get;
//用于把应用程序的控制值设置到control中
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo);
typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
snd_kcontrol_new结构体中的info回调函数用于获得该control的详细信息,该函数必须填充传递给它的第二个参数snd_ctl_elem_info结构体。snd_ctl_elem_info结构体描述control的详细信息,定义如下:
struct snd_ctl_elem_info {
struct snd_ctl_elem_id id; /* 元素ID */
/* 值类型,如:
SNDRV_CTL_ELEM_TYPE_BOOLEAN,bool型
SNDRV_CTL_ELEM_TYPE_INTEGER,int型
*/
snd_ctl_elem_type_t type;
unsigned int access; /* 访问权限 */
/* control中包含的元素数量,比如立体声有左右声道,该字段为2 */
unsigned int count;
__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;
union {
unsigned short d[4]; /* dimensions */
unsigned short *d_ptr; /* indirect - obsoleted */
} dimen;
unsigned char reserved[64-4*sizeof(unsigned short)];
};
snd_kcontrol_new结构体中的get回调函数用于得到control的目前值,put回调函数用于把应用程序的控制值设置到control中,这两个回调函数的第二参数的类型为snd_ctl_elem_value,定义如下:
struct snd_ctl_elem_value {
struct snd_ctl_elem_id id; /* 元素ID */
unsigned int indirect: 1; /* W: indirect access - obsoleted */
union {
union {
long value[128];
long *value_ptr; /* obsoleted */
} integer;
union {
long long value[64];
long long *value_ptr; /* obsoleted */
} integer64;
union {
unsigned int item[128];
unsigned int *item_ptr; /* obsoleted */
} enumerated;
union {
unsigned char data[512];
unsigned char *data_ptr; /* obsoleted */
} bytes;
struct snd_aes_iec958 iec958;
} value;
struct timespec tstamp;
unsigned char reserved[128-sizeof(struct timespec)];
};
Control的名称
name是名称标识字符串,control的名称非常重要,因为control的作用由名称来区分。control的名称定义不是随便取得,而是有标准的,标准是源-方向-功能。
- 源,如"Master"、“PCM”、"CD"和"Line"等等。
- 方向则为"Playback"、“Capture”、"Bypass Playback”或“Bypass Capture”,如果方向省略,意味 着playback和capture双向。
- 功能,根据control的功能,可以是"Switch"、"Volume"和"Route"等等。
符合标准的名称例子如"Master Capture Switch"、“PCM Playback Volume”。
也有一些命名上的特例:如"Capture Volume"、“Playback Volume”,"Capture Switch"等,用于全局的控制。
应用程序想要控制播放音量,通过"Playback Volume"名称找到对应的snd_kcontrol,调用里面的put回调函数进行设置,这也是control的名称标准化的原因。
构造control
设置好snd_kcontrol_new就可以调用snd_ctl_new1函数用于创建一个snd_kcontrol,之后调用snd_ctl_add函数用于将创建的snd_kcontrol添加到对应的card中。
来看看snd_ctl_new1函数:
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;
......
count = ncontrol->count;
if (count == 0)
count = 1;
access = ncontrol->access;
......
//创建snd_kcontrol
err = snd_ctl_new(&kctl, count, access, NULL);
if (err < 0)
return NULL;
/* 根据snd_kcontrol_new初始化snd_kcontrol */
kctl->id.iface = ncontrol->iface;
kctl->id.device = ncontrol->device;
kctl->id.subdevice = ncontrol->subdevice;
if (ncontrol->name) {
strlcpy(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;
}
struct snd_kcontrol {
struct list_head list;
struct snd_ctl_elem_id id;
unsigned int count;
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[0]; /* volatile data */
};
struct snd_ctl_elem_id {
unsigned int numid; /* 元素ID号,应用层通过元素ID号找到对应的snd_kcontrol */
snd_ctl_elem_iface_t iface;
unsigned int device;
unsigned int subdevice;
unsigned char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
unsigned int index;
};
snd_kcontrol_new与snd_kcontrol之间的关系:
snd_ctl_add函数:
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
struct snd_ctl_elem_id id;
unsigned int idx;
unsigned int count;
int err = -EINVAL;
......
//把snd_kcontrol挂入snd_card的controls链表
list_add_tail(&kcontrol->list, &card->controls);
card->controls_count += kcontrol->count;
kcontrol->id.numid = card->last_numid + 1; //设置元素ID号
card->last_numid += kcontrol->count;
id = kcontrol->id;
count = kcontrol->count;
......
}
Control设备的建立
控制逻辑设备创建时序图:
在snd_card_new函数中,会调用snd_card_new函数创建声卡的Control逻辑设备:
int snd_card_new(struct device *parent, int idx, const char *xid,
struct module *module, int extra_size,
struct snd_card **card_ret)
{
......
//创建声卡的控制逻辑设备
err = snd_ctl_create(card);
......
}
snd_ctl_create函数:
int snd_ctl_create(struct snd_card *card)
{
//这个snd_device_ops不同类型的逻辑设备,其回调函数是不一样的
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register,
.dev_disconnect = snd_ctl_dev_disconnect,
};
int err;
......
//初始化Control逻辑设备的device
snd_device_initialize(&card->ctl_dev, card);
//设置Control逻辑设备device的name
dev_set_name(&card->ctl_dev, "controlC%d", card->number);
//创建Control逻辑设备的snd_device
err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
......
}
Control逻辑设备的snd_device:
注册声卡时,会调用snd_device_register_all函数注册所有的逻辑设备:
int snd_card_register(struct snd_card *card)
{
......
//注册声卡的逻辑设备
if ((err = snd_device_register_all(card)) < 0)
return err;
......
}
snd_device_register_all函数:
int snd_device_register_all(struct snd_card *card)
{
struct snd_device *dev;
int err;
......
//遍历snd_card的devices链表
list_for_each_entry(dev, &card->devices, list) {
err = __snd_device_register(dev); //注册逻辑设备
if (err < 0)
return err;
}
return 0;
}
static int __snd_device_register(struct snd_device *dev)
{
if (dev->state == SNDRV_DEV_BUILD) {
//调用snd_device_ops里的dev_register回调注册snd_device,
if (dev->ops->dev_register) {
int err = dev->ops->dev_register(dev);
if (err < 0)
return err;
}
dev->state = SNDRV_DEV_REGISTERED; //状态设置为SNDRV_DEV_REGISTERED,已注册
}
return 0;
}
对于Control逻辑设备,调用snd_ctl_dev_register函数注册:
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;
//注册snd_device
return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
&snd_ctl_f_ops, card, &card->ctl_dev);
}
对于Control逻辑设备其snd_minor设置如下:
控制逻辑设备对应的file_operations为:
static const struct file_operations snd_ctl_f_ops =
{
.owner = THIS_MODULE,
.read = snd_ctl_read,
.open = snd_ctl_open,
.release = snd_ctl_release,
.llseek = no_llseek,
.poll = snd_ctl_poll,
.unlocked_ioctl = snd_ctl_ioctl,
.compat_ioctl = snd_ctl_ioctl_compat,
.fasync = snd_ctl_fasync,
};
应用层:ioctl “/dev/snd/controlC0”,最终调用到snd_ctl_ioctl:
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct snd_ctl_file *ctl;
struct snd_card *card;
struct snd_kctl_ioctl *p;
void __user *argp = (void __user *)arg;
int __user *ip = argp;
int err;
ctl = file->private_data;
card = ctl->card;
......
switch (cmd) {
......
case SNDRV_CTL_IOCTL_ELEM_INFO:
return snd_ctl_elem_info_user(ctl, argp); //获取control的详细信息
case SNDRV_CTL_IOCTL_ELEM_READ:
return snd_ctl_elem_read_user(card, argp);//读取control的当前值
case SNDRV_CTL_IOCTL_ELEM_WRITE:
return snd_ctl_elem_write_user(ctl, argp);//把应用程序的控制值设置到control中
......
}
......
}
获取control的详细信息,snd_ctl_elem_info_user:
static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl,
struct snd_ctl_elem_info __user *_info)
{
struct snd_ctl_elem_info info;
int result;
......
/* 根据元素ID号在snd_card的controls链表里找到对应的snd_kcontrol,然后调用
snd_kcontrol里的info回调函数
*/
result = snd_ctl_elem_info(ctl, &info);
......
}
读取control的当前值,snd_ctl_elem_read_user:
static int snd_ctl_elem_read_user(struct snd_card *card,
struct snd_ctl_elem_value __user *_control)
{
struct snd_ctl_elem_value *control;
int result;
......
/* 根据元素ID号在snd_card的controls链表里找到对应的snd_kcontrol,然后调用
snd_kcontrol里的get回调函数
*/
result = snd_ctl_elem_read(card, control);
......
}
把应用程序的控制值设置到control中,snd_ctl_elem_write_user:
static int snd_ctl_elem_write_user(struct snd_ctl_file *file,
struct snd_ctl_elem_value __user *_control)
{
struct snd_ctl_elem_value *control;
struct snd_card *card;
int result;
......
/* 根据元素ID号在snd_card的controls链表里找到对应的snd_kcontrol,然后调用
snd_kcontrol里的put回调函数
*/
result = snd_ctl_elem_write(card, file, control);
......
}