音频延迟时间

音频延迟时间

本页内容

    1. 先决条件
    2. 测量延迟时间
    3. 最大程度减少延迟时间的最佳做法
    4. 最大程度减少输入延迟时间
    5. 最大程度减少输出延迟时间
    6. 最大程度减少预热延迟时间
    7. 更多信息

视频

音频延迟时间:缓冲区大小

视频

在 Android 上打造出色的多媒体体验

延迟时间是指信号在系统中传输所需的时间。下面是与音频应用相关的常见类型的延迟时间:

  • 音频输出时间延迟是指音频样本由应用生成到通过耳机插孔或内置扬声器播放之间的时间。
  • 音频输入延迟时间是指音频信号由设备音频输入(如麦克风)接收到相同音频数据可被应用使用的时间。
  • 往返延迟时间是指输入延迟时间、应用处理时间和输出延迟时间的总和。

  • 触摸延迟时间是指用户触摸屏幕与触摸事件被应用接收之间的时间。
  • 预热延迟时间是指启动音频管道、数据第一次在缓冲区加入队列所需的时间。

本页面将介绍如何在开发您的音频应用时保证低输入和输出延迟时间以及如何避免出现预热延迟时间。

先决条件


仅在使用 OpenSL ES™ API 的 Android 实现和 Android NDK 时,才支持低延迟时间音频。

测量延迟时间


很难单独测量音频输入和输出延迟时间,因为需要准确地了解第一个样本何时传送入音频路径(尽管可以使用光检测电路和示波器完成)。 如果您了解往返音频延迟时间,则可以使用一般经验法则:音频输入(和输出)延迟时间是经过无信号处理路径的往返音频延迟时间的一半

往返音频延迟时间根据设备型号和 Android 版本号的不同而大不相同。 您可以通过阅读已发布的测量值大略了解 Nexus 设备的往返延迟时间。

您可以通过创建应用测量往返音频延迟时间,该应用能够生成音频信号、侦听此音频信号和测量发送与接收信号之间的时间。或者,您也可以安装此延迟时间测试应用。 此应用使用拉森测试执行往返延迟时间测试。 您也可以查看延迟时间测试应用的源代码

由于最低延迟时间是在信号处理最少的音频路径上获得的,您可能还想使用回环音频适配器,它让测试能够通过耳麦连接器运行。

最大程度减少延迟时间的最佳做法


验证音频性能

Android 兼容性定义文档 (CDD) 枚举了兼容 Android 设备的硬件和软件要求。请参阅 Android 兼容性了解与整体兼容性计划有关的详细信息,参阅 CDD 了解实际的 CDD 文档。

在 CDD 中,往返延迟时间被指定为 20 毫秒或更低(而乐师通常需要 10 毫秒)。 这是因为 20 毫秒可以实现一些重要的用例。

当前没有 API 可以在运行时确定 Android 设备上通过任何路径的音频延迟时间。 不过,您可以使用下列硬件功能标记了解设备是否能为延迟时间提供任何保证:

报告这些标记的标准在 CDD 的 5.6 音频延迟时间和 5.10 专业音频部分中定义。

以下是如何在 Java 中检查这些功能:

boolean hasLowLatencyFeature =
    getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);

boolean hasProFeature =
    getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO);

对于各音频功能的关系,android.hardware.audio.low_latency 功能是 android.hardware.audio.pro 的先决条件。 设备可以实现 android.hardware.audio.low_latency 而不能实现 android.hardware.audio.pro,但反之则不然。

不作有关音频性能的假设

请注意有助于避免延迟时间问题的下列假设:

  • 不要假设移动设备中使用的扬声器和麦克风通常拥有良好的音效。 它们的体积较小,通常音效较差,所以增加信号处理来提高音质。 此信号处理会引起延迟。
  • 不要假设您的输入和输出回调是同步的。对于同步输入和输出,将为每一侧使用单独的缓冲区队列完成处理程序。 即使两侧均采用相同的采样率,也无法保证这些回调的相对顺序或音频时钟的同步。 您的应用应当在适当同步缓冲区的情况下缓冲数据。
  • 不要假设实际采样率与名义采样率完全一致。例如,如果名义采样率是 48,000 Hz,则正常情况下,音频时钟会使用与操作系统 CLOCK_MONOTONIC 稍微不同的采样率计时。 这是因为音频和系统时钟由不同的晶体制成。
  • 不要假设实际回放样本率与实际捕获采样率完全一致,特别是端点在单独的路径上时。 例如,如果您正以 48,000 Hz 的名义采样率从设备上的麦克风捕获数据,并以 48,000 Hz 的名义采样率在 USB 上播放音频,实际采样率很可能会彼此稍有不同。

潜在独立的音频时钟的一个结果是需要异步采样率转换。 异步采样率转换的简单(尽管音频质量不理想)方法是根据需要在接近过零点的位置重复或减少样本。 也可以进行更复杂的转换。

最大程度减少输入延迟时间


