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_Speaker、FR_Speaker、RL_Speaker、RR_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。
好,今天就到这儿。有问题欢迎交流。