12、音频路由与策略:AudioPolicyService、路由策略(听筒/扬声器/蓝牙)、动态切换对延时的影响

音频路由,说白了就是决定声音从哪条路出去、从哪条路进来。你插上耳机,声音自动切到耳机;拔掉耳机,声音回到扬声器。这个看似简单的动作,背后是 AudioPolicyService 在忙活。

我个人习惯把音频路由比作「交通调度」。AudioPolicyService 就是那个交通指挥中心,它要根据当前的路况(设备连接状态)、乘客的请求(音频流类型)、以及交通规则(策略配置),来决定走哪条路。

12.1 AudioPolicyService 的核心职责

AudioPolicyService(简称 APS)是 Android 音频系统的策略大脑。它不负责具体的音频数据搬运,那是 AudioFlinger 的事。APS 只做一件事:决策

  • 设备选择:当前应该用哪个音频设备输出?扬声器?耳机?蓝牙?
  • 路由切换:当设备插拔时,如何平滑切换?
  • 策略执行:不同应用、不同音频类型,走不同的路由策略。

我在项目中遇到过一个问题:某个定制平板,插上 USB-C 耳机后,声音还是从扬声器出来。查了半天,发现是 APS 的音频策略配置文件(audio_policy_configuration.xml)里,USB 设备的优先级配错了。嗯,这种坑其实挺常见的。

12.2 路由策略:听筒、扬声器、蓝牙

Android 定义了几种典型的音频路由策略。每种策略对应不同的使用场景,也对应不同的延时特性。

路由策略 典型场景 路径延时 特点
听筒(Earpiece) 通话 低(~10ms) 近距离、小功率、低延时
扬声器(Speaker) 免提、媒体播放 中(~20ms) 功率大、可能经过 DSP 处理
有线耳机(Wired Headset) 音乐、通话 低(~10ms) 模拟直连,延时最低
蓝牙 SCO 蓝牙通话 高(~40-60ms) 需要编解码、传输、缓冲
蓝牙 A2DP 蓝牙音乐 高(~100-200ms) 高音质、大缓冲、高延时

你想想看,为什么蓝牙通话的延时比蓝牙音乐低那么多?因为 SCO 走的是同步面向连接链路,数据包小、间隔短。而 A2DP 为了音质,用了更大的缓冲区和更复杂的编码器。这就是典型的「延时 vs 质量」的取舍。

12.3 动态切换:延时杀手

动态路由切换,是低延时优化里最容易被忽视的环节。我见过太多项目,静态延时测出来很漂亮,但一切换设备,咔,卡顿、爆音、延时飙升全来了。

为什么会这样?因为切换不是瞬间完成的。APS 需要:

  1. 检测到设备状态变化(比如蓝牙断开)
  2. 查询策略配置,确定新设备
  3. 通知 AudioFlinger 关闭旧设备、打开新设备
  4. 重新建立音频通路
  5. 清空旧缓冲区,填充新缓冲区

每一步都有开销。尤其是第 5 步,缓冲区清空和重新填充,如果处理不好,就会引入几十甚至上百毫秒的额外延时。

关键点:动态切换时,APS 会触发一次「路由重建」。这个过程会暂停音频流、销毁旧 AudioPatch、创建新 AudioPatch。暂停期间的声音,要么丢掉,要么卡住。这就是延时抖动的来源。

12.4 路由策略配置:audio_policy_configuration.xml

APS 的策略配置,核心文件是 audio_policy_configuration.xml。这个文件定义了所有音频设备、设备之间的连接关系、以及路由规则。

我曾经在一个项目中,为了降低蓝牙切换的延时,修改了这个配置文件。具体做法是:

  • 减少蓝牙设备的路由候选数量,让 APS 更快做出决策
  • 为通话场景单独配置一条「快速路径」,跳过不必要的 DSP 处理

下面是一个简化的配置片段:

<!-- audio_policy_configuration.xml 片段 -->
<audioPolicyConfiguration version="1.0">
    <globalConfiguration speaker_drc_enabled="false"/>
    
    <modules>
        <module name="primary" halVersion="3.0">
            <attachedDevices>
                <item>Speaker</item>
                <item>Earpiece</item>
                <item>Wired Headset</item>
            </attachedDevices>
            
            <routeStrategy name="phone_call">
                <!-- 通话优先走听筒 -->
                <device address="Earpiece" />
                <!-- 如果听筒不可用,走有线耳机 -->
                <device address="Wired Headset" />
                <!-- 最后才走蓝牙 SCO -->
                <device address="BT SCO" />
            </routeStrategy>
        </module>
    </modules>
</audioPolicyConfiguration>

注意看 routeStrategy 里的设备顺序。这个顺序决定了 APS 选择设备的优先级。如果你把蓝牙 SCO 放在第一位,那每次通话都会走蓝牙,即使有线耳机插着。这显然不是我们想要的。

12.5 动态切换的延时优化技巧

说几个我实际用过的优化手段:

技巧一:预创建 AudioPatch
在设备切换前,提前创建好目标设备的 AudioPatch。这样切换时只需要做「切换」操作,不需要「创建+切换」。我在一个车载项目中用过这个方案,切换延时从 80ms 降到了 15ms。
技巧二:减少策略层级
路由策略的层级越深,APS 决策越慢。尽量把常用的设备放在顶层,减少条件判断。说白了,就是让 APS 少动脑子。
警告:不要频繁切换
有些应用会在蓝牙连接不稳定时,反复在扬声器和蓝牙之间切换。每次切换都会触发路由重建,导致音频流频繁中断。我曾经遇到一个 case,蓝牙信号弱的时候,每秒切换 3-4 次,音频完全没法听。解决方案是加一个「切换去抖」逻辑:检测到设备状态变化后,等待 200ms 再执行切换,避免抖动。

12.6 路由策略对延时的综合影响

下面这张图,是我自己总结的音频路由决策流程。你可以看到,从应用发起请求到最终声音输出,中间经过了多个决策节点。每个节点都可能引入延时。

音频路由决策流程与延时节点 应用发起音频请求 AudioPolicyService 决策 查询策略配置 → 选择设备 → 确定路由 有线/听筒/扬声器 低延时路径(~10ms) 蓝牙 SCO 中延时路径(~40ms) 蓝牙 A2DP 高延时路径(~150ms) AudioFlinger 创建/切换 AudioPatch → 建立音频通路 声音输出(延时 = 各节点累加) ~1ms ~2-5ms ~0-150ms ~5-20ms 最终延时

从这张图可以看得很清楚:路由策略的选择,直接决定了后续路径的延时基数。你选了 A2DP,那后面再怎么优化,也绕不开蓝牙编解码和传输的延时。所以,低延时优化的第一步,就是选对路由策略。

12.7 实战建议

最后,给几个我自己的实战建议:

  • 能走有线,别走无线。有线耳机的延时是最低的,没有之一。
  • 通话场景,优先 SCO。如果必须用蓝牙,SCO 的延时比 A2DP 低得多。
  • 动态切换时,加去抖。别让 APS 在设备不稳定时反复切换。
  • 预创建 AudioPatch。这是降低切换延时的最有效手段,没有之一。
  • 定期检查策略配置。有时候问题就出在 audio_policy_configuration.xml 里,设备优先级配错了。

嗯,关于音频路由和策略,今天就聊到这儿。这部分内容虽然看起来偏「策略」层面,但它对延时的影响,一点都不比底层优化小。选对路,比跑得快更重要。


公众号:蓝海资料掘金营,微信deep3321