2、音频策略核心:AudioPolicyService启动流程、策略配置文件解析(audio_policy_configuration.xml)、策略引擎初始化

好,我们直接进入正题。上一章聊了音频系统的整体架构,这一章我们聚焦在策略层面——说白了,就是系统怎么决定「这个音频走哪个设备出去」。

我个人习惯把 AudioPolicyService 看作整个音频系统的「交通指挥中心」。它不负责具体的数据搬运,但它决定每一条音频流该走哪条路。这个决策过程,就是音频策略的核心。

2.1 AudioPolicyService 启动流程

AudioPolicyService(简称 APS)是在系统启动时由 mediaserver 进程拉起来的。它的启动顺序很有意思——我当年调试一个车载项目时,就因为这个启动时序问题折腾了两天。

启动流程大致分三步:

  1. 创建 APS 实例:在 mediaserver 的 main 函数中,new AudioPolicyService() 被调用。
  2. 初始化策略引擎:构造函数内部会调用 AudioPolicyClientInterface 来加载配置文件。
  3. 注册服务:将 APS 注册到 ServiceManager,供 AudioFlinger 和其他客户端调用。

嗯,这里要注意:APS 的初始化是同步的。也就是说,如果配置文件解析失败,整个 mediaserver 都会挂掉。我曾经在一个定制 ROM 上遇到过这种情况——一个 XML 标签写错了,系统直接起不来,连 log 都来不及抓。

避坑指南:我曾经在生产环境遇到过因为 audio_policy_configuration.xml 中一个 devicePort 的 type 属性拼写错误,导致 APS 启动时 segfault。建议在提交配置文件前,先用 xmllint 做一次语法校验。

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::setDeviceConnectionState() 的调用链路,看看 HAL 层是否正确上报了插拔事件。

策略引擎的核心数据结构是 AudioPolicyManager。它维护了:

  • 一个 mAvailableOutputDevices 列表,记录当前可用的输出设备。
  • 一个 mAvailableInputDevices 列表,记录当前可用的输入设备。
  • 一个 mRoutes 映射表,记录设备间的连接关系。
  • 一个 mStrategyPreferences 优先级表,决定不同音频类型(媒体、通话、导航)的优先级。

我画了一张图,帮你理解这个初始化流程:

AudioPolicyService 启动与策略引擎初始化流程 1. mediaserver 启动 2. 创建 APS 实例 3. 加载配置文件 4. 解析 audio_policy_configuration.xml 构建 devicePorts / routes 拓扑图 5. 策略引擎初始化 加载策略规则 / 注册设备状态 6. 启动策略线程 7. APS 就绪,等待音频请求 关键数据结构 • mAvailableOutputDevices • mAvailableInputDevices • mRoutes 映射表 • mStrategyPreferences

嗯,这张图基本把流程串起来了。你注意看步骤 4 和步骤 5 之间的交互——配置文件解析的结果直接决定了策略引擎能识别哪些设备。如果 XML 里漏掉了一个 devicePort,那这个设备在策略引擎眼里就是不存在的。

核心要点:AudioPolicyService 的启动本质上是「配置驱动」的。你改一行 XML,整个路由行为就可能天翻地覆。这也是 Android 音频系统灵活性的体现——但也是坑最多的地方。

最后说一句,策略引擎初始化完成后,它会进入一个事件循环,等待音频流创建、设备插拔、策略更新等事件。每次事件触发,引擎都会重新计算当前最优的路由路径。这个计算过程我们下一章会详细拆解。

好,这一章就到这里。记住一句话:音频策略的核心不是代码,是配置。把 XML 吃透了,你就掌握了音频路由的命脉。