ShahiM
ShahiM

Reputation: 3268

How to set bitrate and FPS in Media3 Transformer

I want to compress videos for sharing in my Android app. These are my preferred settings:

Resolution: 1280x720
Container: MP4
Video codec: H.264
Video bitrate: 3 Mbit/s
Video FPS: 30
Audio codec: AAC
Audio sampling rate: 44100
Audio bitrate: 128 kbit/s
Move Moov atom to the start to enable streaming (optional)

I do not want to use FFmpeg as it uses software encoders and is very slow. I am getting about 0.7x encoding speed when converting an H.265, 1080p, 120 fps, 40 Mbit/s video to the above settings. Also I want to avoid the hassle of managing an NDK library.

So I eventually settled on Jetpack Media3 Transformer for my use case. And I have this code working perfectly:

val transformationRequest = TransformationRequest.Builder()
    .setVideoMimeType(MimeTypes.VIDEO_H264)
    .setAudioMimeType(MimeTypes.AUDIO_AAC)
    .setResolution(1280)
    .build()

val inputMediaItem = MediaItem.fromUri(uri)
val outputFile = MediaUtils.createTempFile(c,MediaType.VIDEO)

// Create a Transformer
val transformer = Transformer.Builder(c)
    .setTransformationRequest(transformationRequest) // Pass in TransformationRequest
    .addListener(object: Transformer.Listener {
        override fun onTransformationCompleted(inputMediaItem: MediaItem, transformationResult: TransformationResult) {
            super.onTransformationCompleted(inputMediaItem, transformationResult)
            Log.d("ENCODE", "Encoding Complete")
            Log.d("ENCODE", "Output Size: ${MediaUtils.getFileSize(outputFile)}")
        }
    }) // transformerListener is an implementation of Transformer.Listener
    .build()
withContext(Dispatchers.Main) {
    // Start the transformation
    transformer.startTransformation(inputMediaItem, outputFile.absolutePath)
}

This encode completes at 4-5x speed. But I don’t see a way to set anything except the codecs and resolution. The Transformer Documentation does say this:

video encoding works with default settings, but you can also pass custom video encoder settings or replace the encoder factory to get complete control over how encoders are used

But I cannot find any documentation on how to set the video fps and video/audio bitrate.

Upvotes: 4

Views: 1385

Answers (2)

mulan
mulan

Reputation: 176

In Transformer you can use DefaultEncoderFactory to set your desired bitrate:

transformerBuilder.setEncoderFactory(
    new DefaultEncoderFactory.Builder( /* context= */ this)
    .setRequestedVideoEncoderSettings(
        new VideoEncoderSettings.Builder()
        .setBitrate(BITRATE_VALUE)
        .build())
    .build());

Upvotes: 1

tateisu
tateisu

Reputation: 92

// kotlin

@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
suspend fun transcodeVideoMedia3Transformer(
    context: Context,
    info: VideoInfo,
    inFile: File,
    outFile: File,
    resizeConfig: MovieResizeConfig,
    onProgress: (Float) -> Unit,
): File = try {
    withContext(AppDispatchers.MainImmediate) {

        when (resizeConfig.mode) {
            MovideResizeMode.No -> return@withContext inFile
            MovideResizeMode.Always -> Unit
            MovideResizeMode.Auto -> {
                if (!resizeConfig.isTranscodeRequired(info)) {
                    log.i("transcodeVideoMedia3Transformer: transcode not required.")
                    return@withContext inFile
                }
            }
        }

        val srcMediaItem = MediaItem.fromUri(Uri.fromFile(inFile))
        val editedMediaItem = EditedMediaItem.Builder(srcMediaItem).apply {

            // 入力のフレームレートが高すぎるなら制限する
            if (info.frameRatio != null && info.frameRatio > resizeConfig.limitFrameRate) {
                setFrameRate(resizeConfig.limitFrameRate)
            }

            // 入力のピクセルサイズが大きすぎるなら制限する
            if (info.size.w > 0 && info.size.h > 0 &&
                info.squarePixels > resizeConfig.limitSquarePixels
            ) {
                val aspect = info.size.w.toFloat() / info.size.h.toFloat()
                val newW = sqrt(resizeConfig.limitSquarePixels * aspect)
                val newH = sqrt(resizeConfig.limitSquarePixels / aspect)
                val scale = max(newW, newH) / max(1, max(info.size.w, info.size.h))
                val effects = Effects(
                    /* audioProcessors */ emptyList(),
                    /* videoEffects */ listOf(
                        ScaleAndRotateTransformation.Builder().apply {
                            setScale(scale, scale)
                        }.build()
                    )
                )
                setEffects(effects)
            }
        }.build()

        val request = TransformationRequest.Builder().apply {
            setVideoMimeType(MimeTypes.VIDEO_H264)
            setAudioMimeType(MimeTypes.AUDIO_AAC)
            // ビットレートがないな…
        }.build()

        // 完了検知
        val completed = AtomicBoolean(false)
        val error = AtomicReference<Throwable>(null)
        val listener = object : Transformer.Listener {
            override fun onCompleted(composition: Composition, exportResult: ExportResult) {
                super.onCompleted(composition, exportResult)
                completed.compareAndSet(false, true)
            }

            override fun onError(
                composition: Composition,
                exportResult: ExportResult,
                exportException: ExportException,
            ) {
                super.onError(composition, exportResult, exportException)
                error.compareAndSet(null, exportException)
            }
        }

        val videoEncoderSettings =  VideoEncoderSettings.Builder().apply{
            setBitrate(resizeConfig.limitBitrate.clip(100_000L,Int.MAX_VALUE.toLong()).toInt())
        }.build()

        val encoderFactory = DefaultEncoderFactory.Builder(context).apply{
            setRequestedVideoEncoderSettings(videoEncoderSettings)
            // missing setRequestedAudioEncoderSettings
        }.build()

        // 開始
        val transformer = Transformer.Builder(context).apply {
            setEncoderFactory(encoderFactory)
            setTransformationRequest(request)
            addListener(listener)
        }.build()
        transformer.start(editedMediaItem, outFile.canonicalPath)

        val progressHolder = ProgressHolder()
        while (!completed.get()) {
            error.get()?.let { throw it }
            val progress = when (transformer.getProgress(progressHolder)) {
                Transformer.PROGRESS_STATE_NOT_STARTED -> 0f
                else -> progressHolder.progress.toFloat() / 100f
            }
            onProgress(progress)
            delay(1000L)
        }
        outFile
    }
} catch (ex: Throwable) {
    log.w("delete outFile due to error.")
    outFile.delete()
    throw ex
}

Upvotes: 0

Related Questions