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() 操作。补丁可以连接设备与设备、设备与混音器、混音器与流。

车载场景中,补丁常用于动态切换音源:当倒车雷达触发时,系统创建一条从雷达提示音到主驾扬声器的补丁,同时压低媒体音量;倒车结束后释放补丁,恢复原路由。补丁机制让路由变化不必重建整个音频链路,提升了实时性和稳定性。

Audio HAL 核心概念关系图 应用与框架层 AudioTrack / AudioRecord / AudioManager → AudioFlinger(混音) + AudioPolicyService(策略与路由) 通过 HAL 接口调用,不直接访问驱动 HAL 接口契约 Audio HAL 层(HIDL / AIDL) HIDL 接口 IDevicesFactory / IStreamOut AIDL 接口 IAudioDevicesFactory / IStreamOut 设备抽象 Device / Port / Address 流与端口 Stream / Port 数据与控制 内核驱动与硬件 ALSA / ASoC / Codec / DSP / 功放 / 扬声器 / 麦克风阵列 音频补丁(Audio Patch):连接源端口与目的端口,由 AudioPolicyService 动态创建与释放 示例:倒车雷达提示音 → 主驾扬声器,同时压低媒体音量 补丁控制路由 流数据下发

六、案例:车载多区域音频路由

假设一辆车有主驾、副驾、后排三个区域。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 地址应由策略层动态分配,硬编码会降低多车型适配能力。
调试 Audio HAL 时,优先确认三件事:HAL 服务是否正常注册(lshal / dumpsys)、流参数是否与策略匹配、补丁是否按预期创建和释放。这三步能覆盖大部分车载音频路由问题。

掌握 HAL 接口、HIDL 与 AIDL、设备抽象、流与端口、音频补丁这五个概念,就抓住了 Android Audio HAL 的主干。后续无论是分析 AudioFlinger 混音流程,还是排查多区域路由异常,都可以沿着“接口—服务—流—端口—补丁—驱动”这条链路逐层定位。