一、引言:谁来决定声音该从哪里出来?
假设你正在手机上用耳机听音乐,突然来了一个电话。你接通电话后,声音应该从扬声器还是耳机里出来?听筒还是耳机扬声器?如果用户同时连着蓝牙耳机和有线耳机,又该怎么选?
这些问题看似简单,实则暗藏玄机。在 Android 系统里,负责做这类决策的就是今天的主角------AudioPolicyService(音频策略服务)。
前几篇我们介绍了 AudioFlinger 的混音机制------它就像一个全力运转的"音频工厂",负责把音频数据混合、处理后送给硬件。但工厂只管生产,不管销售策略。AudioPolicyService 就是那个"销售总监":它不碰音频数据,专门负责决策音频应该走哪条路。
这种职责分离的设计非常优雅:AudioFlinger 关注怎么播放 ,AudioPolicyService 关注往哪里播放。本篇将深入 AudioPolicyService 的内部,看看 Android 15 是如何处理这些复杂的路由策略的。
二、AudioPolicyService 架构概览
2.1 在系统中的位置
AudioPolicyService(APS)运行在 audioserver 进程中,和 AudioFlinger 是"邻居"。它们都由 audioserver.rc 启动,共享同一进程空间,但通过独立的 Binder 服务对外暴露接口。
arduino
// frameworks/av/media/audioserver/main_audioserver.cpp
int main(int argc __unused, char **argv)
{
// ...
AudioFlinger::instantiate(); // 音频混音引擎
AudioPolicyService::instantiate(); // 音频策略服务
// ...
}
从架构图可以看出 APS 在整个音频系统中的位置:

