12、音频策略定制实战:修改默认路由策略、添加自定义音频设备、适配特殊硬件场景。
好,咱们终于到了动手环节。前面讲了那么多策略文件的结构、路由规则、设备枚举,说实话,光看不练容易忘。今天这一章,我就带大家走一遍真正的定制流程。我会用三个实战场景,把修改默认路由、添加自定义设备、适配特殊硬件这几个核心操作串起来。
嗯,先说说我个人的习惯。每次拿到一个新平台,我第一件事不是看代码,而是先拉一份完整的 audio_policy_configuration.xml 出来,然后用我写的一个小脚本把它可视化。你想想看,几百行的 XML,光靠肉眼找设备映射关系,太容易漏了。
12.1 修改默认路由策略:让系统优先走你的设备
默认路由策略,说白了就是系统在不知道选谁的时候,会选谁。比如你插上耳机,系统应该切到耳机;拔掉耳机,系统应该切回扬声器。这个逻辑在 audio_policy_configuration.xml 里是通过 route 标签和 mixPort 的 profile 来控制的。
我遇到过一个问题:某款平板电脑,外接了一个 USB-C 转 3.5mm 的转接头,系统死活不认。插上去之后,声音还是从扬声器出来。后来一查,是默认路由里 USB 设备的优先级比扬声器低。
怎么改?看这个例子:
<!-- 修改前:USB 设备排在扬声器后面 -->
<route type="mix" sink="speaker">
<source address="0">usb_device</source>
</route>
<!-- 修改后:把 USB 设备提到前面,并增加一个专属路由 -->
<route type="mix" sink="usb_headset">
<source address="0">usb_device</source>
<source address="1">speaker</source>
</route>
这里的关键是 sink 和 source 的对应关系。系统在路由选择时,会按照 route 列表的顺序去匹配。你把 USB 设备的路由写在前面,系统就会优先尝试它。
audio_policy_configuration.xml 的 globalConfiguration 里设置 defaultOutputDevice。比如 <globalConfiguration defaultOutputDevice="speaker"/>。
12.2 添加自定义音频设备:让系统认识你的硬件
有时候,硬件团队会搞一些“非标”设备。比如一个通过 SPI 接口连接的麦克风阵列,或者一个定制的 DSP 音频处理模块。这些设备在标准的 ALSA 驱动里可能没有对应的 card 和 device 号,所以系统根本不知道它们的存在。
这时候,你就得手动在策略文件里“注册”它们。我建议分三步走:
- 定义设备描述:在
audio_policy_configuration.xml的devicePorts里添加一个新设备。 - 定义混音端口:在
mixPorts里添加对应的 HAL 接口。 - 建立路由:把设备和混音端口用
route连起来。
举个例子,假设我们要添加一个名为 custom_mic_array 的输入设备:
<!-- 第一步:定义设备端口 -->
<devicePort tagName="Custom Mic Array" type="AUDIO_DEVICE_IN_BUILTIN_MIC" role="source">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
</devicePort>
<!-- 第二步:定义混音端口 -->
<mixPort name="custom_mic input" role="sink">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
</mixPort>
<!-- 第三步:建立路由 -->
<route type="mix" sink="custom_mic input">
<source address="0">Custom Mic Array</source>
</route>
嗯,这里要注意一点。设备端口的 type 字段,必须用系统已经定义的 AUDIO_DEVICE_* 常量。你不能自己发明一个 AUDIO_DEVICE_IN_CUSTOM,因为 HAL 层不认识它。我一般会找一个功能最接近的现有类型,比如麦克风就用 AUDIO_DEVICE_IN_BUILTIN_MIC。
adev_get_device_address 函数。否则系统虽然知道有这个设备,但不知道去哪里找它的驱动。我曾经因为这个疏忽,浪费了整整两天调试时间。
12.3 适配特殊硬件场景:一个真实的案例
我记得有一次,我们做一款带 AI 降噪功能的会议音箱。硬件上有一个独立的 DSP 芯片,专门处理麦克风阵列的波束成形。这个 DSP 在 Linux 下被识别为一个单独的声卡,但它的采样率是 16kHz,而主声卡是 48kHz。
问题来了:系统默认会把所有音频流都路由到主声卡,但降噪功能必须走 DSP 声卡。而且,两个声卡之间还要做重采样。
我的解决方案是这样的:
- 创建两个独立的音频策略模块:一个给主声卡,一个给 DSP 声卡。
- 在策略文件里定义两个
audioPolicyConfig,分别对应不同的mixPorts。 - 用
globalConfiguration里的deviceCategory来区分场景:当检测到“会议模式”时,强制路由到 DSP 模块。
核心代码片段如下:
<!-- 定义两个策略模块 -->
<audioPolicyConfig version="1.0">
<!-- 主声卡模块 -->
<module name="primary" halVersion="2.0">
<attachedDevices>
<item>Speaker</item>
<item>Built-In Mic</item>
</attachedDevices>
<!-- ... 其他配置 ... -->
</module>
<!-- DSP 声卡模块 -->
<module name="dsp" halVersion="2.0">
<attachedDevices>
<item>DSP Mic Array</item>
</attachedDevices>
<!-- 注意:这里设置采样率为 16kHz -->
<mixPort name="dsp input" role="sink">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="16000" channelMasks="AUDIO_CHANNEL_IN_MONO"/>
</mixPort>
</module>
</audioPolicyConfig>
然后,在 Java 层的 AudioManager 里,我写了一个监听器,当应用设置音频属性为 AudioAttributes.USAGE_VOICE_COMMUNICATION 时,动态调用 setPreferredDeviceForStrategy 来切换路由。
12.4 知识体系图:音频策略定制的核心逻辑
下面这张图,是我自己总结的音频策略定制流程。每次做新项目,我都会对着它过一遍,确保没有遗漏。
12.5 避坑指南:我踩过的几个坑
最后,分享几个实战中容易出问题的地方。这些都是我亲身经历过的,希望能帮你少走弯路。
- 设备地址写错:
devicePorts里的address字段,必须和 HAL 层adev_get_device_address返回的值完全一致。大小写、空格都不能错。我曾经因为一个下划线写成了连字符,查了三个小时。 - 采样率不匹配:如果自定义设备的采样率和系统默认的不一样,一定要在
mixPort的profile里明确指定。否则系统会用默认的 48kHz 去驱动你的 16kHz 设备,出来的声音全是噪声。 - 忘记重启 audioserver:修改策略文件后,只重启应用是不够的。必须
adb shell restart audioserver或者直接重启设备。嗯,这个我犯过不止一次。 - 路由冲突:如果你定义了多个
route指向同一个sink,系统会按照文件里的顺序选择第一个。但如果你在运行时又通过 API 设置了setPreferredDevice,那就会产生冲突。我的建议是:要么全用静态路由,要么全用动态 API,不要混用。
adb shell dumpsys media.audio_policy 可以实时查看当前的路由状态和设备列表。我每次改完策略文件,第一件事就是跑这个命令,确认设备已经被正确加载。
好了,这一章的内容就到这里。音频策略定制,说白了就是“定义设备、建立路由、动态切换”这三板斧。但每一板斧里都有很多细节,需要你在实际项目中慢慢体会。记住,不要怕改错,改错了大不了重启 audioserver 嘛。