Linux下实现低延迟音频

除去音频处理耗时外,音频延迟主要由缓存引起。本文描述了Linux下如何减小音频缓存,并介绍该如何避免音频缓存欠载和过载。

一、如何减小音频缓存

图1 frame、period和buffer的关系
  • buffer是环形缓冲区,大小为period_size * period_count 。由于buffer可以设置得很大,如果要在一次中断中传输完整个buffer会引入过大的延迟,因此buffer被划分成多个period
  • period是每个硬件中断之间传输的frame的个数,大小由period_size指定
  • frame为同一时刻采集或播放的样本,如果是双声道,那么一个frame包含左右声道的sample

使用更小的buffer(调小period_size和period_count)可以缩短延迟,也更容易触发欠载和过载(在ALSA中统称为XRUN):

  • 欠载指播放音频过程中应用送数据到buffer不及时,导致buffer中无数据可播放
  • 过载指采集音频过程中应用从buffer取数据不及时,导致buffer循环缓冲区被覆盖

渲染音频对实时性要求非常高,需要在固定时间内将固定数量的音频数据传输到音频硬件。音频进程(本文并不区分进程与线程,对Linux调度器而言它们是等价的)要和系统中的其他进程竞争CPU资源,一旦音频进程没有在给定的时间内获得CPU时间片,音频缓冲区耗尽,用户会听到刺耳的爆破音,缓冲区越小越容易出现该问题。

图2 音频进程(红色)与其他进程竞争CPU资源

二、如何避免欠载和过载

常见的欠载和过载的原因如下:

  • 优先级过低
  • 优先级反转
  • 调度延迟
  • 长时间中断禁用
  • 内存管理

优先级过低

图3 内核优先级标度

Linux进程按调度策略可分为普通进程和实时进程:

  • 普通进程的调度策略包括:SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE
  • 实时进程的调度策略包括:SCHED_DEADLINE、SCHED_FIFO、SCHED_RR

如图3所示,内核使用0到139表示内部优先级,值越低,优先级越高。0到99供实时进程使用,普通进程的nice值[-20,+19]映射到范围100到139,可见实时进程的优先级总是比普通进程高。

普通进程使用CFS(完全公平调度类)实现调度。CFS旨在公平地对待共享通用CPU资源的竞争性工作负载,进程nice值越小,优先级越高,分配的CPU时间越多,同时CFS要避免低优先级的进程饥饿,因此会出现较低nice值的进程的CPU被转移到较高nice值的进程的情况,对音频而言,这可能会导致欠载或过载。
与普通进程不同,调度程序总是让优先级高的实时进程运行,换句话说,实时进程运行的过程中禁止低优先级进程的执行。只有在下述事件之一发生时,实时进程才会被另一个进程取代:

  • 进程被另外一个更高优先级的实时进程抢占
  • 进程执行了阻塞操作并进入睡眠
  • 进程停止或被杀死
  • 进程调用sched_yield()自愿放弃CPU
  • 进程事基于SCHED_RR(时间片轮转)的实时进程,而且用完了自己的时间片

为避免音频进程被抢占,可以使用SCHED_FIFO调度策略。当然使用SCHED_FIFO调度策略后,音频进程仍然会被其他优先级更高的SCHED_FIFO进程抢占,需要检查更高优先级的进程保证它们在有限时间内完成。

优先级反转

图4 优先级反转,绿色为低优先级,黄色为中优先级,红色为高优先级

优先级反转出现过程如图4所示:

  1. 绿色进程(低优先级)先执行,获取信号量并执行临界区代码
  2. 红色进程(高优先级)进入就绪状态(ready state),调度器抢占绿色进程让红色进程执行
  3. 红色进程获取信号量阻塞
  4. 黄色进程(中优先级)进入就绪状态,由于黄色进程优先级比绿色进程高,且不需要获取信号量,因此它会先执行完
  5. 绿色进程执行完临界区代码后释放信号量
  6. 红色进程继续执行

在该例子中,高优先级进程要等待中优先级进程执行完后才能执行,因此被称作优先级反转。

