安卓音频低延时与AAudio

音频延迟是指"音频数据产生→硬件发声"或"硬件拾音→数据上抛"的总耗时,传统AudioTrack/AudioRecord链路延迟普遍在100ms 以上,远超实时场景≤45ms的要求。结合前序全链路,延迟主要来源于这几处:

  • 多层数据拷贝:App用户态→JNI→AudioFlinger内核态→HAL,跨进程+跨空间多次拷贝,CPU耗时高。
  • 线程调度低效:AudioFlinger MixerThread采用普通优先级,系统高负载时易被抢占,导致缓冲区空转。
  • 混音链路冗余:所有音频流必经系统混音,即使单一流播放也无法跳过,增加处理耗时。
  • 缓冲区过大:传统API默认缓冲区偏大,保证流畅性的同时牺牲了延迟。
  • Binder IPC开销:Framework与HAL、App与audioserver的跨进程通信,带来额外耗时。

要实现低延迟,必须解决减少拷贝、提升线程优先级、缩短链路、精简缓冲区 四大问题,AAudio正是围绕这几点设计的原生解决方案。AAudio 是在 Android O 版本中引入的全新 Android C API。此 API 专为需要低延迟的高性能音频应用而设计。‌AAudio低延迟关键机制‌:

  • MMAP 模式‌:允许应用直接与音频硬件共享内存,绕过 AudioFlinger 混音器,显著降低延迟(可低至 10ms 以下)。
  • NOIRQ 模式‌:在独占模式下,不使用中断机制,而是由客户端维护硬件计时模型,预测缓冲区读写时机,进一步减少上下文切换开销。

一、AAudio核心组件

  • AAudioStream:核心数据流对象,分为输出流(播放)和输入流(录制),所有操作围绕Stream展开。
  • AAudioStreamBuilder:Stream构造器,用于配置流参数、模式、回调、缓冲区大小。
  • AAudioService:系统级Native服务,运行在audioserver进程,负责Stream管理、硬件调度、权限校验。
  • Shared RingBuffer (共享环形缓冲区):App与AAudioService共享的内存区域,实现零拷贝数据传输。
  • Hardware HAL 适配层:对接AIDL/HIDL音频HAL,兼容新旧HAL架构,数据直达硬件。

二、AAudio 两种工作模式

AAudio通过共享模式控制硬件占用,直接决定延迟高低,是低延迟的关键:

1、AAUDIO_SHARING_MODE_EXCLUSIVE (独占模式)

  • 直接抢占音频硬件通道,绕过 AudioFlinger 混音,数据直达HAL;
  • 延迟最低,是实时场景首选;
  • 缺点:同一时间仅一个Stream占用,不支持多音频混音。

2、AAUDIO_SHARING_MODE_SHARED (共享模式)

  • 经AudioFlinger混音,与其他音频流共享硬件通道;
  • 延迟略高于独占模式,兼容性更好;
  • 适用于需要多音频并发的低延迟场景。

三、AAudio 低延迟核心原理

**1.**零拷贝:共享内存环形缓冲区

这是AAudio低延迟的核心突破,彻底解决传统链路拷贝问题:

  • AAudioService创建一块匿名共享内存,通过mmap映射到App进程和audioserver进程的虚拟地址空间;
  • 播放场景:App直接将PCM数据写入共享缓冲区,AAudioService从缓冲区读取,无跨进程拷贝
  • 录制场景:HAL采集的数据写入共享缓冲区,App直接读取,全程零拷贝;
  • 采用环形缓冲区设计,实现生产-消费异步解耦,避免数据阻塞。

**2.**实时线程调度

  • AAudio内部线程采用SCHED_FIFO实时调度策略,优先级远高于普通应用线程;
  • 系统CPU调度时,优先执行AAudio音频线程,避免线程切换、抢占导致的延迟;
  • 严格限制线程内耗时操作,仅做数据读写,保证响应速度。

**3.**回调驱动机制

  • 摒弃传统write/read轮询方式,采用硬件触发回调
  • 音频硬件准备好接收/发送数据时,立即触发AAudio的数据回调函数;
  • App在回调中精准填充/读取数据,无轮询等待耗时,数据同步更及时。

