5、低延时AudioTrack:MODE_STREAM vs MODE_STATIC、低延时Buffer配置(minBufferSize)、使用AAudio替换AudioTrack

好,咱们今天聊点硬核的。低延时音频,说白了就是让声音从产生到进入耳朵,中间走的路径最短、堵车最少。Android 上做音频开发,绕不开 AudioTrack。但很多人用了一辈子 AudioTrack,延时却一直降不下来。问题出在哪?

我个人习惯,遇到延时问题,先看两件事:模式选对了没Buffer 配小了没。这两步走对了,延时至少砍掉一半。如果还嫌不够,那就上 AAudio——Google 专门为低延时设计的亲儿子。

5.1 MODE_STREAM vs MODE_STATIC:你选对了吗?

AudioTrack 有两种工作模式:MODE_STREAMMODE_STATIC。很多人觉得这俩差不多,随便选一个就行。嗯,这里要注意,选错了,延时直接翻倍。

MODE_STREAM:流式传输

这种模式下,数据是一块一块往 AudioTrack 里写的。你写一块,底层消费一块。适合播放长音频、实时音频流(比如网络电话、在线音乐)。

但问题来了——每次 write() 调用,数据都要从 App 进程拷贝到 AudioFlinger 服务进程。这个拷贝过程,就是延时的主要来源。我在项目中遇到过,用 MODE_STREAM 播放一个 10ms 的短音效,结果实际听到的延时接近 50ms。为什么?因为底层 Buffer 没填满之前,它不会开始播放。

核心结论:MODE_STREAM 适合长音频,不适合短促音效。

MODE_STATIC:静态传输

这种模式下,你把所有数据一次性通过 write() 写进去,然后调用 play()。数据只拷贝一次,之后就在 AudioTrack 内部循环播放。适合短音效、按键音、游戏中的射击声等。

延时优势很明显:数据到位后,底层可以立即开始播放,不需要等 Buffer 填满。我曾经优化过一个游戏音效模块,把 MODE_STREAM 改成 MODE_STATIC,延时从 40ms 降到了 10ms 以内。效果立竿见影。

我的建议:如果你的音频数据长度固定且较短(比如 < 1秒),优先用 MODE_STATIC。如果是实时流或长音频,才考虑 MODE_STREAM。

对比总结

特性 MODE_STREAM MODE_STATIC
数据写入方式 分块多次写入 一次性写入
适用场景 长音频、实时流 短音效、固定长度音频
延时表现 较高(受 Buffer 大小影响) 较低(数据到位即播)
内存占用 较低(按需分配) 较高(一次性分配全部)

5.2 低延时 Buffer 配置:minBufferSize 的秘密

模式选对了,Buffer 大小没配好,延时照样下不来。AudioTrack 创建时,你需要指定一个 Buffer 大小。这个值怎么定?很多人直接写个 4096 或者 8192,觉得越大越稳。其实恰恰相反——Buffer 越大,延时越高

为什么?因为 AudioTrack 内部是一个环形 Buffer。你写数据进去,底层从另一端读出来播放。Buffer 越大,数据在里面排队的时间就越长。你想想看,如果 Buffer 能装 100ms 的数据,那至少得等 100ms 才能听到声音。

那 Buffer 设多小合适?答案是:getMinBufferSize() 返回的值。这个值是系统根据采样率、声道数、编码格式算出来的最小安全值。低于这个值,播放可能会卡顿、丢音。

// 获取最小 Buffer 大小
int sampleRate = 44100;
int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

int minBufferSize = AudioTrack.getMinBufferSize(
    sampleRate, channelConfig, audioFormat);

// 创建低延时 AudioTrack
AudioTrack track = new AudioTrack(
    AudioManager.STREAM_MUSIC,
    sampleRate,
    channelConfig,
    audioFormat,
    minBufferSize,  // 直接用最小值
    AudioTrack.MODE_STATIC);
注意:minBufferSize 是下限,不是推荐值。如果你发现播放有杂音或卡顿,可以适当增大 Buffer,比如 minBufferSize * 2。但不要超过 minBufferSize * 4,否则延时优势就没了。

我曾经在一个项目中,为了追求极致低延时,把 Buffer 设成了 minBufferSize / 2。结果播放时噼里啪啦全是爆音。嗯,后来老老实实改回 minBufferSize,问题解决。所以,minBufferSize 是底线,不要试图突破它

5.3 使用 AAudio 替换 AudioTrack

如果你觉得 AudioTrack 的延时还是不够低,或者你想在 Android O 及以上设备上获得更好的性能,那就该上 AAudio 了。

AAudio 是 Google 在 Android O 引入的 C 语言音频 API。它绕过了 AudioTrack 的一些中间层,直接跟底层音频驱动通信。说白了,就是走了一条更近的路。

AAudio 的优势

  • 更低的延时:AAudio 的架构更扁平,数据路径更短。实测下来,比 AudioTrack 能再降低 10-20ms。
  • 更好的性能模式:AAudio 支持 AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,系统会优先保证低延时。
  • 共享内存模式:AAudio 支持 AAUDIO_SHARING_MODE_SHAREDAAUDIO_SHARING_MODE_EXCLUSIVE。独占模式下,延时更低。

AAudio 的基本用法

// 创建 AAudio 流
AAudioStreamBuilder *builder;
AAudioStream *stream;

AAudio_createStreamBuilder(&builder);
AAudioStreamBuilder_setSampleRate(builder, 44100);
AAudioStreamBuilder_setChannelCount(builder, 1);
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
AAudioStreamBuilder_setSharingMode(builder, AAUDIO_SHARING_MODE_EXCLUSIVE);

AAudioStreamBuilder_openStream(builder, &stream);

// 写入数据
AAudioStream_write(stream, audioData, numFrames, 0);

// 播放
AAudioStream_requestStart(stream);

// 清理
AAudioStream_close(stream);
AAudioStreamBuilder_delete(builder);
避坑指南:我曾经在某个项目里,把 AAUDIO_SHARING_MODE_EXCLUSIVE 设成了默认值,结果发现延时并没有降下来。后来查文档才知道,独占模式需要硬件支持,不是所有设备都能用。建议先用 AAUDIO_SHARING_MODE_SHARED,然后通过 AAudioStream_getSharingMode() 检查实际分配的模式。

什么时候用 AAudio?

我个人建议:如果你的 App 最低支持 Android O(API 26),并且对延时要求极高(比如实时音视频、专业音频处理),那就直接用 AAudio。如果还要兼容老版本,那就用 AudioTrack 做降级方案。

5.4 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以看到,从模式选择到 Buffer 配置,再到 AAudio 替换,每一步都在往低延时的方向走。

低延时音频实现路径 第一步:选择模式 MODE_STREAM vs MODE_STATIC 判断依据 短音效 → MODE_STATIC | 长音频 → MODE_STREAM 第二步:配置低延时 Buffer 使用 getMinBufferSize() 获取最小值,不要低于此值 第三步(可选):使用 AAudio 替换 AudioTrack 性能模式:LOW_LATENCY | 共享模式:EXCLUSIVE(硬件支持时) 每一步都在降低延时,但也要考虑兼容性和稳定性

好了,这一章的内容就这些。模式选 MODE_STATIC,Buffer 用 minBufferSize,再狠一点上 AAudio——这三板斧砍下去,延时想不低都难。下一章咱们聊聊更进阶的话题,敬请期待。


公众号:蓝海资料掘金营,微信deep3321