Skip to content

Commit f8f66bd

Browse files
ychaparovcopybara-github
authored andcommitted
Mp4Muxer: disable sample batching and copying by default.
When sample batching is disabled, copying of the ByteBuffer data is not necessary as samples are written as they arrive. Copying of the BufferInfo is necessary because the info is needed for writing the moov atom. The input ByteBuffer can be in little endian order, or have its position set. AnnexBUtils now ensures big endian order before inspecting bytes, and supports reading from a non-zero position. This change reduces the amount of memory allocations by Mp4Muxer in its default configuration PiperOrigin-RevId: 723401822
1 parent 05e66d9 commit f8f66bd

16 files changed

+162
-273
lines changed

RELEASENOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* DRM:
2525
* Effect:
2626
* Muxers:
27+
* Disable `Mp4Muxer` sample batching and copying by default.
2728
* IMA extension:
2829
* Session:
2930
* UI:

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package androidx.media3.muxer;
1717

18-
import static androidx.media3.common.util.Assertions.checkArgument;
19-
2018
import androidx.media3.common.util.UnstableApi;
2119
import com.google.common.collect.ImmutableList;
2220
import java.nio.ByteBuffer;
@@ -35,9 +33,6 @@ public interface AnnexBToAvccConverter {
3533
return inputBuffer;
3634
}
3735

38-
checkArgument(
39-
inputBuffer.position() == 0, "The input buffer should have position set to 0.");
40-
4136
ImmutableList<ByteBuffer> nalUnitList = AnnexBUtils.findNalUnits(inputBuffer);
4237

4338
int totalBytesNeeded = 0;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import androidx.media3.common.MimeTypes;
2121
import com.google.common.collect.ImmutableList;
2222
import java.nio.ByteBuffer;
23+
import java.nio.ByteOrder;
2324

