2、AudioPolicy基础:AudioPolicy的作用、音频策略文件解析、音频输出设备与输入设备

好,咱们正式开始聊 AudioPolicy。说实话,很多做车载音频的工程师,干了三五年都没搞明白 AudioPolicy 到底在管什么。我当年刚入行时也一样,总觉得这玩意儿就是个配置文件,改改 xml 就完事了。直到有一次,车机死活不出声,我排查了整整两天,最后发现是策略文件里一个优先级写反了。嗯,从那以后,我再也不敢小看它了。

2.1 AudioPolicy 到底在干什么?

简单说,AudioPolicy 就是 Android 音频系统的「交通警察」。它不负责具体怎么播放声音,它只负责一件事:决定声音走哪条路

你想想看,一辆车上有十几个扬声器,还有蓝牙、USB、AUX 输入,甚至还有麦克风阵列。当用户按下播放键,系统怎么知道该把音乐送到哪个设备?当电话进来时,怎么让音乐暂停、让通话走车载麦克风?这些决策,全是 AudioPolicy 干的。

我个人习惯把 AudioPolicy 拆成三个核心职责:

  • 路由决策:哪个音频流该走哪个设备。比如导航提示音走主驾头枕喇叭,音乐走全车喇叭。
  • 策略管理:不同音频流之间的优先级和互斥规则。比如电话优先级高于音乐,来电时音乐自动 duck(压低音量)。
  • 设备管理:系统里有哪些输入输出设备,哪些是当前可用的,哪些是虚拟的。

核心理解:AudioPolicy 不产生声音,它只决定声音去哪。就像铁路调度中心,它不造火车,但它决定火车走哪条轨道。

2.2 音频策略文件解析

策略文件说白了就是一套规则集。Android 原生用的是 audio_policy_configuration.xml,但在车载场景下,我们通常要深度定制。我见过不少项目直接拿手机上的策略文件改改就用,结果车机上的音频路由一塌糊涂。

先看一个典型的策略文件结构:

<!-- audio_policy_configuration.xml 核心结构 -->
<audioPolicyConfiguration version="1.0">
    <!-- 全局配置 -->
    <globalConfiguration speaker_drc_enabled="false"/>

    <!-- 模块定义 -->
    <modules>
        <module name="primary" halVersion="3.0">
            <!-- 设备端口 -->
            <devicePorts>
                <devicePort tagName="Speaker" type="AUDIO_DEVICE_OUT_SPEAKER" role="source"/>
                <devicePort tagName="BT_SCO" type="AUDIO_DEVICE_OUT_BLUETOOTH_SCO" role="source"/>
            </devicePorts>

            <!-- 混音端口 -->
            <mixPorts>
                <mixPort name="primary output" role="sink">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                             samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                </mixPort>
            </mixPorts>

            <!-- 路由 -->
            <routes>
                <route type="mix" sink="primary output"
                       sources="Speaker,BT_SCO"/>
            </routes>
        </module>
    </modules>
</audioPolicyConfiguration>

这里有几个关键点,我重点说一下:

  • devicePorts:定义物理或虚拟设备。注意 role 属性,source 表示输出设备(声音从系统出去),sink 表示输入设备(声音进系统)。
  • mixPorts:这是 AudioFlinger 创建的混音端口,所有音频流最终在这里混合。
  • routes:定义 mixPort 和 devicePort 之间的连接关系。说白了就是「混音后的声音可以送到哪些设备」。

我的经验:在车载项目中,我建议把每个扬声器通道都定义成独立的 devicePort。比如 FL_SpeakerFR_SpeakerRL_SpeakerRR_Speaker。这样路由控制可以精确到每个喇叭,而不是笼统的「Speaker」。

2.3 音频输出设备与输入设备

Android 定义了一大堆设备类型,但车载场景下常用的其实就那几种。我整理了一个表格,方便你对照:

设备类型 宏定义 车载典型用途
内置扬声器 AUDIO_DEVICE_OUT_SPEAKER 全车音响系统
耳机 AUDIO_DEVICE_OUT_WIRED_HEADSET 驾驶员私密通话
蓝牙 SCO AUDIO_DEVICE_OUT_BLUETOOTH_SCO 蓝牙电话
蓝牙 A2DP AUDIO_DEVICE_OUT_BLUETOOTH_A2DP 蓝牙音乐
AUX 输入 AUDIO_DEVICE_OUT_AUX_DIGITAL 外部音源输入
USB 音频 AUDIO_DEVICE_OUT_USB_DEVICE USB 音乐、CarPlay
内置麦克风 AUDIO_DEVICE_IN_BUILTIN_MIC 语音识别、通话
蓝牙 SCO 输入 AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET 蓝牙通话麦克风

这里有个坑,我曾经踩过。在车载项目中,不要直接用 AUDIO_DEVICE_OUT_SPEAKER 代表所有扬声器。为什么?因为策略文件里如果只定义了一个 Speaker 设备,那所有音频流都会混在一起送到同一个输出。你想单独控制导航音量、让后排乘客听音乐、前排听电话?做不到。

避坑指南:我曾经在一个项目中,客户要求「导航提示音只从主驾喇叭出」。我一开始偷懒,只定义了一个 Speaker 设备,结果发现根本没法实现独立路由。最后不得不重写策略文件,把每个喇叭都定义成独立设备,再配合 AudioPolicyManager 的自定义策略才搞定。所以,一开始就规划好设备粒度,后面能省很多事。

2.4 设备优先级与动态切换

AudioPolicy 还有一个重要功能:设备优先级管理。当多个输出设备同时可用时,系统怎么选?

举个例子:用户插上 USB 音乐,同时蓝牙也连着。系统应该把音乐送到 USB 还是蓝牙?这取决于策略文件里定义的优先级。

audio_policy_configuration.xml 中,每个设备可以设置 priority 属性:

<devicePort tagName="USB_Music" type="AUDIO_DEVICE_OUT_USB_DEVICE" role="source">
    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
             samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
    <gains>
        <gain name="gain" mode="joint" minValueMB="-9600" maxValueMB="0" defaultValueMB="0" stepValueMB="100"/>
    </gains>
    <!-- 优先级越高,越优先被选为默认输出 -->
    <priority value="100"/>
</devicePort>

但说实话,车载场景下光靠优先级不够。因为车上有太多「场景化」的需求。比如:

  • 倒车时,雷达提示音必须打断音乐
  • 电话进来时,音乐要 duck 到 20% 音量
  • 语音助手激活时,所有音频都要暂停

这些逻辑,原生 AudioPolicy 搞不定。你需要自己写 AudioPolicyManager 的扩展,或者用 CarAudioService 里的动态路由策略。

我的建议:如果你刚开始做车载音频,先把策略文件里的设备定义和路由关系理清楚。别一上来就想着搞动态路由。基础打牢了,后面加策略只是「增删改查」的事。我曾经带过一个新人,他花了两周时间研究动态路由,结果发现连设备类型都没定义对。嗯,方向错了,越努力越尴尬。

2.5 小结

这一章我们聊了 AudioPolicy 的核心作用、策略文件的结构、以及输入输出设备的定义。说白了,AudioPolicy 就是一套「声音该去哪」的规则。你把它理解成交通规则就好——红灯停绿灯行,但具体怎么开车,那是 AudioFlinger 和 HAL 的事。

下一章,我们会深入 AudioFlinger,看看声音到底是怎么被混音、怎么被送到 HAL 层的。到时候你会发现,策略文件里定义的那些路由关系,最终都会变成 AudioFlinger 里的一个个 Patch。

好,今天就到这儿。有问题欢迎交流。