alsa音频架构

alsa音频架构1

第一部分 alsa子系统关键结构体对象等

1.声卡设备类型定义

#define	SNDRV_DEV_TOPLEVEL	((__force snd_device_type_t) 0)
#define	SNDRV_DEV_CONTROL	((__force snd_device_type_t) 1)	
#define	SNDRV_DEV_LOWLEVEL_PRE	((__force snd_device_type_t) 2)
#define	SNDRV_DEV_LOWLEVEL_NORMAL ((__force snd_device_type_t) 0x1000)
#define	SNDRV_DEV_PCM		((__force snd_device_type_t) 0x1001)
#define	SNDRV_DEV_RAWMIDI	((__force snd_device_type_t) 0x1002)
#define	SNDRV_DEV_TIMER		((__force snd_device_type_t) 0x1003)
#define	SNDRV_DEV_SEQUENCER	((__force snd_device_type_t) 0x1004)
#define	SNDRV_DEV_HWDEP		((__force snd_device_type_t) 0x1005)
#define	SNDRV_DEV_INFO		((__force snd_device_type_t) 0x1006)
#define	SNDRV_DEV_BUS		((__force snd_device_type_t) 0x1007)
#define	SNDRV_DEV_CODEC		((__force snd_device_type_t) 0x1008)
#define	SNDRV_DEV_JACK      ((__force snd_device_type_t) 0x1009)
#define	SNDRV_DEV_LOWLEVEL	((__force snd_device_type_t) 0x2000)

一个声卡可以有多个声卡设备,alsa中用snd_card描述声卡对象,用snd_device描述声卡设备对象

2.声卡结构体

struct snd_card {
	int number;				//声卡索引号
	char id[16];			//id识别字串
	char driver[16];		//驱动名
	char shortname[32];		//短名
	char longname[80];		//长名
	char mixername[80];		/* mixer name */
	char components[128];	/* card components delimited with space */
	struct module *module;	//模块所有者
	void *private_data;		/* private data for soundcard */
	void (*private_free) (struct snd_card *card); /* callback for freeing of private data */
	struct list_head devices;	//设备链表
	unsigned int last_numid;	/* last used numeric ID */
	struct rw_semaphore controls_rwsem;	/* controls list lock */
	rwlock_t ctl_files_rwlock;	/* ctl_files list lock */
	int controls_count;		/* count of all controls */
	int user_ctl_count;		/* count of all user controls */
	struct list_head controls;	//控制链表
	struct list_head ctl_files;	/* active control files */
	struct snd_info_entry *proc_root;	/* root for soundcard specific files */
	struct snd_info_entry *proc_id;	/* the card id */
	struct proc_dir_entry *proc_root_link;	/* number link to real id */
	struct list_head files_list;	/* all files associated to this card */
	struct snd_shutdown_f_ops *s_f_ops; /* file operations in the shutdown state */
	spinlock_t files_lock;		/* lock the files for this card */
	int shutdown;			/* this card is going down */
	int free_on_last_close;		/* free in context of file_release */
	wait_queue_head_t shutdown_sleep;
	struct device *dev;		//设备文件
	struct device *card_dev;	//声卡设备文件
#ifdef CONFIG_PM
	unsigned int power_state;	/* power state */
	struct mutex power_lock;	/* power lock */
	wait_queue_head_t power_sleep;
#endif
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
	struct snd_mixer_oss *mixer_oss;
	int mixer_oss_change_count;
#endif
};


2.1.全局变量snd_cards

在"/sound/core/init.c"

struct snd_card *snd_cards[SNDRV_CARDS];

SNDRV_CARDS为8也就是声卡最多8个

3.声卡设备结构体

struct snd_device {
	struct list_head list;		//链表
	struct snd_card *card;		//所属的声卡
	snd_device_state_t state;	//设备状态
	snd_device_type_t type;		//设备类型
	void *device_data;		/* device structure */
	struct snd_device_ops *ops;	//声卡设备操作函数集
};

3.1 设备状态的值

#define	SNDRV_DEV_BUILD		((__force snd_device_state_t) 0)//创建
#define	SNDRV_DEV_REGISTERED	((__force snd_device_state_t) 1)//注册
#define	SNDRV_DEV_DISCONNECTED	((__force snd_device_state_t) 2)//断开连接

3.2 设备类型
也就是上面 1.声卡设备类型定义 所指定的类型

4.声卡操作函数集

struct snd_device_ops {
	int (*dev_free)(struct snd_device *dev);		//释放
	int (*dev_register)(struct snd_device *dev);	//注册
	int (*dev_disconnect)(struct snd_device *dev);	//断开连接
};


第二部分 声卡

1.声卡创建

传递进来的idx为负值,则系统会分配一个idx作为全局snd_cards数组的索引项值,xid字串用来区分描述声卡id,module一般为THIS_MODULE...

int snd_card_create(int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)
{
	struct snd_card *card;
	int err, idx2;
	if (snd_BUG_ON(!card_ret))
		return -EINVAL;
	*card_ret = NULL;
	if (extra_size < 0)
		extra_size = 0;
	card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);	//分配声卡对象和额外空间的内存
	if (!card)
		return -ENOMEM;
	if (xid)	//若需要填充声卡id识别字串
		strlcpy(card->id, xid, sizeof(card->id));	//card->id=xid 声卡id识别字串
	err = 0;
	
	mutex_lock(&snd_card_mutex);	//idx为负值则交由系统选择一个值______________________<
	if (idx < 0) {	
		for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
			/* idx == -1 == 0xffff means: take any free slot */
			if (~snd_cards_lock & idx & 1<<idx2) {
				if (module_slot_match(module, idx2)) {
					idx = idx2;
					break;
				}
			}
	}
	if (idx < 0) {
		for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++)
			/* idx == -1 == 0xffff means: take any free slot */
			if (~snd_cards_lock & idx & 1<<idx2) {
				if (!slots[idx2] || !*slots[idx2]) {
					idx = idx2;
					break;
				}
			}
	}
	if (idx < 0)
		err = -ENODEV;
	else if (idx < snd_ecards_limit) {
		if (snd_cards_lock & (1 << idx))
			err = -EBUSY;	/* invalid */
	} else if (idx >= SNDRV_CARDS)
		err = -ENODEV;
	if (err < 0) {
		mutex_unlock(&snd_card_mutex);
		snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i), error: %d\n",idx, snd_ecards_limit - 1, err);
		goto __error;
	}
	snd_cards_lock |= 1 << idx;		/* lock it */
	if (idx >= snd_ecards_limit)
		snd_ecards_limit = idx + 1; /* increase the limit */
	mutex_unlock(&snd_card_mutex);		//______________________>
	
	card->number = idx;		//声卡对象索引号 全局数组snd_cards的数组下标
	card->module = module;	//声卡对象模块所有者THIS_MODULE
	INIT_LIST_HEAD(&card->devices);	//初始化声卡设备链表
	init_rwsem(&card->controls_rwsem);	//初始化读写信号量
	rwlock_init(&card->ctl_files_rwlock);	//初始化读写锁
	INIT_LIST_HEAD(&card->controls);	//初始化声卡控制链表
	INIT_LIST_HEAD(&card->ctl_files);	//初始化声卡控制文件链表
	spin_lock_init(&card->files_lock);	//初始化自旋锁
	INIT_LIST_HEAD(&card->files_list);	//初始化声卡文件链表
	init_waitqueue_head(&card->shutdown_sleep);	//初始化关机队列头
#ifdef CONFIG_PM
	mutex_init(&card->power_lock);	//初始化电源互斥锁
	init_waitqueue_head(&card->power_sleep);	//初始化睡眠等待队列头
#endif
	err = snd_ctl_create(card);	//创建用于控制的声卡设备对象
	if (err < 0) {
		snd_printk(KERN_ERR "unable to register control minors\n");
		goto __error;
	}
	err = snd_info_card_create(card);	//proc下面的接口
	if (err < 0) {
		snd_printk(KERN_ERR "unable to create card info\n");
		goto __error_ctl;
	}
	if (extra_size > 0)	//有额外数据
		card->private_data = (char *)card + sizeof(struct snd_card);
	*card_ret = card;
	return 0;

      __error_ctl:
	snd_device_free_all(card, SNDRV_DEV_CMD_PRE);
      __error:
	kfree(card);
  	return err;
}
EXPORT_SYMBOL(snd_card_create);

这里主要是初始化了声卡的devices声卡设备链表,以后创建的声卡设备将挂在该链表上

并调用了snd_ctl_create创建了用于控制的声卡设备对象,这我们可以得出一个结论每个声卡都有一个声卡控制设备对象

第三部分 声卡设备

1.创建声卡设备

在这里 声卡设备与声卡捆绑,指定声卡设备类型,设置声卡设备状态,捆绑对应的snd_device_ops方法,添加声卡设备到声卡的devices链表

int snd_device_new(struct snd_card *card, snd_device_type_t type,void *device_data, struct snd_device_ops *ops)
{
	struct snd_device *dev;

	if (snd_BUG_ON(!card || !device_data || !ops))
		return -ENXIO;
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);	//分配声卡设备内存
	if (dev == NULL) {
		snd_printk(KERN_ERR "Cannot allocate device\n");
		return -ENOMEM;
	}
	dev->card = card;	//设备捆绑对象
	dev->type = type;	//指定设备类型
	dev->state = SNDRV_DEV_BUILD;	//设备状态 已创建
	dev->device_data = device_data;	//设备数据
	dev->ops = ops;	//设备操作函数集
	list_add(&dev->list, &card->devices);	//添加到声卡对象的设备devices链表中
	return 0;
}
EXPORT_SYMBOL(snd_device_new);

这里声卡设备的snd_device_ops是传递进来的结构体指针,实际操作中一般调用以下API来创建不同类型的声卡

1.1 创建声卡设备常见API

snd_ctl_create		-->snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
snd_card_proc_new	-->snd_device_new(card, SNDRV_DEV_INFO, entry, &ops)
snd_timer_new		-->snd_device_new(card, SNDRV_DEV_TIMER, timer, &ops)
snd_jack_new		-->snd_device_new(card, SNDRV_DEV_JACK, jack, &ops)
snd_pcm_new			-->snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)
snd_rawmidi_new		-->snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops)
snd_seq_device_new	-->snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)
snd_hwdep_new		-->snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops)
snd_i2c_bus_create	-->snd_device_new(card, SNDRV_DEV_BUS, bus, &ops)
snd_hda_bus_new		-->snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops)
snd_ac97_bus		-->snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops)
...//不完整

这些API都静态设置了对应类型的snd_device_ops结构体ops,然后调用snd_device_new并将ops传递进来
后面我们再针对不同的声卡设备类型展开分析

创建声卡设备后系统大致入下图描述

创建后的声卡设备的state值为SNDRV_DEV_BUILD

 

第四部分 注册声卡

1.注册声卡

int snd_card_register(struct snd_card *card)
{
	int err;
	if (snd_BUG_ON(!card))
		return -EINVAL;
	if (!card->card_dev) {
		//创建设备文件"/sys/class/sound/cardX"
		card->card_dev = device_create(sound_class, card->dev,MKDEV(0, 0), card,"card%i", card->number);
		if (IS_ERR(card->card_dev))
			card->card_dev = NULL;
	}

	if ((err = snd_device_register_all(card)) < 0)	//-->1.1.注册所有的声卡设备
		return err;
	mutex_lock(&snd_card_mutex);
	if (snd_cards[card->number]) {	//判断对应数组项是否已给占用"/sound/core/init.c" struct snd_card *snd_cards[SNDRV_CARDS];
		mutex_unlock(&snd_card_mutex);
		return 0;
	}
	snd_card_set_id_no_lock(card, card->id[0] == '\0' ? NULL : card->id);
	snd_cards[card->number] = card;	//填充全局snd_cards数组
	mutex_unlock(&snd_card_mutex);
	init_info_for_card(card);	//proc接口
#if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE)
	if (snd_mixer_oss_notify_callback)
		snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER);
#endif
	if (card->card_dev) {	//创建属性文件
		err = device_create_file(card->card_dev, &card_id_attrs);
		if (err < 0)
			return err;
		err = device_create_file(card->card_dev, &card_number_attrs);
		if (err < 0)
			return err;
	}

	return 0;
}
EXPORT_SYMBOL(snd_card_register);

主要是调用snd_device_register_all函数注册所有声卡设备,其次是填充了全局snd_cards数组对应的数组项
1.1注册挂在该声卡下面的所有声卡设备

int snd_device_register_all(struct snd_card *card)
{
	struct snd_device *dev;
	int err;
	
	if (snd_BUG_ON(!card))
		return -ENXIO;
	list_for_each_entry(dev, &card->devices, list) {	//遍历声卡的设备devices链表
		if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {//状态为已建立且存在dev_register方法
			if ((err = dev->ops->dev_register(dev)) < 0)	//调用声卡设备的dev_register方法
				return err;
			dev->state = SNDRV_DEV_REGISTERED;	//修改状态为已注册
		}
	}
	return 0;
}

这里会遍历声卡对象的devices设备链表,然后调用声卡设备所捆绑的声卡设备操作函数集合的dev_register方法,注册初始化对应的声卡设备

注册完声卡后,声卡设备的state值修改为SNDRV_DEV_REGISTERED

 

第五部分 注册声卡设备

不同类型的声卡设备的操作函数集的dev_register不同,但是也有其共性,下面主要是针对共性来分析

1.snd_minor声卡字符设备结构体

struct snd_minor {
	int type;			//声卡设备类型 SNDRV_DEVICE_TYPE_XXX
	int card;			//声卡索引号
	int device;			/* device number */
	const struct file_operations *f_ops;	//文件操作函数集
	void *private_data;		//私有数据
	struct device *dev;		//设备文件
};
1.1 snd_minor的type类型
enum {
	SNDRV_DEVICE_TYPE_CONTROL,			//控制
	SNDRV_DEVICE_TYPE_SEQUENCER,		//音序器
	SNDRV_DEVICE_TYPE_TIMER,			//定时器
	SNDRV_DEVICE_TYPE_HWDEP,			//硬件依赖层
	SNDRV_DEVICE_TYPE_RAWMIDI,			//raw midi
	SNDRV_DEVICE_TYPE_PCM_PLAYBACK,		//PCM回放
	SNDRV_DEVICE_TYPE_PCM_CAPTURE,		//PCM捕捉
};
1.2 全局snd_minors全局数组,数组项最大值SNDRV_OS_MINORS为256
static struct snd_minor *snd_minors[SNDRV_OS_MINORS];


2.注册设备文件snd_register_device
封装了(2.1)snd_register_device_for_dev函数
static inline int snd_register_device(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data,const char *name)
{
	return snd_register_device_for_dev(type, card, dev, f_ops,private_data, name,snd_card_get_device_link(card));
}
2.1注册设备文件

int snd_register_device_for_dev(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,
				void *private_data,const char *name, struct device *device)
{
	int minor;	//次设备号
	struct snd_minor *preg;	//声明一个snd_minor结构体

	if (snd_BUG_ON(!name))
		return -EINVAL;
	preg = kmalloc(sizeof *preg, GFP_KERNEL);	//分配snd_minor结构体内存
	if (preg == NULL)
		return -ENOMEM;
	preg->type = type;	//设置snd_minor类型
	preg->card = card ? card->number : -1;	//声卡索引号
	preg->device = dev;	//设备文件
	preg->f_ops = f_ops;	//文件操作函数集合
	preg->private_data = private_data;	//私有数据
	mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS
	minor = snd_find_free_minor();
#else
	minor = snd_kernel_minor(type, card, dev);	//获取次设备号
	if (minor >= 0 && snd_minors[minor])
		minor = -EBUSY;
#endif
	if (minor < 0) {
		mutex_unlock(&sound_mutex);
		kfree(preg);
		return minor;
	}
	snd_minors[minor] = preg;	//填充全局snd_minors数组项
	preg->dev = device_create(sound_class, device, MKDEV(major, minor),private_data, "%s", name);	//创建"/dev/snd/XXX"
	if (IS_ERR(preg->dev)) {
		snd_minors[minor] = NULL;
		mutex_unlock(&sound_mutex);
		minor = PTR_ERR(preg->dev);
		kfree(preg);
		return minor;
	}
	mutex_unlock(&sound_mutex);
	return 0;
}
EXPORT_SYMBOL(snd_register_device_for_dev);
主要是获取次设备号,并创建设备文件,捆绑文件操作函数集合

