| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -12,26 +12,31 @@ import com.mrousavy.camera.types.Orientation
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import com.mrousavy.camera.types.RecordVideoOptions
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import java.io.File
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				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() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  companion object {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private const val TAG = "ChunkedRecorder"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private fun roundIntervalLengthUp(fps: Float, interval: Float): Float {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      return ceil(fps * interval) / fps
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    fun fromParams(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      callbacks: CameraSession.Callback,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      size: Size,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      enableAudio: Boolean,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      fps: Int? = null,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      fps: Float = 30.0f,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      cameraOrientation: Orientation,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      bitRate: Int,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      options: RecordVideoOptions,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      outputDirectory: File,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      iFrameInterval: Int = 5
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      iFrameInterval: Float = 5.0f
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    ): ChunkedRecordingManager {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      val mimeType = options.videoCodec.toMimeType()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      val cameraOrientationDegrees = cameraOrientation.toDegrees()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      val recordingOrientationDegrees = (options.orientation ?: Orientation.PORTRAIT).toDegrees();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      val recordingOrientationDegrees = (options.orientation ?: Orientation.PORTRAIT).toDegrees()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      val (width, height) = if (cameraOrientation.isLandscape()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        size.height to size.width
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      } else {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -42,25 +47,25 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      val codec = MediaCodec.createEncoderByType(mimeType)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      val roundedInterval = roundIntervalLengthUp(fps, iFrameInterval)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // Set some properties. Failing to specify some of these can cause the MediaCodec
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // configure() call to throw an unhelpful exception.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      format.setInteger(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        MediaFormat.KEY_COLOR_FORMAT,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      )
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      fps?.apply {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        format.setInteger(MediaFormat.KEY_FRAME_RATE, this)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      format.setFloat(MediaFormat.KEY_FRAME_RATE, fps)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // 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)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      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
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // 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)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      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 chunkIndex = -1
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  private var encodedFormat: MediaFormat? = null
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  private var recording = false;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  private var recording = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  private val targetDurationUs = iFrameInterval * 1000000
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -83,15 +88,15 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  // 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 startTimeUs: Long = startTimeUs
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    init {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      muxer.start()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    fun finish() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      muxer.stop()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      muxer.release()
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -146,7 +151,7 @@ class ChunkedRecordingManager(private val encoder: MediaCodec, private val outpu
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  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) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      if (!recording) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				 
 |