Linux Alsa驱动框架

在Linux内核中,对于声卡驱动,其本质上也是一个字符设备驱动程序,主设备号为116

//ls -al dev/snd/
crw-rw----  1 system audio 116,   3 1970-01-01 08:00 controlC0//0号声卡的控制节点
crw-rw----  1 system audio 116,   6 1970-01-01 08:00 controlC1
crw-rw----  1 system audio 116,   9 1970-01-01 08:00 controlC2
crw-rw----  1 system audio 116,   2 1970-01-01 08:00 pcmC0D0p//播放节点
crw-rw----  1 system audio 116,   5 1970-01-01 08:00 pcmC1D0c//录音节点
crw-rw----  1 system audio 116,   4 1970-01-01 08:00 pcmC1D0p
crw-rw----  1 system audio 116,   8 1970-01-01 08:00 pcmC2D0c
crw-rw----  1 system audio 116,   7 1970-01-01 08:00 pcmC2D0p
crw-rw----  1 system audio 116,  33 1970-01-01 08:00 timer

对于字符设备驱动,在Linux内核中都有一个file_operations结构体来管理open/read/write 等等函数,主设备号为116的设备驱动,其file_operations为snd_fops,源码路径:kernel\sound\core\sound.c

static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
};

snd_fops 只有一个open函数,来看一下这个函数

static int snd_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode);//取出次设备号
	struct snd_minor *mptr = NULL;
	const struct file_operations *new_fops;
	int err = 0;

	mutex_lock(&sound_mutex);
	mptr = snd_minors[minor];//根据次设备号,从snd_minors数组中取出一项
	
	new_fops = fops_get(mptr->f_ops);//得到一个新的file_operations结构体
	mutex_unlock(&sound_mutex);
	if (!new_fops)
		return -ENODEV;
	replace_fops(file, new_fops);//用新的结构体替换

	if (file->f_op->open)
		err = file->f_op->open(inode, file);//调用新结构体的open函数
	return err;
}

可以看出,snd_open起到了一个中转的作用,用户空间调用open/read/write函数,实际上是调用了new_fops中对应的函数,而new_fops是根据次设备号,从snd_minors数组中取出的。接下来看下这个snd_minors数组中的元素是在哪里添加的
snd_minors元素的添加,有两个调用链,分别是snd_card_new和snd_pcm_new
snd_card_new
源码路径:kernel\sound\core\init.c

//kernel\sound\core\init.c
int snd_card_new(struct device *parent, int idx, const char *xid,
		    struct module *module, int extra_size,
		    struct snd_card **card_ret)
{
	struct snd_card *card;
	card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL);
	if (xid)
		strlcpy(card->id, xid, sizeof(card->id));
	//省略
	if (idx < 0) /* first check the matching module-name slot */
		idx = get_slot_from_bitmask(idx, module_slot_match, module);
	if (idx < 0) /* if not matched, assign an empty slot */
		idx = get_slot_from_bitmask(idx, check_empty_slot, module);
	//省略
	card->dev = parent;
	card->number = idx;//number为idx
	card->module = module;
	//省略
	card->card_dev.parent = parent;
	card->card_dev.class = sound_class;
	card->card_dev.release = release_card_device;
	card->card_dev.groups = card->dev_groups;
	
	err = snd_ctl_create(card);
	//省略
}

snd_card_new函数主要是创建了一个snd_card结构体,并对其成员进行初始化,最后调用snd_ctl_create

//kernel\sound\core\control.c
int snd_ctl_create(struct snd_card *card)
{
	static struct snd_device_ops ops = {
		.dev_free = snd_ctl_dev_free,
		.dev_register =	snd_ctl_dev_register,
		.dev_disconnect = snd_ctl_dev_disconnect,
	};
	int err;
	//省略
	snd_device_initialize(&card->ctl_dev, card);
	dev_set_name(&card->ctl_dev, "controlC%d", card->number);//控制节点的名字为controlC+ number

	err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);//1
	if (err < 0)
		put_device(&card->ctl_dev);
	return err;
}

注释1处,在snd_device_new函数里面,会创建一个snd_device结构体,并将ops保存到snd_device结构体的ops成员中,最后将其加入到list链表中。注册声卡时(调用snd_card_register),会从list链表中取出snd_device,并调用其ops的dev_register 函数,也就是执行snd_ctl_dev_register函数

//kernel\sound\core\control.c
static int snd_ctl_dev_register(struct snd_device *device)
{
	struct snd_card *card = device->device_data;//取出snd_card

	return snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,
				   &snd_ctl_f_ops, card, &card->ctl_dev);
}

snd_register_device

