23、音频策略常见问题:路由不切换、声音从错误设备输出、焦点冲突导致无声、排查思路。

做音频系统这么多年,我遇到过最头疼的问题,往往不是底层驱动挂了,而是“声音跑偏了”。

你明明插了耳机,声音却从喇叭出来。或者 App 播放得好好的,突然就没声了。用户骂的是手机,但背锅的是我们音频工程师。

这一章,我把最常见的几类音频策略问题掰开揉碎讲清楚。每个问题我都踩过坑,你看了能少走弯路。

23.1 路由不切换:插拔耳机后声音还在喇叭

这个问题太经典了。我入行第三年,第一次独立负责项目就栽在这上面。

用户插上耳机,系统没切过去。或者拔掉耳机,声音没回到喇叭。说白了,就是 AudioPolicyManager 没收到正确的设备连接事件。

23.1.1 根因分析

路由不切换,通常就三个原因:

  • 硬件上报问题:耳机插拔中断没触发,或者 JackService 没检测到状态变化。
  • 策略配置缺失audio_policy_configuration.xml 里没定义耳机这个设备,或者路由规则写错了。
  • 动态策略被覆盖:某些 App 通过 setPreferredDevice 强行锁死了输出设备。

我个人的排查习惯:先看 dumpsys audio 里的设备列表。如果耳机插上后,Devices 字段没出现 DEVICE_OUT_WIRED_HEADSET,那就是硬件层的问题。如果出现了但路由没变,那就是策略层的问题。

23.1.2 实战案例

我记得有一次,某款手机插上 Type-C 耳机后,声音死活不从耳机出。查了两天,最后发现是 audio_policy_configuration.xml 里把 Type-C 耳机的 devicePort 类型写成了 DEVICE_OUT_USB_DEVICE,而不是 DEVICE_OUT_USB_HEADSET

你想想看,策略引擎一看“哦,这是个 USB 设备”,就按 USB 音频设备的规则去路由了,根本没走耳机通路。

<!-- 错误配置 -->
<devicePort tagName="USB Headset" type="AUDIO_DEVICE_OUT_USB_DEVICE" role="sink">

<!-- 正确配置 -->
<devicePort tagName="USB Headset" type="AUDIO_DEVICE_OUT_USB_HEADSET" role="sink">

23.2 声音从错误设备输出:蓝牙通话从听筒出来

这个问题比路由不切换更隐蔽。设备确实切换了,但切到了错误的地方。

举个例子:你连了蓝牙耳机,打电话时声音却从手机听筒出来。用户会以为蓝牙没连上,其实连上了,只是策略选错了。

23.2.1 策略优先级搞错了

AudioPolicyManager 在选输出设备时,会按优先级排序。如果 audio_policy_configuration.xml 里把 DEVICE_OUT_EARPIECE 的优先级设得比 DEVICE_OUT_BLUETOOTH_SCO 还高,那通话时就会优先走听筒。

注意:不要只看 devicePortpriority 属性。还要检查 profile 的匹配规则。有些场景下,策略引擎会因为 profile 不匹配而回退到默认设备。

23.2.2 我的排查方法

遇到这种问题,我一般三步走:

  1. 抓 log:看 AudioPolicyManager::getDeviceForStrategy 的调用栈,确认它选了哪个设备。
  2. 查 profile:看当前音频流(比如 AudioSystem.STREAM_VOICE_CALL)匹配的是哪个 profile。
  3. 看动态覆盖:有没有 App 调了 setDeviceConnectionState 或者 setFocusRequestResult 来干扰策略。

我曾经遇到过一个 case,某视频 App 在播放时调了 setPreferredDevice 把输出锁在听筒上。结果用户切到后台打电话,策略引擎发现“听筒被 App 锁了”,就直接用了听筒。这其实不是策略引擎的 bug,是 App 滥用 API 导致的。

23.3 焦点冲突导致无声:两个 App 抢焦点

这个问题最让人抓狂。因为 log 里看不到任何错误,设备也切对了,就是没声音。

