27、音频策略源码导读:AudioPolicyService关键类解析、策略决策核心函数分析、路由状态机源码走读
好,咱们今天来啃一块硬骨头——AudioPolicyService 的源码。说实话,这章内容我准备了很久。为什么?因为音频策略这块,光看文档是学不会的。你得一行一行读代码,跟着路由状态机走一遍,才能真正理解 Android 是怎么决定「声音从哪出」的。
我在做某个车载项目时,遇到过蓝牙电话切不回扬声器的诡异问题。查了两天,最后发现是策略决策函数里一个策略优先级判断写反了。从那以后,我就养成了定期通读 AudioPolicyService 核心代码的习惯。嗯,咱们今天就把这些关键类和方法捋一遍。
一、AudioPolicyService 关键类解析
先看看 AudioPolicyService 的类结构。说白了,它就是个策略大脑,负责管理音频输入输出设备的选择和切换。
| 类名 | 职责 | 我的理解 |
|---|---|---|
| AudioPolicyService | 对外服务接口,处理客户端请求 | 相当于前台接待,收需求但不做决策 |
| AudioPolicyManager | 核心策略引擎,决策路由 | 真正的幕后大脑,所有策略逻辑在这 |
| AudioPolicyClient | 与 HAL 层交互的桥梁 | 执行者,让硬件动起来 |
| DeviceDescriptor | 描述音频设备属性 | 每个设备的「身份证」 |
| AudioPatch | 表示音频路由路径 | 从源到宿的完整链路 |
我个人习惯把 AudioPolicyManager 看作整个系统的「交通指挥中心」。它手里拿着所有设备的清单,知道每个音频流该走哪条路。
核心要点:AudioPolicyService 本身不做决策,它把请求转发给 AudioPolicyManager。真正的策略逻辑全在 AudioPolicyManager 里。
二、策略决策核心函数分析
咱们直接看代码。AudioPolicyManager 里最核心的函数是 getDeviceForStrategy。这个函数决定了某个音频策略应该使用哪个设备。
// frameworks/av/services/audiopolicy/manager/AudioPolicyManager.cpp
audio_devices_t AudioPolicyManager::getDeviceForStrategy(
audio_strategy_t strategy,
const audio_attributes_t *attributes,
const sp<AudioOutputDescriptor> &outputDesc) {
// 1. 检查是否有强制设备
audio_devices_t forcedDevice = getForcedDeviceForStrategy(strategy);
if (forcedDevice != AUDIO_DEVICE_NONE) {
return forcedDevice;
}
// 2. 根据策略类型选择设备
switch (strategy) {
case STRATEGY_MEDIA:
// 媒体策略:优先选择 A2DP 耳机,其次扬声器
if (isA2dpAvailable()) {
return AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
}
return AUDIO_DEVICE_OUT_SPEAKER;
case STRATEGY_PHONE:
// 电话策略:优先选择有线耳机,其次蓝牙 SCO
if (isWiredHeadsetAvailable()) {
return AUDIO_DEVICE_OUT_WIRED_HEADSET;
}
if (isBluetoothScoAvailable()) {
return AUDIO_DEVICE_OUT_BLUETOOTH_SCO;
}
return AUDIO_DEVICE_OUT_EARPIECE;
// ... 其他策略
}
}
你看,这个函数的逻辑其实很直白。它根据不同的策略类型,按照优先级去检查设备是否可用。我曾经在调试一个通话场景时发现,明明插着有线耳机,电话声音却从听筒出来。查了半天,原来是 isWiredHeadsetAvailable() 里检测的是耳机插入状态,但那个状态在某个场景下没更新。嗯,这种坑很常见。
另一个关键函数是 checkAndSetDevice。它负责在设备切换时做校验和状态更新。
status_t AudioPolicyManager::checkAndSetDevice(
audio_io_handle_t output,
audio_devices_t device,
const sp<AudioOutputDescriptor> &outputDesc) {
// 检查设备是否有效
if (!isValidDevice(device)) {
ALOGE("Invalid device 0x%x", device);
return BAD_VALUE;
}
// 检查设备是否被策略允许
if (!isDeviceAllowedByStrategy(device, outputDesc->getStrategy())) {
ALOGW("Device 0x%x not allowed for strategy %d",
device, outputDesc->getStrategy());
return PERMISSION_DENIED;
}
// 更新输出描述符的设备信息
outputDesc->setDevice(device);
// 通知 HAL 层设备变更
mpClientInterface->setOutputDevice(output, device);
return OK;
}
避坑指南:我曾经在 checkAndSetDevice 里漏掉了 isDeviceAllowedByStrategy 的检查,结果导致某个定制设备在通话时被错误地路由到了扬声器。记住:策略检查不能跳过,哪怕你觉得某个设备「肯定能用」。
三、路由状态机源码走读
路由状态机是 AudioPolicyService 里最精妙的部分。它管理着音频路由的生命周期:从创建、激活、切换到销毁。
咱们先看状态定义:
enum routing_state_t {
ROUTING_STATE_IDLE = 0, // 空闲状态
ROUTING_STATE_PENDING, // 等待路由变更
ROUTING_STATE_ACTIVE, // 路由已激活
ROUTING_STATE_SWITCHING, // 正在切换路由
ROUTING_STATE_ERROR // 路由错误
};
状态机的核心逻辑在 AudioPolicyManager::setOutputDevice 里。我简化一下关键流程:
void AudioPolicyManager::setOutputDevice(
audio_io_handle_t output,
audio_devices_t device,
bool force) {
sp<AudioOutputDescriptor> outputDesc = getOutputDesc(output);
// 状态机:检查当前状态
switch (outputDesc->getRoutingState()) {
case ROUTING_STATE_IDLE:
// 直接设置设备
outputDesc->setDevice(device);
outputDesc->setRoutingState(ROUTING_STATE_ACTIVE);
break;
case ROUTING_STATE_ACTIVE:
// 需要切换设备
outputDesc->setRoutingState(ROUTING_STATE_SWITCHING);
// 执行切换逻辑
performDeviceSwitch(output, device);
outputDesc->setRoutingState(ROUTING_STATE_ACTIVE);
break;
case ROUTING_STATE_SWITCHING:
// 正在切换中,新请求排队
queuePendingDeviceChange(output, device);
break;
case ROUTING_STATE_ERROR:
// 错误状态,需要重置
resetRoutingState(output);
break;
}
}
你想想看,为什么需要状态机?因为音频路由切换不是瞬间完成的。比如从扬声器切换到蓝牙耳机,中间要经过蓝牙连接建立、SCO 链路建立、音频重定向等步骤。如果在这个过程中又来一个新请求,状态机就能优雅地处理——要么排队,要么丢弃。
这张图我画了好几次才满意。你看,从 IDLE 到 ACTIVE 是最简单的路径——直接设置设备就行。但从 ACTIVE 到 SWITCHING 再到 ACTIVE,中间就复杂了。如果切换过程中有新请求进来,就会进入 PENDING 状态排队。
注意:状态机里最容易出问题的是 SWITCHING 状态。如果切换过程中设备断开或者超时,状态机会卡在 SWITCHING 里。我遇到过蓝牙耳机在切换过程中突然断开,结果状态机一直卡在 SWITCHING,后续所有路由请求都被排队了。解决方案是在切换函数里加超时检测,超时后强制回退到 IDLE 状态。
四、路由决策的完整流程
咱们把上面讲的串起来,看看一个完整的路由决策流程是什么样的:
- 应用发起请求:比如播放音乐,AudioTrack 创建时指定了使用 MUSIC 流类型。
- AudioPolicyService 接收:收到请求后,调用
getOutputForAttr方法。 - 策略匹配:AudioPolicyManager 根据音频属性匹配到
STRATEGY_MEDIA策略。 - 设备选择:调用
getDeviceForStrategy,按优先级选择可用设备。 - 状态机检查:检查当前输出设备的状态,决定是直接设置还是切换。
- 执行路由:通过 AudioPolicyClient 通知 HAL 层,创建或更新 AudioPatch。
- 状态更新:更新状态机状态,通知监听者路由变更。
这个流程看起来简单,但每个步骤都有很多细节。比如第 4 步的设备选择,要考虑设备优先级、可用性、策略限制、音频属性匹配等多个因素。
我的经验:调试路由问题时,我建议你在 getDeviceForStrategy 和 setOutputDevice 里加日志。打印出当前策略、可用设备列表、选择的设备、状态机状态。这样你就能清楚地看到每一步发生了什么。我曾经靠这个方法,半天就定位了一个蓝牙耳机连上后声音还在扬声器出的问题——原来是 A2DP 设备注册时漏掉了策略更新。
五、总结
AudioPolicyService 的源码读起来确实有点绕,但抓住三个核心就好:关键类(知道谁负责什么)、决策函数(知道怎么选设备)、状态机(知道怎么切换设备)。
我个人建议你按照这个顺序去读源码:先看 AudioPolicyManager::getDeviceForStrategy,再看 setOutputDevice,最后看状态机的各个状态处理。这样由浅入深,不会一开始就被状态机的各种分支搞晕。
嗯,音频策略这块内容确实不少。但只要你把今天讲的这些关键类和方法吃透了,以后遇到路由相关的问题,你就能快速定位到问题所在。毕竟,源码面前,了无秘密。