CaptureSession创建优化:复用InputConfiguration、预创建Session、减少configure耗时

相机启动慢,很多时候就卡在CaptureSession创建这一步。我做过不少项目,每次排查启动性能,十有八九都能在这里找到优化空间。说白了,CaptureSession的创建就是相机框架在为你准备一条完整的图像处理流水线——从底层硬件到上层应用,中间要经过HAL、Camera Service、SurfaceFlinger等多个模块的握手。

嗯,今天我们就来聊聊怎么把这部分耗时降下来。

CaptureSession创建到底干了什么?

先看一张图,帮你快速理解整个流程:

CaptureSession创建流程与优化点 1. 配置InputConfiguration 2. 创建Session 3. configure流程 优化策略 复用InputConfiguration 减少重复创建开销 预创建Session 提前初始化流水线 减少configure耗时 精简配置项 目标:将Session创建耗时从200ms+降低到50ms以内

整个流程看起来简单,但每一步都可能藏着坑。我见过不少团队,代码里每次打开相机都重新new一个InputConfiguration,然后调用createCaptureSession——结果就是每次启动都慢吞吞的。

复用InputConfiguration

先说说InputConfiguration。这个东西说白了就是你告诉相机框架:我要用什么格式、什么分辨率、什么帧率来干活。很多开发者每次创建Session都重新构建一个,其实完全没必要。

核心思路:如果相机参数没有变化,InputConfiguration对象可以复用。创建这个对象本身不贵,但与之关联的底层资源分配很贵。

我在项目中遇到过这样的情况:同一个相机应用,前后摄像头切换时,InputConfiguration的配置项几乎一模一样,只是摄像头ID不同。但代码里每次都重新new,导致底层反复做格式协商和内存分配。

// ❌ 不推荐:每次都重新创建
private fun createInputConfiguration(): InputConfiguration {
    return InputConfiguration(width, height, format)
}

// ✅ 推荐:复用InputConfiguration
private val cachedInputConfig by lazy {
    InputConfiguration(width, height, format)
}

// 如果参数确实需要变化,用Builder模式管理
class InputConfigManager {
    private var config: InputConfiguration? = null
    
    fun getOrCreate(width: Int, height: Int, format: Int): InputConfiguration {
        if (config == null || 
            config!!.width != width || 
            config!!.height != height || 
            config!!.format != format) {
            config = InputConfiguration(width, height, format)
        }
        return config!!
    }
}

嗯,这里要注意一点:复用InputConfiguration的前提是你的配置参数确实没变。如果每次启动的分辨率、格式都不一样,那复用就没意义了。我建议你在应用启动时就把常用的配置预创建好,放到一个缓存池里。

预创建Session

这个优化点比较有意思。说白了就是:既然用户迟早要打开相机,那我能不能提前把Session建好?

你想想看,很多场景下用户打开相机是预期内的行为。比如在聊天应用里,用户点开拍照按钮之前,其实有很大概率会用到相机。这时候我们就可以在后台提前创建Session。

实践技巧:在Activity的onCreate或者Application初始化时,启动一个低优先级的后台线程去预创建Session。但要注意不要抢占主线程资源。

// 预创建Session的示例
class CameraPreloader(private val context: Context) {
    
    private var preloadedSession: CameraCaptureSession? = null
    
    fun preloadSession(cameraId: String) {
        // 在后台线程执行
        Thread {
            val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
            val characteristics = cameraManager.getCameraCharacteristics(cameraId)
            val config = createPreloadConfig(characteristics)
            
            // 预创建Session但不绑定Surface
            cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
                override fun onOpened(camera: CameraDevice) {
                    camera.createCaptureSession(
                        Arrays.asList(config.surface),
                        object : CameraCaptureSession.StateCallback() {
                            override fun onConfigured(session: CameraCaptureSession) {
                                preloadedSession = session
                            }
                            override fun onConfigureFailed(session: CameraCaptureSession) {
                                // 预创建失败不影响主流程
                            }
                        },
                        null
                    )
                }
                override fun onDisconnected(camera: CameraDevice) {}
                override fun onError(camera: CameraDevice, error: Int) {}
            }, null)
        }.apply { 
            priority = Thread.MIN_PRIORITY
            start()
        }
    }
    
    fun getPreloadedSession(): CameraCaptureSession? {
        return preloadedSession
    }
}