系统中有两个与音频策略相关的服务容易混淆:
- AudioService (Java 层,运行在
system_server):提供AudioManagerAPI,是应用访问的门户 - AudioPolicyService (Native 层,运行在
audioserver):真正执行策略决策的引擎
应用调用 AudioManager.setMode() 时,最终会跨进程 Binder 调用到 APS 的 setPhoneState()。
2.2 核心组件层次
APS 内部的层次结构如下:
scss
AudioPolicyService
├── AudioPolicyManager // 策略管理主体,处理大部分请求
│ ├── Engine (EngineInterface) // 路由决策引擎
│ │ └── EngineBase // 默认引擎实现
│ ├── AudioPolicyMix // Mix策略管理(动态路由)
│ └── HwModule // 硬件模块抽象
└── AudioCommandThread // 异步命令线程
AudioPolicyManager(APM) 是整个策略逻辑的核心,它持有当前系统的全局状态:已连接的设备列表、已打开的输入输出流、活跃的音频策略等。
Engine 是路由决策的大脑,从 APM 的庞大逻辑中单独抽出,通过 EngineInterface 接口隔离。Engine 的默认实现读取 audio_policy_engine_configuration.xml,根据 Mode、ForceUse 等条件计算最终设备选择。
AudioCommandThread 负责异步处理设备连接/断开等耗时操作,避免阻塞调用方。
2.3 与 AudioFlinger 的协作
两者之间的交互简洁而高效:
scss
App AudioPolicyService AudioFlinger
| | |
|-- getOutput() -------> | |
| 决策路由... |
| |---- openOutput() ------> |
| |<--- output handle ------- |
|<-- IoHandle ---------- | |
| |
|-- createTrack(IoHandle) -----------------------> |
APS 只负责决定用哪个输出流(IoHandle),AudioFlinger 负责在这个流上实际创建 Track 并处理音频数据。策略和执行完全解耦。
三、策略配置文件:audio_policy_configuration.xml
3.1 文件位置与加载时机
Android 的音频路由拓扑由 XML 配置文件定义,位置因厂商而异:
bash
# 主配置文件(设备商提供)
/vendor/etc/audio_policy_configuration.xml
# AOSP默认配置(通用参考)
/system/etc/audio_policy_configuration.xml
# 引擎配置(策略规则)
/system/etc/audio_policy_engine_configuration.xml
AudioPolicyManager 初始化时解析这些文件,构建设备拓扑图。
3.2 配置文件结构解析
一个典型的配置文件结构:
xml
<!-- audio_policy_configuration.xml 核心结构 -->
<audioPolicyConfiguration version="1.0">
<globalConfiguration speaker_drc_enabled="true"/>
<modules>
<!-- 主音频模块(通常对应 audio.primary.so HAL) -->
<module name="primary" halVersion="2.0">
<!-- mixPort: 代表 AudioFlinger 中的一个音频线程 -->
<mixPorts>
<mixPort name="primary output" role="source"
flags="AUDIO_OUTPUT_FLAG_PRIMARY">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
<mixPort name="deep_buffer" role="source"
flags="AUDIO_OUTPUT_FLAG_DEEP_BUFFER">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
</mixPorts>
<!-- devicePort: 代表物理音频设备 -->
<devicePorts>
<devicePort tagName="Speaker" type="AUDIO_DEVICE_OUT_SPEAKER"
role="sink">
<gains>
<gain name="" mode="AUDIO_GAIN_MODE_JOINT"
minValueMB="-9600" maxValueMB="0" defaultValueMB="0"
stepValueMB="100"/>
</gains>
</devicePort>
<devicePort tagName="Earpiece" type="AUDIO_DEVICE_OUT_EARPIECE" role="sink"/>
<devicePort tagName="Wired Headset" type="AUDIO_DEVICE_OUT_WIRED_HEADSET" role="sink"/>
<devicePort tagName="BT A2DP" type="AUDIO_DEVICE_OUT_BLUETOOTH_A2DP" role="sink"/>
<devicePort tagName="BT SCO" type="AUDIO_DEVICE_OUT_BLUETOOTH_SCO" role="sink"/>
</devicePorts>
<!-- routes: 定义 mixPort 到 devicePort 的连接关系 -->
<routes>
<route type="mix" sink="Speaker" sources="primary output,deep_buffer"/>
<route type="mix" sink="Wired Headset" sources="primary output,deep_buffer"/>
<route type="mix" sink="BT A2DP" sources="primary output,deep_buffer"/>
</routes>
</module>
</modules>
</audioPolicyConfiguration>
三个核心概念:
mixPort:对应 AudioFlinger 中的一个PlaybackThread,是软件端口devicePort:对应实际的物理硬件设备(扬声器、耳机插孔、蓝牙等)route:定义哪些mixPort的音频可以路由到哪个devicePort
厂商在适配新硬件时,主要工作就是修改这个配置文件,添加新的 devicePort 并配置相应的 route。
3.3 引擎配置:策略规则定义
与拓扑配置不同,audio_policy_engine_configuration.xml 定义的是业务规则:
xml
<!-- 定义 product strategy(产品策略) -->
<ProductStrategies>
<ProductStrategy name="STRATEGY_MEDIA">
<AttributesGroup streamType="AUDIO_STREAM_MUSIC" volumeGroup="music">
<Attributes usage="AUDIO_USAGE_MEDIA"/>
<Attributes usage="AUDIO_USAGE_GAME"/>
</AttributesGroup>
<!-- 设备优先级列表:按优先级从高到低 -->
<DeviceConfiguration>
<Device type="AUDIO_DEVICE_OUT_WIRED_HEADPHONE"/>
<Device type="AUDIO_DEVICE_OUT_WIRED_HEADSET"/>
<Device type="AUDIO_DEVICE_OUT_BLUETOOTH_A2DP"/>
<Device type="AUDIO_DEVICE_OUT_USB_DEVICE"/>
<Device type="AUDIO_DEVICE_OUT_SPEAKER"/>
</DeviceConfiguration>
</ProductStrategy>
<ProductStrategy name="STRATEGY_PHONE">
<AttributesGroup streamType="AUDIO_STREAM_VOICE_CALL">
<Attributes usage="AUDIO_USAGE_VOICE_COMMUNICATION"/>
</AttributesGroup>
<DeviceConfiguration>
<!-- 通话场景:蓝牙免提 > 有线耳机 > 听筒 -->
<Device type="AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET"/>
<Device type="AUDIO_DEVICE_OUT_WIRED_HEADSET"/>
<Device type="AUDIO_DEVICE_OUT_EARPIECE"/>
</DeviceConfiguration>
</ProductStrategy>
</ProductStrategies>
这就是为什么"插上有线耳机音乐会自动切换到耳机"------因为 STRATEGY_MEDIA 的设备优先级列表里,有线耳机排在扬声器前面。
四、Stream 类型与策略映射
4.1 Legacy Stream 与 AudioAttributes
Android 早期只有简单的 stream type:STREAM_MUSIC、STREAM_RING、STREAM_ALARM 等。随着场景增多,Android 5.0 引入了 AudioAttributes,提供更精细的语义描述。
两者之间存在明确的映射关系:
| Stream Type | AudioAttributes Usage | 典型场景 |
|---|---|---|
STREAM_MUSIC |
USAGE_MEDIA / USAGE_GAME |
音乐播放、游戏音效 |
STREAM_VOICE_CALL |
USAGE_VOICE_COMMUNICATION |
电话通话 |
STREAM_RING |
USAGE_NOTIFICATION_RINGTONE |
来电铃声 |
STREAM_ALARM |
USAGE_ALARM |
闹钟 |
STREAM_NOTIFICATION |
USAGE_NOTIFICATION |
消息通知 |
STREAM_SYSTEM |
USAGE_ASSISTANCE_SONIFICATION |
按键音、触控反馈 |
STREAM_DTMF |
USAGE_VOICE_COMMUNICATION_SIGNALLING |
拨号按键音 |
4.2 策略层的映射链
从应用的 AudioAttributes 到最终设备,经历以下映射:
arduino
AudioAttributes (usage=USAGE_MEDIA)
↓ Engine::getProductStrategyForAttributes()
ProductStrategy (STRATEGY_MEDIA)
↓ Engine::getDevicesForProductStrategy()
DevicePriorityList [HEADPHONE > HEADSET > BT_A2DP > USB > SPEAKER]
↓ 过滤当前可用设备
FinalDevice (DEVICE_OUT_WIRED_HEADSET) // 例:有线耳机已插入
关键源码在 EngineBase::getDevicesForProductStrategy():
cpp
// frameworks/av/services/audiopolicy/enginedefault/src/EngineBase.cpp
DeviceVector EngineBase::getDevicesForProductStrategy(
product_strategy_t strategy) const
{
const auto& strategyDevices = getProductStrategies().getDevicesForProductStrategy(strategy);
// 从优先级列表中过滤出当前可用的设备
DeviceVector availableDevices = getApmObserver()->getAvailableOutputDevices();
for (const auto& device : strategyDevices) {
if (availableDevices.contains(device)) {
return DeviceVector(device); // 返回优先级最高的可用设备
}
}
return DeviceVector();
}
逻辑清晰:遍历优先级列表,返回第一个在当前可用设备中存在的设备。
五、音频路由决策全流程
当应用调用 AudioTrack.Builder().build() 时,内部会触发一次完整的路由决策流程:

