首页 > 解决方案 > Camera2录制视频,按下录制按钮后屏幕冻结(仅在使用前置摄像头和某些设备时)

问题描述

我已经浏览了所有的互联网并没有找到任何解决方案,我正在使用 Camera2 api 从我的前置摄像头录制视频,我已经在多个设备上进行了测试并且它工作正常,但是当我尝试我的三星 Galaxy 3 后,我按下录制按钮有时录制工作,有时相机预览冻结,你可以在下面找到我已经实现的代码

  1. 通过延迟加载创建预览和记录请求
private val previewRequest: CaptureRequest? by lazy {
        mCaptureSession.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
            addTarget(viewFinder.holder.surface)
        }.build()
    }


    private val recordRequest: CaptureRequest by lazy {
        mCaptureSession.device.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
            addTarget(viewFinder.holder.surface)
            addTarget(mMediaRecorder.surface)
        }.build()
    }
  1. 我正在使用 AutoFitSurfaceView,onSurfaceCreated 我正在执行以下操作:
when (cameraDirection) { // I am getting this variable to see what camera I should open
                    CameraDirection.BACK -> { //getCameraPosition gets the cameraId for the given //LENS_FACING direction
                        mCameraId = getCameraPosition(CameraCharacteristics.LENS_FACING_BACK)
                    }
                    CameraDirection.FRONT -> {
                        mCameraId = getCameraPosition(CameraCharacteristics.LENS_FACING_FRONT)
                    }
                    else -> {
                        mCameraId = getCameraPosition(CameraCharacteristics.LENS_FACING_BACK)
                    }
                }
                characteristics = cameraManager.getCameraCharacteristics(mCameraId!!)

                // Selects appropriate preview size and configures view finder
                mPreviewSize = getPreviewOutputSize(
                    viewFinder.display, characteristics, SurfaceHolder::class.java
                )
                // Selects appropriate video size
                mVideoSize = getPreviewOutputSize(
                    viewFinder.display, characteristics, MediaRecorder::class.java
                )
                viewFinder.setAspectRatio(mPreviewSize.width, mPreviewSize.height)

                // To ensure that size is set, initialize camera in the view's thread
                viewFinder.post {
                    initializeCamera()
                }
  1. initializeCamera() 函数看起来像这样
private fun initializeCamera() = lifecycleScope.launch(Dispatchers.Main) {
        //viewFinder is the AutoFitSurfaceView
        camera = openCamera(cameraManager, mCameraId!!, cameraHandler)
        setupMediaRecorder()

        val targets = listOf(viewFinder.holder.surface)
        camera.createCaptureSession(targets, object : CameraCaptureSession.StateCallback() {
            override fun onConfigured(session: CameraCaptureSession) {
                mCaptureSession = session
                session.setRepeatingRequest(previewRequest!!, null, cameraHandler)
            }

            override fun onConfigureFailed(session: CameraCaptureSession) {

            }

        }, cameraHandler)
    }
  1. openCamera() 如下所示
private suspend fun openCamera(
        manager: CameraManager,
        cameraId: String,
        handler: Handler? = null
    ): CameraDevice = suspendCancellableCoroutine { cont ->
        manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
            override fun onOpened(device: CameraDevice) = cont.resume(device)

            override fun onDisconnected(device: CameraDevice) {
                finish()
            }

            override fun onError(device: CameraDevice, error: Int) {
                val msg = when (error) {
                    ERROR_CAMERA_DEVICE -> "Fatal (device)"
                    ERROR_CAMERA_DISABLED -> "Device policy"
                    ERROR_CAMERA_IN_USE -> "Camera in use"
                    ERROR_CAMERA_SERVICE -> "Fatal (service)"
                    ERROR_MAX_CAMERAS_IN_USE -> "Maximum cameras in use"
                    else -> "Unknown"
                }
                val exc = RuntimeException("Camera $cameraId error: ($error) $msg")
                if (cont.isActive) cont.resumeWithException(exc)
            }
        }, handler)
    }
  1. setupMediaRecorder() 看起来像这样
private fun setupMediaRecorder() {
        mMediaRecorder = MediaRecorder()
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        mMediaRecorder.setOutputFile(outputFile.absolutePath)
        Log.i("CAMERA_INFO", mCameraId!!)
        val profile = CamcorderProfile.get(mCameraId!!.toInt(), CamcorderProfile.QUALITY_LOW)
        Log.i("CAMERA_INFO", "Frame Rate: " + profile.videoFrameRate)
        mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate)
        mMediaRecorder.setVideoFrameRate(profile.videoFrameRate)
        mMediaRecorder.setVideoSize(mPreviewSize.width, mPreviewSize.height)
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
        when (mCameraDirection) {
            CameraDirection.BACK -> {
                mMediaRecorder.setOrientationHint(90)
            }
            CameraDirection.FRONT -> {
                mMediaRecorder.setOrientationHint(270)
            }
            else -> {

            }
        }
        mMediaRecorder.prepare()
    }

说明:首先调用了 initializeCamera(),在这个函数中,我设置了 Camera 和 previewSession,并准备 MediaRecorder 在用户按下录制按钮时开始录制

用户按下录制按钮后,我正在执行以下操作:

这是以下代码:

button_record_video.setOnClickListener {
            mCaptureSession.close() //Closing the previewSession
            try {

                camera.createCaptureSession( //Creating the record session passing the viewFinder surface //and the MediaRecorder session
                    listOf(
                        viewFinder.holder.surface,
                        mMediaRecorder.surface
                    ), object : CameraCaptureSession.StateCallback() {
                        override fun onConfigured(session: CameraCaptureSession) {
                            mCaptureSession = session
                            session.setRepeatingRequest(recordRequest, null, cameraHandler)
                            mMediaRecorder.start()
                        }

                        override fun onConfigureFailed(p0: CameraCaptureSession) {

                        }

                    }, cameraHandler
                )
            } catch (e: Exception) {

            }

        }

PS:此代码在从后置摄像头捕获时有效,对于前置摄像头,它在某些设备上工作并且在其他设备上偶尔失败(设备测试此代码偶尔失败三星 Galaxy S3)。

需要更多信息,我很乐意提前提供感谢

标签: androidandroid-camera2

解决方案


由于您正在准备媒体记录器,因此您可以只进行一个捕获会话,在预览和录制之间共享。然后,一旦您想开始录制,只需将录制目标 Surface 添加到捕获请求中即可。

这样可以避免创建新捕获会话时出现故障,并且可能与您遇到问题的设备更兼容。此外,您可能希望查看来自 MediaCodec 的持久记录表面,以避免必须为第二个记录创建新会话(如果您想要支持的话)。


推荐阅读