diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java index 4ebe62e846..57ca4989ab 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java @@ -31,6 +31,8 @@ import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.MediaFormatUtil; import androidx.media3.common.util.Util; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.lang.reflect.Field; import java.nio.ByteBuffer; @@ -38,6 +40,32 @@ import java.nio.ByteBuffer; /** Muxer implementation that uses a {@link MediaMuxer}. */ /* package */ final class FrameworkMuxer implements Muxer { + // MediaMuxer supported sample formats are documented in MediaMuxer.addTrack(MediaFormat). + private static final ImmutableMap> + SUPPORTED_CONTAINER_TO_VIDEO_SAMPLE_MIME_TYPES = + ImmutableMap.of( + MimeTypes.VIDEO_MP4, + Util.SDK_INT >= 24 + ? ImmutableList.of( + MimeTypes.VIDEO_H263, + MimeTypes.VIDEO_H264, + MimeTypes.VIDEO_MP4V, + MimeTypes.VIDEO_H265) + : ImmutableList.of( + MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V), + MimeTypes.VIDEO_WEBM, + Util.SDK_INT >= 24 + ? ImmutableList.of(MimeTypes.VIDEO_VP8, MimeTypes.VIDEO_VP9) + : ImmutableList.of(MimeTypes.VIDEO_VP8)); + + private static final ImmutableMap> + SUPPORTED_CONTAINER_TO_AUDIO_SAMPLE_MIME_TYPES = + ImmutableMap.of( + MimeTypes.VIDEO_MP4, + ImmutableList.of(MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB), + MimeTypes.VIDEO_WEBM, + ImmutableList.of(MimeTypes.AUDIO_VORBIS)); + public static final class Factory implements Muxer.Factory { @Override public FrameworkMuxer create(String path, String outputMimeType) throws IOException { @@ -69,29 +97,22 @@ import java.nio.ByteBuffer; @Override public boolean supportsSampleMimeType( @Nullable String sampleMimeType, String containerMimeType) { + return getSupportedSampleMimeTypes(MimeTypes.getTrackType(sampleMimeType), containerMimeType) + .contains(sampleMimeType); + } + + @Override + public ImmutableList getSupportedSampleMimeTypes( + @C.TrackType int trackType, String containerMimeType) { // MediaMuxer supported sample formats are documented in MediaMuxer.addTrack(MediaFormat). - boolean isAudio = MimeTypes.isAudio(sampleMimeType); - boolean isVideo = MimeTypes.isVideo(sampleMimeType); - if (containerMimeType.equals(MimeTypes.VIDEO_MP4)) { - if (isVideo) { - return MimeTypes.VIDEO_H263.equals(sampleMimeType) - || MimeTypes.VIDEO_H264.equals(sampleMimeType) - || MimeTypes.VIDEO_MP4V.equals(sampleMimeType) - || (Util.SDK_INT >= 24 && MimeTypes.VIDEO_H265.equals(sampleMimeType)); - } else if (isAudio) { - return MimeTypes.AUDIO_AAC.equals(sampleMimeType) - || MimeTypes.AUDIO_AMR_NB.equals(sampleMimeType) - || MimeTypes.AUDIO_AMR_WB.equals(sampleMimeType); - } - } else if (containerMimeType.equals(MimeTypes.VIDEO_WEBM) && SDK_INT >= 21) { - if (isVideo) { - return MimeTypes.VIDEO_VP8.equals(sampleMimeType) - || (Util.SDK_INT >= 24 && MimeTypes.VIDEO_VP9.equals(sampleMimeType)); - } else if (isAudio) { - return MimeTypes.AUDIO_VORBIS.equals(sampleMimeType); - } + if (trackType == C.TRACK_TYPE_VIDEO) { + return SUPPORTED_CONTAINER_TO_VIDEO_SAMPLE_MIME_TYPES.getOrDefault( + containerMimeType, ImmutableList.of()); + } else if (trackType == C.TRACK_TYPE_AUDIO) { + return SUPPORTED_CONTAINER_TO_AUDIO_SAMPLE_MIME_TYPES.getOrDefault( + containerMimeType, ImmutableList.of()); } - return false; + return ImmutableList.of(); } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Muxer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Muxer.java index 70a96fae3c..e831bd0727 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Muxer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Muxer.java @@ -17,8 +17,10 @@ package androidx.media3.transformer; import android.os.ParcelFileDescriptor; import androidx.annotation.Nullable; +import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.ByteBuffer; @@ -69,6 +71,13 @@ import java.nio.ByteBuffer; * {@link MimeTypes MIME type}. */ boolean supportsSampleMimeType(@Nullable String sampleMimeType, String containerMimeType); + + /** + * Returns the supported sample {@link MimeTypes MIME types} for the given {@link C.TrackType} + * and container {@link MimeTypes MIME type}. + */ + ImmutableList getSupportedSampleMimeTypes( + @C.TrackType int trackType, String containerMimeType); } /** diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java b/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java index 0bba625b21..d9899e40a9 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/MuxerWrapper.java @@ -26,6 +26,7 @@ import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Util; +import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; /** @@ -84,6 +85,14 @@ import java.nio.ByteBuffer; return muxerFactory.supportsSampleMimeType(mimeType, containerMimeType); } + /** + * Returns the supported {@link MimeTypes MIME types} for the given {@link C.TrackType track + * type}. + */ + public ImmutableList getSupportedSampleMimeTypes(@C.TrackType int trackType) { + return muxerFactory.getSupportedSampleMimeTypes(trackType, containerMimeType); + } + /** * Adds a track format to the muxer. * diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerAudioRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerAudioRenderer.java index 663b440e53..c17285bd69 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerAudioRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerAudioRenderer.java @@ -67,25 +67,35 @@ import androidx.media3.extractor.metadata.mp4.SlowMotionData; return false; } Format inputFormat = checkNotNull(formatHolder.format); - if (shouldTranscode(inputFormat)) { + String sampleMimeType = checkNotNull(inputFormat.sampleMimeType); + if (transformationRequest.audioMimeType == null + && !muxerWrapper.supportsSampleMimeType(sampleMimeType)) { + throw TransformationException.createForMuxer( + new IllegalArgumentException( + "The output sample MIME inferred from the input format is not supported by the muxer." + + " Sample MIME type: " + + sampleMimeType), + TransformationException.ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED); + } + if (shouldPassthrough(inputFormat)) { + samplePipeline = new PassthroughSamplePipeline(inputFormat); + } else { samplePipeline = new AudioSamplePipeline( inputFormat, transformationRequest, encoderFactory, decoderFactory); - } else { - samplePipeline = new PassthroughSamplePipeline(inputFormat); } return true; } - private boolean shouldTranscode(Format inputFormat) { + private boolean shouldPassthrough(Format inputFormat) { if (transformationRequest.audioMimeType != null && !transformationRequest.audioMimeType.equals(inputFormat.sampleMimeType)) { - return true; + return false; } if (transformationRequest.flattenForSlowMotion && isSlowMotion(inputFormat)) { - return true; + return false; } - return false; + return true; } private static boolean isSlowMotion(Format format) { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java index 07cf87343e..945f392244 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerBaseRenderer.java @@ -16,7 +16,6 @@ package androidx.media3.transformer; -import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkStateNotNull; import androidx.annotation.Nullable; @@ -59,45 +58,18 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } /** - * Returns whether the renderer supports the track type of the given input format. + * Returns whether the renderer supports the track type of the given format. * - * @param inputFormat The input format. + * @param format The format. * @return The {@link Capabilities} for this format. - * @throws ExoPlaybackException If the muxer does not support the output sample MIME type derived - * from the input {@code format} and {@link TransformationRequest}. */ @Override @Capabilities - public final int supportsFormat(Format inputFormat) throws ExoPlaybackException { - @Nullable String inputSampleMimeType = inputFormat.sampleMimeType; - if (inputSampleMimeType == null - || MimeTypes.getTrackType(inputSampleMimeType) != getTrackType()) { - return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); - } - - // If the output sample MIME type is given in the transformationRequest it has already been - // validated by the builder. - if (MimeTypes.isAudio(inputSampleMimeType) && transformationRequest.audioMimeType != null) { - checkState(muxerWrapper.supportsSampleMimeType(transformationRequest.audioMimeType)); - return RendererCapabilities.create(C.FORMAT_HANDLED); - } - if (MimeTypes.isVideo(inputSampleMimeType) && transformationRequest.videoMimeType != null) { - checkState(muxerWrapper.supportsSampleMimeType(transformationRequest.videoMimeType)); - return RendererCapabilities.create(C.FORMAT_HANDLED); - } - - // When the output sample MIME type is not given in the transformationRequest, it is inferred - // from the input. - if (muxerWrapper.supportsSampleMimeType(inputSampleMimeType)) { - return RendererCapabilities.create(C.FORMAT_HANDLED); - } - throw wrapTransformationException( - TransformationException.createForMuxer( - new IllegalArgumentException( - "The sample MIME inferred from the input is not supported by the muxer. " - + "Input sample MIME type: " - + inputSampleMimeType), - TransformationException.ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED)); + public final int supportsFormat(Format format) { + return RendererCapabilities.create( + MimeTypes.getTrackType(format.sampleMimeType) == getTrackType() + ? C.FORMAT_HANDLED + : C.FORMAT_UNSUPPORTED_TYPE); } @Override diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java index e1f3847776..f991c0222f 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java @@ -76,7 +76,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return false; } Format inputFormat = checkNotNull(formatHolder.format); - if (shouldTranscode(inputFormat)) { + String sampleMimeType = checkNotNull(inputFormat.sampleMimeType); + if (transformationRequest.audioMimeType == null + && !muxerWrapper.supportsSampleMimeType(sampleMimeType)) { + throw TransformationException.createForMuxer( + new IllegalArgumentException( + "The output sample MIME inferred from the input format is not supported by the muxer." + + " Sample MIME type: " + + sampleMimeType), + TransformationException.ERROR_CODE_MUXER_SAMPLE_MIME_TYPE_UNSUPPORTED); + } + if (shouldPassthrough(inputFormat)) { + samplePipeline = new PassthroughSamplePipeline(inputFormat); + } else { samplePipeline = new VideoSamplePipeline( context, @@ -85,8 +97,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; encoderFactory, decoderFactory, debugViewProvider); - } else { - samplePipeline = new PassthroughSamplePipeline(inputFormat); } if (transformationRequest.flattenForSlowMotion) { sefSlowMotionFlattener = new SefSlowMotionFlattener(inputFormat); @@ -94,19 +104,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return true; } - private boolean shouldTranscode(Format inputFormat) { + private boolean shouldPassthrough(Format inputFormat) { if (transformationRequest.videoMimeType != null && !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) { - return true; + return false; } if (transformationRequest.outputHeight != C.LENGTH_UNSET && transformationRequest.outputHeight != inputFormat.height) { - return true; + return false; } if (!transformationRequest.transformationMatrix.isIdentity()) { - return true; + return false; } - return false; + return true; } /** diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java index ad4f7bbcc8..0c562ff31e 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerTest.java @@ -33,6 +33,7 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.view.Surface; import androidx.annotation.Nullable; +import androidx.media3.common.C; import androidx.media3.common.MediaItem; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Util; @@ -40,6 +41,7 @@ import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.FakeClock; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.io.IOException; import java.nio.ByteBuffer; @@ -732,5 +734,11 @@ public final class TransformerTest { public boolean supportsSampleMimeType(String sampleMimeType, String outputMimeType) { return frameworkMuxerFactory.supportsSampleMimeType(sampleMimeType, outputMimeType); } + + @Override + public ImmutableList getSupportedSampleMimeTypes( + @C.TrackType int trackType, String containerMimeType) { + return frameworkMuxerFactory.getSupportedSampleMimeTypes(trackType, containerMimeType); + } } }