第六部分 声卡驱动的编写框架

综合上面五个部分得出下图


编写过程为先调用snd_card_create创建声卡,接着调用创建声卡设备的API创建不同类型的声卡设备组件,接着调用snd_card_register注册声卡就行.

 大致走完上面的流程后系统的框图


 

第七部分 声卡核心子系统的初始化工作

1.声明子系统

subsys_initcall(init_soundcore);

2.子系统初始化

static int __init init_soundcore(void)
{
	int rc;
	rc = init_oss_soundcore();//初始化oss子系统部分
	if (rc)
		return rc;
	sound_class = class_create(THIS_MODULE, "sound");	//创建设备类"/sys/class/sound/"
	if (IS_ERR(sound_class)) {
		cleanup_oss_soundcore();
		return PTR_ERR(sound_class);
	}
	sound_class->devnode = sound_devnode;	//创建设备节点的方法
	return 0;
}

主要初始化oss子系统部分
2.1 oss子系统初始化

static int __init init_oss_soundcore(void)
{
	if (preclaim_oss && register_chrdev(SOUND_MAJOR, "sound", &soundcore_fops) == -1) {//创建字符设备
		printk(KERN_ERR "soundcore: sound device already in use.\n");
		return -EBUSY;
	}
	return 0;
}

2.2 指定sound_class类的创建设备节点方法sound_devnode

static char *sound_devnode(struct device *dev, mode_t *mode)
{
	if (MAJOR(dev->devt) == SOUND_MAJOR)	//主设备号14 oss子系统
		return NULL;
	return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));	//alsa子系统 "/dev/sndX/"
}

所以alsa子系统的音频设备会出现在/dev/snd/目录下
这里我们可以得知alsa架构的设备节点在/dev/snd/目录下,oss架构的设备节点在/dev下

alsa的主设备号为116,oss架构的主设备号为14

alsa的主设备号在/sound/core/sound.c中定义

static int major = CONFIG_SND_MAJOR;
#define CONFIG_SND_MAJOR	116


第八部分 声卡控制设备浅析 

前面讲到每个声卡都有一个声卡控制设备对象,所以研究下声卡控制设备

在声卡创建函数snd_card_create中调用了snd_ctl_create函数创建声卡控制设备,并将声卡对象作为参数传递进来

1.创建声卡控制设备

int snd_ctl_create(struct snd_card *card)
{
	static struct snd_device_ops ops = {//静态初始化snd_device_ops声卡设备操作函数集结构体
		.dev_free = snd_ctl_dev_free,//释放方法
		.dev_register =	snd_ctl_dev_register,//注册方法
		.dev_disconnect = snd_ctl_dev_disconnect,//断开连接方法
	};

	if (snd_BUG_ON(!card))
		return -ENXIO;
	return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);//创建声卡控制设备
}

这里还需注意一下snd_device_new函数的参数,发现声卡控制设备的device_data是指向声卡对象的

在注册声卡过程中会调用snd_device_register_all函数,该函数则调用声卡控制设备dev_register方法,既snd_ctl_dev_register函数

2.注册声卡控制设备

static int snd_ctl_dev_register(struct snd_device *device)
{
	struct snd_card *card = device->device_data;	//获取声卡对象
	int err, cardnum;
	char name[16];

	if (snd_BUG_ON(!card))
		return -ENXIO;
	cardnum = card->number;	//获取声卡索引号
	if (snd_BUG_ON(cardnum < 0 || cardnum >= SNDRV_CARDS))
		return -ENXIO;
	sprintf(name, "controlC%i", cardnum);	//设置名字-->"/dev/snd/controlCx"
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,&snd_ctl_f_ops, card, name)) < 0)	//注册声卡控制设备
		return err;
	return 0;
}

通过snd_register_device创建了/dev/snd/controlC0设备文件,假设是0号声卡吧!并捆绑了snd_ctl_f_ops设备文件操作函数集

3.声卡控制设备对应的设备文件操作函数集

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,	//32位兼容的命令控制
	.fasync =	snd_ctl_fasync,	//同步方法
};

这样就提供了应用层的接口方法了,比较重要的是命令控制方法,主要有以下控制命令

#define SNDRV_CTL_IOCTL_PVERSION	_IOR('U', 0x00, int)//打印alsa版本
#define SNDRV_CTL_IOCTL_CARD_INFO	_IOR('U', 0x01, struct snd_ctl_card_info)//获取声卡信息
#define SNDRV_CTL_IOCTL_ELEM_LIST	_IOWR('U', 0x10, struct snd_ctl_elem_list)
#define SNDRV_CTL_IOCTL_ELEM_INFO	_IOWR('U', 0x11, struct snd_ctl_elem_info)
#define SNDRV_CTL_IOCTL_ELEM_READ	_IOWR('U', 0x12, struct snd_ctl_elem_value)
#define SNDRV_CTL_IOCTL_ELEM_WRITE	_IOWR('U', 0x13, struct snd_ctl_elem_value)
#define SNDRV_CTL_IOCTL_ELEM_LOCK	_IOW('U', 0x14, struct snd_ctl_elem_id)
#define SNDRV_CTL_IOCTL_ELEM_UNLOCK	_IOW('U', 0x15, struct snd_ctl_elem_id)
#define SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS _IOWR('U', 0x16, int)
#define SNDRV_CTL_IOCTL_ELEM_ADD	_IOWR('U', 0x17, struct snd_ctl_elem_info)
#define SNDRV_CTL_IOCTL_ELEM_REPLACE	_IOWR('U', 0x18, struct snd_ctl_elem_info)
#define SNDRV_CTL_IOCTL_ELEM_REMOVE	_IOWR('U', 0x19, struct snd_ctl_elem_id)
#define SNDRV_CTL_IOCTL_TLV_READ	_IOWR('U', 0x1a, struct snd_ctl_tlv)
#define SNDRV_CTL_IOCTL_TLV_WRITE	_IOWR('U', 0x1b, struct snd_ctl_tlv)
#define SNDRV_CTL_IOCTL_TLV_COMMAND	_IOWR('U', 0x1c, struct snd_ctl_tlv)
#define SNDRV_CTL_IOCTL_POWER	_IOWR('U', 0xd0, int)//还没支持
#define SNDRV_CTL_IOCTL_POWER_STATE	_IOR('U', 0xd1, int)//电源状态

如何与应用层交互参考alsa-lib的说明http://www.alsa-project.org/main/index.php/Main_Page
alsa提供了很多工具可以使用
alsa-utils工具集

aconnect  		is a utility for connecting and disconnecting two existing ports in the ALSA sequencer system.  
alsaconf  		is a configuration tool which tries to detect the sound cards on your system and write a suitable configuration file for ALSA. This program is incompatible with Udev. 
alsactl  		is used to control advanced settings for the ALSA sound card drivers.  
alsaloop  		allows creation of a PCM loopback between a PCM capture device and a PCM playback device. 
alsamixer  		is an Ncurses based mixer program for use with the ALSA sound card drivers. 
amidi  			is used to read from and write to ALSA RawMIDI ports. 
amixer  		allows command-line control of the mixers for the ALSA sound card drivers. 
aplay  			is a command-line soundfile player for the ALSA sound card drivers. 
aplaymidi 		is a command-line utility that plays the specified MIDI file(s) to one or more ALSA sequencer ports. 
arecord  		is a command-line soundfile recorder for the ALSA sound card drivers. 
arecordmidi 	is a command-line utility that records a standard MIDI file from one or more ALSA sequencer ports. 
aseqdump  		is a command-line utility that prints the sequencer events it receives as text.  
aseqnet 		is an ALSA sequencer client which sends and receives event packets over a network. 
iecset 			is a small utility to set or dump the IEC958 (or so-called “S/PDIF”) status bits of the specified sound card via the ALSA control API. 
speaker-test 	is a command-line speaker test tone generator for ALSA.

还有一个带图形界面的alsamixer工具,相当强大

 alsa-utils工具的使用

alsact

l用来对alsa声卡驱动进行一些高级的设置.系统中装有多个声卡,它也可以支持.
有时在音量控制面板无法调整的选项,可以使用alsactl来实现.
alsactl可以将指定声卡的驱动程序设置信息保存到配置文件.或从配置文件中恢复指定
声卡的驱动程序的设置信息.

alsactl格式:

       alsactl [options] [store|restore] <card # or id>

选项:

       -h, --help
		打印帮助信息

        -f, --file
		指定使用的配置文件,默认为/etc/asound.state.

       -F, --force
		与恢复命令一起使用.表示最大限度的恢复设置值.

       -d, --debug
		调试模式,输出更多细节信息.

       -v, --version
		打印alsactl版本号.

文件:
	/etc/asound.state(或使用-f指定的文件)保存有声卡所有混合器的设置信息.

示例:

# rm /etc/asound.state -f
# alsactl store

 

aconnect

是ALSA音序器的连接管理器.用来连接或断开ALSA音序器上的端口.端口是
可以随意定义的.
如,使用aconnect可以连接到任何由aseqview建立的设备端口.

命令格式:

       aconnect [-d] [-options] sender receiver
       aconnect -i|-o [-options]
       aconnect -x

选项:
连接管理
       -d, --disconnect
		断开连接.

       -e, --exclusive
		使用独占模式连接端口.发送和接收端口将不能再与其他端口相连.

       -r, --real queue
		将时间包的时间戳,转换为真实时间队列的当前值.

显示端口
       -i, --input
		显示存在的输入端口.

       -o, --output
		显示存在的输出端口.

       -l, --list
		显示当前的连接状态.

删除连接
       -x, --removeall
		删除所有连接.

示例:

连接端口64:0到65:0:
           % aconnect 64:0 65:0
这个连接是单向的,所有到发送端口64:0的数据,将被重定向到接收65:0端口.如果有另一个端口65:1,也使用64:0作为发送端口,则数据会同时发送到2个接收端口.
端口连接时,使用:
           % aconnect -d 64:0 65:0

地址也可以使用客户端的名字来代替:
           % aconnect External:0 Emu8000:1

使用-i打印出输入端口信息.-o打印出输出端口信息.
           % aconnect -i
           client 0: ’System’ [type=kernel]
               0 ’Timer           ’
               1 ’Announce        ’
           client 64: ’External MIDI-0’ [type=kernel]
               0 ’MIDI 0-0        ’

可以使用-x选项来清除所有的连接.
           % aconnect -x

 

alsamixer

是一个终端界面的声卡音量调节器.

命令格式:
       alsamixer [options]

选项:
       -h, -help
		显示帮助信息.

       -c <card number or idenfication>
		指定需要设置的声卡.默认为0.

       -D <device identification>
		选择需要控制的调节器.

       -g
		设置界面颜色.

       -s
		最小化界面窗口.

快捷键:
	进入alsamixer界面后,可以使用下面快捷键进行控制:

   常规控制:
	左右箭头或n,p	用来选择通道.

	上下箭头或+,-	同时调整选定通道的左右声道的音量.

	B,=	设置左右声道音量相同.

	M	静音当前通道.<,>分别对左,右声道静音.

	空格	选择录音源.在选定的通道上按"空格",可以标记此通道为录音源.此操作仅限输入设备.插入键或";",删除键或"'"分别选定左右通道.

	L	刷新屏幕.

  快捷设置
          PageUp	增大5格音量.

         PageDown	减小5格音量.
	
         End	设置音量为0.

	分别调整左,右或整个通道的音量.
	Q,W,E	增大 左,右,通道 的音量.

	Z,X,C	减小 左,右,通道 的音量.

	alt-q,ESC	退出.
 

amidi

的作用是对ALSA的RawMIDI端口进行读写.
amidi是一个命令行工具,允许你以独占模式向MIDI设备读/写数据.

命令格式:
       amidi options

选项:
	-h,-V,-l,-L	用于显示信息.
	-s,-r,-S,-d	用于发送/接收数据.

       -h, --help
		打印帮助信息.

       -V, --version
		打印版本号.

       -l, --list-devices
		打印所有硬件MIDI端口的列表.

       -L, --list-rawmidis
		打印所有RawMIDI定义.

       -p, --port=name
		设置要使用的ALSA RawMIDI端口.若不指定,则使用声卡0的端口0.

       -s, --send=filename
		发送指定文件的内容到MIDI端口.文件中必须包含raw MIDI命令(.syx,.mid文件).

       -r, --receive=filename
		将MIDI端口接收的数据写入指定文件.

       -S, --send-hex="..."
		发送十六进制字节到MIDI端口.

       -d, --dump
		从MIDI端口接收数据,然后以十六进制形式打印出来.

       -t, --timeout=秒
		指定超时,当端口无数据输出达到超时时长时,将停止接收数据.

示例:

       amidi -p hw:0 -s my_settings.syx
		发送my_settings.syx终端MIDI命令到端口 hw:0.

       amidi -S ’
		发送XG复位到默认端口.

       amidi -p virtual -d
		建立一个虚拟RawMIDI端口,然后发送所有数据到这个端口.

 

amixer

是命令行的ALSA声卡驱动调节器工具.
amixer用来在命令行控制ALSA的调节器,并且支持多声卡.
amixer不加参数时,将打印默认声卡的设置信息.

命令格式:

       amixer [-c card] [cmd]

命令:
       help   显示语法帮助.

       info   显示调节器设备的信息.

       scontrols	显示调节器器的完整列表 .

       scontents	显示包含详细信息的调节器的完整列表.

       set or sset <SCONTROL> <PARAMETER> ...设置调节器信息.

       get or sget <SCONTROL> 显示调节器的信息.

       controls	显示声卡控制器的信息.
		
       contents	显示完整的声卡控制器信息.

       cset <CONTROL> <PARAMETER> ... 设置声卡控制器信息.

       cget <CONTROL>	显示声卡控制器的信息.

选项:
       [-c card]
		选择指定的声卡.

       [-D device]
		选择需要控制的设备名.默认是 default.

       -h     Help
		显示帮助信息.

       -q
		安静模式.不输出设置结果.

示例:

      # amixer -c 1 sset Line,0 80%,40% unmute cap
	设置第2块声卡的"line"的左声道音量为80%,右声道为40%,取消静音,并设置它为声音源.

      # amixer -c 2 cset numid=34 40%
	设置第34个声卡元素为40%.

 

arecord,aplay

是命令行的ALSA声卡驱动的录音和播放工具.
arecord是命令行ALSA声卡驱动的录音程序.支持多种文件格式和多个声卡.
aplay是命令行播放工具,支持多种文件格式.

命令格式:

       arecord [flags] [filename]
       aplay [flags] [filename [filename]] ...

选项:
       -h, --help
             帮助.

       --version
              打印版本信息.

       -l, --list-devices
              列出全部声卡和数字音频设备.

       -L, --list-pcms
              列出全部PCM定义.

       -D, --device=NAME
	     指定PCM设备名称.

       -q --quiet
		安静模式.

       -t, --file-type TYPE
		文件类型(voc,wav,raw或au).

       -c, --channels=#
		设置通道号.

       -f --format=FORMAT
	     设置格式.格式包括:S8  U8  S16_LE  S16_BE  U16_LE U16_BE  S24_LE S24_BE U24_LE U24_BE S32_LE S32_BE U32_LE U32_BE
              FLOAT_LE  FLOAT_BE  FLOAT64_LE  FLOAT64_BE   IEC958_SUBFRAME_LE IEC958_SUBFRAME_BE MU_LAW A_LAW IMA_ADPCM MPEG GSM

       -r, --rate=#<Hz>
		设置频率.

       -d, --duration=#
		设置持续时间,单位为秒.

       -s, --sleep-min=#
		设置最小休眠时间.

       -M, --mmap
		mmap流.

       -N, --nonblock
		设置为非块模式.

       -B, --buffer-time=#
		缓冲持续时长.单位为微妙.

       -v, --verbose
		显示PCM结构和设置.

       -I, --separate-channels
		设置为每个通道一个单独文件.

示例:

       aplay -c 1 -t raw -r 22050 -f mu_law foobar
	播放raw文件foobar.以22050Hz,单声道,8位,mu_law格式.

       arecord -d 10 -f cd -t wav -D copy foobar.wav
	以CD质量录制foobar.wav文件10秒钟.使用PCM的"copy".

 

aplaymidi

用来播放标准的MIDI文件.
aplaymidi是一个命令行工具,可以在一个或多个ALSA端口上播放MIDI
文件.

命令格式:

       aplaymidi -p client:port[,...] [-d delay] midifile ...

