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 6fbf4d4427..f3f060784d 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java @@ -50,12 +50,7 @@ import java.util.List; import java.util.Locale; import org.checkerframework.checker.nullness.qual.PolyNull; -/** - * Writes out various types of boxes as per MP4 (ISO/IEC 14496-12) standards. - * - *

Boxes do not construct their sub-boxes but take them as input {@linkplain ByteBuffer byte - * buffers}. - */ +/** Writes out various types of boxes as per MP4 (ISO/IEC 14496-12) standards. */ /* package */ final class Boxes { /** Provides track's metadata like media format, written samples. */ public interface TrackMetadataProvider { @@ -70,25 +65,25 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ImmutableList writtenChunkSampleCounts(); } - /* Total number of bytes in an integer. */ + /** Total number of bytes in an integer. */ private static final int BYTES_PER_INTEGER = 4; - // Box size (4 bytes) + Box name (4 bytes) + /** Box size (4 bytes) + Box name (4 bytes) */ public static final int BOX_HEADER_SIZE = 8; - // Box size = 1 to indicate 64-bit box size (4 bytes) + Box name (4 bytes) + actual size (8 bytes) + /** + * Box size = 1 to indicate 64-bit box size (4 bytes) + Box name (4 bytes) + actual box size (8 + * bytes) + */ public static final int LARGE_SIZE_BOX_HEADER_SIZE = 16; + /** The size (in bytes) of the mfhd box content. */ public static final int MFHD_BOX_CONTENT_SIZE = 2 * BYTES_PER_INTEGER; + /** The size (in bytes) of the tfhd box content. */ public static final int TFHD_BOX_CONTENT_SIZE = 4 * BYTES_PER_INTEGER; - /** - * The maximum length of boxes which have fixed sizes. - * - *

Technically, we'd know how long they actually are; this upper bound is much simpler to - * produce though and we'll throw if we overflow anyway. - */ + /** The maximum size (in bytes) of boxes that have fixed sizes. */ private static final int MAX_FIXED_LEAF_BOX_SIZE = 200; /** @@ -97,10 +92,13 @@ import org.checkerframework.checker.nullness.qual.PolyNull; */ private static final long MVHD_TIMEBASE = 10_000L; - // unsigned int(2) sample_depends_on = 2 (bit index 25 and 24) + /** unsigned int(2) sample_depends_on = 2 (bit index 25 and 24) */ private static final int TRUN_BOX_SYNC_SAMPLE_FLAGS = 0b00000010_00000000_00000000_00000000; - // unsigned int(2) sample_depends_on = 1 (bit index 25 and 24) - // bit(1) sample_is_non_sync_sample = 1 (bit index 16) + + /** + * unsigned int(2) sample_depends_on = 1 (bit index 25 and 24), bit(1) sample_is_non_sync_sample = + * 1 (bit index 16) + */ private static final int TRUN_BOX_NON_SYNC_SAMPLE_FLAGS = 0b00000001_00000001_00000000_00000000; private Boxes() {} @@ -153,7 +151,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; // Generate the sample durations to calculate the total duration for tkhd box. List sampleDurationsVu = - Boxes.convertPresentationTimestampsToDurationsVu( + convertPresentationTimestampsToDurationsVu( track.writtenSamples(), minInputPtsUs, track.videoUnitTimebase(), @@ -167,17 +165,15 @@ import org.checkerframework.checker.nullness.qual.PolyNull; long trackDurationUs = usFromVu(trackDurationInTrackUnitsVu, track.videoUnitTimebase()); @C.TrackType int trackType = MimeTypes.getTrackType(format.sampleMimeType); - ByteBuffer stts = Boxes.stts(sampleDurationsVu); + ByteBuffer stts = stts(sampleDurationsVu); ByteBuffer ctts = MimeTypes.isVideo(format.sampleMimeType) - ? Boxes.ctts(track.writtenSamples(), sampleDurationsVu, track.videoUnitTimebase()) + ? ctts(track.writtenSamples(), sampleDurationsVu, track.videoUnitTimebase()) : ByteBuffer.allocate(0); - ByteBuffer stsz = Boxes.stsz(track.writtenSamples()); - ByteBuffer stsc = Boxes.stsc(track.writtenChunkSampleCounts()); + ByteBuffer stsz = stsz(track.writtenSamples()); + ByteBuffer stsc = stsc(track.writtenChunkSampleCounts()); ByteBuffer chunkOffsetBox = - isFragmentedMp4 - ? Boxes.stco(track.writtenChunkOffsets()) - : Boxes.co64(track.writtenChunkOffsets()); + isFragmentedMp4 ? stco(track.writtenChunkOffsets()) : co64(track.writtenChunkOffsets()); String handlerType; String handlerName; @@ -190,81 +186,68 @@ import org.checkerframework.checker.nullness.qual.PolyNull; case C.TRACK_TYPE_VIDEO: handlerType = "vide"; handlerName = "VideoHandle"; - mhdBox = Boxes.vmhd(); - sampleEntryBox = Boxes.videoSampleEntry(format); - stsdBox = Boxes.stsd(sampleEntryBox); + mhdBox = vmhd(); + sampleEntryBox = videoSampleEntry(format); + stsdBox = stsd(sampleEntryBox); stblBox = - Boxes.stbl( - stsdBox, - stts, - ctts, - stsz, - stsc, - chunkOffsetBox, - Boxes.stss(track.writtenSamples())); + stbl(stsdBox, stts, ctts, stsz, stsc, chunkOffsetBox, stss(track.writtenSamples())); break; case C.TRACK_TYPE_AUDIO: handlerType = "soun"; handlerName = "SoundHandle"; - mhdBox = Boxes.smhd(); - sampleEntryBox = Boxes.audioSampleEntry(format); - stsdBox = Boxes.stsd(sampleEntryBox); - stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, chunkOffsetBox); + mhdBox = smhd(); + sampleEntryBox = audioSampleEntry(format); + stsdBox = stsd(sampleEntryBox); + stblBox = 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 - // mime type. case C.TRACK_TYPE_UNKNOWN: handlerType = "meta"; handlerName = "MetaHandle"; - mhdBox = Boxes.nmhd(); - sampleEntryBox = Boxes.textMetaDataSampleEntry(format); - stsdBox = Boxes.stsd(sampleEntryBox); - stblBox = Boxes.stbl(stsdBox, stts, stsz, stsc, chunkOffsetBox); + mhdBox = nmhd(); + sampleEntryBox = textMetaDataSampleEntry(format); + stsdBox = stsd(sampleEntryBox); + stblBox = stbl(stsdBox, stts, stsz, stsc, chunkOffsetBox); break; default: throw new IllegalArgumentException("Unsupported track type"); } - // The below statement is also a description of how a mdat box looks like, with all the - // inner boxes and what they actually store. Although they're technically instance methods, - // everything that is written to a box is visible in the argument list. ByteBuffer trakBox = - Boxes.trak( - Boxes.tkhd( + trak( + tkhd( nextTrackId, trackDurationUs, creationTimestampSeconds, modificationTimestampSeconds, metadataCollector.orientationData.orientation, format), - Boxes.mdia( - Boxes.mdhd( + mdia( + mdhd( trackDurationInTrackUnitsVu, track.videoUnitTimebase(), creationTimestampSeconds, modificationTimestampSeconds, languageCode), - Boxes.hdlr(handlerType, handlerName), - Boxes.minf(mhdBox, Boxes.dinf(Boxes.dref(Boxes.localUrl())), stblBox))); + hdlr(handlerType, handlerName), + minf(mhdBox, dinf(dref(localUrl())), stblBox))); trakBoxes.add(trakBox); videoDurationUs = max(videoDurationUs, trackDurationUs); - trexBoxes.add(Boxes.trex(nextTrackId)); + trexBoxes.add(trex(nextTrackId)); nextTrackId++; } ByteBuffer mvhdBox = - Boxes.mvhd( - nextTrackId, creationTimestampSeconds, modificationTimestampSeconds, videoDurationUs); - ByteBuffer udtaBox = Boxes.udta(metadataCollector.locationData); + mvhd(nextTrackId, creationTimestampSeconds, modificationTimestampSeconds, videoDurationUs); + ByteBuffer udtaBox = udta(metadataCollector.locationData); ByteBuffer metaBox = metadataCollector.metadataEntries.isEmpty() ? ByteBuffer.allocate(0) - : Boxes.meta( - Boxes.hdlr(/* handlerType= */ "mdta", /* handlerName= */ ""), - Boxes.keys(Lists.newArrayList(metadataCollector.metadataEntries)), - Boxes.ilst(Lists.newArrayList(metadataCollector.metadataEntries))); + : meta( + hdlr(/* handlerType= */ "mdta", /* handlerName= */ ""), + keys(Lists.newArrayList(metadataCollector.metadataEntries)), + ilst(Lists.newArrayList(metadataCollector.metadataEntries))); List subBoxes = new ArrayList<>(); subBoxes.add(mvhdBox); @@ -272,15 +255,14 @@ import org.checkerframework.checker.nullness.qual.PolyNull; subBoxes.add(metaBox); subBoxes.addAll(trakBoxes); if (isFragmentedMp4) { - subBoxes.add(Boxes.mvex(trexBoxes)); + subBoxes.add(mvex(trexBoxes)); } ByteBuffer moovBox = BoxUtils.wrapBoxesIntoBox("moov", subBoxes); - // Also add XMP if needed if (metadataCollector.xmpData != null) { return BoxUtils.concatenateBuffers( - moovBox, Boxes.uuid(Boxes.XMP_UUID, ByteBuffer.wrap(metadataCollector.xmpData.data))); + moovBox, uuid(XMP_UUID, ByteBuffer.wrap(metadataCollector.xmpData.data))); } else { // No need for another copy if there is no XMP to be appended. return moovBox; @@ -300,10 +282,10 @@ import org.checkerframework.checker.nullness.qual.PolyNull; int orientation, Format format) { ByteBuffer contents = ByteBuffer.allocate(MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x00000007); // version and flags; allow presentation, etc. + contents.putInt(0x00000007); // version and flags: allow presentation, etc. - contents.putInt(creationTimestampSeconds); // creation_time; unsigned int(32) - contents.putInt(modificationTimestampSeconds); // modification_time; unsigned int(32) + contents.putInt(creationTimestampSeconds); // creation_time: unsigned int(32) + contents.putInt(modificationTimestampSeconds); // modification_time: unsigned int(32) contents.putInt(trackId); contents.putInt(0); // reserved @@ -345,8 +327,8 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ByteBuffer contents = ByteBuffer.allocate(MAX_FIXED_LEAF_BOX_SIZE); contents.putInt(0); // version and flags - contents.putInt(creationTimestampSeconds); // creation_time; unsigned int(32) - contents.putInt(modificationTimestampSeconds); // modification_time; unsigned int(32) + contents.putInt(creationTimestampSeconds); // creation_time: unsigned int(32) + contents.putInt(modificationTimestampSeconds); // modification_time: unsigned int(32) contents.putInt((int) MVHD_TIMEBASE); // The per-track timescales might be different. contents.putInt( (int) vuFromUs(videoDurationUs, MVHD_TIMEBASE)); // Duration of the entire video. @@ -390,8 +372,8 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ByteBuffer contents = ByteBuffer.allocate(MAX_FIXED_LEAF_BOX_SIZE); contents.putInt(0x0); // version and flags - contents.putInt(creationTimestampSeconds); // creation_time; unsigned int(32) - contents.putInt(modificationTimestampSeconds); // modification_time; unsigned int(32) + contents.putInt(creationTimestampSeconds); // creation_time: unsigned int(32) + contents.putInt(modificationTimestampSeconds); // modification_time: unsigned int(32) contents.putInt(videoUnitTimebase); @@ -463,9 +445,9 @@ import org.checkerframework.checker.nullness.qual.PolyNull; String mimeType = checkNotNull(format.sampleMimeType); byte[] mimeBytes = Util.getUtf8Bytes(mimeType); contents.put(mimeBytes); // content_encoding - contents.put((byte) 0x00); + contents.put((byte) 0x0); contents.put(mimeBytes); // mime_format - contents.put((byte) 0x00); + contents.put((byte) 0x0); contents.flip(); return BoxUtils.wrapIntoBox("mett", contents); @@ -478,8 +460,6 @@ import org.checkerframework.checker.nullness.qual.PolyNull; /** Returns the dref (data references) box. */ public static ByteBuffer dref(ByteBuffer... dataLocationBoxes) { - // We have a "number of contained boxes" field; let's pretend this is also a box so that - // wrapBoxesIntoBoxes() can concatenate it with the rest. ByteBuffer header = ByteBuffer.allocate(8); header.putInt(0); header.putInt(dataLocationBoxes.length); @@ -506,12 +486,9 @@ import org.checkerframework.checker.nullness.qual.PolyNull; public static ByteBuffer localUrl() { ByteBuffer contents = ByteBuffer.allocate(4); - // Flag indicating that the data is in fact in this very file instead of a remote - // URL. Accordingly, no actual URL string is present. + // Indicates that the data is in this file instead of in a remote URL. Hence no URL is written. contents.putInt(1); - // Since we set the flag to 1, no actual URL needs to follow. - contents.flip(); return BoxUtils.wrapIntoBox("url ", contents); } @@ -529,14 +506,14 @@ import org.checkerframework.checker.nullness.qual.PolyNull; */ public static ByteBuffer hdlr(String handlerType, String handlerName) { ByteBuffer contents = ByteBuffer.allocate(MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x0); // version and flags. - contents.putInt(0); // pre_defined. - contents.put(Util.getUtf8Bytes(handlerType)); // handler_type. - contents.putInt(0); // reserved. - contents.putInt(0); // reserved. - contents.putInt(0); // reserved. - contents.put(Util.getUtf8Bytes(handlerName)); // name. - contents.put((byte) 0); // The null terminator for name. + contents.putInt(0x0); // version and flags + contents.putInt(0); // pre_defined + contents.put(Util.getUtf8Bytes(handlerType)); // handler_type + contents.putInt(0); // reserved + contents.putInt(0); // reserved + contents.putInt(0); // reserved + contents.put(Util.getUtf8Bytes(handlerName)); // name + contents.put((byte) 0); // The null terminator for name contents.flip(); return BoxUtils.wrapIntoBox("hdlr", contents); @@ -566,7 +543,6 @@ import org.checkerframework.checker.nullness.qual.PolyNull; *

This box contains user data like location info. */ public static ByteBuffer udta(@Nullable Mp4LocationData location) { - // We can just omit the entire box if there is no location info available. if (location == null) { return ByteBuffer.allocate(0); } @@ -576,7 +552,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ByteBuffer xyzBoxContents = ByteBuffer.allocate(locationString.length() + 2 + 2); xyzBoxContents.putShort((short) (xyzBoxContents.capacity() - 4)); - xyzBoxContents.putShort((short) 0x15C7); // language code? + xyzBoxContents.putShort((short) 0x15C7); // language code xyzBoxContents.put(Util.getUtf8Bytes(locationString)); checkState(xyzBoxContents.limit() == xyzBoxContents.capacity()); @@ -679,11 +655,11 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ByteBuffer contents = ByteBuffer.allocate(codecSpecificBox.remaining() + MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x00); // reserved + contents.putInt(0x0); // reserved contents.putShort((short) 0x0); // reserved contents.putShort((short) 0x1); // data ref index - contents.putInt(0x00); // reserved - contents.putInt(0x00); // reserved + contents.putInt(0x0); // reserved + contents.putInt(0x0); // reserved int channelCount = format.channelCount; contents.putShort((short) channelCount); @@ -863,19 +839,17 @@ import org.checkerframework.checker.nullness.qual.PolyNull; public static ByteBuffer stts(List durationsVu) { ByteBuffer contents = ByteBuffer.allocate(durationsVu.size() * 8 + MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x0); // version and flags. + contents.putInt(0x0); // version and flags - // We will know total entry count only after processing all the sample durations, so put in a + // Total entry count is known only after processing all sample durations, so put in a // placeholder for total entry count and store its index. int totalEntryCountIndex = contents.position(); - contents.putInt(0x0); // entry_count. + contents.putInt(0x0); // entry_count int totalEntryCount = 0; long lastDurationVu = -1L; int lastSampleCountIndex = -1; - // Note that the framework MediaMuxer adjust time deltas within plus-minus 100 us, so that - // samples have repeating duration values. It saves few entries in the table. for (int i = 0; i < durationsVu.size(); i++) { int durationVu = durationsVu.get(i); if (lastDurationVu != durationVu) { @@ -903,7 +877,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; List samplesInfo, List durationVu, int videoUnitTimescale) { // Generate the sample composition offsets list to create ctts box. List compositionOffsets = - Boxes.calculateSampleCompositionTimeOffsets(samplesInfo, durationVu, videoUnitTimescale); + calculateSampleCompositionTimeOffsets(samplesInfo, durationVu, videoUnitTimescale); if (compositionOffsets.isEmpty()) { return ByteBuffer.allocate(0); @@ -915,10 +889,10 @@ import org.checkerframework.checker.nullness.qual.PolyNull; contents.putInt(1); // version and flags. - // We will know total entry count only after processing all the composition offsets, so put in + // Total entry count is known only after processing all the composition offsets, so put in // a placeholder for total entry count and store its index. int totalEntryCountIndex = contents.position(); - contents.putInt(0x0); // entry_count. + contents.putInt(0x0); // entry_count int totalEntryCount = 0; int lastCompositionOffset = -1; @@ -996,14 +970,14 @@ import org.checkerframework.checker.nullness.qual.PolyNull; public static ByteBuffer stsz(List writtenSamples) { ByteBuffer contents = ByteBuffer.allocate(writtenSamples.size() * 4 + MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x0); // version and flags. + contents.putInt(0x0); // version and flags // TODO: b/270583563 - Consider optimizing for identically-sized samples. - // sample_size; specifying the default sample size. Set to zero to indicate that the samples - // have different sizes and they are stored in the sample size table. + // sample_size: specifying the default sample size. Set to zero to indicate that the samples + // have different sizes and they are stored in the sample size table. contents.putInt(0); - contents.putInt(writtenSamples.size()); // sample_count. + contents.putInt(writtenSamples.size()); // sample_count for (int i = 0; i < writtenSamples.size(); i++) { contents.putInt(writtenSamples.get(i).size); @@ -1018,17 +992,17 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ByteBuffer contents = ByteBuffer.allocate(writtenChunkSampleCounts.size() * 12 + MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x0); // version and flags. - contents.putInt(writtenChunkSampleCounts.size()); // entry_count. + contents.putInt(0x0); // version and flags + contents.putInt(writtenChunkSampleCounts.size()); // entry_count int currentChunk = 1; // TODO: b/270583563 - Consider optimizing for consecutive chunks having same number of samples. for (int i = 0; i < writtenChunkSampleCounts.size(); i++) { int samplesInChunk = writtenChunkSampleCounts.get(i); - contents.putInt(currentChunk); // first_chunk. - contents.putInt(samplesInChunk); // samples_per_chunk. - // sample_description_index; we have only one sample description in each track. + contents.putInt(currentChunk); // first_chunk + contents.putInt(samplesInChunk); // samples_per_chunk + // sample_description_index: there is only one sample description in each track. contents.putInt(1); currentChunk += 1; @@ -1044,12 +1018,12 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ByteBuffer.allocate(2 * BYTES_PER_INTEGER + writtenChunkOffsets.size() * BYTES_PER_INTEGER); contents.putInt(0x0); // version and flags - contents.putInt(writtenChunkOffsets.size()); // entry_count; unsigned int(32) + contents.putInt(writtenChunkOffsets.size()); // entry_count: unsigned int(32) for (int i = 0; i < writtenChunkOffsets.size(); i++) { long chunkOffset = writtenChunkOffsets.get(i); checkState(chunkOffset <= UNSIGNED_INT_MAX_VALUE, "Only 32-bit chunk offset is allowed"); - contents.putInt((int) chunkOffset); // chunk_offset; unsigned int(32) + contents.putInt((int) chunkOffset); // chunk_offset: unsigned int(32) } contents.flip(); @@ -1063,10 +1037,10 @@ import org.checkerframework.checker.nullness.qual.PolyNull; 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) + 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.putLong(writtenChunkOffsets.get(i)); // chunk_offset: unsigned int(64) } contents.flip(); @@ -1077,19 +1051,19 @@ import org.checkerframework.checker.nullness.qual.PolyNull; public static ByteBuffer stss(List writtenSamples) { ByteBuffer contents = ByteBuffer.allocate(writtenSamples.size() * 4 + MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x0); // version and flags. + contents.putInt(0x0); // version and flags - // We will know total entry count only after processing all the sample, so put in a placeholder + // Total entry count is known only after processing all sample, so put in a placeholder // for total entry count and store its index. int totalEntryCountIndex = contents.position(); - contents.putInt(writtenSamples.size()); // entry_count. + contents.putInt(writtenSamples.size()); // entry_count int currentSampleNumber = 1; int totalKeyFrames = 0; for (int i = 0; i < writtenSamples.size(); i++) { MediaCodec.BufferInfo info = writtenSamples.get(i); if ((info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) > 0) { - contents.putInt(currentSampleNumber); // sample_number. + contents.putInt(currentSampleNumber); totalKeyFrames++; } @@ -1106,8 +1080,8 @@ import org.checkerframework.checker.nullness.qual.PolyNull; public static ByteBuffer stsd(ByteBuffer sampleEntryBox) { ByteBuffer contents = ByteBuffer.allocate(sampleEntryBox.limit() + MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x0); // version and flags. - contents.putInt(1); // entry_count, We have only one sample description in each track. + contents.putInt(0x0); // version and flags + contents.putInt(1); // entry_count: there is only one sample description in each track. contents.put(sampleEntryBox); contents.flip(); @@ -1265,22 +1239,14 @@ import org.checkerframework.checker.nullness.qual.PolyNull; /** Adjusts the duration of the very last sample if needed. */ private static void adjustLastSampleDuration( List durationsToBeAdjustedVu, @Mp4Muxer.LastSampleDurationBehavior int behavior) { - // Technically, MP4 file stores frame durations, not timestamps. If a frame starts at a - // given timestamp then the duration of the last frame is not obvious. If samples follow each - // other in roughly regular intervals (e.g. in a normal, 30 fps video), it can be safely assumed - // that the last sample will have same duration (~33ms) as other samples. On the other hand, if - // there are just a few, irregularly spaced frames, with duplication, the entire duration of the - // video will increase, creating abnormal gaps. - + // For a track having less than 3 samples, duplicating the last frame duration will + // significantly increase the overall track duration, so avoid that. if (durationsToBeAdjustedVu.size() <= 2) { - // Nothing to duplicate if there are 0 or 1 entries. return; } switch (behavior) { case Mp4Muxer.LAST_SAMPLE_DURATION_BEHAVIOR_DUPLICATE_PREV_DURATION: - // This is the default MediaMuxer behavior: the last sample duration is a copy of the - // previous sample duration. durationsToBeAdjustedVu.set( durationsToBeAdjustedVu.size() - 1, durationsToBeAdjustedVu.get(durationsToBeAdjustedVu.size() - 2)); @@ -1299,10 +1265,10 @@ import org.checkerframework.checker.nullness.qual.PolyNull; private static ByteBuffer d263Box() { ByteBuffer d263Box = ByteBuffer.allocate(7); d263Box.put(" ".getBytes(UTF_8)); // 4 spaces (vendor) - d263Box.put((byte) 0x00); // decoder version + d263Box.put((byte) 0x0); // decoder version // TODO: b/352000778 - Get profile and level from format. d263Box.put((byte) 0x10); // level - d263Box.put((byte) 0x00); // profile + d263Box.put((byte) 0x0); // profile d263Box.flip(); return BoxUtils.wrapIntoBox("d263", d263Box); @@ -1429,7 +1395,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; contents.put(bitDepthLumaMinus8); contents.put(bitDepthChromaMinus8); - // avgFrameRate; value 0 indicates an unspecified average frame rate. + // avgFrameRate: value 0 indicates an unspecified average frame rate. contents.putShort((short) 0); // constantFrameRate (2 bits) + numTemporalLayers (3 bits) + temporalIdNested (1 bit) + @@ -1560,7 +1526,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; byte[] hdrStaticInfo = colorInfo.hdrStaticInfo; if (hdrStaticInfo != null) { ByteBuffer contents = ByteBuffer.allocate(MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x00); // version and flag. + contents.putInt(0x0); // version and flag contents.put(hdrStaticInfo); contents.flip(); return BoxUtils.wrapIntoBox("SmDm", contents); @@ -1590,7 +1556,6 @@ import org.checkerframework.checker.nullness.qual.PolyNull; contents.put((byte) 'l'); contents.put((byte) 'x'); - // Parameters going into the file. short primaries = 0; short transfer = 0; short matrix = 0; @@ -1688,7 +1653,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; getSizeBuffer(csdSize + dsiSizeBuffer.remaining() + dcdSizeBuffer.remaining() + 21); ByteBuffer contents = ByteBuffer.allocate(csdSize + MAX_FIXED_LEAF_BOX_SIZE); - contents.putInt(0x00); // Version and flags. + contents.putInt(0x0); // version and flags contents.put((byte) 0x03); // ES_DescrTag contents.put(esdSizeBuffer); @@ -1696,7 +1661,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; contents.putShort((short) 0x0000); // ES_ID // streamDependenceFlag (1 bit) + URL_Flag (1 bit) + OCRstreamFlag (1 bit) + streamPriority (5 // bits) - contents.put(isVideo ? (byte) 0x1f : (byte) 0x00); + contents.put(isVideo ? (byte) 0x1f : (byte) 0x0); contents.put((byte) 0x04); // DecoderConfigDescrTag contents.put(dcdSizeBuffer); @@ -1709,7 +1674,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; int size = isVideo ? 0x017700 : 0x000300; contents.putShort((short) ((size >> 8) & 0xFFFF)); // First 16 bits of buffer size. - contents.put((byte) 0x00); // Last 8 bits of buffer size. + contents.put((byte) 0x0); // Last 8 bits of buffer size. contents.putInt(peakBitrate != Format.NO_VALUE ? peakBitrate : 0); contents.putInt(averageBitrate != Format.NO_VALUE ? averageBitrate : 0); @@ -1818,16 +1783,13 @@ import org.checkerframework.checker.nullness.qual.PolyNull; throw new IllegalArgumentException("Non-length-3 language code: " + code); } - // Use an int so that we don't bump into the issue of Java not having unsigned types. We take - // the last 5 bits of each letter to supply 5 bits each of the eventual code. - + // Take only last 5 bits of each letter. int value = (bytes[2] & 0x1F); value += (bytes[1] & 0x1F) << 5; value += (bytes[0] & 0x1F) << 10; - // This adds up to 15 bits; the 16th one is really supposed to be 0. - checkState((value & 0x8000) == 0); - return (short) (value & 0xFFFF); + // Total 15 bits for the language code and the 16th bit should be 0. + return (short) (value & 0x7FFF); } /** @@ -1859,6 +1821,6 @@ import org.checkerframework.checker.nullness.qual.PolyNull; /** Converts microseconds to video units, using the provided timebase. */ private static long vuFromUs(long timestampUs, long videoUnitTimebase) { - return timestampUs * videoUnitTimebase / 1_000_000L; // (division for us to s conversion) + return timestampUs * videoUnitTimebase / 1_000_000L; // Division for microsecond to second. } }