说白了,就是音频焦点被别的 App 抢走了。你的 App 还在播放,但系统把它的音量 mute 了。

23.3.1 焦点机制回顾

Android 的音频焦点分几种:AUDIOFOCUS_GAINAUDIOFOCUS_GAIN_TRANSIENTAUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 等等。

当一个 App 请求 AUDIOFOCUS_GAIN 时,系统会通知当前持有焦点的 App 放弃焦点。如果当前 App 没正确处理 onAudioFocusChange 回调,就可能出现“无声”的假象。

避坑指南:我曾经调试过一个音乐 App,用户切到导航 App 后音乐就停了,但导航结束后音乐没恢复。查了半天,发现音乐 App 在收到 AUDIOFOCUS_LOSS 后调了 pause(),但没在导航 App 释放焦点后重新请求焦点。解决方案是在 onAudioFocusChange 里监听 AUDIOFOCUS_GAIN 并恢复播放。

23.3.2 焦点冲突的典型场景

场景 表现 根因
音乐 App + 导航 App 导航播报时音乐无声,播报完音乐不恢复 音乐 App 没正确处理 AUDIOFOCUS_LOSS_TRANSIENT
游戏 + 语音通话 游戏声音突然消失 语音 App 请求了 AUDIOFOCUS_GAIN,游戏被迫放弃焦点
多个媒体 App 同时播放 只有最后一个 App 有声音 系统默认策略是“后一个抢走前一个的焦点”

23.4 排查思路:从现象到根因

遇到音频策略问题,别慌。我总结了一套排查流程,你照着走,90% 的问题都能定位。

23.4.1 第一步:确认现象

先搞清楚“什么场景下出问题”。是插拔耳机?是切换 App?还是特定 App 播放时?

我习惯让测试同学录个视频,把操作步骤和声音表现都拍下来。很多时候,光看文字描述是看不出问题的。

23.4.2 第二步:抓 log 看设备状态

adb shell dumpsys audio 看当前设备列表和路由策略。重点关注:

  • Devices 字段:当前有哪些设备可用
  • Routes 字段:当前路由规则
  • Focus Stack 字段:当前焦点持有者

23.4.3 第三步:复现并抓取 AudioPolicyManager log

adb logcat -s AudioPolicyManager 过滤出策略引擎的日志。看它每次路由决策时选了哪个设备,为什么选它。

我个人习惯:在 AudioPolicyManager::getDeviceForStrategy 里加一行临时 log,打印出所有候选设备及其优先级。这样一眼就能看出策略引擎为什么选错了。

23.4.4 第四步:检查焦点冲突

如果设备路由没问题,但没声音,那八成是焦点问题。用 adb shell dumpsys audioFocus Stack,确认当前焦点在谁手里。

也可以抓 MediaFocusControl 的 log,看焦点请求和释放的时序。

23.4.5 第五步:验证策略配置

最后一步,检查 audio_policy_configuration.xmlaudio_policy_engine_configuration.xml。看设备定义、profile 匹配、路由规则有没有写错。

嗯,这里要注意:有些厂商会自定义策略,比如在 AudioPolicyManager 里加自己的路由逻辑。这种 case 下,光看 xml 是不够的,得看源码。

音频策略问题排查流程 第一步 确认现象 第二步 抓 dumpsys audio 第三步 抓 AudioPolicyManager log 第四步 检查焦点冲突 第五步 验证策略配置 常见问题分类 路由不切换 插拔耳机后未切换 错误设备输出 蓝牙通话从听筒出 焦点冲突 两个 App 抢焦点 配置错误 xml 写错或遗漏 按步骤排查,90% 的问题都能定位到根因

这张图是我自己画的排查流程。你从第一步走到第五步,基本能把问题范围缩小到具体模块。剩下的就是看代码、改配置、验证了。

音频策略的问题,说白了就是“设备、路由、焦点”这三件事没配合好。你只要把这三件事的 log 都看明白了,没有解决不了的问题。

公众号:蓝海资料掘金营,微信deep3321