feat: Add customizable Video Bit Rate (videoBitRate) (#1882)

* feat: Add `videoBitRate` option to `RecordVideoOptions`

* feat: Implement `videoBitRate` on iOS

* feat: Implement `videoBitRate` on Android

* chore: Format

* docs: Separate recording and photo docs

* docs: Fix links

* docs: Add docs about bitrate and quality

* docs: Add blob

* fix: Don't use inline style for CI

* fix: Correctly log default bitRate

* fix: Fix typo

* fix: Calculate default bit-rate on Android depending on resolution

* Update RecordingSession.kt
This commit is contained in:
Marc Rousavy
2023-09-29 15:27:09 +02:00
committed by GitHub
parent 1c8c081e11
commit 902dc634a4
14 changed files with 294 additions and 64 deletions

View File

@@ -33,6 +33,10 @@ suspend fun CameraView.startRecording(options: ReadableMap, onRecordCallback: Ca
if (options.hasKey("fileType")) {
fileType = VideoFileType.fromUnionValue(options.getString("fileType"))
}
var bitRate: Double? = null
if (options.hasKey("videoBitRate")) {
bitRate = options.getDouble("videoBitRate")
}
val callback = { video: RecordingSession.Video ->
val map = Arguments.createMap()
@@ -44,7 +48,7 @@ suspend fun CameraView.startRecording(options: ReadableMap, onRecordCallback: Ca
val errorMap = makeErrorMap(error.code, error.message)
onRecordCallback(null, errorMap)
}
cameraSession.startRecording(audio == true, codec, fileType, callback, onError)
cameraSession.startRecording(audio == true, codec, fileType, bitRate, callback, onError)
}
@SuppressLint("RestrictedApi")

View File

@@ -279,6 +279,7 @@ class CameraSession(
enableAudio: Boolean,
codec: VideoCodec,
fileType: VideoFileType,
bitRate: Double?,
callback: (video: RecordingSession.Video) -> Unit,
onError: (error: RecorderError) -> Unit
) {
@@ -287,7 +288,8 @@ class CameraSession(
val outputs = outputs ?: throw CameraNotReadyError()
val videoOutput = outputs.videoOutput ?: throw VideoNotEnabledError()
val recording = RecordingSession(context, videoOutput.size, enableAudio, fps, codec, orientation, fileType, callback, onError)
val recording =
RecordingSession(context, videoOutput.size, enableAudio, fps, codec, orientation, fileType, bitRate, callback, onError)
recording.start()
this.recording = recording
}

View File

@@ -22,14 +22,13 @@ class RecordingSession(
private val codec: VideoCodec = VideoCodec.H264,
private val orientation: Orientation,
private val fileType: VideoFileType = VideoFileType.MP4,
videoBitRate: Double? = null,
private val callback: (video: Video) -> Unit,
private val onError: (error: RecorderError) -> Unit
) {
companion object {
private const val TAG = "RecordingSession"
// bits per second
private const val VIDEO_BIT_RATE = 10_000_000
private const val AUDIO_SAMPLING_RATE = 44_100
private const val AUDIO_BIT_RATE = 16 * AUDIO_SAMPLING_RATE
private const val AUDIO_CHANNELS = 1
@@ -37,6 +36,7 @@ class RecordingSession(
data class Video(val path: String, val durationMs: Long)
private val bitRate = videoBitRate ?: getDefaultBitRate()
private val recorder: MediaRecorder
private val outputFile: File
private var startTime: Long? = null
@@ -44,7 +44,6 @@ class RecordingSession(
val surface: Surface = MediaCodec.createPersistentInputSurface()
init {
outputFile = File.createTempFile("mrousavy", fileType.toExtension(), context.cacheDir)
Log.i(TAG, "Creating RecordingSession for ${outputFile.absolutePath}")
@@ -56,11 +55,11 @@ class RecordingSession(
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
recorder.setOutputFile(outputFile.absolutePath)
recorder.setVideoEncodingBitRate(VIDEO_BIT_RATE)
recorder.setVideoEncodingBitRate((bitRate * 1_000_000).toInt())
recorder.setVideoSize(size.height, size.width)
if (fps != null) recorder.setVideoFrameRate(fps)
Log.i(TAG, "Using $codec Video Codec..")
Log.i(TAG, "Using $codec Video Codec at $bitRate Mbps..")
recorder.setVideoEncoder(codec.toVideoCodec())
if (enableAudio) {
Log.i(TAG, "Adding Audio Channel..")
@@ -131,8 +130,22 @@ class RecordingSession(
}
}
private fun getDefaultBitRate(): Double {
var baseBitRate = when (size.width * size.height) {
in 0..640 * 480 -> 2.0
in 640 * 480..1280 * 720 -> 5.0
in 1280 * 720..1920 * 1080 -> 10.0
in 1920 * 1080..3840 * 2160 -> 30.0
in 3840 * 2160..7680 * 4320 -> 100.0
else -> 100.0
}
baseBitRate = baseBitRate / 30.0 * (fps ?: 30).toDouble()
if (this.codec == VideoCodec.H265) baseBitRate *= 0.8
return baseBitRate
}
override fun toString(): String {
val audio = if (enableAudio) "with audio" else "without audio"
return "${size.width} x ${size.height} @ $fps FPS $codec $fileType $orientation RecordingSession ($audio)"
return "${size.width} x ${size.height} @ $fps FPS $codec $fileType $orientation $bitRate Mbps RecordingSession ($audio)"
}
}