5.1 getOutputForAttr():路由决策入口
cpp
// frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
status_t AudioPolicyManager::getOutputForAttr(
const audio_attributes_t *attr,
audio_io_handle_t *output,
audio_session_t session,
audio_stream_type_t *stream,
uid_t uid,
const audio_config_t *config,
audio_output_flags_t *flags,
audio_port_handle_t *selectedDeviceId,
audio_port_handle_t *portId,
std::vector<audio_io_handle_t> *secondaryOutputs,
output_type_t *outputType,
bool *isSpatialized,
bool *isBitPerfect)
{
// 1. AudioAttributes 转 ProductStrategy
result_device_t resultDevice = getDeviceAndMixForAttributes(*attr, uid, ...);
// 2. 通过 Engine 决策输出设备
sp<DeviceDescriptor> outputDevice = resultDevice.device;
// 3. 选择合适的输出流(matchFlags + 设备匹配)
*output = getOutputForDevice(outputDevice, session, *stream,
config, flags, ...);
return NO_ERROR;
}
5.2 Engine 的综合决策
Engine 在决策时考虑多个维度:
cpp
// frameworks/av/services/audiopolicy/enginedefault/src/Engine.cpp
DeviceVector Engine::getOutputDevicesForAttributes(
const audio_attributes_t &attributes,
const sp<DeviceDescriptor> preferredDevice,
bool fromCache) const
{
// 优先使用指定设备(应用层强制路由)
if (preferredDevice != nullptr) {
return DeviceVector(preferredDevice);
}
// 获取产品策略
auto strategy = getProductStrategyForAttributes(attributes);
// IN_CALL 模式:强制走通话路由
if (mPhoneState == AUDIO_MODE_IN_CALL) {
strategy = getProductStrategyForStream(AUDIO_STREAM_VOICE_CALL);
}
return getDevicesForProductStrategy(strategy);
}
三个决策因素的优先级:
- 应用指定设备 (
setPreferredDevice()):优先级最高 - 音频模式(Mode) :
IN_CALL会强制走通话路由 - 策略优先级列表:正常情况下按设备优先级选择
5.3 openOutput:把决策落地
找到目标设备后,APM 调用 getOutputForDevice() 选择或创建对应的输出流:
cpp
audio_io_handle_t AudioPolicyManager::getOutputForDevice(
const sp<DeviceDescriptor>& device,
audio_session_t session,
audio_stream_type_t stream,
const audio_config_t *config,
audio_output_flags_t *flags)
{
// 在已有的输出流中找到可复用的
for (size_t i = 0; i < mOutputs.size(); i++) {
sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);
if (desc->supportedDevices().contains(device) &&
desc->isCompatibleProfile(...)) {
return desc->mIoHandle; // 复用已有输出流
}
}
// 没有合适的,请求 AudioFlinger 打开新输出流
audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
status_t status = mpClientInterface->openOutput(
profile->getModuleHandle(), &output, &outputConfig, device, ...);
return output;
}
六、设备管理:连接、断开与优先级
6.1 设备连接通知
当用户插入有线耳机,内核驱动检测到插入事件,最终通过以下路径通知到 APM:
arduino
内核 jack detection
↓ AudioHAL (setParameters "headset_connect=true")
↓ AudioFlinger
↓ AudioPolicyClient::setAudioPortConfig()
↓ AudioPolicyManager::setDeviceConnectionState()
APM 收到连接通知后做三件事:
cpp
// frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
status_t AudioPolicyManager::setDeviceConnectionState(
const sp<DeviceDescriptor> &device,
audio_policy_dev_state_t state, ...)
{
if (state == AUDIO_POLICY_DEVICE_STATE_AVAILABLE) {
// 1. 更新可用设备列表
mAvailableOutputDevices.add(device);
// 2. 异步通知 AudioFlinger 更新路由
checkOutputsForDevice(device, state, outputs);
// 3. 触发路由重新计算
updateCallAndOutputRouting();
}
// ...
}
6.2 设备断开的 Failover 机制
设备断开时,APM 会自动执行 Failover------找下一个优先级最高的可用设备:
cpp
void AudioPolicyManager::checkOutputsForDevice(
const sp<DeviceDescriptor>& device,
audio_policy_dev_state_t state,
SortedVector<audio_io_handle_t>& outputs)
{
if (state == AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE) {
// 设备断开:关闭使用该设备的输出流,触发重新路由
for (auto output : outputs) {
sp<SwAudioOutputDescriptor> desc = mOutputs.valueFor(output);
if (desc->devices().contains(device)) {
// 切换到下一个优先级设备
DeviceVector newDevices = getNewOutputDevices(desc, false);
if (newDevices != desc->devices()) {
setOutputDevices(desc, newDevices, true);
}
}
}
}
}
这就是"拔出耳机音乐自动切回扬声器"的实现原理------不是魔法,是 Failover。
6.3 BT A2DP 的特殊处理
蓝牙 A2DP 设备的连接/断开比有线设备复杂,因为涉及蓝牙协议栈的异步状态:
cpp
// 蓝牙设备连接时,需要等待 A2DP profile 建立完成
status_t AudioPolicyManager::setDeviceConnectionStateInt(
audio_devices_t deviceType,
audio_policy_dev_state_t state, ...)
{
if (audio_is_a2dp_out_device(deviceType)) {
// A2DP 连接是异步的,通过 BtA2dpDeviceStatus 跟踪状态
if (state == AUDIO_POLICY_DEVICE_STATE_AVAILABLE) {
// 设置延迟生效:等待 A2DP 编解码协商完成
mA2dpSuspended = false;
}
}
// ...
}
蓝牙设备有独立的 A2dpSuspended 状态,在通话中会被挂起(切回听筒/有线耳机),通话结束后自动恢复。
七、音频模式切换
7.1 四种音频模式
Android 定义了四种音频模式,影响系统整体的路由策略:
| Mode | 值 | 触发条件 | 效果 |
|---|---|---|---|
AUDIO_MODE_NORMAL |
0 | 默认状态 | 按各策略优先级路由 |
AUDIO_MODE_RINGTONE |
1 | 来电铃声 | 铃声走 RINGTONE 策略 |
AUDIO_MODE_IN_CALL |
2 | 接通电话 | 强制走 PHONE 策略,启用 EC/NS |
AUDIO_MODE_IN_COMMUNICATION |
3 | VoIP 通话 | 类似 IN_CALL,但走软件路径 |
7.2 setPhoneState() 触发路由重计算
当 TelecomService 检测到通话状态变化时,会调用:
cpp
// frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
void AudioPolicyManager::setPhoneState(audio_mode_t state)
{
audio_mode_t oldState = mPhoneState;
mPhoneState = state;
// 通知 Engine 更新模式
mEngine->setPhoneState(state);
// 进入通话:关闭蓝牙 A2DP,激活 SCO
if (isStateInCall(state)) {
if (!isStateInCall(oldState)) {
// NORMAL -> IN_CALL: 挂起所有媒体输出
for (auto output : mOutputs) {
if (isMediaOutput(output)) {
setStrategyMute(STRATEGY_MEDIA, true, output);
}
}
}
}
// 触发通话输入输出路由更新
updateCallAndOutputRouting();
}
进入 IN_CALL 模式后,即使应用还在播放音乐,STRATEGY_MEDIA 的音频也会被 mute,通话声音独占通话路径。
7.3 EC/NS 的自动启停
进入 IN_CALL 和 IN_COMMUNICATION 模式时,APM 还会自动在输入链路上挂载回声消除(EC)和噪声抑制(NS)效果器,这是通话质量的基本保障。
八、ForceUse:我说了算
8.1 什么是 ForceUse
ForceUse 是一种策略覆盖机制,允许特定场景强制指定使用某类设备。它是系统级功能,普通 App 无法调用(需要 MODIFY_AUDIO_ROUTING 权限)。
cpp
// 强制使用场景定义
enum audio_policy_force_use {
AUDIO_POLICY_FORCE_FOR_COMMUNICATION, // 通话设备
AUDIO_POLICY_FORCE_FOR_MEDIA, // 媒体输出
AUDIO_POLICY_FORCE_FOR_RECORD, // 录音输入
AUDIO_POLICY_FORCE_FOR_DOCK, // 底座
AUDIO_POLICY_FORCE_FOR_SYSTEM, // 系统音效
AUDIO_POLICY_FORCE_FOR_HDMI_SYSTEM_AUDIO, // HDMI
AUDIO_POLICY_FORCE_FOR_ENCODED_SURROUND, // 环绕声
AUDIO_POLICY_FORCE_FOR_VIBRATE_RINGING, // 振动铃声
};
// 强制使用的设备
enum audio_policy_forced_cfg {
AUDIO_POLICY_FORCE_NONE, // 不强制(默认)
AUDIO_POLICY_FORCE_SPEAKER, // 强制扬声器
AUDIO_POLICY_FORCE_HEADPHONES, // 强制耳机
AUDIO_POLICY_FORCE_BT_SCO, // 强制蓝牙通话
AUDIO_POLICY_FORCE_BT_A2DP, // 强制蓝牙音乐
AUDIO_POLICY_FORCE_WIRED_ACCESSORY, // 强制有线设备
AUDIO_POLICY_FORCE_BT_CAR_DOCK, // 强制车载底座
AUDIO_POLICY_FORCE_DESK_DOCK, // 强制桌面底座
AUDIO_POLICY_FORCE_ANALOG_DOCK, // 强制模拟底座
AUDIO_POLICY_FORCE_DIGITAL_DOCK, // 强制数字底座
AUDIO_POLICY_FORCE_NO_BT_A2DP, // 禁用蓝牙A2DP
AUDIO_POLICY_FORCE_SYSTEM_ENFORCED, // 系统强制
AUDIO_POLICY_FORCE_HDMI_SYSTEM_AUDIO_ENFORCED, // HDMI强制
AUDIO_POLICY_FORCE_ENCODED_SURROUND_NEVER, // 禁用环绕声
AUDIO_POLICY_FORCE_ENCODED_SURROUND_ALWAYS, // 强制环绕声
AUDIO_POLICY_FORCE_ENCODED_SURROUND_MANUAL, // 手动设置环绕声
};
8.2 典型场景:免提电话
"免提"按钮的实现就是 ForceUse:
java
// frameworks/base/media/java/android/media/AudioManager.java
public void setSpeakerphoneOn(boolean on) {
final IAudioService service = getService();
service.setForceUse(
AudioSystem.FOR_COMMUNICATION,
on ? AudioSystem.FORCE_SPEAKER : AudioSystem.FORCE_NONE);
}
这个调用会传递到 APS,修改 AUDIO_POLICY_FORCE_FOR_COMMUNICATION 对应的强制配置。Engine 在下次路由决策时,会检查 ForceUse 配置,在通话场景强制选择扬声器。
8.3 典型场景:禁用蓝牙 A2DP
当用户在开发者选项中"禁用绝对音量"时,实际上触发的是:
java
// 禁用 BT A2DP 自动切换
AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NO_BT_A2DP);
这会让 Engine 在媒体路由决策时跳过所有蓝牙 A2DP 设备,即使蓝牙耳机已连接也不会自动切换。
九、Android 15 的新变化
9.1 AIDL 全面替代 HIDL
Android 15 完成了音频 HAL 接口从 HIDL(HAL Interface Definition Language)到 AIDL 的迁移。这是一个影响深远的底层变化:
HIDL(Android 8-14):
scss
// hardware/interfaces/audio/2.0/IDevice.hal
interface IDevice {
setParameters(vec<ParameterValue> context, vec<ParameterValue> parameters)
generates (Result retval);
};
AIDL(Android 15+):
csharp
// hardware/interfaces/audio/aidl/android/hardware/audio/core/IModule.aidl
interface IModule {
AudioPatch setAudioPatch(in AudioPatch requested);
void resetAudioPatch(int patchId);
void setAudioPortConfig(in AudioPortConfig requested);
}
AIDL 接口更加类型安全,支持并行处理,且与 Java/Kotlin 集成更好。
9.2 音量组(Volume Group)的增强
Android 15 在 Volume Group 管理上做了重要改进。Volume Group 是一组共享相同音量策略的音频流:
xml
<!-- audio_policy_engine_configuration.xml Android 15 新增字段 -->
<VolumeGroups>
<VolumeGroup name="music" indexMin="0" indexMax="15">
<Volume deviceCategory="DEVICE_CATEGORY_SPEAKER" ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
<Volume deviceCategory="DEVICE_CATEGORY_HEADSET" ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
</VolumeGroup>
<!-- Android 15 新增:空间音频音量组 -->
<VolumeGroup name="spatialized" indexMin="0" indexMax="15">
<Volume deviceCategory="DEVICE_CATEGORY_HEADSET" ref="SPATIALIZED_VOLUME_CURVE"/>
</VolumeGroup>
</VolumeGroups>
9.3 空间音频路由策略
Android 15 增加了对空间音频(Spatialized Audio)的原生路由支持:
cpp
// frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
bool AudioPolicyManager::canBeSpatialized(
const audio_attributes_t *attr,
const audio_config_t *config,
const AudioDeviceTypeAddrVector &devices)
{
// 仅支持头戴式设备(耳机)的空间化
for (const auto& device : devices) {
if (!isHeadTrackingSupportedForDevice(device)) {
return false;
}
}
// 检查音频格式支持(需要立体声以上)
if (config->channel_mask != AUDIO_CHANNEL_OUT_STEREO &&
!audio_is_channel_mask_spatialized(config->channel_mask)) {
return false;
}
return mSpatializerOutput != AUDIO_IO_HANDLE_NONE;
}
空间音频输出走独立的 SpatializerOutput,避免与普通输出混合时精度损失。
9.4 AudioRecord 权限策略的变化
Android 15 加强了麦克风访问控制,APS 在 getInputForAttr() 中增加了更严格的权限检查:
cpp
// Android 15: 新增敏感麦克风使用审计
status_t AudioPolicyManager::getInputForAttr(
const audio_attributes_t *attr,
audio_io_handle_t *input, ...)
{
// 后台应用使用麦克风需要额外权限(Android 14已有)
// Android 15新增:记录精确的麦克风使用时间戳用于隐私报告
if (mAudioPolicyMix.is_background_capture(uid)) {
halInputSource = AUDIO_SOURCE_REMOTE_SUBMIX;
}
// ...
}
十、实战案例
10.1 案例一:监听设备连接状态变化
App 如何知道用户插入或拔出了耳机?
kotlin
class AudioDeviceListener(private val context: Context) {
private val audioManager = context.getSystemService(AudioManager::class.java)
// Android 6+ 推荐方式:AudioDeviceCallback
private val deviceCallback = object : AudioManager.AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) {
for (device in addedDevices) {
when (device.type) {
AudioDeviceInfo.TYPE_WIRED_HEADSET,
AudioDeviceInfo.TYPE_WIRED_HEADPHONES -> {
Log.d("Audio", "有线耳机已连接: ${device.productName}")
// 此时路由已自动切换,无需手动操作
}
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP -> {
Log.d("Audio", "蓝牙耳机已连接: ${device.productName}")
}
}
}
}
override fun onAudioDevicesRemoved(removedDevices: Array<AudioDeviceInfo>) {
for (device in removedDevices) {
Log.d("Audio", "设备已断开: ${device.productName} (type=${device.type})")
}
}
}
fun startListening() {
audioManager.registerAudioDeviceCallback(deviceCallback, Handler(Looper.getMainLooper()))
}
fun stopListening() {
audioManager.unregisterAudioDeviceCallback(deviceCallback)
}
}
10.2 案例二:指定音频输出设备
Android 6.0 引入了 setPreferredDevice() API,允许 App 指定输出到特定设备:
kotlin
class PreferredDevicePlayer(private val context: Context) {
private val audioManager = context.getSystemService(AudioManager::class.java)
private var audioTrack: AudioTrack? = null
fun playOnSpeaker(audioData: ByteArray, sampleRate: Int) {
// 找到扬声器设备
val speakerDevice = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
.firstOrNull { it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER }
?: run {
Log.e("Audio", "扬声器不可用")
return
}
val minBufferSize = AudioTrack.getMinBufferSize(
sampleRate,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT
)
audioTrack = AudioTrack.Builder()
.setAudioAttributes(AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.build())
.setAudioFormat(AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(sampleRate)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build())
.setBufferSizeInBytes(minBufferSize * 4)
.setTransferMode(AudioTrack.MODE_STREAM)
.build()
// 强制指定输出设备:告知 APS 走这个设备
// 注意:这会绕过设备优先级策略
audioTrack?.preferredDevice = speakerDevice
audioTrack?.play()
audioTrack?.write(audioData, 0, audioData.size)
}
fun clearPreferredDevice() {
// 清除后恢复 APS 的自动路由策略
audioTrack?.preferredDevice = null
}
fun release() {
audioTrack?.stop()
audioTrack?.release()
audioTrack = null
}
}
注意 :setPreferredDevice() 是 App 层的设备偏好,优先级最高,会覆盖 APS 的自动路由决策。慎用------用户可能并不希望你的 App 劫持音频设备。
10.3 案例三:检测当前音频路由
kotlin
fun getCurrentOutputDevice(audioManager: AudioManager): AudioDeviceInfo? {
// 方法一:通过 AudioTrack 查询(Android 10+)
// 创建一个临时 AudioTrack 来查询活跃设备
val format = AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(44100)
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build()
val track = AudioTrack.Builder()
.setAudioAttributes(AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.build())
.setAudioFormat(format)
.setBufferSizeInBytes(4096)
.build()
// routedDevice 反映 APS 的路由决策结果
val routedDevice = track.routedDevice
track.release()
return routedDevice
}
// 更简单的方式:监听路由变更
fun listenForRouteChanges(track: AudioTrack) {
track.addOnRoutingChangedListener({ audioTrack ->
val newDevice = audioTrack.routedDevice
Log.d("Audio", "路由变更 → ${newDevice?.productName} (type=${newDevice?.type})")
}, Handler(Looper.getMainLooper()))
}
十一、调试技巧
11.1 dumpsys 查看策略状态
dumpsys media.audio_policy 是 APS 调试的瑞士军刀,输出内容极其丰富:
bash
# 查看完整策略状态
adb shell dumpsys media.audio_policy
# 关键输出节选:
#
# AudioPolicyManager state:
# Audio Hw Modules:
# - primary (2.0): 5 Outputs, 2 Inputs
# Output: 0x8 Primary output flags: 0x10000(deep buffer)
# Devices: AUDIO_DEVICE_OUT_SPEAKER
# ...
# Available input devices:
# - type:0x80000004 name:builtin_mic id:1 handle:0
#
# Phone state: NORMAL
# Force use for COMMUNICATION: NONE
# Force use for MEDIA: NONE
#
# Outputs:
# - 8 [MixerThread] DEVICE: AUDIO_DEVICE_OUT_SPEAKER
# Profiles: PCM 16 Bits, 48000 Hz, stereo
重点关注:
Phone state:当前音频模式Force use for XXX:ForceUse 状态Outputs/Inputs:活跃的输入输出流
11.2 追踪路由决策日志
bash
# 开启 APM 详细日志
adb shell setprop log.tag.APM_AudioPolicyManager V
adb shell setprop log.tag.AudioPolicyService V
# 过滤关键日志
adb logcat -s APM_AudioPolicyManager:V AudioPolicyService:V \
| grep -E "getOutputForAttr|setDeviceConnection|setPhoneState"
典型的路由决策日志:
makefile
APM_AudioPolicyManager: getOutputForAttr() usage 1, format 1, channelMask 3
APM_AudioPolicyManager: getOutputForDevice() strategy 0 device 0x2 flags 0x0
APM_AudioPolicyManager: Found output 8 for device AUDIO_DEVICE_OUT_WIRED_HEADSET
11.3 验证设备优先级
bash
# 查看当前可用的输出设备
adb shell dumpsys media.audio_policy | grep "Available output"
# 手动触发设备连接(测试用)
adb shell media volume --set 10 --stream 3
# 查看音频焦点状态
adb shell dumpsys audio | grep -A5 "Audio Focus"
11.4 常见路由问题诊断
问题 1:插入耳机后声音仍从扬声器出来
bash
# 检查内核是否检测到耳机插入
adb shell cat /sys/class/switch/h2w/state
# 0 = 未插入, 1 = 有耳机, 2 = 有耳机+麦克风
# 检查 APM 是否收到连接通知
adb logcat | grep "setDeviceConnectionState.*HEADSET.*AVAILABLE"
问题 2:通话音质差(回声、噪声)
bash
# 检查是否正确进入 IN_CALL 模式
adb shell dumpsys media.audio_policy | grep "Phone state"
# 预期: Phone state: IN_CALL
# 检查 EC/NS 是否启用
adb shell dumpsys media.audio_policy | grep "Effect"
问题 3:蓝牙音频延迟高
bash
# 检查蓝牙编解码格式
adb shell dumpsys bluetooth_manager | grep "A2dp"
# 查看是否使用了低延迟编解码(aptX LL, LDAC low latency等)
十二、总结
本篇深入解析了 AudioPolicyService 的工作原理:
| 核心概念 | 说明 |
|---|---|
| 职责分离 | APS 做策略决策,AF 做音频处理,两者通过 IoHandle 协作 |
| 配置驱动 | audio_policy_configuration.xml 定义拓扑,engine_configuration.xml 定义策略规则 |
| 优先级链 | 应用指定 > 音频模式 > ForceUse > 设备优先级列表 |
| 自动 Failover | 设备断开时自动切换到下一优先级设备 |
| Android 15 升级 | AIDL 替代 HIDL,空间音频原生支持,更严格权限控制 |
理解了 AudioPolicyService,你就理解了 Android 音频路由的"大脑"。下一篇我们将深入音频焦点管理------当多个 App 同时要播放音频时,谁有权发出声音?Android 的焦点仲裁机制又是如何工作的?敬请期待。
踩坑提示 :
setPreferredDevice()是一把双刃剑。用它可以精确控制音频路由,但一旦指定了设备,APS 的自动路由逻辑就不再起作用。笔者曾见过一个 Bug:App 指定了扬声器输出,用户接电话时却发现所有声音都跑到扬声器里------因为开发者忘记在onAudioFocusChange()中清除 preferredDevice。记住:用完要清理,audioTrack.preferredDevice = null。