选项:
       -h, --help
              输出帮助信息.

       -V, --version
              输出版本信息.

       -l, --list
              输出可以使用的输出端口列表.

       -p, --port=client:port,...
	    设置端口.

       -d, --delay=seconds
	    设置MIDI文件结束后,等待时长.

 

arecordmidi

用于录制标准的MIDI文件.
arecordmidi可以从一个或多个ALSA端口上,录制一个标准MIDI文件.

命令格式:

       arecordmidi -p client:port[,...] [options] midifile

选项:
       -h,--help
              打印帮助信息.

       -V,--version
              打印版本号.

       -l,--list
              打印可以使用的输入端口.

       -p,--port=client:port,...
	     设置端口.

       -b,--bpm=beats
	     设置MIDI文件的速率,默认为120 BPM.

       -f,--fps=frames
	      设置帧率.

       -s,--split-channels
	      设置每个通道将录制成一个单独的MIDI文件.

       -d,--dump
	      在标准输出上,以文本形式显示接受到的事件信息

 

aseqnet

是ALSA调节器的网络连接工具.
aseqnet是ALSA调节器的客户端程序,可以从网络上发送和接收事件数据包.
网络上有主机A,主机B.A为服务器端,B为客户端.ALSA调节器系统必须同事运行
在两个服务器上.然后建立服务器端口:

hostA% aseqnet
 sequencer opened: 128:0

在HostB上执行:

           hostB% aseqnet hostA
           sequencer opened: 132:0

现在所有发送到HostA:128:0的数据将被传送到HostB:132:0上,反之亦然.

命令格式:

       aseqnet [remotehost]

选项:
       -p port
		指定TCP端口号或服务名.

       -s addr
		设置指定地址用于读操作.

       -d addr
		设置指定地址用于写操作.
       -v
		详细输出模式.

 

设置或输出IEC958状态位.
iecset

是个小工具,通过ALSA的API,设置或输出IEC958(或称S/PDIF)状态位信息.
直接运行iecset将输出当前IEC958的状态信息. 命令格式:

       iecset [options] [cmd arg...]

选项:
       -D device
		设置需要打开的设备名.

       -c card
		设置需要打开的网卡名.

       -x
		输出AESx字节格式的状态信息.

       -i
		从标准输入读取命令信息,每行一个命令.

命令:
       professional <bool>
		专业模式(true)或用户模式(false).

       audio <bool>
		音频模式(true).

       rate <int>
		采样频率,单位Hz.

       emphasis <int>
		设置加强值.0 = none, 1 = 50/15us, 2 = CCITT.

       lock <bool>
		速率锁.

       sbits <int>
		采样位:2 = 20bit, 4 = 24bit, 6 = undefined.

       wordlength <int>
		设置字长:0  =  No,  2 = 22-18 bit, 4 = 23-19 bit, 5 = 24-20
              bit, 6 = 20-16 bit. 

       category <int>
		分类:值从0到0x7f.

       copyright <bool>
		设置是否包含版权.

       original <boo>
		原始标记:

示例:

输出当前IEC958信息.
$ iecset
	Mode: consumer
	Data: audio
	Rate: 44100 Hz
	Copyright: permitted
	Emphasis: none
	Category: general
	Original: 1st generation
	Clock: 1000 ppm

显示当前第1块声卡的IEC958状态位.
$ iecset -Dhw:0
	Mode: consumer
	Data: non-audio
	Rate: 44100 Hz
	Copyright: permitted
	Emphasis: none
	Category: general
	Original: 1st generation
	Clock: 1000 ppm

设置当前为用户模式,并打开"非音频"位.
$ iecset pro off audio off
	Mode: consumer
	Data: non-audio
	Rate: 44100 Hz
	Copyright: permitted
	Emphasis: none
	Category: general
	Original: 1st generation
	Clock: 1000 ppm

 

speaker-test

是一个针对 ALSA驱动的声音测试工具.
speaker-test可以分别对左右声道进行单独的测试.

命令格式:

       speaker-test [-options]

选项:
       -c | --channels NUM
		设置通道数目.

       -D | --device NAME
		设置使用的PCM设备名.

       -f | --frequency FREQ
		设置声音频率.

       --help
		输出帮助信息.

       -b | --buffer TIME
		设置缓冲区时长.0为使用最大的缓冲区大小.

       -p | --period TIME
		设置节拍为多少微秒.

       -r | --rate RATE
		设置音频率.

       -t | --test pink|sine|wav
              -t pink	表示测试时使用噪声.
              -t sine	表示测试时使用音频信号声.
              -t wav	表示测试时使用WAV文件.

       -l | --nloops COUNT
		设置测试循环的次数.

       -w | --wavfile
		设置测试时播放的wav文件.

       -W | --wavdir
		设置一个包含wav文件的目录.默认为/usr/share/sounds/alsa.

示例:

在一个音频接口上进行立体声测试
#  speaker-test -Dplug:front -c2

在两个音频接口上进行4声道测试.
#  speaker-test -Dplug:surround40 -c4

在立体声接口上进行5.1声道测试.
# speaker-test -Dplug:surround51 -c6

测试低音扬声器.
# speaker-test -Dplug:surround51 -c6 -s1 -f75

 

alsa音频架构2--ASoc

设计ASoc的目的是为嵌入式系统片上处理器音频单元或外部的音频解码芯片提供更好的ALSA支持

ASoC有多个组件组成snd_soc_platform/snd_soc_codec/snd_soc_dai/snd_soc_card以及ALSA的snd_pcm

snd_soc_platform和snd_soc_codec就行平台与设备的关系缺一不可,snd_soc_card是它们实例化的一个对象

snd_soc_dai是snd_soc_platform和snd_soc_codec的数字音频接口,snd_soc_codec的dai为codec_dai,snd_soc_platform的dai为cpu_dai

snd_pcm是snd_soc_card实例化后注册的声卡类型

在sound/soc/soc-core.c中初始化了上面提到的4个重要结构体的链表头

static LIST_HEAD(card_list);
static LIST_HEAD(dai_list);
static LIST_HEAD(platform_list);
static LIST_HEAD(codec_list);

 

第九部分 soc声卡设备snd_soc_card

1.soc声卡设备

struct snd_soc_card {
	const char *name;	//设备名
	struct device *dev;	//设备文件
	struct snd_card *snd_card;	//所属声卡
	struct module *owner;
	struct list_head list;
	struct mutex mutex;
	bool instantiated;	//实例化标志
	int (*probe)(struct platform_device *pdev);
	int (*remove)(struct platform_device *pdev);
	/* the pre and post PM functions are used to do any PM work before and after the codec and DAI's do any PM work. */
	int (*suspend_pre)(struct platform_device *pdev, pm_message_t state);
	int (*suspend_post)(struct platform_device *pdev, pm_message_t state);
	int (*resume_pre)(struct platform_device *pdev);
	int (*resume_post)(struct platform_device *pdev);
	/* callbacks */
	int (*set_bias_level)(struct snd_soc_card *,enum snd_soc_bias_level level);
	long pmdown_time;
	/* CPU <--> Codec DAI links  */
	struct snd_soc_dai_link *dai_link;	//dai link
	int num_links;
	struct snd_soc_pcm_runtime *rtd;
	int num_rtd;
	struct work_struct deferred_resume_work;
	/* lists of probed devices belonging to this card */
	struct list_head codec_dev_list;
	struct list_head platform_dev_list;
	struct list_head dai_dev_list;
};

snd_soc_card包含了snd_card,可以理解为声卡驱动的一个封装.

2.soc pcm

struct snd_soc_pcm_runtime  {
	struct device dev;			//设备文件
	struct snd_soc_card *card;	//soc声卡设备
	struct snd_soc_dai_link *dai_link;	//dai link
	unsigned int complete:1;
	unsigned int dev_registered:1;
	/* Symmetry data - only valid if symmetry is being enforced */
	unsigned int rate;
	long pmdown_time;
	/* runtime devices */
	struct snd_pcm *pcm;	//pcm结构体
	struct snd_soc_codec *codec;	//codec设备
	struct snd_soc_platform *platform;	//soc平台设备
	struct snd_soc_dai *codec_dai;	//dai设备 codec
	struct snd_soc_dai *cpu_dai;	//dai设备 cpu
	struct delayed_work delayed_work;
};

snd_soc_pcm_runtime结构体中包含一个snd_pcm结构体,所以可以认为它是pcm声卡设备的一个封装,其次他也是Asoc各个组件的一个关系网点

3.soc声卡设备的匹配过程

在sound/soc/soc-core.c中定义了一个平台设备驱动

static struct platform_driver soc_driver = {
	.driver		= {
		.name		= "soc-audio",
		.owner		= THIS_MODULE,
		.pm		= &soc_pm_ops,
	},
	.probe		= soc_probe,
	.remove		= soc_remove,
};

我们知道平台设备驱动和平台设备的匹配靠.driver.name名字,也就是在另一处代码中必须定义了平台设备platform_device且设备名必须为"soc-audio",

这样平台设备和驱动才能匹配,才会调用平台驱动的probe方法,既soc_probe

所以一般声卡的驱动程序中会按以下格式设计平台设备

int ret;
static struct platform_device *xxx;
xxx=platform_device_alloc("soc-audio", 0);	//分配平台驱动
//平台资源的添加
ret=platform_device_add(xxx);	//添加平台设备
if(ret)
	platform_device_put(xxx);	//增加引用计数

 

4.匹配调用的probe方法soc_probe

static int soc_probe(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);	//获取soc声卡设备
	int ret = 0;
	/* Bodge while we unpick instantiation */
	card->dev = &pdev->dev;
	INIT_LIST_HEAD(&card->dai_dev_list);		//初始化dai_dev_list链表
	INIT_LIST_HEAD(&card->codec_dev_list);		//初始化codec_dev_list链表
	INIT_LIST_HEAD(&card->platform_dev_list);	//初始化platform_dev_list链表
	ret = snd_soc_register_card(card);	//注册soc声卡设备
	if (ret != 0) {
		dev_err(&pdev->dev, "Failed to register card\n");
		return ret;
	}
	return 0;
}

这里调用了snd_soc_register_card注册了soc声卡设备

5.注册soc声卡设备

static int snd_soc_register_card(struct snd_soc_card *card)
{
	int i;
	if (!card->name || !card->dev)
		return -EINVAL;
	card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * card->num_links,GFP_KERNEL);	//分配多个soc pcm内存
	if (card->rtd == NULL)
		return -ENOMEM;
	for (i = 0; i < card->num_links; i++)
		card->rtd[i].dai_link = &card->dai_link[i];	//dai link数组
	INIT_LIST_HEAD(&card->list);	
	card->instantiated = 0;	//soc声卡实例化标志设置为0
	mutex_init(&card->mutex);

	mutex_lock(&client_mutex);
	list_add(&card->list, &card_list);	//添加soc声卡到全局card_list链表
	snd_soc_instantiate_cards();	//实例化所有soc声卡
	mutex_unlock(&client_mutex);
	dev_dbg(card->dev, "Registered card '%s'\n", card->name);
	return 0;
}




最终调用snd_soc_instantiate_cards实例化所有声卡

 

第十部分 soc平台 snd_soc_platform

1.soc平台设备

struct snd_soc_platform {
	const char *name;	//设备名
	int id;	//设备id
	struct device *dev;	//设备文件
	struct snd_soc_platform_driver *driver;	//soc平台驱动
	unsigned int suspended:1; /* platform is suspended */
	unsigned int probed:1;	//"probe"标志
	struct snd_soc_card *card;	//soc声卡设备
	struct list_head list;
	struct list_head card_list;
};

2.soc平台驱动

struct snd_soc_platform_driver {
	int (*probe)(struct snd_soc_platform *);
	int (*remove)(struct snd_soc_platform *);
	int (*suspend)(struct snd_soc_dai *dai);
	int (*resume)(struct snd_soc_dai *dai);
	/* pcm creation and destruction */
	int (*pcm_new)(struct snd_card *, struct snd_soc_dai *,struct snd_pcm *);
	void (*pcm_free)(struct snd_pcm *);
	snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,struct snd_soc_dai *);
	/* platform stream ops */
	struct snd_pcm_ops *ops;
};

3.注册soc平台驱动

int snd_soc_register_platform(struct device *dev,struct snd_soc_platform_driver *platform_drv)
{
	struct snd_soc_platform *platform;	//声明soc平台设备
	dev_dbg(dev, "platform register %s\n", dev_name(dev));
	platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);	//分配soc平台内存
	if (platform == NULL)
			return -ENOMEM;
	/* create platform component name */
	platform->name = fmt_single_name(dev, &platform->id);	//设置soc平台设备名及id
	if (platform->name == NULL) {
		kfree(platform);
		return -ENOMEM;
	}
	platform->dev = dev;	//设备文件
	platform->driver = platform_drv;	//捆绑soc平台设备驱动
	mutex_lock(&client_mutex);
	list_add(&platform->list, &platform_list);	//添加到全局platform_list链表
	snd_soc_instantiate_cards();	//实例化所有soc声卡设备
	mutex_unlock(&client_mutex);
	pr_debug("Registered platform '%s'\n", platform->name);
	return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_register_platform);

注册soc平台驱动需要驱动自己去调用snd_soc_register_platform注册.


最终调用snd_soc_instantiate_cards实例化所有声卡

4.注销soc平台驱动

void snd_soc_unregister_platform(struct device *dev)
{
	struct snd_soc_platform *platform;
	list_for_each_entry(platform, &platform_list, list) {	//遍历全局platform_list链表
		if (dev == platform->dev)	//查找到匹配的soc平台
			goto found;
	}
	return;
	
found:
	mutex_lock(&client_mutex);
	list_del(&platform->list);	//移除链表
	mutex_unlock(&client_mutex);
	pr_debug("Unregistered platform '%s'\n", platform->name);
	kfree(platform->name);
	kfree(platform);
}
EXPORT_SYMBOL_GPL(snd_soc_unregister_platform);


 

第十一部分 codec设备 snd_soc_codec

1.codec设备结构体

struct snd_soc_codec {
	const char *name;	//设备名
	int id;				//设备id号
	struct device *dev;	//设备文件
	struct snd_soc_codec_driver *driver;	//所属的codec驱动
	struct mutex mutex;
	struct snd_soc_card *card;	//soc声卡设备
	struct list_head list;
	struct list_head card_list;
	int num_dai;	//dai设备(codec_dai)个数
	/* runtime */
	struct snd_ac97 *ac97;  /* for ad-hoc ac97 devices */
	unsigned int active;
	unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */
	unsigned int cache_only:1;  /* Suppress writes to hardware */
	unsigned int cache_sync:1; /* Cache needs to be synced to hardware */
	unsigned int suspended:1; /* Codec is in suspend PM state */
	unsigned int probed:1; //"probe"标志
	unsigned int ac97_registered:1; /* Codec has been AC97 registered */
	unsigned int ac97_created:1; /* Codec has been created by SoC */
	unsigned int sysfs_registered:1; /* codec has been sysfs registered */
	/* codec IO */
	void *control_data; /* codec control (i2c/3wire) data */
	hw_write_t hw_write;
	unsigned int (*hw_read)(struct snd_soc_codec *, unsigned int);
	void *reg_cache;	//cache
	/* dapm */
	u32 pop_time;
	struct list_head dapm_widgets;
	struct list_head dapm_paths;
	enum snd_soc_bias_level bias_level;
	enum snd_soc_bias_level suspend_bias_level;
	struct delayed_work delayed_work;
#ifdef CONFIG_DEBUG_FS
	struct dentry *debugfs_codec_root;
	struct dentry *debugfs_reg;
	struct dentry *debugfs_pop_time;
	struct dentry *debugfs_dapm;
#endif
};

2.codec驱动结构体

struct snd_soc_codec_driver {
	/* driver ops */
	int (*probe)(struct snd_soc_codec *);
	int (*remove)(struct snd_soc_codec *);
	int (*suspend)(struct snd_soc_codec *,pm_message_t state);
	int (*resume)(struct snd_soc_codec *);
	/* codec IO */
	unsigned int (*read)(struct snd_soc_codec *, unsigned int);
	int (*write)(struct snd_soc_codec *, unsigned int, unsigned int);
	int (*display_register)(struct snd_soc_codec *, char *,size_t, unsigned int);
	int (*volatile_register)(unsigned int);
	int (*readable_register)(unsigned int);
	short reg_cache_size;
	short reg_cache_step;
	short reg_word_size;
	const void *reg_cache_default;
	/* codec bias level */
	int (*set_bias_level)(struct snd_soc_codec *,enum snd_soc_bias_level level);
};


