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 10851ca00d..6db416fb23 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java @@ -42,7 +42,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Locale; /** * Writes out various types of boxes as per MP4 (ISO/IEC 14496-12) standards. @@ -636,14 +635,13 @@ import java.util.Locale; * @return A list of all the sample durations. */ // TODO: b/280084657 - Add support for setting last sample duration. - // TODO: b/317373578 - Consider changing return type to List. - public static List convertPresentationTimestampsToDurationsVu( + public static List convertPresentationTimestampsToDurationsVu( List samplesInfo, long firstSamplePresentationTimeUs, int videoUnitTimescale, @Mp4Muxer.LastFrameDurationBehavior int lastDurationBehavior) { List presentationTimestampsUs = new ArrayList<>(samplesInfo.size()); - List durationsVu = new ArrayList<>(samplesInfo.size()); + List durationsVu = new ArrayList<>(samplesInfo.size()); if (samplesInfo.isEmpty()) { return durationsVu; @@ -672,23 +670,20 @@ import java.util.Locale; long currentSampleDurationVu = vuFromUs(nextSampleTimeUs, videoUnitTimescale) - vuFromUs(currentSampleTimeUs, videoUnitTimescale); - if (currentSampleDurationVu > Integer.MAX_VALUE) { - throw new IllegalArgumentException( - String.format( - Locale.US, "Timestamp delta %d doesn't fit into an int", currentSampleDurationVu)); - } - durationsVu.add(currentSampleDurationVu); + checkState( + currentSampleDurationVu <= Integer.MAX_VALUE, "Only 32-bit sample duration is allowed"); + durationsVu.add((int) currentSampleDurationVu); currentSampleTimeUs = nextSampleTimeUs; } // Default duration for the last sample. - durationsVu.add(0L); + durationsVu.add(0); adjustLastSampleDuration(durationsVu, lastDurationBehavior); return durationsVu; } /** Generates the stts (decoding time to sample) box. */ - public static ByteBuffer stts(List durationsVu) { + public static ByteBuffer stts(List durationsVu) { ByteBuffer contents = ByteBuffer.allocate(durationsVu.size() * 8 + MAX_FIXED_LEAF_BOX_SIZE); contents.putInt(0x0); // version and flags. @@ -705,7 +700,7 @@ import java.util.Locale; // 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++) { - long durationVu = durationsVu.get(i); + int durationVu = durationsVu.get(i); if (lastDurationVu != durationVu) { lastDurationVu = durationVu; lastSampleCountIndex = contents.position(); @@ -713,7 +708,7 @@ import java.util.Locale; // sample_count; this will be updated instead of adding a new entry if the next sample has // the same duration. contents.putInt(1); - contents.putInt((int) durationVu); // sample_delta. + contents.putInt(durationVu); // sample_delta totalEntryCount++; } else { contents.putInt(lastSampleCountIndex, contents.getInt(lastSampleCountIndex) + 1); @@ -728,7 +723,7 @@ import java.util.Locale; /** Returns the ctts (composition time to sample) box. */ public static ByteBuffer ctts( - List samplesInfo, List durationVu, int videoUnitTimescale) { + List samplesInfo, List durationVu, int videoUnitTimescale) { // Generate the sample composition offsets list to create ctts box. List compositionOffsets = Boxes.calculateSampleCompositionTimeOffsets(samplesInfo, durationVu, videoUnitTimescale); @@ -786,7 +781,7 @@ import java.util.Locale; * @return A list of all the sample composition time offsets. */ public static List calculateSampleCompositionTimeOffsets( - List samplesInfo, List durationVu, int videoUnitTimescale) { + List samplesInfo, List durationVu, int videoUnitTimescale) { List compositionOffsets = new ArrayList<>(samplesInfo.size()); if (samplesInfo.isEmpty()) { return compositionOffsets; @@ -802,7 +797,9 @@ import java.util.Locale; samplesInfo.get(sampleId).presentationTimeUs - firstSamplePresentationTimeUs; long currentCompositionOffsetVu = vuFromUs(currentSampleCompositionTimeUs, videoUnitTimescale) - currentSampleDecodeTime; - checkState(currentCompositionOffsetVu <= Integer.MAX_VALUE, "Only 32-bit offset is allowed"); + checkState( + currentCompositionOffsetVu <= Integer.MAX_VALUE, + "Only 32-bit composition offset is allowed"); currentSampleDecodeTime += durationVu.get(sampleId); // DT(n+1) = DT(n) + STTS(n) compositionOffsets.add((int) currentCompositionOffsetVu); @@ -1022,7 +1019,7 @@ import java.util.Locale; contents.putInt(dataOffset); // A signed int(32) for (int i = 0; i < samplesMetadata.size(); i++) { SampleMetadata currentSampleMetadata = samplesMetadata.get(i); - contents.putInt((int) currentSampleMetadata.durationVu); // An unsigned int(32) + contents.putInt(currentSampleMetadata.durationVu); // An unsigned int(32) contents.putInt(currentSampleMetadata.size); // An unsigned int(32) contents.putInt( (currentSampleMetadata.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0 @@ -1064,7 +1061,7 @@ import java.util.Locale; // TODO: b/317117431 - Change this method to getLastSampleDuration(). /** Adjusts the duration of the very last sample if needed. */ private static void adjustLastSampleDuration( - List durationsToBeAdjustedVu, @Mp4Muxer.LastFrameDurationBehavior int behavior) { + List durationsToBeAdjustedVu, @Mp4Muxer.LastFrameDurationBehavior 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 diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java index 00b4a90adf..474133b2f9 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java @@ -49,12 +49,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /* package */ final class FragmentedMp4Writer { /** Provides a limited set of sample metadata. */ public static class SampleMetadata { - public final long durationVu; + public final int durationVu; public final int size; public final int flags; public final int compositionTimeOffsetVu; - public SampleMetadata(long durationsVu, int size, int flags, int compositionTimeOffsetVu) { + public SampleMetadata(int durationsVu, int size, int flags, int compositionTimeOffsetVu) { this.durationVu = durationsVu; this.size = size; this.flags = flags; @@ -320,7 +320,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; boolean hasBFrame = false; ImmutableList pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build(); - List sampleDurations = + List sampleDurations = Boxes.convertPresentationTimestampsToDurationsVu( pendingSamplesBufferInfo, /* firstSamplePresentationTimeUs= */ currentFragmentSequenceNumber == 1 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 8565430ee7..6eb348866f 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4MoovStructure.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4MoovStructure.java @@ -79,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; String languageCode = bcp47LanguageTagToIso3(format.language); // Generate the sample durations to calculate the total duration for tkhd box. - List sampleDurationsVu = + List sampleDurationsVu = Boxes.convertPresentationTimestampsToDurationsVu( track.writtenSamples(), minInputPtsUs, 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 a6710fb37b..44e74aac17 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java @@ -403,14 +403,14 @@ public class BoxesTest { List sampleBufferInfos = createBufferInfoListWithSamplePresentationTimestamps(0L); - List durationsVu = + List durationsVu = Boxes.convertPresentationTimestampsToDurationsVu( sampleBufferInfos, /* firstSamplePresentationTimeUs= */ 0L, VU_TIMEBASE, LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME); - assertThat(durationsVu).containsExactly(0L); + assertThat(durationsVu).containsExactly(0); } @Test @@ -419,14 +419,14 @@ public class BoxesTest { List sampleBufferInfos = createBufferInfoListWithSamplePresentationTimestamps(5_000L); - List durationsVu = + List durationsVu = Boxes.convertPresentationTimestampsToDurationsVu( sampleBufferInfos, /* firstSamplePresentationTimeUs= */ 0L, VU_TIMEBASE, LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME); - assertThat(durationsVu).containsExactly(0L); + assertThat(durationsVu).containsExactly(0); } @Test @@ -435,14 +435,14 @@ public class BoxesTest { List sampleBufferInfos = createBufferInfoListWithSamplePresentationTimestamps(0L, 30_000L, 80_000L); - List durationsVu = + List durationsVu = Boxes.convertPresentationTimestampsToDurationsVu( sampleBufferInfos, /* firstSamplePresentationTimeUs= */ 0L, VU_TIMEBASE, LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME); - assertThat(durationsVu).containsExactly(3_000L, 5_000L, 0L); + assertThat(durationsVu).containsExactly(3_000, 5_000, 0); } @Test @@ -451,14 +451,14 @@ public class BoxesTest { List sampleBufferInfos = createBufferInfoListWithSamplePresentationTimestamps(0L, 30_000L, 80_000L); - List durationsVu = + List durationsVu = Boxes.convertPresentationTimestampsToDurationsVu( sampleBufferInfos, /* firstSamplePresentationTimeUs= */ 0L, VU_TIMEBASE, LAST_FRAME_DURATION_BEHAVIOR_DUPLICATE_PREV_DURATION); - assertThat(durationsVu).containsExactly(3_000L, 5_000L, 5_000L); + assertThat(durationsVu).containsExactly(3_000, 5_000, 5_000); } @Test @@ -467,19 +467,19 @@ public class BoxesTest { List sampleBufferInfos = createBufferInfoListWithSamplePresentationTimestamps(0L, 10_000L, 1_000L, 2_000L, 11_000L); - List durationsVu = + List durationsVu = Boxes.convertPresentationTimestampsToDurationsVu( sampleBufferInfos, /* firstSamplePresentationTimeUs= */ 0L, VU_TIMEBASE, LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME); - assertThat(durationsVu).containsExactly(100L, 100L, 800L, 100L, 0L); + assertThat(durationsVu).containsExactly(100, 100, 800, 100, 0); } @Test public void createSttsBox_withSingleSampleDuration_matchesExpected() throws IOException { - ImmutableList sampleDurations = ImmutableList.of(500L); + ImmutableList sampleDurations = ImmutableList.of(500); ByteBuffer sttsBox = Boxes.stts(sampleDurations); @@ -492,7 +492,7 @@ public class BoxesTest { @Test public void createSttsBox_withAllDifferentSampleDurations_matchesExpected() throws IOException { - ImmutableList sampleDurations = ImmutableList.of(1_000L, 2_000L, 3_000L, 5_000L); + ImmutableList sampleDurations = ImmutableList.of(1_000, 2_000, 3_000, 5_000); ByteBuffer sttsBox = Boxes.stts(sampleDurations); @@ -506,7 +506,7 @@ public class BoxesTest { @Test public void createSttsBox_withFewConsecutiveSameSampleDurations_matchesExpected() throws IOException { - ImmutableList sampleDurations = ImmutableList.of(1_000L, 2_000L, 2_000L, 2_000L); + ImmutableList sampleDurations = ImmutableList.of(1_000, 2_000, 2_000, 2_000); ByteBuffer sttsBox = Boxes.stts(sampleDurations); @@ -521,7 +521,7 @@ public class BoxesTest { public void createCttsBox_withSingleSampleTimestamp_returnsEmptyBox() { List sampleBufferInfos = createBufferInfoListWithSamplePresentationTimestamps(400); - List durationsVu = + List durationsVu = Boxes.convertPresentationTimestampsToDurationsVu( sampleBufferInfos, /* firstSamplePresentationTimeUs= */ 0L, @@ -538,7 +538,7 @@ public class BoxesTest { public void createCttsBox_withNoBframesSampleTimestamps_returnsEmptyBox() throws IOException { List sampleBufferInfos = createBufferInfoListWithSamplePresentationTimestamps(0L, 1000L, 2000L); - List durationsVu = + List durationsVu = Boxes.convertPresentationTimestampsToDurationsVu( sampleBufferInfos, /* firstSamplePresentationTimeUs= */ 0L, @@ -557,7 +557,7 @@ public class BoxesTest { createBufferInfoListWithSamplePresentationTimestamps( 0, 400, 200, 100, 300, 800, 600, 500, 700); - List durationsVu = + List durationsVu = Boxes.convertPresentationTimestampsToDurationsVu( sampleBufferInfos, /* firstSamplePresentationTimeUs= */ 0L, @@ -577,7 +577,7 @@ public class BoxesTest { createBufferInfoListWithSamplePresentationTimestamps( 23698215060L, 23698248252L, 23698347988L, 23698488968L, 23698547416L); - List durationsVu = + List durationsVu = Boxes.convertPresentationTimestampsToDurationsVu( sampleBufferInfos, /* firstSamplePresentationTimeUs= */ 23698215060L, @@ -679,7 +679,7 @@ public class BoxesTest { for (int i = 0; i < sampleCount; i++) { samplesMetadata.add( new SampleMetadata( - /* durationsVu= */ 2_000L, + /* durationsVu= */ 2_000, /* size= */ 5_000, /* flags= */ i == 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0, /* compositionTimeOffsetVu= */ 0)); @@ -700,7 +700,7 @@ public class BoxesTest { for (int i = 0; i < sampleCount; i++) { samplesMetadata.add( new SampleMetadata( - /* durationsVu= */ 2_000L, + /* durationsVu= */ 2_000, /* size= */ 5_000, /* flags= */ i == 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0, /* compositionTimeOffsetVu= */ 100));