Skip to content

Commit 3e57cad

Browse files
SheenaChhabracopybara-github
authored andcommitted
Add API to reserve free space after ftyp
PiperOrigin-RevId: 761536934
1 parent 5586c5c commit 3e57cad

File tree

4 files changed

+109
-8
lines changed

4 files changed

+109
-8
lines changed

libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ public static final class Builder {
208208
private boolean attemptStreamableOutputEnabled;
209209
private @FileFormat int outputFileFormat;
210210
@Nullable private Mp4AtFileParameters mp4AtFileParameters;
211+
private int freeSpaceAfterFtypInBytes;
211212

212213
/**
213214
* Creates a {@link Builder} instance with default values.
@@ -326,6 +327,27 @@ public Mp4Muxer.Builder setMp4AtFileParameters(Mp4AtFileParameters mp4AtFilePara
326327
return this;
327328
}
328329

330+
/**
331+
* Sets the amount of free space (in bytes) to be reserved immediately after the {@code ftyp}
332+
* box (File Type box) in the MP4 file.
333+
*
334+
* <p>The {@code moov} box (Movie Box) is written in the reserved space if {@link
335+
* #setAttemptStreamableOutputEnabled(boolean)} is set to {@code true}, and the size of the
336+
* {@code moov} box is not greater than {@code bytes}. Otherwise, a {@code free} box of the
337+
* requested size is written.
338+
*
339+
* <p>By default 400_000 bytes are reserved if {@link
340+
* #setAttemptStreamableOutputEnabled(boolean)} is set to {@code true}.
341+
*
342+
* <p>This method is experimental and will be renamed or removed in a future release.
343+
*/
344+
@CanIgnoreReturnValue
345+
public Mp4Muxer.Builder experimentalSetFreeSpaceAfterFileTypeBox(int bytes) {
346+
checkArgument(bytes >= 0);
347+
this.freeSpaceAfterFtypInBytes = bytes;
348+
return this;
349+
}
350+
329351
/** Builds an {@link Mp4Muxer} instance. */
330352
public Mp4Muxer build() {
331353
checkArgument(
@@ -341,7 +363,8 @@ public Mp4Muxer build() {
341363
sampleBatchingEnabled,
342364
attemptStreamableOutputEnabled,
343365
outputFileFormat,
344-
mp4AtFileParameters);
366+
mp4AtFileParameters,
367+
freeSpaceAfterFtypInBytes);
345368
}
346369
}
347370

@@ -379,6 +402,7 @@ public Mp4Muxer build() {
379402
private final boolean sampleCopyEnabled;
380403
private final boolean sampleBatchingEnabled;
381404
private final boolean attemptStreamableOutputEnabled;
405+
private final int freeSpaceAfterFtypInBytes;
382406
private final @FileFormat int outputFileFormat;
383407
@Nullable private final Mp4AtFileParameters mp4AtFileParameters;
384408
private final MetadataCollector metadataCollector;
@@ -401,7 +425,8 @@ private Mp4Muxer(
401425
boolean sampleBatchingEnabled,
402426
boolean attemptStreamableOutputEnabled,
403427
@FileFormat int outputFileFormat,
404-
@Nullable Mp4AtFileParameters mp4AtFileParameters) {
428+
@Nullable Mp4AtFileParameters mp4AtFileParameters,
429+
int freeSpaceAfterFtypInBytes) {
405430
this.outputStream = outputStream;
406431
outputChannel = outputStream.getChannel();
407432
this.lastSampleDurationBehavior = lastFrameDurationBehavior;
@@ -411,6 +436,7 @@ private Mp4Muxer(
411436
this.attemptStreamableOutputEnabled = attemptStreamableOutputEnabled;
412437
this.outputFileFormat = outputFileFormat;
413438
this.mp4AtFileParameters = mp4AtFileParameters;
439+
this.freeSpaceAfterFtypInBytes = freeSpaceAfterFtypInBytes;
414440
metadataCollector = new MetadataCollector();
415441
mp4Writer =
416442
new Mp4Writer(
@@ -420,7 +446,8 @@ private Mp4Muxer(
420446
lastFrameDurationBehavior,
421447
sampleCopyEnabled,
422448
sampleBatchingEnabled,
423-
attemptStreamableOutputEnabled);
449+
attemptStreamableOutputEnabled,
450+
freeSpaceAfterFtypInBytes);
424451
trackIdToTrack = new ArrayList<>();
425452
auxiliaryTracks = new ArrayList<>();
426453
}
@@ -588,7 +615,8 @@ private void ensureSetupForAuxiliaryTracks() throws FileNotFoundException {
588615
lastSampleDurationBehavior,
589616
sampleCopyEnabled,
590617
sampleBatchingEnabled,
591-
attemptStreamableOutputEnabled);
618+
attemptStreamableOutputEnabled,
619+
freeSpaceAfterFtypInBytes);
592620
}
593621
}
594622

libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Writer.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
private final List<Track> auxiliaryTracks;
6161
private final AtomicBoolean hasWrittenSamples;
6262
private final LinearByteBufferAllocator linearByteBufferAllocator;
63+
private final int freeSpaceAfterFtypInBytes;
6364

