12、音频策略引擎:策略引擎架构、策略规则解析、动态策略调整、多设备输出策略

各位好,欢迎来到第十二讲。

前面我们聊了 AudioFlinger 的线程模型、混音流程,还有设备管理。今天要讲的,是整个音频框架里最“聪明”的部分——策略引擎

说白了,策略引擎就是 Android 音频系统的“大脑”。它决定了一件事:当多个音频流同时存在时,谁该出声?出多大声音?从哪个设备出来?

我个人习惯把策略引擎比作一个交通指挥中心。车流(音频流)来了,它要判断哪条路能走、哪条路要等、哪条路要优先放行。没有它,整个音频系统就是一团乱麻。

12.1 策略引擎架构

先看整体架构。策略引擎在 AudioFlinger 之上,但又不完全属于 AudioFlinger。它位于 AudioPolicyService 内部,是独立的一套逻辑。

核心组件:

  • AudioPolicyService:对外接口,接收上层请求
  • AudioPolicyManager:策略执行者,真正的决策中心
  • AudioPolicyConfig:策略配置文件,定义规则
  • AudioPolicyClient:与 AudioFlinger 通信的桥梁

我画了一张架构图,方便你理解它们之间的关系:

应用层(MediaPlayer / AudioTrack) AudioPolicyService(策略服务) 策略引擎核心 AudioPolicyManager (策略执行者) AudioPolicyConfig (策略规则) AudioPolicyClient (与Flinger通信) AudioFlinger(音频引擎)

嗯,这里要注意:策略引擎不是每次请求都重新解析配置文件。它启动时加载一次,后续通过事件驱动来更新。我在项目中遇到过一个问题:修改了 audio_policy_configuration.xml 后没重启服务,结果新规则死活不生效。后来才发现,策略引擎只在启动时读取配置。

12.2 策略规则解析

策略规则定义在 /system/etc/audio_policy_configuration.xml 里。你想想看,这个文件就是策略引擎的“宪法”。

核心规则包括:

  • 设备优先级:比如有线耳机 > 蓝牙耳机 > 扬声器
  • 音频流类型:音乐、通话、闹钟、通知等各有不同策略
  • 路由规则:什么类型的音频走哪个设备
  • 音量曲线:不同设备的音量映射关系

举个例子,看一段简化的配置:

<!-- 设备优先级定义 -->
<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>

<!-- 路由规则:音乐走扬声器,除非插了耳机 -->
<route type="mix">
    <source type="AUDIO_USAGE_MEDIA"/>
    <sink type="AUDIO_DEVICE_OUT_SPEAKER"/>
    <sink type="AUDIO_DEVICE_OUT_WIRED_HEADSET"/>
</route>

这段配置告诉策略引擎:音乐默认走扬声器,但如果插了有线耳机,就自动切换到耳机。说白了,这就是一个 if-else 逻辑,只不过用 XML 表达出来了。

避坑指南:我曾经在配置文件中把 devicePort 的 type 写错了,结果插上耳机后声音还是从扬声器出来。排查了半天才发现是 typo。所以,修改配置文件时一定要仔细核对设备类型名称。

12.3 动态策略调整

策略引擎不是死板的。它会根据系统状态动态调整策略。比如:

  • 插入耳机时,自动切换音频输出设备
  • 蓝牙连接时,询问是否切换到蓝牙设备
  • 来电时,自动降低音乐音量(Ducking)
  • 闹钟响起时,强制提高音量

这些动态调整的核心机制是事件监听。AudioPolicyService 会注册各种系统广播:

// 伪代码:动态策略调整流程
void AudioPolicyManager::onDeviceConnected(AudioDevice device) {
    // 1. 更新设备列表
    mAvailableDevices.add(device);
    
    // 2. 检查是否有正在播放的音频流
    for (auto& stream : mActiveStreams) {
        // 3. 判断是否需要切换路由
        if (shouldSwitchRoute(stream, device)) {
            // 4. 执行路由切换
            switchAudioRoute(stream, device);
        }
    }
    
    // 5. 通知上层设备变化
    notifyDeviceChanged(device);
}

为什么会这样设计?因为音频策略需要实时响应。你想想看,用户插上耳机后如果等两秒才切换,体验得多糟糕。

我记得有一次做车载系统,用户连接蓝牙后,音乐切换有 1 秒延迟。排查发现是策略引擎在处理蓝牙连接事件时,做了太多同步操作。后来改成异步处理,延迟降到了 100ms 以内。

12.4 多设备输出策略

这是策略引擎最复杂的部分。现代 Android 设备往往有多个音频输出设备:扬声器、有线耳机、蓝牙耳机、USB-C 耳机、HDMI 音频等。策略引擎需要决定:同时输出到多个设备,还是只输出到一个?

多设备输出策略分为几种模式:

模式 说明 典型场景
独占模式 只输出到一个设备 通话时只走听筒
并发模式 同时输出到多个设备 音乐同时走扬声器和蓝牙音箱
动态切换 根据优先级自动切换 插耳机时从扬声器切换到耳机
混合模式 不同音频流走不同设备 导航音走扬声器,音乐走蓝牙

嗯,这里要特别说一下混合模式。这是 Android 10 之后引入的,允许不同音频流走不同设备。比如你开车时,导航提示音从车载扬声器出来,而音乐通过蓝牙耳机播放。这在以前是做不到的。

实现原理其实不复杂:

// 混合模式路由决策
AudioDevice AudioPolicyManager::getDeviceForStream(audio_usage_t usage) {
    switch (usage) {
        case AUDIO_USAGE_NAVIGATION:
            // 导航走车载扬声器
            return mCarSpeaker;
        case AUDIO_USAGE_MEDIA:
            // 音乐走蓝牙耳机
            return mBluetoothHeadset;
        case AUDIO_USAGE_VOICE_COMMUNICATION:
            // 通话走听筒
            return mEarpiece;
        default:
            // 其他走默认设备
            return getDefaultDevice();
    }
}

我曾经在项目中遇到一个坑:混合模式下,两个设备如果采样率不同,会导致音频不同步。比如导航是 16kHz,音乐是 48kHz,听起来就像两个人在各说各的。解决方案是让 AudioFlinger 对低采样率的音频流做重采样,统一输出格式。

注意事项:多设备输出时,一定要考虑功耗问题。同时驱动多个音频设备,耗电量会明显增加。我在平板项目上就吃过这个亏——同时输出到扬声器和蓝牙,续航直接掉了 15%。

最后总结一下策略引擎的核心思想:规则驱动 + 事件响应 + 动态调整。它不像 AudioFlinger 那样处理音频数据,而是做决策。决策的质量,直接决定了用户体验的好坏。

好了,这一讲就到这里。策略引擎的内容其实很深,今天我们只覆盖了主干。下一讲我们会深入策略引擎的源码实现,看看那些决策逻辑到底是怎么写的。


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