24、Camera性能优化:启动速度优化、Capture延迟优化、内存优化、功耗优化、Profiling工具
性能优化,说白了就是跟硬件抢时间、跟系统争资源。我在做Camera HAL层开发那几年,几乎每天都在跟这几个指标打交道。启动慢了用户骂,拍照卡了用户骂,手机发烫了用户还是骂。嗯,今天我就把这几块硬骨头掰开揉碎,跟你聊聊实战中的那些坑和招。
核心优化维度:启动速度、Capture延迟、内存占用、功耗控制。这四个维度相互牵制,你压了内存可能就拖了速度,降了功耗可能就增了延迟。所以,优化是个平衡的艺术。
24.1 启动速度优化
相机启动,用户感知最明显。从点击图标到取景画面出现,超过1秒用户就开始烦躁了。我见过有些项目启动要2.5秒,产品经理直接拍桌子。
启动流程大致分三段:进程启动、HAL初始化、Preview通路建立。每一段都有优化空间。
24.1.1 进程启动阶段
- 冷启动 vs 热启动:冷启动要加载APK、创建进程、初始化Application。我建议把CameraService做成常驻进程,或者用Binder热连接,能省下200-300ms。
- ContentProvider懒加载:很多App在Application.onCreate里做了一堆初始化。其实相机模块用到的ContentProvider可以延迟到真正需要时再加载。我曾经在一个项目里,光是把一个第三方SDK的初始化后移,就砍掉了400ms。
- 类预加载:用ClassLoader.preload把关键类提前加载。注意别加载太多,否则反而拖慢启动。
24.1.2 HAL初始化阶段
HAL初始化是重头戏。Camera HAL3的open()和configureStreams()是两大耗时点。
// 伪代码:HAL初始化优化示例
// 1. 并行初始化Sensor和ISP
async_init_sensor(); // 不阻塞
async_init_isp(); // 同时进行
wait_for_both(); // 汇合点
// 2. 复用Stream配置缓存
if (cached_config_available) {
apply_cached_stream_config();
} else {
do_full_configure();
}
我的经验:Sensor上电和I2C通信是HAL初始化的瓶颈。我试过把Sensor的PLL配置做成预置表,启动时直接查表而不是重新计算,省了约150ms。另外,如果平台支持,尽量用MIPI CSI的快速锁定模式。
24.1.3 Preview通路建立
从configureStreams到第一个Preview帧出来,中间要经过Buffer分配、Pipeline配置、ISP tuning加载。这里有个关键点:不要等所有Stream都配好了才出图。可以先配一个低分辨率的Preview Stream,等界面出来了再动态切换到全分辨率。
24.2 Capture延迟优化
Capture延迟,就是按下快门到图像数据回来的时间。这个指标直接影响连拍速度和HDR体验。
延迟主要来自三部分:Sensor曝光、ISP处理、Buffer流转。我一个个说。
24.2.1 Sensor曝光优化
- 减少曝光行数:曝光时间跟帧率直接相关。如果场景允许,用更短的曝光时间。但注意别牺牲画质。
- 预曝光:在用户按下快门前,Sensor已经在持续曝光了。利用好这个"历史帧",可以减少一次完整的曝光等待。我记得在某个项目中,我们用预曝光+帧缓存,把Capture延迟从120ms降到了60ms。
- 双Sensor交错曝光:如果硬件支持双Sensor,可以让一个Sensor曝光时另一个在读出,交替进行。
24.2.2 ISP处理优化
ISP处理是CPU/GPU/ISP三者的博弈。我建议:
- 减少不必要的Tuning:很多ISP pipeline默认开了全部模块(AWB、AE、AF、去噪、锐化...)。其实Capture帧可以跳过部分后处理,只保留必要的。
- 硬件加速:能用ISP硬件做的,别用CPU。比如HDR合成、多帧降噪,交给ISP的专用硬件单元。
- Pipeline并行化:把ISP处理拆成多个阶段,用流水线方式并行。比如第一帧在做去噪时,第二帧已经开始AWB了。
24.2.3 Buffer流转优化
// Buffer流转延迟优化:减少拷贝
// 坏做法:多次拷贝
sensor_buffer -> ISP_input -> ISP_output -> encoder_input
// 好做法:零拷贝链
sensor_buffer (dma_buf)
-> ISP (直接操作dma_buf)
-> encoder (直接消费dma_buf)
注意:零拷贝虽然好,但要注意Buffer的生命周期管理。我曾经踩过一个坑:ISP还在处理Buffer时,Encoder已经把它释放了,结果画面出现撕裂。解决方案是用引用计数或者Fence机制。
24.3 内存优化
相机是内存大户。一个4K分辨率的YUV帧就要12MB左右,再加上多帧缓存、ISP内部Buffer、编码器Buffer,分分钟吃掉几百MB。我见过有些低端机,相机一开直接OOM。
24.3.1 Buffer复用策略
- Buffer Pool:预分配一组Buffer,循环使用。不要频繁申请释放。
- 按需分配:不要一次性把所有Stream的Buffer都分配好。根据当前模式(拍照、录像、预览)动态调整。
- 压缩格式:能走NV12就别走RGBA,能走HEIF就别走JPEG。压缩格式能省一半以上内存。
24.3.2 内存泄漏排查
相机模块最容易泄漏的地方:
- Callback未注销:注册了CameraDevice.StateCallback但没在onClosed里清理。
- Surface持有引用:SurfaceTexture或SurfaceView被CameraSession持有,导致Activity无法释放。
- Native层Buffer泄漏:HAL层申请的dma_buf没有正确释放。这个最难查,我一般用dma_buf的debugfs来监控。
我的排查工具:除了Android自带的Profiler,我还会用dumpsys meminfo看Native Heap,用cat /d/dma_buf/bufinfo看dma_buf使用情况。有一次发现某个Sensor驱动在切换分辨率时没有释放旧的Buffer,导致内存持续增长。定位到问题后,一行代码就修好了。
24.4 功耗优化
相机是功耗大户。Sensor、ISP、GPU、CPU全开,电流轻松上2A。手机发烫不说,电池也扛不住。
24.4.1 Sensor功耗
- 降低帧率:预览不需要60fps时,用30fps甚至15fps。Sensor的功耗跟帧率基本线性关系。
- 关闭不用的Sensor:如果只用主摄,把广角和长焦的电源关掉。别让它们空转。
- 快速休眠:Camera进入后台时,尽快让Sensor进入standby模式。我见过有些实现,退到后台后Sensor还在持续曝光,白白浪费电。
24.4.2 ISP和CPU功耗
- 动态调频:根据场景调整ISP和CPU的频率。拍静态照片时不需要最高频率,录像时再拉高。
- 减少CPU介入:能用ISP硬件做的,别让CPU参与。比如人脸检测、场景识别,现在很多ISP都内置了这些功能。
- GPU渲染优化:Preview画面用GPU渲染时,注意减少Overdraw。我建议用SurfaceView而不是TextureView,因为SurfaceView有独立的合成层,省GPU。
24.4.3 功耗测量方法
| 方法 | 精度 | 适用场景 |
|---|---|---|
| BatteryStats | 低 | 快速估算 |
| PowerMonitor | 高 | 精确测量 |
| ThermalEngine日志 | 中 | 温升监控 |
| 电流钳+示波器 | 最高 | 硬件调试 |
24.5 Profiling工具
没有工具,优化就是盲人摸象。我常用的工具链如下:
24.5.1 Android自带工具
- Systrace / Perfetto:看整体流程,找耗时瓶颈。我习惯在关键节点加Trace.beginSection/endSection。
- SimplePerf:看CPU热点,找函数级别的性能问题。
- dumpsys media.camera:看CameraService内部状态,比如Stream配置、Buffer使用情况。
24.5.2 硬件级工具
- DS-5 / Streamline:ARM官方的性能分析工具,可以看CPU/GPU/ISP的实时负载。
- 逻辑分析仪:抓MIPI CSI信号,看Sensor输出时序。我用来排查Sensor和ISP之间的同步问题。
- 热成像仪:看手机发热点。有一次我发现ISP芯片局部温度过高,后来发现是散热设计问题。
24.5.3 自定义Profiling
有时候工具不够用,我就自己写。比如:
// 自定义帧率统计
class FrameRateMonitor {
long lastTimestamp = 0;
int frameCount = 0;
void onFrameReceived() {
frameCount++;
long now = System.nanoTime();
if (now - lastTimestamp > 1_000_000_000L) { // 每秒统计
float fps = frameCount * 1.0f;
Log.d("CameraPerf", "Current FPS: " + fps);
frameCount = 0;
lastTimestamp = now;
}
}
}
总结一下:性能优化没有银弹。启动速度、Capture延迟、内存、功耗,每个维度都要单独分析,但又不能孤立看待。我的建议是:先用Profiling工具找到瓶颈,然后针对性地优化,最后回归测试验证效果。别一上来就大改,容易引入新问题。
避坑指南:我曾经在一个项目里,为了优化启动速度,把HAL初始化里的Sensor校准给跳过了。结果启动是快了,但拍出来的照片颜色偏得离谱。后来老老实实加回来,改用异步校准+缓存结果的方式。所以,优化之前先搞清楚每个步骤的"必要性",别为了快而牺牲正确性。