diff --git a/library/src/main/java/com/mux/video/upload/api/MuxUpload.kt b/library/src/main/java/com/mux/video/upload/api/MuxUpload.kt index eba1b7f8..d94c2786 100644 --- a/library/src/main/java/com/mux/video/upload/api/MuxUpload.kt +++ b/library/src/main/java/com/mux/video/upload/api/MuxUpload.kt @@ -5,6 +5,8 @@ import android.util.Log import androidx.annotation.MainThread import com.mux.video.upload.MuxUploadSdk import com.mux.video.upload.api.MuxUpload.Builder +import com.mux.video.upload.internal.InputStandardization +import com.mux.video.upload.internal.MaximumResolution import com.mux.video.upload.internal.UploadInfo import com.mux.video.upload.internal.update import kotlinx.coroutines.* @@ -356,7 +358,6 @@ class MuxUpload private constructor( inputFile = videoFile, chunkSize = 8 * 1024 * 1024, // GCP recommends at least 8M chunk size retriesPerChunk = 3, - standardizationRequested = true, optOut = false, uploadJob = null, statusFlow = null, @@ -370,13 +371,23 @@ class MuxUpload private constructor( return this } + /** + * If requested, the Upload SDK will try to standardize the input file in order to optimize it + * for use with Mux Video + */ + @Suppress("unused") + fun standardizationRequested(enabled: Boolean, maxResolution: MaximumResolution): Builder { + uploadInfo.update(InputStandardization(enabled, maxResolution)) + return this + } + /** * If requested, the Upload SDK will try to standardize the input file in order to optimize it * for use with Mux Video */ @Suppress("unused") fun standardizationRequested(enabled: Boolean): Builder { - uploadInfo.update(standardizationRequested = enabled) + uploadInfo.update(InputStandardization(enabled)) return this } diff --git a/library/src/main/java/com/mux/video/upload/internal/TranscoderContext.kt b/library/src/main/java/com/mux/video/upload/internal/TranscoderContext.kt index f5b5830d..38448c84 100644 --- a/library/src/main/java/com/mux/video/upload/internal/TranscoderContext.kt +++ b/library/src/main/java/com/mux/video/upload/internal/TranscoderContext.kt @@ -28,12 +28,14 @@ internal class TranscoderContext private constructor( ) { private val logger get() = MuxUploadSdk.logger - val MAX_ALLOWED_BITRATE = 8000000 - val MAX_ALLOWED_FRAMERATE = 120; - val MAX_ALLOWED_WIDTH = 1920 - val MAX_ALLOWED_HEIGTH = 1080 + val MAX_ALLOWED_BITRATE: Int + val MAX_ALLOWED_FRAMERATE = 120 + val MIN_ALLOWED_FRAMERATE = 5 + val MAX_ALLOWED_WIDTH: Int + val MAX_ALLOWED_HEIGTH: Int val OPTIMAL_FRAMERATE = 30 val I_FRAME_INTERVAL = 5 // in seconds + val MAX_ALLOWED_I_FRAME_INTERVAL: Int val OUTPUT_SAMPLERATE = 48000 val OUTPUT_NUMBER_OF_CHANNELS = 2 val OUTPUT_AUDIO_BITRATE = 96000 @@ -60,6 +62,7 @@ internal class TranscoderContext private constructor( private var targetedWidth = -1 private var targetedHeight = -1 private var targetedFramerate = -1 + private var targetedIFrameInterval = -1 private var targetedBitrate = -1 private var scaledSizeYuv: Nv12Buffer? = null private var resampleCreated = false @@ -100,6 +103,20 @@ internal class TranscoderContext private constructor( private var inputFileDurationMs:Long = 0 // Ms private var errorDescription = "" + init { + if (uploadInfo.inputStandardization.maximumResolution == MaximumResolution.Preset3840x2160) { + this.MAX_ALLOWED_BITRATE = 20000000 + this.MAX_ALLOWED_I_FRAME_INTERVAL = 10 + this.MAX_ALLOWED_WIDTH = 4096 + this.MAX_ALLOWED_HEIGTH = 4096 + } else { + this.MAX_ALLOWED_BITRATE = 8000000 + this.MAX_ALLOWED_I_FRAME_INTERVAL = 5 + this.MAX_ALLOWED_WIDTH = uploadInfo.inputStandardization.maximumResolution.width + this.MAX_ALLOWED_HEIGTH = uploadInfo.inputStandardization.maximumResolution.height + } + } + companion object { const val LOG_TAG = "TranscoderContext" @@ -212,7 +229,7 @@ internal class TranscoderContext private constructor( } inputFramerate = format.getIntegerCompat(MediaFormat.KEY_FRAME_RATE, -1) targetedFramerate = OPTIMAL_FRAMERATE - if (inputFramerate > MAX_ALLOWED_FRAMERATE) { + if (inputFramerate > MAX_ALLOWED_FRAMERATE || inputFramerate < MIN_ALLOWED_FRAMERATE) { logger.v( LOG_TAG, "Should standardize because the input frame rate is too high" @@ -223,6 +240,13 @@ internal class TranscoderContext private constructor( } else { targetedFramerate = inputFramerate } + val iFrameInterval = format.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL) + if (iFrameInterval > MAX_ALLOWED_I_FRAME_INTERVAL) { + shouldStandardize = true + targetedIFrameInterval = I_FRAME_INTERVAL + } else { + targetedIFrameInterval = iFrameInterval + } videoTrackIndex = i; inputVideoFormat = format; extractor.selectTrack(i) @@ -316,7 +340,7 @@ internal class TranscoderContext private constructor( ) outputVideoFormat!!.setInteger("slice-height", targetedHeight + targetedHeight/2); outputVideoFormat!!.setInteger("stride", targetedWidth); - outputVideoFormat!!.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL) + outputVideoFormat!!.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, targetedIFrameInterval) outputVideoFormat!!.setInteger( MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR diff --git a/library/src/main/java/com/mux/video/upload/internal/UploadInfo.kt b/library/src/main/java/com/mux/video/upload/internal/UploadInfo.kt index 7e6d70cf..0c86e925 100644 --- a/library/src/main/java/com/mux/video/upload/internal/UploadInfo.kt +++ b/library/src/main/java/com/mux/video/upload/internal/UploadInfo.kt @@ -7,6 +7,46 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.flow.StateFlow import java.io.File +@Suppress("unused") +enum class MaximumResolution(val width: Int, val height: Int) { + /** + * By default the standardized input will be + * scaled down to 1920x1080 (1080p) from a larger + * size. Inputs with smaller dimensions won't be + * scaled up. + */ + Default(1920, 1080), + + /** + * The standardized input will be scaled down + * to 1280x720 (720p) from a larger size. Inputs + * with smaller dimensions won't be scaled up. + */ + Preset1280x720(1280, 720), // 720p + + /** + * The standardized input will be scaled down + * to 1920x1080 (1080p) from a larger size. Inputs + * with smaller dimensions won't be scaled up. + */ + Preset1920x1080(1920, 1080), // 1080p + + /** + * The standardized input will be scaled down + * to 3840x2160 (2160p/4K) from a larger size. + * Inputs with smaller dimensions won't be scaled + * up. + */ + Preset3840x2160(3840, 2160) // 2160p +} + +data class InputStandardization( + @JvmSynthetic internal val standardizationRequested: Boolean = true, + @JvmSynthetic internal val maximumResolution: MaximumResolution = MaximumResolution.Default, +) { + +} + /** * This object is the SDK's internal representation of an upload that is in-progress. The public * object is [MuxUpload], which is backed by an instance of this object. @@ -18,7 +58,7 @@ import java.io.File * Job and Flows populated */ internal data class UploadInfo( - @JvmSynthetic internal val standardizationRequested: Boolean = true, + @JvmSynthetic internal val inputStandardization: InputStandardization = InputStandardization(), @JvmSynthetic internal val remoteUri: Uri, @JvmSynthetic internal val inputFile: File, @JvmSynthetic internal val standardizedFile: File? = null, @@ -28,10 +68,11 @@ internal data class UploadInfo( @JvmSynthetic internal val uploadJob: Deferred>?, @JvmSynthetic internal val statusFlow: StateFlow?, ) { - fun isRunning(): Boolean = /*uploadJob?.isActive*/ + fun isRunning(): Boolean = statusFlow?.value?.let { it is UploadStatus.Uploading || it is UploadStatus.Started || it is UploadStatus.Preparing } ?: false + fun isStandardizationRequested(): Boolean = inputStandardization.standardizationRequested } /** @@ -40,7 +81,7 @@ internal data class UploadInfo( */ @JvmSynthetic internal fun UploadInfo.update( - standardizationRequested: Boolean = this.standardizationRequested, + inputStandardization: InputStandardization = InputStandardization(), remoteUri: Uri = this.remoteUri, file: File = this.inputFile, standardizedFile: File? = this.standardizedFile, @@ -50,7 +91,7 @@ internal fun UploadInfo.update( uploadJob: Deferred>? = this.uploadJob, statusFlow: StateFlow? = this.statusFlow, ) = UploadInfo( - standardizationRequested, + inputStandardization, remoteUri, file, standardizedFile, diff --git a/library/src/main/java/com/mux/video/upload/internal/UploadJobFactory.kt b/library/src/main/java/com/mux/video/upload/internal/UploadJobFactory.kt index 9b04f590..96ad237f 100644 --- a/library/src/main/java/com/mux/video/upload/internal/UploadJobFactory.kt +++ b/library/src/main/java/com/mux/video/upload/internal/UploadJobFactory.kt @@ -78,7 +78,7 @@ internal class UploadJobFactory private constructor( val startTime = System.currentTimeMillis() try { // See if the file need to be converted to a standard input. - if (uploadInfo.standardizationRequested + if (uploadInfo.isStandardizationRequested() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ) { statusFlow.value = UploadStatus.Preparing diff --git a/library/src/main/java/com/mux/video/upload/internal/UploadMetrics.kt b/library/src/main/java/com/mux/video/upload/internal/UploadMetrics.kt index 4b6c9324..fcbdabd8 100644 --- a/library/src/main/java/com/mux/video/upload/internal/UploadMetrics.kt +++ b/library/src/main/java/com/mux/video/upload/internal/UploadMetrics.kt @@ -169,7 +169,7 @@ internal class UploadMetrics private constructor() { body.put("version", "1") val data = getEventInfo(startTimeMillis, "upload_start_time", endTimeMillis, "upload_end_time", inputFileDurationMs, uploadInfo) - data.put("input_standardization_requested", uploadInfo.standardizationRequested + data.put("input_standardization_requested", uploadInfo.isStandardizationRequested() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) body.put("data", data) sendPost(body) @@ -190,7 +190,7 @@ internal class UploadMetrics private constructor() { body.put("version", "1") val data = getEventInfo(startTimeMillis, "upload_start_time", endTimeMillis, "upload_end_time", inputFileDurationMs, uploadInfo) - data.put("input_standardization_requested", uploadInfo.standardizationRequested + data.put("input_standardization_requested", uploadInfo.isStandardizationRequested() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) data.put("error_description", errorDescription) body.put("data", data)