Search code examples
androidkotlinandroid-cameraandroid-camera2mediarecorder

MediaRecorder stop() function fails


class MainActivity : AppCompatActivity() {
    // Thread handlers for Camera2 API threads
    lateinit var handler: Handler
    lateinit var handlerThread: HandlerThread

    lateinit var backgroundHandler: Handler
    lateinit var backgroundHandlerThread: HandlerThread

    // Camera display and management objects
    lateinit var cameraManager : CameraManager
    lateinit var textureView: TextureView
    lateinit var cameraCaptureSession: CameraCaptureSession
    lateinit var cameraDevice: CameraDevice

    // Used for reading, writing, and saving
    lateinit var imageReader: ImageReader

    private lateinit var updateButton: Button

    // To be used to modify camera request settings
    lateinit var capReq: CaptureRequest.Builder

    // Variables used to manage capture request settings
    private var flashOn : Boolean = false

    private lateinit var builder : AlertDialog.Builder

    private lateinit var mediaRecorder: MediaRecorder
    private lateinit var videoFilePath: String

    private var recording = false

    private lateinit var psurface: Surface

    private lateinit var captureRequestBuilder : CaptureRequest.Builder
    private lateinit var recordingSurface : Surface
    private lateinit var previewSurface : Surface

    @RequiresApi(Build.VERSION_CODES.S)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // TODO: THERE IS NO NEED TO REPEATEDLY CALL PERMS, ITS ANNOYING IN THE LOGS
        get_permissions()


        textureView = findViewById(R.id.textureView)
        textureView.surfaceTextureListener = surfaceTextureListener
        cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
        // Logging some stuff
        var characteristics = cameraManager.getCameraCharacteristics(cameraManager.cameraIdList[0])
        Log.d("CM INFO: RANGE", characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE).toString())
        Log.d("CM INFO: STEP", characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP).toString())


        handlerThread = HandlerThread("videoThread")
        handlerThread.start()
        handler = Handler((handlerThread).looper)

        backgroundHandlerThread = HandlerThread("CameraVideoThread")
        backgroundHandlerThread.start()
        backgroundHandler = Handler(backgroundHandlerThread.looper)

        imageReader = ImageReader.newInstance(1080, 1920, ImageFormat.JPEG, 3)
        imageReader.setOnImageAvailableListener(object: ImageReader.OnImageAvailableListener {
            @RequiresApi(Build.VERSION_CODES.O)
            override fun onImageAvailable(reader: ImageReader?) {
                GlobalScope.launch {
                    writeImage(reader)

                    Log.d("Coroutine Launched", "Coroutine Launched")
                }
                Toast.makeText(this@MainActivity, "Image Captured", Toast.LENGTH_SHORT).show()
            }

        }, handler)


        findViewById<Button>(R.id.capture).apply {
            setOnClickListener {
                Log.d("BUTTON CLICKED", "BUTTON CLICKED")
                    capReq = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
                    capReq.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
//                    capReq.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH)
                    capReq.addTarget(imageReader.surface)

                    cameraCaptureSession.capture(capReq.build(), null, null)
//                caReqV = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
//                capReq.addTarget(imageReader.surface)
//                cameraCaptureSession.capture(capReq.build(), null, handler)
                Log.d("Picture Taken", "Picture Taken")
            }
        }

        val recordButton = findViewById<Button>(R.id.record);
        findViewById<Button>(R.id.record).apply {
            setOnClickListener {
//                if (mediaRecorder == null) {
//                    setUpMediaRecorder()
//                }
                if (recording) {
                    try {
                       mediaRecorder.stop()
                    } catch(e: IllegalStateException){
                        e.printStackTrace()
                    }

                    mediaRecorder.reset()

                    recordButton.text = "Record"
                    recording = false

                    Log.d("BUTTON CLICKED", "RECORDING STOPPED")
                } else {
                    Log.d("BUTTON CLICKED", "RECORDING STARTED")
                    recording = true
                    recordButton.text = "Recording..."

                    setUpMediaRecorder()
                    startRecording()
                }
            }
        }



        fun onDestory() {
            super.onDestroy()
            cameraDevice.close()
            handler.removeCallbacksAndMessages(null)
            handlerThread.quitSafely()
        }
    }

    private val surfaceTextureListener = object: TextureView.SurfaceTextureListener {
            override fun onSurfaceTextureAvailable(
                surface: SurfaceTexture,
                width: Int,
                height: Int
            ) {
                open_camera()
            }

            override fun onSurfaceTextureSizeChanged(
                surface: SurfaceTexture,
                width: Int,
                height: Int
            ) {
                TODO("Not yet implemented")
            }

            override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
                return false
            }

            override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
                return
            }

        }

    fun startRecording() {
        var surface = Surface(textureView.surfaceTexture)
//        recordingSurface = mediaRecorder.surface
        captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
        captureRequestBuilder.addTarget(surface)

        cameraDevice.createCaptureSession(listOf(surface), object : CameraCaptureSession.StateCallback() {
            override fun onConfigureFailed(session: CameraCaptureSession) {

            }

            override fun onConfigured(session: CameraCaptureSession) {
                cameraCaptureSession = session
                captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureResult.CONTROL_AF_MODE_CONTINUOUS_VIDEO)
                try {
                    session.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler)
                    mediaRecorder.start()
                } catch (e: CameraAccessException) {
                    e.printStackTrace()
                    Log.e(TAG, "Failed to start camera preview because it couldn't access the camera")
                } catch (e: IllegalStateException) {
                    e.printStackTrace()
                }
            }
        }, backgroundHandler)
    }

