前言
做直播、K 歌、乐器类 App 的工程师都知道一个词:延迟。从用户按一个键到听到声音,中间超过 50ms 就能感知到"不跟手"。
Android 的音频延迟问题根源不在你的代码,在系统音频链路。这一篇从 AudioTrack 讲到 Oboe,说清楚低延迟音频怎么做。
一、Android 音频链路
你的 App 系统服务 硬件
+--------+ +-----------+ +--------+
| 写 PCM | --> | AudioFlinger| -> | DSP |
| (Audio | | Mixer Thread| | I2S |
| Track) | | (多路混音) | | Speaker|
+--------+ +-----------+ +--------+
延迟来源:
50ms 以下的往返延迟,人耳基本无感。超过 100ms 就明显感知到延迟。
二、AudioTrack:基础方案
AudioTrack 是 Android 最底层的音频播放 API,直接写 PCM 数据。
class AudioPlayer(
private val sampleRate: Int = 44100,
private val channelConfig: Int = AudioFormat.CHANNEL_OUT_MONO,
private val audioFormat: Int = AudioFormat.ENCODING_PCM_16BIT
) {
private var audioTrack: AudioTrack? = null
fun start() {
val minBufSize = AudioTrack.getMinBufferSize(
sampleRate, channelConfig, audioFormat
)
// 模式选择:
// MODE_STATIC: 一次性加载全部 PCM,循环播放(适合短音效)
// MODE_STREAM: 持续写 PCM 数据(适合流式播放)
audioTrack = AudioTrack.Builder()
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)
.setAudioFormat(
AudioFormat.Builder()
.setSampleRate(sampleRate)
.setChannelMask(channelConfig)
.setEncoding(audioFormat)
.build()
)
.setBufferSizeInBytes(minBufSize * 2)
.setTransferMode(AudioTrack.MODE_STREAM)
// 关键:设 PERFORMANCE_MODE_LOW_LATENCY
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
.build()
audioTrack?.play()
}
fun writePcm(pcmData: ByteArray) {
audioTrack?.write(pcmData, 0, pcmData.size)
}
fun stop() {
audioTrack?.stop()
audioTrack?.release()
audioTrack = null
}
}
PERFORMANCE_MODE_LOW_LATENCY 做的事:
用更小的 buffer(降低到 2-5ms 级别)
跳过 AudioFlinger 的重采样(要求你的采样率跟设备一致)
优先用 fast mixer thread(优先级高于普通 mixer)
但 AudioTrack 的延迟下限受限于 AudioFlinger。如果你的设备 AudioFlinger 的 fast mixer frame size 是 192 samples(44100Hz 下约 4.3ms),AudioTrack 的往返延迟不可能低于 ~40ms。
三、Oboe:更低延迟
Oboe 是 Google 的 C++ 音频库,封装了 AAudio(Android 8.0+)和 OpenSL ES(兼容旧版)。它的优势是能走"Fast Path",跳过 AudioFlinger 的普通混音线程。
添加依赖
# CMakeLists.txt
find_package(obaa REQUIRED)
target_link_libraries(app oboe)
// build.gradle (app 模块)
android {
defaultConfig {
externalNativeBuild {
cmake { cppFlags "-std=c++17" }
}
}
}
dependencies {
implementation "com.google.oboe:oboe:1.9.0"
}
Oboe 播放器
// audio_engine.cpp
#include <oboe/Oboe.h>
class AudioEngine : public oboe::AudioStreamCallback {
private:
std::shared_ptr<oboe::AudioStream> stream;
public:
void start() {
oboe::AudioStreamBuilder builder;
builder.setFormat(oboe::AudioFormat::Float)
->setSharingMode(oboe::SharingMode::Exclusive)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setSampleRate(48000)
->setChannelCount(1)
->setDataCallback(this)
->openStream(stream);
stream->requestStart();
}
// Oboe 回调:在这里填 PCM 数据
oboe::DataCallbackResult onAudioReady(
oboe::AudioStream* s,
void* audioData,
int32_t numFrames
) override {
auto* output = static_cast<float*>(audioData);
// 生成正弦波 440Hz
for (int i = 0; i < numFrames; i++) {
output[i] = generateSineWave();
}
return oboe::DataCallbackResult::Continue;
}
private:
float phase = 0.0f;
float phaseIncrement = 2.0f * M_PI * 440.0f / 48000.0f;
float generateSineWave() {
float value = sinf(phase);
phase += phaseIncrement;
if (phase >= 2.0f * M_PI) phase -= 2.0f * M_PI;
return value * 0.3f; // 音量
}
};
关键参数解释
->setSharingMode(oboe::SharingMode::Exclusive)
Exclusive 模式:你的 App 独占音频设备,不走 AudioFlinger 混音。延迟最低(可到 10-20ms 往返),但如果其他 App 正在播放音频,打开会失败。
->setPerformanceMode(obee::PerformanceMode::LowLatency)
LowLatency 模式:用 fast path,buffer 尽量小。
延迟对比
四、录音低延迟
class AudioInputEngine {
std::shared_ptr<oboe::AudioStream> inputStream;
void start() {
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Input)
->setFormat(oboe::AudioFormat::Float)
->setSharingMode(oboe::SharingMode::Exclusive)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setSampleRate(48000)
->setChannelCount(1)
->setDataCallback(inputCallback)
->openStream(inputStream);
inputStream->requestStart();
}
std::function<oboe::DataCallbackResult(
oboe::AudioStream*, void*, int32_t)> inputCallback =
[this](oboe::AudioStream* s, void* data, int32_t numFrames) {
auto* input = static_cast<float*>(data);
// 处理麦克风输入数据
processMicData(input, numFrames);
return oboe::DataCallbackResult::Continue;
};
};
实际测试:用 Oboe 做"麦克风录音 -> 处理 -> 立即播放"的往返链路,延迟约 18ms。做耳返(K 歌监听自己声音)足够用。
五、常见问题排查
问题 1:Exclusive 模式打不开
auto result = builder.openStream(stream);
if (result == oboe::Result::ErrorUnavailable) {
// 设备被其他 App 独占
// fallback 到 Shared 模式
builder.setSharingMode(oboe::SharingMode::Shared);
builder.openStream(stream);
}
问题 2:偶发卡顿(buffer underrun)
// 监听 underrun
oboe::DataCallbackResult onAudioReady(...) {
// 如果填充太慢,Oboe 会回调这个
// 检查是否在回调里做了重操作
// onAudioReady 跑在音频线程,不能做 I/O、不能锁、不能分配内存
}
规则:AudioCallback 里不能做任何阻塞操作。数据处理提前在另一个线程做好,callback 只做"读 buffer -> 填 audioData"。
问题 3:采样率不匹配
// 不要假设设备支持 44100
// 让 Oboe 选最优采样率
builder.setSampleRate(oboe::kAutomaticSampleRate);
// Oboe 会用设备原生采样率,跳过重采样
六、Full-Duplex(同时录放)
// 录音 stream 和播放 stream 用同一个 callback
class FullDuplexEngine : public oboe::AudioStreamCallback {
std::shared_ptr<oboe::AudioStream> input;
std::shared_ptr<oboe::AudioStream> output;
void start() {
// 先开 input,再开 output,共用 callback
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Input)
->setFormat(oboe::AudioFormat::Float)
->setSharingMode(oboe::SharingMode::Exclusive)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setSampleRate(48000)
->setChannelCount(1)
->setDataCallback(this)
->openStream(input);
builder.setDirection(oboe::Direction::Output);
builder.openStream(output);
// 先启动 input 再启动 output
input->requestStart();
output->requestStart();
}
oboe::DataCallbackResult onAudioReady(
oboe::AudioStream* s, void* data, int32_t numFrames
) override {
if (s == input.get()) {
// 录音回调:存数据
ringBuffer.write(static_cast<float*>(data), numFrames);
} else {
// 播放回调:读数据
ringBuffer.read(static_cast<float*>(data), numFrames);
}
return oboe::DataCallbackResult::Continue;
}
};
小结
Android 低延迟音频的核心选择:能用 Oboe 就用 Oboe(AAudio backend),AudioTrack 是 fallback 方案。
做实时音频的三个铁律:回调线程不做 I/O、不做锁、不分配内存。
下篇讲传感器框架,高频加速度计数据怎么处理才不丢帧。