26、性能剖析工具:Systrace/Perfetto使用、GPU Profiler、解码器内部统计、自定义打点
做视频硬解码优化,最怕什么?
怕的是「感觉卡,但不知道卡在哪」。
我早年调试一个4K播放器,画面每隔几秒就抖一下。肉眼能看出来,但log里全是正常的。折腾了两天,最后用Systrace一抓——原来是GPU渲染管线里有个纹理上传操作卡了主线程。嗯,从那以后,我再也不敢凭感觉调优了。
这一章,我就把压箱底的性能剖析工具链拿出来。咱们一个一个说清楚。
26.1 Systrace与Perfetto:系统级追踪的「老大哥」和「新王者」
先说Systrace。这是Android早期就有的工具,基于ftrace。它能抓取CPU调度、SurfaceFlinger、VSync、Binder调用等系统事件。
我个人习惯,在分析解码卡顿时,先开Systrace看两个东西:
- VSync信号是否稳定——如果VSync出现断层,说明渲染管线被阻塞了
- 解码线程的CPU调度情况——看它是不是被频繁迁移到小核上
但Systrace有个硬伤:它只能抓几十秒,而且数据量一大就卡。所以Android 10之后,Google主推Perfetto。
Perfetto比Systrace强在哪?我列个表:
| 对比项 | Systrace | Perfetto |
|---|---|---|
| 抓取时长 | 通常5~10秒 | 可长达数分钟 |
| 数据量 | 超过100MB就卡死 | 支持GB级数据 |
| SQL分析 | 不支持 | 支持SQL查询 |
| 自定义数据源 | 有限 | 支持protobuf扩展 |
| UI界面 | 老旧Web UI | 全新UI,交互流畅 |
说白了,如果你还在用Systrace,建议尽快迁移到Perfetto。我去年把一个老项目从Systrace切到Perfetto,排查问题的效率至少提升了50%。
26.2 GPU Profiler:解码器之外的「隐形瓶颈」
很多人以为解码优化只看CPU就够了。其实不然。
你想想看,解码器输出的是一帧帧YUV或RGBA数据。这些数据最终要交给GPU去合成、显示。如果GPU这边处理不过来,照样掉帧。
Android上的GPU Profiler工具,我常用这几个:
- AGI (Android GPU Inspector)——Google官方工具,支持抓取GPU指令、着色器、纹理内存
- Snapdragon Profiler——高通平台专用,能看Adreno GPU的详细状态
- Mali Graphics Debugger——ARM平台专用,适合Exynos/麒麟芯片
我曾经遇到一个案例:解码器输出帧率是60fps,但屏幕上只有30fps。用AGI一查,发现是GPU在合成时,每帧都要做一次色彩空间转换(YUV→RGB),这个操作占用了大量着色器周期。后来我们把转换逻辑挪到解码器的输出端,用CPU提前转好,GPU这边就轻松了。
26.3 解码器内部统计:MediaCodec自带的「体检报告」
MediaCodec其实提供了不少内部统计信息,只是很多人不知道。
通过 MediaCodec.getOutputFormat() 和 MediaCodec.getInputFormat(),你可以拿到一些关键指标:
// 获取解码器内部统计
MediaFormat format = codec.getOutputFormat();
int width = format.getInteger(MediaFormat.KEY_WIDTH);
int height = format.getInteger(MediaFormat.KEY_HEIGHT);
// 注意:以下字段并非所有设备都支持
if (format.containsKey("encoder-delay")) {
int delay = format.getInteger("encoder-delay");
Log.d(TAG, "编码器延迟: " + delay + "帧");
}
if (format.containsKey("input-buffer-count")) {
int inputCount = format.getInteger("input-buffer-count");
Log.d(TAG, "输入缓冲区数量: " + inputCount);
}
但说实话,MediaCodec暴露的统计信息非常有限。更靠谱的方式,是使用 Codec2 的 C2Param 接口。Codec2是Android 12之后主推的新一代编解码框架,它提供了更细粒度的控制参数。
举个例子,你可以通过 C2StreamPictureSizeInfo 拿到实际解码分辨率,通过 C2StreamAvgBitrateInfo 拿到码率统计。这些在MediaCodec的老接口里是拿不到的。
26.4 自定义打点:把「黑盒」变成「白盒」
工具再强,也有覆盖不到的地方。比如:你的业务逻辑里,从网络接收数据包到送入解码器,中间经过了几个队列?每个队列的等待时间是多少?
这些信息,Systrace和Perfetto都抓不到。怎么办?自己打点。
Android上自定义打点,我推荐两种方式:
- Trace.beginSection() / endSection()——最简单,直接写入系统trace,可以在Systrace/Perfetto里看到
- 自定义环形缓冲区——适合高频打点场景,避免log过多影响性能
下面是我在实际项目中用过的打点模板:
public class DecoderTrace {
private static final String TAG = "DecoderTrace";
private static final int BUFFER_SIZE = 1024;
private final long[] timestamps = new long[BUFFER_SIZE];
private final String[] labels = new String[BUFFER_SIZE];
private int index = 0;
public void mark(String label) {
// 写入系统trace
Trace.beginSection(label);
Trace.endSection();
// 同时写入环形缓冲区
timestamps[index] = System.nanoTime();
labels[index] = label;
index = (index + 1) % BUFFER_SIZE;
}
public void dump() {
// 输出最近BUFFER_SIZE条记录
for (int i = 0; i < BUFFER_SIZE; i++) {
if (labels[i] != null) {
Log.d(TAG, labels[i] + " @ " + timestamps[i]);
}
}
}
}
使用的时候,在关键路径上打点:
trace.mark("网络接收完成");
// ... 数据入队 ...
trace.mark("数据入队");
// ... 解码器dequeueInputBuffer ...
trace.mark("送入解码器");
// ... 解码器返回输出 ...
trace.mark("解码完成");
我曾经用这套方案,定位到一个诡异的「间歇性卡顿」。Perfetto上看CPU和GPU都正常,但自定义打点显示:每隔30秒左右,网络接收完成到数据入队之间,会多出200ms的延迟。最后发现是网络库的GC引起的。嗯,这种问题,没有自定义打点根本查不出来。
26.5 知识体系总览
说了这么多,我把这四种工具的关系画成一张图,方便你理解:
这四种工具,不是互相替代的关系,而是互补的。系统级追踪看大方向,GPU Profiler查渲染瓶颈,解码器内部统计确认硬件状态,自定义打点补全业务细节。四者结合,才能把解码性能问题彻底挖干净。
好了,这一章的内容就到这里。下一章我们聊聊具体怎么用Perfetto抓取解码器的trace数据,以及如何解读那些花花绿绿的时间轴。