热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

深入OSS(OpenSoundSystem)的开发

文章标题:深入OSS(OpenSoundSystem)的开发。Linux是中国IT实验室的一个技术频道。包含桌面应用,Linux系统管理,内核研究,嵌入式系统和开源等一些基本分类

  本文将对OSS(Open Sound System)的开发进行一些深入的讨论,具体的内容包括:播放音频的时延问题,并定量的对不同的缓冲区配置进行分析;非阻塞write;应用程序对驱动程序中DMA buffer的直接访问。这些是在深入OSS的开发过程中,开发者会遇到的一些实际问题,比如,在开发基于Linux平台的游戏程序时,就必须要考虑到如何降低播放音频的时延,使得在需要的时候,能够将游戏的音效尽快地播放出来,并与画面的进行保持同步。
  
  在讨论这些方面时,除了从使用的角度介绍以外,还结合具体的驱动实现,分析这些功能对应的内部原理,以加深读者的理解。
  
  为了在阅读文章时有一个共同的认识,本文首先简单介绍了OSS的一些基本内容。
  
  1.OSS简介
  OSS的层次结构非常简单,应用程序通过API(定义于 )访问OSS driver,OSS driver控制声卡。如下图所示:
  
 

  声卡中主要有两个基本装置:Mixer和CODEC(ADC/DAC)。Mixer用来控制输入音量的大小,对应的设备文件为/dev/mixer;CODEC用来实现录音(模拟信号转变为数字信号)和播放声音(数字信号转变为模拟信号)的功能,对应的设备文件为/dev/dsp。
  
  开发OSS应用程序的一般流程是:
  1)包含OSS头文件:#include
  2)打开设备文件,返回文件描述符
  3)使用ioctl设置设备的参数,控制设备的特性
  4)对于录音,从设备读(read)
  5)对于播放,向设备写(write)
  6)关闭打开的设备
  
  2.缓冲区设置的性能分析
  在设置驱动内部的缓冲区时,存在一个矛盾:在声卡驱动程序中,为了防止抖动的出现,保证播放的性能,设置了内部缓冲区-DMA buffer。在播放时,应用程序通过驱动程序首先将音频数据从应用程序缓冲区-APP buffer,写入到DMA buffer。接着,由DMA控制器把DMA buffer中的音频数据发送到DAC(Digital-Analog Converter)。某些时刻CPU非常的繁忙,比如正在从磁盘读入数据,或者正在重画屏幕,没有时间向DMA buffer放入新的音频数据。DAC由于没有输入新的音频数据,导致声音播放的间断,这就出现了声音的抖动现象。此时,需要将DMA buffer设置的足够大,使得DAC始终有数据播放。但是,DMA buffer的增大使得每次从APP buffer拷贝的时间也变长,导致了更大的播放延迟。这对于那些延迟敏感的应用场合,如与用户有交互的音频应用程序,就会出现问题。
  
  对于这个矛盾,可以从两个不同的方面分别着手解决。驱动程序采用多缓冲(Multi-buffering)的方式,即将大的DMA buffer分割成多个小的缓冲区,称之为fragment,它们的大小相同。驱动程序开始时只需等待两个fragment满了就开始播放。这样可以通过增加fragment的个数来增加缓冲区的大小,但同时每个fragment被限制在合适的大小,也不影响时延。音频驱动程序中的多缓冲机制一般会利用底层DMA控制器的scatter-gather功能。
  
  另一方面,应用程序也可指导驱动程序选择合适大小的缓冲区,使得在没有抖动的情况下,时延尽可能的小。特别的,应用程序将驱动程序中的缓冲通过mmap映射到自己地址空间后,会以自己的方式来处理这些缓冲区(与驱动程序的不一定一致),这时应用程序往往会先根据自己的需要设置驱动程序中内部缓冲区的大小。
  
  在OSS的ioctl接口中,SNDCTL_DSP_SETFRAGMENT就是用来设置驱动程序内部缓冲区大小。具体的用法如下:
  
  int param;
  param = ( 0x0004 <<16) + 0x000a;
  if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, ¶m) == -1) { 
        ...error handling...
  }
  
  参数param由两部分组成:低16位为fragment的大小,此处0x000a表示fragment大小为2^0xa,即1024字节;高16位为fragment的数量,此处为0x0004,即4个fragement。设置好fragment参数后,通过ioctl的SNDCTL_DSP_SETFRAGMENT命令调整驱动程序中的缓冲区。
  
  为了给音频程序的开发者展示缓冲区配置对播放效果的影响,我们将对缓冲区配置与播放性能的关系进行测试。下面首先介绍测试的环境,包括测试方法的原理和测试结果的含义;接着针对两种情况进行测试,并解释测试的结果。
  
  测试环境
  测试是在PC机上进行的,具体的测试环境参见下表。
  

  测试软件(latencytest)由两部分组成:音频播放测试程序、系统运行负载模拟程序。(注:latencytest软件主要目的是测试内核的时延,但这里作为对不同缓冲配置进行比较的工具。)
  
  音频播放测试程序的工作流程见下面的代码。为了保证音频播放在调度上的优先性,音频播放测试程序使用SCHED_FIFO调度策略(通过sched_setscheduler())。
  
  while(1)
  {
   time1=my_gettime();
   通过空循环消耗一定的CPU时间
   time2=my_gettime();
   write(audio_fd,playbuffer,fragmentsize);
   time3=my_gettime();
  }
  
  my_gettime返回当前的时刻,在每个操作的开始和结束分别记录下时间,就可以得到操作所花费的时间。audio_fd为打开音频设备的文件描述符,playbuffer是应用程序中存放音频数据的缓冲区,也就是APP buffer,fragmentsize为一个fragment的大小,write操作控制向驱动写入一个fragment。空循环用来模拟在播放音频时的CPU运算负载,典型的例子是合成器(synthesizer)实时产生波形后,再进行播放(write)。空循环消耗的时间长度设置为一个fragment播放时延的80%。
  
  相关指标的计算方法如下:
  1) 一个fragment的播放时延(fragm.latency) = fragment大小/(频率*2*2)。以fragment大小为512字节和以上的测试环境为例,一个fragment时延 = 512/(44100*2*2) = 2.90ms[44100表示44.1KHz的采样频率,第一个2表示立体声的两个声道,第二个2表示16bit为2个字节]。
  2) 一个fragment的传输时延 = 将一个fragment从APP buffer拷贝到DMA buffer的时延。
  3) time3-time1 = 一次循环持续的时间 = 空循环消耗的CPU时间 + 一个fragment的传输时延。
  4) time2-time1 = 空循环消耗的实际CPU时间(cpu latency)。
  
  为了模拟真实的系统运行情况,在测试程序播放音频数据的同时,还运行了一个系统负载。一共设置5种负载场景,按顺序分别是:
  1) 高强度的图形输出(使用x11perf来模拟大量的BitBlt操作)
  2) 高强度对/proc文件系统的访问(使用top,更新频率为0.01秒)
  3) 高强度的磁盘写(向硬盘写一个大文件)
  4) 高强度的磁盘拷贝(将一个文件拷贝到另一个地方)
  5) 高强度的磁盘读(从硬盘读一个大文件)
  
  针对不同的系统负载场景,测试分别给出了各自的结果。测试结果以图形的形式表示,测试结果中图形的含义留待性能分析时再行解释。
  
  性能分析
  下面,我们分别对两种缓冲区的配置进行性能比较,
  1) 情况1:fragment大小为512字节,fragment个数为2。测试结果1(2x512.html)
  2) 情况2:fragment大小为2048字节,fragment个数为4。测试结果2(4x2048.html)
  
  为了看懂测试结果,需要了解测试结果图形中各种标识的含义:
  1) 红线:全部缓冲区的播放时延。全部缓冲区播放时延 = 一个fragment时延 x fragment的个数。对于测试的第一种情况,全部缓冲区时延 = 2.90ms x 2 = 5.8ms。
  2) 白线:实际的调度时延,即一次循环的时间(time3-time1)。如果白线越过了红线,则说明所有的缓冲区中音频数据播放结束后,应用程序仍然没有来得及将新的数据放入到缓冲区中,此时会出现声音的丢失,同时overruns相应的增加1。
  3) 绿线:CPU执行空循环的时间(即前面的time2-time1)。绿线的标称值为fragm.latency x 80%。由于播放进程使用SCHED_FIFO调度策略,所以如果绿线所代表的时间变大,则说明出现了总线竞争,或者是系统长时间的处于内核中。
  4) 黄线:一个fragment播放时延。白线应该接近于黄线。
  5) 白色的between +/-1ms:实际的调度时延落入到fragm.latency +/-1ms范围的比例。
  6) 白色的between +/-2ms:实际的调度时延落入到fragm.latency +/-2ms范围的比例。
  7) 绿色的between +/-0.2ms:CPU的空循环时延波动+/-0.2ms范围的比例(即落入到标称值+/-0.2ms范围的比例)。
  8) 绿色的between +/-0.1ms:CPU的空循环时延波动+/-0.1ms范围的比例(即落入到标称值+/-0.1ms范围的比例)。
  
  第一种情况的缓冲区很小,每个fragment只有512字节,总共的缓冲区大小为2 x 512 = 1024字节。1024字节只能播放5.8ms。根据OSS的说明,由于Unix是一个多任务的操作系统,有多个进程共享CPU,播放程序必须要保证选择的缓冲区配置要提供足够的大小,使得当CPU被其它进程使用时(此时不能继续向声卡传送新的音频数据),不至于出现欠载的现象。欠载是指应用程序提供音频数据的速度跟不上声卡播放的速度,这时播放就会出现暂停或滴答声。因此,不推荐使用fragment大小小于256字节的设置。从测试结果中看到,不管使用那种系统负载,都会出现欠载的现象,特别是在写硬盘的情况下,一共发生了14次欠载(overruns = 14)。
  
  当然,对于那些实时性要求高的音频播放程序,希望使用较小的缓冲区,因为只有这样才能保证较小的时延。在上面的测试结果我们看到了欠载的现象,但是,这并不完全是缓冲区过小所导致的。实际上,由于Linux内核是不可抢占的,所以无法确知Lin
推荐阅读
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文介绍了使用CentOS7.0 U盘刻录工具进行安装的详细步骤,包括使用USBWriter工具刻录ISO文件到USB驱动器、格式化USB磁盘、设置启动顺序等。通过本文的指导,用户可以轻松地使用U盘安装CentOS7.0操作系统。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 推荐系统遇上深度学习(十七)详解推荐系统中的常用评测指标
    原创:石晓文小小挖掘机2018-06-18笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值, ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
author-avatar
wszr12345597
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有