3.注册codec驱动

int snd_soc_register_codec(struct device *dev,struct snd_soc_codec_driver *codec_drv,struct snd_soc_dai_driver *dai_drv, int num_dai)
{
	struct snd_soc_codec *codec;	//声明codec设备
	int ret, i;
	dev_dbg(dev, "codec register %s\n", dev_name(dev));
	codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);	//分配codec设备内存
	if (codec == NULL)
		return -ENOMEM;
	/* create CODEC component name */
	codec->name = fmt_single_name(dev, &codec->id);	//创建codec设备名及id号
	if (codec->name == NULL) {
		kfree(codec);
		return -ENOMEM;
	}
	/* allocate CODEC register cache */	//cache设置
	if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
		if (codec_drv->reg_cache_default)
			codec->reg_cache = kmemdup(codec_drv->reg_cache_default,codec_drv->reg_cache_size * codec_drv->reg_word_size, GFP_KERNEL);
		else
			codec->reg_cache = kzalloc(codec_drv->reg_cache_size *codec_drv->reg_word_size, GFP_KERNEL);
		if (codec->reg_cache == NULL) {
			kfree(codec->name);
			kfree(codec);
			return -ENOMEM;
		}
	}
	codec->dev = dev;	//捆绑设备文件
	codec->driver = codec_drv;	//捆绑codec驱动
	codec->bias_level = SND_SOC_BIAS_OFF;
	codec->num_dai = num_dai;	//dai设备个数
	mutex_init(&codec->mutex);
	INIT_LIST_HEAD(&codec->dapm_widgets);
	INIT_LIST_HEAD(&codec->dapm_paths);
	for (i = 0; i < num_dai; i++) {	//格式化dai设备驱动的playback和capture PCM流
		fixup_codec_formats(&dai_drv[i].playback);
		fixup_codec_formats(&dai_drv[i].capture);
	}
	/* register any DAIs */
	if (num_dai) {
		ret = snd_soc_register_dais(dev, dai_drv, num_dai);	//注册dai设备驱动
		if (ret < 0)
			goto error;
	}
	mutex_lock(&client_mutex);
	list_add(&codec->list, &codec_list);	//添加进全局codec_list链表
	snd_soc_instantiate_cards();	//实例化所有soc声卡设备
	mutex_unlock(&client_mutex);
	pr_debug("Registered codec '%s'\n", codec->name);
	return 0;
error:
	if (codec->reg_cache)
		kfree(codec->reg_cache);
	kfree(codec->name);
	kfree(codec);
	return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_codec);


 

这里调用了snd_soc_register_dais注册了几个dai(codec_dai),后面会解释

最终调用snd_soc_instantiate_cards实例化所有声卡

4.注销codec驱动

void snd_soc_unregister_codec(struct device *dev)
{
	struct snd_soc_codec *codec;
	int i;
	list_for_each_entry(codec, &codec_list, list) {	//遍历全局codec_list链表
		if (dev == codec->dev)	//查找到匹配的codec设备
			goto found;
	}
	return;

found:
	if (codec->num_dai)
		for (i = 0; i < codec->num_dai; i++)	//注销codec设备下的dai设备(codec_dai)
			snd_soc_unregister_dai(dev);
	mutex_lock(&client_mutex);
	list_del(&codec->list);	//移除链表
	mutex_unlock(&client_mutex);
	pr_debug("Unregistered codec '%s'\n", codec->name);
	if (codec->reg_cache)
		kfree(codec->reg_cache);
	kfree(codec->name);
	kfree(codec);
}
EXPORT_SYMBOL_GPL(snd_soc_unregister_codec);


第十二部分 dai数字音频接口(dai声卡设备) snd_soc_dai

1.dai声卡设备(数字音频接口)

struct snd_soc_dai {
	const char *name;	//设备名
	int id;				//设备id
	struct device *dev;	//设备文件
	void *ac97_pdata;	/* platform_data for the ac97 codec */
	/* driver ops */
	struct snd_soc_dai_driver *driver;	//所属dai声卡驱动
	/* DAI runtime info */
	unsigned int capture_active:1;		/* stream is in use */
	unsigned int playback_active:1;		/* stream is in use */
	unsigned int symmetric_rates:1;
	struct snd_pcm_runtime *runtime;	//pcm runtime
	unsigned int active;
	unsigned char pop_wait:1;
	unsigned char probed:1;	//"probe"标志
	/* DAI DMA data */
	void *playback_dma_data;
	void *capture_dma_data;
	/* parent platform/codec */
	union {
		struct snd_soc_platform *platform;	//platform设备
		struct snd_soc_codec *codec;	//codec设备
	};
	struct snd_soc_card *card;	//soc声卡设备
	struct list_head list;
	struct list_head card_list;
};

2.dai声卡驱动

struct snd_soc_dai_driver {
	/* DAI description */
	const char *name;	//名字
	unsigned int id;	//设备id
	int ac97_control;
	/* DAI driver callbacks */
	int (*probe)(struct snd_soc_dai *dai);
	int (*remove)(struct snd_soc_dai *dai);
	int (*suspend)(struct snd_soc_dai *dai);
	int (*resume)(struct snd_soc_dai *dai);
	/* ops */
	struct snd_soc_dai_ops *ops;	//dai操作函数集
	/* DAI capabilities */
	struct snd_soc_pcm_stream capture;	//soc pcm流-捕获
	struct snd_soc_pcm_stream playback;	//soc pcm流-回放
	unsigned int symmetric_rates:1;
};

3.dai link

struct snd_soc_dai_link {
	/* config - must be set by machine driver */
	const char *name;			/* Codec name */
	const char *stream_name;		/* Stream name */
	const char *codec_name;		/* for multi-codec */
	const char *platform_name;	/* for multi-platform */
	const char *cpu_dai_name;
	const char *codec_dai_name;
	/* Keep DAI active over suspend */
	unsigned int ignore_suspend:1;
	/* Symmetry requirements */
	unsigned int symmetric_rates:1;
	/* codec/machine specific init - e.g. add machine controls */
	int (*init)(struct snd_soc_pcm_runtime *rtd);
	/* machine stream operations */
	struct snd_soc_ops *ops;	//soc操作函数集
};

3.注册若干个dai驱动

int snd_soc_register_dais(struct device *dev,struct snd_soc_dai_driver *dai_drv, size_t count)
{
	struct snd_soc_dai *dai;	//dai设备声明
	int i, ret = 0;

	dev_dbg(dev, "dai register %s #%Zu\n", dev_name(dev), count);
	for (i = 0; i < count; i++) {	//dai声卡设备个数
		dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);	//分配内存
		if (dai == NULL) {
			ret = -ENOMEM;
			goto err;
		}
		/* create DAI component name */
		dai->name = fmt_multiple_name(dev, &dai_drv[i]);	//格式化dai声卡设备名
		if (dai->name == NULL) {
			kfree(dai);
			ret = -EINVAL;
			goto err;
		}
		dai->dev = dev;	//设备文件
		dai->driver = &dai_drv[i];	//捆绑dai声卡驱动
		if (dai->driver->id)	//设置dai声卡设备id
			dai->id = dai->driver->id;
		else
			dai->id = i;
		if (!dai->driver->ops)	//若dai声卡驱动未指定dai操作函数集
			dai->driver->ops = &null_dai_ops;	//则使用默认的空函数集
		mutex_lock(&client_mutex);
		list_add(&dai->list, &dai_list);	//添加dai设备到全局dai_list链表
		mutex_unlock(&client_mutex);
		pr_debug("Registered DAI '%s'\n", dai->name);
	}
	mutex_lock(&client_mutex);
	snd_soc_instantiate_cards();	//实例化所有soc声卡设备
	mutex_unlock(&client_mutex);
	return 0;

err:
	for (i--; i >= 0; i--)
		snd_soc_unregister_dai(dev);
	return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_register_dais);




 4.注销若干个dai驱动

void snd_soc_unregister_dais(struct device *dev, size_t count)
{
	int i;

	for (i = 0; i < count; i++)
		snd_soc_unregister_dai(dev);	//注销dai设备
}
EXPORT_SYMBOL_GPL(snd_soc_unregister_dais);

5.注册一个dai驱动

int snd_soc_register_dai(struct device *dev,struct snd_soc_dai_driver *dai_drv)
{
	struct snd_soc_dai *dai;
	dev_dbg(dev, "dai register %s\n", dev_name(dev));
	dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);	//分配dai设备内存
	if (dai == NULL)
			return -ENOMEM;
	/* create DAI component name */
	dai->name = fmt_single_name(dev, &dai->id);	//设置名字
	if (dai->name == NULL) {
		kfree(dai);
		return -ENOMEM;
	}
	dai->dev = dev;	//设备文件
	dai->driver = dai_drv;	//捆绑dai驱动
	if (!dai->driver->ops)	//若为指定dai驱动操作函数集
		dai->driver->ops = &null_dai_ops;	//则设置默认函数集
	mutex_lock(&client_mutex);
	list_add(&dai->list, &dai_list);	//添加进全局dai_list链表
	snd_soc_instantiate_cards();	//实例化所有soc声卡设备
	mutex_unlock(&client_mutex);
	pr_debug("Registered DAI '%s'\n", dai->name);
	return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_register_dai);

6.注销一个dai设备

void snd_soc_unregister_dai(struct device *dev)
{
	struct snd_soc_dai *dai;
	list_for_each_entry(dai, &dai_list, list) {	//遍历全局dai_list链表
		if (dev == dai->dev)	//查找到匹配的dai设备
			goto found;
	}
	return;

found:
	mutex_lock(&client_mutex);
	list_del(&dai->list);	//移除链表
	mutex_unlock(&client_mutex);

	pr_debug("Unregistered DAI '%s'\n", dai->name);
	kfree(dai->name);
	kfree(dai);
}
EXPORT_SYMBOL_GPL(snd_soc_unregister_dai);


 一般的说snd_soc_register_dais是codec设备注册的时候调用的,用来注册dai设备(codec_dai类型)

而snd_soc_register_dai是驱动自己调用注册的,用来注册dai设备(一般是cpu_dai类型)

 不管是注册若干个或是一个,最终调用snd_soc_instantiate_cards实例化所有声卡

 

 第十三部分 soc声卡实例化

前面注册各种组件的时候,都会调用到snd_soc_instantiate_cards实例化所有声卡

1.snd_soc_instantiate_cards 实例化所有soc声卡

static void snd_soc_instantiate_cards(void)	//实例化所有soc声卡设备
{
	struct snd_soc_card *card;
	list_for_each_entry(card, &card_list, list)	//遍历声卡全局链表card_list
		snd_soc_instantiate_card(card);	//实例化soc声卡设备
}

遍历全局链表,实例化soc声卡设备,链表项是在注册soc声卡的时候添加进去的

2.snd_soc_instantiate_card 实例化一个soc声卡

static void snd_soc_instantiate_card(struct snd_soc_card *card)	//实例化soc声卡设备
{
	struct platform_device *pdev = to_platform_device(card->dev);	//获取平台设备
	int ret, i;
	mutex_lock(&card->mutex);
	if (card->instantiated) {	//调用soc声卡设备已经实例化(instantiated标志为1)
		mutex_unlock(&card->mutex);
		return;
	}
	/* bind DAIs */
	for (i = 0; i < card->num_links; i++)
		soc_bind_dai_link(card, i);	//绑定cpu_dai/codec_dai/codec/platform
	/* bind completed ? */
	if (card->num_rtd != card->num_links) {	//所有的都绑定好了?
		mutex_unlock(&card->mutex);
		return;
	}
	/* card bind complete so register a sound card */
	ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);	//创建声卡
	if (ret < 0) {
		printk(KERN_ERR "asoc: can't create sound card for card %s\n",card->name);
		mutex_unlock(&card->mutex);
		return;
	}
	card->snd_card->dev = card->dev;	//设备文件
#ifdef CONFIG_PM
	/* deferred resume work */
	INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif
	/* initialise the sound card only once */
	if (card->probe) {	//soc声卡设备存在probe方法
		ret = card->probe(pdev);	//则调用其probe方法
		if (ret < 0)
			goto card_probe_error;
	}
	for (i = 0; i < card->num_links; i++) {
		ret = soc_probe_dai_link(card, i);	//调用dai的probe方法
		if (ret < 0) {
			pr_err("asoc: failed to instantiate card %s: %d\n",card->name, ret);
			goto probe_dai_err;
		}
	}
	snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),"%s",  card->name);
	snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),"%s", card->name);
	ret = snd_card_register(card->snd_card);	//注册声卡
	if (ret < 0) {
		printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
		goto probe_dai_err;
	}
#ifdef CONFIG_SND_SOC_AC97_BUS
	/* register any AC97 codecs */
	for (i = 0; i < card->num_rtd; i++) {
		ret = soc_register_ac97_dai_link(&card->rtd[i]);
		if (ret < 0) {
			printk(KERN_ERR "asoc: failed to register AC97 %s\n", card->name);
			while (--i >= 0)
				soc_unregister_ac97_dai_link(&card->rtd[i]);
			goto probe_dai_err;
		}
	}
#endif
	card->instantiated = 1;	//实例化标志置1
	mutex_unlock(&card->mutex);
	return;
probe_dai_err:
	for (i = 0; i < card->num_links; i++)
		soc_remove_dai_link(card, i);
card_probe_error:
	if (card->remove)
		card->remove(pdev);
	snd_card_free(card->snd_card);
	mutex_unlock(&card->mutex);
}

该函数用到 前面一篇文章(alsa音频架构1)中讲到的 snd_card_create创建声卡,snd_card_register注册声卡

还没看见创建声卡设备哦,注册声卡的时候会调用snd_device_register_all注册声卡设备,所以声卡设备的创建应该在snd_card_registerr之前

这里还调用了几个重要的函数,下面分别解析下

3.soc_bind_dai_link 绑定cpu_dai/codec_dai/codec/platform

static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
	struct snd_soc_dai_link *dai_link = &card->dai_link[num];	//获取dai link
	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];	//获取soc pcm
	struct snd_soc_codec *codec;
	struct snd_soc_platform *platform;
	struct snd_soc_dai *codec_dai, *cpu_dai;

	if (rtd->complete)
		return 1;
	dev_dbg(card->dev, "binding %s at idx %d\n", dai_link->name, num);
	/* do we already have the CPU DAI for this link ? */
	if (rtd->cpu_dai) {	//若dai设备(cpu_dai)已经设置了
		goto find_codec;	//则直接查找dai设备(codec_dai)
	}
	/* no, then find CPU DAI from registered DAIs*/
	list_for_each_entry(cpu_dai, &dai_list, list) {	//遍历全局dai_list链表
		if (!strcmp(cpu_dai->name, dai_link->cpu_dai_name)) {	//查找符合的cpu_dai设备
			if (!try_module_get(cpu_dai->dev->driver->owner))
				return -ENODEV;
			rtd->cpu_dai = cpu_dai;	//查找到匹配,则设置soc pcm的dai设备(cpu_dai)
			goto find_codec;	//跳转查找dai设备(codec_dai)
		}
	}
	dev_dbg(card->dev, "CPU DAI %s not registered\n",dai_link->cpu_dai_name);

find_codec:	//查找dai设备(codec_dai)
	/* do we already have the CODEC for this link ? */
	if (rtd->codec) {	//若dai设备(codec_dai)已经设置了
		goto find_platform;	//则直接查找dai设备(cpu_dai)
	}
	/* no, then find CODEC from registered CODECs*/
	list_for_each_entry(codec, &codec_list, list) {	//遍历全局codec_list链表
		if (!strcmp(codec->name, dai_link->codec_name)) {	//查找符合的codec设备
			rtd->codec = codec;	//soc pcm捆绑codec设备
			if (!try_module_get(codec->dev->driver->owner))
				return -ENODEV;
			/* CODEC found, so find CODEC DAI from registered DAIs from this CODEC*/
			list_for_each_entry(codec_dai, &dai_list, list) {	//遍历全局dai_list链表
				if (codec->dev == codec_dai->dev &&!strcmp(codec_dai->name, dai_link->codec_dai_name)) {	//查找符合的dai设备(codec_dai)
					rtd->codec_dai = codec_dai;	//查找到匹配,则设置soc pcm的dai设备(codec_dai)
					goto find_platform;
				}
			}
			dev_dbg(card->dev, "CODEC DAI %s not registered\n",dai_link->codec_dai_name);
			goto find_platform;
		}
	}
	dev_dbg(card->dev, "CODEC %s not registered\n",dai_link->codec_name);

