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(
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;
@Nullable private AnnexBToAvccConverter annexBToAvccConverter;
private boolean sampleCopyEnabled;
private boolean sampleBatchingEnabled;
private boolean attemptStreamableOutputEnabled;
private @FileFormat int outputFileFormat;
@Nullable private EditableVideoParameters editableVideoParameters;
@ -214,6 +215,7 @@ public final class Mp4Muxer implements Muxer {
lastSampleDurationBehavior =
LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS;
sampleCopyEnabled = true;
sampleBatchingEnabled = true;
attemptStreamableOutputEnabled = true;
outputFileFormat = FILE_FORMAT_DEFAULT;
}
@ -260,6 +262,21 @@ public final class Mp4Muxer implements Muxer {
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
* make the file more efficient to read sequentially.
@ -309,6 +326,7 @@ public final class Mp4Muxer implements Muxer {
lastSampleDurationBehavior,
annexBToAvccConverter == null ? AnnexBToAvccConverter.DEFAULT : annexBToAvccConverter,
sampleCopyEnabled,
sampleBatchingEnabled,
attemptStreamableOutputEnabled,
outputFileFormat,
editableVideoParameters);
@ -322,6 +340,7 @@ public final class Mp4Muxer implements Muxer {
private final @LastSampleDurationBehavior int lastSampleDurationBehavior;
private final AnnexBToAvccConverter annexBToAvccConverter;
private final boolean sampleCopyEnabled;
private final boolean sampleBatchingEnabled;
private final boolean attemptStreamableOutputEnabled;
private final @FileFormat int outputFileFormat;
@Nullable private final EditableVideoParameters editableVideoParameters;
@ -339,6 +358,7 @@ public final class Mp4Muxer implements Muxer {
@LastSampleDurationBehavior int lastFrameDurationBehavior,
AnnexBToAvccConverter annexBToAvccConverter,
boolean sampleCopyEnabled,
boolean sampleBatchingEnabled,
boolean attemptStreamableOutputEnabled,
@FileFormat int outputFileFormat,
@Nullable EditableVideoParameters editableVideoParameters) {
@ -347,6 +367,7 @@ public final class Mp4Muxer implements Muxer {
this.lastSampleDurationBehavior = lastFrameDurationBehavior;
this.annexBToAvccConverter = annexBToAvccConverter;
this.sampleCopyEnabled = sampleCopyEnabled;
this.sampleBatchingEnabled = sampleBatchingEnabled;
this.attemptStreamableOutputEnabled = attemptStreamableOutputEnabled;
this.outputFileFormat = outputFileFormat;
this.editableVideoParameters = editableVideoParameters;
@ -358,6 +379,7 @@ public final class Mp4Muxer implements Muxer {
annexBToAvccConverter,
lastFrameDurationBehavior,
sampleCopyEnabled,
sampleBatchingEnabled,
attemptStreamableOutputEnabled);
editableVideoTracks = new ArrayList<>();
}
@ -415,10 +437,13 @@ public final class Mp4Muxer implements Muxer {
/**
* {@inheritDoc}
*
* <p>Samples are written to the file in batches. If {@link Builder#setSampleCopyEnabled(boolean)
* sample copying} is disabled, the {@code byteBuffer} and the {@code bufferInfo} must not be
* modified after calling this method. Otherwise, they are copied and it is safe to modify them
* after this method returns.
* <p>When sample batching is {@linkplain Mp4Muxer.Builder#setSampleBatchingEnabled(boolean)
* enabled}, provide sample data ({@link ByteBuffer}, {@link BufferInfo}) that won't be modified
* after calling the {@link #writeSampleData(TrackToken, ByteBuffer, BufferInfo)} method, unless
* 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 byteBuffer The encoded sample. The muxer takes ownership of the buffer if {@link
@ -522,6 +547,7 @@ public final class Mp4Muxer implements Muxer {
annexBToAvccConverter,
lastSampleDurationBehavior,
sampleCopyEnabled,
sampleBatchingEnabled,
attemptStreamableOutputEnabled);
}
}

View File

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