**4.**精简链路,砍掉冗余环节

对比传统AudioTrack,AAudio播放链路大幅精简:

传统链路:App→Java层→JNI→Binder→AudioFlinger混音→Binder→HAL→驱动

AAudio 独占链路:App(Native)→共享内存→AAudioService→HAL→驱动

直接砍掉Binder通信、系统混音两大冗余环节,链路耗时骤减。

四、AAudio核心方法

  • AAudio_createStreamBuilder(&builder):初始化构建器对象。
  • AAudioStreamBuilder_setDeviceId(builder, deviceId):指定音频设备 ID。通常可使用默认设备,但如需特定输入/输出设备(如蓝牙耳机),需通过 AudioManager 获取 ID 并设置。
  • AAudioStreamBuilder_setDirection(builder, direction):设置流方向(输入 AAUDIO_DIRECTION_INPUT 或输出 AAUDIO_DIRECTION_OUTPUT)。
  • AAudioStreamBuilder_setSharingMode(builder, mode):设置共享模式。
    • AAUDIO_SHARING_MODE_EXCLUSIVE:独占设备,延迟最低,但可能因设备被占用而创建失败。
    • AAUDIO_SHARING_MODE_SHARED:允许与其他流混合,默认模式,兼容性更好。
  • AAudioStreamBuilder_setPerformanceMode(builder, mode):设置性能模式。
    • AAUDIO_PERFORMANCE_MODE_LOW_LATENCY:‌关键设置‌。只有显式请求此模式,系统才会尝试启用 MMAP 路径。
    • AAUDIO_PERFORMANCE_MODE_POWER_SAVING:以延迟换取节能。
    • AAUDIO_PERFORMANCE_MODE_NONE:默认平衡模式。
  • AAudioStreamBuilder_setFormat(builder, format):设置样本格式(如 AAUDIO_FORMAT_PCM_FLOATAAUDIO_FORMAT_PCM_I16)。若未指定,AAudio 会选择设备最优格式,打开流后需查询实际格式并进行必要转换。
  • AAudioStreamBuilder_setSampleRate(builder, sampleRate)AAudioStreamBuilder_setChannelCount(builder, channelCount):设置采样率和声道数。
  • AAudioStreamBuilder_openStream(builder, &stream):根据配置创建并打开音频流。
    • 注意 ‌:打开流后,应调用 AAudioStream_getDataFormat(stream) 等查询函数确认实际使用的格式、采样率等,因为系统可能会调整请求的参数以匹配硬件能力。
    • 完成后应调用 AAudioStreamBuilder_delete(builder) 释放构建器资源。
  • AAudioStreamBuilder_setDataCallback(builder, callback, userData):注册数据回调函数。系统在需要数据时自动调用此函数,适用于实时性要求高的场景。回调函数中应避免执行耗时操作(如内存分配、锁竞争),以确保低延迟。

    typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
    AAudioStream *stream,
    ///上下文环境
    void userData,
    ///填充音频数据
    void audioData,
    ///需要填充多少帧数据,具体的换算方式:dataSize = numFrames
    channels
    (format == AAUDIO_FORMAT_PCM_I16 ? 2 : 1)
    int32_t numFrames);

  • AAudioStream_write(stream, buffer, numFrames, timeoutNanos):向输出流写入数据。

  • AAudioStream_read(stream, buffer, numFrames, timeoutNanos):从输入流读取数据。

    • timeoutNanos 设为 0 表示非阻塞,立即返回;设为正数表示阻塞等待指定时间。

流状态管理

音频流具有明确的状态机,需正确管理状态转换。

  • 主要状态 ‌:
    • AAUDIO_STREAM_STATE_OPEN:已创建但未启动。
    • AAUDIO_STREAM_STATE_STARTED:正在传输数据。
    • AAUDIO_STREAM_STATE_PAUSED:暂停,缓冲区数据保留。
    • AAUDIO_STREAM_STATE_FLUSHED:刷新,缓冲区数据清空。
    • AAUDIO_STREAM_STATE_STOPPED:停止。
    • AAUDIO_STREAM_STATE_CLOSED:已关闭。
  • 状态控制函数 ‌:
    • AAudioStream_requestStart(stream):启动流,进入 STARTED 状态。
    • AAudioStream_requestStop(stream):停止流。
    • AAudioStream_requestPause(stream):暂停流。
    • AAudioStream_requestFlush(stream):刷新缓冲区,通常在停止前调用以清除残留数据。
    • AAudioStream_close(stream):关闭流,释放资源。‌**务必在不再使用时关闭流,尤其是独占模式流,以便其他应用访问设备。**‌