find_platform:	//查找soc平台
	/* do we already have the CODEC DAI for this link ? */
	if (rtd->platform) {	
		goto out;
	}
	/* no, then find CPU DAI from registered DAIs*/
	list_for_each_entry(platform, &platform_list, list) {	//遍历全局platform_list链表
		if (!strcmp(platform->name, dai_link->platform_name)) {	//查找符合的soc平台
			if (!try_module_get(platform->dev->driver->owner))
				return -ENODEV;
			rtd->platform = platform;	//pcm pcm捆绑soc平台
			goto out;
		}
	}
	dev_dbg(card->dev, "platform %s not registered\n",dai_link->platform_name);
	return 0;

out:
	/* mark rtd as complete if we found all 4 of our client devices */
	if (rtd->codec && rtd->codec_dai && rtd->platform && rtd->cpu_dai) {	//四种设备都设置齐全了
		rtd->complete = 1;	//设置soc pcm完成标志complete为1
		card->num_rtd++;
	}
	return 1;
}


 

 4.soc_probe_dai_link  "probe" cpu_dai/codec_dai/codec/platform各个组件

static int soc_probe_dai_link(struct snd_soc_card *card, int num)
{
	struct snd_soc_dai_link *dai_link = &card->dai_link[num];	//获取dai link
	struct snd_soc_pcm_runtime *rtd = &card->rtd[num];	//获取soc pcm
	struct snd_soc_codec *codec = rtd->codec;	//获取codec设备
	struct snd_soc_platform *platform = rtd->platform;	//获取soc平台
	struct snd_soc_dai *codec_dai = rtd->codec_dai, *cpu_dai = rtd->cpu_dai;//获取dai设备数组(codec_dai/cpu_dai)
	int ret;
//___________________________________捆绑多种设备关系_________________________________________//
	dev_dbg(card->dev, "probe %s dai link %d\n", card->name, num);
	/* config components */
	codec_dai->codec = codec;		//dai设备(codec_dai)捆绑codec设备
	codec->card = card;				//codec设备捆绑soc声卡设备
	cpu_dai->platform = platform;	//dai设备(cpu_dai)捆绑soc平台
	rtd->card = card;				//soc pcm捆绑soc声卡设备
	rtd->dev.parent = card->dev;	
	codec_dai->card = card;			//dai设备(codec_dai)捆绑soc声卡设备
	cpu_dai->card = card;			//dai设备(cpu_dai)捆绑soc声卡设备
	/* set default power off timeout */
	rtd->pmdown_time = pmdown_time;
//___________________________________probe多种设备_____________________________________________//		
	/* probe the cpu_dai */	//------------"probe" dai(cpu_dai)设备
	if (!cpu_dai->probed) {	//dai设备(cpu_dai)probed标志为0
		if (cpu_dai->driver->probe) {	//若dai声卡驱动存在probe方法
			ret = cpu_dai->driver->probe(cpu_dai);	//则调用其probe方法
			if (ret < 0) {
				printk(KERN_ERR "asoc: failed to probe CPU DAI %s\n",cpu_dai->name);
				return ret;
			}
		}
		cpu_dai->probed = 1;	//设置dai设备(cpu_dai)probed标志
		/* mark cpu_dai as probed and add to card cpu_dai list */
		list_add(&cpu_dai->card_list, &card->dai_dev_list);	//添加dai设备(cpu_dai)到soc声卡设备的dai_dev_list链表
	}
	/* probe the CODEC */	//------------"probe" codec设备
	if (!codec->probed) {	//codec设备的probed标志为0
		if (codec->driver->probe) {	//codec驱动存在probe方法
			ret = codec->driver->probe(codec);	//则调用其probe方法
			if (ret < 0) {
				printk(KERN_ERR "asoc: failed to probe CODEC %s\n",codec->name);
				return ret;
			}
		}
		soc_init_codec_debugfs(codec);	//初始化codec
		/* mark codec as probed and add to card codec list */
		codec->probed = 1;	//codec设备的probed标志置1
		list_add(&codec->card_list, &card->codec_dev_list);	//添加codec设备到soc声卡设备的codec_dev_list链表
	}
	/* probe the platform */	//------------"probe" platform设备
	if (!platform->probed) {	//platform设备的probed标志位0
		if (platform->driver->probe) {	//platform设备驱动存在probe方法
			ret = platform->driver->probe(platform);	//则调用其probe方法
			if (ret < 0) {
				printk(KERN_ERR "asoc: failed to probe platform %s\n",platform->name);
				return ret;
			}
		}
		/* mark platform as probed and add to card platform list */
		platform->probed = 1;	//platform设备probed标志置1
		list_add(&platform->card_list, &card->platform_dev_list);	//添加platform设备到soc声卡设备的platform_dev_list链表
	}
	/* probe the CODEC DAI */	//------------"probe" dai(codec_dai)设备
	if (!codec_dai->probed) {	//dai设备(codec_dai)probed标志为0
		if (codec_dai->driver->probe) {	//dai设备驱动存在probe方法
			ret = codec_dai->driver->probe(codec_dai);	//则调用其probe方法
			if (ret < 0) {
				printk(KERN_ERR "asoc: failed to probe CODEC DAI %s\n",codec_dai->name);
				return ret;
			}
		}
		/* mark cpu_dai as probed and add to card cpu_dai list */
		codec_dai->probed = 1;	//设置dai设备(codec_dai)probed标志
		list_add(&codec_dai->card_list, &card->dai_dev_list);	//添加dai设备(codec_dai)到soc声卡设备的dai_dev_list链表
	}
//_____________________________________________________________________________________//	
	/* DAPM dai link stream work */
	INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);	//初始化工作队列
	/* now that all clients have probed, initialise the DAI link */
	if (dai_link->init) {	//dai link存在init方法
		ret = dai_link->init(rtd);	//则调用其init方法
		if (ret < 0) {
			printk(KERN_ERR "asoc: failed to init %s\n", dai_link->stream_name);
			return ret;
		}
	}
	/* Make sure all DAPM widgets are instantiated */
	snd_soc_dapm_new_widgets(codec);
	snd_soc_dapm_sync(codec);
	/* register the rtd device */
	rtd->dev.release = rtd_release;
	rtd->dev.init_name = dai_link->name;
	ret = device_register(&rtd->dev);	//注册soc pcm设备文件
	if (ret < 0) {
		printk(KERN_ERR "asoc: failed to register DAI runtime device %d\n", ret);
		return ret;
	}
	rtd->dev_registered = 1;
	ret = device_create_file(&rtd->dev, &dev_attr_pmdown_time);	//创建soc pcm设备文件
	if (ret < 0)
		printk(KERN_WARNING "asoc: failed to add pmdown_time sysfs\n");
	/* add DAPM sysfs entries for this codec */
	ret = snd_soc_dapm_sys_add(&rtd->dev);
	if (ret < 0)
		printk(KERN_WARNING "asoc: failed to add codec dapm sysfs entries\n");
	/* add codec sysfs entries */
	ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);	//创建soc pcm设备属性文件
	if (ret < 0)
		printk(KERN_WARNING "asoc: failed to add codec sysfs files\n");
	/* create the pcm */
	ret = soc_new_pcm(rtd, num);	//创建新pcm设备
	if (ret < 0) {
		printk(KERN_ERR "asoc: can't create pcm %s\n", dai_link->stream_name);
		return ret;
	}
	/* add platform data for AC97 devices */
	if (rtd->codec_dai->driver->ac97_control)
		snd_ac97_dev_add_pdata(codec->ac97, rtd->cpu_dai->ac97_pdata);
	return 0;
}


 

 果真在soc_probe_dai_link函数中(创建声卡和注册声卡之间)调用了 soc_new_pcm创建了声卡设备

4.1.soc_new_pcm

static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
	struct snd_soc_codec *codec = rtd->codec;	//获取codec设备
	struct snd_soc_platform *platform = rtd->platform;	//获取soc平台
	struct snd_soc_dai *codec_dai = rtd->codec_dai;	//获取dai(codec_dai)设备
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;	//获取dai(cpu_dai)设备
	struct snd_pcm *pcm;	//pcm结构体声明
	char new_name[64];
	int ret = 0, playback = 0, capture = 0;

	/* check client and interface hw capabilities */
	snprintf(new_name, sizeof(new_name), "%s %s-%d",rtd->dai_link->stream_name, codec_dai->name, num);
	if (codec_dai->driver->playback.channels_min) //最小通道数不为0也就是存在回放通道
		playback = 1;     //则将其通道数设置为1
	if (codec_dai->driver->capture.channels_min)  //最小通道数不为0也就是存在捕捉通道
		capture = 1;      //则将其通道数设置为1
	dev_dbg(rtd->card->dev, "registered pcm #%d %s\n",num,new_name);
	ret = snd_pcm_new(rtd->card->snd_card, new_name,num, playback, capture, &pcm);	//创建pcm声卡设备
	if (ret < 0) {
		printk(KERN_ERR "asoc: can't create pcm for codec %s\n", codec->name);
		return ret;
	}
	rtd->pcm = pcm;	//捆绑soc pcm和pcm结构体
	pcm->private_data = rtd;	//soc pcm放在pcm结构体的私有数据段
	//soc_pcm_ops部分方法函数集设置为soc平台驱动对应的方法
	soc_pcm_ops.mmap = platform->driver->ops->mmap;
	soc_pcm_ops.pointer = platform->driver->ops->pointer;
	soc_pcm_ops.ioctl = platform->driver->ops->ioctl;
	soc_pcm_ops.copy = platform->driver->ops->copy;
	soc_pcm_ops.silence = platform->driver->ops->silence;
	soc_pcm_ops.ack = platform->driver->ops->ack;
	soc_pcm_ops.page = platform->driver->ops->page;
	if (playback)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
	if (capture)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);
	ret = platform->driver->pcm_new(rtd->card->snd_card, codec_dai, pcm);	//调用soc平台的pcm_new方法
	if (ret < 0) {
		printk(KERN_ERR "asoc: platform pcm constructor failed\n");
		return ret;
	}
	pcm->private_free = platform->driver->pcm_free;
	printk(KERN_INFO "asoc: %s <-> %s mapping ok\n", codec_dai->name,cpu_dai->name);
	return ret;
}

这里调用了snd_pcm_new创建声卡,前一篇文章也说了snd_pcm_new封装了 snd_device_new函数,也就是创建声卡设备的函数

4.1.1 snd_pcm_set_ops

void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops)
{
	struct snd_pcm_str *stream = &pcm->streams[direction];	//获取pcm流
	struct snd_pcm_substream *substream;
	
	for (substream = stream->substream; substream != NULL; substream = substream->next)	//遍历pcm子流
		substream->ops = ops;	//设置pcm子流的ops操作函数集
}

这个函数是设置了pcm子流的操作函数集

关于pcm的部分,待续...alsa第三篇

 

编写一个ASoc声卡驱动需要哪些工作呢?

1.平台设备的创建及平台资源的分配(platform_device_alloc/platform_device_add...),来匹配从而调用probe方法来创建snd_soc_card设备

 2.定义并赋值snd_soc_codec_driversnd_soc_dai_driver结构体并调用snd_soc_register_codec注册snd_soc_codec_driver(codec设备组件),同时也等价于注册了snd_soc_dai_driver(codec_dai组件)

 3.定义并赋值snd_soc_platform_driver结构体调用snd_soc_register_platform注册snd_soc_platform_driver(soc平台组件)

 4.定义并赋值snd_soc_dai_driver并调用snd_soc_register_dai注册snd_soc_dai_driver(cpu_dai组件)

下面是我的开发板平台的音频组件注册部分:

 

//dai设备(cpu_dai)
davinci-mcasp.c/davinci-hdmi.c
{
snd_soc_register_dai(&pdev->dev, &davinci_mcasp_dai[pdata->op_mode]);
}

//soc声卡设备
ti81xx-dvr.c
{
platform_device_alloc("soc-audio", 0);
platform_device_alloc("soc-audio", 1);
}

//codec设备/dai设备(codec_dai)
ti81xx_hdmi.c /tlv320aic3x.c /tvp5158-audio.c
{
snd_soc_register_codec(&pdev->dev, &soc_codec_tvp5158,&tvp5158_dai, 1);
snd_soc_register_codec(&pdev->dev, &soc_codec_hdmi,&ti81xx_dai, 1);
snd_soc_register_codec(&i2c->dev,&soc_codec_dev_aic3x, &aic3x_dai, 1);
}

//soc平台
davinci-pcm.c{
snd_soc_register_platform(&pdev->dev, &davinci_soc_platform);m
}

下面是文档的说明:

The codec driver is generic and hardware independent code that configures the codec to provide audio capture and playback. It should contain no code that is
specific to the target platform or machine. All platform and machine specific code should be added to the platform and machine drivers respectively.

codec驱动是通用和硬件抽象的代码,用于配置编码提供音频捕捉和回放,它不允许包含特殊的针对平台或机器的代码,所有的平台和机器特殊代码必须添加到平台或机器驱动中

An ASoC platform driver can be divided into audio DMA and SoC DAI configuration
and control. The platform drivers only target the SoC CPU and must have no board
specific code.

一个ASoc平台驱动分成了音频DMA 和 Soc Dai配置和控制 两部分.平台驱动只针对片上CPU且必须不包含板级特殊代码

The ASoC machine (or board) driver is the code that glues together the platform
and codec drivers.

The machine driver can contain codec and platform specific code. It registers
the audio subsystem with the kernel as a platform device and is represented by
the following struct:-

ASoc机器或板级驱动是整合platform和codec代码的代码(这里的机器就是snd_soc_card)

机器驱动可以包含codec和platform。它作为一个平台设备注册音频子系统到内核

ASoC currently supports the three main Digital Audio Interfaces (DAI) found on
SoC controllers and portable audio CODECs today, namely AC97, I2S and PCM.

dai数字音频接口,Asoc当前支持3中主流数字音频接口(soc控制器和编写的音频编码芯片上):AC97,I2S,PCM

总的说来:codec跟硬件无关负责配置编码(回放/捕捉),dai是具体的数字音频接口(ac97/pcm/i2s),platform是跟芯片相关跟板无关的代码,机器(soc声卡设备)是platform和codec的桥梁

alsa音频架构3-pcm

第十四部分 snd_pcm

1.pcm结构体

struct snd_pcm {
	struct snd_card *card;	//声卡
	struct list_head list;
	int device;	//设备号
	unsigned int info_flags;
	unsigned short dev_class;
	unsigned short dev_subclass;
	char id[64];	//id字串
	char name[80];	//名字
	struct snd_pcm_str streams[2];	//pcm流数组 0-回放 1-捕捉
	struct mutex open_mutex;
	wait_queue_head_t open_wait;	//PCM打开等待队列
	void *private_data;
	void (*private_free) (struct snd_pcm *pcm);	//(soc_new_pcm函数)platform->driver->pcm_free
	struct device *dev; //设备文件
};


2.pcm流

struct snd_pcm_str {
	int stream;				//流类型
	struct snd_pcm *pcm;	//捆绑的pcm结构体
	/* -- substreams -- */
	unsigned int substream_count;	//子流个数
	unsigned int substream_opened;	//子流打开标志
	struct snd_pcm_substream *substream;	//pcm子流
#ifdef CONFIG_SND_VERBOSE_PROCFS
	struct snd_info_entry *proc_root;
	struct snd_info_entry *proc_info_entry;
#endif
};


3.pcm子流

