You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义Camera2相机实现拍照、录像时预览冻结问题求助

问题根因

你对Camera2的逻辑存在一处关键理解偏差:CameraCaptureSession的输出目标列表只是声明这个会话允许输出到这些Surface,具体每一次捕获请求(包括重复请求)实际输出到哪些Surface,是由你调用addTarget添加的目标决定的。
另外你每次拍照、启动录像都重新创建会话,Camera2的会话创建是耗时操作,过程中旧会话停止、新会话未完全启动,也是预览卡顿冻结的核心原因。你新建会话后只发起了拍照/录像的对应请求,没有把预览Surface加入请求的输出目标,相机不会给预览Surface送帧,自然就会冻住。


具体修改方案

1. 取消频繁重建CaptureSession的逻辑

提前初始化所有需要的输出Surface,一次性创建会话,不需要每次操作都重建:

  • 相机启动阶段就初始化好拍照用的ImageReader,提前拿到它的Surface
  • 录像相关的MediaRecorder Surface如果不想提前初始化占用资源,也可以先预留位置,兼容度更高的做法是初始化时就把三个核心Surface(预览、拍照、录像)都加入会话目标列表,绝大多数设备都支持同时输出这三类流。
    修改后的预览启动逻辑示例:
private fun startCameraPreview(){
    if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
        == PackageManager.PERMISSION_GRANTED &&
        ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.RECORD_AUDIO)
        == PackageManager.PERMISSION_GRANTED){

        lifecycleScope.launch(Dispatchers.Main) {
            // 提前初始化拍照ImageReader,不要每次拍照才创建
            selectMostMatchingImageCaptureSize()
            selectMostMatchingVideoRecordSize()
            captureImageReader = ImageReader.newInstance(
                captureSize!!.width, captureSize!!.height, ImageFormat.JPEG, IMAGE_BUFFER_SIZE
            )

            camera = openCamera(cameraManager, cameraId!!, cameraHandler)
            // 会话目标一次性加全所有需要的输出
            val cameraOutputTargets = listOf(
                viewBinding.cameraSurfaceView.holder.surface,
                captureImageReader.surface,
                mediaRecorder.surface
            )
            session = createCaptureSession(camera, cameraOutputTargets, cameraHandler)

            // 始终保持预览的重复请求运行,这是预览不冻结的核心
            val captureBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            captureBuilder.set(
                CaptureRequest.CONTROL_AF_MODE,
                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
            )
            captureBuilder.addTarget(viewBinding.cameraSurfaceView.holder.surface)
            session.setRepeatingRequest(captureBuilder.build(), null, cameraHandler)
        }
    }else{
        requestCameraPermission()
    }
}

2. 拍照逻辑简化,不需要重建会话

直接在原有会话上发起单次拍照请求即可,不会打断正在运行的预览重复请求:

private fun captureImage(){
    if (captureSize == null) return
    viewModel.documentPreviewSizeToCaptureSizeScaleFactor =
        captureSize!!.width / previewSize!!.width.toFloat()
    lifecycleScope.launch(Dispatchers.IO) {
        // 直接调用拍照逻辑,不需要重建会话
        takePhoto().use { result ->
            // 原有保存图片、写EXIF的逻辑保持不变
        }
    }
}

3. 录像逻辑修改,请求同时添加预览和录像Surface

启动录像时直接替换重复请求即可,不需要重建会话,请求里要同时加两个输出目标:

protected fun recordVideo() {
    lifecycleScope.launch(Dispatchers.IO) {
        val recordRequest = session.device
            .createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
                // 同时添加预览和录像的Surface,两个目标都能收到帧数据
                addTarget(viewBinding.cameraSurfaceView.holder.surface)
                addTarget(mediaRecorder.surface)
            }
        // 替换原有预览请求,现在同时输出到预览和录像
        session.setRepeatingRequest(recordRequest.build(), null, cameraHandler)
        mediaRecorder.start()
    }
}

停止录像时直接切回纯预览请求即可,不需要重建会话:

private fun stopRecording() {
    mediaRecorder.stop()
    mediaRecorder.reset()
    // 切回纯预览的重复请求
    val captureBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
    captureBuilder.set(
        CaptureRequest.CONTROL_AF_MODE,
        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
    )
    captureBuilder.addTarget(viewBinding.cameraSurfaceView.holder.surface)
    session.setRepeatingRequest(captureBuilder.build(), null, cameraHandler)
}

低端设备兼容优化

如果部分老旧设备不支持同时添加三个输出流到会话,可以做降级处理:仅在启动录像的时候重建一次会话(加入预览+录像Surface),停止录像后再重建会话回到预览+拍照Surface的配置,比你原有每次拍照都重建会话的方案卡顿次数会少很多。另外要确认你选择的预览、拍照、录像分辨率都是设备支持的组合,可以通过CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP查询设备支持的流组合,避免选到不支持的配置导致输出异常。


内容的提问来源于stack exchange,提问作者Chathuranga Shan Jayarathna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 18:06:02