08.音频系统:第003课_Linux音频驱动程序:第001节_alsa音频驱动框架

在上小节我们分析了Adndroid系统音频的框架,这么一个复杂的系统我们怎么去学习呢?我们从下往上学,先分析音频的驱动程序,看看linux系统中驱动程序是怎么编写的,他的结构是怎么样的,然后在琢磨Tinyalsa,是如何去播放,录制声音的。在该课时接下来的所有小节都会讲解linux音频驱动程序。该小节先讲解一下alsa音频驱动的框架:
08.音频系统:第003课_Linux音频驱动程序:第001节_alsa音频驱动框架_第1张图片
在编写应用程序的时候,我们都是使用标准的open,read,write等访问驱动程序,最简单的方法就是驱动也提供与应用程序对应的open,read,write等。

一般编写驱动程序如下:
1.构造一个结构体,即file_opevations结构体
2.告诉内核,即通过register_char注册。

上面是简单的驱动程序,但是我们的alsa音频驱动也属于linux,所以他也遵循以上套路,linux的声卡驱动有两个版本:

08.音频系统:第003课_Linux音频驱动程序:第001节_alsa音频驱动框架_第2张图片
分别为oss,alsa,但是oss是一个收费的版本,现在流行的是alsa(Advanced Linux Sound Architecture),下面是我们开发板声卡的相关设备节点:
执行ls -l /dev/snd/
08.音频系统:第003课_Linux音频驱动程序:第001节_alsa音频驱动框架_第3张图片
从名字上看controlC0起控制作用,C0的意思代表第一个声卡,pcmC0D0c与pcmC0D0p是一对,其pcmC0D0c表示声卡0的录音(capture),pcmC0D0p表示声卡0的播放(playbaclc)。

从设备节点,我们可以猜测,一个声卡可以由多个device,一个device有播放,录音通道。

每个设备节点对应一个file_opevations结构体,但是他们的主设备号一样,则对应同一个file_opevations结构体。

我们看看代码是如何实现的,代开kennel/sound/core/sound.c:

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

static int __init alsa_sound_init(void)
	register_chrdev(major, "alsa", &snd_fops)

可以看到snd_fops结构体比较特殊,只有一个snd_open函数,这里snd_open起到的是一个中转的作用,会根据次设备号找到更加具体的file_operations结构体。如controlC0,pcmC0D0c,pcmC0D0p等节分别有自己file_operations结构体。
进入snd_open函数如下:

static int snd_open(struct inode *inode, struct file *file)
	unsigned int minor = iminor(inode);
	mptr = snd_minors[minor];
	/*根据次设备号,获得一个新的file_operations结构体*/
	new_fops = fops_get(mptr->f_ops);
	/*把老的file_operations结构体换成重新获得的file_operations结构体*/
	replace_fops(file, new_fops);

从这里我们可以总结出既然都遵循alsa规范,如下:
08.音频系统:第003课_Linux音频驱动程序:第001节_alsa音频驱动框架_第4张图片
事先定义好file_opevations结构体,首先是:
顶层:定义一个file_opevations结构体,只实现open。
下层:controlC0,pcmC0D0c,pcmC0D0p等节点分别有自己对应的file_opevations结构体。

这些接口确定之后,上层应用程序就可以使用确定的接口,访问声卡,那么谁访去访问硬件呢?那么显然,还有更加底层的工作:
08.音频系统:第003课_Linux音频驱动程序:第001节_alsa音频驱动框架_第5张图片
分配设置一个snd_card结构体,这个结构体能提供参数,设置参数,传输数据等等。我们继续查看源码,在kennel/sound/core/sound.搜索snd_minors:

int snd_register_device(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data, struct device *device)
	snd_minors[minor] = preg;

可以知道snd_minors数组是在snd_register_device函数中被设置,我们在源码中搜索snd_register_device,看他在哪里被调用,然后一路跟随,可以知道snd_register_device被调用的过程如下,注意下面是从上到下被调用的过程:

