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_STREAM 和 MODE_STATIC。很多人觉得这俩差不多,随便选一个就行。嗯,这里要注意,选错了,延时直接翻倍。
MODE_STREAM:流式传输
这种模式下,数据是一块一块往 AudioTrack 里写的。你写一块,底层消费一块。适合播放长音频、实时音频流(比如网络电话、在线音乐)。
但问题来了——每次 write() 调用,数据都要从 App 进程拷贝到 AudioFlinger 服务进程。这个拷贝过程,就是延时的主要来源。我在项目中遇到过,用 MODE_STREAM 播放一个 10ms 的短音效,结果实际听到的延时接近 50ms。为什么?因为底层 Buffer 没填满之前,它不会开始播放。
MODE_STATIC:静态传输
这种模式下,你把所有数据一次性通过 write() 写进去,然后调用 play()。数据只拷贝一次,之后就在 AudioTrack 内部循环播放。适合短音效、按键音、游戏中的射击声等。
延时优势很明显:数据到位后,底层可以立即开始播放,不需要等 Buffer 填满。我曾经优化过一个游戏音效模块,把 MODE_STREAM 改成 MODE_STATIC,延时从 40ms 降到了 10ms 以内。效果立竿见影。
对比总结
| 特性 | 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);
我曾经在一个项目中,为了追求极致低延时,把 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_SHARED和AAUDIO_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_STATIC,Buffer 用 minBufferSize,再狠一点上 AAudio——这三板斧砍下去,延时想不低都难。下一章咱们聊聊更进阶的话题,敬请期待。
公众号:蓝海资料掘金营,微信deep3321