避免优先级反转的一种方法是使用有优先级继承(priority inheritance)功能的互斥锁,假如图4的例子将信号量换为这种互斥锁,当红色进程获取互斥锁阻塞时,绿色进程的优先级会临时提高到和红色进程一样,这样黄色进程就无法抢占绿色进程,也就不会发生优先级反转。
Linux中的rt_mutex实现了优先级继承,可以在编译内核时通过配置宏CONFIG_RT_MUTEXES开启。

避免优先级反转的另一种思路是设计之初就避免访问临界区资源。譬如用无锁队列,如果有多个地方需要访问共享资源,可以考虑创建一个“server”负责直接操作共享资源并通过非阻塞的消息队列获取“client”操作共享资源的请求,而不是在每个访问的地方加锁。

调度延迟

调度延迟是指从线程已准备好运行到环境切换完成可以在 CPU 上实际运行之间的间隔时间。延迟越短越好,一旦超过 2 毫秒,就会造成音频问题。

非抢占内核中,如果系统处于核心态并正在处理系统调用,那么系统中的其他进程是无法夺取其CPU时间的。调度器必须等到系统调用执行结束,才能选择另一个进程执行。如果内核处于相对耗时较长的操作中,比如文件系统或内存管理相关的任务,这可能导致调度延迟增加。编译内核时打开CONFIG_PREEMPT选项可以启用对内核抢占的支持。开启内核抢占后,如果高优先级进程有事情需要完成,不仅用户空间应用程序可以被中断,内核也可以被中断。

CONFIG_PREEMPT在一些决策上为了兼顾吞吐量仍然会牺牲延迟指标,而 CONFIG_PREEMPT_RT(Linux实时内核补丁)则是为追求低延迟设计,它实现了完全可抢占特性,譬如CONFIG_PREEMPT只能抢占系统调用,无法抢占中断,而CONFIG_PREEMPT_RT将中断处理程序转换为可抢占的内核线程。在ARM处理器上的测试显示CONFIG_PREEMPT_RT的最大中断响应时间比CONFIG_PREEMPT少了37%~72%。

降低调度延迟的另一种方法是通过isolcpus或cpuset让音频进程在被隔离的CPU上运行,这样能保证该CPU上不会有其他的用户进程执行。

长时间中断禁用

如果某个中断处理程序运行时间较长,或者中断被长时间禁用,那么音频直接内存访问(DMA)的中断会被延迟,有可能导致XRUN。为避免中断处理程序运行时间过长,大部分设备驱动程序分为两部分,一部分是中断处理程序,它被设计为快速完成,一般只是拷贝数据到设备指定位置,另一部分在内核线程中完成剩余的工作。

内存管理

Malloc申请内存的耗时是不受限制的,因此音频进程应该预先申请内存而不是在处理过程中申请。由于请求分页机制的存在,申请的虚拟内存并不会马上被映射到物理内存,而是要延迟到程序第一次访问这些虚拟内存的时候,类似地,程序也只是被访问到的部分被加载到了物理内存。调用mlockall()接口可以确保程序被加载到了物理内存,并且避免虚拟内存被换出到交换文件,但是malloc申请的内存仍然不会被马上映射到物理内存,为了确保malloc申请的内存映射到物理内存,进程应该在申请完内存后马上写一遍内存。

最后总结下避免欠载和过载的方法:

  • 音频进程使用SCHED_FIFO调度策略,并设置合理的优先级
  • 使用无锁队列等方法避免访问临界区资源
  • 打开CONFIG_PREEMPT选项启用内核抢占
  • 使用CONFIG_PREEMPT_RT Linux实时内核补丁
  • 通过isolcpus或cpuset让音频进程在被隔离的CPU上运行
  • 调用mlockall()接口确保程序被加载到了物理内存,并且避免虚拟内存被换出到交换文件
  • 预先申请内存,并申请完内存后马上写一遍内存

参考文章
音频延迟的促成因素
音频开发中常见的四个错误
Introduction to Sound Programming with ALSA
RTOS debugging, part 4: Priority inversion ? when the important stuff has to wait

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容