From dae5ebb820571e8c1886df11d99c15e911eb20ee Mon Sep 17 00:00:00 2001 From: Googler Date: Thu, 24 Apr 2025 11:07:57 -0700 Subject: [PATCH] Update DefaultEncoderFactory to set GOP parameters If set on the requested VideoEncoderSettings, then these parameters are passed into the MediaFormat used by the DefaultEncoderFactory to configure the underlying codec. PiperOrigin-RevId: 751059914 --- .../transformer/DefaultEncoderFactory.java | 28 ++++ .../DefaultEncoderFactoryTest.java | 158 ++++++++++++++++++ 2 files changed, 186 insertions(+) diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java index bf12f0bf51..d8bb593a5d 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultEncoderFactory.java @@ -46,6 +46,7 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.checkerframework.checker.nullness.qual.RequiresNonNull; /** A default implementation of {@link Codec.EncoderFactory}. */ @@ -395,6 +396,33 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { } } + int maxBFrames = supportedVideoEncoderSettings.maxBFrames; + if (SDK_INT >= 29 && maxBFrames != VideoEncoderSettings.NO_VALUE) { + mediaFormat.setInteger(MediaFormat.KEY_MAX_B_FRAMES, maxBFrames); + } + + int numNonBidirectionalTemporalLayers = + supportedVideoEncoderSettings.numNonBidirectionalTemporalLayers; + int numBidirectionalTemporalLayers = + supportedVideoEncoderSettings.numBidirectionalTemporalLayers; + if (SDK_INT >= 25 && numNonBidirectionalTemporalLayers >= 0) { + String temporalSchema; + if (numNonBidirectionalTemporalLayers == 0) { + temporalSchema = "none"; + } else if (numBidirectionalTemporalLayers > 0) { + temporalSchema = + String.format( + Locale.ROOT, + "android.generic.%d+%d", + numNonBidirectionalTemporalLayers, + numBidirectionalTemporalLayers); + } else { + temporalSchema = + String.format(Locale.ROOT, "android.generic.%d", numNonBidirectionalTemporalLayers); + } + mediaFormat.setString(MediaFormat.KEY_TEMPORAL_LAYERING, temporalSchema); + } + return new DefaultCodec( context, encoderSupportedFormat, diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/DefaultEncoderFactoryTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/DefaultEncoderFactoryTest.java index 61b7ecbd69..95b3f0afe4 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/DefaultEncoderFactoryTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/DefaultEncoderFactoryTest.java @@ -337,6 +337,164 @@ public class DefaultEncoderFactoryTest { .isFalse(); } + @Test + @Config(sdk = 29) + public void createForVideoEncoding_withMaxBFrames_configuresEncoderWithMaxBFrames() + throws Exception { + Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); + + DefaultCodec videoEncoder = + new DefaultEncoderFactory.Builder(context) + .setRequestedVideoEncoderSettings( + new VideoEncoderSettings.Builder().setMaxBFrames(3).build()) + .build() + .createForVideoEncoding(requestedVideoFormat, /* logSessionId= */ null); + + assertThat(videoEncoder.getConfigurationMediaFormat().getInteger(MediaFormat.KEY_MAX_B_FRAMES)) + .isEqualTo(3); + } + + @Test + @Config(sdk = 23) + public void createForVideoEncoding_withMaxBFramesOnApi23_doesNotConfigureMaxBFrames() + throws Exception { + Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); + + DefaultCodec videoEncoder = + new DefaultEncoderFactory.Builder(context) + .setRequestedVideoEncoderSettings( + new VideoEncoderSettings.Builder().setMaxBFrames(3).build()) + .build() + .createForVideoEncoding(requestedVideoFormat, /* logSessionId= */ null); + + assertThat(videoEncoder.getConfigurationMediaFormat().containsKey(MediaFormat.KEY_MAX_B_FRAMES)) + .isFalse(); + } + + @Test + @Config(sdk = 29) + public void createForVideoEncoding_withDefaultEncoderSettings_doesNotConfigureMaxBFrames() + throws Exception { + Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); + + DefaultCodec videoEncoder = + new DefaultEncoderFactory.Builder(context) + .build() + .createForVideoEncoding(requestedVideoFormat, /* logSessionId= */ null); + + assertThat(videoEncoder.getConfigurationMediaFormat().containsKey(MediaFormat.KEY_MAX_B_FRAMES)) + .isFalse(); + } + + @Config(sdk = 29) + @Test + public void + createForVideoEncoding_withTemporalLayeringSchemaWithZeroLayers_configuresEncoderWithTemporalLayeringSchema() + throws Exception { + Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); + + DefaultCodec videoEncoder = + new DefaultEncoderFactory.Builder(context) + .setRequestedVideoEncoderSettings( + new VideoEncoderSettings.Builder() + .setTemporalLayers( + /* numNonBidirectionalLayers= */ 0, /* numBidirectionalLayers= */ 0) + .build()) + .build() + .createForVideoEncoding(requestedVideoFormat, /* logSessionId= */ null); + + assertThat( + videoEncoder.getConfigurationMediaFormat().getString(MediaFormat.KEY_TEMPORAL_LAYERING)) + .isEqualTo("none"); + } + + @Config(sdk = 29) + @Test + public void + createForVideoEncoding_withTemporalLayeringSchemaWithoutBidirectionalLayers_configuresEncoderWithTemporalLayeringSchema() + throws Exception { + Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); + + DefaultCodec videoEncoder = + new DefaultEncoderFactory.Builder(context) + .setRequestedVideoEncoderSettings( + new VideoEncoderSettings.Builder() + .setTemporalLayers( + /* numNonBidirectionalLayers= */ 1, /* numBidirectionalLayers= */ 0) + .build()) + .build() + .createForVideoEncoding(requestedVideoFormat, /* logSessionId= */ null); + + assertThat( + videoEncoder.getConfigurationMediaFormat().getString(MediaFormat.KEY_TEMPORAL_LAYERING)) + .isEqualTo("android.generic.1"); + } + + @Config(sdk = 29) + @Test + public void + createForVideoEncoding_withTemporalLayeringSchemaWithBidirectionalLayers_configuresEncoderWithTemporalLayeringSchema() + throws Exception { + Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); + + DefaultCodec videoEncoder = + new DefaultEncoderFactory.Builder(context) + .setRequestedVideoEncoderSettings( + new VideoEncoderSettings.Builder() + .setTemporalLayers( + /* numNonBidirectionalLayers= */ 1, /* numBidirectionalLayers= */ 2) + .build()) + .build() + .createForVideoEncoding(requestedVideoFormat, /* logSessionId= */ null); + + assertThat( + videoEncoder.getConfigurationMediaFormat().getString(MediaFormat.KEY_TEMPORAL_LAYERING)) + .isEqualTo("android.generic.1+2"); + } + + @Config(sdk = 23) + @Test + public void + createForVideoEncoding_withTemporalLayeringSchemaOnApi23_doesNotConfigureTemporalLayeringSchema() + throws Exception { + Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); + + DefaultCodec videoEncoder = + new DefaultEncoderFactory.Builder(context) + .setRequestedVideoEncoderSettings( + new VideoEncoderSettings.Builder() + .setTemporalLayers( + /* numNonBidirectionalLayers= */ 1, /* numBidirectionalLayers= */ 2) + .build()) + .build() + .createForVideoEncoding(requestedVideoFormat, /* logSessionId= */ null); + + assertThat( + videoEncoder + .getConfigurationMediaFormat() + .containsKey(MediaFormat.KEY_TEMPORAL_LAYERING)) + .isFalse(); + } + + @Config(sdk = 29) + @Test + public void + createForVideoEncoding_withDefaultEncoderSettings_doesNotConfigureTemporalLayeringSchema() + throws Exception { + Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); + + DefaultCodec videoEncoder = + new DefaultEncoderFactory.Builder(context) + .build() + .createForVideoEncoding(requestedVideoFormat, /* logSessionId= */ null); + + assertThat( + videoEncoder + .getConfigurationMediaFormat() + .containsKey(MediaFormat.KEY_TEMPORAL_LAYERING)) + .isFalse(); + } + @Test public void createForVideoEncoding_withNoAvailableEncoderFromEncoderSelector_throws() { Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);