在上小节我们分析了Adndroid系统音频的框架,这么一个复杂的系统我们怎么去学习呢?我们从下往上学,先分析音频的驱动程序,看看linux系统中驱动程序是怎么编写的,他的结构是怎么样的,然后在琢磨Tinyalsa,是如何去播放,录制声音的。在该课时接下来的所有小节都会讲解linux音频驱动程序。该小节先讲解一下alsa音频驱动的框架:
在编写应用程序的时候,我们都是使用标准的open,read,write等访问驱动程序,最简单的方法就是驱动也提供与应用程序对应的open,read,write等。
一般编写驱动程序如下:
1.构造一个结构体,即file_opevations结构体
2.告诉内核,即通过register_char注册。
上面是简单的驱动程序,但是我们的alsa音频驱动也属于linux,所以他也遵循以上套路,linux的声卡驱动有两个版本:
分别为oss,alsa,但是oss是一个收费的版本,现在流行的是alsa(Advanced Linux Sound Architecture),下面是我们开发板声卡的相关设备节点:
执行ls -l /dev/snd/
从名字上看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规范,如下:
事先定义好file_opevations结构体,首先是:
顶层:定义一个file_opevations结构体,只实现open。
下层:controlC0,pcmC0D0c,pcmC0D0p等节点分别有自己对应的file_opevations结构体。
这些接口确定之后,上层应用程序就可以使用确定的接口,访问声卡,那么谁访去访问硬件呢?那么显然,还有更加底层的工作:
分配设置一个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)
下面我们进行流程分析:
我们注册声卡的时候,要调用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,
}
};
可以看到有两个定义,一个对应输出,一个对应输入。