Perfetto实战:解码Audio underrun的深层表现与优化策略

张开发
2026/4/13 17:47:15 15 分钟阅读

分享文章

Perfetto实战:解码Audio underrun的深层表现与优化策略
1. 音频underrun问题初探当你用手机听音乐时突然出现咔咔的断断续续声或者玩游戏时音效突然消失又恢复这很可能就是遇到了音频underrun问题。作为Android开发者我经常在性能优化中遇到这类问题今天就来聊聊如何用Perfetto工具深入分析这个音频界的隐形杀手。underrun本质上是个供需失衡问题。想象一下快餐店的点餐窗口顾客(消费者)源源不断来取餐后厨(生产者)需要持续制作汉堡。如果某时刻顾客取餐速度超过后厨制作速度就会出现断供 - 这就是underrun。在Android音频系统中AudioTrack是生产者负责填充音频数据AudioFlinger的PlaybackThread是消费者负责播放数据。当播放线程发现缓冲区空了就会触发underrun事件。2. 快速诊断underrun的两种方法2.1 日志分析法最直接的诊断方式是通过logcat抓取underrun日志。在终端执行adb logcat | grep underrun典型underrun日志长这样V AudioFlinger: track(60) underrun, track state ACTIVE framesReady(192) framesDesired(1090)关键信息是framesReady值远小于framesDesired说明缓冲区数据不足。我遇到过最极端的情况是framesReady为0这时音频会完全中断。2.2 音频波形分析法更直观的方法是导出各环节音频数据对比分析。首先开启AudioFlinger的tee调试功能adb shell setprop af.tee.sink 1 adb shell dumpsys media.audio_flinger audio_dump.txt然后用Audacity等工具对比AudioTrack输出和MixerThread输出的波形。正常情况两者应该基本一致如果发现MixerThread波形出现明显间隔就像被啃过一样那就是典型的underrun表现。3. Perfetto深度追踪实战3.1 配置追踪参数要捕捉underrun的完整调用链需要配置perfetto配置文件buffers: { size_kb: 102400 fill_policy: DISCARD } data_sources: { config { name: linux.ftrace ftrace_config { ftrace_events: sched/sched_switch ftrace_events: audio/audio_underrun ftrace_events: audio/audio_buffer atrace_categories: audio } } } duration_ms: 20000这个配置会记录20秒内的音频相关事件特别关注了sched_switch(线程调度)和audio_underrun事件。3.2 关键trace点解析在AudioFlinger代码中添加的ATRACE标记会体现在perfetto中process__noResampleOneTrack显示混音时各track的帧数prepareTracks_l展示每个track的framesReady状态threadLoop_write记录实际写入音频设备的帧数正常情况这些事件应该保持稳定间隔出现异常波动时就可能发生underrun。我曾在某项目中发现prepareTracks_l的间隔从平均3ms突然跳到15ms这就是典型的调度延迟导致的underrun。4. 六种典型underrun场景分析4.1 CPU调度延迟最常见的问题场景。当音频线程被长时间抢占时会出现类似这样的perfetto轨迹|-----------------| 音频线程就绪 |-----| 被高优先级任务抢占 |--------| 重新调度后处理积压数据解决方法是通过cpuset将音频线程绑定到大核// 在AudioFlinger线程初始化时设置CPU亲和性 cpu_set_t cpu_set; CPU_ZERO(cpu_set); CPU_SET(6, cpu_set); // 绑定到CPU6 sched_setaffinity(0, sizeof(cpu_set), cpu_set);4.2 内存带宽争抢在低端设备上当GPU大量读写内存时音频DMA可能无法及时获取数据。perfetto中表现为audio_buffer事件间隔稳定但framesReady持续偏低同时存在gpu_memcpy等高带宽操作优化方案是调整音频缓冲区策略// 在audio_policy_configuration.xml中 mixPort nameprimary output bufferSize1024 bufferCount4/4.3 中断风暴某些外设驱动异常可能引发中断风暴。在perfetto的irq事件中会看到某个IRQ号频繁触发(如每秒超过1000次)audio_thread被频繁打断这类问题需要更新驱动或调整中断亲和性echo 0f /proc/irq/123/smp_affinity5. 高级优化策略5.1 动态缓冲区调节基于负载预测动态调整缓冲区大小这是我改进过的算法实现// 在AudioFlinger的MixerThread中 void updateBufferSize() { float load calculateCPULoad(); // 计算最近1s的CPU负载 if (load 0.8f) { mBufferSize min(mMaxBufferSize, mBufferSize * 1.2f); } else { mBufferSize max(mMinBufferSize, mBufferSize * 0.95f); } mAudioDevice-setBufferSize(mBufferSize); }5.2 优先级继承机制解决优先级反转问题的关键代码// 在AudioTrack客户端侧 void obtainBuffer() { android::AutoMutex lock(mLock); if (mProxy-framesReady() mDesiredFrames) { // 提升服务端线程优先级 requestPriorityBoost(); mCond.wait(mLock); } }6. 实战案例游戏音频卡顿优化某游戏在加载场景时频繁出现音频卡顿。通过perfetto分析发现Asset加载线程(优先级120)长时间占用CPU音频线程(优先级90)被严重抢占出现连续underrun事件最终解决方案将音频线程优先级提升到100对Asset加载采用分片加载策略增加音频缓冲池大小优化后underrun次数从每秒15次降为0CPU利用率反而降低了12%。这个案例告诉我们合理的线程调度比单纯提升CPU频率更有效。

更多文章