From b0b54ca018ede81046c9775b1c2378f5f3f85a2e Mon Sep 17 00:00:00 2001 From: sheenachhabra Date: Fri, 27 Sep 2024 10:58:04 -0700 Subject: [PATCH] Calculate min timestamp across tracks in the Boxes.moov method The Boxes.moov method can do the calculation instead of caller doing it. PiperOrigin-RevId: 679653033 --- .../java/androidx/media3/muxer/Boxes.java | 23 +++++++++++++- .../media3/muxer/FragmentedMp4Writer.java | 7 +---- .../java/androidx/media3/muxer/Mp4Writer.java | 31 ++----------------- 3 files changed, 25 insertions(+), 36 deletions(-) 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 f7da2bfecc..d2a1b2fc6c 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.MuxerUtil.UNSIGNED_INT_MAX_VALUE; import static java.lang.Math.max; +import static java.lang.Math.min; import static java.nio.charset.StandardCharsets.UTF_8; import android.media.MediaCodec; @@ -118,7 +119,6 @@ import org.checkerframework.checker.nullness.qual.PolyNull; public static ByteBuffer moov( List tracks, MetadataCollector metadataCollector, - long minInputPtsUs, boolean isFragmentedMp4, @Mp4Muxer.LastSampleDurationBehavior int lastSampleDurationBehavior) { // The timestamp will always fit into a 32-bit integer. This is already validated in the @@ -127,6 +127,15 @@ import org.checkerframework.checker.nullness.qual.PolyNull; int creationTimestampSeconds = (int) metadataCollector.timestampData.creationTimestampSeconds; int modificationTimestampSeconds = (int) metadataCollector.timestampData.modificationTimestampSeconds; + long minInputPtsUs = findMinimumPresentationTimestampUsAcrossTracks(tracks); + + // For a non fragmented MP4 file, avoid writing an empty moov box. + // For a fragmented MP4 file, the minInputPtsUs gets ignored as the moov box is written without + // any sample info. + if (!isFragmentedMp4 && minInputPtsUs == C.TIME_UNSET) { + return ByteBuffer.allocate(0); + } + List trakBoxes = new ArrayList<>(); List trexBoxes = new ArrayList<>(); @@ -134,6 +143,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull; long videoDurationUs = 0L; for (int i = 0; i < tracks.size(); i++) { Track track = tracks.get(i); + // For a non fragmented MP4 file, avoid writing an empty track. if (!isFragmentedMp4 && track.writtenSamples.isEmpty()) { continue; } @@ -1830,4 +1840,15 @@ import org.checkerframework.checker.nullness.qual.PolyNull; return Util.scaleLargeValue( timestampUs, videoUnitTimebase, C.MICROS_PER_SECOND, RoundingMode.HALF_UP); } + + private static long findMinimumPresentationTimestampUsAcrossTracks(List tracks) { + long minInputPtsUs = Long.MAX_VALUE; + for (int i = 0; i < tracks.size(); i++) { + Track track = tracks.get(i); + if (!track.writtenSamples.isEmpty()) { + minInputPtsUs = min(track.writtenSamples.get(0).presentationTimeUs, minInputPtsUs); + } + } + return minInputPtsUs != Long.MAX_VALUE ? minInputPtsUs : C.TIME_UNSET; + } } 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 50d2de8a40..35f044c339 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java @@ -202,14 +202,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private void createHeader() throws IOException { output.position(0L); output.write(Boxes.ftyp()); - // The minInputPtsUs is actually ignored as there are no pending samples to write. output.write( Boxes.moov( - tracks, - metadataCollector, - /* minInputPtsUs= */ 0L, - /* isFragmentedMp4= */ true, - lastSampleDurationBehavior)); + tracks, metadataCollector, /* isFragmentedMp4= */ true, lastSampleDurationBehavior)); } private boolean shouldFlushPendingSamples( diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Writer.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Writer.java index 9c47284900..2a5639c39d 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Writer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Writer.java @@ -202,7 +202,6 @@ import java.util.concurrent.atomic.AtomicBoolean; Boxes.moov( editableVideoTracks, editableVideoMetadataCollector, - findMinimumPresentationTimestampUsAcrossTracks(editableVideoTracks), /* isFragmentedMp4= */ false, lastSampleDurationBehavior); ByteBuffer edvdBoxHeader = @@ -273,17 +272,6 @@ import java.util.concurrent.atomic.AtomicBoolean; outputFileChannel.truncate(newMoovLocation + moovBytesNeeded); } - private static long findMinimumPresentationTimestampUsAcrossTracks(List tracks) { - long minInputPtsUs = Long.MAX_VALUE; - for (int i = 0; i < tracks.size(); i++) { - Track track = tracks.get(i); - if (!track.writtenSamples.isEmpty()) { - minInputPtsUs = min(track.writtenSamples.get(0).presentationTimeUs, minInputPtsUs); - } - } - return minInputPtsUs; - } - private void writeHeader() throws IOException { outputFileChannel.position(0L); outputFileChannel.write(Boxes.ftyp()); @@ -311,24 +299,9 @@ import java.util.concurrent.atomic.AtomicBoolean; } private ByteBuffer assembleCurrentMoovData() { - // Recalculate the min timestamp every time, in case some new samples have smaller timestamps. - long minInputPtsUs = findMinimumPresentationTimestampUsAcrossTracks(tracks); - ByteBuffer moovHeader; - if (minInputPtsUs != Long.MAX_VALUE) { - moovHeader = - Boxes.moov( - tracks, - metadataCollector, - minInputPtsUs, - /* isFragmentedMp4= */ false, - lastSampleDurationBehavior); - } else { - // Skip moov box, if there are no samples. - moovHeader = ByteBuffer.allocate(0); - } - - return moovHeader; + return Boxes.moov( + tracks, metadataCollector, /* isFragmentedMp4= */ false, lastSampleDurationBehavior); } /**