struct snd_pcm_substream {
	struct snd_pcm *pcm;	//捆绑pcm结构体
	struct snd_pcm_str *pstr;	//捆绑的pcm流
	void *private_data;		/* copied from pcm->private_data */
	int number;
	char name[32];			//子流名
	int stream;			//流类型
	struct pm_qos_request_list latency_pm_qos_req; /* pm_qos request */
	size_t buffer_bytes_max;	/* limit ring buffer size */
	struct snd_dma_buffer dma_buffer;
	unsigned int dma_buf_id;
	size_t dma_max;
	/* -- hardware operations -- */
	struct snd_pcm_ops *ops;	//pcm操作函数集
	/* -- runtime information -- */
	struct snd_pcm_runtime *runtime;	//pcm runtime
        /* -- timer section -- */
	struct snd_timer *timer;		//声卡定时器
	unsigned timer_running: 1;	/* time is running */
	/* -- next substream -- */
	struct snd_pcm_substream *next;	//下一个pcm子流
	/* -- linked substreams -- */
	struct list_head link_list;	/* linked list member */
	struct snd_pcm_group self_group;	/* fake group for non linked substream (with substream lock inside) */
	struct snd_pcm_group *group;		/* pointer to current group */
	/* -- assigned files -- */
	void *file;	//指向 snd_pcm_file结构体
	int ref_count;
	atomic_t mmap_count;
	unsigned int f_flags;
	void (*pcm_release)(struct snd_pcm_substream *);
	struct pid *pid;
#ifdef CONFIG_SND_VERBOSE_PROCFS
	struct snd_info_entry *proc_root;
	struct snd_info_entry *proc_info_entry;
	struct snd_info_entry *proc_hw_params_entry;
	struct snd_info_entry *proc_sw_params_entry;
	struct snd_info_entry *proc_status_entry;
	struct snd_info_entry *proc_prealloc_entry;
	struct snd_info_entry *proc_prealloc_max_entry;
#endif
	/* misc flags */
	unsigned int hw_opened: 1;
};


4.snd_pcm_new 创建PCM设备

int snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count,struct snd_pcm ** rpcm)
{
	struct snd_pcm *pcm;
	int err;
	static struct snd_device_ops ops = {
		.dev_free = snd_pcm_dev_free,				//释放
		.dev_register =	snd_pcm_dev_register,		//注册
		.dev_disconnect = snd_pcm_dev_disconnect,	//断开连接
	};
	if (snd_BUG_ON(!card))
		return -ENXIO;
	if (rpcm)
		*rpcm = NULL;
	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);	//分配snd_pcm内存
	if (pcm == NULL) {
		snd_printk(KERN_ERR "Cannot allocate PCM\n");
		return -ENOMEM;
	}
	pcm->card = card;	//捆绑声卡
	pcm->device = device;	//设备号
	if (id)	//id识别字串
		strlcpy(pcm->id, id, sizeof(pcm->id));	//填充pcm结构体id字串
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {	//回放 SNDRV_PCM_STREAM_PLAYBACK=0
		snd_pcm_free(pcm);
		return err;
	}
	if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {	//捕捉 SNDRV_PCM_STREAM_CAPTURE=1
		snd_pcm_free(pcm);
		return err;
	}
	mutex_init(&pcm->open_mutex);
	init_waitqueue_head(&pcm->open_wait);	//初始化PCM打开等待队列
	if ((err = snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops)) < 0) {	//创建声卡设备,声卡设备的device_data指向snd_pcm结构体对象
		snd_pcm_free(pcm);
		return err;
	}
	if (rpcm)
		*rpcm = pcm;
	return 0;
}
EXPORT_SYMBOL(snd_pcm_new);


5.创建pcm子流

int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{
	int idx, err;
	struct snd_pcm_str *pstr = &pcm->streams[stream];	//获取pcm流
	struct snd_pcm_substream *substream, *prev;

#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
	mutex_init(&pstr->oss.setup_mutex);
#endif
	pstr->stream = stream;	//获取流类型 0:回放 1:捕捉
	pstr->pcm = pcm;	//捆绑pcm设备
	pstr->substream_count = substream_count;	//子流个数
	if (substream_count > 0) {
		err = snd_pcm_stream_proc_init(pstr);	//proc接口
		if (err < 0) {
			snd_printk(KERN_ERR "Error in snd_pcm_stream_proc_init\n");
			return err;
		}
	}
	prev = NULL;
	for (idx = 0, prev = NULL; idx < substream_count; idx++) {
		substream = kzalloc(sizeof(*substream), GFP_KERNEL);	//分配子流内存
		if (substream == NULL) {
			snd_printk(KERN_ERR "Cannot allocate PCM substream\n");
			return -ENOMEM;
		}
		substream->pcm = pcm;	//捆绑pcm设备
		substream->pstr = pstr;	//捆绑pcm子流
		substream->number = idx;	//索引号
		substream->stream = stream;	//子流类型 0:回放 1:捕捉
		sprintf(substream->name, "subdevice #%i", idx);	//子流名
		substream->buffer_bytes_max = UINT_MAX;
		if (prev == NULL)	//处理第一个pcm子流时走的分支
			pstr->substream = substream;	//pcm流捆绑子流
		else	//前一个子流的next指针指向当前子流,也就是串成一串(0->next--1->next-->2...)
			prev->next = substream;
		err = snd_pcm_substream_proc_init(substream);	//proc接口
		if (err < 0) {
			snd_printk(KERN_ERR "Error in snd_pcm_stream_proc_init\n");
			if (prev == NULL)
				pstr->substream = NULL;
			else
				prev->next = NULL;
			kfree(substream);
			return err;
		}
		substream->group = &substream->self_group;
		spin_lock_init(&substream->self_group.lock);
		INIT_LIST_HEAD(&substream->self_group.substreams);
		list_add_tail(&substream->link_list, &substream->self_group.substreams);
		atomic_set(&substream->mmap_count, 0);
		prev = substream;	//prev指向当前substream
	}
	return 0;
}				
EXPORT_SYMBOL(snd_pcm_new_stream);


6 注册方法snd_pcm_dev_register

static int snd_pcm_dev_register(struct snd_device *device)
{
	int cidx, err;
	struct snd_pcm_substream *substream;
	struct snd_pcm_notify *notify;
	char str[16];
	struct snd_pcm *pcm;
	struct device *dev;

	if (snd_BUG_ON(!device || !device->device_data))
		return -ENXIO;
	pcm = device->device_data;	//声卡设备的私有数据中获取snd_pcm对象
	mutex_lock(®ister_mutex);
	err = snd_pcm_add(pcm);	//添加snd_pcm对象到全局snd_pcm_devices链表
	if (err) {
		mutex_unlock(®ister_mutex);
		return err;
	}
	for (cidx = 0; cidx < 2; cidx++) {
		int devtype = -1;
		if (pcm->streams[cidx].substream == NULL)	//判断pcm子流是否为空
			continue;
		switch (cidx) {
		case SNDRV_PCM_STREAM_PLAYBACK:	//回放
			sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);//pcmC+声卡号+D+设备号+p
			devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;	//设置设备类型
			break;
		case SNDRV_PCM_STREAM_CAPTURE:	//捕捉
			sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);//pcmC+声卡号+D+设备号+c
			devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;	//设置设备类型
			break;
		}

		dev = pcm->dev;
		if (!dev)
			dev = snd_card_get_device_link(pcm->card);
		err = snd_register_device_for_dev(devtype, pcm->card,pcm->device,&snd_pcm_f_ops[cidx],pcm, str, dev);	
		//注册声卡设备 "/dev/snd/pcmC0D0p" "/dev/snd/pcmC0D0c"...这里特别注意私有数据是snd_pcm对象
		if (err < 0) {
			list_del(&pcm->list);
			mutex_unlock(®ister_mutex);
			return err;
		}
		snd_add_device_sysfs_file(devtype, pcm->card, pcm->device,&pcm_attrs);	//添加属性文件
		for (substream = pcm->streams[cidx].substream; substream; substream = substream->next) //遍历PCM子流链表
			snd_pcm_timer_init(substream);	//初始化子流定时器
	}
	list_for_each_entry(notify, &snd_pcm_notify_list, list)
		notify->n_register(pcm);

	mutex_unlock(®ister_mutex);
	return 0;
}

6.1 添加snd_pcm对象到全局snd_pcm_devices链表

static int snd_pcm_add(struct snd_pcm *newpcm)
{
	struct snd_pcm *pcm;
	list_for_each_entry(pcm, &snd_pcm_devices, list) {	//遍历全局snd_pcm_devices链表
		if (pcm->card == newpcm->card && pcm->device == newpcm->device)	//匹配声卡和设备号
			return -EBUSY;
		if (pcm->card->number > newpcm->card->number ||(pcm->card == newpcm->card && pcm->device > newpcm->device)) {
			list_add(&newpcm->list, pcm->list.prev);	//链接新的pcm到旧的pcm(根据设备号来排序)
			return 0;
		}
	}
	list_add_tail(&newpcm->list, &snd_pcm_devices);	//添加pcm到全局snd_pcm_devices
	return 0;
}

7.pcm相关的字符设备

snd_register_device_for_dev函数前面讲过注册字符设备文件,捆绑对应的文件操作函数集,也就是snd_pcm_f_ops数组项

PCM声卡设备操作函数集

const struct file_operations snd_pcm_f_ops[2] = {
	{	//回放 对应"/dev/snd/pcmC0D0p"设备文件的操作接口
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,				//写方法
		.aio_write =	snd_pcm_aio_write,			//异步写方法
		.open =			snd_pcm_playback_open,		//打开方法
		.release =		snd_pcm_release,			//释放方法
		.llseek =		no_llseek,
		.poll =			snd_pcm_playback_poll,		//轮询方法
		.unlocked_ioctl =	snd_pcm_playback_ioctl,	//命令控制方法
		.compat_ioctl = snd_pcm_ioctl_compat,		//兼容的命令控制方法
		.mmap =			snd_pcm_mmap,				//映射方法
		.fasync =		snd_pcm_fasync,				//同步方法
		.get_unmapped_area =	snd_pcm_get_unmapped_area,	//获取没映射的地址
	},
	{	//捕捉 对应"/dev/snd/pcmC0D0c"设备文件的操作接口
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,				//读方法
		.aio_read =		snd_pcm_aio_read,			//异步读方法
		.open =			snd_pcm_capture_open,		//打开方法
		.release =		snd_pcm_release,			//释放方法
		.llseek =		no_llseek,
		.poll =			snd_pcm_capture_poll,		//轮询方法
		.unlocked_ioctl =	snd_pcm_capture_ioctl,	//命令控制方法
		.compat_ioctl = snd_pcm_ioctl_compat,		//兼容的命令控制方法
		.mmap =			snd_pcm_mmap,				//映射方法
		.fasync =		snd_pcm_fasync,				//同步方法
		.get_unmapped_area =	snd_pcm_get_unmapped_area,	//获取没映射的地址
	}
};

当我们在操作/dev/snd/pcmCxDxX时候会调用到上面的函数集的方法,

X为'c'时代表capture调用snd_pcm_f_ops[1]的方法,X为'p'时代表playback调用snd_pcm_f_ops[0]的方法
 

我在内核代码上添加了打印语句:

当我用arecord时候调用open方法接着会调用ioctl方法设置参数属性,接着会调用mmap方法映射内存,接着不断继续用ioctl方法录制声音

主要是SNDRV_PCM_IOCTL_WRITEI_FRAMES命令录制,和SNDRV_PCM_IOCTL_SYNC_PTR命令同步.

 

当我用arecord时候调用open方法接着会调用ioctl方法设置参数属性,接着会调用mmap方法映射内存,接着不断继续用ioctl方法播放声音

主要是SNDRV_PCM_IOCTL_READI_FRAMES命令回放,和SNDRV_PCM_IOCTL_SYNC_PTR命令同步.

 

在pcm字符设备的操作函数集中会经常用到下面的结构体

8.snd_pcm_file

struct snd_pcm_file {
	struct snd_pcm_substream *substream;
	int no_compat_mmap;
};

该结构体很简单基本就是封装了pcm子流和一个映射方式标志

9.pcm runtime

struct snd_pcm_runtime {
	/* -- Status -- */
	struct snd_pcm_substream *trigger_master;	//pcm子流
	struct timespec trigger_tstamp;	/* trigger timestamp */
	int overrange;
	snd_pcm_uframes_t avail_max;
	snd_pcm_uframes_t hw_ptr_base;	/* Position at buffer restart */
	snd_pcm_uframes_t hw_ptr_interrupt; /* Position at interrupt time */
	unsigned long hw_ptr_jiffies;	/* Time when hw_ptr is updated */
	unsigned long hw_ptr_buffer_jiffies; /* buffer time in jiffies */
	snd_pcm_sframes_t delay;	/* extra delay; typically FIFO size */
	/* -- HW params -- */
	snd_pcm_access_t access;	/* access mode */
	snd_pcm_format_t format;	/* SNDRV_PCM_FORMAT_* */
	snd_pcm_subformat_t subformat;	/* subformat */
	unsigned int rate;		/* rate in Hz */
	unsigned int channels;		/* channels */
	snd_pcm_uframes_t period_size;	/* period size */
	unsigned int periods;		/* periods */
	snd_pcm_uframes_t buffer_size;	/* buffer size */
	snd_pcm_uframes_t min_align;	/* Min alignment for the format */
	size_t byte_align;
	unsigned int frame_bits;
	unsigned int sample_bits;
	unsigned int info;
	unsigned int rate_num;
	unsigned int rate_den;
	/* -- SW params -- */
	int tstamp_mode;		/* mmap timestamp is updated */
  	unsigned int period_step;
	snd_pcm_uframes_t start_threshold;
	snd_pcm_uframes_t stop_threshold;
	snd_pcm_uframes_t silence_threshold; /* Silence filling happens when noise is nearest than this */
	snd_pcm_uframes_t silence_size;	/* Silence filling size */
	snd_pcm_uframes_t boundary;	/* pointers wrap point */
	snd_pcm_uframes_t silence_start; /* starting pointer to silence area */
	snd_pcm_uframes_t silence_filled; /* size filled with silence */
	union snd_pcm_sync_id sync;	/* hardware synchronization ID */
	/* -- mmap -- */
	struct snd_pcm_mmap_status *status;
	struct snd_pcm_mmap_control *control;
	/* -- locking / scheduling -- */
	snd_pcm_uframes_t twake; 	/* do transfer (!poll) wakeup if non-zero */
	wait_queue_head_t sleep;	/* poll sleep */
	wait_queue_head_t tsleep;	/* transfer sleep */
	struct fasync_struct *fasync;
	/* -- private section -- */
	void *private_data;
	void (*private_free)(struct snd_pcm_runtime *runtime);
	/* -- hardware description -- */
	struct snd_pcm_hardware hw;	//pcm硬件信息
	struct snd_pcm_hw_constraints hw_constraints;	//pcm硬件约束
	/* -- interrupt callbacks -- */
	void (*transfer_ack_begin)(struct snd_pcm_substream *substream);
	void (*transfer_ack_end)(struct snd_pcm_substream *substream);
	/* -- timer -- */
	unsigned int timer_resolution;	/* timer resolution */
	int tstamp_type;		/* timestamp type */
	/* -- DMA -- */           
	unsigned char *dma_area;	/* DMA area */
	dma_addr_t dma_addr;		/* physical bus address (not accessible from main CPU) */
	size_t dma_bytes;		/* size of DMA area */
	struct snd_dma_buffer *dma_buffer_p;	/* allocated buffer */
};


10.pcm字符设备打开方法

回放

static int snd_pcm_playback_open(struct inode *inode, struct file *file)
{
	struct snd_pcm *pcm;
	int err = nonseekable_open(inode, file);	//设置打开方式
	if (err < 0)
		return err;
	pcm = snd_lookup_minor_data(iminor(inode),SNDRV_DEVICE_TYPE_PCM_PLAYBACK);	//获取snd_pcm对象
	return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);	//打开回放通道
}

捕捉

static int snd_pcm_capture_open(struct inode *inode, struct file *file)
{
	struct snd_pcm *pcm;
	int err = nonseekable_open(inode, file);	//设置打开方式
	if (err < 0)
		return err;
	pcm = snd_lookup_minor_data(iminor(inode),SNDRV_DEVICE_TYPE_PCM_CAPTURE);	//获取snd_pcm对象
	return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE);	//打开捕捉通道
}

10.1.次设备号获取snd_pcm对象

void *snd_lookup_minor_data(unsigned int minor, int type)
{
	struct snd_minor *mreg;
	void *private_data;

	if (minor >= ARRAY_SIZE(snd_minors))
		return NULL;
	mutex_lock(&sound_mutex);
	mreg = snd_minors[minor];	//根据次设备号,从全局snd_minor数组获取对应的snd_minor对象
	if (mreg && mreg->type == type)
		private_data = mreg->private_data;	//前面特别注意私有数据为snd_pcm对象
	else
		private_data = NULL;
	mutex_unlock(&sound_mutex);
	return private_data;	//返回snd_pcm对象
}
EXPORT_SYMBOL(snd_lookup_minor_data);

10.2.snd_pcm_open