我曾经在一个社交App里做过这个优化。用户从首页到相机页面的平均耗时从800ms降到了350ms。说白了,就是让用户感觉相机是「秒开」的。

注意:预创建Session会占用相机硬件资源。如果设备只有单个摄像头,预创建期间其他应用可能无法使用相机。建议只在确定用户即将使用相机的场景下做预创建,并且设置超时机制。

减少configure耗时

configure这一步是CaptureSession创建中最耗时的环节。它要做什么?底层要重新配置ISP管线、分配buffer、建立Surface通路。每一步都是实打实的硬件操作。

怎么减少这个耗时?我总结了几个实战经验:

优化手段 原理 效果
减少Surface数量 每个Surface对应一路输出,减少Surface就减少配置工作量 可降低30-50ms
固定输出尺寸 避免每次重新协商尺寸,使用预定义的常用尺寸 可降低20-40ms
复用OutputConfiguration OutputConfiguration对象复用,减少底层重建 可降低10-20ms
延迟添加非关键Surface 先创建核心Session,后续再添加其他输出 可降低50-80ms

我个人习惯是在创建Session时只添加必要的Surface。比如预览和拍照,如果拍照不是立即需要,可以先只配预览Surface,等用户点击拍照时再动态添加。

// 分步创建Session的示例
class SessionCreator {
    
    // 第一步:只创建预览Session
    fun createPreviewSession(camera: CameraDevice, previewSurface: Surface): CameraCaptureSession {
        val outputs = Arrays.asList(OutputConfiguration(previewSurface))
        val config = SessionConfiguration(SessionConfiguration.SESSION_REGULAR, outputs, 
            Executors.newSingleThreadExecutor(), object : CameraCaptureSession.StateCallback() {
                override fun onConfigured(session: CameraCaptureSession) {
                    // 预览Session就绪
                }
                override fun onConfigureFailed(session: CameraCaptureSession) {
                    // 处理失败
                }
            })
        camera.createCaptureSession(config)
    }
    
    // 第二步:动态添加拍照Surface
    fun addCaptureSurface(session: CameraCaptureSession, captureSurface: Surface) {
        // 使用session的updateOutputConfiguration方法
        // 或者重新创建Session(如果设备不支持动态更新)
    }
}

这里有个坑:不是所有设备都支持动态添加Surface。我在某款低端机上就踩过这个坑——调用updateOutputConfiguration直接抛异常。所以一定要做好兼容性判断。

避坑指南:我曾经在项目里一股脑把所有Surface都塞进Session,结果configure耗时飙到300ms。后来改成按需添加,预览阶段只配预览Surface,拍照时再重新创建Session(复用InputConfiguration),整体耗时降到了80ms以内。

实战总结

这三个优化点,说白了就是三板斧:

  • 复用InputConfiguration——减少对象创建和底层资源分配
  • 预创建Session——把耗时操作提前到用户感知不到的时候
  • 减少configure耗时——精简配置,按需添加Surface

嗯,我建议你在做优化之前,先用CameraTrace工具打点,看看自己的Session创建到底卡在哪一步。不同设备、不同Android版本的表现差异很大,对症下药才是关键。

最后说一句:CaptureSession创建优化不是一蹴而就的事。我见过有些团队一上来就想把200ms优化到20ms,结果折腾半天发现硬件瓶颈摆在那里。合理的优化目标应该是:让用户感觉不到等待,而不是追求极致的数字。


公众号:蓝海资料掘金营,微信deep3321