2425
/** NAL unit utilities for start codes and emulation prevention. */
2526
/* package */ final class AnnexBUtils {
@@ -40,11 +41,13 @@ public static ImmutableList findNalUnits(ByteBuffer input) {
4041
if (input.remaining() == 0) {
4142
return ImmutableList.of();
4243
}
44+
input = input.asReadOnlyBuffer();
45+
input.order(ByteOrder.BIG_ENDIAN);
4346

4447
// The algorithm always searches for 0x000001 start code but it will work for 0x00000001 start
4548
// code as well because the first 0 will be considered as a leading 0 and will be skipped.
4649

47-
int nalStartCodeIndex = skipLeadingZerosAndFindNalStartCodeIndex(input, /* currentIndex= */ 0);
50+
int nalStartCodeIndex = skipLeadingZerosAndFindNalStartCodeIndex(input, input.position());
4851

4952
int nalStartIndex = nalStartCodeIndex + THREE_BYTE_NAL_START_CODE_SIZE;
5053
boolean readingNalUnit = true;
@@ -69,7 +72,6 @@ public static ImmutableList findNalUnits(ByteBuffer input) {
6972
}
7073
}
7174

72-
input.rewind();
7375
return nalUnits.build();
7476
}
7577

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,6 @@ public Builder(FileOutputStream outputStream) {
219219
this.outputStream = outputStream;
220220
lastSampleDurationBehavior =
221221
LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS;
222-
sampleCopyEnabled = true;
223-
sampleBatchingEnabled = true;
224222
attemptStreamableOutputEnabled = true;
225223
outputFileFormat = FILE_FORMAT_DEFAULT;
226224
}
@@ -259,7 +257,11 @@ public Mp4Muxer.Builder setAnnexBToAvccConverter(AnnexBToAvccConverter annexBToA
259257
* to reuse them immediately. Otherwise, the muxer takes ownership of the {@link ByteBuffer} and
260258
* the {@link BufferInfo} and the caller must not modify them.
261259
*
262-
*

The default value is {@code true}.

260+
*

When {@linkplain #setSampleBatchingEnabled(boolean) sample batching} is disabled, samples

261+
* are written as they {@linkplain #writeSampleData(int, ByteBuffer, BufferInfo) arrive} and
262+
* sample copying is disabled.
263+
*
264+
*

The default value is {@code false}.

263265
*/
264266
@CanIgnoreReturnValue
265267
public Mp4Muxer.Builder setSampleCopyingEnabled(boolean enabled) {
@@ -274,7 +276,13 @@ public Mp4Muxer.Builder setSampleCopyingEnabled(boolean enabled) {
274276
* samples are written as they {@linkplain #writeSampleData(int, ByteBuffer, BufferInfo)
275277
* arrive}.
276278
*
277-
*

The default value is {@code true}.

279+
*

When sample batching is enabled, and {@linkplain #setSampleCopyingEnabled(boolean) sample

280+
* copying} is disabled the {@link ByteBuffer} contents provided to {@link #writeSampleData(int,
281+
* ByteBuffer, BufferInfo)} should not be modified. Otherwise, if sample batching is disabled or
282+
* sample copying is enabled, the {@linkplain ByteBuffer sample data} contents can be modified
283+
* after calling {@link #writeSampleData(int, ByteBuffer, BufferInfo)}.
284+
*
285+
*

The default value is {@code false}.

278286
*/
279287
@CanIgnoreReturnValue
280288
public Mp4Muxer.Builder setSampleBatchingEnabled(boolean enabled) {
@@ -391,7 +399,7 @@ private Mp4Muxer(
391399
outputChannel = outputStream.getChannel();
392400
this.lastSampleDurationBehavior = lastFrameDurationBehavior;
393401
this.annexBToAvccConverter = annexBToAvccConverter;
394-
this.sampleCopyEnabled = sampleCopyEnabled;
402+
this.sampleCopyEnabled = sampleBatchingEnabled && sampleCopyEnabled;
395403
this.sampleBatchingEnabled = sampleBatchingEnabled;
396404
this.attemptStreamableOutputEnabled = attemptStreamableOutputEnabled;
397405
this.outputFileFormat = outputFileFormat;
@@ -470,14 +478,6 @@ && isAuxiliaryTrack(format)) {
470478
/**
471479
* Writes encoded sample data.
472480
*
473-
*

When sample batching is {@linkplain Mp4Muxer.Builder#setSampleBatchingEnabled(boolean)

474-
* enabled}, provide sample data ({@link ByteBuffer}, {@link BufferInfo}) that won't be modified
475-
* after calling the {@link #writeSampleData(int, ByteBuffer, BufferInfo)} method, unless sample
476-
* copying is also {@linkplain Mp4Muxer.Builder#setSampleCopyingEnabled(boolean) enabled}. This
477-
* ensures data integrity within the batch. If sample copying is {@linkplain
478-
* Mp4Muxer.Builder#setSampleCopyingEnabled(boolean) enabled}, it's safe to modify the data after
479-
* the method returns, as the muxer internally creates a sample copy.
480-
*
481481
* @param trackId The track id for which this sample is being written.
482482
* @param byteBuffer The encoded sample. The muxer takes ownership of the buffer if {@link
483483
* Builder#setSampleCopyingEnabled(boolean) sample copying} is disabled. Otherwise, the

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,22 +92,21 @@ public void writeSampleData(ByteBuffer byteBuffer, BufferInfo bufferInfo) {
9292
}
9393

9494
ByteBuffer byteBufferToAdd = byteBuffer;
95-
BufferInfo bufferInfoToAdd = bufferInfo;
96-
9795
if (sampleCopyEnabled) {
9896
// Copy sample data and release the original buffer.
9997
byteBufferToAdd = ByteBuffer.allocateDirect(byteBuffer.remaining());
10098
byteBufferToAdd.put(byteBuffer);
10199
byteBufferToAdd.rewind();
102-
103-
bufferInfoToAdd = new BufferInfo();
104-
bufferInfoToAdd.set(
105-
/* newOffset= */ byteBufferToAdd.position(),
106-
/* newSize= */ byteBufferToAdd.remaining(),
107-
bufferInfo.presentationTimeUs,
108-
bufferInfo.flags);
109100
}
110101

102+
// Always copy the buffer info as it is retained until the track is finalized.
103+
BufferInfo bufferInfoToAdd = new BufferInfo();
104+
bufferInfoToAdd.set(
105+
/* newOffset= */ byteBufferToAdd.position(),
106+
/* newSize= */ byteBufferToAdd.remaining(),
107+
bufferInfo.presentationTimeUs,
108+
bufferInfo.flags);
109+
111110
pendingSamplesBufferInfo.addLast(bufferInfoToAdd);
112111
pendingSamplesByteBuffer.addLast(byteBufferToAdd);
113112
}

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import androidx.test.ext.junit.runners.AndroidJUnit4;
2323
import com.google.common.collect.ImmutableList;
2424
import java.nio.ByteBuffer;
25+
import java.nio.ByteOrder;
2526
import org.junit.Test;
2627
import org.junit.runner.RunWith;
2728

@@ -53,6 +54,17 @@ public void findNalUnits_singleNalUnitWithFourByteStartCode_returnsSingleElement
5354
assertThat(components).containsExactly(ByteBuffer.wrap(getBytesFromHexString("ABCDEF")));
5455
}
5556

57+
@Test
58+
public void
59+
findNalUnits_singleNalUnitWithFourByteStartCodeAndLittleEndianOrder_returnsSingleElement() {
60+
ByteBuffer buffer =
61+
ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF")).order(ByteOrder.LITTLE_ENDIAN);
62+
63+
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
64+
65+
assertThat(components).containsExactly(ByteBuffer.wrap(getBytesFromHexString("ABCDEF")));
66+
}
67+
5668
@Test
5769
public void findNalUnits_singleNalUnitWithThreeByteStartCode_returnsSingleElement() {
5870
ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("000001ABCDEF"));
@@ -100,6 +112,34 @@ public void findNalUnits_withTrainingZeroesFollowedByGarbageData_throws() {
100112
assertThrows(IllegalStateException.class, () -> AnnexBUtils.findNalUnits(buffer));
101113
}
102114

115+
@Test
116+
public void findNalUnits_withPositionAndLimitSet_returnsNalUnit() {
117+
ByteBuffer buffer =
118+
ByteBuffer.wrap(getBytesFromHexString("12345600000001ABCDEF002233445566778899"));
119+
buffer.position(3);
120+
buffer.limit(10);
121+
122+
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
123+
124+
assertThat(components).containsExactly(ByteBuffer.wrap(getBytesFromHexString("ABCDEF")));
125+
}
126+
127+
@Test
128+
public void findNalUnits_withMultipleNalUnitsAndPositionSet_returnsAllNalUnits() {
129+
ByteBuffer buffer =
130+
ByteBuffer.wrap(getBytesFromHexString("123456000001ABCDEF000001DDCC000001BBAA"));
131+
buffer.position(3);
132+
133+
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
134+
135+
assertThat(components)
136+
.containsExactly(
137+
ByteBuffer.wrap(getBytesFromHexString("ABCDEF")),
138+
ByteBuffer.wrap(getBytesFromHexString("DDCC")),
139+
ByteBuffer.wrap(getBytesFromHexString("BBAA")))
140+
.inOrder();
141+
}
142+
103143
@Test
104144
public void findNalUnits_withTrailingZeroes_stripsTrailingZeroes() {
105145
// The first NAL unit has some training zeroes.

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,10 @@ public void createMp4File_muxerNotClosed_createsPartiallyWrittenValidFile() thro
138138

139139
// Muxer not closed.
140140

141-
// Audio sample written = 192 out of 195.
142-
// Video sample written = 125 out of 127.
141+
// The output depends on Mp4Muxer.MOOV_BOX_UPDATE_INTERVAL_US and whether or not
142+
// sample batching is enabled.
143+
// Audio sample written = 187 out of 195.
144+
// Video sample written = 93 out of 127.
143145
// Output is still a valid MP4 file.
144146
FakeExtractorOutput fakeExtractorOutput =
145147
TestUtil.extractAllSamplesFromFilePath(

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import static androidx.media3.container.MdtaMetadataEntry.TYPE_INDICATOR_STRING;
2121
import static androidx.media3.muxer.MuxerTestUtil.FAKE_VIDEO_FORMAT;
2222
import static androidx.media3.muxer.MuxerTestUtil.XMP_SAMPLE_DATA;
23-
import static com.google.common.truth.Truth.assertThat;
2423

2524
import android.content.Context;
2625
import android.media.MediaCodec.BufferInfo;
@@ -292,7 +291,6 @@ public void writeMp4File_addManyLargeStringMetadata_doesNotThrow() throws Except
292291

293292
try {
294293
muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
295-
assertThat(sampleAndSampleInfo.first.remaining()).isEqualTo(0);
296294
} finally {
297295
muxer.close();
298296
}

libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4.dump

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ seekMap:
33
duration = 4236600
44
getPosition(0) = [[timeUs=0, position=400052]]
55
getPosition(1) = [[timeUs=0, position=400052], [timeUs=1002955, position=430177]]
6-
getPosition(2118300) = [[timeUs=2003566, position=2503758], [timeUs=3003444, position=4426916]]
7-
getPosition(4236600) = [[timeUs=4003277, position=4459282]]
6+
getPosition(2118300) = [[timeUs=2003566, position=2562813], [timeUs=3003444, position=4426916]]
7+
getPosition(4236600) = [[timeUs=4003277, position=6370233]]
88
numberOfTracks = 2
99
track 0:
1010
total output bytes = 7944083
Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
ftyp (28 bytes):
22
Data = length 20, hash EF896440
3-
moov (881 bytes):
3+
moov (913 bytes):
44
mvhd (108 bytes):
55
Data = length 100, hash 2613A5C
66
meta (191 bytes):
@@ -10,40 +10,40 @@ moov (881 bytes):
1010
Data = length 70, hash C5147150
1111
ilst (72 bytes):
1212
Data = length 64, hash 3B286CD9
13-
trak (574 bytes):
13+
trak (606 bytes):
1414
tkhd (92 bytes):
1515
Data = length 84, hash 3D79758F
16-
mdia (474 bytes):
16+
mdia (506 bytes):
1717
mdhd (32 bytes):
1818
Data = length 24, hash 41542D81
1919
hdlr (44 bytes):
2020
Data = length 36, hash A0852FF2
21-
minf (390 bytes):
21+
minf (422 bytes):
2222
vmhd (20 bytes):
2323
Data = length 12, hash EE830681
2424
dinf (36 bytes):
2525
Data = length 28, hash D535436B
26-
stbl (326 bytes):
26+
stbl (358 bytes):
2727
stsd (166 bytes):
2828
Data = length 158, hash 11532063
2929
stts (24 bytes):
3030
Data = length 16, hash E534C287
3131
stsz (40 bytes):
3232
Data = length 32, hash B3F09E
3333
stsc (28 bytes):
34-
Data = length 20, hash 8FA6E089
35-
co64 (24 bytes):
36-
Data = length 16, hash E4EE6662
34+
Data = length 20, hash 8F6E8285
35+
co64 (56 bytes):
36+
Data = length 48, hash A4175473
3737
stss (36 bytes):
3838
Data = length 28, hash 53024615
39-
free (399127 bytes):
40-
Data = length 399119, hash 82DEBDDF
39+
free (399095 bytes):
40+
Data = length 399087, hash A215F9DF
4141
mdat (296 bytes):
4242
Data = length 280, hash 8DCFD2E3
4343
axte (400628 bytes):
4444
ftyp (28 bytes):
4545
Data = length 20, hash EF896440
46-
moov (1446 bytes):
46+
moov (1510 bytes):
4747
mvhd (108 bytes):
4848
Data = length 100, hash 2613A5D
4949
meta (182 bytes):
@@ -53,59 +53,59 @@ axte (400628 bytes):
5353
Data = length 72, hash 4E5C3894
5454
ilst (61 bytes):
5555
Data = length 53, hash 75D49FBD
56-
trak (574 bytes):
56+
trak (606 bytes):
5757
tkhd (92 bytes):
5858
Data = length 84, hash 3D79758F
59-
mdia (474 bytes):
59+
mdia (506 bytes):
6060
mdhd (32 bytes):
6161
Data = length 24, hash 41542D81
6262
hdlr (44 bytes):
6363
Data = length 36, hash A0852FF2
64-
minf (390 bytes):
64+
minf (422 bytes):
6565
vmhd (20 bytes):
6666
Data = length 12, hash EE830681
6767
dinf (36 bytes):
6868
Data = length 28, hash D535436B
69-
stbl (326 bytes):
69+
stbl (358 bytes):
7070
stsd (166 bytes):
7171
Data = length 158, hash 11532063
7272
stts (24 bytes):
7373
Data = length 16, hash E534C287
7474
stsz (40 bytes):
7575
Data = length 32, hash B3F09E
7676
stsc (28 bytes):
77-
Data = length 20, hash 8FA6E089
78-
co64 (24 bytes):
79-
Data = length 16, hash E4EE6662
77+
Data = length 20, hash 8F6E8285
78+
co64 (56 bytes):
79+
Data = length 48, hash A4175473
8080
stss (36 bytes):
8181
Data = length 28, hash 53024615
82-
trak (574 bytes):
82+
trak (606 bytes):
8383
tkhd (92 bytes):
8484
Data = length 84, hash 2ECB0510
85-
mdia (474 bytes):
85+
mdia (506 bytes):
8686
mdhd (32 bytes):
8787
Data = length 24, hash 41542D81
8888
hdlr (44 bytes):
8989
Data = length 36, hash A0852FF2
90-
minf (390 bytes):
90+
minf (422 bytes):
9191
vmhd (20 bytes):
9292
Data = length 12, hash EE830681
9393
dinf (36 bytes):
9494
Data = length 28, hash D535436B
95-
stbl (326 bytes):
95+
stbl (358 bytes):
9696
stsd (166 bytes):
9797
Data = length 158, hash 11532063
9898
stts (24 bytes):
9999
Data = length 16, hash E534C287
100100
stsz (40 bytes):
101101
Data = length 32, hash B3F09E
102102
stsc (28 bytes):
103-
Data = length 20, hash 8FA6E089
104-
co64 (24 bytes):
105-
Data = length 16, hash E4EE6699
103+
Data = length 20, hash 8F6E8285
104+
co64 (56 bytes):
105+
Data = length 48, hash 44DD22A5
106106
stss (36 bytes):
107107
Data = length 28, hash 53024615
108-
free (398562 bytes):
109-
Data = length 398554, hash C1D2F8C1
108+
free (398498 bytes):
109+
Data = length 398490, hash B07980C1
110110
mdat (576 bytes):
111111
Data = length 560, hash 9E0D5FC5

0 commit comments

Comments
 (0)