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.
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.