30、摄像头优化:CameraX使用、预览帧率优化、拍照延迟优化、图像处理流水线

摄像头优化,说实话,是Android性能优化里最让人头疼的一块。为什么?因为它涉及硬件、驱动、框架、应用层,任何一个环节掉链子,用户感知到的就是「卡顿」、「拍照慢」、「取景器掉帧」。我这些年调过的摄像头问题,少说也有几十个了,从低端机到旗舰机,踩过的坑能写一本小册子。

今天咱们就聊聊CameraX的使用、预览帧率、拍照延迟,还有背后的图像处理流水线。嗯,这里我默认你已经对Camera2 API有一定了解,CameraX说白了就是Google在Camera2之上封装的一套更友好的库。

一、为什么选CameraX?

我个人习惯,新项目直接上CameraX。除非你有非常特殊的定制需求,比如要直接操作底层ISP,否则CameraX能帮你省掉80%的兼容性工作。

CameraX的核心优势就三点:

  • 生命周期感知:自动绑定Activity/Fragment的生命周期,不用手动释放Camera
  • 设备兼容性:Google做了大量厂商适配,你不需要为每个机型写if-else
  • 用例驱动:预览、拍照、分析,三个用例组合使用,代码清晰

核心观点:CameraX不是银弹,但它把Camera2的复杂度降低了一个数量级。如果你的App需要快速上线,选它没错。

二、预览帧率优化

预览帧率低,用户第一反应就是「这App好卡」。我遇到过最夸张的一个案例,某款千元机预览只有15fps,用户直接给了差评。后来发现是分辨率设置不合理。

优化预览帧率,我总结了三个关键点:

1. 选择合适的分辨率

不是分辨率越高越好。你想想看,屏幕才1080p,你非要开4K预览,GPU和ISP都扛不住。CameraX提供了Preview.Builder.setTargetResolution(),建议直接匹配屏幕尺寸。

// 推荐做法:根据屏幕尺寸设置预览分辨率
val screenSize = Point()
windowManager.defaultDisplay.getRealSize(screenSize)
val preview = Preview.Builder()
    .setTargetResolution(screenSize)  // 匹配屏幕
    .build()

小技巧:我曾经在某个项目里发现,设置setTargetAspectRatio()比直接设置分辨率更高效。因为CameraX会自动选择最接近的硬件支持分辨率,减少缩放开销。

2. 控制帧率上限

很多设备默认预览帧率是30fps,但如果你不需要那么高,可以主动降低。比如扫码场景,15fps就够用了。

// 设置预览帧率范围
val preview = Preview.Builder()
    .setTargetFrameRate(15, 30)  // 最低15fps,最高30fps
    .build()

这里要注意,setTargetFrameRate只是一个提示,不是强制值。底层驱动可能会忽略它。我在某款MTK平台上就遇到过,设置后完全没效果,最后还是得走Camera2的setRepeatingBurst才搞定。

3. 减少预览回调的负担

如果你用了ImageAnalysis用例做实时处理,比如人脸检测、二维码识别,一定要控制回调频率。否则预览线程会被拖死。

val analysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .setTargetFrameRate(10, 15)  // 分析帧率降低到10-15fps
    .build()

避坑指南:我曾经在一个直播项目里,预览和分析都跑30fps,结果发热严重,手机直接降频。后来把分析帧率降到15fps,预览保持30fps,问题解决。记住:预览和分析是两个独立流水线,别混为一谈。

三、拍照延迟优化

拍照延迟,说白了就是从按下快门到看到照片的时间。用户对这个特别敏感,超过200ms就会觉得「慢」。

我优化拍照延迟的思路,一般分三步走:

1. 使用CaptureMode优化

CameraX提供了ImageCapture.CaptureMode,有MIN_LATENCYMAX_QUALITY两种模式。追求速度就选前者。

val imageCapture = ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
    .build()

嗯,这里要提醒一下:MIN_LATENCY模式会降低画质,因为底层会跳过一些后处理步骤。如果你的App是专业摄影类,慎用。

2. 预对焦和预曝光

很多拍照延迟是因为对焦和测光占用了大量时间。我建议在用户进入拍照界面时就触发一次预对焦,而不是等用户按下快门才做。

// 在预览开始后,主动触发一次对焦
camera.cameraControl.startFocusAndMetering(
    FocusMeteringAction.Builder(
        MeteringPointFactory.fromPoint(surfaceProvider, x, y)
    ).build()
)

我在一个扫码App里用过这个方案,拍照延迟从300ms降到了120ms。效果立竿见影。

3. 异步保存与缩略图

拍照完成后,保存到文件是耗时操作。别在主线程做。CameraX默认是异步的,但如果你用了自定义OnImageCapturedCallback,记得把文件IO放到子线程。

imageCapture.takePicture(
    ContextCompat.getMainExecutor(this),
    object : ImageCapture.OnImageCapturedCallback() {
        override fun onCaptureSuccess(image: ImageProxy) {
            // 这里还是在主线程,别做耗时操作
            thread {
                // 保存到文件
                saveImageToFile(image)
            }
        }
    }
)

核心观点:拍照延迟优化的本质,就是把「能提前做的事提前做,能异步做的事异步做」。预对焦、预曝光、异步保存,这三板斧用好了,延迟能降一半。

四、图像处理流水线

理解图像处理流水线,是做好摄像头优化的基础。说白了,就是一张照片从镜头到屏幕,中间经历了什么。

我画了一张图,帮你理清整个流程:

CameraX 图像处理流水线 镜头 + 传感器 RAW数据输出 ISP (图像信号处理器) 去噪、白平衡、Demosaic HAL层 硬件抽象接口 Camera2 API 底层控制接口 CameraX 用例封装层 Surface / ImageReader 数据传递通道 预览 (Preview) SurfaceView / TextureView 拍照 (ImageCapture) JPEG / YUV输出 分析 (ImageAnalysis) 实时帧回调 硬件层 框架层 应用层

从图上你能看到,数据从传感器出来,经过ISP处理,再到HAL层,然后通过Camera2/CameraX框架,最终到达应用层的三个用例。每个环节都可能成为瓶颈。

流水线优化的几个关键点

  • ISP是硬件瓶颈:ISP的处理能力是固定的,你没法优化它。但你可以通过降低分辨率、关闭某些后处理特性来减轻它的负担。
  • 内存拷贝要避免:从ImageReader到应用层,每多一次拷贝,就多一次延迟。CameraX的ImageProxy已经做了零拷贝优化,但如果你自己写Camera2,记得用ImageReadersetOnImageAvailableListener时,及时释放Image对象。
  • GPU vs CPU:图像后处理(比如滤镜、美颜)用GPU做比CPU快得多。我建议用RenderScript或OpenGL做实时处理,别用Bitmap操作。

避坑指南:我曾经在一个项目里,用CPU做YUV转RGB,结果预览帧率直接掉到10fps。后来换成RenderScript的ScriptIntrinsicYuvToRGB,帧率回到了30fps。记住:能用硬件加速就别用软件。

五、总结一下

摄像头优化,说白了就是三个字:减、控、异

  • :减少分辨率、减少帧率、减少不必要的处理
  • :控制生命周期、控制回调频率、控制内存使用
  • :异步保存、异步处理、异步加载

CameraX帮我们封装了很多底层细节,但理解流水线原理,才能知道瓶颈在哪。嗯,今天就聊到这。如果你在实际项目中遇到摄像头优化的问题,欢迎来交流。


专注资料整理