4. Audio HAL核心概念
Audio HAL 是 Android 音频框架与底层驱动之间的“翻译层”。应用层通过 AudioTrack、AudioRecord、AudioManager 等 API 发出请求,AudioFlinger 与 AudioPolicyService 负责混音、路由和策略决策,而真正把 PCM 数据送到扬声器、从麦克风取回采样点的,是 Audio HAL 及其背后的内核驱动与 DSP。理解 Audio HAL,关键要抓住五个概念:HAL 接口、HIDL 与 AIDL、音频设备抽象、流与端口、音频补丁。
一、HAL 接口:框架与厂商的契约
Audio HAL 接口是一组由 Android 定义的 C/C++ 或绑定式接口,规定了框架可以调用哪些方法、传入哪些参数、期望得到什么结果。传统实现以 audio_hw_device_t、audio_stream_out_t、audio_stream_in_t 等结构体为核心,厂商在 hardware/libhardware/modules/audio 或 vendor 分区中实现这些函数指针。框架不关心底层是 Codec、DSP 还是外置功放,只要求接口语义一致。
这种契约式设计带来两个直接结果:一是可替换性,同一套 Android 框架可以适配不同 SoC 和 Codec;二是责任边界清晰,框架负责策略与混音,HAL 负责设备控制与数据传输。车载场景中,HAL 还要额外处理多区域音频、总线路由、音量曲线和硬件同步。
二、HIDL 与 AIDL:从 Treble 到稳定 AIDL
Android 8.0 引入 Treble 后,Audio HAL 从直连式 hw_get_module 逐步迁移到 HIDL(HAL Interface Definition Language)。HIDL 通过 IAudio、IDevicesFactory、IStreamOut 等接口,把 HAL 放入独立进程,实现 framework 与 vendor 的进程隔离和版本管理。Android 11 起,音频 HAL 开始向 AIDL 迁移;Android 13/14 中,AIDL Audio HAL 成为新设备的主流选择。
HIDL 与 AIDL 的核心差异如下表所示:
| 维度 | HIDL | AIDL Audio HAL |
|---|---|---|
| 引入版本 | Android 8.0 | Android 11 起逐步推广 |
| 接口定义 | .hal 文件,hidl-gen 生成 | .aidl 文件,aidl 工具生成 |
| 进程模型 | 独立 binder 化进程 | 独立进程,Binder 通信 |
| 版本管理 | 主版本 + 次版本,接口冻结 | 通过接口版本与 VINTF 声明 |
| 典型接口 | IDevicesFactory、IStreamOut | IAudioDevicesFactory、IStreamOut |
| 车载关注点 | 多流、多设备、低延迟 | 多区域、总线路由、动态策略 |
对车载音频工程师而言,HIDL 与 AIDL 的选择会影响调试方式:HIDL 时代常用 lshal、dumpsys media.audio_flinger 查看服务状态;AIDL 时代则更多依赖 dumpsys 与 cmd audio 系列命令,同时要关注 VINTF manifest 中声明的接口版本是否与 framework 匹配。
三、音频设备抽象:从“一个设备”到“一组端口”
Audio HAL 把物理音频硬件抽象为“设备”。传统上,一个 audio_hw_device_t 代表一张声卡或一个音频控制器,内部再划分输入流和输出流。随着车载多区域、多音源需求增加,设备抽象进一步细化为“端口”和“补丁”。
设备抽象的关键参数包括:设备类型(AUDIO_DEVICE_OUT_SPEAKER、AUDIO_DEVICE_OUT_BUS、AUDIO_DEVICE_IN_BUILTIN_MIC 等)、地址(如 bus0_media_out)、通道数、采样率、位深和增益。车载系统常把不同区域映射为不同 bus 地址,例如主驾导航、副驾娱乐、后排影音分别走独立输出端口。
四、流与端口:数据通道与控制端点
“流”是 Audio HAL 中承载 PCM 数据的通道,分为输出流(audio_stream_out)和输入流(audio_stream_in)。每个流有独立的采样率、格式、通道掩码和缓冲区,框架通过 write() 或 read() 与 HAL 交换数据。流是动态的:播放开始创建,停止后销毁。
“端口”更偏向控制与路由,描述音频信号从哪个源到哪个目的地的逻辑端点。一个端口可以对应一个物理设备,也可以对应一个虚拟总线。流负责“搬数据”,端口负责“定方向”。在 AIDL Audio HAL 中,IModule 下的 IStreamOut、IStreamIn 与 IDevice 共同构成流与端口的组合关系。
五、音频补丁:把流、端口和设备连起来
音频补丁(Audio Patch)是 Android 音频路由中的连接单元,用来描述“哪个源端口连接到哪个目的端口”。它由 AudioPolicyService 管理,通过 createAudioPatch() 和 releaseAudioPatch() 操作。补丁可以连接设备与设备、设备与混音器、混音器与流。
车载场景中,补丁常用于动态切换音源:当倒车雷达触发时,系统创建一条从雷达提示音到主驾扬声器的补丁,同时压低媒体音量;倒车结束后释放补丁,恢复原路由。补丁机制让路由变化不必重建整个音频链路,提升了实时性和稳定性。
六、案例:车载多区域音频路由
假设一辆车有主驾、副驾、后排三个区域。AudioPolicyService 根据音源类型和区域策略,决定创建哪些流和补丁。导航提示音走主驾 bus,媒体音乐走全车 bus,倒车雷达走主驾并触发音量闪避。HAL 层需要为每个 bus 地址实现独立的输出流,并在 createAudioPatch() 中正确响应路由切换。
// 典型 AIDL Audio HAL 输出流创建片段(示意)
ndk::ScopedAStatus StreamOut::create(
const AudioConfig& config,
const AudioOutputFlags& flags,
std::shared_ptr<IStreamOutCallback> callback) {
// 根据 config 中的 sampleRate、channelMask、format 配置 ALSA PCM
// 根据 flags 判断是否为低延迟、深缓冲或总线输出
return ndk::ScopedAStatus::ok();
}
// 补丁创建示意
status_t AudioPolicyService::createAudioPatch(
const struct audio_patch *patch) {
// patch->sources[0].id 为源端口
// patch->sinks[0].id 为目的端口
// 调用 HAL 的 createAudioPatch 完成底层路由
}
七、常见误区与复盘
- 误区一:把流当设备。流是数据通道,设备是控制端点。一个设备可以承载多个流,一个流也可以在不同设备间迁移。
- 误区二:认为 HIDL 和 AIDL 只是语法差异。两者在进程模型、版本管理、调试工具和 VINTF 声明上都有区别,迁移时需要同步更新 manifest 和 sepolicy。
- 误区三:忽略补丁的生命周期。补丁创建后未释放会导致路由残留,表现为“声音从错误区域出来”或“音量控制失效”。
- 误区四:设备地址硬编码。车载 bus 地址应由策略层动态分配,硬编码会降低多车型适配能力。
掌握 HAL 接口、HIDL 与 AIDL、设备抽象、流与端口、音频补丁这五个概念,就抓住了 Android Audio HAL 的主干。后续无论是分析 AudioFlinger 混音流程,还是排查多区域路由异常,都可以沿着“接口—服务—流—端口—补丁—驱动”这条链路逐层定位。