Write "stco" box instead of "co64" for fragmented MP4
As per MP4 spec ISO 14496-12: 8.7.5 Chunk Offset Box, Both "stco" and "co64" can be used to store chunk offsets. While "stco" supports 32-bit offsets, "co64" supports 64-bit offsets. In non fragmented MP4, the mdat box can be extremely large, hence muxer uses "co64" box. But for fragmented MP4, muxer does not write any data in this chunk offset box (present in "moov" box) because all sample related info is present in "moof" box. Technically, "co64" box should also work in fragmented MP4because its empty only but QuickTime player fails to play video if "co64" box is present in fragmented MP4 output file. Testing: Verified that QuickTime player does not play video when "co64" box is present but is able to play when "stco" box is present. #minor-release PiperOrigin-RevId: 601147046
This commit is contained in:
parent
966b710897
commit
0acf6902e5
@ -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.ColorUtils.MEDIAFORMAT_TRANSFER_TO_MP4_TRANSFER;
|
||||||
import static androidx.media3.muxer.Mp4Utils.BYTES_PER_INTEGER;
|
import static androidx.media3.muxer.Mp4Utils.BYTES_PER_INTEGER;
|
||||||
import static androidx.media3.muxer.Mp4Utils.MVHD_TIMEBASE;
|
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;
|
||||||
import android.media.MediaCodec.BufferInfo;
|
import android.media.MediaCodec.BufferInfo;
|
||||||
@ -736,16 +737,35 @@ import java.util.Locale;
|
|||||||
return BoxUtils.wrapIntoBox("stsc", contents);
|
return BoxUtils.wrapIntoBox("stsc", contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the co64 (chunk offset) box. */
|
/** Returns the stco (32-bit chunk offset) box. */
|
||||||
public static ByteBuffer co64(List<Long> writtenChunkOffsets) {
|
public static ByteBuffer stco(List<Long> writtenChunkOffsets) {
|
||||||
ByteBuffer contents =
|
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(0x0); // version and flags
|
||||||
contents.putInt(writtenChunkOffsets.size()); // entry_count.
|
contents.putInt(writtenChunkOffsets.size()); // entry_count; unsigned int(32)
|
||||||
|
|
||||||
for (int i = 0; i < writtenChunkOffsets.size(); i++) {
|
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<Long> 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();
|
contents.flip();
|
||||||
|
@ -93,7 +93,10 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
ByteBuffer stts = Boxes.stts(sampleDurationsVu);
|
ByteBuffer stts = Boxes.stts(sampleDurationsVu);
|
||||||
ByteBuffer stsz = Boxes.stsz(track.writtenSamples());
|
ByteBuffer stsz = Boxes.stsz(track.writtenSamples());
|
||||||
ByteBuffer stsc = Boxes.stsc(track.writtenChunkSampleCounts());
|
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 handlerType;
|
||||||
String handlerName;
|
String handlerName;
|
||||||
@ -109,7 +112,9 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
mhdBox = Boxes.vmhd();
|
mhdBox = Boxes.vmhd();
|
||||||
sampleEntryBox = Boxes.videoSampleEntry(format);
|
sampleEntryBox = Boxes.videoSampleEntry(format);
|
||||||
stsdBox = Boxes.stsd(sampleEntryBox);
|
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;
|
break;
|
||||||
case C.TRACK_TYPE_AUDIO:
|
case C.TRACK_TYPE_AUDIO:
|
||||||
handlerType = "soun";
|
handlerType = "soun";
|
||||||
@ -117,7 +122,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
mhdBox = Boxes.smhd();
|
mhdBox = Boxes.smhd();
|
||||||
sampleEntryBox = Boxes.audioSampleEntry(format);
|
sampleEntryBox = Boxes.audioSampleEntry(format);
|
||||||
stsdBox = Boxes.stsd(sampleEntryBox);
|
stsdBox = Boxes.stsd(sampleEntryBox);
|
||||||
stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, co64);
|
stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, chunkOffsetBox);
|
||||||
break;
|
break;
|
||||||
case C.TRACK_TYPE_METADATA:
|
case C.TRACK_TYPE_METADATA:
|
||||||
// TODO: (b/280443593) - Check if we can identify a metadata track type from a custom
|
// 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();
|
mhdBox = Boxes.nmhd();
|
||||||
sampleEntryBox = Boxes.textMetaDataSampleEntry(format);
|
sampleEntryBox = Boxes.textMetaDataSampleEntry(format);
|
||||||
stsdBox = Boxes.stsd(sampleEntryBox);
|
stsdBox = Boxes.stsd(sampleEntryBox);
|
||||||
stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, co64);
|
stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, chunkOffsetBox);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported track type");
|
throw new IllegalArgumentException("Unsupported track type");
|
||||||
|
@ -497,6 +497,17 @@ public class BoxesTest {
|
|||||||
context, dumpableBox, MuxerTestUtil.getExpectedDumpFilePath("stsc_box"));
|
context, dumpableBox, MuxerTestUtil.getExpectedDumpFilePath("stsc_box"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createStcoBox_matchesExpected() throws IOException {
|
||||||
|
ImmutableList<Long> 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
|
@Test
|
||||||
public void createCo64Box_matchesExpected() throws IOException {
|
public void createCo64Box_matchesExpected() throws IOException {
|
||||||
ImmutableList<Long> chunkOffsets = ImmutableList.of(1_000L, 5_000L, 7_000L, 10_000L);
|
ImmutableList<Long> chunkOffsets = ImmutableList.of(1_000L, 5_000L, 7_000L, 10_000L);
|
||||||
|
@ -25,7 +25,7 @@ moov (1209 bytes):
|
|||||||
Data = length 12, hash EE830681
|
Data = length 12, hash EE830681
|
||||||
stsc (16 bytes):
|
stsc (16 bytes):
|
||||||
Data = length 8, hash 94446F01
|
Data = length 8, hash 94446F01
|
||||||
co64 (16 bytes):
|
stco (16 bytes):
|
||||||
Data = length 8, hash 94446F01
|
Data = length 8, hash 94446F01
|
||||||
stss (16 bytes):
|
stss (16 bytes):
|
||||||
Data = length 8, hash 94446F01
|
Data = length 8, hash 94446F01
|
||||||
@ -51,7 +51,7 @@ moov (1209 bytes):
|
|||||||
Data = length 12, hash EE830681
|
Data = length 12, hash EE830681
|
||||||
stsc (16 bytes):
|
stsc (16 bytes):
|
||||||
Data = length 8, hash 94446F01
|
Data = length 8, hash 94446F01
|
||||||
co64 (16 bytes):
|
stco (16 bytes):
|
||||||
Data = length 8, hash 94446F01
|
Data = length 8, hash 94446F01
|
||||||
mvex (72 bytes):
|
mvex (72 bytes):
|
||||||
trex (32 bytes):
|
trex (32 bytes):
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
stco (32 bytes):
|
||||||
|
Data = length 24, hash E472D485
|
Loading…
x
Reference in New Issue
Block a user