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 啸叫的快速定位

啸叫就是正反馈。你想想看,麦克风收声→播放出来→又被麦克风收进去→再放大……循环下去就啸叫了。

排查步骤很简单:

  1. 先降低音量,看啸叫是否消失。如果消失,说明是声学反馈过强。
  2. 检查是否开启了“免提”模式。免提模式下,扬声器功率大,更容易啸叫。
  3. dumpsys media.audio_policy 查看当前音频路由,确认没有同时开启“播放”和“录音”到同一个设备。

24.3 录制与播放不同步

这个问题的典型场景是:录了一段视频,回放时发现口型对不上。要么声音比画面快,要么慢。

24.3.1 不同步的根源

说白了,就是音频时钟和视频时钟没对齐。Android里,音频走的是 AudioTrack 的时钟,视频走的是 SurfaceFlinger 的VSync时钟。两个时钟的基准不同,漂移是必然的。

核心指标: 音频的 presentation time 和视频的 timestamp 必须落在同一个时间轴上。Android推荐使用 AudioTimestamp 来获取音频的精确播放位置。

24.3.2 排查与修复

  • 检查时间戳来源: 录音端,确保 AudioRecordgetTimestamp() 返回的是 CLOCK_MONOTONIC 时间。不要用 System.currentTimeMillis(),那个是wall clock,会被NTP调整。
  • 检查buffer大小: 如果音频buffer太大,会导致音频延迟过高,看起来就是声音比画面慢。我建议录音buffer控制在20ms以内,播放buffer控制在40ms以内。
  • 使用同步机制: 在APP层,可以用 MediaCodecInputBufferOutputBufferpresentationTimeUs 来对齐音视频。
我建议: 在开发阶段,就打印出每一帧音频和视频的timestamp,画成曲线图。一眼就能看出谁在漂移。这个习惯帮我解决了好几个棘手的同步问题。

24.4 知识体系总览

下面这张图,是我自己总结的音频问题排查框架。你可以把它当成一张“作战地图”。

音频问题排查知识体系 声音卡顿/断流 回声与啸叫 录播不同步 播放underrun / 录制overrun CPU负载高 / 调度延迟 蓝牙射频干扰 / codec瓶颈 声学回声:AEC算法未生效 电回声:reference信号错位 啸叫:声学正反馈 时钟基准不一致 buffer大小导致延迟偏移 timestamp未对齐 核心排查思路:时序 + 数据量 → 定位根因

24.5 综合排查流程

遇到问题别慌,按这个流程走一遍,大部分问题都能定位到:

  1. 先看现象: 是卡顿?是回声?还是不同步?确定问题大类。
  2. 抓日志:logcat -b audio 抓音频日志,同时用 dumpsys 抓状态快照。
  3. 看统计: 检查underrun/overrun计数,看是否在持续增长。
  4. 看负载:topsystrace 看CPU和调度情况。
  5. 看路由: 确认音频设备切换是否正确,有没有异常的路由策略。
  6. 看时间戳: 如果是同步问题,打印出每一帧的timestamp,画图分析。
最后提醒一句: 不要一上来就怀疑算法。我见过太多人花了一周调AEC参数,最后发现是麦克风堵了。先排除硬件和驱动层的问题,再动软件。

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