28、音频策略扩展接口:自定义策略插件机制、动态策略更新API、厂商策略扩展实践
音频策略这东西,说白了就是系统怎么决定“声音从哪出”。
但现实很骨感。每个厂商的硬件不一样,每个产品的场景也不一样。Google 给的那套默认策略,很多时候只是个“能跑”的起点。我这些年做过的项目里,几乎没有哪一个是直接用默认策略就能交付的。嗯,今天我们就聊聊怎么把策略这块“玩出花来”。
为什么需要策略扩展?
你想想看,一个手机厂商,可能同时做平板、电视、手表。每个设备的音频拓扑都不一样。有的用 SmartPA,有的用 Codec 直出,有的还有独立的 DSP。如果策略写死了,那换一个产品就得改框架层代码——这谁受得了?
所以,Android 从某个版本开始,就留了口子。允许你通过插件机制、动态 API 来定制策略。我个人的习惯是,先把默认策略跑一遍,看看哪些地方“不对劲”,然后再决定要不要上扩展。
自定义策略插件机制
插件机制,说白了就是让厂商能写自己的策略模块,然后塞进音频服务里。Android 的 AudioPolicy 本身是一个抽象类,厂商可以继承它,重写关键方法。
我记得第一次接触这个机制时,是在一个车载项目上。车机有十几个扬声器,还有独立的功放通道。默认的 AudioPolicy 根本处理不了这种拓扑。我们当时就是写了一个自定义的 AudioPolicy 插件,直接接管了路由决策。
插件机制的典型实现方式:
- 继承
AudioPolicy基类 - 实现
getOutputForAttr、getInputForAttr等核心方法 - 通过
AudioPolicyConfig加载自定义配置 - 注册到 AudioFlinger 中
// 伪代码示例:自定义策略插件
class CustomAudioPolicy : public AudioPolicy {
public:
audio_io_handle_t getOutputForAttr(
const audio_attributes_t *attr,
audio_io_handle_t output,
audio_session_t session,
audio_stream_type_t stream,
uid_t uid
) override {
// 这里你可以写自己的路由逻辑
// 比如:根据 uid 判断应用,或者根据设备状态选择输出
if (isCarMode()) {
return getCarSpeakerOutput();
}
return AudioPolicy::getOutputForAttr(attr, output, session, stream, uid);
}
};
动态策略更新 API
有些场景下,策略需要运行时改变。比如:插入耳机时,策略要切换路由;开启游戏模式时,策略要调整延迟。这些如果靠重启服务来实现,用户体验就太差了。
Android 提供了动态策略更新 API,允许你在不重启音频服务的情况下,修改策略行为。
常用的动态 API 包括:
AudioPolicyManager::setDeviceConnectionState:动态通知设备连接状态变化AudioPolicyManager::setForceUse:强制指定某种场景下的使用策略AudioPolicyManager::setParameters:传递自定义参数给策略层
我建议你在设计动态策略时,一定要考虑“回退机制”。比如,你强制把媒体声音路由到蓝牙音箱,但如果蓝牙突然断开,策略要能自动回退到扬声器。否则,用户会以为手机坏了。
// 动态更新示例:强制媒体输出到蓝牙
AudioSystem::setForceUse(
FORCE_MEDIA,
FORCE_BLUETOOTH_A2DP
);
厂商策略扩展实践
厂商扩展策略,通常有几种模式:
- 配置驱动:通过 XML 或 JSON 配置文件,定义路由规则。框架解析配置,动态生成策略。
- 代码注入:直接修改 AudioPolicy 源码,加入厂商特有的逻辑。
- 混合模式:配置 + 代码,配置决定大部分行为,代码处理特殊场景。
我个人比较推荐配置驱动的方式。为什么呢?因为代码注入虽然灵活,但每次 Android 版本升级,你都得重新适配。配置驱动的话,只要接口不变,配置可以复用。我在一个项目里,用 XML 定义了 200 多条路由规则,覆盖了所有可能的设备组合。后来升级到 Android 13,只改了 3 行代码就适配完了。
下面是一个典型的厂商策略扩展流程图:
实战中的几个关键点
说了这么多理论,来点实际的。我在几个项目里总结了一些经验:
| 场景 | 推荐方案 | 注意事项 |
|---|---|---|
| 多扬声器设备 | 自定义插件 + 配置驱动 | 注意扬声器分组和功率限制 |
| 车载多音区 | 代码注入 + 动态 API | 不同音区之间要隔离,避免串扰 |
| 游戏低延迟模式 | 动态 API + 参数传递 | 切换时要做淡入淡出,避免爆音 |
| 会议场景 | 配置驱动 + 插件 | 回声消除和麦克风阵列要联动 |
总结一下
音频策略扩展,本质上就是给框架装上一个“可编程的大脑”。插件机制让你能写自己的决策逻辑,动态 API 让你能在运行时调整行为,配置驱动则让策略变得可维护。
嗯,最后说一句:不要为了扩展而扩展。如果默认策略能满足需求,就别动它。我见过太多项目,明明一个 setForceUse 就能解决的事,非要写一个几百行的插件——结果 bug 比功能还多。
公众号:蓝海资料掘金营,微信deep3321