24、常见问题排查:声音卡顿/断流、回声与啸叫、录制与播放不同步
做音频底层优化这么多年,我遇到过最多的问题,其实不是那些高大上的算法调优,反而是这几个“老面孔”:声音卡顿、回声啸叫、录播不同步。你想想看,用户感知最强烈的,往往就是这些基础体验问题。
今天我就把这三大类问题的排查思路,掰开揉碎了讲给你听。每个问题我都会结合项目中的真实案例,帮你建立一套自己的排查“肌肉记忆”。
核心观点: 90%的音频问题,根源都在于“时序”和“数据量”的错配。抓住这两个点,你就抓住了排查的命门。
24.1 声音卡顿与断流
声音卡顿,说白了就是音频数据“断供”了。要么是生产者(录音端)没及时产出,要么是消费者(播放端)没及时消费。
24.1.1 卡顿的典型场景与根因
- 场景一:播放卡顿,像老唱片跳针
我排查过一个案例,播放器每隔几秒就“咯噔”一下。最后发现是音频Track的buffer size设得太小,而CPU负载一高,写数据的速度跟不上消费速度。
排查技巧: 用dumpsys media.audio_flinger查看每个Track的underrun count。如果这个值在持续增长,那就是典型的buffer underrun。 - 场景二:录制卡顿,录出来的声音一断一断
这通常是录音端的callback被阻塞了。我记得有一次,是因为录音线程里做了耗时的文件I/O操作,导致AudioRecord的read()返回不及时。
避坑指南: 我曾经在录音回调里直接写文件,结果声音断得没法听。记住:录音/播放的回调里,只做数据搬运,别做任何阻塞操作。 - 场景三:蓝牙耳机断流
蓝牙A2DP的卡顿,多半是射频干扰或者codec编码速度跟不上。你可以先切到SBC编码试试,如果问题消失,那就是codec的锅。
24.1.2 排查工具与命令
| 问题类型 | 排查命令/工具 | 关键指标 |
|---|---|---|
| 播放卡顿 | dumpsys media.audio_flinger |
underrun count, frame count |
| 录制卡顿 | dumpsys media.audio_record |
overrun count, callback time |
| 系统负载 | top -H -p [pid] |
线程CPU占用率, sched latency |
| 调度延迟 | systrace / perfetto |
wakeup latency, binder call duration |
个人习惯: 我一般先看
dumpsys 的统计信息,如果underrun为0,那问题大概率不在AudioFlinger内部,而是上层APP或者HAL层的问题。
24.2 回声与啸叫
回声和啸叫,本质上是声学反馈问题。但Android系统里,还有一层“电回声”——也就是数字信号回路。
24.2.1 回声的两种类型
- 声学回声: 扬声器的声音被麦克风重新拾取。这需要AEC(声学回声消除)算法来处理。
排查要点:检查AEC是否开启。在通话中,用adb shell dumpsys media.audio_policy查看当前音频路由,确认麦克风和扬声器是否同时处于活跃状态。 - 电回声: 这个比较隐蔽。我在项目中遇到过,是因为HAL层的loopback reference没配置对。播放的数据没有正确回传给AEC算法,导致算法“睁眼瞎”。
我曾经踩过的坑: 某款平台,AEC的reference信号是从DSP内部拿的,但DSP做了重采样,导致reference和capture信号不同步。结果AEC不但没消掉回声,反而引入了新的噪声。排查了整整两天才定位到。
24.2.2 啸叫的快速定位
啸叫就是正反馈。你想想看,麦克风收声→播放出来→又被麦克风收进去→再放大……循环下去就啸叫了。
排查步骤很简单:
- 先降低音量,看啸叫是否消失。如果消失,说明是声学反馈过强。
- 检查是否开启了“免提”模式。免提模式下,扬声器功率大,更容易啸叫。
- 用
dumpsys media.audio_policy查看当前音频路由,确认没有同时开启“播放”和“录音”到同一个设备。
24.3 录制与播放不同步
这个问题的典型场景是:录了一段视频,回放时发现口型对不上。要么声音比画面快,要么慢。
24.3.1 不同步的根源
说白了,就是音频时钟和视频时钟没对齐。Android里,音频走的是 AudioTrack 的时钟,视频走的是 SurfaceFlinger 的VSync时钟。两个时钟的基准不同,漂移是必然的。
核心指标: 音频的
presentation time 和视频的 timestamp 必须落在同一个时间轴上。Android推荐使用 AudioTimestamp 来获取音频的精确播放位置。
24.3.2 排查与修复
- 检查时间戳来源: 录音端,确保
AudioRecord的getTimestamp()返回的是CLOCK_MONOTONIC时间。不要用System.currentTimeMillis(),那个是wall clock,会被NTP调整。 - 检查buffer大小: 如果音频buffer太大,会导致音频延迟过高,看起来就是声音比画面慢。我建议录音buffer控制在20ms以内,播放buffer控制在40ms以内。
- 使用同步机制: 在APP层,可以用
MediaCodec的InputBuffer和OutputBuffer的presentationTimeUs来对齐音视频。
我建议: 在开发阶段,就打印出每一帧音频和视频的timestamp,画成曲线图。一眼就能看出谁在漂移。这个习惯帮我解决了好几个棘手的同步问题。
24.4 知识体系总览
下面这张图,是我自己总结的音频问题排查框架。你可以把它当成一张“作战地图”。
24.5 综合排查流程
遇到问题别慌,按这个流程走一遍,大部分问题都能定位到:
- 先看现象: 是卡顿?是回声?还是不同步?确定问题大类。
- 抓日志: 用
logcat -b audio抓音频日志,同时用dumpsys抓状态快照。 - 看统计: 检查underrun/overrun计数,看是否在持续增长。
- 看负载: 用
top或systrace看CPU和调度情况。 - 看路由: 确认音频设备切换是否正确,有没有异常的路由策略。
- 看时间戳: 如果是同步问题,打印出每一帧的timestamp,画图分析。
最后提醒一句: 不要一上来就怀疑算法。我见过太多人花了一周调AEC参数,最后发现是麦克风堵了。先排除硬件和驱动层的问题,再动软件。
公众号:蓝海资料掘金营,微信deep3321