Administrator
发布于 2026-09-22 / 6,676 阅读
0
0

Android 音频系统:AudioTrack、Oboe 与低延迟音频实战

前言

做直播、K 歌、乐器类 App 的工程师都知道一个词:延迟。从用户按一个键到听到声音,中间超过 50ms 就能感知到"不跟手"。

Android 的音频延迟问题根源不在你的代码,在系统音频链路。这一篇从 AudioTrack 讲到 Oboe,说清楚低延迟音频怎么做。

一、Android 音频链路

你的 App          系统服务          硬件
+--------+     +-----------+    +--------+
| 写 PCM | --> | AudioFlinger| -> | DSP    |
| (Audio |     | Mixer Thread|   | I2S    |
| Track) |     | (多路混音) |    | Speaker|
+--------+     +-----------+    +--------+

延迟来源:

环节

典型延迟

说明

App 写 buffer

5-20ms

取决于 buffer size

AudioFlinger 混音

10-40ms

多个 App 混音

HAL 层

5-15ms

硬件抽象层

DSP 处理

2-10ms

芯片音频处理

总往返

40-80ms

从写到播

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 尽量小。

延迟对比

方案

往返延迟

兼容性

适用场景

MediaPlayer

150-300ms

全版本

播放音频文件

AudioTrack (Normal)

40-80ms

API 5+

流式播放

AudioTrack (LowLatency)

30-50ms

API 26+

实时音效

Oboe (AAudio)

10-20ms

API 26+

乐器/K歌/直播

OpenSL ES (旧)

20-40ms

API 9+

已废弃,不推荐

四、录音低延迟

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、不做锁、不分配内存。

下篇讲传感器框架,高频加速度计数据怎么处理才不丢帧。


评论