static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
{
	int err;
	struct snd_pcm_file *pcm_file;
	wait_queue_t wait;

	if (pcm == NULL) {
		err = -ENODEV;
		goto __error1;
	}
	err = snd_card_file_add(pcm->card, file);
	if (err < 0)
		goto __error1;
	if (!try_module_get(pcm->card->module)) {
		err = -EFAULT;
		goto __error2;
	}
	init_waitqueue_entry(&wait, current);
	add_wait_queue(&pcm->open_wait, &wait);
	mutex_lock(&pcm->open_mutex);
	while (1) {
		err = snd_pcm_open_file(file, pcm, stream, &pcm_file);	//4.1 snd_pcm_open_file
		if (err >= 0)
			break;
		if (err == -EAGAIN) {
			if (file->f_flags & O_NONBLOCK) {
				err = -EBUSY;
				break;
			}
		} else
			break;
		set_current_state(TASK_INTERRUPTIBLE);	//设置为可中断
		mutex_unlock(&pcm->open_mutex);
		schedule();	//进入调度
		mutex_lock(&pcm->open_mutex);
		if (signal_pending(current)) {	//挂起当前进程
			err = -ERESTARTSYS;
			break;
		}
	}
	remove_wait_queue(&pcm->open_wait, &wait);
	mutex_unlock(&pcm->open_mutex);
	if (err < 0)
		goto __error;
	return err;

      __error:
	module_put(pcm->card->module);
      __error2:
      	snd_card_file_remove(pcm->card, file);
      __error1:
      	return err;
}

10.2.1 snd_pcm_open_file

static int snd_pcm_open_file(struct file *file,struct snd_pcm *pcm,int stream,struct snd_pcm_file **rpcm_file)
{
	struct snd_pcm_file *pcm_file;
	struct snd_pcm_substream *substream;
	struct snd_pcm_str *str;
	int err;

	if (rpcm_file)
		*rpcm_file = NULL;

	err = snd_pcm_open_substream(pcm, stream, file, &substream);	//4.1.1 snd_pcm_open_substream
	if (err < 0)
		return err;

	pcm_file = kzalloc(sizeof(*pcm_file), GFP_KERNEL);	//分配snd_pcm_file对象内存
	if (pcm_file == NULL) {
		snd_pcm_release_substream(substream);
		return -ENOMEM;
	}
	pcm_file->substream = substream;	//snd_pcm_file对象捆绑pcm子流
	if (substream->ref_count == 1) {
		str = substream->pstr;	//获取pcm流
		substream->file = pcm_file;		//pcm子流捆绑snd_pcm_file对象
		substream->pcm_release = pcm_release_private;
	}
	file->private_data = pcm_file;	//snd_pcm_file对象存放在文件对象的私有数据里
	if (rpcm_file)
		*rpcm_file = pcm_file;
	return 0;
}

10.2.1.1 snd_pcm_open_substream

int snd_pcm_open_substream(struct snd_pcm *pcm, int stream,struct file *file,struct snd_pcm_substream **rsubstream)
{
	struct snd_pcm_substream *substream;
	int err;

	err = snd_pcm_attach_substream(pcm, stream, file, &substream);	//获取pcm子流
	if (err < 0)
		return err;
	if (substream->ref_count > 1) {
		*rsubstream = substream;
		return 0;
	}
	err = snd_pcm_hw_constraints_init(substream);	//初始化pcm硬件约束
	if (err < 0) {
		snd_printd("snd_pcm_hw_constraints_init failed\n");
		goto error;
	}
	if ((err = substream->ops->open(substream)) < 0)	//调用pcm子流的open方法
		goto error;
	substream->hw_opened = 1;
	err = snd_pcm_hw_constraints_complete(substream);	//设置pcm硬件约束
	if (err < 0) {
		snd_printd("snd_pcm_hw_constraints_complete failed\n");
		goto error;
	}
	*rsubstream = substream;
	return 0;
 error:
	snd_pcm_release_substream(substream);
	return err;
}
EXPORT_SYMBOL(snd_pcm_open_substream);


这里调用了pcm子流的open方法,在上一篇文章ASoc中有这么几句代码

snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &soc_pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &soc_pcm_ops);

11.pcm子流操作集

static struct snd_pcm_ops soc_pcm_ops = {
	.open		= soc_pcm_open,
	.close		= soc_codec_close,
	.hw_params	= soc_pcm_hw_params,
	.hw_free	= soc_pcm_hw_free,
	.prepare	= soc_pcm_prepare,
	.trigger	= soc_pcm_trigger,
	.pointer	= soc_pcm_pointer,
};

除了open方法这里给调用,其他的方法在ioctl和close等字符设备方法中会使用到,souceinsight搜索ops->xxx,基本上在pcm_native.c文件中会调用

11.1 open方法soc_pcm_open

static int soc_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;	//获取soc pcm
	struct snd_pcm_runtime *runtime = substream->runtime;	//获取pcmrt
	struct snd_soc_platform *platform = rtd->platform;	//获取soc平台
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;	//获取dai设备(cpu_dai)
	struct snd_soc_dai *codec_dai = rtd->codec_dai;	//获取dai设备(codec_dai)
	struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver;	//获取dai(cpu_dai)声卡驱动
	struct snd_soc_dai_driver *codec_dai_drv = codec_dai->driver;	//获取dai(codec_dai)声卡驱动
	int ret = 0;

	mutex_lock(&pcm_mutex);
	/* startup the audio subsystem */
	if (cpu_dai->driver->ops->startup) {	
		ret = cpu_dai->driver->ops->startup(substream, cpu_dai);	//调用dai设备(cpu_dai)声卡驱动的startup方法
		if (ret < 0) {
			printk(KERN_ERR "asoc: can't open interface %s\n",cpu_dai->name);
			goto out;
		}
	}
	if (platform->driver->ops->open) {	
		ret = platform->driver->ops->open(substream);	//调用soc平台驱动的open方法
		if (ret < 0) {
			printk(KERN_ERR "asoc: can't open platform %s\n", platform->name);
			goto platform_err;
		}
	}
	if (codec_dai->driver->ops->startup) {
		ret = codec_dai->driver->ops->startup(substream, codec_dai); //调用dai设备(codec_dai)声卡驱动的startup方法
		if (ret < 0) {
			printk(KERN_ERR "asoc: can't open codec %s\n",codec_dai->name);
			goto codec_dai_err;
		}
	}
	if (rtd->dai_link->ops && rtd->dai_link->ops->startup) {
		ret = rtd->dai_link->ops->startup(substream);	//调用dai link的startup方法
		if (ret < 0) {
			printk(KERN_ERR "asoc: %s startup failed\n", rtd->dai_link->name);
			goto machine_err;
		}
	}
	/* Check that the codec and cpu DAI's are compatible */
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {	//回放
		runtime->hw.rate_min =max(codec_dai_drv->playback.rate_min,cpu_dai_drv->playback.rate_min);	//最小采样率
		runtime->hw.rate_max =min(codec_dai_drv->playback.rate_max,cpu_dai_drv->playback.rate_max);	//最大采样率
		runtime->hw.channels_min =max(codec_dai_drv->playback.channels_min,cpu_dai_drv->playback.channels_min);	//最小通道数
		runtime->hw.channels_max =min(codec_dai_drv->playback.channels_max,cpu_dai_drv->playback.channels_max);	//最大通道数
		runtime->hw.formats =codec_dai_drv->playback.formats & cpu_dai_drv->playback.formats;	//采样格式
		runtime->hw.rates =codec_dai_drv->playback.rates & cpu_dai_drv->playback.rates;	//设置采样率
		if (codec_dai_drv->playback.rates& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
			runtime->hw.rates |= cpu_dai_drv->playback.rates;	//设置采样率
		if (cpu_dai_drv->playback.rates& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
			runtime->hw.rates |= codec_dai_drv->playback.rates;	//设置采样率
	} else {												//捕捉
		runtime->hw.rate_min =max(codec_dai_drv->capture.rate_min,cpu_dai_drv->capture.rate_min);	//最小采样率
		runtime->hw.rate_max =min(codec_dai_drv->capture.rate_max,cpu_dai_drv->capture.rate_max);	//最大采样率
		runtime->hw.channels_min =max(codec_dai_drv->capture.channels_min,cpu_dai_drv->capture.channels_min);	//最小通道数
		runtime->hw.channels_max =min(codec_dai_drv->capture.channels_max,cpu_dai_drv->capture.channels_max);	//最大通道数
		runtime->hw.formats =codec_dai_drv->capture.formats & cpu_dai_drv->capture.formats;	//采样格式
		runtime->hw.rates =codec_dai_drv->capture.rates & cpu_dai_drv->capture.rates;	//设置采样率
		if (codec_dai_drv->capture.rates& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
			runtime->hw.rates |= cpu_dai_drv->capture.rates;	//设置采样率
		if (cpu_dai_drv->capture.rates& (SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_CONTINUOUS))
			runtime->hw.rates |= codec_dai_drv->capture.rates;	//设置采样率
	}
	snd_pcm_limit_hw_rates(runtime);	//设置采样率范围
	if (!runtime->hw.rates) {
		printk(KERN_ERR "asoc: %s <-> %s No matching rates\n",codec_dai->name, cpu_dai->name);
		goto config_err;
	}
	if (!runtime->hw.formats) {
		printk(KERN_ERR "asoc: %s <-> %s No matching formats\n",codec_dai->name, cpu_dai->name);
		goto config_err;
	}
	if (!runtime->hw.channels_min || !runtime->hw.channels_max) {
		printk(KERN_ERR "asoc: %s <-> %s No matching channels\n",codec_dai->name, cpu_dai->name);
		goto config_err;
	}
	/* Symmetry only applies if we've already got an active stream. */
	if (cpu_dai->active || codec_dai->active) {
		ret = soc_pcm_apply_symmetry(substream);
		if (ret != 0)
			goto config_err;
	}
	pr_debug("asoc: %s <-> %s info:\n",codec_dai->name, cpu_dai->name);
	pr_debug("asoc: rate mask 0x%x\n", runtime->hw.rates);
	pr_debug("asoc: min ch %d max ch %d\n", runtime->hw.channels_min,runtime->hw.channels_max);
	pr_debug("asoc: min rate %d max rate %d\n", runtime->hw.rate_min,runtime->hw.rate_max);
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		cpu_dai->playback_active++;	//更新dai(cpu_dai)的playback_active计数
		codec_dai->playback_active++;	//更新dai(codec_dai)的playback_active计数
	} else {
		cpu_dai->capture_active++;	//更新dai(cpu_dai)的capture_active计数
		codec_dai->capture_active++;	//更新dai(codec_dai)的capture_active计数
	}
	cpu_dai->active++;	//更新dai(cpu_dai)的active计数
	codec_dai->active++;	//更新dai(codec_dai)的active计数
	rtd->codec->active++;	//更新codec设备的active计数
	mutex_unlock(&pcm_mutex);
	return 0;
config_err:
	if (rtd->dai_link->ops && rtd->dai_link->ops->shutdown)
		rtd->dai_link->ops->shutdown(substream);
machine_err:
	if (codec_dai->driver->ops->shutdown)
		codec_dai->driver->ops->shutdown(substream, codec_dai);
codec_dai_err:
	if (platform->driver->ops->close)
		platform->driver->ops->close(substream);
platform_err:
	if (cpu_dai->driver->ops->shutdown)
		cpu_dai->driver->ops->shutdown(substream, cpu_dai);
out:
	mutex_unlock(&pcm_mutex);
	return ret;
}


12.pcm字符设备映射方法

。。。

13.pcm字符设备命令控制方法

13.1 捕捉控制命令

static long snd_pcm_capture_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
	struct snd_pcm_file *pcm_file;

	pcm_file = file->private_data;	//获取snd_pcm_file对象
	if (((cmd >> 8) & 0xff) != 'A')
		return -ENOTTY;
	return snd_pcm_capture_ioctl1(file, pcm_file->substream, cmd,(void __user *)arg);
}

13.1.1 snd_pcm_capture_ioctl1

static int snd_pcm_capture_ioctl1(struct file *file,struct snd_pcm_substream *substream,unsigned int cmd, void __user *arg)
{
	if (snd_BUG_ON(!substream))
		return -ENXIO;
	if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_CAPTURE))
		return -EINVAL;
	switch (cmd) {
	case SNDRV_PCM_IOCTL_READI_FRAMES:
	{
		struct snd_xferi xferi;
		struct snd_xferi __user *_xferi = arg;
		struct snd_pcm_runtime *runtime = substream->runtime;
		snd_pcm_sframes_t result;
		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
			return -EBADFD;
		if (put_user(0, &_xferi->result))
			return -EFAULT;
		if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
			return -EFAULT;
		result = snd_pcm_lib_read(substream, xferi.buf, xferi.frames);
		__put_user(result, &_xferi->result);
		return result < 0 ? result : 0;
	}
	case SNDRV_PCM_IOCTL_READN_FRAMES:
	{
		struct snd_xfern xfern;
		struct snd_xfern __user *_xfern = arg;
		struct snd_pcm_runtime *runtime = substream->runtime;
		void *bufs;
		snd_pcm_sframes_t result;
		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
			return -EBADFD;
		if (runtime->channels > 128)
			return -EINVAL;
		if (put_user(0, &_xfern->result))
			return -EFAULT;
		if (copy_from_user(&xfern, _xfern, sizeof(xfern)))
			return -EFAULT;
		bufs = memdup_user(xfern.bufs,sizeof(void *) * runtime->channels);
		if (IS_ERR(bufs))
			return PTR_ERR(bufs);
		result = snd_pcm_lib_readv(substream, bufs, xfern.frames);
		kfree(bufs);
		__put_user(result, &_xfern->result);
		return result < 0 ? result : 0;
	}
	case SNDRV_PCM_IOCTL_REWIND:
	{
		snd_pcm_uframes_t frames;
		snd_pcm_uframes_t __user *_frames = arg;
		snd_pcm_sframes_t result;
		if (get_user(frames, _frames))
			return -EFAULT;
		if (put_user(0, _frames))
			return -EFAULT;
		result = snd_pcm_capture_rewind(substream, frames);
		__put_user(result, _frames);
		return result < 0 ? result : 0;
	}
	case SNDRV_PCM_IOCTL_FORWARD:
	{
		snd_pcm_uframes_t frames;
		snd_pcm_uframes_t __user *_frames = arg;
		snd_pcm_sframes_t result;
		if (get_user(frames, _frames))
			return -EFAULT;
		if (put_user(0, _frames))
			return -EFAULT;
		result = snd_pcm_capture_forward(substream, frames);
		__put_user(result, _frames);
		return result < 0 ? result : 0;
	}
	}
	return snd_pcm_common_ioctl1(file, substream, cmd, arg);  // 通用命令
}

13.2 回放控制命令

static long snd_pcm_playback_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
	struct snd_pcm_file *pcm_file;

	pcm_file = file->private_data;	//获取snd_pcm_file对?
	if (((cmd >> 8) & 0xff) != 'A')
		return -ENOTTY;
	return snd_pcm_playback_ioctl1(file, pcm_file->substream, cmd,(void __user *)arg);
}

13.2.1 snd_pcm_playback_ioctl1

static int snd_pcm_playback_ioctl1(struct file *file,struct snd_pcm_substream *substream,unsigned int cmd, void __user *arg)
{
	if (snd_BUG_ON(!substream))
		return -ENXIO;
	if (snd_BUG_ON(substream->stream != SNDRV_PCM_STREAM_PLAYBACK))
		return -EINVAL;
	switch (cmd) {
	case SNDRV_PCM_IOCTL_WRITEI_FRAMES:
	{
		struct snd_xferi xferi;
		struct snd_xferi __user *_xferi = arg;
		struct snd_pcm_runtime *runtime = substream->runtime;
		snd_pcm_sframes_t result;
		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
			return -EBADFD;
		if (put_user(0, &_xferi->result))
			return -EFAULT;
		if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
			return -EFAULT;
		result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
		__put_user(result, &_xferi->result);
		return result < 0 ? result : 0;
	}
	case SNDRV_PCM_IOCTL_WRITEN_FRAMES:
	{
		struct snd_xfern xfern;
		struct snd_xfern __user *_xfern = arg;
		struct snd_pcm_runtime *runtime = substream->runtime;
		void __user **bufs;
		snd_pcm_sframes_t result;
		if (runtime->status->state == SNDRV_PCM_STATE_OPEN)
			return -EBADFD;
		if (runtime->channels > 128)
			return -EINVAL;
		if (put_user(0, &_xfern->result))
			return -EFAULT;
		if (copy_from_user(&xfern, _xfern, sizeof(xfern)))
			return -EFAULT;
		bufs = memdup_user(xfern.bufs,sizeof(void *) * runtime->channels);
		if (IS_ERR(bufs))
			return PTR_ERR(bufs);
		result = snd_pcm_lib_writev(substream, bufs, xfern.frames);
		kfree(bufs);
		__put_user(result, &_xfern->result);
		return result < 0 ? result : 0;
	}
	case SNDRV_PCM_IOCTL_REWIND:
	{
		snd_pcm_uframes_t frames;
		snd_pcm_uframes_t __user *_frames = arg;
		snd_pcm_sframes_t result;
		if (get_user(frames, _frames))
			return -EFAULT;
		if (put_user(0, _frames))
			return -EFAULT;
		result = snd_pcm_playback_rewind(substream, frames);
		__put_user(result, _frames);
		return result < 0 ? result : 0;
	}
	case SNDRV_PCM_IOCTL_FORWARD:
	{
		snd_pcm_uframes_t frames;
		snd_pcm_uframes_t __user *_frames = arg;
		snd_pcm_sframes_t result;
		if (get_user(frames, _frames))
			return -EFAULT;
		if (put_user(0, _frames))
			return -EFAULT;
		result = snd_pcm_playback_forward(substream, frames);
		__put_user(result, _frames);
		return result < 0 ? result : 0;
	}
	}
	return snd_pcm_common_ioctl1(file, substream, cmd, arg); //通用命令
}

