diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java index 1862952e9c..de393a5849 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java @@ -22,6 +22,7 @@ import static androidx.media3.muxer.ColorUtils.MEDIAFORMAT_STANDARD_TO_PRIMARIES import static androidx.media3.muxer.ColorUtils.MEDIAFORMAT_TRANSFER_TO_MP4_TRANSFER; import static androidx.media3.muxer.Mp4Utils.BYTES_PER_INTEGER; import static androidx.media3.muxer.Mp4Utils.MVHD_TIMEBASE; +import static androidx.media3.muxer.Mp4Utils.UNSIGNED_INT_MAX_VALUE; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; @@ -736,16 +737,35 @@ import java.util.Locale; return BoxUtils.wrapIntoBox("stsc", contents); } - /** Returns the co64 (chunk offset) box. */ - public static ByteBuffer co64(List writtenChunkOffsets) { + /** Returns the stco (32-bit chunk offset) box. */ + public static ByteBuffer stco(List writtenChunkOffsets) { ByteBuffer contents = - ByteBuffer.allocate(writtenChunkOffsets.size() * 8 + Mp4Utils.MAX_FIXED_LEAF_BOX_SIZE); + ByteBuffer.allocate(2 * BYTES_PER_INTEGER + writtenChunkOffsets.size() * BYTES_PER_INTEGER); - contents.putInt(0x0); // version. - contents.putInt(writtenChunkOffsets.size()); // entry_count. + contents.putInt(0x0); // version and flags + contents.putInt(writtenChunkOffsets.size()); // entry_count; unsigned int(32) for (int i = 0; i < writtenChunkOffsets.size(); i++) { - contents.putLong(writtenChunkOffsets.get(i)); // chunk_offset. + long chunkOffset = writtenChunkOffsets.get(i); + checkState(chunkOffset <= UNSIGNED_INT_MAX_VALUE, "Only 32-bit offset is allowed"); + contents.putInt((int) chunkOffset); // chunk_offset; unsigned int(32) + } + + contents.flip(); + return BoxUtils.wrapIntoBox("stco", contents); + } + + /** Returns the co64 (64-bit chunk offset) box. */ + public static ByteBuffer co64(List writtenChunkOffsets) { + ByteBuffer contents = + ByteBuffer.allocate( + 2 * BYTES_PER_INTEGER + 2 * writtenChunkOffsets.size() * BYTES_PER_INTEGER); + + contents.putInt(0x0); // version and flags + contents.putInt(writtenChunkOffsets.size()); // entry_count; unsigned int(32) + + for (int i = 0; i < writtenChunkOffsets.size(); i++) { + contents.putLong(writtenChunkOffsets.get(i)); // chunk_offset; unsigned int(64) } contents.flip(); diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4MoovStructure.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4MoovStructure.java index 978a62600c..ffacb22326 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4MoovStructure.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4MoovStructure.java @@ -93,7 +93,10 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ByteBuffer stts = Boxes.stts(sampleDurationsVu); ByteBuffer stsz = Boxes.stsz(track.writtenSamples()); ByteBuffer stsc = Boxes.stsc(track.writtenChunkSampleCounts()); - ByteBuffer co64 = Boxes.co64(track.writtenChunkOffsets()); + ByteBuffer chunkOffsetBox = + isFragmentedMp4 + ? Boxes.stco(track.writtenChunkOffsets()) + : Boxes.co64(track.writtenChunkOffsets()); String handlerType; String handlerName; @@ -109,7 +112,9 @@ import org.checkerframework.checker.nullness.qual.PolyNull; mhdBox = Boxes.vmhd(); sampleEntryBox = Boxes.videoSampleEntry(format); stsdBox = Boxes.stsd(sampleEntryBox); - stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, co64, Boxes.stss(track.writtenSamples())); + stblBox = + Boxes.stbl( + stsdBox, stts, stsz, stsc, chunkOffsetBox, Boxes.stss(track.writtenSamples())); break; case C.TRACK_TYPE_AUDIO: handlerType = "soun"; @@ -117,7 +122,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; mhdBox = Boxes.smhd(); sampleEntryBox = Boxes.audioSampleEntry(format); stsdBox = Boxes.stsd(sampleEntryBox); - stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, co64); + stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, chunkOffsetBox); break; case C.TRACK_TYPE_METADATA: // TODO: (b/280443593) - Check if we can identify a metadata track type from a custom @@ -128,7 +133,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; mhdBox = Boxes.nmhd(); sampleEntryBox = Boxes.textMetaDataSampleEntry(format); stsdBox = Boxes.stsd(sampleEntryBox); - stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, co64); + stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, chunkOffsetBox); break; default: throw new IllegalArgumentException("Unsupported track type"); diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java index 8204898860..2f38b53878 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java @@ -497,6 +497,17 @@ public class BoxesTest { context, dumpableBox, MuxerTestUtil.getExpectedDumpFilePath("stsc_box")); } + @Test + public void createStcoBox_matchesExpected() throws IOException { + ImmutableList chunkOffsets = ImmutableList.of(1_000L, 5_000L, 7_000L, 10_000L); + + ByteBuffer stcoBox = Boxes.stco(chunkOffsets); + + DumpableMp4Box dumpableBox = new DumpableMp4Box(stcoBox); + DumpFileAsserts.assertOutput( + context, dumpableBox, MuxerTestUtil.getExpectedDumpFilePath("stco_box")); + } + @Test public void createCo64Box_matchesExpected() throws IOException { ImmutableList chunkOffsets = ImmutableList.of(1_000L, 5_000L, 7_000L, 10_000L); diff --git a/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4_fragmented_box_structure.dump b/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4_fragmented_box_structure.dump index c64995fb65..737a6b591a 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4_fragmented_box_structure.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/hdr10-720p.mp4_fragmented_box_structure.dump @@ -25,7 +25,7 @@ moov (1209 bytes): Data = length 12, hash EE830681 stsc (16 bytes): Data = length 8, hash 94446F01 - co64 (16 bytes): + stco (16 bytes): Data = length 8, hash 94446F01 stss (16 bytes): Data = length 8, hash 94446F01 @@ -51,7 +51,7 @@ moov (1209 bytes): Data = length 12, hash EE830681 stsc (16 bytes): Data = length 8, hash 94446F01 - co64 (16 bytes): + stco (16 bytes): Data = length 8, hash 94446F01 mvex (72 bytes): trex (32 bytes): diff --git a/libraries/test_data/src/test/assets/muxerdumps/stco_box.dump b/libraries/test_data/src/test/assets/muxerdumps/stco_box.dump new file mode 100644 index 0000000000..f1d13fecf3 --- /dev/null +++ b/libraries/test_data/src/test/assets/muxerdumps/stco_box.dump @@ -0,0 +1,2 @@ +stco (32 bytes): + Data = length 24, hash E472D485