/*sound.c*/
snd_register_device(int type, struct snd_card *card, int dev,const struct file_operations *f_ops,void *private_data, struct device *device)
	/*Control.c*/
	static int snd_ctl_dev_register(struct snd_device *device)
		/*Control.c*/
		int snd_ctl_create(struct snd_card *card)
			/*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)

最后我们在snd_card_new函数中可以看到一个struct snd_card *card;结构体,如果我们在搜索snd_card_new,可以看到一大堆。

那么我们的声卡驱动应该如何编写呢?一般如下:
1.定义一个struct snd_card *card;
2.设置snd_pcm_new
3.注册snd_card_register(card)

下面我们进行流程分析:
08.音频系统:第003课_Linux音频驱动程序:第001节_alsa音频驱动框架_第6张图片
我们注册声卡的时候,要调用snd_card_new:

int snd_card_new(struct device *parent, int idx, const char *xid,struct module *module, int extra_size,struct snd_card **card_ret)
	err = snd_ctl_create(card);
		static struct snd_device_ops ops = {
			.dev_free = snd_ctl_dev_free,
			.dev_register =	snd_ctl_dev_register,
			.dev_disconnect = snd_ctl_dev_disconnect,
		};
		err = snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
		

可以知道其会创建一个controlC接口,这是一个声卡最最基本的东西,如pcmC0D0c,pcmC0D0p是可以不存在的 ,但是controlC我们必须创建。
执行snd_device_new函数的时候,snd_device_ops结构体的snd_ctl_dev_register函数会被调用:

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,
};
static int snd_ctl_dev_register(struct snd_device *device)
	snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1,&snd_ctl_f_ops, card, &card->ctl_dev);
		

其上的snd_ctl_f_ops结构体,就是对节点真正控制的描述符。我们知道对于声卡还有pcmC0D0c,pcmC0D0p等节点,他们是在哪里设置的呢?我们随便打开一个声卡驱动程序,如Echoaudio.c文件:

static int snd_echo_probe(struct pci_dev *pci,const struct pci_device_id *pci_id)
		err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,0, &card);
		snd_echo_new_pcm(chip)
			snd_pcm_new(chip->card, "PCM", 0, num_pipes_out(chip),num_analog_busses_in(chip), &pcm)
				static struct snd_device_ops ops = {
					.dev_free = snd_pcm_dev_free,
					.dev_register =	snd_pcm_dev_register,
					.dev_disconnect = snd_pcm_dev_disconnect,
				};
				snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count))
				snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) 
				snd_device_new(card, SNDRV_DEV_PCM, pcm, &ops))

其是通过snd_pcm_new进行创建pcmC0D0c,pcmC0D0p节点,在调用snd_pcm_new之后,会导致dev_register = snd_pcm_dev_register函数被调用:

static int snd_pcm_dev_register(struct snd_device *device)
	case SNDRV_PCM_STREAM_PLAYBACK:
		devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
	case SNDRV_PCM_STREAM_CAPTURE:
		devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
	snd_register_device(devtype, pcm->card, pcm->device,&snd_pcm_f_ops[cidx], pcm,&pcm->streams[cidx].dev);		

可以知道,其根据SNDRV_PCM_STREAM_PLAYBACK创建pcmC0D0p,根据SNDRV_DEVICE_TYPE_PCM_CAPTURE则创建
pcmC0D0c节点。

一个pcm,就是一个逻辑设备device,一个pcm中有两个通道stream,一个为playback,一个为capture。他们分别对应节点pcmC0D0c,pcmC0D0p。

在调用snd_register_device函数时,我们需要注册一个snd_pcm_f_ops结构体:

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_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,
	},
	{
		.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_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,
	}
};

可以看到有两个定义,一个对应输出,一个对应输入。

你可能感兴趣的:(RK3399移植,linux,音视频,RK3399,驱动移植,嵌入式开发)