安卓上低延迟音频播放

42

我正在尝试为一个简单的应用程序最小化音频延迟:

我有一段视频在PC上,我通过RTP将视频的音频传输到移动客户端。使用非常相似的缓冲算法,我可以在iOS上实现90毫秒的延迟,但在Android上却可怕地达到了±180毫秒。

我猜这种差异源于Android上众所周知的延迟问题

然而,在阅读了一些资料后,我找到了这篇文章,其中提到:

  1. 某些设备从Android 4.1/4.2开始支持低延迟音频。

  2. 使用libpd,即Pure Data库 for Android可以实现低延迟音频。

我有两个问题,与这两个声明直接相关:

  1. 在Jellybean中,我在哪里可以找到更多关于新的低延迟音频的信息?这是我能找到的所有信息,但具体信息非常缺乏。这些变化对我来说应该是透明的,还是我需要实现一些新的类/API调用才能注意到应用程序中的任何变化?我正在使用AudioTrack API,我甚至不确定它是否应从此改进中受益,或者我是否应该寻找其他机制来进行音频播放。

  2. 我应该考虑使用libpd吗?在我看来,这似乎是我实现更低延迟的唯一机会,但由于我一直认为PD是一种音频合成工具,所以它真的适用于仅从网络流中抓取帧并播放它们的项目吗?我实际上没有进行任何合成。我是否走错了路?

作为补充说明,在有人提到OpenSL ES之前,这篇文章非常清楚地表明,不应该期望使用它会有延迟方面的改进

"由于OpenSL ES是本机C API,调用OpenSL ES的非Dalvik应用程序线程没有Dalvik相关开销,如垃圾回收暂停。但是,除此之外,使用OpenSL ES没有其他性能优势。特别地,使用OpenSL ES不会比平台通常提供的具有更低的音频延迟、更高的调度优先级等等。"

13
我是Android团队的一名成员,与您引用文章的作者密切合作。您引用的那段话现在已经不完全正确了。当该文章撰写时,OpenSL可用的最小缓冲区仍然相当大。现在,在Jellybean中缓冲区大小已经缩小,延迟已经降低到“Dalvik相关开销,例如垃圾回收暂停”的考虑非常重要的程度。可靠地利用较小的Jellybean缓冲区的唯一方法是使用OpenSL。 - Ian Ni-Lewis
7个回答

69