错误处理与查询

  • AAudioStream_getState(stream):获取当前流状态。
  • AAudioStream_getBufferSizeInFrames(stream):获取当前缓冲区大小。
  • AAudioStream_getXRunCount(stream):获取欠载(Underrun,输出时)或过载(Overrun,输入时)次数,用于监控性能问题。
  • API 调用返回值类型aaudio_result_t,需检查返回值是否为 AAUDIO_OK,否则通过 AAudio_convertResultToText(result) 获取错误描述。

五、AAudio播放&录制实战

1. AAudio****播放(输出流)完整流程

  1. 构建 Stream:通过AAudioStreamBuilder配置参数(方向、采样率、声道、格式、共享模式、回调)。
  2. 打开 Stream:AAudioService校验权限,分配硬件通道,创建共享环形缓冲区。
  3. 启动 Stream:切换流状态为Running,开启实时线程,等待硬件回调。
  4. 数据回调:硬件触发回调,App在回调函数中填充PCM数据到共享缓冲区。
  5. 数据传输:AAudioService从共享内存读取数据,直接下发至HAL层。
  6. 停止/关闭:播放结束,停止Stream,释放硬件通道与共享内存。

代码实现:

复制代码
AAudioStream *playStream;
AAudioStreamBuilder *playBuilder;
//构建stream
aaudio_result_t result = AAudio_createStreamBuilder(&playBuilder);
if (result != AAUDIO_OK) {
    //打印错误类型
    printf("Failed to create stream builder: %s", AAudio_convertResultToText(result));
    return;
}
//设置传输方向
AAudioStreamBuilder_setDirection(playBuilder, AAUDIO_DIRECTION_OUTPUT);
//设置分享模式
AAudioStreamBuilder_setSharingMode(playBuilder, AAUDIO_SHARING_MODE_EXCLUSIVE);
//设置性能模式
AAudioStreamBuilder_setPerformanceMode(playBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
//设置格式
AAudioStreamBuilder_setFormat(playBuilder, AAUDIO_FORMAT_PCM_I16); // 16位PCM
//设置声道
AAudioStreamBuilder_setChannelCount(playBuilder, 2); // 双声道
//设置采样率
AAudioStreamBuilder_setSampleRate(playBuilder, 48000);
//设置数据回调
AAudioStreamBuilder_setDataCallback(playBuilder, dataCallback, nullptr);

result = AAudioStreamBuilder_openStream(playBuilder, &playStream);
if (result != AAUDIO_OK) {
    //打印错误类型
    printf("Failed to openstream: %s", AAudio_convertResultToText(result));
    return;
}
//释放构建器资源
AAudioStreamBuilder_delete(playBuilder);
//启动流
AAudioStream_requestStart(playStream);
/********流程结束,释放资源*****************/
AAudioStream_requestStop(playStream);
AAudioStream_close(playStream);


//数据回调
aaudio_data_callback_result_t dataCallback(AAudioStream *stream, void *userData,
                                          void *audioData, int32_t numFrames) {
    //播放流程中填充audioData数据
    // 立体声 16-bit PCM:每帧 2 个样本,每个样本 2 字节
    int16_t *samples = (int16_t *)audioData;
    int32_t totalSamples = numFrames * 2; // 2 通道
    for (int i = 0; i < totalSamples; ++i) {
        samples[i] = generateNextSample(); // 自定义生成函数
    }

    return AAUDIO_CALLBACK_RESULT_CONTINUE;
    return AAUDIO_CALLBACK_RESULT_CONTINUE;
}

⚠️ ‌注意 ‌:播放流中‌不能 ‌调用 AAudioStream_read()AAudioStream_write(),否则会出错。所有数据必须通过回调写入 audioData

2、AAudio录制(输入流)完整流程

  1. 构建录制Stream,配置输入方向、录音参数、共享模式。
  2. 打开Stream,获取麦克风硬件独占权,创建共享缓冲区。
  3. 启动Stream,麦克风采集数据,经HAL写入共享缓冲区。
  4. 触发数据回调,App从共享内存读取PCM裸数据。
  5. 录制完成,停止Stream,释放麦克风资源。

代码实现:

复制代码
AAudioStream *recordStream;
AAudioStreamBuilder *recordBuilder;
//构建stream
aaudio_result_t result = AAudio_createStreamBuilder(&recordBuilder);
if (result != AAUDIO_OK) {
    //打印错误类型
    printf("Failed to create stream builder: %s", AAudio_convertResultToText(result));
    return;
}
//设置传输方向,录制为AAUDIO_DIRECTION_INPUT
AAudioStreamBuilder_setDirection(recordBuilder, AAUDIO_DIRECTION_INPUT);
//设置共享模式--设置为独占模式
AAudioStreamBuilder_setSharingMode(recordBuilder, AAUDIO_SHARING_MODE_EXCLUSIVE);
//设置性能模式--低延时
AAudioStreamBuilder_setPerformanceMode(recordBuilder,AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
//设置格式
AAudioStreamBuilder_setFormat(recordBuilder, AAUDIO_FORMAT_PCM_I16);
//设置通道
AAudioStreamBuilder_setChannelCount(recordBuilder, 2);
//设置采样率
AAudioStreamBuilder_setSampleRate(recordBuilder, 48000);
//设置数据回调
AAudioStreamBuilder_setDataCallback(recordBuilder, dataCallback, nullptr);

aaudio_result_t result = AAudioStreamBuilder_openStream(recordBuilder, &recordStream);
if (result != AAUDIO_OK) {
    //打印错误类型
    printf("Failed to open stream: %s", AAudio_convertResultToText(result));
    return;
}
//释放构建器资源
AAudioStreamBuilder_delete(recordBuilder);
//启动流
AAudioStream_requestStart(recordStream);

/*******流程结束,释放资源**********/
AAudioStream_requestStop(recordStream);
AAudioStream_close(recordStream);

//数据回调
aaudio_data_callback_result_t dataCallback(AAudioStream *stream, void *userData,
                                          void *audioData, int32_t numFrames) {
    int16_t *recordedSamples = (int16_t *)audioData;
    int32_t totalSamples = numFrames * 2; // 立体声

    // 将录音数据复制到自定义缓冲区(如环形缓冲区)
    memcpy(myBuffer + bufferOffset, recordedSamples, totalSamples * sizeof(int16_t));
    bufferOffset += totalSamples;

    return AAUDIO_CALLBACK_RESULT_CONTINUE;
}

⚠️ ‌注意 ‌:录音流中‌不能 ‌调用 AAudioStream_write()audioData 写入数据,因为该缓冲区由系统填充。同样,‌不能 ‌调用 AAudioStream_read()

相关推荐
Jwest20211 小时前
佳维视工业安卓一体机在医生移动查房车中的应用
android
Lanren的编程日记1 小时前
任务77:Flutter 鸿蒙应用视频录制功能实战:视频录制+录制控制+视频编辑,打造完整视频处理能力
flutter·音视频·harmonyos
大龄程序员狗哥1 小时前
第49篇:TensorFlow Lite实战——将图像分类模型部署到安卓手机(项目实战)
android·分类·tensorflow
BetterNow.2 小时前
安卓内存Previous为什么可以算进freeRam
android·linux·安卓·安卓性能·安卓内存
码云数智-园园2 小时前
PHP 8.x 命名的参数与属性(Attribute):告别注释,构建真正的元数据
android·ide·android studio
0pen12 小时前
ZygiskNext 源码解析(三):zygiskd 的模块管理、memfd 与 companion
android·安全·开源
Android_xiong_st2 小时前
(原创)2026安卓面试复盘
android·面试·职场和发展
_pengliang2 小时前
uni-app 实现sse流式音频技术方案
uni-app·音视频
码点2 小时前
Android 9休眠时任意键唤醒屏幕
android·linux·运维