int snd_register_device(int type, struct snd_card *card, int dev,
			const struct file_operations *f_ops,
			void *private_data, struct device *device)
{
	int minor;
	int err = 0;
	struct snd_minor *preg;

	if (snd_BUG_ON(!device))
		return -EINVAL;

	preg = kmalloc(sizeof *preg, GFP_KERNEL);
	if (preg == NULL)
		return -ENOMEM;
	preg->type = type;
	preg->card = card ? card->number : -1;
	preg->device = dev;
	preg->f_ops = f_ops;//f_ops为传入进来的snd_ctl_f_ops
	preg->private_data = private_data;
	preg->card_ptr = card;
	mutex_lock(&sound_mutex);
	minor = snd_find_free_minor(type, card, dev);
	
	//省略
	snd_minors[minor] = preg;//放入元素

}

最后是往snd_minors元素中放入元素,其f_ops为snd_ctl_f_ops

snd_pcm_new
源码路径:kernel\sound\core\pcm.c

//kernel\sound\core\pcm.c
int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)
{
	return _snd_pcm_new(card, id, device, playback_count, capture_count,
			false, rpcm);
}

_snd_pcm_new

//kernel\sound\core\pcm.c
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, bool internal,
		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,
	};
	static struct snd_device_ops internal_ops = {
		.dev_free = snd_pcm_dev_free,
	};
	
	pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);

	pcm->card = card;
	pcm->device = device;
	pcm->internal = internal;
	
	/*在snd_pcm_new_stream函数中确定播放节点格式为 pcmC%iD%ip,录音节点格式为pcmC%iD%ic*/
	err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
				 playback_count);//播放
	err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);//录音
	
	err = snd_device_new(card, SNDRV_DEV_PCM, pcm,
			     internal ? &internal_ops : &ops);

	//省略

snd_device_new前面提过,在注册声卡时导致ops的dev_register 函数被调用,也就是调用snd_pcm_dev_register

//kernel\sound\core\pcm.c
static int snd_pcm_dev_register(struct snd_device *device)
{
	int cidx, err;
		//省略
	for (cidx = 0; cidx < 2; cidx++) {
		/* register pcm */
		err = snd_register_device(devtype, pcm->card, pcm->device,
					  &snd_pcm_f_ops[cidx], pcm,
					  &pcm->streams[cidx].dev);
		//省略
	return err;
}

对于录音和播放节点,都会调用snd_register_device往snd_minors数组中放入元素,播放节点对应的f_ops为snd_pcm_f_ops[0],录音对应的为snd_pcm_f_ops[1]

总结
在Alsa框架中,Linux驱动已经为我们准备好了这些file_operations结构体并放入snd_minors中,要写一个Alsa声卡驱动我们只需要做以下三点:

  • 调用snd_card_new,分配snd_card,并创建控制节点
  • 调用snd_pcm_new 创建pcm节点
  • 调用snd_card_register注册snd_card

一个声卡必然会有控制节点。控制节点对应的file_operations为snd_ctl_f_ops

//kernel\sound\core\control.c
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,
};

一个声卡不一定会有pcm节点,pcm节点对应的file_operations为snd_pcm_f_ops。snd_pcm_f_ops[0]对应的是播放,snd_pcm_f_ops[1]对应的是录音

//kernel\sound\core\pcm_native.c
const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.write_iter =		snd_pcm_writev,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_poll,
		.unlocked_ioctl =	snd_pcm_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.read_iter =		snd_pcm_readv,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_poll,
		.unlocked_ioctl =	snd_pcm_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	}
};
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ALSA(Advanced Linux Sound Architecture)是Linux内核中的音频驱动框架,它提供了一套完整的音频处理流程,包括音频采集、音频处理和音频播放等功能。ALSA框架的设计目标是提供一个高效、灵活、可靠的音频处理框架,让开发人员能够方便地开发音频应用程序。 ALSA框架的核心包括以下几个组件: 1. 驱动程序:驱动程序是ALSA框架的核心组件,负责管理音频设备硬件,并提供音频数据输入输出的接口。ALSA驱动程序一般由硬件厂商或开源社区开发,可以通过内核模块的形式加载到Linux内核中。 2. 应用程序接口:ALSA框架提供了一套完整的应用程序接口,包括ALSA库和ALSA命令行工具。ALSA库提供了一组API,让开发人员能够方便地访问ALSA驱动程序提供的音频数据输入输出接口。ALSA命令行工具则提供了一组命令行工具,让用户能够方便地对音频设备进行配置和管理。 3. 中间件:ALSA框架还提供了一些中间件组件,如MIDI子系统、混音器子系统等,用于提供更高级的音频处理功能。 ALSA框架的音频处理流程如下: 1. 音频采集:当音频设备接收到音频信号时,ALSA驱动程序将音频信号采集到内存中,并通过DMA(直接内存访问)将音频数据写入音频缓冲区。 2. 音频处理:ALSA驱动程序将音频信号从音频缓冲区读取到内存中,然后对音频数据进行处理。音频处理包括音频格式转换、音频采样率转换、音频混音等处理。 3. 音频播放:ALSA驱动程序将处理后的音频数据从内存中读取到音频缓冲区,并通过DMA将音频数据传输到音频设备中进行播放。 总之,ALSA框架提供了一套完整的音频处理流程,让开发人员能够方便地开发音频应用程序,并提供了一组API和命令行工具,方便用户对音频设备进行配置和管理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值