第三章 性能指标与度量:帧率、延迟与抖动

做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_BEGINATRACE_END,然后用Perfetto看时间差。
  • 用户感知测试:用高速摄像机(240fps以上)拍手机屏幕和物理事件(比如手指按下),逐帧数。

3.3 抖动(Jitter)—— 帧率稳定的隐形杀手

抖动,说白了就是帧与帧之间的时间间隔不一致。你想想看,如果每帧都是33.3ms(30fps),那很完美。但如果一帧是30ms,下一帧是40ms,再下一帧又是30ms,人眼就会感觉到微弱的“跳动”。

我习惯用帧间隔的标准差来衡量抖动。标准差越小,画面越稳。一般要求抖动在±1帧以内,也就是30fps时,帧间隔波动不超过33.3ms ± 5ms。

测量方法:

  1. 用Perfetto抓取ChoreographerSurfaceFlinger的vsync事件。
  2. 导出每帧的时间戳,计算相邻帧的时间差。
  3. 计算这些时间差的平均值、标准差、最大值、最小值。
  4. 如果标准差超过平均值的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,发现:

  1. HAL的processCaptureRequest在拍照后突然停止了500ms。
  2. 这500ms里,CPU在跑一个高优先级的surfaceflinger线程。
  3. 再往下看,surfaceflinger在等待一个BufferQueue的锁。
  4. 而这个锁被一个CameraService的线程持有了,它在做JPEG编码。

你看,通过Perfetto,我一步步从“延迟高”追到了“JPEG编码占用了BufferQueue锁”。解决方案也很简单:把JPEG编码放到一个独立的线程池里,不要占用CameraService的主线程。

总结一下:

  • 帧率看平均值,更要看分布。
  • 延迟要分环节测量,用户感知的是总延迟。
  • 抖动用标准差衡量,波动超过10%就要查。
  • 工具首选Perfetto,Systrace可以作为备选。

下一章,我们会深入HAL层,看看具体怎么优化这些指标。到时候我会分享一些我踩过的坑和调优的“黑魔法”。