Allow creating fragmented MP4 file via InAppMuxer

PiperOrigin-RevId: 594431665
This commit is contained in:
sheenachhabra 2023-12-29 04:11:30 -08:00 committed by Copybara-Service
parent e0257f403f
commit 27ae6d974e
6 changed files with 140 additions and 80 deletions

View File

@ -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<String> 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;

View File

@ -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<Effect> 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));

View File

@ -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<Effect> videoEffects = ImmutableList.of(RgbFilter.createGrayscaleFilter());
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_AV1_VIDEO_URI_STRING));
EditedMediaItem editedMediaItem =

View File

@ -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}.
*
* <p>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}.
*
* <p>The default value is {@code null}.
*
* <p>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);
}

View File

@ -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),

View File

@ -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))