Change sample duration type from Long to Integer

In muxer only 32-bit sample duration is supported. Earlier the duration was type
casted at different places.

PiperOrigin-RevId: 643954226
This commit is contained in:
sheenachhabra 2024-06-17 04:10:26 -07:00 committed by Copybara-Service
parent eedfb9960e
commit ded1adc092
4 changed files with 39 additions and 42 deletions

View File

@ -42,7 +42,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
/** /**
* Writes out various types of boxes as per MP4 (ISO/IEC 14496-12) standards. * 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. * @return A list of all the sample durations.
*/ */
// TODO: b/280084657 - Add support for setting last sample duration. // TODO: b/280084657 - Add support for setting last sample duration.
// TODO: b/317373578 - Consider changing return type to List<Integer>. public static List<Integer> convertPresentationTimestampsToDurationsVu(
public static List<Long> convertPresentationTimestampsToDurationsVu(
List<BufferInfo> samplesInfo, List<BufferInfo> samplesInfo,
long firstSamplePresentationTimeUs, long firstSamplePresentationTimeUs,
int videoUnitTimescale, int videoUnitTimescale,
@Mp4Muxer.LastFrameDurationBehavior int lastDurationBehavior) { @Mp4Muxer.LastFrameDurationBehavior int lastDurationBehavior) {
List<Long> presentationTimestampsUs = new ArrayList<>(samplesInfo.size()); List<Long> presentationTimestampsUs = new ArrayList<>(samplesInfo.size());
List<Long> durationsVu = new ArrayList<>(samplesInfo.size()); List<Integer> durationsVu = new ArrayList<>(samplesInfo.size());
if (samplesInfo.isEmpty()) { if (samplesInfo.isEmpty()) {
return durationsVu; return durationsVu;
@ -672,23 +670,20 @@ import java.util.Locale;
long currentSampleDurationVu = long currentSampleDurationVu =
vuFromUs(nextSampleTimeUs, videoUnitTimescale) vuFromUs(nextSampleTimeUs, videoUnitTimescale)
- vuFromUs(currentSampleTimeUs, videoUnitTimescale); - vuFromUs(currentSampleTimeUs, videoUnitTimescale);
if (currentSampleDurationVu > Integer.MAX_VALUE) { checkState(
throw new IllegalArgumentException( currentSampleDurationVu <= Integer.MAX_VALUE, "Only 32-bit sample duration is allowed");
String.format( durationsVu.add((int) currentSampleDurationVu);
Locale.US, "Timestamp delta %d doesn't fit into an int", currentSampleDurationVu));
}
durationsVu.add(currentSampleDurationVu);
currentSampleTimeUs = nextSampleTimeUs; currentSampleTimeUs = nextSampleTimeUs;
} }
// Default duration for the last sample. // Default duration for the last sample.
durationsVu.add(0L); durationsVu.add(0);
adjustLastSampleDuration(durationsVu, lastDurationBehavior); adjustLastSampleDuration(durationsVu, lastDurationBehavior);
return durationsVu; return durationsVu;
} }
/** Generates the stts (decoding time to sample) box. */ /** Generates the stts (decoding time to sample) box. */
public static ByteBuffer stts(List<Long> durationsVu) { public static ByteBuffer stts(List<Integer> durationsVu) {
ByteBuffer contents = ByteBuffer.allocate(durationsVu.size() * 8 + MAX_FIXED_LEAF_BOX_SIZE); ByteBuffer contents = ByteBuffer.allocate(durationsVu.size() * 8 + MAX_FIXED_LEAF_BOX_SIZE);
contents.putInt(0x0); // version and flags. 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 // 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. // samples have repeating duration values. It saves few entries in the table.
for (int i = 0; i < durationsVu.size(); i++) { for (int i = 0; i < durationsVu.size(); i++) {
long durationVu = durationsVu.get(i); int durationVu = durationsVu.get(i);
if (lastDurationVu != durationVu) { if (lastDurationVu != durationVu) {
lastDurationVu = durationVu; lastDurationVu = durationVu;
lastSampleCountIndex = contents.position(); 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 // sample_count; this will be updated instead of adding a new entry if the next sample has
// the same duration. // the same duration.
contents.putInt(1); contents.putInt(1);
contents.putInt((int) durationVu); // sample_delta. contents.putInt(durationVu); // sample_delta
totalEntryCount++; totalEntryCount++;
} else { } else {
contents.putInt(lastSampleCountIndex, contents.getInt(lastSampleCountIndex) + 1); contents.putInt(lastSampleCountIndex, contents.getInt(lastSampleCountIndex) + 1);
@ -728,7 +723,7 @@ import java.util.Locale;
/** Returns the ctts (composition time to sample) box. */ /** Returns the ctts (composition time to sample) box. */
public static ByteBuffer ctts( public static ByteBuffer ctts(
List<BufferInfo> samplesInfo, List<Long> durationVu, int videoUnitTimescale) { List<BufferInfo> samplesInfo, List<Integer> durationVu, int videoUnitTimescale) {
// Generate the sample composition offsets list to create ctts box. // Generate the sample composition offsets list to create ctts box.
List<Integer> compositionOffsets = List<Integer> compositionOffsets =
Boxes.calculateSampleCompositionTimeOffsets(samplesInfo, durationVu, videoUnitTimescale); Boxes.calculateSampleCompositionTimeOffsets(samplesInfo, durationVu, videoUnitTimescale);
@ -786,7 +781,7 @@ import java.util.Locale;
* @return A list of all the sample composition time offsets. * @return A list of all the sample composition time offsets.
*/ */
public static List<Integer> calculateSampleCompositionTimeOffsets( public static List<Integer> calculateSampleCompositionTimeOffsets(
List<BufferInfo> samplesInfo, List<Long> durationVu, int videoUnitTimescale) { List<BufferInfo> samplesInfo, List<Integer> durationVu, int videoUnitTimescale) {
List<Integer> compositionOffsets = new ArrayList<>(samplesInfo.size()); List<Integer> compositionOffsets = new ArrayList<>(samplesInfo.size());
if (samplesInfo.isEmpty()) { if (samplesInfo.isEmpty()) {
return compositionOffsets; return compositionOffsets;
@ -802,7 +797,9 @@ import java.util.Locale;
samplesInfo.get(sampleId).presentationTimeUs - firstSamplePresentationTimeUs; samplesInfo.get(sampleId).presentationTimeUs - firstSamplePresentationTimeUs;
long currentCompositionOffsetVu = long currentCompositionOffsetVu =
vuFromUs(currentSampleCompositionTimeUs, videoUnitTimescale) - currentSampleDecodeTime; 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) currentSampleDecodeTime += durationVu.get(sampleId); // DT(n+1) = DT(n) + STTS(n)
compositionOffsets.add((int) currentCompositionOffsetVu); compositionOffsets.add((int) currentCompositionOffsetVu);
@ -1022,7 +1019,7 @@ import java.util.Locale;
contents.putInt(dataOffset); // A signed int(32) contents.putInt(dataOffset); // A signed int(32)
for (int i = 0; i < samplesMetadata.size(); i++) { for (int i = 0; i < samplesMetadata.size(); i++) {
SampleMetadata currentSampleMetadata = samplesMetadata.get(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.size); // An unsigned int(32)
contents.putInt( contents.putInt(
(currentSampleMetadata.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0 (currentSampleMetadata.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0
@ -1064,7 +1061,7 @@ import java.util.Locale;
// TODO: b/317117431 - Change this method to getLastSampleDuration(). // TODO: b/317117431 - Change this method to getLastSampleDuration().
/** Adjusts the duration of the very last sample if needed. */ /** Adjusts the duration of the very last sample if needed. */
private static void adjustLastSampleDuration( private static void adjustLastSampleDuration(
List<Long> durationsToBeAdjustedVu, @Mp4Muxer.LastFrameDurationBehavior int behavior) { List<Integer> durationsToBeAdjustedVu, @Mp4Muxer.LastFrameDurationBehavior int behavior) {
// Technically, MP4 file stores frame durations, not timestamps. If a frame starts at a // 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 // 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 // other in roughly regular intervals (e.g. in a normal, 30 fps video), it can be safely assumed

View File

@ -49,12 +49,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* package */ final class FragmentedMp4Writer { /* package */ final class FragmentedMp4Writer {
/** Provides a limited set of sample metadata. */ /** Provides a limited set of sample metadata. */
public static class SampleMetadata { public static class SampleMetadata {
public final long durationVu; public final int durationVu;
public final int size; public final int size;
public final int flags; public final int flags;
public final int compositionTimeOffsetVu; 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.durationVu = durationsVu;
this.size = size; this.size = size;
this.flags = flags; this.flags = flags;
@ -320,7 +320,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
boolean hasBFrame = false; boolean hasBFrame = false;
ImmutableList<BufferInfo> pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build(); ImmutableList<BufferInfo> pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build();
List<Long> sampleDurations = List<Integer> sampleDurations =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
pendingSamplesBufferInfo, pendingSamplesBufferInfo,
/* firstSamplePresentationTimeUs= */ currentFragmentSequenceNumber == 1 /* firstSamplePresentationTimeUs= */ currentFragmentSequenceNumber == 1

View File

@ -79,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
String languageCode = bcp47LanguageTagToIso3(format.language); String languageCode = bcp47LanguageTagToIso3(format.language);
// Generate the sample durations to calculate the total duration for tkhd box. // Generate the sample durations to calculate the total duration for tkhd box.
List<Long> sampleDurationsVu = List<Integer> sampleDurationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
track.writtenSamples(), track.writtenSamples(),
minInputPtsUs, minInputPtsUs,

View File

@ -403,14 +403,14 @@ public class BoxesTest {
List<MediaCodec.BufferInfo> sampleBufferInfos = List<MediaCodec.BufferInfo> sampleBufferInfos =
createBufferInfoListWithSamplePresentationTimestamps(0L); createBufferInfoListWithSamplePresentationTimestamps(0L);
List<Long> durationsVu = List<Integer> durationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
sampleBufferInfos, sampleBufferInfos,
/* firstSamplePresentationTimeUs= */ 0L, /* firstSamplePresentationTimeUs= */ 0L,
VU_TIMEBASE, VU_TIMEBASE,
LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME); LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME);
assertThat(durationsVu).containsExactly(0L); assertThat(durationsVu).containsExactly(0);
} }
@Test @Test
@ -419,14 +419,14 @@ public class BoxesTest {
List<MediaCodec.BufferInfo> sampleBufferInfos = List<MediaCodec.BufferInfo> sampleBufferInfos =
createBufferInfoListWithSamplePresentationTimestamps(5_000L); createBufferInfoListWithSamplePresentationTimestamps(5_000L);
List<Long> durationsVu = List<Integer> durationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
sampleBufferInfos, sampleBufferInfos,
/* firstSamplePresentationTimeUs= */ 0L, /* firstSamplePresentationTimeUs= */ 0L,
VU_TIMEBASE, VU_TIMEBASE,
LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME); LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME);
assertThat(durationsVu).containsExactly(0L); assertThat(durationsVu).containsExactly(0);
} }
@Test @Test
@ -435,14 +435,14 @@ public class BoxesTest {
List<MediaCodec.BufferInfo> sampleBufferInfos = List<MediaCodec.BufferInfo> sampleBufferInfos =
createBufferInfoListWithSamplePresentationTimestamps(0L, 30_000L, 80_000L); createBufferInfoListWithSamplePresentationTimestamps(0L, 30_000L, 80_000L);
List<Long> durationsVu = List<Integer> durationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
sampleBufferInfos, sampleBufferInfos,
/* firstSamplePresentationTimeUs= */ 0L, /* firstSamplePresentationTimeUs= */ 0L,
VU_TIMEBASE, VU_TIMEBASE,
LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME); LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME);
assertThat(durationsVu).containsExactly(3_000L, 5_000L, 0L); assertThat(durationsVu).containsExactly(3_000, 5_000, 0);
} }
@Test @Test
@ -451,14 +451,14 @@ public class BoxesTest {
List<MediaCodec.BufferInfo> sampleBufferInfos = List<MediaCodec.BufferInfo> sampleBufferInfos =
createBufferInfoListWithSamplePresentationTimestamps(0L, 30_000L, 80_000L); createBufferInfoListWithSamplePresentationTimestamps(0L, 30_000L, 80_000L);
List<Long> durationsVu = List<Integer> durationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
sampleBufferInfos, sampleBufferInfos,
/* firstSamplePresentationTimeUs= */ 0L, /* firstSamplePresentationTimeUs= */ 0L,
VU_TIMEBASE, VU_TIMEBASE,
LAST_FRAME_DURATION_BEHAVIOR_DUPLICATE_PREV_DURATION); 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 @Test
@ -467,19 +467,19 @@ public class BoxesTest {
List<MediaCodec.BufferInfo> sampleBufferInfos = List<MediaCodec.BufferInfo> sampleBufferInfos =
createBufferInfoListWithSamplePresentationTimestamps(0L, 10_000L, 1_000L, 2_000L, 11_000L); createBufferInfoListWithSamplePresentationTimestamps(0L, 10_000L, 1_000L, 2_000L, 11_000L);
List<Long> durationsVu = List<Integer> durationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
sampleBufferInfos, sampleBufferInfos,
/* firstSamplePresentationTimeUs= */ 0L, /* firstSamplePresentationTimeUs= */ 0L,
VU_TIMEBASE, VU_TIMEBASE,
LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME); LAST_FRAME_DURATION_BEHAVIOR_INSERT_SHORT_FRAME);
assertThat(durationsVu).containsExactly(100L, 100L, 800L, 100L, 0L); assertThat(durationsVu).containsExactly(100, 100, 800, 100, 0);
} }
@Test @Test
public void createSttsBox_withSingleSampleDuration_matchesExpected() throws IOException { public void createSttsBox_withSingleSampleDuration_matchesExpected() throws IOException {
ImmutableList<Long> sampleDurations = ImmutableList.of(500L); ImmutableList<Integer> sampleDurations = ImmutableList.of(500);
ByteBuffer sttsBox = Boxes.stts(sampleDurations); ByteBuffer sttsBox = Boxes.stts(sampleDurations);
@ -492,7 +492,7 @@ public class BoxesTest {
@Test @Test
public void createSttsBox_withAllDifferentSampleDurations_matchesExpected() throws IOException { public void createSttsBox_withAllDifferentSampleDurations_matchesExpected() throws IOException {
ImmutableList<Long> sampleDurations = ImmutableList.of(1_000L, 2_000L, 3_000L, 5_000L); ImmutableList<Integer> sampleDurations = ImmutableList.of(1_000, 2_000, 3_000, 5_000);
ByteBuffer sttsBox = Boxes.stts(sampleDurations); ByteBuffer sttsBox = Boxes.stts(sampleDurations);
@ -506,7 +506,7 @@ public class BoxesTest {
@Test @Test
public void createSttsBox_withFewConsecutiveSameSampleDurations_matchesExpected() public void createSttsBox_withFewConsecutiveSameSampleDurations_matchesExpected()
throws IOException { throws IOException {
ImmutableList<Long> sampleDurations = ImmutableList.of(1_000L, 2_000L, 2_000L, 2_000L); ImmutableList<Integer> sampleDurations = ImmutableList.of(1_000, 2_000, 2_000, 2_000);
ByteBuffer sttsBox = Boxes.stts(sampleDurations); ByteBuffer sttsBox = Boxes.stts(sampleDurations);
@ -521,7 +521,7 @@ public class BoxesTest {
public void createCttsBox_withSingleSampleTimestamp_returnsEmptyBox() { public void createCttsBox_withSingleSampleTimestamp_returnsEmptyBox() {
List<MediaCodec.BufferInfo> sampleBufferInfos = List<MediaCodec.BufferInfo> sampleBufferInfos =
createBufferInfoListWithSamplePresentationTimestamps(400); createBufferInfoListWithSamplePresentationTimestamps(400);
List<Long> durationsVu = List<Integer> durationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
sampleBufferInfos, sampleBufferInfos,
/* firstSamplePresentationTimeUs= */ 0L, /* firstSamplePresentationTimeUs= */ 0L,
@ -538,7 +538,7 @@ public class BoxesTest {
public void createCttsBox_withNoBframesSampleTimestamps_returnsEmptyBox() throws IOException { public void createCttsBox_withNoBframesSampleTimestamps_returnsEmptyBox() throws IOException {
List<MediaCodec.BufferInfo> sampleBufferInfos = List<MediaCodec.BufferInfo> sampleBufferInfos =
createBufferInfoListWithSamplePresentationTimestamps(0L, 1000L, 2000L); createBufferInfoListWithSamplePresentationTimestamps(0L, 1000L, 2000L);
List<Long> durationsVu = List<Integer> durationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
sampleBufferInfos, sampleBufferInfos,
/* firstSamplePresentationTimeUs= */ 0L, /* firstSamplePresentationTimeUs= */ 0L,
@ -557,7 +557,7 @@ public class BoxesTest {
createBufferInfoListWithSamplePresentationTimestamps( createBufferInfoListWithSamplePresentationTimestamps(
0, 400, 200, 100, 300, 800, 600, 500, 700); 0, 400, 200, 100, 300, 800, 600, 500, 700);
List<Long> durationsVu = List<Integer> durationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
sampleBufferInfos, sampleBufferInfos,
/* firstSamplePresentationTimeUs= */ 0L, /* firstSamplePresentationTimeUs= */ 0L,
@ -577,7 +577,7 @@ public class BoxesTest {
createBufferInfoListWithSamplePresentationTimestamps( createBufferInfoListWithSamplePresentationTimestamps(
23698215060L, 23698248252L, 23698347988L, 23698488968L, 23698547416L); 23698215060L, 23698248252L, 23698347988L, 23698488968L, 23698547416L);
List<Long> durationsVu = List<Integer> durationsVu =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(
sampleBufferInfos, sampleBufferInfos,
/* firstSamplePresentationTimeUs= */ 23698215060L, /* firstSamplePresentationTimeUs= */ 23698215060L,
@ -679,7 +679,7 @@ public class BoxesTest {
for (int i = 0; i < sampleCount; i++) { for (int i = 0; i < sampleCount; i++) {
samplesMetadata.add( samplesMetadata.add(
new SampleMetadata( new SampleMetadata(
/* durationsVu= */ 2_000L, /* durationsVu= */ 2_000,
/* size= */ 5_000, /* size= */ 5_000,
/* flags= */ i == 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0, /* flags= */ i == 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0,
/* compositionTimeOffsetVu= */ 0)); /* compositionTimeOffsetVu= */ 0));
@ -700,7 +700,7 @@ public class BoxesTest {
for (int i = 0; i < sampleCount; i++) { for (int i = 0; i < sampleCount; i++) {
samplesMetadata.add( samplesMetadata.add(
new SampleMetadata( new SampleMetadata(
/* durationsVu= */ 2_000L, /* durationsVu= */ 2_000,
/* size= */ 5_000, /* size= */ 5_000,
/* flags= */ i == 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0, /* flags= */ i == 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0,
/* compositionTimeOffsetVu= */ 100)); /* compositionTimeOffsetVu= */ 100));