Add an API to disable sample batching in Mp4Muxer

Mp4Muxer caches the samples and then writes them in batches.
The new API allows disabling the batching and writing sample
immediately.

PiperOrigin-RevId: 689352771
This commit is contained in:
sheenachhabra 2024-10-24 06:11:16 -07:00 committed by Copybara-Service
parent b36de302f7
commit f181855c5e
5 changed files with 2784 additions and 6 deletions

View File

@ -113,4 +113,63 @@ public class Mp4MuxerEndToEndNonParameterizedAndroidTest {
/*DumpFileAsserts.assertOutput( /*DumpFileAsserts.assertOutput(
context, fakeExtractorOutput, AndroidMuxerTestUtil.getExpectedDumpFilePath(vp9Mp4));*/ context, fakeExtractorOutput, AndroidMuxerTestUtil.getExpectedDumpFilePath(vp9Mp4));*/
} }
@Test
public void createMp4File_withSampleBatchingDisabled_matchesExpected() throws Exception {
@Nullable Mp4Muxer mp4Muxer = null;
try {
mp4Muxer =
new Mp4Muxer.Builder(checkNotNull(outputStream)).setSampleBatchingEnabled(false).build();
mp4Muxer.addMetadataEntry(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
feedInputDataToMuxer(context, mp4Muxer, checkNotNull(H265_HDR10_MP4));
} finally {
if (mp4Muxer != null) {
mp4Muxer.close();
}
}
FakeExtractorOutput fakeExtractorOutput =
TestUtil.extractAllSamplesFromFilePath(
new Mp4Extractor(new DefaultSubtitleParserFactory()), checkNotNull(outputPath));
DumpFileAsserts.assertOutput(
context,
fakeExtractorOutput,
AndroidMuxerTestUtil.getExpectedDumpFilePath("sample_batching_disabled_" + H265_HDR10_MP4));
}
@Test
public void createMp4File_withSampleBatchingAndAttemptStreamableOutputDisabled_matchesExpected()
throws Exception {
@Nullable Mp4Muxer mp4Muxer = null;
try {
mp4Muxer =
new Mp4Muxer.Builder(checkNotNull(outputStream))
.setSampleBatchingEnabled(false)
.setAttemptStreamableOutputEnabled(false)
.build();
mp4Muxer.addMetadataEntry(
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
feedInputDataToMuxer(context, mp4Muxer, checkNotNull(H265_HDR10_MP4));
} finally {
if (mp4Muxer != null) {
mp4Muxer.close();
}
}
FakeExtractorOutput fakeExtractorOutput =
TestUtil.extractAllSamplesFromFilePath(
new Mp4Extractor(new DefaultSubtitleParserFactory()), checkNotNull(outputPath));
DumpFileAsserts.assertOutput(
context,
fakeExtractorOutput,
AndroidMuxerTestUtil.getExpectedDumpFilePath(
"sample_batching_and_attempt_streamable_output_disabled_" + H265_HDR10_MP4));
}
} }

View File

