|  |  | @@ -12,26 +12,31 @@ import com.mrousavy.camera.types.Orientation | 
			
		
	
		
		
			
				
					
					|  |  |  | import com.mrousavy.camera.types.RecordVideoOptions |  |  |  | import com.mrousavy.camera.types.RecordVideoOptions | 
			
		
	
		
		
			
				
					
					|  |  |  | import java.io.File |  |  |  | import java.io.File | 
			
		
	
		
		
			
				
					
					|  |  |  | import java.nio.ByteBuffer |  |  |  | import java.nio.ByteBuffer | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | import kotlin.math.ceil | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | class ChunkedRecordingManager(private val encoder: MediaCodec, private val outputDirectory: File, private val orientationHint: Int, private val iFrameInterval: Int, private val callbacks: CameraSession.Callback) : |  |  |  | class ChunkedRecordingManager(private val encoder: MediaCodec, private val outputDirectory: File, private val orientationHint: Int, private val iFrameInterval: Float, private val callbacks: CameraSession.Callback) : | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |   MediaCodec.Callback() { |  |  |  |   MediaCodec.Callback() { | 
			
		
	
		
		
			
				
					
					|  |  |  |   companion object { |  |  |  |   companion object { | 
			
		
	
		
		
			
				
					
					|  |  |  |     private const val TAG = "ChunkedRecorder" |  |  |  |     private const val TAG = "ChunkedRecorder" | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     private fun roundIntervalLengthUp(fps: Float, interval: Float): Float { | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |       return ceil(fps * interval) / fps | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     fun fromParams( |  |  |  |     fun fromParams( | 
			
		
	
		
		
			
				
					
					|  |  |  |       callbacks: CameraSession.Callback, |  |  |  |       callbacks: CameraSession.Callback, | 
			
		
	
		
		
			
				
					
					|  |  |  |       size: Size, |  |  |  |       size: Size, | 
			
		
	
		
		
			
				
					
					|  |  |  |       enableAudio: Boolean, |  |  |  |       enableAudio: Boolean, | 
			
		
	
		
		
			
				
					
					|  |  |  |       fps: Int? = null, |  |  |  |       fps: Float = 30.0f, | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |       cameraOrientation: Orientation, |  |  |  |       cameraOrientation: Orientation, | 
			
		
	
		
		
			
				
					
					|  |  |  |       bitRate: Int, |  |  |  |       bitRate: Int, | 
			
		
	
		
		
			
				
					
					|  |  |  |       options: RecordVideoOptions, |  |  |  |       options: RecordVideoOptions, | 
			
		
	
		
		
			
				
					
					|  |  |  |       outputDirectory: File, |  |  |  |       outputDirectory: File, | 
			
		
	
		
		
			
				
					
					|  |  |  |       iFrameInterval: Int = 5 |  |  |  |       iFrameInterval: Float = 5.0f | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     ): ChunkedRecordingManager { |  |  |  |     ): ChunkedRecordingManager { | 
			
		
	
		
		
			
				
					
					|  |  |  |       val mimeType = options.videoCodec.toMimeType() |  |  |  |       val mimeType = options.videoCodec.toMimeType() | 
			
		
	
		
		
			
				
					
					|  |  |  |       val cameraOrientationDegrees = cameraOrientation.toDegrees() |  |  |  |       val cameraOrientationDegrees = cameraOrientation.toDegrees() | 
			
		
	
		
		
			
				
					
					|  |  |  |       val recordingOrientationDegrees = (options.orientation ?: Orientation.PORTRAIT).toDegrees(); |  |  |  |       val recordingOrientationDegrees = (options.orientation ?: Orientation.PORTRAIT).toDegrees() | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |       val (width, height) = if (cameraOrientation.isLandscape()) { |  |  |  |       val (width, height) = if (cameraOrientation.isLandscape()) { | 
			
		
	
		
		
			
				
					
					|  |  |  |         size.height to size.width |  |  |  |         size.height to size.width | 
			
		
	
		
		
			
				
					
					|  |  |  |       } else { |  |  |  |       } else { | 
			
		
	
	
		
		
			
				
					
					|  |  | @@ -42,25 +47,25 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |       val codec = MediaCodec.createEncoderByType(mimeType) |  |  |  |       val codec = MediaCodec.createEncoderByType(mimeType) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |       val roundedInterval = roundIntervalLengthUp(fps, iFrameInterval) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |       // Set some properties. Failing to specify some of these can cause the MediaCodec |  |  |  |       // Set some properties. Failing to specify some of these can cause the MediaCodec | 
			
		
	
		
		
			
				
					
					|  |  |  |       // configure() call to throw an unhelpful exception. |  |  |  |       // configure() call to throw an unhelpful exception. | 
			
		
	
		
		
			
				
					
					|  |  |  |       format.setInteger( |  |  |  |       format.setInteger( | 
			
		
	
		
		
			
				
					
					|  |  |  |         MediaFormat.KEY_COLOR_FORMAT, |  |  |  |         MediaFormat.KEY_COLOR_FORMAT, | 
			
		
	
		
		
			
				
					
					|  |  |  |         MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface |  |  |  |         MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface | 
			
		
	
		
		
			
				
					
					|  |  |  |       ) |  |  |  |       ) | 
			
		
	
		
		
			
				
					
					|  |  |  |       fps?.apply { |  |  |  |       format.setFloat(MediaFormat.KEY_FRAME_RATE, fps) | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |         format.setInteger(MediaFormat.KEY_FRAME_RATE, this) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |       } |  |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |       // TODO: Pull this out into configuration |  |  |  |       // TODO: Pull this out into configuration | 
			
		
	
		
		
			
				
					
					|  |  |  |       format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval) |  |  |  |       format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, roundedInterval) | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |       format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate) |  |  |  |       format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate) | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |       Log.d(TAG, "Video Format: $format, camera orientation $cameraOrientationDegrees, recordingOrientation: $recordingOrientationDegrees") |  |  |  |       Log.d(TAG, "Video Format: $format, camera orientation $cameraOrientationDegrees, recordingOrientation: $recordingOrientationDegrees, Set fps: $fps") | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |       // Create a MediaCodec encoder, and configure it with our format.  Get a Surface |  |  |  |       // Create a MediaCodec encoder, and configure it with our format.  Get a Surface | 
			
		
	
		
		
			
				
					
					|  |  |  |       // we can use for input and wrap it with a class that handles the EGL work. |  |  |  |       // we can use for input and wrap it with a class that handles the EGL work. | 
			
		
	
		
		
			
				
					
					|  |  |  |       codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) |  |  |  |       codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) | 
			
		
	
		
		
			
				
					
					|  |  |  |       return ChunkedRecordingManager( |  |  |  |       return ChunkedRecordingManager( | 
			
		
	
		
		
			
				
					
					|  |  |  |         codec, outputDirectory, recordingOrientationDegrees, iFrameInterval, callbacks |  |  |  |         codec, outputDirectory, recordingOrientationDegrees, roundedInterval, callbacks | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |       ) |  |  |  |       ) | 
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  |   } |  |  |  |   } | 
			
		
	
	
		
		
			
				
					
					|  |  | @@ -69,7 +74,7 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu | 
			
		
	
		
		
			
				
					
					|  |  |  |   private var currentFrameNumber: Int = 0 |  |  |  |   private var currentFrameNumber: Int = 0 | 
			
		
	
		
		
			
				
					
					|  |  |  |   private var chunkIndex = -1 |  |  |  |   private var chunkIndex = -1 | 
			
		
	
		
		
			
				
					
					|  |  |  |   private var encodedFormat: MediaFormat? = null |  |  |  |   private var encodedFormat: MediaFormat? = null | 
			
		
	
		
		
			
				
					
					|  |  |  |   private var recording = false; |  |  |  |   private var recording = false | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |   private val targetDurationUs = iFrameInterval * 1000000 |  |  |  |   private val targetDurationUs = iFrameInterval * 1000000 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
	
		
		
			
				
					
					|  |  | @@ -83,15 +88,15 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu | 
			
		
	
		
		
			
				
					
					|  |  |  |   } |  |  |  |   } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |   // Muxer specific |  |  |  |   // Muxer specific | 
			
		
	
		
		
			
				
					
					|  |  |  |   private class MuxerContext(val muxer: MediaMuxer, val filepath: File, val chunkIndex: Int, startTimeUs: Long, encodedFormat: MediaFormat) { |  |  |  |   private class MuxerContext( | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     val muxer: MediaMuxer, val filepath: File, val chunkIndex: Int, | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     val startTimeUs: Long, encodedFormat: MediaFormat | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |   ) { | 
			
		
	
		
		
			
				
					
					|  |  |  |     val videoTrack: Int = muxer.addTrack(encodedFormat) |  |  |  |     val videoTrack: Int = muxer.addTrack(encodedFormat) | 
			
		
	
		
		
			
				
					
					|  |  |  |     val startTimeUs: Long = startTimeUs |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     init { |  |  |  |     init { | 
			
		
	
		
		
			
				
					
					|  |  |  |       muxer.start() |  |  |  |       muxer.start() | 
			
		
	
		
		
			
				
					
					|  |  |  |     } |  |  |  |     } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     fun finish() { |  |  |  |     fun finish() { | 
			
		
	
		
		
			
				
					
					|  |  |  |       muxer.stop() |  |  |  |       muxer.stop() | 
			
		
	
		
		
			
				
					
					|  |  |  |       muxer.release() |  |  |  |       muxer.release() | 
			
		
	
	
		
		
			
				
					
					|  |  | @@ -146,7 +151,7 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu | 
			
		
	
		
		
			
				
					
					|  |  |  |   override fun onInputBufferAvailable(codec: MediaCodec, index: Int) { |  |  |  |   override fun onInputBufferAvailable(codec: MediaCodec, index: Int) { | 
			
		
	
		
		
			
				
					
					|  |  |  |   } |  |  |  |   } | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |   override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, bufferInfo: MediaCodec.BufferInfo) { |  |  |  |   override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, bufferInfo: BufferInfo) { | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     synchronized(this) { |  |  |  |     synchronized(this) { | 
			
		
	
		
		
			
				
					
					|  |  |  |       if (!recording) { |  |  |  |       if (!recording) { | 
			
		
	
		
		
			
				
					
					|  |  |  |         return |  |  |  |         return | 
			
		
	
	
		
		
			
				
					
					|  |  |   |