13.3 通用控制命令

static int snd_pcm_common_ioctl1(struct file *file,struct snd_pcm_substream *substream,unsigned int cmd, void __user *arg)
{
	switch (cmd) {
	case SNDRV_PCM_IOCTL_PVERSION:
		return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;
	case SNDRV_PCM_IOCTL_INFO:
		return snd_pcm_info_user(substream, arg);
	case SNDRV_PCM_IOCTL_TSTAMP:	/* just for compatibility */
		return 0;
	case SNDRV_PCM_IOCTL_TTSTAMP:
		return snd_pcm_tstamp(substream, arg);
	case SNDRV_PCM_IOCTL_HW_REFINE:
		return snd_pcm_hw_refine_user(substream, arg);
	case SNDRV_PCM_IOCTL_HW_PARAMS:
		return snd_pcm_hw_params_user(substream, arg);
	case SNDRV_PCM_IOCTL_HW_FREE:
		return snd_pcm_hw_free(substream);
	case SNDRV_PCM_IOCTL_SW_PARAMS:
		return snd_pcm_sw_params_user(substream, arg);
	case SNDRV_PCM_IOCTL_STATUS:
		return snd_pcm_status_user(substream, arg);
	case SNDRV_PCM_IOCTL_CHANNEL_INFO:
		return snd_pcm_channel_info_user(substream, arg);
	case SNDRV_PCM_IOCTL_PREPARE:
		return snd_pcm_prepare(substream, file);
	case SNDRV_PCM_IOCTL_RESET:
		return snd_pcm_reset(substream);
	case SNDRV_PCM_IOCTL_START:
		return snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
	case SNDRV_PCM_IOCTL_LINK:
		return snd_pcm_link(substream, (int)(unsigned long) arg);
	case SNDRV_PCM_IOCTL_UNLINK:
		return snd_pcm_unlink(substream);
	case SNDRV_PCM_IOCTL_RESUME:
		return snd_pcm_resume(substream);
	case SNDRV_PCM_IOCTL_XRUN:
		return snd_pcm_xrun(substream);
	case SNDRV_PCM_IOCTL_HWSYNC:
		return snd_pcm_hwsync(substream);
	case SNDRV_PCM_IOCTL_DELAY:
		return snd_pcm_delay(substream, arg);
	case SNDRV_PCM_IOCTL_SYNC_PTR:
		return snd_pcm_sync_ptr(substream, arg);
#ifdef CONFIG_SND_SUPPORT_OLD_API
	case SNDRV_PCM_IOCTL_HW_REFINE_OLD:
		return snd_pcm_hw_refine_old_user(substream, arg);
	case SNDRV_PCM_IOCTL_HW_PARAMS_OLD:
		return snd_pcm_hw_params_old_user(substream, arg);
#endif
	case SNDRV_PCM_IOCTL_DRAIN:
		return snd_pcm_drain(substream, file);
	case SNDRV_PCM_IOCTL_DROP:
		return snd_pcm_drop(substream);
	case SNDRV_PCM_IOCTL_PAUSE:
	{
		int res;
		snd_pcm_stream_lock_irq(substream);
		res = snd_pcm_pause(substream, (int)(unsigned long)arg);
		snd_pcm_stream_unlock_irq(substream);
		return res;
	}
	}
	snd_printd("unknown ioctl = 0x%x\n", cmd);
	return -ENOTTY;
}

13.4 命令总结

//capture专用命令
SNDRV_PCM_IOCTL_WRITEI_FRAMES					
SNDRV_PCM_IOCTL_WRITEN_FRAMES	
SNDRV_PCM_IOCTL_REWIND			
SNDRV_PCM_IOCTL_FORWARD			
//playback专用命令
SNDRV_PCM_IOCTL_READI_FRAMES					
SNDRV_PCM_IOCTL_READN_FRAMES	
SNDRV_PCM_IOCTL_REWIND			
SNDRV_PCM_IOCTL_FORWARD			
//通用命令
SNDRV_PCM_IOCTL_PVERSION			
SNDRV_PCM_IOCTL_INFO				
SNDRV_PCM_IOCTL_TSTAMP		
SNDRV_PCM_IOCTL_TTSTAMP			
SNDRV_PCM_IOCTL_HW_REFINE		
SNDRV_PCM_IOCTL_HW_PARAMS		
SNDRV_PCM_IOCTL_HW_FREE			
SNDRV_PCM_IOCTL_SW_PARAMS		
SNDRV_PCM_IOCTL_STATUS			
SNDRV_PCM_IOCTL_CHANNEL_INFO	
SNDRV_PCM_IOCTL_PREPARE			
SNDRV_PCM_IOCTL_RESET			
SNDRV_PCM_IOCTL_START			
SNDRV_PCM_IOCTL_LINK			
SNDRV_PCM_IOCTL_UNLINK			
SNDRV_PCM_IOCTL_RESUME			
SNDRV_PCM_IOCTL_XRUN			
SNDRV_PCM_IOCTL_HWSYNC			
SNDRV_PCM_IOCTL_DELAY			
SNDRV_PCM_IOCTL_SYNC_PTR		
SNDRV_PCM_IOCTL_HW_REFINE_OLD	
SNDRV_PCM_IOCTL_HW_PARAMS_OLD	
SNDRV_PCM_IOCTL_DRAIN			
SNDRV_PCM_IOCTL_DROP			
SNDRV_PCM_IOCTL_PAUSE



alsa音频架构4-声卡控制

第十五部分 声卡控制接口

前面第一篇中讲到了每注册一个声卡都会创建注册一个对应的声卡控制设备,并给出了其操作函数集snd_ctl_f_ops,
在"amixer,aplay,arecord的使用"一文中提及了amixer如何设置获取声卡的控制选项
接着在第二篇中讲到Asoc声卡驱动编写的步骤,步骤中没牵扯到控制设备的控制选项,这里要补充第5个步骤
第5个步骤就是在设备驱动中要调用snd_soc_add_controls函数添加相应的控制选项,也就是添加一系列的snd_kcontrol_new对象

1.snd_kcontrol_new结构体

struct snd_kcontrol_new {
	snd_ctl_elem_iface_t iface;	/* 接口号 */
	unsigned int device;		/* 设备号 */
	unsigned int subdevice;		/* 子设备号 */
	unsigned char *name;		/* item名 */
	unsigned int index;			/* item索引号 */
	unsigned int access;		/* 通道权限(读/写/执行) */
	unsigned int count;			/* count of same elements */
	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;
};


3.snd_soc_add_controls函数原型

int snd_soc_add_controls(struct snd_soc_codec *codec,const struct snd_kcontrol_new *controls, int num_controls)
{
	struct snd_card *card = codec->card->snd_card;	//获取声卡
	int err, i;

	for (i = 0; i < num_controls; i++) {	//添加num_controls个控制元素
		const struct snd_kcontrol_new *control = &controls[i];	//获取snd_kcontrol_new对象
		err = snd_ctl_add(card, snd_soc_cnew(control, codec, NULL));	//添加1个控制元素
		if (err < 0) {
			dev_err(codec->dev, "%s: Failed to add %s: %d\n",codec->name, control->name, err);
			return err;
		}
	}
	return 0;
}
EXPORT_SYMBOL_GPL(snd_soc_add_controls);

调用snd_ctl_add函数之前会先调用snd_soc_cnew创建一个新的snd_kcontrol结构体对象

4.snd_kcontrol结构体

struct snd_kcontrol {
	struct list_head list;		/* list of controls */
	struct snd_ctl_elem_id id;	//控制元素id
	unsigned int count;		/* count of same elements */
	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 */
};

5.snd_soc_cnew函数

struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,void *data, char *long_name)
{
	struct snd_kcontrol_new template;

	memcpy(&template, _template, sizeof(template));	//拷贝一份snd_kcontrol_new
	if (long_name)	//"NULL"
		template.name = long_name;
	template.index = 0;
	return snd_ctl_new1(&template, data);
}
EXPORT_SYMBOL_GPL(snd_soc_cnew);

snd_soc_cnew函数基本只是做个拷贝工作,主要的任务还是交给snd_ctl_new1函数处理

5.1snd_ctl_new1函数

struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,void *private_data)
{
	struct snd_kcontrol kctl;
	unsigned int access;
	
	if (snd_BUG_ON(!ncontrol || !ncontrol->info))
		return NULL;
	memset(&kctl, 0, sizeof(kctl));	//初始化snd_kcontrol对象
//_____________________下面的工作基本上是将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));	//拷贝item名到snd_kcontrol对象
		if (strcmp(ncontrol->name, kctl.id.name) != 0)
			snd_printk(KERN_WARNING"Control name '%s' truncated to '%s'\n",ncontrol->name, kctl.id.name);
	}
	kctl.id.index = ncontrol->index;	//索引号
	kctl.count = ncontrol->count ? ncontrol->count : 1;
	access = ncontrol->access == 0 ?	//通道权限
			SNDRV_CTL_ELEM_ACCESS_READWRITE :(ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|SNDRV_CTL_ELEM_ACCESS_INACTIVE|
			SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE|SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND|SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK));
	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;	//指向codec设备
	return snd_ctl_new(&kctl, access);
}

赋值完后又交由snd_ctl_new函数处理通道权限

5.2snd_ctl_new函数

static struct snd_kcontrol *snd_ctl_new(struct snd_kcontrol *control,unsigned int access)
{
	struct snd_kcontrol *kctl;
	unsigned int idx;
	
	if (snd_BUG_ON(!control || !control->count))
		return NULL;
	if (control->count > MAX_CONTROL_COUNT)
		return NULL;
	kctl = kzalloc(sizeof(*kctl) + sizeof(struct snd_kcontrol_volatile) * control->count, GFP_KERNEL);	//分配snd_kcontrol内存	
	if (kctl == NULL) {
		snd_printk(KERN_ERR "Cannot allocate control instance\n");
		return NULL;
	}
	*kctl = *control;	//获取snd_kcontrol对象
	for (idx = 0; idx < kctl->count; idx++)
		kctl->vd[idx].access = access;	//设置通道权限
	return kctl;
}

最后将初始化,赋值处理好的snd_kcontrol结构体对象返回作为snd_ctl_add函数的第二个参数

6.添加1个控制元素

添加1个控制元素由snd_ctl_add函数实现,而1个控制元素用snd_ctl_elem_id结构体对象去表达
6.1 snd_ctl_elem_id结构体

struct snd_ctl_elem_id {
	unsigned int numid;			/* 控制编号 */
	snd_ctl_elem_iface_t iface;	/* 接口号 */
	unsigned int device;		/* 设备号 */
	unsigned int subdevice;		/* 子设备号 */
	unsigned char name[44];		/* item名 */
	unsigned int index;			/* item索引号 */
};

6.2 snd_ctl_add函数

int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
	struct snd_ctl_elem_id id;
	unsigned int idx;
	int err = -EINVAL;

	if (! kcontrol)
		return err;
	if (snd_BUG_ON(!card || !kcontrol->info))
		goto error;
	id = kcontrol->id;//获取控制元素id
	down_write(&card->controls_rwsem);
	if (snd_ctl_find_id(card, &id)) {	//根据id查找控制接口
		up_write(&card->controls_rwsem);
		snd_printd(KERN_ERR "control %i:%i:%i:%s:%i is already present\n",id.iface,id.device,id.subdevice,id.name,id.index);
		err = -EBUSY;
		goto error;
	}
	if (snd_ctl_find_hole(card, kcontrol->count) < 0) {	//设置card的last_numid
		up_write(&card->controls_rwsem);
		err = -ENOMEM;
		goto error;
	}
	list_add_tail(&kcontrol->list, &card->controls);	//添加到所属声卡控制链表(以后可以在链表中搜查需要的项)
	card->controls_count += kcontrol->count;
	kcontrol->id.numid = card->last_numid + 1;	//获取控制编号
	card->last_numid += kcontrol->count;	//更新card的last_numid
	up_write(&card->controls_rwsem);
	for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)
		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
	return 0;
 error:
	snd_ctl_free_one(kcontrol);
	return err;
}
EXPORT_SYMBOL(snd_ctl_add);

设置snd_kcontrol对象的控制元素snd_ctl_elem_id对象,并将其添加到所属声卡的控制链表中

7.前面提到的snd_ctl_f_ops

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,
};

主要关注其unlocked_ioctl方法既snd_ctl_ioctl

8.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;
	if (snd_BUG_ON(!card))
		return -ENXIO;
	switch (cmd) {
	case SNDRV_CTL_IOCTL_PVERSION:
		return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
	case SNDRV_CTL_IOCTL_CARD_INFO:
		return snd_ctl_card_info(card, ctl, cmd, argp);
	case SNDRV_CTL_IOCTL_ELEM_LIST:	//对应amixer controls
		return snd_ctl_elem_list(card, argp);
	case SNDRV_CTL_IOCTL_ELEM_INFO:	//对应amixer contents
		return snd_ctl_elem_info_user(ctl, argp);
	case SNDRV_CTL_IOCTL_ELEM_READ:	//对应amixer cget cID P
		return snd_ctl_elem_read_user(card, argp);
	case SNDRV_CTL_IOCTL_ELEM_WRITE:     //对应amixer cget cID
		return snd_ctl_elem_write_user(ctl, argp);
	case SNDRV_CTL_IOCTL_ELEM_LOCK:
		return snd_ctl_elem_lock(ctl, argp);
	case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
		return snd_ctl_elem_unlock(ctl, argp);
	case SNDRV_CTL_IOCTL_ELEM_ADD:
		return snd_ctl_elem_add_user(ctl, argp, 0);
	case SNDRV_CTL_IOCTL_ELEM_REPLACE:
		return snd_ctl_elem_add_user(ctl, argp, 1);
	case SNDRV_CTL_IOCTL_ELEM_REMOVE:
		return snd_ctl_elem_remove(ctl, argp);
	case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
		return snd_ctl_subscribe_events(ctl, ip);
	case SNDRV_CTL_IOCTL_TLV_READ:
		return snd_ctl_tlv_ioctl(ctl, argp, 0);
	case SNDRV_CTL_IOCTL_TLV_WRITE:
		return snd_ctl_tlv_ioctl(ctl, argp, 1);
	case SNDRV_CTL_IOCTL_TLV_COMMAND:
		return snd_ctl_tlv_ioctl(ctl, argp, -1);
	case SNDRV_CTL_IOCTL_POWER:
		return -ENOPROTOOPT;
	case SNDRV_CTL_IOCTL_POWER_STATE:
		return put_user(card->power_state, ip) ? -EFAULT : 0;
	}
	down_read(&snd_ioctl_rwsem);
	list_for_each_entry(p, &snd_control_ioctls, list) {
		err = p->fioctl(card, ctl, cmd, arg);
		if (err != -ENOIOCTLCMD) {
			up_read(&snd_ioctl_rwsem);
			return err;
		}
	}
	up_read(&snd_ioctl_rwsem);
	snd_printdd("unknown ioctl = 0x%x\n", cmd);
	return -ENOTTY;
}









 

 

 

 

 

 

 

 

 

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值