@ -200,6 +200,7 @@ public final class Mp4Muxer implements Muxer {
private @LastSampleDurationBehavior int lastSampleDurationBehavior; private @LastSampleDurationBehavior int lastSampleDurationBehavior;
@Nullable private AnnexBToAvccConverter annexBToAvccConverter; @Nullable private AnnexBToAvccConverter annexBToAvccConverter;
private boolean sampleCopyEnabled; private boolean sampleCopyEnabled;
private boolean sampleBatchingEnabled;
private boolean attemptStreamableOutputEnabled; private boolean attemptStreamableOutputEnabled;
private @FileFormat int outputFileFormat; private @FileFormat int outputFileFormat;
@Nullable private EditableVideoParameters editableVideoParameters; @Nullable private EditableVideoParameters editableVideoParameters;
@ -214,6 +215,7 @@ public final class Mp4Muxer implements Muxer {
lastSampleDurationBehavior = lastSampleDurationBehavior =
LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS; LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS;
sampleCopyEnabled = true; sampleCopyEnabled = true;
sampleBatchingEnabled = true;
attemptStreamableOutputEnabled = true; attemptStreamableOutputEnabled = true;
outputFileFormat = FILE_FORMAT_DEFAULT; outputFileFormat = FILE_FORMAT_DEFAULT;
} }
@ -260,6 +262,21 @@ public final class Mp4Muxer implements Muxer {
return this; return this;
} }
/**
* Sets whether to enable sample batching.
*
* <p>If sample batching is enabled, samples are {@linkplain #writeSampleData(TrackToken,
* ByteBuffer, BufferInfo) written} in batches for each track, otherwise samples are written as
* they arrive.
*
* <p>The default value is {@code true}.
*/
@CanIgnoreReturnValue
public Mp4Muxer.Builder setSampleBatchingEnabled(boolean enabled) {
this.sampleBatchingEnabled = enabled;
return this;
}
/** /**
* Sets whether to attempt to write a file where the metadata is stored at the start, which can * Sets whether to attempt to write a file where the metadata is stored at the start, which can
* make the file more efficient to read sequentially. * make the file more efficient to read sequentially.
@ -309,6 +326,7 @@ public final class Mp4Muxer implements Muxer {
lastSampleDurationBehavior, lastSampleDurationBehavior,
annexBToAvccConverter == null ? AnnexBToAvccConverter.DEFAULT : annexBToAvccConverter, annexBToAvccConverter == null ? AnnexBToAvccConverter.DEFAULT : annexBToAvccConverter,
sampleCopyEnabled, sampleCopyEnabled,
sampleBatchingEnabled,
attemptStreamableOutputEnabled, attemptStreamableOutputEnabled,
outputFileFormat, outputFileFormat,
editableVideoParameters); editableVideoParameters);
@ -322,6 +340,7 @@ public final class Mp4Muxer implements Muxer {
private final @LastSampleDurationBehavior int lastSampleDurationBehavior; private final @LastSampleDurationBehavior int lastSampleDurationBehavior;
private final AnnexBToAvccConverter annexBToAvccConverter; private final AnnexBToAvccConverter annexBToAvccConverter;
private final boolean sampleCopyEnabled; private final boolean sampleCopyEnabled;
private final boolean sampleBatchingEnabled;
private final boolean attemptStreamableOutputEnabled; private final boolean attemptStreamableOutputEnabled;
private final @FileFormat int outputFileFormat; private final @FileFormat int outputFileFormat;
@Nullable private final EditableVideoParameters editableVideoParameters; @Nullable private final EditableVideoParameters editableVideoParameters;
@ -339,6 +358,7 @@ public final class Mp4Muxer implements Muxer {
@LastSampleDurationBehavior int lastFrameDurationBehavior, @LastSampleDurationBehavior int lastFrameDurationBehavior,
AnnexBToAvccConverter annexBToAvccConverter, AnnexBToAvccConverter annexBToAvccConverter,
boolean sampleCopyEnabled, boolean sampleCopyEnabled,
boolean sampleBatchingEnabled,
boolean attemptStreamableOutputEnabled, boolean attemptStreamableOutputEnabled,
@FileFormat int outputFileFormat, @FileFormat int outputFileFormat,
@Nullable EditableVideoParameters editableVideoParameters) { @Nullable EditableVideoParameters editableVideoParameters) {
@ -347,6 +367,7 @@ public final class Mp4Muxer implements Muxer {
this.lastSampleDurationBehavior = lastFrameDurationBehavior; this.lastSampleDurationBehavior = lastFrameDurationBehavior;
this.annexBToAvccConverter = annexBToAvccConverter; this.annexBToAvccConverter = annexBToAvccConverter;
this.sampleCopyEnabled = sampleCopyEnabled; this.sampleCopyEnabled = sampleCopyEnabled;
this.sampleBatchingEnabled = sampleBatchingEnabled;
this.attemptStreamableOutputEnabled = attemptStreamableOutputEnabled; this.attemptStreamableOutputEnabled = attemptStreamableOutputEnabled;
this.outputFileFormat = outputFileFormat; this.outputFileFormat = outputFileFormat;
this.editableVideoParameters = editableVideoParameters; this.editableVideoParameters = editableVideoParameters;
@ -358,6 +379,7 @@ public final class Mp4Muxer implements Muxer {
annexBToAvccConverter, annexBToAvccConverter,
lastFrameDurationBehavior, lastFrameDurationBehavior,
sampleCopyEnabled, sampleCopyEnabled,
sampleBatchingEnabled,
attemptStreamableOutputEnabled); attemptStreamableOutputEnabled);
editableVideoTracks = new ArrayList<>(); editableVideoTracks = new ArrayList<>();
} }
@ -415,10 +437,13 @@ public final class Mp4Muxer implements Muxer {
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* <p>Samples are written to the file in batches. If {@link Builder#setSampleCopyEnabled(boolean) * <p>When sample batching is {@linkplain Mp4Muxer.Builder#setSampleBatchingEnabled(boolean)
* sample copying} is disabled, the {@code byteBuffer} and the {@code bufferInfo} must not be * enabled}, provide sample data ({@link ByteBuffer}, {@link BufferInfo}) that won't be modified
* modified after calling this method. Otherwise, they are copied and it is safe to modify them * after calling the {@link #writeSampleData(TrackToken, ByteBuffer, BufferInfo)} method, unless
* after this method returns. * sample copying is also {@linkplain Mp4Muxer.Builder#setSampleCopyEnabled(boolean) enabled}.
* This ensures data integrity within the batch. If sample copying is {@linkplain
* Mp4Muxer.Builder#setSampleCopyEnabled(boolean) enabled}, it's safe to modify the data after the
* method returns, as the muxer internally creates a sample copy.
* *
* @param trackToken The {@link TrackToken} for which this sample is being written. * @param trackToken The {@link TrackToken} for which this sample is being written.
* @param byteBuffer The encoded sample. The muxer takes ownership of the buffer if {@link * @param byteBuffer The encoded sample. The muxer takes ownership of the buffer if {@link
@ -522,6 +547,7 @@ public final class Mp4Muxer implements Muxer {
annexBToAvccConverter, annexBToAvccConverter,
lastSampleDurationBehavior, lastSampleDurationBehavior,
sampleCopyEnabled, sampleCopyEnabled,
sampleBatchingEnabled,
attemptStreamableOutputEnabled); attemptStreamableOutputEnabled);
} }
} }

View File

@ -43,6 +43,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
/** Writes all media samples into a single mdat box. */ /** Writes all media samples into a single mdat box. */
/* package */ final class Mp4Writer { /* package */ final class Mp4Writer {
private static final long INTERLEAVE_DURATION_US = 1_000_000L; private static final long INTERLEAVE_DURATION_US = 1_000_000L;
// Used for updating the moov box periodically when sample batching is disabled.
private static final long MOOV_BOX_UPDATE_INTERVAL_US = 1_000_000L;
private static final int DEFAULT_MOOV_BOX_SIZE_BYTES = 400_000; private static final int DEFAULT_MOOV_BOX_SIZE_BYTES = 400_000;
private static final String FREE_BOX_TYPE = "free"; private static final String FREE_BOX_TYPE = "free";
@ -51,6 +53,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private final AnnexBToAvccConverter annexBToAvccConverter; private final AnnexBToAvccConverter annexBToAvccConverter;
private final @Mp4Muxer.LastSampleDurationBehavior int lastSampleDurationBehavior; private final @Mp4Muxer.LastSampleDurationBehavior int lastSampleDurationBehavior;
private final boolean sampleCopyEnabled; private final boolean sampleCopyEnabled;
private final boolean sampleBatchingEnabled;
private final List<Track> tracks; private final List<Track> tracks;
private final List<Track> editableVideoTracks; private final List<Track> editableVideoTracks;
private final AtomicBoolean hasWrittenSamples; private final AtomicBoolean hasWrittenSamples;
@ -63,9 +66,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
private long mdatStart; private long mdatStart;
private long mdatEnd; private long mdatEnd;
private long mdatDataEnd; // Always <= mdatEnd private long mdatDataEnd; // Always <= mdatEnd
// Typically written from the end of the mdat box to the end of the file. // Typically written from the end of the mdat box to the end of the file.
private Range<Long> lastMoovWritten; private Range<Long> lastMoovWritten;
// Used for writing moov box periodically when sample batching is disabled.
private long lastMoovWrittenAtSampleTimestampUs;
/** /**
* Creates an instance. * Creates an instance.
@ -79,6 +83,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* AVCC format (which uses length prefixes). * AVCC format (which uses length prefixes).
* @param lastSampleDurationBehavior The {@link Mp4Muxer.LastSampleDurationBehavior}. * @param lastSampleDurationBehavior The {@link Mp4Muxer.LastSampleDurationBehavior}.
* @param sampleCopyEnabled Whether sample copying is enabled. * @param sampleCopyEnabled Whether sample copying is enabled.
* @param sampleBatchingEnabled Whether sample batching is enabled.
* @param attemptStreamableOutputEnabled Whether to attempt to write a streamable output. * @param attemptStreamableOutputEnabled Whether to attempt to write a streamable output.
*/ */
public Mp4Writer( public Mp4Writer(
@ -87,17 +92,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
AnnexBToAvccConverter annexBToAvccConverter, AnnexBToAvccConverter annexBToAvccConverter,
@Mp4Muxer.LastSampleDurationBehavior int lastSampleDurationBehavior, @Mp4Muxer.LastSampleDurationBehavior int lastSampleDurationBehavior,
boolean sampleCopyEnabled, boolean sampleCopyEnabled,
boolean sampleBatchingEnabled,
boolean attemptStreamableOutputEnabled) { boolean attemptStreamableOutputEnabled) {
this.outputFileChannel = fileChannel; this.outputFileChannel = fileChannel;
this.metadataCollector = metadataCollector; this.metadataCollector = metadataCollector;
this.annexBToAvccConverter = annexBToAvccConverter; this.annexBToAvccConverter = annexBToAvccConverter;
this.lastSampleDurationBehavior = lastSampleDurationBehavior; this.lastSampleDurationBehavior = lastSampleDurationBehavior;
this.sampleCopyEnabled = sampleCopyEnabled; this.sampleCopyEnabled = sampleCopyEnabled;
this.sampleBatchingEnabled = sampleBatchingEnabled;
tracks = new ArrayList<>(); tracks = new ArrayList<>();
editableVideoTracks = new ArrayList<>(); editableVideoTracks = new ArrayList<>();
hasWrittenSamples = new AtomicBoolean(false); hasWrittenSamples = new AtomicBoolean(false);
canWriteMoovAtStart = attemptStreamableOutputEnabled; canWriteMoovAtStart = attemptStreamableOutputEnabled;
lastMoovWritten = Range.closed(0L, 0L); lastMoovWritten = Range.closed(0L, 0L);
lastMoovWrittenAtSampleTimestampUs = 0L;
} }
/** /**
@ -141,7 +149,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
public void writeSampleData(Track track, ByteBuffer byteBuffer, BufferInfo bufferInfo) public void writeSampleData(Track track, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws IOException { throws IOException {
track.writeSampleData(byteBuffer, bufferInfo); track.writeSampleData(byteBuffer, bufferInfo);
if (sampleBatchingEnabled) {
doInterleave(); doInterleave();
} else {
writePendingTrackSamples(track);
boolean primaryTrackSampleWritten = tracks.contains(track);
long currentSampleTimestampUs = bufferInfo.presentationTimeUs;
if (primaryTrackSampleWritten
&& canWriteMoovAtStart
&& (currentSampleTimestampUs - lastMoovWrittenAtSampleTimestampUs
>= MOOV_BOX_UPDATE_INTERVAL_US)) {
maybeWriteMoovAtStart();
lastMoovWrittenAtSampleTimestampUs = currentSampleTimestampUs;
}
}
} }
/** /**