diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java index d95785254c..5b277b4f74 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java @@ -88,13 +88,6 @@ public final class Mp4Muxer { /** A builder for {@link Mp4Muxer} instances. */ public static final class Builder { - // TODO: b/262704382 - Optimize the default duration. - /** - * The default fragment duration for the {@linkplain #setFragmentedMp4Enabled(boolean) - * fragmented MP4}. - */ - public static final int DEFAULT_FRAGMENT_DURATION_US = 2_000_000; - private final FileOutputStream fileOutputStream; private @LastFrameDurationBehavior int lastFrameDurationBehavior; @@ -194,6 +187,13 @@ public final class Mp4Muxer { public static final ImmutableList SUPPORTED_AUDIO_SAMPLE_MIME_TYPES = ImmutableList.of(MimeTypes.AUDIO_AAC); + // TODO: b/262704382 - Optimize the default duration. + /** + * The default fragment duration for the {@linkplain Builder#setFragmentedMp4Enabled(boolean) + * fragmented MP4}. + */ + public static final int DEFAULT_FRAGMENT_DURATION_US = 2_000_000; + private final Mp4Writer mp4Writer; private final MetadataCollector metadataCollector; diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java index 2951342b6c..edae7f3326 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java @@ -65,7 +65,9 @@ public class TransformerWithInAppMuxerEndToEndTest { return; } Transformer transformer = - new Transformer.Builder(context).setMuxerFactory(new InAppMuxer.Factory()).build(); + new Transformer.Builder(context) + .setMuxerFactory(new InAppMuxer.Factory.Builder().build()) + .build(); ImmutableList videoEffects = ImmutableList.of(RgbFilter.createGrayscaleFilter()); MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_FILE_ASSET_DIRECTORY + inputFile)); EditedMediaItem editedMediaItem = @@ -85,7 +87,9 @@ public class TransformerWithInAppMuxerEndToEndTest { assumeTrue(checkNotNull(inputFile).equals(H264_MP4)); String testId = "audioEditing_completesSuccessfully"; Transformer transformer = - new Transformer.Builder(context).setMuxerFactory(new InAppMuxer.Factory()).build(); + new Transformer.Builder(context) + .setMuxerFactory(new InAppMuxer.Factory.Builder().build()) + .build(); ChannelMixingAudioProcessor channelMixingAudioProcessor = new ChannelMixingAudioProcessor(); channelMixingAudioProcessor.putChannelMixingMatrix( ChannelMixingMatrix.create(/* inputChannelCount= */ 1, /* outputChannelCount= */ 2)); diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformerWithInAppMuxerEndToEndTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformerWithInAppMuxerEndToEndTest.java index 2a6fd46193..dc91cd7cd8 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformerWithInAppMuxerEndToEndTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformerWithInAppMuxerEndToEndTest.java @@ -49,7 +49,9 @@ public class TransformerWithInAppMuxerEndToEndTest { return; } Transformer transformer = - new Transformer.Builder(context).setMuxerFactory(new InAppMuxer.Factory()).build(); + new Transformer.Builder(context) + .setMuxerFactory(new InAppMuxer.Factory.Builder().build()) + .build(); ImmutableList videoEffects = ImmutableList.of(RgbFilter.createGrayscaleFilter()); MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_AV1_VIDEO_URI_STRING)); EditedMediaItem editedMediaItem = diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/InAppMuxer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/InAppMuxer.java index 8131d9eae5..c7b670d520 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/InAppMuxer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/InAppMuxer.java @@ -33,6 +33,7 @@ import androidx.media3.container.XmpData; import androidx.media3.muxer.Mp4Muxer; import androidx.media3.muxer.Mp4Muxer.TrackToken; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -70,34 +71,76 @@ public final class InAppMuxer implements Muxer { /** {@link Muxer.Factory} for {@link InAppMuxer}. */ public static final class Factory implements Muxer.Factory { - private final long maxDelayBetweenSamplesMs; - private final @Nullable MetadataProvider metadataProvider; - /** - * Creates an instance with {@link Muxer#getMaxDelayBetweenSamplesMs() maxDelayBetweenSamplesMs} - * set to {@link DefaultMuxer.Factory#DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS} and {@link - * #metadataProvider} set to {@code null}. - * - *

If the {@link #metadataProvider} is not set then the {@linkplain Metadata.Entry metadata} - * from the input file is set as it is in the output file. - */ - public Factory() { - this( - /* maxDelayBetweenSamplesMs= */ DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS, - /* metadataProvider= */ null); + /** A builder for {@link Factory} instances. */ + public static final class Builder { + private long maxDelayBetweenSamplesMs; + private @Nullable MetadataProvider metadataProvider; + private boolean fragmentedMp4Enabled; + private int fragmentDurationUs; + + /** Creates a {@link Builder} instance with default values. */ + public Builder() { + maxDelayBetweenSamplesMs = DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS; + fragmentDurationUs = Mp4Muxer.DEFAULT_FRAGMENT_DURATION_US; + } + + /** See {@link Muxer#getMaxDelayBetweenSamplesMs()}. */ + @CanIgnoreReturnValue + public Builder setMaxDelayBetweenSamplesMs(long maxDelayBetweenSamplesMs) { + this.maxDelayBetweenSamplesMs = maxDelayBetweenSamplesMs; + return this; + } + + /** + * Sets an implementation of {@link MetadataProvider}. + * + *

The default value is {@code null}. + * + *

If the value is not set then the {@linkplain Metadata.Entry metadata} from the input + * file is set as it is in the output file. + */ + @CanIgnoreReturnValue + public Builder setMetadataProvider(MetadataProvider metadataProvider) { + this.metadataProvider = metadataProvider; + return this; + } + + /** See {@link Mp4Muxer.Builder#setFragmentedMp4Enabled(boolean)}. */ + @CanIgnoreReturnValue + public Builder setFragmentedMp4Enabled(boolean fragmentedMp4Enabled) { + this.fragmentedMp4Enabled = fragmentedMp4Enabled; + return this; + } + + /** See {@link Mp4Muxer.Builder#setFragmentDurationUs(int)}. */ + @CanIgnoreReturnValue + public Builder setFragmentDurationUs(int fragmentDurationUs) { + this.fragmentDurationUs = fragmentDurationUs; + return this; + } + + /** Builds a {@link Factory} instance. */ + public Factory build() { + return new Factory( + maxDelayBetweenSamplesMs, metadataProvider, fragmentedMp4Enabled, fragmentDurationUs); + } } - /** - * {@link Muxer.Factory} for {@link InAppMuxer}. - * - * @param maxDelayBetweenSamplesMs See {@link Muxer#getMaxDelayBetweenSamplesMs()}. - * @param metadataProvider A {@link MetadataProvider} implementation. If the value is set to - * {@code null} then the {@linkplain Metadata.Entry metadata} from the input file is set as - * it is in the output file. - */ - public Factory(long maxDelayBetweenSamplesMs, @Nullable MetadataProvider metadataProvider) { + private final long maxDelayBetweenSamplesMs; + private final @Nullable MetadataProvider metadataProvider; + private final boolean fragmentedMp4Enabled; + private final int fragmentDurationUs; + + private Factory( + long maxDelayBetweenSamplesMs, + @Nullable MetadataProvider metadataProvider, + boolean fragmentedMp4Enabled, + int fragmentDurationUs) { this.maxDelayBetweenSamplesMs = maxDelayBetweenSamplesMs; this.metadataProvider = metadataProvider; + this.fragmentedMp4Enabled = fragmentedMp4Enabled; + this.fragmentDurationUs = fragmentDurationUs; } @Override @@ -109,7 +152,11 @@ public final class InAppMuxer implements Muxer { throw new MuxerException("Error creating file output stream", e); } - Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(outputStream).build(); + Mp4Muxer mp4Muxer = + new Mp4Muxer.Builder(outputStream) + .setFragmentedMp4Enabled(fragmentedMp4Enabled) + .setFragmentDurationUs(fragmentDurationUs) + .build(); return new InAppMuxer(mp4Muxer, maxDelayBetweenSamplesMs, metadataProvider); } diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/EncodedSampleExporterTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/EncodedSampleExporterTest.java index 481ed519b9..4791fead30 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/EncodedSampleExporterTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/EncodedSampleExporterTest.java @@ -65,7 +65,7 @@ public final class EncodedSampleExporterTest { new TransformationRequest.Builder().build(), new MuxerWrapper( /* outputPath= */ "unused", - new InAppMuxer.Factory(), + new InAppMuxer.Factory.Builder().build(), mock(MuxerWrapper.Listener.class), MuxerWrapper.MUXER_MODE_DEFAULT, /* dropSamplesBeforeFirstVideoSample= */ false), diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java index 99b1ae0597..ef1abd32a4 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java @@ -57,12 +57,15 @@ public class TransformerWithInAppMuxerEndToEndTest { @Test public void transmux_withLocationMetadata_outputMatchesExpected() throws Exception { Muxer.Factory inAppMuxerFactory = - new InAppMuxer.Factory( - DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS, - metadataEntries -> { - metadataEntries.removeIf((Metadata.Entry entry) -> entry instanceof Mp4LocationData); - metadataEntries.add(new Mp4LocationData(/* latitude= */ 45f, /* longitude= */ -90f)); - }); + new InAppMuxer.Factory.Builder() + .setMetadataProvider( + metadataEntries -> { + metadataEntries.removeIf( + (Metadata.Entry entry) -> entry instanceof Mp4LocationData); + metadataEntries.add( + new Mp4LocationData(/* latitude= */ 45f, /* longitude= */ -90f)); + }) + .build(); Transformer transformer = new Transformer.Builder(context) .setClock(new FakeClock(/* isAutoAdvancing= */ true)) @@ -90,9 +93,9 @@ public class TransformerWithInAppMuxerEndToEndTest { String xmpSampleData = "media/xmp/sample_datetime_xmp.xmp"; byte[] xmpData = androidx.media3.test.utils.TestUtil.getByteArray(context, xmpSampleData); Muxer.Factory inAppMuxerFactory = - new InAppMuxer.Factory( - DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS, - metadataEntries -> metadataEntries.add(new XmpData(xmpData))); + new InAppMuxer.Factory.Builder() + .setMetadataProvider(metadataEntries -> metadataEntries.add(new XmpData(xmpData))) + .build(); Transformer transformer = new Transformer.Builder(context) .setClock(new FakeClock(/* isAutoAdvancing= */ true)) @@ -110,17 +113,18 @@ public class TransformerWithInAppMuxerEndToEndTest { @Test public void transmux_withCaptureFps_outputMatchesExpected() throws Exception { Muxer.Factory inAppMuxerFactory = - new InAppMuxer.Factory( - DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS, - metadataEntries -> { - float captureFps = 60.0f; - metadataEntries.add( - new MdtaMetadataEntry( - MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS, - /* value= */ Util.toByteArray(captureFps), - /* localeIndicator= */ 0, - MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32)); - }); + new InAppMuxer.Factory.Builder() + .setMetadataProvider( + metadataEntries -> { + float captureFps = 60.0f; + metadataEntries.add( + new MdtaMetadataEntry( + MdtaMetadataEntry.KEY_ANDROID_CAPTURE_FPS, + /* value= */ Util.toByteArray(captureFps), + /* localeIndicator= */ 0, + MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32)); + }) + .build(); Transformer transformer = new Transformer.Builder(context) .setClock(new FakeClock(/* isAutoAdvancing= */ true)) @@ -145,11 +149,13 @@ public class TransformerWithInAppMuxerEndToEndTest { @Test public void transmux_withCreationTime_outputMatchesExpected() throws Exception { Muxer.Factory inAppMuxerFactory = - new InAppMuxer.Factory( - DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS, - metadataEntries -> - metadataEntries.add( - new Mp4TimestampData(/* creationTimestampSeconds= */ 2_000_000_000L))); + new InAppMuxer.Factory.Builder() + .setMetadataProvider( + metadataEntries -> + metadataEntries.add( + new Mp4TimestampData(/* creationTimestampSeconds= */ 2_000_000_000L))) + .build(); + Transformer transformer = new Transformer.Builder(context) .setClock(new FakeClock(/* isAutoAdvancing= */ true)) @@ -175,26 +181,27 @@ public class TransformerWithInAppMuxerEndToEndTest { @Test public void transmux_withCustomeMetadata_outputMatchesExpected() throws Exception { Muxer.Factory inAppMuxerFactory = - new InAppMuxer.Factory( - DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS, - metadataEntries -> { - String stringKey = "StringKey"; - String stringValue = "StringValue"; - metadataEntries.add( - new MdtaMetadataEntry( - stringKey, - Util.getUtf8Bytes(stringValue), - /* localeIndicator= */ 0, - MdtaMetadataEntry.TYPE_INDICATOR_STRING)); - String floatKey = "FloatKey"; - float floatValue = 600.0f; - metadataEntries.add( - new MdtaMetadataEntry( - floatKey, - Util.toByteArray(floatValue), - /* localeIndicator= */ 0, - MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32)); - }); + new InAppMuxer.Factory.Builder() + .setMetadataProvider( + metadataEntries -> { + String stringKey = "StringKey"; + String stringValue = "StringValue"; + metadataEntries.add( + new MdtaMetadataEntry( + stringKey, + Util.getUtf8Bytes(stringValue), + /* localeIndicator= */ 0, + MdtaMetadataEntry.TYPE_INDICATOR_STRING)); + String floatKey = "FloatKey"; + float floatValue = 600.0f; + metadataEntries.add( + new MdtaMetadataEntry( + floatKey, + Util.toByteArray(floatValue), + /* localeIndicator= */ 0, + MdtaMetadataEntry.TYPE_INDICATOR_FLOAT32)); + }) + .build(); Transformer transformer = new Transformer.Builder(context) .setClock(new FakeClock(/* isAutoAdvancing= */ true))