4. 音频采集与播放:AudioRecord、AudioTrack、OpenSL ES、AAudio的API使用、配置与回调机制
在Android音视频系统架构中,音频采集与播放是数据链路的起点与终点。无论是VoIP通话、直播推流、语音识别,还是音乐播放器、游戏音效,底层都依赖系统提供的音频API。Android平台历经多年演进,目前存在四套并行的音频I/O方案:传统Java层的AudioRecord/AudioTrack、高性能原生层的OpenSL ES、以及新一代的AAudio。理解这四者的设计哲学、配置参数与回调机制,是构建低延迟、高稳定音频系统的核心能力。本章将从API选型、配置流程、回调模型、工程实践四个维度展开深度剖析。
上图展示了四套API在Android音频架构中的位置与核心特征。AudioRecord/AudioTrack位于Java框架层,通过JNI调用Native服务;OpenSL ES与AAudio则直接暴露Native接口,减少跨层开销。从延迟角度看,AudioTrack在旧版本上延迟可达100ms以上,而AAudio在Pixel系列设备上可低至20ms以内。选型时需综合考虑应用场景、最低支持版本、性能要求与维护成本。
4.1 核心原理:四套API的设计哲学与数据通路
理解API的本质差异,需要从数据流的角度切入。音频采集链路为:麦克风→Codec ADC→I2S/TDM总线→DMA Buffer→ALSA驱动→AudioFlinger→应用缓冲区。播放链路则反向。四套API的差异主要体现在缓冲区管理与线程模型上。
- AudioRecord/AudioTrack:基于Java层封装,内部通过JNI调用Native AudioFlinger。采集时使用
read()阻塞式读取,播放时使用write()阻塞式写入。缓冲区由系统分配,应用侧通过setBufferSizeInBytes()指定大小。优点是兼容性极佳,缺点是延迟较高且无法精细控制回调时机。 - OpenSL ES:基于C语言的Native API,采用BufferQueue模型。应用创建
AudioRecorder或AudioPlayer对象,注册回调函数,系统在缓冲区填满或耗尽时触发回调。开发者需要手动管理Buffer的入队与出队,灵活性高但复杂度也高。 - AAudio:Android 8.0引入的现代Native API,设计目标为低延迟与高可控性。它支持两种模式:共享模式(Shared)与独占模式(Exclusive)。独占模式下应用直接与HAL通信,绕过AudioFlinger混音,延迟最低。回调模型基于
AAudioStream_dataCallback,在音频线程中执行,要求回调函数必须快速返回,禁止阻塞操作。
4.2 配置参数与操作流程:从初始化到启动
无论使用哪套API,核心配置参数都包括:采样率(SampleRate)、声道数(Channel)、音频格式(Format)、缓冲区大小(BufferSize)。以下分别给出各API的典型配置流程。
4.2.1 AudioRecord采集配置
// 1. 计算最小缓冲区
int minBufSize = AudioRecord.getMinBufferSize(44100,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT);
// 2. 初始化AudioRecord
AudioRecord record = new AudioRecord(
MediaRecorder.AudioSource.MIC,
44100,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
minBufSize * 2); // 通常设为最小值的2倍
// 3. 启动采集
record.startRecording();
// 4. 循环读取
byte[] buffer = new byte[minBufSize];
while (isRecording) {
int read = record.read(buffer, 0, buffer.length);
// 处理PCM数据
}
// 5. 停止与释放
record.stop();
record.release();
注意:read()是阻塞调用,若缓冲区未填满,线程会挂起。在实时性要求高的场景(如VoIP),建议将读取操作放在独立线程,并使用setRecordPositionUpdateListener监听位置更新,实现非阻塞式采集。
4.2.2 AAudio播放配置
AAudioStreamBuilder *builder;
AAudioStream *stream;
// 1. 创建Builder
AAudio_createStreamBuilder(&builder);
// 2. 配置参数
AAudioStreamBuilder_setDirection(builder, AAUDIO_DIRECTION_OUTPUT);
AAudioStreamBuilder_setSampleRate(builder, 48000);
AAudioStreamBuilder_setChannelCount(builder, 2);
AAudioStreamBuilder_setFormat(builder, AAUDIO_FORMAT_PCM_I16);
AAudioStreamBuilder_setBufferCapacityInFrames(builder, 960);
// 3. 设置数据回调
AAudioStreamBuilder_setDataCallback(builder, myDataCallback, userData);
// 4. 打开流
AAudioStreamBuilder_openStream(builder, &stream);
// 5. 启动
AAudioStream_requestStart(stream);
// 回调函数示例
aaudio_data_callback_result_t myDataCallback(
AAudioStream *stream, void *userData, void *audioData, int32_t numFrames) {
// 将PCM数据填充到audioData,长度 = numFrames * channelCount * sizeof(int16_t)
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
AAudio的BufferCapacityInFrames决定了最大缓冲帧数,而实际缓冲区大小由系统根据延迟需求动态调整。开发者可通过AAudioStream_getFramesPerBurst()获取每次回调的帧数,通常为128或256帧。
| 配置项 | AudioRecord/Track | OpenSL ES | AAudio |
|---|---|---|---|
| 采样率 | 构造时指定 | SLDataFormat_PCM | Builder设置 |
| 缓冲区控制 | setBufferSizeInBytes | BufferQueue手动管理 | setBufferCapacityInFrames |
| 回调触发 | Listener(可选) | 必选回调 | 必选回调 |
| 延迟优化 | 一般 | 较好 | 最优(独占模式) |
| 最低API | 3 | 9 | 26 |
4.3 回调机制深度拆解:线程模型与数据一致性
回调机制是高性能音频系统的核心。AAudio与OpenSL ES的回调运行在实时音频线程中,该线程优先级极高,任何阻塞操作(如锁、内存分配、I/O)都可能导致音频卡顿甚至系统重启。开发者必须遵守以下铁律:
- 禁止阻塞:回调内不得调用
sleep()、mutex.lock()、malloc()等操作。 - 数据拷贝:回调中应尽快将音频数据拷贝到应用缓冲区,然后立即返回。
- 无锁队列:跨线程传递数据时,使用无锁环形队列(如
boost::lockfree::spsc_queue)或原子变量。 - CPU亲和性:音频线程通常绑定到特定CPU核心,避免被调度器迁移。
AAudioStream_write()会导致死锁。回调模式与阻塞模式不能混用。若需在回调中停止流,应使用AAudioStream_requestStop()的异步版本,而非同步等待。
4.4 实战案例:构建低延迟VoIP音频采集模块
假设我们需要实现一个VoIP通话模块,要求端到端延迟低于80ms,且需兼容Android 8.0以下设备。技术选型为:AAudio作为主路径(API 26+),OpenSL ES作为降级路径(API 21-25),AudioRecord作为最后兜底(API 21以下)。
初始现象与目标:在Pixel 6设备上测试,使用AudioRecord采集延迟约120ms,无法满足VoIP要求。改用AAudio独占模式后,采集延迟降至25ms,播放延迟降至30ms,整体链路满足要求。
关键参数与操作步骤:
- 查询设备是否支持独占模式:
AAudioStream_isMMapSupported()。 - 设置
AAUDIO_PERFORMANCE_MODE_LOW_LATENCY性能模式。 - 使用
AAudioStream_getFramesPerBurst()确定回调帧数,通常为192帧(48kHz采样率下约4ms)。 - 在回调中直接进行PCM编码(如Opus),避免额外拷贝。
- 对于API 21-25设备,使用OpenSL ES的
AndroidSimpleBufferQueue接口,注册SlAndroidSimpleBufferQueueItf回调。
结果对比:在Pixel 6上,AAudio独占模式采集延迟25ms,播放延迟30ms;OpenSL ES延迟约45ms;AudioRecord延迟约120ms。在低端设备(如Redmi 9A)上,AAudio可能不支持独占模式,自动降级为共享模式,延迟约60ms,仍优于AudioRecord。
风险与代价:AAudio独占模式在部分设备上存在兼容性问题(如某些三星机型),需在初始化时捕获异常并降级。此外,独占模式会占用唯一的低延迟音频通道,若同时播放铃声或导航音,可能被系统抢占。
AAudioStream_getPerformanceMode()确认实际生效模式。
4.5 常见误区与总结复盘
在多年音频开发中,以下误区反复出现,值得警惕:
- 误区一:缓冲区越大越稳定。实际上,过大的缓冲区会显著增加延迟,且不会提升稳定性。正确做法是使用
getMinBufferSize()作为起点,再根据实际卡顿情况微调。 - 误区二:回调中做格式转换。格式转换(如PCM→AAC)耗时较长,应放在工作线程。回调中只做内存拷贝。
- 误区三:忽略采样率匹配。如果采集为44.1kHz,播放为48kHz,必须进行重采样。在AAudio中,可通过
AAudioStreamBuilder_setSampleRateConversionQuality()设置重采样质量,但最好在应用层统一采样率。 - 误区四:不处理音频焦点变化。VoIP应用应监听
AudioManager.OnAudioFocusChangeListener,在失去焦点时暂停采集,避免后台录音。
总结而言,Android音频I/O的选型与实现,本质是在延迟、兼容性、CPU占用、开发复杂度之间做权衡。AudioRecord/AudioTrack适合对延迟不敏感的应用(如录音机、音乐播放);OpenSL ES适合需要Native处理且需兼容旧版本的应用;AAudio是当前低延迟场景的首选,但需做好降级预案。掌握回调机制与线程模型,是避免音频卡顿、崩溃的关键。建议开发者在项目初期就建立音频延迟测试基准,使用dumpsys media.audio_flinger查看实际缓冲区大小,持续优化。