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 4d7a654f83..a3fe966ce7 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java @@ -24,6 +24,7 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.media3.common.Format; import androidx.media3.common.Metadata; +import androidx.media3.common.util.Log; import androidx.media3.common.util.UnstableApi; import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.Mp4LocationData; @@ -38,6 +39,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; /** * A muxer for creating an MP4 container file. @@ -176,6 +178,10 @@ public final class Mp4Muxer implements Muxer { } } + private static final String TAG = "Mp4Muxer"; + + private final FileOutputStream fileOutputStream; + private final FileChannel fileChannel; private final MetadataCollector metadataCollector; private final Mp4Writer mp4Writer; @@ -185,12 +191,14 @@ public final class Mp4Muxer implements Muxer { AnnexBToAvccConverter annexBToAvccConverter, boolean sampleCopyEnabled, boolean attemptStreamableOutputEnabled) { + this.fileOutputStream = fileOutputStream; + this.fileChannel = fileOutputStream.getChannel(); metadataCollector = new MetadataCollector(); Mp4MoovStructure moovStructure = new Mp4MoovStructure(metadataCollector, lastFrameDurationBehavior); mp4Writer = new Mp4Writer( - fileOutputStream, + fileChannel, moovStructure, annexBToAvccConverter, sampleCopyEnabled, @@ -288,10 +296,32 @@ public final class Mp4Muxer implements Muxer { @Override public void close() throws MuxerException { + @Nullable MuxerException exception = null; try { - mp4Writer.close(); + mp4Writer.finishWritingSamples(); } catch (IOException e) { - throw new MuxerException("Failed to close the muxer", e); + exception = new MuxerException("Failed to finish writing samples", e); + } + try { + fileChannel.close(); + } catch (IOException e) { + if (exception == null) { + exception = new MuxerException("Failed to close output channel", e); + } else { + Log.e(TAG, "Failed to close output channel", e); + } + } + try { + fileOutputStream.close(); + } catch (IOException e) { + if (exception == null) { + exception = new MuxerException("Failed to close output stream", e); + } else { + Log.e(TAG, "Failed to close output stream", e); + } + } + if (exception != null) { + throw exception; } } } 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 15b20b6376..4b4b94f0f8 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Writer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Writer.java @@ -28,7 +28,6 @@ import androidx.media3.common.Format; import androidx.media3.common.util.Util; import androidx.media3.muxer.Muxer.TrackToken; import com.google.common.collect.Range; -import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; @@ -43,7 +42,6 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int DEFAULT_MOOV_BOX_SIZE_BYTES = 400_000; private static final String FREE_BOX_TYPE = "free"; - private final FileOutputStream outputStream; private final FileChannel output; private final Mp4MoovStructure moovGenerator; private final AnnexBToAvccConverter annexBToAvccConverter; @@ -66,7 +64,8 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Creates an instance. * - * @param outputStream The {@link FileOutputStream} to write the data to. + * @param fileChannel The {@link FileChannel} to write the data to. The {@link FileChannel} can be + * closed after {@link #finishWritingSamples() finishing writing samples}. * @param moovGenerator An {@link Mp4MoovStructure} instance to generate the moov box. * @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 @@ -75,13 +74,12 @@ import java.util.concurrent.atomic.AtomicBoolean; * @param attemptStreamableOutputEnabled Whether to attempt to write a streamable output. */ public Mp4Writer( - FileOutputStream outputStream, + FileChannel fileChannel, Mp4MoovStructure moovGenerator, AnnexBToAvccConverter annexBToAvccConverter, boolean sampleCopyEnabled, boolean attemptStreamableOutputEnabled) { - this.outputStream = outputStream; - this.output = outputStream.getChannel(); + this.output = fileChannel; this.moovGenerator = moovGenerator; this.annexBToAvccConverter = annexBToAvccConverter; this.sampleCopyEnabled = sampleCopyEnabled; @@ -105,19 +103,19 @@ import java.util.concurrent.atomic.AtomicBoolean; doInterleave(); } - public void close() throws IOException { - try { - for (int i = 0; i < tracks.size(); i++) { - flushPending(tracks.get(i)); - } + /** + * Writes all the pending samples and the final moov box to the disk. + * + *
The output {@link FileChannel} can be closed after calling this method. + */ + public void finishWritingSamples() throws IOException { + for (int i = 0; i < tracks.size(); i++) { + flushPending(tracks.get(i)); + } - // Leave the file empty if no samples are written. - if (hasWrittenSamples.get()) { - writeMoovAndTrim(); - } - } finally { - output.close(); - outputStream.close(); + // Leave the file empty if no samples are written. + if (hasWrittenSamples.get()) { + writeMoovAndTrim(); } }