diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java index fc56869723..b0137b4271 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java @@ -124,13 +124,10 @@ public final class FragmentedMp4Muxer implements Muxer { FileOutputStream fileOutputStream, long fragmentDurationMs, boolean sampleCopyEnabled) { checkNotNull(fileOutputStream); metadataCollector = new MetadataCollector(); - Mp4MoovStructure moovStructure = - new Mp4MoovStructure( - metadataCollector, Mp4Muxer.LAST_FRAME_DURATION_BEHAVIOR_DUPLICATE_PREV_DURATION); fragmentedMp4Writer = new FragmentedMp4Writer( fileOutputStream, - moovStructure, + metadataCollector, AnnexBToAvccConverter.DEFAULT, fragmentDurationMs, sampleCopyEnabled); 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 474133b2f9..2f7f1485e3 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java @@ -64,11 +64,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final FileOutputStream outputStream; private final FileChannel output; - private final Mp4MoovStructure moovGenerator; + private final MetadataCollector metadataCollector; private final AnnexBToAvccConverter annexBToAvccConverter; - private final List tracks; private final long fragmentDurationUs; private final boolean sampleCopyEnabled; + private final @Mp4Muxer.LastFrameDurationBehavior int lastFrameDurationBehavior; + private final List tracks; private @MonotonicNonNull Track videoTrack; private int currentFragmentSequenceNumber; @@ -80,7 +81,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; * Creates an instance. * * @param outputStream The {@link FileOutputStream} to write the data to. - * @param moovGenerator An {@link Mp4MoovStructure} instance to generate the moov box. + * @param metadataCollector A {@link MetadataCollector}. * @param annexBToAvccConverter The {@link AnnexBToAvccConverter} to be used to convert H.264 and * H.265 NAL units from the Annex-B format (using start codes to delineate NAL units) to the * AVCC format (which uses length prefixes). @@ -89,17 +90,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; */ public FragmentedMp4Writer( FileOutputStream outputStream, - Mp4MoovStructure moovGenerator, + MetadataCollector metadataCollector, AnnexBToAvccConverter annexBToAvccConverter, long fragmentDurationMs, boolean sampleCopyEnabled) { this.outputStream = outputStream; - this.output = outputStream.getChannel(); - this.moovGenerator = moovGenerator; + output = outputStream.getChannel(); + this.metadataCollector = metadataCollector; this.annexBToAvccConverter = annexBToAvccConverter; - this.sampleCopyEnabled = sampleCopyEnabled; - tracks = new ArrayList<>(); this.fragmentDurationUs = fragmentDurationMs * 1_000; + this.sampleCopyEnabled = sampleCopyEnabled; + lastFrameDurationBehavior = Mp4Muxer.LAST_FRAME_DURATION_BEHAVIOR_DUPLICATE_PREV_DURATION; + tracks = new ArrayList<>(); minInputPresentationTimeUs = Long.MAX_VALUE; currentFragmentSequenceNumber = 1; } @@ -200,8 +202,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; output.write(Boxes.ftyp()); // The minInputPtsUs is actually ignored as there are no pending samples to write. output.write( - moovGenerator.moovMetadataHeader( - tracks, /* minInputPtsUs= */ 0L, /* isFragmentedMp4= */ true)); + Mp4MoovStructure.moov( + tracks, + metadataCollector, + /* minInputPtsUs= */ 0L, + /* isFragmentedMp4= */ true, + lastFrameDurationBehavior)); } private boolean shouldFlushPendingSamples( 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 0e03392eeb..054b72ce09 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4MoovStructure.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4MoovStructure.java @@ -30,7 +30,7 @@ import java.util.Locale; import org.checkerframework.checker.nullness.qual.PolyNull; /** Builds the moov box structure of an MP4 file. */ -/* package */ class Mp4MoovStructure { +/* package */ final class Mp4MoovStructure { /** Provides track's metadata like media format, written samples. */ public interface TrackMetadataProvider { Format format(); @@ -44,20 +44,16 @@ import org.checkerframework.checker.nullness.qual.PolyNull; ImmutableList writtenChunkSampleCounts(); } - private final MetadataCollector metadataCollector; - private final @Mp4Muxer.LastFrameDurationBehavior int lastFrameDurationBehavior; + private Mp4MoovStructure() {} - public Mp4MoovStructure( - MetadataCollector metadataCollector, - @Mp4Muxer.LastFrameDurationBehavior int lastFrameDurationBehavior) { - this.metadataCollector = metadataCollector; - this.lastFrameDurationBehavior = lastFrameDurationBehavior; - } - - /** Generates a mdat header. */ + /** Returns the moov box. */ @SuppressWarnings("InlinedApi") - public ByteBuffer moovMetadataHeader( - List tracks, long minInputPtsUs, boolean isFragmentedMp4) { + public static ByteBuffer moov( + List tracks, + MetadataCollector metadataCollector, + long minInputPtsUs, + boolean isFragmentedMp4, + @Mp4Muxer.LastFrameDurationBehavior int lastFrameDurationBehavior) { // The timestamp will always fit into a 32-bit integer. This is already validated in the // Mp4Muxer.setTimestampData() API. The value after type casting might be negative, but it is // still valid because it is meant to be read as an unsigned integer. diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java index 2877a9eac7..3ed66cf536 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java @@ -296,13 +296,12 @@ public final class Mp4Muxer implements Muxer { this.outputFileFormat = outputFileFormat; this.cacheFileProvider = cacheFileProvider; metadataCollector = new MetadataCollector(); - Mp4MoovStructure moovStructure = - new Mp4MoovStructure(metadataCollector, lastFrameDurationBehavior); mp4Writer = new Mp4Writer( outputChannel, - moovStructure, + metadataCollector, annexBToAvccConverter, + lastFrameDurationBehavior, sampleCopyEnabled, attemptStreamableOutputEnabled); editableVideoTracks = new ArrayList<>(); @@ -465,13 +464,12 @@ public final class Mp4Muxer implements Muxer { cacheFilePath = checkNotNull(cacheFileProvider).getCacheFilePath(); cacheFileOutputStream = new FileOutputStream(cacheFilePath); editableVideoMetadataCollector = new MetadataCollector(); - Mp4MoovStructure mp4MoovStructure = - new Mp4MoovStructure(editableVideoMetadataCollector, lastFrameDurationBehavior); editableVideoMp4Writer = new Mp4Writer( cacheFileOutputStream.getChannel(), - mp4MoovStructure, + checkNotNull(editableVideoMetadataCollector), annexBToAvccConverter, + lastFrameDurationBehavior, sampleCopyEnabled, attemptStreamableOutputEnabled); } 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 03f7f57232..949ba92ffd 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Writer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Writer.java @@ -43,11 +43,12 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final String FREE_BOX_TYPE = "free"; private final FileChannel outputFileChannel; - private final Mp4MoovStructure moovGenerator; + private final MetadataCollector metadataCollector; private final AnnexBToAvccConverter annexBToAvccConverter; + private final @Mp4Muxer.LastFrameDurationBehavior int lastFrameDurationBehavior; + private final boolean sampleCopyEnabled; private final List tracks; private final AtomicBoolean hasWrittenSamples; - private final boolean sampleCopyEnabled; // Stores location of the space reserved for the moov box at the beginning of the file (after ftyp // box) @@ -67,26 +68,30 @@ import java.util.concurrent.atomic.AtomicBoolean; * @param fileChannel The {@link FileChannel} to write the data to. The {@link FileChannel} can be * closed after {@linkplain #finishWritingSamplesAndFinalizeMoovBox() finishing writing * samples}. - * @param moovGenerator An {@link Mp4MoovStructure} instance to generate the moov box. + * @param metadataCollector A {@link MetadataCollector}. * @param annexBToAvccConverter The {@link AnnexBToAvccConverter} to be used to convert H.264 and * H.265 NAL units from the Annex-B format (using start codes to delineate NAL units) to the * AVCC format (which uses length prefixes). + * @param lastFrameDurationBehavior The {@link Mp4Muxer.LastFrameDurationBehavior} for the video + * track. * @param sampleCopyEnabled Whether sample copying is enabled. * @param attemptStreamableOutputEnabled Whether to attempt to write a streamable output. */ public Mp4Writer( FileChannel fileChannel, - Mp4MoovStructure moovGenerator, + MetadataCollector metadataCollector, AnnexBToAvccConverter annexBToAvccConverter, + @Mp4Muxer.LastFrameDurationBehavior int lastFrameDurationBehavior, boolean sampleCopyEnabled, boolean attemptStreamableOutputEnabled) { this.outputFileChannel = fileChannel; - this.moovGenerator = moovGenerator; + this.metadataCollector = metadataCollector; this.annexBToAvccConverter = annexBToAvccConverter; + this.lastFrameDurationBehavior = lastFrameDurationBehavior; this.sampleCopyEnabled = sampleCopyEnabled; - canWriteMoovAtStart = attemptStreamableOutputEnabled; tracks = new ArrayList<>(); hasWrittenSamples = new AtomicBoolean(false); + canWriteMoovAtStart = attemptStreamableOutputEnabled; lastMoovWritten = Range.closed(0L, 0L); } @@ -239,7 +244,12 @@ import java.util.concurrent.atomic.AtomicBoolean; ByteBuffer moovHeader; if (minInputPtsUs != Long.MAX_VALUE) { moovHeader = - moovGenerator.moovMetadataHeader(tracks, minInputPtsUs, /* isFragmentedMp4= */ false); + Mp4MoovStructure.moov( + tracks, + metadataCollector, + minInputPtsUs, + /* isFragmentedMp4= */ false, + lastFrameDurationBehavior); } else { // Skip moov box, if there are no samples. moovHeader = ByteBuffer.allocate(0);