6465
// Stores location of the space reserved for the moov box at the beginning of the file (after ftyp
6566
// box)
@@ -88,6 +89,7 @@
8889
* @param sampleCopyEnabled Whether sample copying is enabled.
8990
* @param sampleBatchingEnabled Whether sample batching is enabled.
9091
* @param attemptStreamableOutputEnabled Whether to attempt to write a streamable output.
92+
* @param freeSpaceAfterFtypInBytes Free space to be reserved (in bytes) after the ftyp box.
9193
*/
9294
public Mp4Writer(
9395
FileChannel fileChannel,
@@ -96,13 +98,18 @@ public Mp4Writer(
9698
@Mp4Muxer.LastSampleDurationBehavior int lastSampleDurationBehavior,
9799
boolean sampleCopyEnabled,
98100
boolean sampleBatchingEnabled,
99-
boolean attemptStreamableOutputEnabled) {
101+
boolean attemptStreamableOutputEnabled,
102+
int freeSpaceAfterFtypInBytes) {
100103
this.outputFileChannel = fileChannel;
101104
this.metadataCollector = metadataCollector;
102105
this.annexBToAvccConverter = annexBToAvccConverter;
103106
this.lastSampleDurationBehavior = lastSampleDurationBehavior;
104107
this.sampleCopyEnabled = sampleCopyEnabled;
105108
this.sampleBatchingEnabled = sampleBatchingEnabled;
109+
this.freeSpaceAfterFtypInBytes =
110+
freeSpaceAfterFtypInBytes > 0
111+
? freeSpaceAfterFtypInBytes
112+
: (attemptStreamableOutputEnabled ? DEFAULT_MOOV_BOX_SIZE_BYTES : 0);
106113
tracks = new ArrayList<>();
107114
auxiliaryTracks = new ArrayList<>();
108115
hasWrittenSamples = new AtomicBoolean(false);
@@ -308,11 +315,10 @@ private void writeHeader() throws IOException {
308315
outputFileChannel.position(0L);
309316
outputFileChannel.write(Boxes.ftyp());
310317

311-
if (canWriteMoovAtStart) {
312-
// Reserve some space for moov box by adding a free box.
318+
if (freeSpaceAfterFtypInBytes > 0) {
313319
reservedMoovSpaceStart = outputFileChannel.position();
314320
outputFileChannel.write(
315-
BoxUtils.wrapIntoBox(FREE_BOX_TYPE, ByteBuffer.allocate(DEFAULT_MOOV_BOX_SIZE_BYTES)));
321+
BoxUtils.wrapIntoBox(FREE_BOX_TYPE, ByteBuffer.allocate(freeSpaceAfterFtypInBytes)));
316322
reservedMoovSpaceEnd = outputFileChannel.position();
317323
}
318324

libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,38 @@ public void createMp4File_withSampleBatchingAndAttemptStreamableOutputDisabled_m
926926
"sample_batching_and_attempt_streamable_output_disabled_" + H265_HDR10_MP4));
927927
}
928928

929+
@Test
930+
public void
931+
writeMp4File_withSomeFreeSpaceAfterFileTypeBoxAndAttemptStreamableOutputDisabled_reservesExpectedFreeSpace()
932+
throws Exception {
933+
String outputFilePath = temporaryFolder.newFile().getPath();
934+
Mp4Muxer muxer =
935+
new Mp4Muxer.Builder(new FileOutputStream(outputFilePath))
936+
.experimentalSetFreeSpaceAfterFileTypeBox(500)
937+
.setAttemptStreamableOutputEnabled(false)
938+
.build();
939+
Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
940+
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
941+
942+
try {
943+
muxer.addMetadataEntry(
944+
new Mp4TimestampData(
945+
/* creationTimestampSeconds= */ 1_000_000L,
946+
/* modificationTimestampSeconds= */ 5_000_000L));
947+
int trackId = muxer.addTrack(FAKE_VIDEO_FORMAT);
948+
muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
949+
} finally {
950+
muxer.close();
951+
}
952+
953+
DumpableMp4Box dumpableBox =
954+
new DumpableMp4Box(ByteBuffer.wrap(TestUtil.getByteArrayFromFilePath(outputFilePath)));
955+
DumpFileAsserts.assertOutput(
956+
context,
957+
dumpableBox,
958+
MuxerTestUtil.getExpectedDumpFilePath("mp4_with_some_free_space_after_ftyp.mp4"));
959+
}
960+
929961
private static void writeFakeSamples(Mp4Muxer muxer, int trackId, int sampleCount)
930962
throws MuxerException {
931963
for (int i = 0; i < sampleCount; i++) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
ftyp (28 bytes):
2+
Data = length 20, hash EF896440
3+
free (508 bytes):
4+
Data = length 500, hash 96A2F981
5+
mdat (72 bytes):
6+
Data = length 56, hash DB5662FB
7+
moov (658 bytes):
8+
mvhd (108 bytes):
9+
Data = length 100, hash 2613A5C
10+
trak (542 bytes):
11+
tkhd (92 bytes):
12+
Data = length 84, hash 3D79758F
13+
mdia (442 bytes):
14+
mdhd (32 bytes):
15+
Data = length 24, hash 41542D81
16+
hdlr (44 bytes):
17+
Data = length 36, hash A0852FF2
18+
minf (358 bytes):
19+
vmhd (20 bytes):
20+
Data = length 12, hash EE830681
21+
dinf (36 bytes):
22+
Data = length 28, hash D535436B
23+
stbl (294 bytes):
24+
stsd (166 bytes):
25+
Data = length 158, hash 11532063
26+
stts (24 bytes):
27+
Data = length 16, hash E4FC6483
28+
stsz (24 bytes):
29+
Data = length 16, hash 50B7F5BA
30+
stsc (28 bytes):
31+
Data = length 20, hash 8F6E8285
32+
co64 (24 bytes):
33+
Data = length 16, hash E4EE4D68
34+
stss (20 bytes):
35+
Data = length 12, hash EE911E03

0 commit comments

Comments
 (0)