Linux ALSA驱动框架分析之(三):Control逻辑设备的创建

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);

	......
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值