diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 88dbca7d39..6077cf2604 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -72,6 +72,8 @@ in both `Mp4Muxer.Builder` and `FragmentedMp4Muxer.Builder`. * `Mp4Muxer.addTrack()` and `FragmentedMp4Muxer.addTrack()` now return an `int` track id instead of a `TrackToken`. + * `Mp4Muxer` and `FragmentedMp4Muxer` no longer implement `Muxer` + interface. * IMA extension: * Session: * Fix bug where calling a `Player` method on a `MediaController` connected 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 0943008ca1..4c909deee6 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java @@ -83,7 +83,7 @@ import java.nio.ByteBuffer; * */ @UnstableApi -public final class FragmentedMp4Muxer implements Muxer { +public final class FragmentedMp4Muxer { /** The default fragment duration. */ public static final long DEFAULT_FRAGMENT_DURATION_MS = 2_000; @@ -178,7 +178,14 @@ public final class FragmentedMp4Muxer implements Muxer { trackIdToTrack = new SparseArray<>(); } - @Override + /** + * Adds a track of the given media format. + * + *

All tracks must be added before {@linkplain #writeSampleData writing any samples}. + * + * @param format The {@link Format} of the track. + * @return A track id for this track, which should be passed to {@link #writeSampleData}. + */ public int addTrack(Format format) { Track track = fragmentedMp4Writer.addTrack(/* sortKey= */ 1, format); trackIdToTrack.append(track.id, track); @@ -186,7 +193,7 @@ public final class FragmentedMp4Muxer implements Muxer { } /** - * {@inheritDoc} + * Writes encoded sample data. * *

Samples are written to the disk in batches. If {@link * Builder#setSampleCopyingEnabled(boolean) sample copying} is disabled, the {@code byteBuffer} @@ -202,7 +209,6 @@ public final class FragmentedMp4Muxer implements Muxer { * @param bufferInfo The {@link BufferInfo} related to this sample. * @throws MuxerException If there is any error while writing data to the disk. */ - @Override public void writeSampleData(int trackId, ByteBuffer byteBuffer, BufferInfo bufferInfo) throws MuxerException { try { @@ -218,7 +224,7 @@ public final class FragmentedMp4Muxer implements Muxer { } /** - * {@inheritDoc} + * Adds {@linkplain Metadata.Entry metadata} about the output file. * *

List of supported {@linkplain Metadata.Entry metadata entries}: * @@ -236,13 +242,18 @@ public final class FragmentedMp4Muxer implements Muxer { * IllegalArgumentException} is thrown if the {@linkplain Metadata.Entry metadata} is not * supported. */ - @Override public void addMetadataEntry(Metadata.Entry metadataEntry) { checkArgument(MuxerUtil.isMetadataSupported(metadataEntry), "Unsupported metadata"); metadataCollector.addMetadata(metadataEntry); } - @Override + /** + * Closes the file. + * + *

The muxer cannot be used anymore once this method returns. + * + * @throws MuxerException If the muxer fails to finish writing the output. + */ public void close() throws MuxerException { try { fragmentedMp4Writer.close(); 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 a8474b9dbb..f988c83eb7 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java @@ -109,7 +109,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; * */ @UnstableApi -public final class Mp4Muxer implements Muxer { +public final class Mp4Muxer { /** Parameters for {@link #FILE_FORMAT_MP4_WITH_AUXILIARY_TRACKS_EXTENSION}. */ public static final class Mp4AtFileParameters { /** Provides temporary cache files to be used by the muxer. */ @@ -411,7 +411,7 @@ public final class Mp4Muxer implements Muxer { } /** - * {@inheritDoc} + * Adds a track of the given media format. * *

Tracks can be added at any point before the muxer is closed, even after writing samples to * other tracks. @@ -423,7 +423,6 @@ public final class Mp4Muxer implements Muxer { * #writeSampleData}. * @throws MuxerException If an error occurs while adding track. */ - @Override public int addTrack(Format format) throws MuxerException { return addTrack(/* sortKey= */ 1, format); } @@ -469,7 +468,7 @@ public final class Mp4Muxer implements Muxer { } /** - * {@inheritDoc} + * Writes encoded sample data. * *

When sample batching is {@linkplain Mp4Muxer.Builder#setSampleBatchingEnabled(boolean) * enabled}, provide sample data ({@link ByteBuffer}, {@link BufferInfo}) that won't be modified @@ -486,7 +485,6 @@ public final class Mp4Muxer implements Muxer { * @param bufferInfo The {@link BufferInfo} related to this sample. * @throws MuxerException If an error occurs while writing data to the output file. */ - @Override public void writeSampleData(int trackId, ByteBuffer byteBuffer, BufferInfo bufferInfo) throws MuxerException { Track track = trackIdToTrack.get(trackId); @@ -507,7 +505,7 @@ public final class Mp4Muxer implements Muxer { } /** - * {@inheritDoc} + * Adds {@linkplain Metadata.Entry metadata} about the output file. * *

List of supported {@linkplain Metadata.Entry metadata entries}: * @@ -525,13 +523,18 @@ public final class Mp4Muxer implements Muxer { * IllegalArgumentException} is thrown if the {@linkplain Metadata.Entry metadata} is not * supported. */ - @Override public void addMetadataEntry(Metadata.Entry metadataEntry) { checkArgument(isMetadataSupported(metadataEntry), "Unsupported metadata"); metadataCollector.addMetadata(metadataEntry); } - @Override + /** + * Closes the file. + * + *

The muxer cannot be used anymore once this method returns. + * + * @throws MuxerException If the muxer fails to finish writing the output. + */ public void close() throws MuxerException { @Nullable MuxerException exception = null; try { diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java index 5aa6031f48..5343800929 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java @@ -16,11 +16,15 @@ package androidx.media3.muxer; import static androidx.media3.common.util.Assertions.checkNotNull; -import static androidx.media3.muxer.MuxerTestUtil.feedInputDataToMuxer; +import static androidx.media3.muxer.MuxerTestUtil.MP4_FILE_ASSET_DIRECTORY; import android.content.Context; +import android.media.MediaCodec; +import android.net.Uri; import androidx.annotation.Nullable; +import androidx.media3.common.util.MediaFormatUtil; import androidx.media3.container.Mp4TimestampData; +import androidx.media3.exoplayer.MediaExtractorCompat; import androidx.media3.extractor.mp4.FragmentedMp4Extractor; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpableMp4Box; @@ -31,6 +35,8 @@ import com.google.common.collect.ImmutableList; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.junit.After; import org.junit.Before; @@ -75,7 +81,7 @@ public class FragmentedMp4MuxerEndToEndTest { @Test public void createFragmentedMp4File_fromInputFileSampleData_matchesExpected() throws Exception { - @Nullable Muxer fragmentedMp4Muxer = null; + @Nullable FragmentedMp4Muxer fragmentedMp4Muxer = null; try { fragmentedMp4Muxer = new FragmentedMp4Muxer.Builder(checkNotNull(outputStream)).build(); @@ -102,7 +108,7 @@ public class FragmentedMp4MuxerEndToEndTest { @Test public void createFragmentedMp4File_fromInputFileSampleData_matchesExpectedBoxStructure() throws Exception { - @Nullable Muxer fragmentedMp4Muxer = null; + @Nullable FragmentedMp4Muxer fragmentedMp4Muxer = null; try { fragmentedMp4Muxer = new FragmentedMp4Muxer.Builder(checkNotNull(outputStream)).build(); @@ -125,4 +131,39 @@ public class FragmentedMp4MuxerEndToEndTest { dumpableMp4Box, MuxerTestUtil.getExpectedDumpFilePath(H265_HDR10_MP4 + "_fragmented_box_structure")); } + + private static void feedInputDataToMuxer( + Context context, FragmentedMp4Muxer muxer, String inputFileName) + throws IOException, MuxerException { + MediaExtractorCompat extractor = new MediaExtractorCompat(context); + Uri fileUri = Uri.parse(MP4_FILE_ASSET_DIRECTORY + inputFileName); + extractor.setDataSource(fileUri, /* offset= */ 0); + + List addedTracks = new ArrayList<>(); + for (int i = 0; i < extractor.getTrackCount(); i++) { + int trackId = + muxer.addTrack(MediaFormatUtil.createFormatFromMediaFormat(extractor.getTrackFormat(i))); + addedTracks.add(trackId); + extractor.selectTrack(i); + } + + do { + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + bufferInfo.flags = extractor.getSampleFlags(); + bufferInfo.offset = 0; + bufferInfo.presentationTimeUs = extractor.getSampleTime(); + int sampleSize = (int) extractor.getSampleSize(); + bufferInfo.size = sampleSize; + + ByteBuffer sampleBuffer = ByteBuffer.allocateDirect(sampleSize); + extractor.readSampleData(sampleBuffer, /* offset= */ 0); + + sampleBuffer.rewind(); + + muxer.writeSampleData( + addedTracks.get(extractor.getSampleTrackIndex()), sampleBuffer, bufferInfo); + } while (extractor.advance()); + + extractor.release(); + } } diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndParameterizedTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndParameterizedTest.java index b3a320e05c..51bf44eed5 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndParameterizedTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndParameterizedTest.java @@ -16,11 +16,15 @@ package androidx.media3.muxer; import static androidx.media3.common.util.Assertions.checkNotNull; -import static androidx.media3.muxer.MuxerTestUtil.feedInputDataToMuxer; +import static androidx.media3.muxer.MuxerTestUtil.MP4_FILE_ASSET_DIRECTORY; import android.content.Context; +import android.media.MediaCodec; +import android.net.Uri; import androidx.annotation.Nullable; +import androidx.media3.common.util.MediaFormatUtil; import androidx.media3.container.Mp4TimestampData; +import androidx.media3.exoplayer.MediaExtractorCompat; import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.FakeExtractorOutput; @@ -29,6 +33,9 @@ import androidx.test.core.app.ApplicationProvider; import com.google.common.collect.ImmutableList; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.junit.After; import org.junit.Before; @@ -128,4 +135,38 @@ public class Mp4MuxerEndToEndParameterizedTest { DumpFileAsserts.assertOutput( context, fakeExtractorOutput, MuxerTestUtil.getExpectedDumpFilePath(inputFile)); } + + private static void feedInputDataToMuxer(Context context, Mp4Muxer muxer, String inputFileName) + throws IOException, MuxerException { + MediaExtractorCompat extractor = new MediaExtractorCompat(context); + Uri fileUri = Uri.parse(MP4_FILE_ASSET_DIRECTORY + inputFileName); + extractor.setDataSource(fileUri, /* offset= */ 0); + + List addedTracks = new ArrayList<>(); + for (int i = 0; i < extractor.getTrackCount(); i++) { + int trackId = + muxer.addTrack(MediaFormatUtil.createFormatFromMediaFormat(extractor.getTrackFormat(i))); + addedTracks.add(trackId); + extractor.selectTrack(i); + } + + do { + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + bufferInfo.flags = extractor.getSampleFlags(); + bufferInfo.offset = 0; + bufferInfo.presentationTimeUs = extractor.getSampleTime(); + int sampleSize = (int) extractor.getSampleSize(); + bufferInfo.size = sampleSize; + + ByteBuffer sampleBuffer = ByteBuffer.allocateDirect(sampleSize); + extractor.readSampleData(sampleBuffer, /* offset= */ 0); + + sampleBuffer.rewind(); + + muxer.writeSampleData( + addedTracks.get(extractor.getSampleTrackIndex()), sampleBuffer, bufferInfo); + } while (extractor.advance()); + + extractor.release(); + } } diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java index e33ea9630f..c40869c56c 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java @@ -18,8 +18,8 @@ package androidx.media3.muxer; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.muxer.Mp4Muxer.LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS; import static androidx.media3.muxer.MuxerTestUtil.FAKE_VIDEO_FORMAT; +import static androidx.media3.muxer.MuxerTestUtil.MP4_FILE_ASSET_DIRECTORY; import static androidx.media3.muxer.MuxerTestUtil.XMP_SAMPLE_DATA; -import static androidx.media3.muxer.MuxerTestUtil.feedInputDataToMuxer; import static androidx.media3.muxer.MuxerTestUtil.getFakeSampleAndSampleInfo; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -27,14 +27,17 @@ import static org.junit.Assert.assertThrows; import android.content.Context; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; +import android.net.Uri; import android.util.Pair; import androidx.media3.common.C; +import androidx.media3.common.util.MediaFormatUtil; import androidx.media3.common.util.Util; import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.Mp4LocationData; import androidx.media3.container.Mp4OrientationData; import androidx.media3.container.Mp4TimestampData; import androidx.media3.container.XmpData; +import androidx.media3.exoplayer.MediaExtractorCompat; import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.extractor.text.DefaultSubtitleParserFactory; import androidx.media3.test.utils.DumpFileAsserts; @@ -44,7 +47,10 @@ import androidx.media3.test.utils.TestUtil; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.io.FileOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -905,4 +911,38 @@ public class Mp4MuxerEndToEndTest { muxer.writeSampleData(trackId, sampleAndSampleInfo.first, sampleAndSampleInfo.second); } } + + private static void feedInputDataToMuxer(Context context, Mp4Muxer muxer, String inputFileName) + throws IOException, MuxerException { + MediaExtractorCompat extractor = new MediaExtractorCompat(context); + Uri fileUri = Uri.parse(MP4_FILE_ASSET_DIRECTORY + inputFileName); + extractor.setDataSource(fileUri, /* offset= */ 0); + + List addedTracks = new ArrayList<>(); + for (int i = 0; i < extractor.getTrackCount(); i++) { + int trackId = + muxer.addTrack(MediaFormatUtil.createFormatFromMediaFormat(extractor.getTrackFormat(i))); + addedTracks.add(trackId); + extractor.selectTrack(i); + } + + do { + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + bufferInfo.flags = extractor.getSampleFlags(); + bufferInfo.offset = 0; + bufferInfo.presentationTimeUs = extractor.getSampleTime(); + int sampleSize = (int) extractor.getSampleSize(); + bufferInfo.size = sampleSize; + + ByteBuffer sampleBuffer = ByteBuffer.allocateDirect(sampleSize); + extractor.readSampleData(sampleBuffer, /* offset= */ 0); + + sampleBuffer.rewind(); + + muxer.writeSampleData( + addedTracks.get(extractor.getSampleTrackIndex()), sampleBuffer, bufferInfo); + } while (extractor.advance()); + + extractor.release(); + } } diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/MuxerTestUtil.java b/libraries/muxer/src/test/java/androidx/media3/muxer/MuxerTestUtil.java index ff37e729c4..8a251d71fb 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/MuxerTestUtil.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/MuxerTestUtil.java @@ -18,20 +18,13 @@ package androidx.media3.muxer; import static androidx.media3.common.MimeTypes.AUDIO_AAC; import static androidx.media3.common.MimeTypes.VIDEO_H264; -import android.content.Context; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; -import android.net.Uri; import android.util.Pair; import androidx.media3.common.Format; -import androidx.media3.common.util.MediaFormatUtil; -import androidx.media3.exoplayer.MediaExtractorCompat; import com.google.common.collect.ImmutableList; import com.google.common.io.BaseEncoding; -import java.io.IOException; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; /** Utilities for muxer test cases. */ /* package */ class MuxerTestUtil { @@ -54,13 +47,13 @@ import java.util.List; .build(); public static final String XMP_SAMPLE_DATA = "media/xmp/sample_datetime_xmp.xmp"; + public static final String MP4_FILE_ASSET_DIRECTORY = "asset:///media/mp4/"; private static final byte[] FAKE_H264_SAMPLE = BaseEncoding.base16() .decode( "0000000167F4000A919B2BF3CB3640000003004000000C83C48965800000000168EBE3C448000001658884002BFFFEF5DBF32CAE4A43FF"); - private static final String MP4_FILE_ASSET_DIRECTORY = "asset:///media/mp4/"; private static final String DUMP_FILE_OUTPUT_DIRECTORY = "muxerdumps"; private static final String DUMP_FILE_EXTENSION = "dump"; @@ -81,39 +74,5 @@ import java.util.List; return new Pair<>(sampleDirectBuffer, bufferInfo); } - public static void feedInputDataToMuxer(Context context, Muxer muxer, String inputFileName) - throws IOException, MuxerException { - MediaExtractorCompat extractor = new MediaExtractorCompat(context); - Uri fileUri = Uri.parse(MP4_FILE_ASSET_DIRECTORY + inputFileName); - extractor.setDataSource(fileUri, /* offset= */ 0); - - List addedTracks = new ArrayList<>(); - for (int i = 0; i < extractor.getTrackCount(); i++) { - int trackId = - muxer.addTrack(MediaFormatUtil.createFormatFromMediaFormat(extractor.getTrackFormat(i))); - addedTracks.add(trackId); - extractor.selectTrack(i); - } - - do { - MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); - bufferInfo.flags = extractor.getSampleFlags(); - bufferInfo.offset = 0; - bufferInfo.presentationTimeUs = extractor.getSampleTime(); - int sampleSize = (int) extractor.getSampleSize(); - bufferInfo.size = sampleSize; - - ByteBuffer sampleBuffer = ByteBuffer.allocateDirect(sampleSize); - extractor.readSampleData(sampleBuffer, /* offset= */ 0); - - sampleBuffer.rewind(); - - muxer.writeSampleData( - addedTracks.get(extractor.getSampleTrackIndex()), sampleBuffer, bufferInfo); - } while (extractor.advance()); - - extractor.release(); - } - private MuxerTestUtil() {} }