11、音频策略调试工具:dumpsys audio命令详解、日志分析技巧(logcat)、策略转储文件解读
调试音频策略,说白了就是跟系统“对账”。你定了一套路由规则,系统到底听没听?听对了没有?
我早年刚接触Android音频时,最头疼的就是策略不生效。明明配置了A2DP优先,结果插上蓝牙耳机,声音还是从扬声器出来。后来我才明白——光靠看代码是没用的,你得学会让系统“开口说话”。
今天要讲的这三个工具,就是让系统开口的手段。dumpsys audio、logcat、还有策略转储文件,它们各有侧重,组合起来用,基本能解决90%的路由问题。
11.1 dumpsys audio:系统的“体检报告”
dumpsys audio 是Android系统提供的一个诊断命令。我个人习惯把它叫做“音频系统的快照”。你执行这条命令时,系统会把当前所有音频相关的状态、设备、策略、客户端信息一股脑儿吐出来。
基本用法很简单:
adb shell dumpsys audio
输出内容很长,但别慌。我把它拆成几个关键区块来讲。
11.1.1 设备列表:看看系统认出了哪些硬件
输出开头部分会列出所有已注册的音频设备。你会看到类似这样的信息:
AudioPolicyManager:
Devices:
- type: 0x2 (AUDIO_DEVICE_OUT_SPEAKER)
address: ""
name: "speaker"
- type: 0x8 (AUDIO_DEVICE_OUT_WIRED_HEADSET)
address: ""
name: "headset"
- type: 0x80 (AUDIO_DEVICE_OUT_BLUETOOTH_A2DP)
address: "00:11:22:33:44:55"
name: "Bluetooth A2DP Sink"
这里要注意的是type字段。它用十六进制表示设备类型。0x2是扬声器,0x8是有线耳机,0x80是蓝牙A2DP。每个设备还有一个address,蓝牙设备会显示MAC地址,USB设备会显示设备路径。
11.1.2 策略规则:系统当前在用什么规则
再往下翻,你会看到策略引擎的当前状态:
AudioPolicyManager:
Engine Config:
mEngineLib: libaudiopolicymanagerdefault.so
mPhoneState: 0 (CALL_STATE_IDLE)
mForceUse:
FOR_COMMUNICATION: 0 (FORCE_NONE)
FOR_MEDIA: 0 (FORCE_NONE)
FOR_RECORD: 0 (FORCE_NONE)
FOR_DOCK: 0 (FORCE_NONE)
mForceUse 是重点。它表示当前有没有强制使用某个设备。比如你插了耳机,但声音还是从扬声器出来,可以看看FOR_MEDIA是不是被设成了FORCE_SPEAKER。如果是,那就是有上层应用强制改了策略。
我曾经遇到一个案例:某视频App播放时声音死活不从蓝牙走。一查dumpsys,发现FOR_MEDIA被设成了FORCE_WIRED_ACCESSORY。后来定位到是App自己调了setForceUse接口。嗯,这种问题最坑人。
11.1.3 客户端列表:谁在占用音频
这部分会列出所有正在使用音频的客户端:
AudioFlinger:
Clients:
pid: 1234
package: com.tencent.qqmusic
tracks:
- stream_type: 3 (MUSIC)
device: 0x80 (A2DP)
flags: 0x100
sample_rate: 48000
channel_mask: 0x3 (STEREO)
这里能看到每个App的音频流类型、输出设备、采样率等信息。如果某个App的声音没按预期路由,先看它的device字段是不是对的。
11.2 logcat日志分析:追踪音频事件的“时间线”
dumpsys 是静态快照,logcat 是动态日志。两者配合,才能还原问题全貌。
音频相关的logcat标签主要有这几个:
| 标签 | 用途 | 过滤命令 |
|---|---|---|
| AudioPolicyManager | 策略引擎决策日志 | adb logcat -s AudioPolicyManager |
| AudioFlinger | 音频混音和输出日志 | adb logcat -s AudioFlinger |
| AudioService | 上层服务调度日志 | adb logcat -s AudioService |
| audio_hw_primary | 硬件抽象层日志 | adb logcat -s audio_hw_primary |
我个人习惯这样抓:
adb logcat -c # 先清空旧日志
adb logcat -v time -s AudioPolicyManager AudioFlinger AudioService > audio_log.txt
然后复现问题。等复现完了,按Ctrl+C停止抓取。打开audio_log.txt,按时间线看事件顺序。
11.2.1 关键日志模式:设备插拔
插拔耳机时,你会看到这样的日志:
01-01 12:00:01.123 AudioPolicyManager: setDeviceConnectionState(0x8, CONNECTED)
01-01 12:00:01.124 AudioPolicyManager: checkOutputsForDevice(0x8)
01-01 12:00:01.125 AudioPolicyManager: switchOutputDevice(0x2 -> 0x8)
这三行日志说明:系统检测到耳机插入(0x8),检查了哪些输出可以用,然后把输出设备从扬声器(0x2)切换到了耳机(0x8)。
如果只看到前两行,没有第三行,说明策略引擎认为不需要切换。这时候就要去查策略配置文件了。
11.2.2 避坑指南:日志级别
我曾经被坑过一次:某台设备上蓝牙连上后声音没切过去。抓了logcat,发现AudioPolicyManager的日志很少,根本看不出决策过程。后来才发现,那台设备的系统把日志级别设成了WARN,很多DEBUG级别的日志被过滤掉了。
解决办法:
adb shell setprop persist.log.tag.AudioPolicyManager DEBUG
adb reboot
重启后,所有策略决策的细节日志就都出来了。
11.3 策略转储文件:配置的“源代码”
dumpsys 和 logcat 看的是运行时状态。但有时候,问题出在配置本身。这时候就需要看策略转储文件。
Android 10之后,策略配置通常放在这里:
/vendor/etc/audio_policy_configuration.xml
/vendor/etc/audio_policy_engine_configuration.xml
你可以用adb pull拉下来看:
adb pull /vendor/etc/audio_policy_configuration.xml .
11.3.1 配置文件结构
打开这个XML文件,你会看到这样的结构:
<audioPolicyConfiguration version="1.0">
<globalConfiguration speaker_drc_enabled="false"/>
<modules>
<module name="primary" halVersion="3.0">
<attachedDevices>
<item>Speaker</item>
<item>Wired Headset</item>
</attachedDevices>
<devicePorts>
<devicePort tagName="Speaker" type="AUDIO_DEVICE_OUT_SPEAKER" role="source">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</devicePort>
</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,Wired Headset"/>
</routes>
</module>
</modules>
</audioPolicyConfiguration>
这里的关键是routes部分。它定义了mixPort和devicePort之间的连接关系。如果某个设备没有出现在routes里,那策略引擎永远不会把音频路由到它。
- 设备是否在attachedDevices里?不在的话,系统不会主动识别它。
- 设备是否在routes里?不在的话,策略引擎不会路由到它。
- 采样率和格式是否匹配?不匹配的话,可能无声或声音异常。
11.3.2 策略引擎配置
另一个文件 audio_policy_engine_configuration.xml 定义了更细粒度的路由规则:
<policyEngineConfiguration>
<engineSuffix>default</engineSuffix>
<criterionTypes>
<criterionType name="Usage">
<criterion name="USAGE_MEDIA" value="1"/>
<criterion name="USAGE_VOICE_COMMUNICATION" value="2"/>
</criterionType>
</criterionTypes>
<policy>
<rules>
<rule>
<condition>Usage == USAGE_MEDIA</condition>
<output>A2DP, Speaker, Wired Headset</output>
</rule>
</rules>
</policy>
</policyEngineConfiguration>
这个文件定义了“当Usage是媒体时,优先使用A2DP,其次Speaker,最后Wired Headset”。如果这个规则写错了,比如把A2DP漏掉了,那蓝牙耳机就永远不会有声音。
11.4 实战:一个完整的调试流程
假设你遇到一个问题:插上蓝牙耳机,音乐App的声音还是从扬声器出来。我会这样排查:
- 第一步:dumpsys audio,看蓝牙设备是否在设备列表里。如果不在,查蓝牙连接和HAL层。
- 第二步:dumpsys audio,看mForceUse有没有被强制设为扬声器。如果有,查上层应用。
- 第三步:抓logcat,看插拔蓝牙时有没有switchOutputDevice的日志。如果没有,查策略配置。
- 第四步:拉取audio_policy_configuration.xml,看蓝牙设备是否在routes里。
- 第五步:拉取audio_policy_engine_configuration.xml,看媒体路由规则是否包含A2DP。
按这个顺序走,99%的问题都能定位到根因。
dumpsys audio | grep -A 5 "Devices:" 只看设备列表。这样比翻几百行输出快得多。
11.5 知识体系图
下面这张图总结了本章的核心逻辑:三个工具分别覆盖了调试的三个层面。
这三个工具就像医生的听诊器、CT机和化验单。dumpsys audio 告诉你“现在怎么样”,logcat 告诉你“刚才发生了什么”,策略转储文件告诉你“应该是什么样”。三者对照,问题无所遁形。
好了,这一章的内容就到这里。记住我说的:调试音频策略,别靠猜,让数据说话。
公众号:蓝海资料掘金营,微信deep3321