第三章 性能指标与度量:帧率、延迟与抖动
做Camera HAL优化这么多年,我见过太多人一上来就闷头调参数,结果调了半天连问题出在哪都不知道。说白了,你得先知道怎么量,才能谈怎么优化。这一章,我们就来聊聊三个最核心的指标:帧率、延迟、抖动,以及怎么用工具把它们抓出来。
3.1 帧率(FPS)—— 流畅度的第一道门槛
帧率,就是每秒能输出多少帧画面。30fps、60fps、120fps,数字越大,画面越流畅。但这里有个坑——平均帧率不代表一切。
我记得有一次,客户反馈预览画面“一顿一顿”的。我一看log,平均帧率29.8fps,挺正常的啊。后来用Perfetto一抓,才发现每秒钟有几次掉到15fps,然后又跳回30fps。平均下来看着还行,但人眼对那种突然的卡顿特别敏感。
测量方法:
- 简单粗暴法:用手机拍秒表,数10秒内画面变化的次数。不精确,但快速排查够用。
- 工具法:用
dumpsys SurfaceFlinger查看每帧的时间戳,或者用Perfetto直接抓取vsync信号和HAL的buffer输出。 - 代码埋点:在HAL的
processCaptureRequest入口和出口打上trace,计算每秒完成的request数量。
我的习惯:我一般不会只看平均FPS。我会看FPS的分布直方图——比如90%的帧在30fps,但10%的帧掉到了15fps以下,那这个系统就是有问题的。
3.2 延迟(Latency)—— 从按下快门到看见照片
延迟,就是操作到响应的时间差。在Camera领域,延迟分好几种:
- 快门延迟:按下拍照键到传感器开始曝光的时间。
- 处理延迟:传感器输出raw数据到HAL处理完、送到framework的时间。
- 显示延迟:从buffer送到显示到屏幕上真正亮起来的时间。
我遇到过最头疼的一个项目,快门延迟高达200ms。用户按下去,要等0.2秒才听到快门声。用户都以为手机死机了。后来发现是HAL里有个同步锁没处理好,导致request排队了。
避坑指南:我曾经在调试时只关注了“处理延迟”,忽略了“显示延迟”。结果处理延迟优化到了20ms,但用户还是觉得卡。后来才发现是显示管线里多了一帧的buffer排队。记住:用户感知的延迟 = 所有环节延迟之和。
测量方法:
- 硬件打点:用示波器抓GPIO电平变化。传感器曝光开始拉高,HAL处理完成拉低。这是最准的。
- 软件trace:在关键路径上加
ATRACE_BEGIN和ATRACE_END,然后用Perfetto看时间差。 - 用户感知测试:用高速摄像机(240fps以上)拍手机屏幕和物理事件(比如手指按下),逐帧数。
3.3 抖动(Jitter)—— 帧率稳定的隐形杀手
抖动,说白了就是帧与帧之间的时间间隔不一致。你想想看,如果每帧都是33.3ms(30fps),那很完美。但如果一帧是30ms,下一帧是40ms,再下一帧又是30ms,人眼就会感觉到微弱的“跳动”。
我习惯用帧间隔的标准差来衡量抖动。标准差越小,画面越稳。一般要求抖动在±1帧以内,也就是30fps时,帧间隔波动不超过33.3ms ± 5ms。
测量方法:
- 用Perfetto抓取
Choreographer或SurfaceFlinger的vsync事件。 - 导出每帧的时间戳,计算相邻帧的时间差。
- 计算这些时间差的平均值、标准差、最大值、最小值。
- 如果标准差超过平均值的10%,就要警惕了。
举个例子,我最近调的一个项目,帧率稳定在30fps,但抖动达到了±15ms。用户反馈“画面在轻微晃动”。后来发现是ISP的3A算法(自动对焦、自动白平衡、自动曝光)偶尔会占用过多CPU时间,导致HAL的request处理被延迟了。
3.4 常用性能分析工具:Perfetto 与 Systrace
这两个工具,说白了就是Camera HAL工程师的“听诊器”。没有它们,你就像蒙着眼睛修车。
3.4.1 Systrace(老牌工具)
Systrace是Android早期推出的trace工具。它基于ftrace,可以抓取内核、framework、HAL层的trace点。优点是简单易用,缺点是不够细。
# 抓取Camera相关的trace
python systrace.py -t 10 -o trace.html camera input view res
我个人习惯用Systrace做快速排查。比如看HAL的processCaptureRequest是否被卡住,或者看BufferQueue是否有阻塞。但说实话,Systrace的UI有点老了,而且对多线程的支持不够好。
3.4.2 Perfetto(新一代神器)
Perfetto是Google推出的新一代trace工具,功能比Systrace强太多了。它支持更细粒度的trace,可以看CPU调度、内存分配、GPU负载、甚至自定义的HAL trace点。
我的建议:如果你还在用Systrace,赶紧切换到Perfetto。Perfetto的UI是Web版的,交互更友好,而且支持SQL查询trace数据。我经常用它来写SQL语句,直接查出“哪一帧的延迟超过了50ms”。
# 抓取Perfetto trace
adb shell perfetto -c /data/misc/perfetto-configs/camera.cfg -o /data/misc/perfetto-traces/trace.perfetto-trace
# 配置文件示例(camera.cfg)
buffers: {
size_kb: 522240
fill_policy: RING_BUFFER
}
data_sources: {
config {
name: "android.trace"
target_buffer: 0
android_trace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "power/cpu_frequency"
ftrace_events: "gpu_mem/gpu_mem_total"
atrace_categories: "camera"
atrace_apps: "com.android.camera"
}
}
}
duration_ms: 10000
抓完trace后,用ui.perfetto.dev打开,你可以看到这样的信息:
| 指标 | Systrace | Perfetto |
|---|---|---|
| UI交互 | 桌面版,较老 | Web版,现代化 |
| 数据查询 | 不支持 | 支持SQL查询 |
| 多线程支持 | 一般 | 优秀 |
| 自定义trace点 | 支持ATRACE | 支持ATRACE + 自定义数据源 |
| GPU trace | 有限 | 支持GPU频率、内存等 |
3.5 实战:用Perfetto定位一帧延迟的根因
嗯,这里我分享一个真实案例。有一次,用户反馈拍照后要等1秒才能看到预览恢复。我抓了Perfetto trace,发现:
- HAL的
processCaptureRequest在拍照后突然停止了500ms。 - 这500ms里,CPU在跑一个高优先级的
surfaceflinger线程。 - 再往下看,
surfaceflinger在等待一个BufferQueue的锁。 - 而这个锁被一个
CameraService的线程持有了,它在做JPEG编码。
你看,通过Perfetto,我一步步从“延迟高”追到了“JPEG编码占用了BufferQueue锁”。解决方案也很简单:把JPEG编码放到一个独立的线程池里,不要占用CameraService的主线程。
总结一下:
- 帧率看平均值,更要看分布。
- 延迟要分环节测量,用户感知的是总延迟。
- 抖动用标准差衡量,波动超过10%就要查。
- 工具首选Perfetto,Systrace可以作为备选。
下一章,我们会深入HAL层,看看具体怎么优化这些指标。到时候我会分享一些我踩过的坑和调优的“黑魔法”。