Search code examples
androidandroid-camera2

Camera2 recording video, Screen freeze after pressing the record button (only when using front camera and on certain devices)


I have surfed all the internet and didn't find any solution, I am using Camera2 api to record a video from my front camera, I have tested on multiple devices and its working fine, but when I tried on my Samsung Galaxy 3, after I press the record button sometimes the recording work, and sometimes the camera preview freeze, you can find below the code I have implemented

  1. Create Preview and Record request by lazy loading
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. I am using AutoFitSurfaceView, onSurfaceCreated I'm doing the following:
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() function look like this
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() looks like the following
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() looks like this
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()
    }

Explanation: first initializeCamera() is called and in this function I am setting the Camera and the previewSession and preparing the MediaRecorder to start recording when the user press the record button

After the user press on record button I am doing the following:

  • Closing the previewSession
  • Create the recordSession
  • after the session is successfully configured, I am setting the recordRequest that I am initializing by lazy loading

here is the following code:

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: This code is working when capturing from the back camera as for the front camera its working on some devices and failing OCCASIONALLY on others (device tested that this code fail OCCASIONALLY Samsung Galaxy S3).

Any more information needed, I can gladly provide Thanks in Advance


Solution

  • Since you are preparing the media recorder right away, you could just have one capture session, shared between preview and recording. Then just add in the recording target Surface to the capture request once you want to start recording.

    That avoids the glitch from creating a new capture session, and may be more compatible with the devices you're seeing an issue on. In addition, you might want to look at persistent recording surfaces from MediaCodec, to avoid having to create a new session for the second recording (if that's something you want to support).