Board logo

标题: 深入OSS开发(2) [打印本页]

作者: look_w    时间: 2018-5-9 19:44     标题: 深入OSS开发(2)

3.非阻塞写(non-blocking write)如果播放程序写入的速度超过了DAC的播放速度,DMA buffer就会充满了音频数据。应用程序调用write时就会因为没有空闲的DMA buffer而被阻塞,直到DMA buffer出现空闲为止。此时,从某种程度来说,应用程序的推进速度依赖于播放的速度,不同的播放速度就会产生不同的推进速度。因此,有时我们不希望write被阻塞,这就需要我们能够知道DMA buffer的使用情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
for (;;) {
audio_buf_info info;
/* Ask OSS if there is any free space in the buffer. */
if (ioctl(dsp,SNDCTL_DSP_GETOSPACE,&info) != 0) {
perror("Unable to query buffer space");
close(dsp);
return 1;
};
/* Any empty fragments? */
if (info.fragments > 0) break;
/* Not enough free space in the buffer. Waste time. */
usleep(100);
};




以上的代码不停的查询驱动程序中是否有空的fragment(SNDCTL_DSP_GETOSPACE),如果没有,则进入睡眠(usleep(100)),此时应用程序做其它的事情,比如更新画面,网络传输等。如果有空闲的fragment(info.fragments > 0),则退出循环,接着就可以进行非阻塞的write了。
4.DMA buffer的直接访问(mmap)除了依赖于操作系统内核提供更好的调度性能,音频播放应用程序也可以采用一些技术以提高音频播放的实时性。绕过APP buffer,直接访问DMA buffer的mmap方法就是其中之一。
我们知道,将音频数据输出到音频设备通常使用系统调用write,但是这会带来性能上的损失,因为要进行一次从用户空间到内核空间的缓冲区拷贝。这时,可以考虑利用mmap系统调用,获得直接访问DMA buffer的能力。DMA控制器不停的扫描DMA buffer,将数据发送到DAC。这有点类似于显卡对显存的操作,大家都知道,GUI可以通过mmap将framebuffer(显存)映射到自己的地址空间,然后直接操纵显存。这里的DMA buffer就是声卡的framebuffer。
理解mmap方法的最好方法是通过实际的例子, 。      
代码中有详细的注释,这里只给出一些说明。
PlayerDMA函数的参数samples指向存放音频数据的缓冲,rate/bits/channels分别说明音频数据的采样速率、每次采样的位数、声道数。
在打开/dev/dsp以后,根据/rate/bits/channels参数的要求配置驱动程序。需要注意的是,这些要求并一定能得到满足,驱动程序要根据自己的情况选择,因此在配置后,需要再次查询,获取驱动程序真正使用的参数值。
在使用mmap之前,要查看驱动程序是否支持这种模式(SNDCTL_DSP_GETCAPS)。使用SNDCTL_DSP_GETOSPACE得知驱动选择的framgment大小和个数,就可以计算出全部DMA buffer的大小dmabuffer_size。
mmap将dmabuffer_size大小的DMA buffer映射到调用进程的地址空间,DMA buffer在应用进程的起始地址为dmabuffer。以后就可以直接使用指针dmabuffer访问DMA buffer了。这里需要对mmap中的参数做些解释。
音频驱动程序针对播放和录音分别有各自的缓冲区,mmap不能同时映射这两组缓冲,具体选择映射哪个缓冲取决于mmap的prot参数。PROT_READ选择输入(录音)缓冲,PROT_WRITE选择输出(播放)缓冲,代码中使用了PROT_WRITE|PROT_READ,也是选择输出缓冲。(这是BSD系统的要求,如果只有PROT_WRITE,那么每次对缓冲的访问都会出现segmentation/bus error)。
一旦DMA buffer被mmap后,就不能再通过read/write接口来控制驱动程序了。只能通过SNDCTL_DSP_SETTRIGGER打开DAC的使能位,当然,先要关闭使能位。
DMA一旦启动后,就会周而复始的扫描DMA buffer。当然我们总是希望提前为DMA准备好新的数据,使得DMA的播放始终连续。因此,PlayerDMA函数将mmap后的DMA buffer分割成前后两块,中间设置一个界限。当DMA扫描前面一块时,就填充后面一块。一旦DMA越过了界限,就去填充前面一块。
使用mmap的问题是,不是所有的声卡驱动程序都支持mmap方式。因此,在出现不兼容的情况下,应用程序要能够转而去使用传统的方式。
最后,为了能深入的理解mmap的实现原理,我们以某种声卡驱动程序为例,介绍了其内部mmap函数时具体实现。
audio_mmap()是实现mmap接口的函数,它首先根据mmap调用的prot参数(vma->vm_flags),选择合适的缓冲(输入还是输出);vma->vm_end - vma->vm_start为需要映射到应用进程地址空间的大小,必须和DMA buffer的大小(s->fragsize * s->nbfrags)一致;如果DMA buffer还没有建立,则调用audio_setup_buf(s)建立;接着对所有的fragment,从映射起始地址开始(vma->vm_start),建立实际物理地址与映射的虚拟地址之间的对应关系(remap_page_range)。最后设置mmap标志(s->mapped = 1)。
5.结束语当然,除了上面所讨论的问题以外,音频应用的开发还有很多实际的问题需要去面对,比如多路音频流的合并,各种音频文件格式的打开等等。
OSS音频接口存在于Linux内核中许多年了,由于在体系结构上有许多的局限性,在Linux 2.6内核中引入了一种全新的音频体系和接口——ALSA(Advanced Linux Sound Architecture),它提供了很多比OSS更好的特性,包括完全的thread-safe和SMP-safe,模块化的设计,支持多个声卡等等。为了保持和OSS接口的兼容性,ALSA还提供了OSS的仿真接口,使得那些为OSS接口开发的大量应用程序仍然能够在新的ALSA体系下正常的工作。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0