21、音频调试与日志:AudioFlinger日志系统、dumpsys audio命令详解、关键调试点、常见问题定位
做音频系统开发,说白了就是跟「看不见摸不着的声音」打交道。你没法像调试UI那样一眼看出问题在哪。所以,日志系统和调试工具就是我们的眼睛和耳朵。
这一章,我带你彻底搞懂AudioFlinger的日志体系。包括怎么用dumpsys audio、关键调试点埋在哪、以及我这些年踩过的坑。
21.1 AudioFlinger日志系统架构
AudioFlinger的日志,不是随便打几个ALOGD就完事了。它有一套分层结构。
| 日志级别 | 宏定义 | 典型场景 |
|---|---|---|
| Verbose | ALOGV | 每个buffer的写入/读取 |
| Debug | ALOGD | 设备切换、路由变化 |
| Info | ALOGI | 服务启动、线程创建 |
| Warning | ALOGW | 参数异常、降级处理 |
| Error | ALOGE | 硬件故障、内存分配失败 |
我个人习惯,在调试阶段把ALOGV全打开。但线上版本一定要关掉,否则日志量能把存储撑爆。
AudioFlinger— 主服务日志AudioHAL— 硬件抽象层日志AudioPolicyManager— 策略管理日志AudioTrack/AudioRecord— 客户端日志
你想想看,如果声音卡顿,你第一反应是看哪个Tag?我建议先看AudioFlinger,因为大部分调度问题都出在这里。
21.2 dumpsys audio命令详解
dumpsys audio是调试音频问题的「瑞士军刀」。它能输出整个音频系统的状态快照。
21.2.1 基本用法
adb shell dumpsys audio
这条命令会输出海量信息。我一般会配合grep来过滤关键内容。
21.2.2 关键输出字段解读
| 字段 | 含义 | 调试价值 |
|---|---|---|
| Audio Flinger | AudioFlinger内部状态 | 查看线程数、设备连接状态 |
| Audio Policy | 策略引擎状态 | 查看当前路由、设备优先级 |
| Audio Tracks | 活跃的Track列表 | 查看每个Track的采样率、通道数 |
| Audio Records | 活跃的Record列表 | 查看录音流状态 |
| Effects | 音效模块状态 | 查看是否有音效挂载失败 |
Audio Tracks里有没有standby状态的Track。如果有,说明音频流被挂起了,可能是超时策略导致的。
21.2.3 常用过滤命令
# 只看Track信息
adb shell dumpsys audio | grep -A 20 "Audio Tracks"
# 只看设备信息
adb shell dumpsys audio | grep -A 10 "Devices"
# 只看错误信息
adb shell dumpsys audio | grep -i "error\|fail\|warn"
我曾经遇到一个案子,客户说播放音乐时偶尔有爆音。我通过dumpsys audio发现某个Track的buffer size异常小,只有256帧。正常应该是1024帧。最后定位到是上层应用设置错了参数。
21.3 关键调试点
AudioFlinger的代码里,有几个地方我建议你重点关注。这些是问题的高发区。
21.3.1 线程调度入口
在Threads.cpp中,threadLoop()是核心循环。我一般在这里加日志,记录每次循环的开始时间和结束时间。
// 伪代码示意
void PlaybackThread::threadLoop() {
while (!exitPending()) {
ALOGV("threadLoop start, mStandby=%d", mStandby);
// ... 核心逻辑 ...
ALOGV("threadLoop end, writeTime=%lld", (long long)elapsed);
}
}
21.3.2 Mixer处理路径
混音器是另一个关键点。如果多个音频流同时播放,混音器的工作负载会很高。
ALOGV级别的日志。在AudioMixer.cpp中搜索ALOGV,你会看到很多有用的调试点。
21.3.3 设备切换点
在AudioFlinger.cpp中,setDeviceConnectionState()是设备切换的入口。我建议在这里记录新旧设备的信息。
status_t AudioFlinger::setDeviceConnectionState(...) {
ALOGI("setDeviceConnectionState: device=%s, state=%d",
deviceToString(device), state);
// ... 后续逻辑 ...
}
21.4 常见问题定位
下面是我总结的几个高频问题,以及对应的排查思路。
21.4.1 无声问题
- 先确认硬件通路:用
tinymix查看codec状态 - 再确认软件通路:用
dumpsys audio查看Track是否正常 - 最后看音量:检查
AudioService的音量曲线
我记得有一次,客户反馈蓝牙耳机没声音。我查了半天,最后发现是AudioPolicyManager里蓝牙设备的优先级配置错了,导致路由没切过去。
21.4.2 卡顿/爆音问题
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 周期性卡顿 | buffer underrun | 检查dumpsys audio中的underrun count |
| 随机爆音 | 中断延迟过高 | 检查schedtune和CPU频率 |
| 切换时爆音 | 设备切换时序问题 | 查看setDeviceConnectionState日志 |
21.4.3 延迟过高问题
音频延迟是很多场景的痛点,比如K歌、游戏。
- 检查
AudioTrack的bufferSize,越小延迟越低 - 检查
PlaybackThread的mPeriodSize,这个值决定了每次写入的数据量 - 检查底层HAL的
openOutputStream返回的bufferSize
嗯,这里要注意,有些厂商的HAL实现会强制覆盖buffer size。你上层设得再小也没用。
21.5 知识体系总览
下面这张图,是我梳理的音频调试知识体系。你可以把它当作一个检查清单。
这张图把调试体系分成了五个维度。你遇到问题时,可以对照着看,从哪个维度入手最合适。
好了,这一章的内容就到这里。音频调试是个细活,需要耐心和经验积累。多动手、多打日志,慢慢你就会有感觉了。