28. Camera性能优化:帧率监控、内存管理、功耗优化、多线程处理
做Camera开发,说白了就是在跟资源较劲。CPU、内存、带宽、功耗,哪一块没伺候好,整个采集链路就会出问题。我这些年踩过的坑,十有八九都跟性能有关。今天咱们就把这块硬骨头啃下来。
28.1 帧率监控:别让数据骗了你
帧率是Camera最直观的性能指标。但很多人只看logcat里打印的fps,那玩意儿其实不太靠谱。为什么?因为系统打印的往往是平均帧率,中间卡了几帧你根本看不出来。
我个人习惯用两种方式监控帧率:
- 逐帧时间戳分析:记录每一帧的到达时间,计算相邻帧的时间差
- 滑动窗口统计:取最近30帧或60帧,计算实时帧率
来看一段我常用的帧率监控代码:
public class FpsMonitor {
private static final int WINDOW_SIZE = 30;
private final long[] frameTimestamps = new long[WINDOW_SIZE];
private int index = 0;
private int count = 0;
public void onFrameCaptured() {
long now = System.nanoTime();
frameTimestamps[index] = now;
index = (index + 1) % WINDOW_SIZE;
if (count < WINDOW_SIZE) count++;
if (count == WINDOW_SIZE) {
long oldest = frameTimestamps[index];
long elapsed = now - oldest; // 纳秒
double fps = (double) WINDOW_SIZE / (elapsed / 1_000_000_000.0);
Log.d("FpsMonitor", "实时帧率: " + String.format("%.1f", fps));
}
}
}
28.2 内存管理:ImageReader是个双刃剑
Camera采集最吃内存的地方,就是ImageReader。你想想看,1080p的YUV420_888图像,一帧就要3MB左右。如果开了3帧的缓冲池,那就是9MB。要是再开个预览和拍照双路流,内存直接起飞。
我遇到过最离谱的一次,是某款手机在拍照时OOM了。查了半天,发现是ImageReader的OnImageAvailableListener里没有及时close Image对象。嗯,这个坑我帮你们踩过了。
内存优化的几个关键点:
- 控制缓冲池大小:预览场景2-3帧足够,拍照场景可以适当增加
- 及时释放Image:用完必须close,最好用try-finally保证
- 复用Surface:预览和拍照尽量用同一个Surface,减少内存拷贝
- 降低分辨率:不是所有场景都需要4K,720p在很多场景下完全够用
28.3 功耗优化:别让手机变成暖手宝
Camera是功耗大户,这个大家都知道。但具体怎么优化,很多人就懵了。我总结了几条实战经验:
| 优化手段 | 效果 | 适用场景 |
|---|---|---|
| 降低帧率 | 显著降低功耗 | 非实时场景(扫码、文档扫描) |
| 使用低分辨率 | 减少带宽和计算量 | 预览、人脸检测 |
| 关闭不必要的特性 | 减少ISP负载 | 所有场景 |
| 使用硬件编码 | 比软编省电50%以上 | 视频录制 |
| 动态调整帧率 | 按需分配资源 | 场景切换频繁时 |
这里重点说一下动态帧率调整。说白了就是:场景简单时跑低帧率,场景复杂时跑高帧率。比如人脸检测,没人脸时15fps就够了,检测到人脸再切到30fps。我在做门禁项目时就用这个方案,功耗降低了40%。
28.4 多线程处理:别让主线程背锅
Camera的整个采集链路,涉及多个线程。我见过最糟糕的写法,是在主线程里直接操作Camera。结果呢?UI卡成PPT,ANR警告满天飞。
正确的做法是这样的:
- Camera操作线程:专门用来open、configure、start/stop preview
- 帧处理线程:处理ImageReader回调,做图像算法
- UI线程:只负责更新界面,不做任何耗时操作
- 编码线程:视频录制时,把编码放到独立线程
来看一个典型的多线程架构:
public class CameraManager {
private HandlerThread cameraThread;
private HandlerThread frameThread;
private Handler cameraHandler;
private Handler frameHandler;
public void init() {
cameraThread = new HandlerThread("CameraThread");
cameraThread.start();
cameraHandler = new Handler(cameraThread.getLooper());
frameThread = new HandlerThread("FrameThread");
frameThread.start();
frameHandler = new Handler(frameThread.getLooper());
}
public void startPreview() {
cameraHandler.post(() -> {
// 在这里操作Camera
camera.open();
camera.configure();
camera.startPreview();
});
}
private ImageReader.OnImageAvailableListener frameCallback = reader -> {
Image image = reader.acquireLatestImage();
frameHandler.post(() -> {
// 在这里处理帧数据
processFrame(image);
image.close();
});
};
}
28.5 知识体系总览
说了这么多,咱们用一张图把整个性能优化的知识体系串起来:
这张图把四个优化方向的关系讲清楚了。帧率监控是眼睛,帮你发现问题;内存管理和功耗优化是两条腿,保证系统跑得稳、跑得久;多线程处理是骨架,把整个架构撑起来。缺了哪一个,你的Camera应用都走不远。
好了,这一章的内容就到这里。性能优化是个持续的过程,没有银弹。我的建议是:先跑起来,再优化。别一开始就想着完美,那样反而容易陷入过度设计的泥潭。