为了在Android 4.2.2版本中实现最低延迟,您应按以下顺序执行,从最不明显到最明显:

  1. 如果可能,请选择支持FEATURE_AUDIO_PRO的设备,或者支持FEATURE_AUDIO_LOW_LATENCY。 ("Low latency" 是50毫秒单向;pro是小于20毫秒往返。)

  2. 使用OpenSL。Dalvik GC的平均成本较低,但运行时所需时间比低延迟音频线程允许的时间更长。

  3. 在缓冲区队列回调中处理音频。系统在具有比正常用户模式线程更有利的调度的线程中运行缓冲区队列回调。

  4. 将缓冲区大小设置为AudioManager.getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER)的倍数。否则,您的回调将偶尔每个时间片获得两个调用而不是一个。除非您的CPU使用率非常轻,否则这可能会导致故障。(在Android M上,由于缓冲处理代码中的错误,使用系统缓冲区大小非常重要。)

  5. 使用AudioManager.getProperty(PROPERTY_OUTPUT_SAMPLE_RATE)提供的采样率。否则,您的缓冲区将绕过系统重新采样器。

  6. 永远不要在缓冲区回调中进行系统调用或锁定同步对象。如果必须同步,请使用无锁结构。为了获得最佳结果,请使用完全无需等待的结构,例如单个读取器单个写入器环形缓冲区。许多开发人员会犯这种错误,并最终出现难以预测和难以调试的故障。

  7. 使用矢量指令,如NEON、SSE或目标处理器上的等效指令集。

  • 测试并测量您的代码。跟踪其运行所需的时间—请记住需要知道最坏情况的性能,而不是平均性能,因为最坏情况会导致故障。同时请保守处理。您已经知道,如果处理音频所需的时间超过播放它所需的时间,那么就永远无法实现低延迟。但在 Android 上,这更为重要,因为 CPU 频率波动很大。您可以使用大约 60-70% 的 CPU 进行音频处理,但请记住,随着设备变热或变冷,或者 wifi 或 LTE 无线电开启和关闭等原因,这将发生变化。

  • 低延迟音频已经不再是 Android 的新特性了,但仍然需要对硬件、驱动程序、内核和框架进行设备特定更改才能实现。这意味着您可以期望从不同设备中获得不同的延迟值,并且考虑到 Android 手机销售的许多不同价格区间,这些差异可能会一直存在。寻找 FEATURE_AUDIO_PRO 或 FEATURE_AUDIO_LOW_LATENCY,以确定满足您应用程序所需延迟标准的设备。


    关于第5点,如果源音频的录制速率为44100,但PROPERTY_OUTPUT_SAMPLE_RATE为48000,则音频将播放得太快。有什么最好的解决方法? - Ian1971
    Ian,我相信PROPERTY_OUTPUT_SAMPLE_RATE(POSR)是指您设备的声音处理器的本机音频采样率。这意味着,如果您提供的音频采样率为48kHz,并且您设备的POSR也是48kHz,则系统将不必进行任何“额外工作”,因此可以在没有额外延迟的情况下播放。 - rmigneco
    将音频采样率设置为44.1kHz并不意味着它会以不同的速度播放,但这要求操作系统/设备进行额外的重新采样步骤以在48kHz时处理音频,从而增加了延迟。 - rmigneco
    3
    在 Android M 上,由于缓冲区处理代码中的一个错误,在使用系统缓冲区大小时非常重要确保准确无误。关于这个问题还有其他信息吗?我好像也遇到了这个问题。我猜在这种情况下人们被迫使用低延迟音频。 - Matthew Mitchell
    1
    是的,@Jenix,你说得对。但是也许如果你尝试实现低延迟重采样,源的重采样应该事先完成,而不是在运行时进行。 - Stéphane
    显示剩余6条评论

    6
    从您提供的第一点链接中得知:
    “低延迟音频 Android 4.2 改进了对低延迟音频播放的支持,从 Android 4.1 中使用 OpenSL ES、Soundpool 和 tone generator API 减少音频输出延迟所做的改进开始。这些改进取决于硬件支持 - 提供这些低延迟音频功能的设备可以通过硬件特性常量向应用程序广告其支持。”
    完整引用如下:
    “性能 由于 OpenSL ES 是本地 C API,因此调用 OpenSL ES 的非 Dalvik 应用程序线程没有 Dalvik 相关的开销,例如垃圾收集暂停。但是,OpenSL ES 的使用除此之外没有其他额外的性能优势。特别是,OpenSL ES 的使用不会导致比平台通常提供的更低的音频延迟、更高的调度优先级等。另一方面,随着 Android 平台和特定设备实现的不断发展,OpenSL ES 应用程序可以期望从任何未来的系统性能改进中获益。”
    因此,与驱动程序和硬件通信的 API 是 OpenSL(与 Opengl 处理图形的方式相同)。然而早期版本的 Android 设计在驱动程序和/或硬件方面存在问题。这些问题已在 4.1 和 4.2 版本中得到解决和纠正,因此如果硬件有足够的能力,则可以使用 OpenSL 实现低延迟。
    再次从 puredata 库网站上的这个注释中可以明显看出,该库本身使用 OpenSL 来实现低延迟:
    “符合设备的低延迟支持 截至 2012 年 12 月 28 日,Android 上的 Pd 的最新版本支持符合 Android 设备的低延迟音频。在更新副本时,请确保从 GitHub 拉取 pd-for-android 和 libpd 子模块的最新版本。
    在撰写本文时,Galaxy Nexus、Nexus 4 和 Nexus 10 为音频输出提供了低延迟轨道。要命中低延迟轨道,应用程序必须使用 OpenSL,并且必须以正确的采样率和缓冲区大小运行。这些参数是设备相关的(Galaxy Nexus 和 Nexus 10 以 44100Hz 运行,而 Nexus 4 则以 48000Hz 运行;每个设备的缓冲区大小都不同)。
    与往常一样,Pd for Android 尽可能地掩盖了所有这些复杂性,在向后兼容早期版本的 Android 的同时提供对新低延迟功能的访问。在幕后,Pd for Android 的音频组件将在 Android 2.3 及更高版本上使用 OpenSL,而在 Android 2.2 及更早版本上则回退到旧的 AudioTrack/AudioRecord API 中的 Java。”

    6
    使用OpenSL ES时,为了在Jellybean及其后续版本的Android上获得低延迟输出,您应满足以下要求:
    • 音频应为单声道或立体声线性PCM。
    • 音频采样率应与输出的本机采样率相同(实际上,在某些设备上可能不需要这样做,因为FastMixer能够进行重采样,如果供应商将其配置为这样做。但在我的测试中,当在FastMixer中从44.1升频到48 kHz时,我得到了非常明显的伪像)。
    • 您的BufferQueue应至少有2个缓冲区。(此要求已经放宽。请参见Glenn Kasten的此提交。我不确定这是在哪个Android版本中首次出现的,但猜测可能是4.4)。
    • 您不能使用某些效果(例如混响、低音增强、均衡、虚拟化等)。
    SoundPool类也会尝试在可能时内部使用快速的AudioTrack(适用于与上述相同的标准,除了BufferQueue部分)。

    3

    2
    这是一个很好的解释,但不是100%更新。在Android L中,AudioFlinger缓冲区已经减少,从通路中删除了两个缓冲区。目前在应用程序和硬件抽象层(HAL)之间存在一个有效的延迟缓冲区(看起来像更多,但一些代码是同步的——更多的memcpys并不总是意味着更多的延迟)。另外,不确定6毫秒的“总线延迟”数字来自哪里。那似乎太高了。 - Ian Ni-Lewis
    嗨,Ian,希望你一切都好。我们这里有一个更新的版本:http://superpowered.com/android-marshmallow-latency - Patrick Vlaskovits
    2
    大致看起来是正确的。唯一缺失的是Nexus 9 DSP,它在Tegra K1数据手册中被称为AHUB。我猜那里的FIFO比总线更多地考虑了后期软件延迟。我认为你可能忽视了一个事实,即HAL写调用是阻塞式的。该调用直到下一个中断触发才返回,因此整个引擎的调度都是基于音频中断而不是基于速率单调调度器。这其中存在一些严重问题,其中很多问题是特定于基于Linux的操作系统,但我不认为“推送”模型是其中之一。 - Ian Ni-Lewis
    嘿,Ian,好的,这个 GIF 图片 http://superpowered.com/images/Android-Marshmallow-Audio-Path-Latency-Superpowered-Audio.gif展示了使用环回连接器的往返延迟,其中没有任何 Tegra DSP 处于活动状态。 HAL 写入调用可能会在某些 HAL 上阻塞,但在其他 HAL 上可能不会阻塞。由于当前“基于睡眠”的 AudioFlinger 架构和其他因素,AudioFlinger 和用户应用程序端(AudioTrack 等)将超过 1 个缓冲区添加到往返延迟中。 - Patrick Vlaskovits
    没有任何DSP处于活动状态--你可能是指OpenSL效果已关闭。这些效果并不是DSP所做的唯一事情--它还处理混音、路由、扬声器保护等等。"基于睡眠" --我可以理解你为什么这么说,因为混音循环中的代码路径之一确实包含了一个睡眠。从代码中不明显的是,该代码被调用的频率非常低,因为几乎所有的HAL都进行阻塞写入。我们不知道有任何不这样做的。话虽如此,我们有一些数据表明,具有异步写入+睡眠的HAL实际上比具有阻塞写入的HAL具有更好的延迟。 - Ian Ni-Lewis
    显示剩余2条评论

    2
    另一个用于音频延迟和缓冲区大小的数据库如下:
    http://superpowered.com/latency/#table 源代码:
    https://github.com/superpoweredSDK/SuperpoweredLatency

    2

    有一个新的C++库Oboe,可以帮助减少音频延迟。我已经在我的项目中使用它,并且它运行良好。 它具有以下功能,可帮助减少音频延迟:

    • 自动延迟调整
    • 选择音频API(在API 16+上使用OpenSL ES或在API 27+上使用AAudio)

    嗯,您如何将任何类型的文件转换为Oboe示例算法正确读取的原始数据? - Ráfagan
    1
    我使用免费的应用程序Audacity将音频转换为Android原始格式。 - Wrobel
    你是否使用基于oboe示例的某种算法?它们以48000赫兹和16位整数读取原始文件。你也使用这种原始格式吗? - Ráfagan
    我正在使用16位44100赫兹格式,但您可以使用48000或其他频率。 - Wrobel
    @Wrobel,你有用蓝牙耳机测试过Oboe吗?使用时是否存在明显的延迟? - Roman Samoilenko

    0

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接