//    private fun showSettings() {
//        Dialog() dialog = new Dialog(this);
//
//    }

    @RequiresApi(Build.VERSION_CODES.O)
    private suspend fun writeImage(reader: ImageReader?){
        var image = reader?.acquireLatestImage()
        var buffer = image!!.planes[0].buffer
        var bytes = ByteArray(buffer.remaining())
        buffer.get(bytes)

        var file_name = DateTimeFormatter.ISO_INSTANT.format(Instant.now())

        var file = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), file_name + ".jpeg")
        var opStream = FileOutputStream(file)

        opStream.write(bytes)
        opStream.close()
        image.close()
    }

    @RequiresApi(Build.VERSION_CODES.S)
    private fun setUpMediaRecorder() {
        mediaRecorder = MediaRecorder()
        // sus
//        mediaRecorder.setPreviewDisplay(Surface(textureView.surfaceTexture));
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mediaRecorder.setVideoSize(1080, 1920)
        mediaRecorder.setVideoFrameRate(30);
        mediaRecorder.setVideoEncodingBitRate(10_000_000)
//        mediaRecorder.setVideoEncoder(MPEG_4_SP)
        var file_name = DateTimeFormatter.ISO_INSTANT.format(Instant.now())
        var file = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), file_name + ".mp4")
        mediaRecorder.setOutputFile(file.absolutePath)

        try {
            mediaRecorder.prepare()
            recordingSurface = mediaRecorder.surface

        } catch(e: IOException) {
            e.printStackTrace()
        }

    }


    @SuppressLint("MissingPermission")
    fun open_camera() {
        cameraManager.openCamera(cameraManager.cameraIdList[0], object: CameraDevice.StateCallback() {
            @RequiresApi(Build.VERSION_CODES.S)
            override fun onOpened(cam: CameraDevice) {
                cameraDevice = cam

                capReq = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)

                // if flash is on
                if (flashOn) {
                    capReq.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH)
                }


                var surface = Surface(textureView.surfaceTexture)
                capReq.addTarget(surface)

                cameraDevice.createCaptureSession(listOf(surface, imageReader.surface), object:CameraCaptureSession.StateCallback() {
                    override fun onConfigured(ccs: CameraCaptureSession) {
                        cameraCaptureSession = ccs

                        ccs.setRepeatingRequest(capReq.build(), null, null)
                    }

                    override fun onConfigureFailed(session: CameraCaptureSession) {
                        return
                    }
                }, handler)
            }

            override fun onDisconnected(camera: CameraDevice) {
                return
            }

            override fun onError(camera: CameraDevice, error: Int) {
                return
            }
        }, handler)
    }

    fun get_permissions() {
        var permissionsList = mutableListOf<String>()

        if(checkSelfPermission(android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)
            permissionsList.add(android.Manifest.permission.CAMERA)
        if(checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
            permissionsList.add(android.Manifest.permission.READ_EXTERNAL_STORAGE)
        if(checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
            permissionsList.add(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)

        if (permissionsList.size > 0) {
            requestPermissions(permissionsList.toTypedArray(), 101)
        }

    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        grantResults.forEach {
            if (it!=PackageManager.PERMISSION_GRANTED) {
                get_permissions()
            }
        }
    }
}

I'm trying to use Camera2 API MediaRecorder to record a video and as the try-catch branches for both prepare() and start() are not throwing an error, so I assume that they are successfully completing. However when I click on the recording button the second time (to stop the recording), the exception error throws a "stop failed" error. I'm not totally sure what is throwing the error.

I've tried searching everywhere online and there just isn't any applicable info or knowledgeable people on the topic of Camera2 API. I suspect it's something to do with my capture session but I'm not sure what.


Solution

  • With the code as-is in your snippet, you don't use recordingSurface in setting up the camera session at all. So no data will actually be sent from the camera to the mediarecorder.

    That will cause an error when you call stop() since MediaRecorder is designed to complain when no real data was received, since it's highly unlikely that was intended.

    So include the recordingSurface in your recording camera capture session and the capture request you build in startRecording, and see if that help.