Android 15音频子系统(五):AudioPolicyService策略管理深度解析

0 阅读16分钟

一、引言:谁来决定声音该从哪里出来?

假设你正在手机上用耳机听音乐,突然来了一个电话。你接通电话后,声音应该从扬声器还是耳机里出来?听筒还是耳机扬声器?如果用户同时连着蓝牙耳机和有线耳机,又该怎么选?

这些问题看似简单,实则暗藏玄机。在 Android 系统里,负责做这类决策的就是今天的主角——AudioPolicyService(音频策略服务)

前几篇我们介绍了 AudioFlinger 的混音机制——它就像一个全力运转的"音频工厂",负责把音频数据混合、处理后送给硬件。但工厂只管生产,不管销售策略。AudioPolicyService 就是那个"销售总监":它不碰音频数据,专门负责决策音频应该走哪条路

这种职责分离的设计非常优雅:AudioFlinger 关注怎么播放,AudioPolicyService 关注往哪里播放。本篇将深入 AudioPolicyService 的内部,看看 Android 15 是如何处理这些复杂的路由策略的。

二、AudioPolicyService 架构概览

2.1 在系统中的位置

AudioPolicyService(APS)运行在 audioserver 进程中,和 AudioFlinger 是"邻居"。它们都由 audioserver.rc 启动,共享同一进程空间,但通过独立的 Binder 服务对外暴露接口。

// frameworks/av/media/audioserver/main_audioserver.cpp
int main(int argc __unused, char **argv)
{
    // ...
    AudioFlinger::instantiate();        // 音频混音引擎
    AudioPolicyService::instantiate();  // 音频策略服务
    // ...
}

从架构图可以看出 APS 在整个音频系统中的位置:

05-01-audiopolicy-architecture.png

系统中有两个与音频策略相关的服务容易混淆:

  • AudioService(Java 层,运行在 system_server):提供 AudioManager API,是应用访问的门户
  • AudioPolicyService(Native 层,运行在 audioserver):真正执行策略决策的引擎

应用调用 AudioManager.setMode() 时,最终会跨进程 Binder 调用到 APS 的 setPhoneState()

2.2 核心组件层次

APS 内部的层次结构如下:

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 的协作

两者之间的交互简洁而高效:

App                 AudioPolicyService          AudioFlinger
 |                        |                          |
 |-- getOutput() -------> |                          |
 |                   决策路由...                      |
 |                        |---- openOutput() ------> |
 |                        |<--- output handle ------- |
 |<-- IoHandle ---------- |                          |
 |                                                   |
 |-- createTrack(IoHandle) -----------------------> |

APS 只负责决定用哪个输出流(IoHandle),AudioFlinger 负责在这个流上实际创建 Track 并处理音频数据。策略和执行完全解耦。

三、策略配置文件:audio_policy_configuration.xml

3.1 文件位置与加载时机

Android 的音频路由拓扑由 XML 配置文件定义,位置因厂商而异:

# 主配置文件(设备商提供)
/vendor/etc/audio_policy_configuration.xml

# AOSP默认配置(通用参考)
/system/etc/audio_policy_configuration.xml

# 引擎配置(策略规则)
/system/etc/audio_policy_engine_configuration.xml

AudioPolicyManager 初始化时解析这些文件,构建设备拓扑图。

3.2 配置文件结构解析

一个典型的配置文件结构:

<!-- 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 定义的是业务规则

<!-- 定义 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_MUSICSTREAM_RINGSTREAM_ALARM 等。随着场景增多,Android 5.0 引入了 AudioAttributes,提供更精细的语义描述。

两者之间存在明确的映射关系:

Stream TypeAudioAttributes Usage典型场景
STREAM_MUSICUSAGE_MEDIA / USAGE_GAME音乐播放、游戏音效
STREAM_VOICE_CALLUSAGE_VOICE_COMMUNICATION电话通话
STREAM_RINGUSAGE_NOTIFICATION_RINGTONE来电铃声
STREAM_ALARMUSAGE_ALARM闹钟
STREAM_NOTIFICATIONUSAGE_NOTIFICATION消息通知
STREAM_SYSTEMUSAGE_ASSISTANCE_SONIFICATION按键音、触控反馈
STREAM_DTMFUSAGE_VOICE_COMMUNICATION_SIGNALLING拨号按键音

4.2 策略层的映射链

从应用的 AudioAttributes 到最终设备,经历以下映射:

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()

// 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() 时,内部会触发一次完整的路由决策流程:

05-02-audio-routing-flow.png

5.1 getOutputForAttr():路由决策入口

// 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 在决策时考虑多个维度:

// 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);
}

三个决策因素的优先级

  1. 应用指定设备setPreferredDevice()):优先级最高
  2. 音频模式(Mode)IN_CALL 会强制走通话路由
  3. 策略优先级列表:正常情况下按设备优先级选择

5.3 openOutput:把决策落地

找到目标设备后,APM 调用 getOutputForDevice() 选择或创建对应的输出流:

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:

内核 jack detection
    ↓ AudioHAL (setParameters "headset_connect=true")
    ↓ AudioFlinger
    ↓ AudioPolicyClient::setAudioPortConfig()
    ↓ AudioPolicyManager::setDeviceConnectionState()

APM 收到连接通知后做三件事:

// 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——找下一个优先级最高的可用设备:

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 设备的连接/断开比有线设备复杂,因为涉及蓝牙协议栈的异步状态:

// 蓝牙设备连接时,需要等待 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_NORMAL0默认状态按各策略优先级路由
AUDIO_MODE_RINGTONE1来电铃声铃声走 RINGTONE 策略
AUDIO_MODE_IN_CALL2接通电话强制走 PHONE 策略,启用 EC/NS
AUDIO_MODE_IN_COMMUNICATION3VoIP 通话类似 IN_CALL,但走软件路径

7.2 setPhoneState() 触发路由重计算

当 TelecomService 检测到通话状态变化时,会调用:

// 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 权限)。

// 强制使用场景定义
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:

// 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

当用户在开发者选项中"禁用绝对音量"时,实际上触发的是:

// 禁用 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)

// hardware/interfaces/audio/2.0/IDevice.hal
interface IDevice {
    setParameters(vec<ParameterValue> context, vec<ParameterValue> parameters)
        generates (Result retval);
};

AIDL(Android 15+)

// 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 是一组共享相同音量策略的音频流:

<!-- 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)的原生路由支持:

// 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() 中增加了更严格的权限检查:

// 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 如何知道用户插入或拔出了耳机?

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 指定输出到特定设备:

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 案例三:检测当前音频路由

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 调试的瑞士军刀,输出内容极其丰富:

# 查看完整策略状态
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 追踪路由决策日志

# 开启 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"

典型的路由决策日志:

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 验证设备优先级

# 查看当前可用的输出设备
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:插入耳机后声音仍从扬声器出来

# 检查内核是否检测到耳机插入
adb shell cat /sys/class/switch/h2w/state
# 0 = 未插入, 1 = 有耳机, 2 = 有耳机+麦克风

# 检查 APM 是否收到连接通知
adb logcat | grep "setDeviceConnectionState.*HEADSET.*AVAILABLE"

问题 2:通话音质差(回声、噪声)

# 检查是否正确进入 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:蓝牙音频延迟高

# 检查蓝牙编解码格式
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