Replace Transformer.Muxer interface with Muxer.Muxer

PiperOrigin-RevId: 633193701
This commit is contained in:
sheenachhabra 2024-05-13 06:51:54 -07:00 committed by Copybara-Service
parent 5950e884f6
commit 3a3145521b
23 changed files with 156 additions and 185 deletions

View File

@ -32,6 +32,9 @@
at stereo when handling PCM input. at stereo when handling PCM input.
* When selecting tracks in `ExoPlayerAssetLoader`, ignore audio channel * When selecting tracks in `ExoPlayerAssetLoader`, ignore audio channel
count constraints as they only apply for playback. count constraints as they only apply for playback.
* Replace `androidx.media3.transformer.Muxer` interface with
`androidx.media3.muxer.Muxer` and remove
`androidx.media3.transformer.Muxer`.
* Track Selection: * Track Selection:
* Extractors: * Extractors:
* MPEG-TS: Roll forward the change ensuring the last frame is rendered by * MPEG-TS: Roll forward the change ensuring the last frame is rendered by

View File

@ -83,6 +83,7 @@ dependencies {
implementation project(modulePrefix + 'lib-exoplayer') implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash') implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'lib-transformer') implementation project(modulePrefix + 'lib-transformer')
implementation project(modulePrefix + 'lib-muxer')
implementation project(modulePrefix + 'lib-ui') implementation project(modulePrefix + 'lib-ui')
// For MediaPipe and its dependencies: // For MediaPipe and its dependencies:

View File

@ -80,6 +80,7 @@ import androidx.media3.effect.TextureOverlay;
import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor; import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor;
import androidx.media3.exoplayer.util.DebugTextViewHelper; import androidx.media3.exoplayer.util.DebugTextViewHelper;
import androidx.media3.muxer.Muxer;
import androidx.media3.transformer.Composition; import androidx.media3.transformer.Composition;
import androidx.media3.transformer.DefaultEncoderFactory; import androidx.media3.transformer.DefaultEncoderFactory;
import androidx.media3.transformer.DefaultMuxer; import androidx.media3.transformer.DefaultMuxer;
@ -90,7 +91,6 @@ import androidx.media3.transformer.ExportException;
import androidx.media3.transformer.ExportResult; import androidx.media3.transformer.ExportResult;
import androidx.media3.transformer.InAppMuxer; import androidx.media3.transformer.InAppMuxer;
import androidx.media3.transformer.JsonUtil; import androidx.media3.transformer.JsonUtil;
import androidx.media3.transformer.Muxer;
import androidx.media3.transformer.ProgressHolder; import androidx.media3.transformer.ProgressHolder;
import androidx.media3.transformer.Transformer; import androidx.media3.transformer.Transformer;
import androidx.media3.ui.AspectRatioFrameLayout; import androidx.media3.ui.AspectRatioFrameLayout;

View File

@ -19,6 +19,7 @@ import android.content.Context;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaExtractor; import android.media.MediaExtractor;
import androidx.media3.common.util.MediaFormatUtil; import androidx.media3.common.util.MediaFormatUtil;
import androidx.media3.muxer.Muxer.MuxerException;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
@ -37,7 +38,7 @@ import java.util.List;
} }
public static void feedInputDataToMuxer(Context context, Muxer muxer, String inputFileName) public static void feedInputDataToMuxer(Context context, Muxer muxer, String inputFileName)
throws IOException { throws IOException, MuxerException {
MediaExtractor extractor = new MediaExtractor(); MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource( extractor.setDataSource(
context.getResources().getAssets().openFd(MP4_FILE_ASSET_DIRECTORY + inputFileName)); context.getResources().getAssets().openFd(MP4_FILE_ASSET_DIRECTORY + inputFileName));

View File

@ -62,7 +62,7 @@ public class FragmentedMp4MuxerEndToEndAndroidTest {
} }
@Test @Test
public void createFragmentedMp4File_fromInputFileSampleData_matchesExpected() throws IOException { public void createFragmentedMp4File_fromInputFileSampleData_matchesExpected() throws Exception {
@Nullable Muxer fragmentedMp4Muxer = null; @Nullable Muxer fragmentedMp4Muxer = null;
try { try {
@ -89,7 +89,7 @@ public class FragmentedMp4MuxerEndToEndAndroidTest {
@Test @Test
public void createFragmentedMp4File_fromInputFileSampleData_matchesExpectedBoxStructure() public void createFragmentedMp4File_fromInputFileSampleData_matchesExpectedBoxStructure()
throws IOException { throws Exception {
@Nullable Muxer fragmentedMp4Muxer = null; @Nullable Muxer fragmentedMp4Muxer = null;
try { try {

View File

@ -73,7 +73,7 @@ public class Mp4MuxerEndToEndAndroidTest {
} }
@Test @Test
public void createMp4File_fromInputFileSampleData_matchesExpected() throws IOException { public void createMp4File_fromInputFileSampleData_matchesExpected() throws Exception {
@Nullable Mp4Muxer mp4Muxer = null; @Nullable Mp4Muxer mp4Muxer = null;
try { try {
@ -96,7 +96,7 @@ public class Mp4MuxerEndToEndAndroidTest {
} }
@Test @Test
public void createMp4File_muxerNotClosed_createsPartiallyWrittenValidFile() throws IOException { public void createMp4File_muxerNotClosed_createsPartiallyWrittenValidFile() throws Exception {
// Skip for all parameter values except when the input is a large file. The muxer writes samples // Skip for all parameter values except when the input is a large file. The muxer writes samples
// in batches (and flushes data only when it's closed), so a large input file is needed to // in batches (and flushes data only when it's closed), so a large input file is needed to
// ensure some data has been written after taking all the inputs but before closing the muxer. // ensure some data has been written after taking all the inputs but before closing the muxer.

View File

@ -156,12 +156,21 @@ public final class FragmentedMp4Muxer implements Muxer {
* Builder#setSampleCopyEnabled(boolean) sample copying} is disabled. Otherwise, the position * Builder#setSampleCopyEnabled(boolean) sample copying} is disabled. Otherwise, the position
* of the buffer is updated but the caller retains ownership. * of the buffer is updated but the caller retains ownership.
* @param bufferInfo The {@link BufferInfo} related to this sample. * @param bufferInfo The {@link BufferInfo} related to this sample.
* @throws IOException If there is any error while writing data to the disk. * @throws MuxerException If there is any error while writing data to the disk.
*/ */
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo) public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws IOException { throws MuxerException {
fragmentedMp4Writer.writeSampleData(trackToken, byteBuffer, bufferInfo); try {
fragmentedMp4Writer.writeSampleData(trackToken, byteBuffer, bufferInfo);
} catch (IOException e) {
throw new MuxerException(
"Failed to write sample for presentationTimeUs="
+ bufferInfo.presentationTimeUs
+ ", size="
+ bufferInfo.size,
e);
}
} }
/** /**
@ -190,7 +199,11 @@ public final class FragmentedMp4Muxer implements Muxer {
} }
@Override @Override
public void close() throws IOException { public void close() throws MuxerException {
fragmentedMp4Writer.close(); try {
fragmentedMp4Writer.close();
} catch (IOException e) {
throw new MuxerException("Failed to close the muxer", e);
}
} }
} }

View File

@ -221,12 +221,21 @@ public final class Mp4Muxer implements Muxer {
* Builder#setSampleCopyEnabled(boolean) sample copying} is disabled. Otherwise, the position * Builder#setSampleCopyEnabled(boolean) sample copying} is disabled. Otherwise, the position
* of the buffer is updated but the caller retains ownership. * of the buffer is updated but the caller retains ownership.
* @param bufferInfo The {@link BufferInfo} related to this sample. * @param bufferInfo The {@link BufferInfo} related to this sample.
* @throws IOException If there is any error while writing data to the disk. * @throws MuxerException If there is any error while writing data to the disk.
*/ */
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo) public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws IOException { throws MuxerException {
mp4Writer.writeSampleData(trackToken, byteBuffer, bufferInfo); try {
mp4Writer.writeSampleData(trackToken, byteBuffer, bufferInfo);
} catch (IOException e) {
throw new MuxerException(
"Failed to write sample for presentationTimeUs="
+ bufferInfo.presentationTimeUs
+ ", size="
+ bufferInfo.size,
e);
}
} }
/** /**
@ -255,7 +264,11 @@ public final class Mp4Muxer implements Muxer {
} }
@Override @Override
public void close() throws IOException { public void close() throws MuxerException {
mp4Writer.close(); try {
mp4Writer.close();
} catch (IOException e) {
throw new MuxerException("Failed to close the muxer", e);
}
} }
} }

View File

@ -16,28 +16,81 @@
package androidx.media3.muxer; package androidx.media3.muxer;
import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.BufferInfo;
import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.io.IOException; import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** The muxer for producing media container files. */ /** The muxer for producing media container files. */
@UnstableApi @UnstableApi
public interface Muxer { public interface Muxer {
/** Thrown when a muxer failure occurs. */
final class MuxerException extends Exception {
/**
* Creates an instance.
*
* @param message See {@link #getMessage()}.
* @param cause See {@link #getCause()}.
*/
public MuxerException(String message, Throwable cause) {
super(message, cause);
}
}
/** Factory for muxers. */
interface Factory {
/**
* Returns a new {@link Muxer}.
*
* @param path The path to the output file.
* @throws MuxerException If an error occurs opening the output file for writing.
*/
Muxer create(String path) throws MuxerException;
/**
* Returns the supported sample {@linkplain MimeTypes MIME types} for the given {@link
* C.TrackType}.
*/
ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType);
}
/** A token representing an added track. */ /** A token representing an added track. */
interface TrackToken {} interface TrackToken {}
/** Adds a track of the given media format. */ /**
TrackToken addTrack(Format format); * Adds a track of the given media format.
*
* @param format The {@link Format} of the track.
* @return The {@link TrackToken} for this track, which should be passed to {@link
* #writeSampleData}.
* @throws MuxerException If the muxer encounters a problem while adding the track.
*/
TrackToken addTrack(Format format) throws MuxerException;
/** Writes encoded sample data. */ /**
* Writes encoded sample data.
*
* @param trackToken The {@link TrackToken} of the track, previously returned by {@link
* #addTrack(Format)}.
* @param byteBuffer A buffer containing the sample data to write to the container.
* @param bufferInfo The {@link BufferInfo} of the sample.
* @throws MuxerException If the muxer fails to write the sample.
*/
void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo) void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws IOException; throws MuxerException;
/** Adds {@linkplain Metadata.Entry metadata} about the output file. */ /** Adds {@linkplain Metadata.Entry metadata} about the output file. */
void addMetadataEntry(Metadata.Entry metadataEntry); void addMetadataEntry(Metadata.Entry metadataEntry);
/** Closes the file. */ /**
void close() throws IOException; * Closes the file.
*
* <p>The muxer cannot be used anymore once this method returns.
*
* @throws MuxerException If the muxer fails to finish writing the output.
*/
void close() throws MuxerException;
} }

View File

@ -39,7 +39,6 @@ import androidx.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -54,7 +53,7 @@ public class Mp4MuxerEndToEndTest {
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@Test @Test
public void createMp4File_addTrackAndMetadataButNoSamples_createsEmptyFile() throws IOException { public void createMp4File_addTrackAndMetadataButNoSamples_createsEmptyFile() throws Exception {
String outputFilePath = temporaryFolder.newFile().getPath(); String outputFilePath = temporaryFolder.newFile().getPath();
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build(); Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
@ -75,7 +74,7 @@ public class Mp4MuxerEndToEndTest {
} }
@Test @Test
public void createMp4File_withSameTracksOffset_matchesExpected() throws IOException { public void createMp4File_withSameTracksOffset_matchesExpected() throws Exception {
String outputFilePath = temporaryFolder.newFile().getPath(); String outputFilePath = temporaryFolder.newFile().getPath();
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build(); Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
mp4Muxer.addMetadataEntry( mp4Muxer.addMetadataEntry(
@ -118,7 +117,7 @@ public class Mp4MuxerEndToEndTest {
} }
@Test @Test
public void createMp4File_withDifferentTracksOffset_matchesExpected() throws IOException { public void createMp4File_withDifferentTracksOffset_matchesExpected() throws Exception {
String outputFilePath = temporaryFolder.newFile().getPath(); String outputFilePath = temporaryFolder.newFile().getPath();
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build(); Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
mp4Muxer.addMetadataEntry( mp4Muxer.addMetadataEntry(
@ -157,7 +156,7 @@ public class Mp4MuxerEndToEndTest {
} }
@Test @Test
public void writeSampleData_withOutOfOrderSampleTimestamps_throws() throws IOException { public void writeSampleData_withOutOfOrderSampleTimestamps_throws() throws Exception {
String outputFilePath = temporaryFolder.newFile().getPath(); String outputFilePath = temporaryFolder.newFile().getPath();
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build(); Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
Pair<ByteBuffer, BufferInfo> track1Sample1 = Pair<ByteBuffer, BufferInfo> track1Sample1 =

View File

@ -36,7 +36,8 @@ import androidx.media3.common.audio.AudioProcessor;
import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.effect.RgbFilter; import androidx.media3.effect.RgbFilter;
import androidx.media3.muxer.Muxer.TrackToken; import androidx.media3.muxer.Muxer;
import androidx.media3.muxer.Muxer.MuxerException;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.platform.app.InstrumentationRegistry;
import com.google.common.base.Ascii; import com.google.common.base.Ascii;
@ -418,7 +419,7 @@ public class TransformerPauseResumeTest {
} }
@Override @Override
public Muxer create(String path) throws Muxer.MuxerException { public Muxer create(String path) throws MuxerException {
return new FrameBlockingMuxer(wrappedMuxerFactory.create(path), listener); return new FrameBlockingMuxer(wrappedMuxerFactory.create(path), listener);
} }
@ -473,8 +474,8 @@ public class TransformerPauseResumeTest {
} }
@Override @Override
public void release() throws MuxerException { public void close() throws MuxerException {
wrappedMuxer.release(); wrappedMuxer.close();
} }
} }
} }

View File

@ -20,6 +20,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.muxer.Muxer;
import androidx.media3.muxer.Muxer.TrackToken; import androidx.media3.muxer.Muxer.TrackToken;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -71,9 +72,9 @@ public final class DefaultMuxer implements Muxer {
} }
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer data, BufferInfo bufferInfo) public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws MuxerException { throws MuxerException {
muxer.writeSampleData(trackToken, data, bufferInfo); muxer.writeSampleData(trackToken, byteBuffer, bufferInfo);
} }
@Override @Override
@ -82,7 +83,7 @@ public final class DefaultMuxer implements Muxer {
} }
@Override @Override
public void release() throws MuxerException { public void close() throws MuxerException {
muxer.release(); muxer.close();
} }
} }

View File

@ -32,7 +32,7 @@ import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.MediaFormatUtil; import androidx.media3.common.util.MediaFormatUtil;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.container.Mp4LocationData; import androidx.media3.container.Mp4LocationData;
import androidx.media3.muxer.Muxer.TrackToken; import androidx.media3.muxer.Muxer;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -199,7 +199,7 @@ import java.util.Map;
} }
@Override @Override
public void release() throws MuxerException { public void close() throws MuxerException {
if (isReleased) { if (isReleased) {
return; return;
} }

View File

@ -25,12 +25,12 @@ import androidx.media3.container.Mp4OrientationData;
import androidx.media3.muxer.FragmentedMp4Muxer; import androidx.media3.muxer.FragmentedMp4Muxer;
import androidx.media3.muxer.Mp4Muxer; import androidx.media3.muxer.Mp4Muxer;
import androidx.media3.muxer.Mp4Utils; import androidx.media3.muxer.Mp4Utils;
import androidx.media3.muxer.Muxer;
import androidx.media3.muxer.Muxer.TrackToken; import androidx.media3.muxer.Muxer.TrackToken;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
@ -170,7 +170,7 @@ public final class InAppMuxer implements Muxer {
} }
@Override @Override
public TrackToken addTrack(Format format) { public TrackToken addTrack(Format format) throws MuxerException {
TrackToken trackToken = muxer.addTrack(format); TrackToken trackToken = muxer.addTrack(format);
if (MimeTypes.isVideo(format.sampleMimeType)) { if (MimeTypes.isVideo(format.sampleMimeType)) {
muxer.addMetadataEntry(new Mp4OrientationData(format.rotationDegrees)); muxer.addMetadataEntry(new Mp4OrientationData(format.rotationDegrees));
@ -179,19 +179,9 @@ public final class InAppMuxer implements Muxer {
} }
@Override @Override
public void writeSampleData(TrackToken trackToken, ByteBuffer data, BufferInfo bufferInfo) public void writeSampleData(TrackToken trackToken, ByteBuffer byteBuffer, BufferInfo bufferInfo)
throws MuxerException { throws MuxerException {
muxer.writeSampleData(trackToken, byteBuffer, bufferInfo);
try {
muxer.writeSampleData(trackToken, data, bufferInfo);
} catch (IOException e) {
throw new MuxerException(
"Failed to write sample for presentationTimeUs="
+ bufferInfo.presentationTimeUs
+ ", size="
+ bufferInfo.size,
e);
}
} }
@Override @Override
@ -202,14 +192,9 @@ public final class InAppMuxer implements Muxer {
} }
@Override @Override
public void release() throws MuxerException { public void close() throws MuxerException {
writeMetadata(); writeMetadata();
muxer.close();
try {
muxer.close();
} catch (IOException e) {
throw new MuxerException("Error closing muxer", e);
}
} }
private void writeMetadata() { private void writeMetadata() {

View File

@ -1,106 +0,0 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.transformer;
import android.media.MediaCodec.BufferInfo;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.muxer.Muxer.TrackToken;
import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer;
/**
* Abstracts media muxing operations.
*
* <p>Query whether {@linkplain Factory#getSupportedSampleMimeTypes(int) sample MIME types} are
* supported and {@linkplain #addTrack(Format) add all tracks}, then {@linkplain #writeSampleData
* write sample data} to mux samples. Once any sample data has been written, it is not possible to
* add tracks. After writing all sample data, {@linkplain #release() release} the instance to finish
* writing to the output and return any resources to the system.
*/
@UnstableApi
// TODO: b/330695864 - Replace with the Muxer interface from the Muxer module.
public interface Muxer {
/** Thrown when a muxing failure occurs. */
final class MuxerException extends Exception {
/**
* Creates an instance.
*
* @param message See {@link #getMessage()}.
* @param cause See {@link #getCause()}.
*/
public MuxerException(String message, Throwable cause) {
super(message, cause);
}
}
/** Factory for muxers. */
interface Factory {
/**
* Returns a new muxer writing to a file.
*
* @param path The path to the output file.
* @throws IllegalArgumentException If the path is invalid.
* @throws MuxerException If an error occurs opening the output file for writing.
*/
Muxer create(String path) throws MuxerException;
/**
* Returns the supported sample {@linkplain MimeTypes MIME types} for the given {@link
* C.TrackType}.
*/
ImmutableList<String> getSupportedSampleMimeTypes(@C.TrackType int trackType);
}
/**
* Adds a track with the specified format.
*
* @param format The {@link Format} of the track.
* @return The {@link TrackToken} for this track, which should be passed to {@link
* #writeSampleData}.
* @throws MuxerException If the muxer encounters a problem while adding the track.
*/
TrackToken addTrack(Format format) throws MuxerException;
/**
* Writes the specified sample.
*
* @param trackToken The {@link TrackToken} of the track, previously returned by {@link
* #addTrack(Format)}.
* @param data A buffer containing the sample data to write to the container.
* @param bufferInfo The {@link BufferInfo} of the sample.
* @throws MuxerException If the muxer fails to write the sample.
*/
void writeSampleData(TrackToken trackToken, ByteBuffer data, BufferInfo bufferInfo)
throws MuxerException;
/** Adds {@linkplain Metadata.Entry metadata} about the output file. */
void addMetadataEntry(Metadata.Entry metadataEntry);
/**
* Finishes writing the output and releases any resources associated with muxing.
*
* <p>The muxer cannot be used anymore once this method has been called.
*
* @throws MuxerException If the muxer fails to finish writing the output and {@code
* forCancellation} is false.
*/
void release() throws MuxerException;
}

View File

@ -48,6 +48,8 @@ import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.container.NalUnitUtil; import androidx.media3.container.NalUnitUtil;
import androidx.media3.effect.DebugTraceUtil; import androidx.media3.effect.DebugTraceUtil;
import androidx.media3.muxer.Muxer;
import androidx.media3.muxer.Muxer.MuxerException;
import androidx.media3.muxer.Muxer.TrackToken; import androidx.media3.muxer.Muxer.TrackToken;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.File; import java.io.File;
@ -374,11 +376,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @throws IllegalStateException If the number of formats added exceeds the {@linkplain * @throws IllegalStateException If the number of formats added exceeds the {@linkplain
* #setTrackCount track count}, if {@link #setTrackCount(int)} has not been called or if there * #setTrackCount track count}, if {@link #setTrackCount(int)} has not been called or if there
* is already a track of that {@link C.TrackType}. * is already a track of that {@link C.TrackType}.
* @throws Muxer.MuxerException If the underlying {@link Muxer} encounters a problem while adding * @throws MuxerException If the underlying {@link Muxer} encounters a problem while adding the
* the track. * track.
*/ */
public void addTrackFormat(Format format) public void addTrackFormat(Format format) throws AppendTrackFormatException, MuxerException {
throws AppendTrackFormatException, Muxer.MuxerException {
@Nullable String sampleMimeType = format.sampleMimeType; @Nullable String sampleMimeType = format.sampleMimeType;
@C.TrackType int trackType = MimeTypes.getTrackType(sampleMimeType); @C.TrackType int trackType = MimeTypes.getTrackType(sampleMimeType);
checkArgument( checkArgument(
@ -521,11 +522,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* received a format} for every {@linkplain #setTrackCount(int) track}. * received a format} for every {@linkplain #setTrackCount(int) track}.
* @throws IllegalArgumentException If the muxer doesn't have a {@linkplain #endTrack(int) * @throws IllegalArgumentException If the muxer doesn't have a {@linkplain #endTrack(int)
* non-ended} track of the given {@link C.TrackType}. * non-ended} track of the given {@link C.TrackType}.
* @throws Muxer.MuxerException If the underlying {@link Muxer} fails to write the sample. * @throws MuxerException If the underlying {@link Muxer} fails to write the sample.
*/ */
public boolean writeSample( public boolean writeSample(
@C.TrackType int trackType, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs) @C.TrackType int trackType, ByteBuffer data, boolean isKeyFrame, long presentationTimeUs)
throws Muxer.MuxerException { throws MuxerException {
checkArgument(contains(trackTypeToInfo, trackType)); checkArgument(contains(trackTypeToInfo, trackType));
TrackInfo trackInfo = trackTypeToInfo.get(trackType); TrackInfo trackInfo = trackTypeToInfo.get(trackType);
boolean canWriteSample = canWriteSample(trackType, presentationTimeUs); boolean canWriteSample = canWriteSample(trackType, presentationTimeUs);
@ -653,11 +654,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* #MUXER_RELEASE_REASON_CANCELLED} or {@link #MUXER_RELEASE_REASON_ERROR}. * #MUXER_RELEASE_REASON_CANCELLED} or {@link #MUXER_RELEASE_REASON_ERROR}.
* *
* @param releaseReason The reason to release the muxer. * @param releaseReason The reason to release the muxer.
* @throws Muxer.MuxerException If the underlying {@link Muxer} fails to finish writing the output * @throws MuxerException If the underlying {@link Muxer} fails to finish writing the output and
* and the {@code releaseReason} is not {@link #MUXER_RELEASE_REASON_CANCELLED}. * the {@code releaseReason} is not {@link #MUXER_RELEASE_REASON_CANCELLED}.
*/ */
public void finishWritingAndMaybeRelease(@MuxerReleaseReason int releaseReason) public void finishWritingAndMaybeRelease(@MuxerReleaseReason int releaseReason)
throws Muxer.MuxerException { throws MuxerException {
if (releaseReason == MUXER_RELEASE_REASON_COMPLETED && muxerMode == MUXER_MODE_MUX_PARTIAL) { if (releaseReason == MUXER_RELEASE_REASON_COMPLETED && muxerMode == MUXER_MODE_MUX_PARTIAL) {
return; return;
} }
@ -665,8 +666,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
abortScheduledExecutorService.shutdownNow(); abortScheduledExecutorService.shutdownNow();
if (muxer != null) { if (muxer != null) {
try { try {
muxer.release(); muxer.close();
} catch (Muxer.MuxerException e) { } catch (MuxerException e) {
if (releaseReason == MUXER_RELEASE_REASON_CANCELLED if (releaseReason == MUXER_RELEASE_REASON_CANCELLED
&& checkNotNull(e.getMessage()) && checkNotNull(e.getMessage())
.equals(FrameworkMuxer.MUXER_STOPPING_FAILED_ERROR_MESSAGE)) { .equals(FrameworkMuxer.MUXER_STOPPING_FAILED_ERROR_MESSAGE)) {
@ -736,7 +737,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@EnsuresNonNull("muxer") @EnsuresNonNull("muxer")
private void ensureMuxerInitialized() throws Muxer.MuxerException { private void ensureMuxerInitialized() throws MuxerException {
if (muxer == null) { if (muxer == null) {
muxer = muxerFactory.create(outputPath); muxer = muxerFactory.create(outputPath);
} }

View File

@ -30,6 +30,7 @@ import androidx.media3.common.Format;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.decoder.DecoderInputBuffer; import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.muxer.Muxer.MuxerException;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import java.util.List; import java.util.List;
@ -110,7 +111,7 @@ import java.util.List;
} }
try { try {
muxerWrapper.addTrackFormat(inputFormat); muxerWrapper.addTrackFormat(inputFormat);
} catch (Muxer.MuxerException e) { } catch (MuxerException e) {
throw ExportException.createForMuxer(e, ExportException.ERROR_CODE_MUXING_FAILED); throw ExportException.createForMuxer(e, ExportException.ERROR_CODE_MUXING_FAILED);
} catch (MuxerWrapper.AppendTrackFormatException e) { } catch (MuxerWrapper.AppendTrackFormatException e) {
throw ExportException.createForMuxer(e, ExportException.ERROR_CODE_MUXING_APPEND); throw ExportException.createForMuxer(e, ExportException.ERROR_CODE_MUXING_APPEND);
@ -136,7 +137,7 @@ import java.util.List;
muxerInputBuffer.timeUs)) { muxerInputBuffer.timeUs)) {
return false; return false;
} }
} catch (Muxer.MuxerException e) { } catch (MuxerException e) {
throw ExportException.createForMuxer(e, ExportException.ERROR_CODE_MUXING_FAILED); throw ExportException.createForMuxer(e, ExportException.ERROR_CODE_MUXING_FAILED);
} }

View File

@ -59,6 +59,7 @@ import androidx.media3.effect.DebugTraceUtil;
import androidx.media3.effect.DefaultVideoFrameProcessor; import androidx.media3.effect.DefaultVideoFrameProcessor;
import androidx.media3.effect.Presentation; import androidx.media3.effect.Presentation;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.muxer.Muxer;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;

View File

@ -62,6 +62,7 @@ import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.muxer.Muxer.MuxerException;
import androidx.media3.transformer.AssetLoader.CompositionSettings; import androidx.media3.transformer.AssetLoader.CompositionSettings;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
@ -436,7 +437,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
try { try {
muxerWrapper.finishWritingAndMaybeRelease(getMuxerReleaseReason(endReason)); muxerWrapper.finishWritingAndMaybeRelease(getMuxerReleaseReason(endReason));
} catch (Muxer.MuxerException e) { } catch (MuxerException e) {
if (releaseExportException == null) { if (releaseExportException == null) {
releaseExportException = ExportException.createForMuxer(e, ERROR_CODE_MUXING_FAILED); releaseExportException = ExportException.createForMuxer(e, ERROR_CODE_MUXING_FAILED);
} }

View File

@ -26,7 +26,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.muxer.Muxer.TrackToken; import androidx.media3.muxer.Muxer;
import androidx.media3.test.utils.DumpableFormat; import androidx.media3.test.utils.DumpableFormat;
import androidx.media3.test.utils.Dumper; import androidx.media3.test.utils.Dumper;
import androidx.media3.test.utils.Dumper.Dumpable; import androidx.media3.test.utils.Dumper.Dumpable;
@ -72,7 +72,7 @@ public final class CapturingMuxer implements Muxer, Dumpable {
} }
@Override @Override
public Muxer create(String path) throws Muxer.MuxerException { public Muxer create(String path) throws MuxerException {
muxer = new CapturingMuxer(wrappedFactory.create(path), handleAudioAsPcm); muxer = new CapturingMuxer(wrappedFactory.create(path), handleAudioAsPcm);
return muxer; return muxer;
} }
@ -142,9 +142,9 @@ public final class CapturingMuxer implements Muxer, Dumpable {
} }
@Override @Override
public void release() throws MuxerException { public void close() throws MuxerException {
released = true; released = true;
wrappedMuxer.release(); wrappedMuxer.close();
} }
// Dumper.Dumpable implementation. // Dumper.Dumpable implementation.

View File

@ -30,6 +30,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.ColorInfo; import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.muxer.Muxer.MuxerException;
import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpFileAsserts;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -74,7 +75,7 @@ public class MuxerWrapperTest {
@Nullable private MuxerWrapper muxerWrapper; @Nullable private MuxerWrapper muxerWrapper;
@After @After
public void tearDown() throws Muxer.MuxerException { public void tearDown() throws MuxerException {
if (muxerWrapper != null) { if (muxerWrapper != null) {
// Release with reason cancellation so that underlying resources are always released. // Release with reason cancellation so that underlying resources are always released.
muxerWrapper.finishWritingAndMaybeRelease(MuxerWrapper.MUXER_RELEASE_REASON_CANCELLED); muxerWrapper.finishWritingAndMaybeRelease(MuxerWrapper.MUXER_RELEASE_REASON_CANCELLED);

View File

@ -24,6 +24,7 @@ import androidx.media3.common.audio.ChannelMixingMatrix;
import androidx.media3.common.audio.SonicAudioProcessor; import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.muxer.Muxer;
import androidx.media3.test.utils.FakeClock; import androidx.media3.test.utils.FakeClock;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;

View File

@ -34,6 +34,7 @@ import androidx.media3.container.Mp4TimestampData;
import androidx.media3.container.XmpData; import androidx.media3.container.XmpData;
import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory; import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.muxer.Muxer;
import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpFileAsserts;
import androidx.media3.test.utils.FakeClock; import androidx.media3.test.utils.FakeClock;
import androidx.media3.test.utils.FakeExtractorOutput; import androidx.media3.test.utils.FakeExtractorOutput;