此部分会提供建议,帮助您在使用内置麦克风或外部耳麦麦克风录音时减少音频输入延迟时间。

  • 如果您的应用要监控输入,建议您的用户使用耳麦(例如,通过在第一次运行时显示最好使用耳机屏幕)。 请注意,仅使用耳麦无法保证尽可能最低的延迟时间。 您可能需要执行其他步骤从音频路径中移除任何不需要的信号处理(例如,在录音时通过使用 VOICE_RECOGNITION 预设值)。
  • 准备好处理由 PROPERTY_OUTPUT_SAMPLE_RATE 的 getProperty(String) 报告的名义采样率 44,100 和 48,000 Hz。 也有其他采样率,但很少见。
  • 准备好处理由 PROPERTY_OUTPUT_FRAMES_PER_BUFFER 的 getProperty(String) 报告的缓冲区大小。 典型的缓冲区大小包括 96、128、160、192、240、256 或 512 帧,但也有其他值。

最大程度减少输出延迟时间


创建您的音频播放器时使用最佳采样率

要获得最低延迟时间,您必须提供与设备的最佳采样率和缓冲区大小匹配的音频数据。 如需了解详细信息,请参阅面向更低的延迟时间设计

如下列代码示例中所示,在 Java 中,您可以从 AudioManager 获得最佳采样率:

AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String sampleRateStr = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
int sampleRate = Integer.parseInt(sampleRateStr);
if (sampleRate == 0) sampleRate = 44100; // Use a default value if property not found

知道最佳采样率后,您可以在使用 OpenSL ES 创建您的播放器时提供具体数值:

// create buffer queue audio player
void Java_com_example_audio_generatetone_MainActivity_createBufferQueueAudioPlayer
        (JNIEnv* env, jclass clazz, jint sampleRate, jint framesPerBuffer)
{
   ...
   // specify the audio source format
   SLDataFormat_PCM format_pcm;
   format_pcm.numChannels = 2;
   format_pcm.samplesPerSec = (SLuint32) sampleRate * 1000;
   ...
}

samplesPerSec 指的是每个通道的采样率,单位为毫赫(1 Hz = 1000 mHz)。

将音频数据加入队列时使用最佳缓冲区大小

您可以通过 AudioManager API 采用与获得最佳采样率相似的方式获得最佳缓冲区大小:

AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
int framesPerBufferInt = Integer.parseInt(framesPerBuffer);
if (framesPerBufferInt == 0) framesPerBufferInt = 256; // Use default

PROPERTY_OUTPUT_FRAMES_PER_BUFFER 属性表示 HAL(硬件抽象层)缓冲区可以容纳的音频帧数量。 您应构建音频缓冲区,使其可以容纳这个数量的确切倍数。 如果使用准确数量的音频帧,会定期出现回调,这将减少抖动。

使用 API 而不是硬编码值来确定缓冲区大小至关重要,因为在不同的设备和 Android 版本号中,HAL 缓冲区大小会有所不同。

避免添加涉及信号处理的输出接口

快速混合器仅支持下列这些接口:

  • SL_IID_ANDROIDSIMPLEBUFFERQUEUE
  • SL_IID_VOLUME
  • SL_IID_MUTESOLO

不支持以下这些接口,因为它们涉及信号处理,且会导致快速音轨的请求被拒绝:

  • SL_IID_BASSBOOST
  • SL_IID_EFFECTSEND
  • SL_IID_ENVIRONMENTALREVERB
  • SL_IID_EQUALIZER
  • SL_IID_PLAYBACKRATE
  • SL_IID_PRESETREVERB
  • SL_IID_VIRTUALIZER
  • SL_IID_ANDROIDEFFECT
  • SL_IID_ANDROIDEFFECTSEND

创建播放器时,请确保仅添加快速接口,如以下示例所示:

const SLInterfaceID interface_ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };

验证您正在使用低延迟时间音轨

完成下列这些步骤以验证您是否已成功获得低延迟时间音轨:

    1. 启动您的应用,然后运行下列命令:
adb shell ps | grep your_app_name
    1. 记下您应用的进程 ID。
    2. 现在,从您的应用播放一些音频。您大约有三秒钟的时间可以从终端运行下列命令:
adb shell dumpsys media.audio_flinger
  1. 扫描您的进程 ID。如果您在 Name 列看到 F,表示它在低延迟时间音轨上(F 代表快速音轨)。

最大程度减少预热延迟时间


第一次将音频数据加入队列时,设备音频电路需要较短、但仍十分重要的一段时间来预热。 要避免这种预热延迟时间,您可以将包含无声的音频数据缓冲区加入队列,如以下代码示例所示:

#define CHANNELS 1
static short* silenceBuffer;
int numSamples = frames * CHANNELS;
silenceBuffer = malloc(sizeof(*silenceBuffer) * numSamples);
    for (i = 0; i < numSamples; i++) {
        silenceBuffer[i] = 0;
    }

需要生成音频时,您可以将包含真实音频数据的缓冲区加入队列。

:持续输出音频将消耗大量的电量。请确保您在 onPause() 方法中停止输出。另外,请考虑在用户无活动的一段时间后暂停无声输出。

更多信息


  1. 适合应用开发者的音频延迟时间
  2. 影响音频延迟时间的因素
  3. 测量音频延迟时间
  4. 音频预热
  5. 延迟时间(音频)
  6. 往返延迟时间
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章