2、音频策略核心:AudioPolicyService启动流程、策略配置文件解析(audio_policy_configuration.xml)、策略引擎初始化
好,我们直接进入正题。上一章聊了音频系统的整体架构,这一章我们聚焦在策略层面——说白了,就是系统怎么决定「这个音频走哪个设备出去」。
我个人习惯把 AudioPolicyService 看作整个音频系统的「交通指挥中心」。它不负责具体的数据搬运,但它决定每一条音频流该走哪条路。这个决策过程,就是音频策略的核心。
2.1 AudioPolicyService 启动流程
AudioPolicyService(简称 APS)是在系统启动时由 mediaserver 进程拉起来的。它的启动顺序很有意思——我当年调试一个车载项目时,就因为这个启动时序问题折腾了两天。
启动流程大致分三步:
- 创建 APS 实例:在 mediaserver 的 main 函数中,new AudioPolicyService() 被调用。
- 初始化策略引擎:构造函数内部会调用 AudioPolicyClientInterface 来加载配置文件。
- 注册服务:将 APS 注册到 ServiceManager,供 AudioFlinger 和其他客户端调用。
嗯,这里要注意:APS 的初始化是同步的。也就是说,如果配置文件解析失败,整个 mediaserver 都会挂掉。我曾经在一个定制 ROM 上遇到过这种情况——一个 XML 标签写错了,系统直接起不来,连 log 都来不及抓。
2.2 策略配置文件解析(audio_policy_configuration.xml)
这个文件是整个策略体系的「宪法」。它定义了系统有哪些音频设备、它们之间怎么连接、以及哪些策略规则生效。
文件结构其实不复杂,核心就这几个部分:
- globalConfiguration:全局策略开关,比如是否支持通话、是否启用 HDMI 直通。
- modules:每个音频模块(比如 primary、a2dp、usb)的配置。
- devicePorts:所有物理和逻辑音频设备的声明。
- route:设备之间的连接关系,说白了就是「哪个输出可以连到哪个输入」。
- profiles:每个设备的音频格式支持情况。
我截取一个典型的 primary 模块配置片段:
<module name="primary" halVersion="2.0">
<attachedDevices>
<item>Speaker</item>
<item>Built-In Mic</item>
</attachedDevices>
<devicePorts>
<devicePort tagName="Speaker" type="AUDIO_DEVICE_OUT_SPEAKER" role="source">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</devicePort>
</devicePorts>
<routes>
<route type="mix" sink="Speaker"
sources="mixer_out"/>
</routes>
</module>
你想想看,这个文件其实就是在描述一张「音频拓扑图」。每个 devicePort 是一个节点,每条 route 是一条边。APS 启动时,会把这个拓扑图加载到内存中,然后基于它来做路由决策。
为什么会用 XML?我个人理解,是为了方便 OEM 厂商定制。你不需要改 C++ 代码,只需要改这个 XML 文件,就能重新定义设备的音频路由行为。我见过一些厂商把这个文件改得面目全非——为了适配奇怪的硬件拓扑,他们会在 route 里加很多虚拟节点。
2.3 策略引擎初始化
配置文件解析完成后,APS 会初始化策略引擎。这个引擎的核心是一个状态机,它维护了当前所有音频流的活跃状态、设备连接状态、以及策略规则。
初始化过程大致如下:
| 步骤 | 动作 | 说明 |
|---|---|---|
| 1 | 加载配置文件 | 解析 XML,构建设备拓扑图 |
| 2 | 注册策略规则 | 加载内置策略(如通话优先、媒体降级) |
| 3 | 初始化设备状态 | 查询 HAL 层当前连接的物理设备 |
| 4 | 启动策略线程 | 监听音频流事件和设备插拔事件 |
这里有个关键点:策略引擎的初始化是「懒加载」的。也就是说,它不会一开始就把所有策略都算好,而是等到有音频流请求时,才动态计算路由。这样做的好处是启动快,坏处是第一次播放音频时会有延迟——我曾在某个低端设备上测到过 200ms 的路由计算延迟。
策略引擎的核心数据结构是 AudioPolicyManager。它维护了:
- 一个
mAvailableOutputDevices列表,记录当前可用的输出设备。 - 一个
mAvailableInputDevices列表,记录当前可用的输入设备。 - 一个
mRoutes映射表,记录设备间的连接关系。 - 一个
mStrategyPreferences优先级表,决定不同音频类型(媒体、通话、导航)的优先级。
我画了一张图,帮你理解这个初始化流程:
嗯,这张图基本把流程串起来了。你注意看步骤 4 和步骤 5 之间的交互——配置文件解析的结果直接决定了策略引擎能识别哪些设备。如果 XML 里漏掉了一个 devicePort,那这个设备在策略引擎眼里就是不存在的。
最后说一句,策略引擎初始化完成后,它会进入一个事件循环,等待音频流创建、设备插拔、策略更新等事件。每次事件触发,引擎都会重新计算当前最优的路由路径。这个计算过程我们下一章会详细拆解。
好,这一章就到这里。记住一句话:音频策略的核心不是代码,是配置。把 XML 吃透了,你就掌握了音频路由的命脉。