12. 帧率控制与VSync:动态帧率调整、VSync对齐、减少帧丢失
帧率控制这件事,说白了就是「让相机干活别太累,也别偷懒」。我见过不少项目,一上来就把帧率拉到满,结果功耗飙上去,画面却没变流畅多少。你想想看,用户看的是画面效果,不是帧率数字。
VSync 对齐就更关键了。我早期调一个预览卡顿的问题,折腾了两周,最后发现是帧率和屏幕刷新率没对齐,白白丢了一半的帧。嗯,这坑我踩过,今天咱们就把它填平。
12.1 动态帧率调整:别让相机一直跑满速
相机预览的帧率,真的需要一直 30fps 吗?不一定。场景暗的时候,每帧曝光时间都长,你硬要 30fps,结果就是每帧都欠曝,噪点还多。我建议的做法是:根据场景亮度动态调整目标帧率。
核心思路:帧率 = min(传感器能力, 应用需求, 场景限制)
具体怎么调?我一般分三步走:
- 亮度检测:从每一帧的 AE 统计信息里拿到当前 lux 值。
- 帧率映射:亮度低 -> 降帧率(比如 15fps),亮度高 -> 恢复满帧率。
- 平滑切换:不要一下子从 30 跳到 15,中间加个渐变,避免用户感知到卡顿。
// 伪代码示例:动态帧率调整逻辑
int getTargetFps(float lux) {
if (lux > 200.0f) {
return 30; // 光线充足,满帧跑
} else if (lux > 50.0f) {
return 24; // 室内光线,降一点
} else if (lux > 10.0f) {
return 15; // 暗光,降帧保画质
} else {
return 10; // 极暗,能看就行
}
}
我的经验:动态帧率调整一定要和 AE 算法配合好。我曾经遇到一个 case,帧率降下去了,AE 却还在按 30fps 算曝光时间,结果画面忽明忽暗。后来我把帧率信息同步给 AE 模块,问题就解决了。
12.2 VSync 对齐:让每一帧都踩在点上
VSync 是什么?简单说就是屏幕刷新的「节拍器」。屏幕每 16.6ms(60Hz)刷新一次,你的相机帧就得在这个时间窗口内准备好。如果没对齐,就会出现「帧准备好了,屏幕刚刷过去」的情况——这一帧就丢了。
我习惯把 VSync 对齐分成两个层面来看:
- 应用层对齐:Camera 应用的 Surface 要注册到 Choreographer 的 VSync 回调上。
- HAL 层对齐:Sensor 的帧同步信号要和 Display 的 VSync 做硬件级对齐。
应用层对齐的代码大概长这样:
// 注册 Choreographer 回调,实现 VSync 对齐
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
// frameTimeNanos 就是下一次 VSync 的时间点
// 在这里提交新的预览帧到 Surface
previewSurface.queueBuffer(buffer, ...);
// 继续注册下一帧回调
Choreographer.getInstance().postFrameCallback(this);
}
});
注意:Choreographer 的回调是在 UI 线程执行的。如果你的帧处理逻辑太重,会卡住 UI。我建议把帧处理放到单独的 HandlerThread 里,回调只做「提交」操作。
12.3 减少帧丢失:从源头堵住漏洞
帧丢失的原因很多,我总结下来,最常见的有三个:
| 原因 | 表现 | 解决方案 |
|---|---|---|
| Buffer 不足 | Surface 队列满了,新帧被丢弃 | 增加 Buffer 数量,或降低帧率 |
| 处理超时 | ISP/HAL 处理一帧超过 33ms | 优化算法,或降帧率 |
| VSync 失配 | 帧准备好时屏幕刚刷完 | 做 VSync 对齐,或使用 Triple Buffer |
我曾经在一个项目里,预览帧率标称 30fps,实际测出来只有 22fps。查了半天,发现是 HAL 层用了 4 个 Buffer,但应用层只消费了 3 个,第 4 个 Buffer 永远在排队。嗯,这种「Buffer 死锁」问题,说白了就是生产者和消费者没协调好。
减少帧丢失的 checklist:
- ✅ 确认 Buffer 数量 >= 3(推荐 4 个)
- ✅ 确认每一帧的处理时间 < 帧间隔(30fps 就是 33ms)
- ✅ 确认 VSync 信号已对齐(用 systrace 看 SurfaceFlinger 的 vsync-app)
- ✅ 确认没有 Buffer 泄漏(用 dumpsy SurfaceFlinger 检查)
12.4 知识体系图:帧率控制与 VSync 的核心逻辑
12.5 实战避坑指南
最后,分享几个我踩过的坑:
- 不要盲目用 Triple Buffer:Triple Buffer 能减少 VSync 失配,但会增加内存和延迟。我建议只在 60fps 高帧率场景下启用。
- 注意帧率切换的平滑性:从 30fps 降到 15fps,中间最好过渡一两秒。我曾经直接硬切,结果用户反馈「画面一顿一顿的」。
- 用 systrace 看 VSync 对齐:在 systrace 里找到 SurfaceFlinger 的 vsync-app 和相机线程的 dequeueBuffer,看它们的时间戳是否对齐。这是最直接的验证方法。
我的小工具:我写了一个简单的帧率监控脚本,每 5 秒统计一次实际帧率,如果低于目标帧率的 80%,就自动 dump 一次 systrace。这样能快速定位帧丢失的原因。
帧率控制这件事,说到底就是「该快的时候快,该慢的时候慢」。VSync 对齐是基本功,减少帧丢失是细节活。把这两件事做好了,功耗和体验就都稳了。
公众号:蓝海资料掘金营,微信deep3321