From 532e0ffdc3d4feaca219f92007ea99e250b0213d Mon Sep 17 00:00:00 2001 From: claincly Date: Tue, 29 Nov 2022 13:49:15 +0000 Subject: [PATCH] Move video encoding MIME type fallback to VTSP Main change: - Removed `Codec.EncoderFactory.createForVideoEncoding`'s argument of a list of allowed MIME types - Moved the check for whether a video MIME type is supported to VTSP PiperOrigin-RevId: 491611799 --- .../transformer/AndroidTestUtil.java | 5 +- .../TransformerAndroidTestRunner.java | 5 +- .../transformer/TransformerEndToEndTest.java | 3 +- .../android/exoplayer2/transformer/Codec.java | 21 +++-- .../transformer/DefaultEncoderFactory.java | 73 ++++------------ .../exoplayer2/transformer/EncoderUtil.java | 19 ++--- .../VideoTranscodingSamplePipeline.java | 84 +++++++++++++++---- .../DefaultEncoderFactoryTest.java | 75 ++++------------- .../transformer/EncoderUtilTest.java | 24 ++++++ .../transformer/VideoEncoderWrapperTest.java | 44 +++++++++- 10 files changed, 190 insertions(+), 163 deletions(-) diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java index 5a2af177d0..21d3a5473c 100644 --- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java @@ -478,9 +478,8 @@ public final class AndroidTestUtil { } @Override - public Codec createForVideoEncoding(Format format, List allowedMimeTypes) - throws TransformationException { - return encoderFactory.createForVideoEncoding(format, allowedMimeTypes); + public Codec createForVideoEncoding(Format format) throws TransformationException { + return encoderFactory.createForVideoEncoding(format); } @Override diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformerAndroidTestRunner.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformerAndroidTestRunner.java index ad994fa30c..f6edebc7eb 100644 --- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformerAndroidTestRunner.java +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformerAndroidTestRunner.java @@ -454,9 +454,8 @@ public class TransformerAndroidTestRunner { } @Override - public Codec createForVideoEncoding(Format format, List allowedMimeTypes) - throws TransformationException { - Codec videoEncoder = encoderFactory.createForVideoEncoding(format, allowedMimeTypes); + public Codec createForVideoEncoding(Format format) throws TransformationException { + Codec videoEncoder = encoderFactory.createForVideoEncoding(format); videoEncoderName = videoEncoder.getName(); return videoEncoder; } diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java index 25eb95e7dd..55ce1bfb30 100644 --- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/TransformerEndToEndTest.java @@ -146,8 +146,7 @@ public class TransformerEndToEndTest { } @Override - public Codec createForVideoEncoding(Format format, List allowedMimeTypes) - throws TransformationException { + public Codec createForVideoEncoding(Format format) throws TransformationException { throw TransformationException.createForCodec( new IllegalArgumentException(), /* isVideo= */ true, diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java index cac8f56ef1..9b5d209283 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/Codec.java @@ -87,23 +87,22 @@ public interface Codec { /** * Returns a {@link Codec} for video encoding. * - *

This method must validate that the {@link Codec} is configured to produce one of the - * {@code allowedMimeTypes}. The {@linkplain Format#sampleMimeType sample MIME type} given in - * {@code format} is not necessarily allowed. + *

The caller should ensure the {@linkplain Format#sampleMimeType MIME type} is supported on + * the device before calling this method. If encoding to HDR, the caller should also ensure the + * {@linkplain Format#colorInfo color characteristics} are supported. * * @param format The {@link Format} (of the output data) used to determine the underlying * encoder and its configuration values. {@link Format#sampleMimeType}, {@link Format#width} * and {@link Format#height} are set to those of the desired output video format. {@link - * Format#rotationDegrees} is 0 and {@link Format#width} {@code >=} {@link Format#height}, - * therefore the video is always in landscape orientation. {@link Format#frameRate} is set - * to the output video's frame rate, if available. - * @param allowedMimeTypes The non-empty list of allowed output sample {@linkplain MimeTypes - * MIME types}. - * @return A {@link Codec} for video encoding. + * Format#frameRate} is set to the requested output frame rate, if available. {@link + * Format#colorInfo} is set to the requested output color characteristics, if available. + * {@link Format#rotationDegrees} is 0 and {@link Format#width} {@code >=} {@link + * Format#height}, therefore the video is always in landscape orientation. + * @return A {@link Codec} for encoding video to the requested {@linkplain Format#sampleMimeType + * MIME type}. * @throws TransformationException If no suitable {@link Codec} can be created. */ - Codec createForVideoEncoding(Format format, List allowedMimeTypes) - throws TransformationException; + Codec createForVideoEncoding(Format format) throws TransformationException; /** Returns whether the audio needs to be encoded because of encoder specific configuration. */ default boolean audioNeedsEncoding() { diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java index 24480ebe13..4dbcb397f5 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactory.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.transformer; +import static com.google.android.exoplayer2.transformer.TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; @@ -211,21 +212,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { * VideoEncoderSettings#bitrate} set to request for a specific encoding bitrate. Bitrate settings * in {@link Format} are ignored when {@link VideoEncoderSettings#bitrate} or {@link * VideoEncoderSettings#enableHighQualityTargeting} is set. - * - * @param format The {@link Format} (of the output data) used to determine the underlying encoder - * and its configuration values. {@link Format#sampleMimeType}, {@link Format#width} and - * {@link Format#height} are set to those of the desired output video format. {@link - * Format#rotationDegrees} is 0 and {@link Format#width} {@code >=} {@link Format#height}, - * therefore the video is always in landscape orientation. {@link Format#frameRate} is set to - * the output video's frame rate, if available. - * @param allowedMimeTypes The non-empty list of allowed output sample {@linkplain MimeTypes MIME - * types}. - * @return A {@link DefaultCodec} for video encoding. - * @throws TransformationException If no suitable {@link DefaultCodec} can be created. */ @Override - public Codec createForVideoEncoding(Format format, List allowedMimeTypes) - throws TransformationException { + public DefaultCodec createForVideoEncoding(Format format) throws TransformationException { if (format.frameRate == Format.NO_VALUE) { format = format.buildUpon().setFrameRate(DEFAULT_FRAME_RATE).build(); } @@ -236,17 +225,12 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { checkArgument(format.height <= format.width); checkArgument(format.rotationDegrees == 0); checkNotNull(format.sampleMimeType); - checkArgument(!allowedMimeTypes.isEmpty()); checkStateNotNull(videoEncoderSelector); @Nullable VideoEncoderQueryResult encoderAndClosestFormatSupport = findEncoderWithClosestSupportedFormat( - format, - requestedVideoEncoderSettings, - videoEncoderSelector, - allowedMimeTypes, - enableFallback); + format, requestedVideoEncoderSettings, videoEncoderSelector, enableFallback); if (encoderAndClosestFormatSupport == null) { throw createTransformationException(format); @@ -310,13 +294,14 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { MediaFormatUtil.maybeSetColorInfo(mediaFormat, encoderSupportedFormat.colorInfo); if (Util.SDK_INT >= 31 && ColorInfo.isTransferHdr(format.colorInfo)) { + // TODO(b/260389841): Validate the picked encoder supports HDR editing. if (EncoderUtil.getSupportedColorFormats(encoderInfo, mimeType) .contains(MediaCodecInfo.CodecCapabilities.COLOR_Format32bitABGR2101010)) { mediaFormat.setInteger( MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_Format32bitABGR2101010); } else { - throw createTransformationException(format); + throw createTransformationException(format, ERROR_CODE_HDR_ENCODING_UNSUPPORTED); } } else { mediaFormat.setInteger( @@ -380,15 +365,8 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { Format requestedFormat, VideoEncoderSettings videoEncoderSettings, EncoderSelector encoderSelector, - List allowedMimeTypes, boolean enableFallback) { - String requestedMimeType = requestedFormat.sampleMimeType; - @Nullable - String mimeType = findFallbackMimeType(encoderSelector, requestedMimeType, allowedMimeTypes); - if (mimeType == null || (!enableFallback && !requestedMimeType.equals(mimeType))) { - return null; - } - + String mimeType = checkNotNull(requestedFormat.sampleMimeType); ImmutableList filteredEncoderInfos = encoderSelector.selectEncoderInfos(mimeType); if (filteredEncoderInfos.isEmpty()) { @@ -678,36 +656,6 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { return ImmutableList.copyOf(filteredEncoders); } - /** - * Finds a {@linkplain MimeTypes MIME type} that is supported by the encoder and in the {@code - * allowedMimeTypes}. - */ - @Nullable - private static String findFallbackMimeType( - EncoderSelector encoderSelector, String requestedMimeType, List allowedMimeTypes) { - if (mimeTypeIsSupported(encoderSelector, requestedMimeType, allowedMimeTypes)) { - return requestedMimeType; - } else if (mimeTypeIsSupported(encoderSelector, MimeTypes.VIDEO_H265, allowedMimeTypes)) { - return MimeTypes.VIDEO_H265; - } else if (mimeTypeIsSupported(encoderSelector, MimeTypes.VIDEO_H264, allowedMimeTypes)) { - return MimeTypes.VIDEO_H264; - } else { - for (int i = 0; i < allowedMimeTypes.size(); i++) { - String allowedMimeType = allowedMimeTypes.get(i); - if (mimeTypeIsSupported(encoderSelector, allowedMimeType, allowedMimeTypes)) { - return allowedMimeType; - } - } - } - return null; - } - - private static boolean mimeTypeIsSupported( - EncoderSelector encoderSelector, String mimeType, List allowedMimeTypes) { - return !encoderSelector.selectEncoderInfos(mimeType).isEmpty() - && allowedMimeTypes.contains(mimeType); - } - /** * Computes the video bit rate using the Kush Gauge. * @@ -730,12 +678,19 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { @RequiresNonNull("#1.sampleMimeType") private static TransformationException createTransformationException(Format format) { + return createTransformationException( + format, TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED); + } + + @RequiresNonNull("#1.sampleMimeType") + private static TransformationException createTransformationException( + Format format, @TransformationException.ErrorCode int errorCode) { return TransformationException.createForCodec( new IllegalArgumentException("The requested encoding format is not supported."), MimeTypes.isVideo(format.sampleMimeType), /* isDecoder= */ false, format, /* mediaCodecName= */ null, - TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED); + errorCode); } } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderUtil.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderUtil.java index b86c808840..dc1126b7ec 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderUtil.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/EncoderUtil.java @@ -69,8 +69,8 @@ public final class EncoderUtil { } /** - * Returns the names of encoders that support HDR editing for the given format, or an empty list - * if the format is unknown or not supported for HDR encoding. + * Returns the names of encoders that support HDR editing for the given {@code mimeType} and + * {@code ColorInfo}, or an empty list if the format is unknown or not supported for HDR encoding. */ public static ImmutableList getSupportedEncoderNamesForHdrEditing( String mimeType, @Nullable ColorInfo colorInfo) { @@ -78,13 +78,12 @@ public final class EncoderUtil { return ImmutableList.of(); } - @ColorTransfer int colorTransfer = colorInfo.colorTransfer; - ImmutableList profiles = getCodecProfilesForHdrFormat(mimeType, colorTransfer); - ImmutableList.Builder resultBuilder = ImmutableList.builder(); - ImmutableList mediaCodecInfos = - EncoderSelector.DEFAULT.selectEncoderInfos(mimeType); - for (int i = 0; i < mediaCodecInfos.size(); i++) { - MediaCodecInfo mediaCodecInfo = mediaCodecInfos.get(i); + ImmutableList encoders = getSupportedEncoders(mimeType); + ImmutableList allowedColorProfiles = + getCodecProfilesForHdrFormat(mimeType, colorInfo.colorTransfer); + ImmutableList.Builder resultBuilder = new ImmutableList.Builder<>(); + for (int i = 0; i < encoders.size(); i++) { + MediaCodecInfo mediaCodecInfo = encoders.get(i); if (mediaCodecInfo.isAlias() || !isFeatureSupported( mediaCodecInfo, mimeType, MediaCodecInfo.CodecCapabilities.FEATURE_HdrEditing)) { @@ -92,7 +91,7 @@ public final class EncoderUtil { } for (MediaCodecInfo.CodecProfileLevel codecProfileLevel : mediaCodecInfo.getCapabilitiesForType(mimeType).profileLevels) { - if (profiles.contains(codecProfileLevel.profile)) { + if (allowedColorProfiles.contains(codecProfileLevel.profile)) { resultBuilder.add(mediaCodecInfo.getName()); } } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java index 7178c28377..9dfd6f0589 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.transformer; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Util.SDK_INT; import android.content.Context; @@ -36,6 +37,7 @@ import com.google.android.exoplayer2.util.FrameInfo; import com.google.android.exoplayer2.util.FrameProcessingException; import com.google.android.exoplayer2.util.FrameProcessor; import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.SurfaceInfo; import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.video.ColorInfo; @@ -369,7 +371,7 @@ import org.checkerframework.dataflow.qual.Pure; private final Codec.EncoderFactory encoderFactory; private final Format inputFormat; - private final List allowedOutputMimeTypes; + private final List muxerSupportedMimeTypes; private final TransformationRequest transformationRequest; private final FallbackListener fallbackListener; private final String requestedOutputMimeType; @@ -384,12 +386,12 @@ import org.checkerframework.dataflow.qual.Pure; public EncoderWrapper( Codec.EncoderFactory encoderFactory, Format inputFormat, - List allowedOutputMimeTypes, + List muxerSupportedMimeTypes, TransformationRequest transformationRequest, FallbackListener fallbackListener) { this.encoderFactory = encoderFactory; this.inputFormat = inputFormat; - this.allowedOutputMimeTypes = allowedOutputMimeTypes; + this.muxerSupportedMimeTypes = muxerSupportedMimeTypes; this.transformationRequest = transformationRequest; this.fallbackListener = fallbackListener; @@ -454,20 +456,31 @@ import org.checkerframework.dataflow.qual.Pure; .setColorInfo(getSupportedInputColor()) .build(); + @Nullable + String supportedMimeType = + selectEncoderAndMuxerSupportedMimeType( + requestedOutputMimeType, muxerSupportedMimeTypes, requestedEncoderFormat.colorInfo); + if (supportedMimeType == null) { + throw TransformationException.createForCodec( + new IllegalArgumentException("No MIME type is supported by both encoder and muxer."), + /* isVideo= */ true, + /* isDecoder= */ false, + requestedEncoderFormat, + /* mediaCodecName= */ null, + TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED); + } + encoder = - encoderFactory.createForVideoEncoding(requestedEncoderFormat, allowedOutputMimeTypes); + encoderFactory.createForVideoEncoding( + requestedEncoderFormat.buildUpon().setSampleMimeType(supportedMimeType).build()); Format encoderSupportedFormat = encoder.getConfigurationFormat(); - if (ColorInfo.isTransferHdr(requestedEncoderFormat.colorInfo)) { - if (!requestedOutputMimeType.equals(encoderSupportedFormat.sampleMimeType)) { - throw createEncodingException( - new IllegalStateException("MIME type fallback unsupported with HDR editing"), - encoderSupportedFormat); - } else if (!supportedEncoderNamesForHdrEditing.contains(encoder.getName())) { - throw createEncodingException( - new IllegalStateException("Selected encoder doesn't support HDR editing"), - encoderSupportedFormat); - } + checkState(supportedMimeType.equals(encoderSupportedFormat.sampleMimeType)); + if (ColorInfo.isTransferHdr(requestedEncoderFormat.colorInfo) + && !supportedEncoderNamesForHdrEditing.contains(encoder.getName())) { + throw createEncodingException( + new IllegalStateException("Selected encoder doesn't support HDR editing"), + encoderSupportedFormat); } boolean isInputToneMapped = ColorInfo.isTransferHdr(inputFormat.colorInfo) @@ -553,5 +566,48 @@ import org.checkerframework.dataflow.qual.Pure; checkNotNull(encoder).getName(), TransformationException.ERROR_CODE_ENCODING_FAILED); } + + /** + * Finds a {@linkplain MimeTypes MIME type} that is supported by both the encoder and the muxer. + * + * @param requestedMimeType The requested {@linkplain MimeTypes MIME type}. + * @param muxerSupportedMimeTypes The list of sample {@linkplain MimeTypes MIME types} that the + * muxer supports. + * @param colorInfo The requested encoding {@link ColorInfo}, if available. + * @return A {@linkplain MimeTypes MIME type} that is supported by an encoder and the muxer, or + * {@code null} if no such {@linkplain MimeTypes MIME type} exists. + */ + @Nullable + private static String selectEncoderAndMuxerSupportedMimeType( + String requestedMimeType, + List muxerSupportedMimeTypes, + @Nullable ColorInfo colorInfo) { + ImmutableList mimeTypesToCheck = + new ImmutableList.Builder() + .add(requestedMimeType) + .add(MimeTypes.VIDEO_H265) + .add(MimeTypes.VIDEO_H264) + .addAll(muxerSupportedMimeTypes) + .build(); + + for (int i = 0; i < mimeTypesToCheck.size(); i++) { + String mimeType = mimeTypesToCheck.get(i); + if (mimeTypeAndColorAreSupported(mimeType, muxerSupportedMimeTypes, colorInfo)) { + return mimeType; + } + } + return null; + } + + private static boolean mimeTypeAndColorAreSupported( + String mimeType, List muxerSupportedMimeTypes, @Nullable ColorInfo colorInfo) { + if (!muxerSupportedMimeTypes.contains(mimeType)) { + return false; + } + if (ColorInfo.isTransferHdr(colorInfo)) { + return !EncoderUtil.getSupportedEncoderNamesForHdrEditing(mimeType, colorInfo).isEmpty(); + } + return !EncoderUtil.getSupportedEncoders(mimeType).isEmpty(); + } } } diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactoryTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactoryTest.java index a7df903799..d280d94544 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactoryTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/DefaultEncoderFactoryTest.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.transformer; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.android.exoplayer2.transformer.TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -84,9 +85,7 @@ public class DefaultEncoderFactoryTest { Format actualVideoFormat = new DefaultEncoderFactory.Builder(context) .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) + .createForVideoEncoding(requestedVideoFormat) .getConfigurationFormat(); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); @@ -97,22 +96,15 @@ public class DefaultEncoderFactoryTest { } @Test - public void createForVideoEncoding_withFallbackOnAndUnsupportedMimeType_configuresEncoder() - throws Exception { + public void createForVideoEncoding_withFallbackOnAndUnsupportedMimeType_throws() { Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H265, 1920, 1080, 30); - Format actualVideoFormat = - new DefaultEncoderFactory.Builder(context) - .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) - .getConfigurationFormat(); + DefaultEncoderFactory encoderFactory = new DefaultEncoderFactory.Builder(context).build(); - assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); - assertThat(actualVideoFormat.width).isEqualTo(1920); - assertThat(actualVideoFormat.height).isEqualTo(1080); - // 1920 * 1080 * 30 * 0.07 * 2. - assertThat(actualVideoFormat.averageBitrate).isEqualTo(8_709_120); + TransformationException transformationException = + assertThrows( + TransformationException.class, + () -> encoderFactory.createForVideoEncoding(requestedVideoFormat)); + assertThat(transformationException.errorCode).isEqualTo(ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED); } @Test @@ -122,9 +114,7 @@ public class DefaultEncoderFactoryTest { Format actualVideoFormat = new DefaultEncoderFactory.Builder(context) .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) + .createForVideoEncoding(requestedVideoFormat) .getConfigurationFormat(); assertThat(actualVideoFormat.width).isEqualTo(1920); @@ -142,9 +132,7 @@ public class DefaultEncoderFactoryTest { new DefaultEncoderFactory.Builder(context) .setRequestedVideoEncoderSettings(VideoEncoderSettings.DEFAULT) .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) + .createForVideoEncoding(requestedVideoFormat) .getConfigurationFormat(); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); @@ -161,9 +149,7 @@ public class DefaultEncoderFactoryTest { Format actualVideoFormat = new DefaultEncoderFactory.Builder(context) .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) + .createForVideoEncoding(requestedVideoFormat) .getConfigurationFormat(); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); @@ -185,9 +171,7 @@ public class DefaultEncoderFactoryTest { .setRequestedVideoEncoderSettings( new VideoEncoderSettings.Builder().setEnableHighQualityTargeting(true).build()) .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) + .createForVideoEncoding(requestedVideoFormat) .getConfigurationFormat(); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); @@ -210,9 +194,7 @@ public class DefaultEncoderFactoryTest { .setRequestedVideoEncoderSettings( new VideoEncoderSettings.Builder().setBitrate(10_000_000).build()) .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)) + .createForVideoEncoding(requestedVideoFormat) .getConfigurationFormat(); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); @@ -230,9 +212,7 @@ public class DefaultEncoderFactoryTest { Codec videoEncoder = new DefaultEncoderFactory.Builder(context) .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)); + .createForVideoEncoding(requestedVideoFormat); assertThat(videoEncoder).isInstanceOf(DefaultCodec.class); MediaFormat configurationMediaFormat = @@ -244,25 +224,6 @@ public class DefaultEncoderFactoryTest { .isEqualTo(Integer.MAX_VALUE); } - @Test - public void createForVideoEncoding_withNoSupportedEncoder_throws() { - Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); - - TransformationException exception = - assertThrows( - TransformationException.class, - () -> - new DefaultEncoderFactory.Builder(context) - .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H265))); - - assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); - assertThat(exception.errorCode) - .isEqualTo(TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED); - } - @Test public void createForVideoEncoding_withNoAvailableEncoderFromEncoderSelector_throws() { Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); @@ -270,11 +231,9 @@ public class DefaultEncoderFactoryTest { TransformationException.class, () -> new DefaultEncoderFactory.Builder(context) - .setVideoEncoderSelector(mimeType -> ImmutableList.of()) + .setVideoEncoderSelector((mimeType) -> ImmutableList.of()) .build() - .createForVideoEncoding( - requestedVideoFormat, - /* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))); + .createForVideoEncoding(requestedVideoFormat)); } private static Format createVideoFormat(String mimeType, int width, int height, int frameRate) { diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/EncoderUtilTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/EncoderUtilTest.java index 022e4cbefc..56eb3e8ef1 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/EncoderUtilTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/EncoderUtilTest.java @@ -23,11 +23,14 @@ import android.media.MediaFormat; import android.util.Size; import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.video.ColorInfo; import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; import org.robolectric.shadows.MediaCodecInfoBuilder; import org.robolectric.shadows.ShadowMediaCodecList; @@ -116,4 +119,25 @@ public class EncoderUtilTest { assertThat(closestSupportedResolution.getWidth()).isEqualTo(1920); assertThat(closestSupportedResolution.getHeight()).isEqualTo(1080); } + + /** + * @see EncoderUtil#getSupportedEncoderNamesForHdrEditing(String, ColorInfo) + */ + @Config(sdk = {30, 31}) + @Test + public void getSupportedEncoderNamesForHdrEditing_returnsEmptyList() { + // This test is run on 30 and 31 because the tested logic differentiate at API31. + // getSupportedEncoderNamesForHdrEditing returns an empty list for API < 31. It returns an empty + // list for API >= 31 as well, because currently it is not possible to make ShadowMediaCodec + // support HDR. + assertThat( + EncoderUtil.getSupportedEncoderNamesForHdrEditing( + MIME_TYPE, + new ColorInfo( + C.COLOR_SPACE_BT2020, + C.COLOR_RANGE_FULL, + C.COLOR_TRANSFER_HLG, + /* hdrStaticInfo= */ null))) + .isEmpty(); + } } diff --git a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/VideoEncoderWrapperTest.java b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/VideoEncoderWrapperTest.java index fe17980468..9fc8a231ff 100644 --- a/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/VideoEncoderWrapperTest.java +++ b/library/transformer/src/test/java/com/google/android/exoplayer2/transformer/VideoEncoderWrapperTest.java @@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.media.MediaCodecInfo; +import android.media.MediaFormat; import android.net.Uri; import android.os.Looper; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -34,6 +36,8 @@ import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.shadows.MediaCodecInfoBuilder; +import org.robolectric.shadows.ShadowMediaCodecList; /** Unit tests for {@link VideoTranscodingSamplePipeline.EncoderWrapper}. */ @RunWith(AndroidJUnit4.class) @@ -49,14 +53,15 @@ public final class VideoEncoderWrapperTest { private final VideoTranscodingSamplePipeline.EncoderWrapper encoderWrapper = new VideoTranscodingSamplePipeline.EncoderWrapper( fakeEncoderFactory, - /* inputFormat= */ new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H265).build(), - /* allowedOutputMimeTypes= */ ImmutableList.of(), + /* inputFormat= */ new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build(), + /* muxerSupportedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264), emptyTransformationRequest, fallbackListener); @Before public void registerTrack() { fallbackListener.registerTrack(); + createShadowH264Encoder(); } @Test @@ -111,6 +116,39 @@ public final class VideoEncoderWrapperTest { assertThat(surfaceInfo.height).isEqualTo(fallbackHeight); } + private static void createShadowH264Encoder() { + MediaFormat avcFormat = new MediaFormat(); + avcFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC); + MediaCodecInfo.CodecProfileLevel profileLevel = new MediaCodecInfo.CodecProfileLevel(); + profileLevel.profile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh; + // Using Level4 gives us 8192 16x16 blocks. If using width 1920 uses 120 blocks, 8192 / 120 = 68 + // blocks will be left for encoding height 1088. + profileLevel.level = MediaCodecInfo.CodecProfileLevel.AVCLevel4; + + createShadowVideoEncoder(avcFormat, profileLevel, "test.transformer.avc.encoder"); + } + + private static void createShadowVideoEncoder( + MediaFormat supportedFormat, + MediaCodecInfo.CodecProfileLevel supportedProfileLevel, + String name) { + // ShadowMediaCodecList is static. The added encoders will be visible for every test. + ShadowMediaCodecList.addCodec( + MediaCodecInfoBuilder.newBuilder() + .setName(name) + .setIsEncoder(true) + .setCapabilities( + MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder() + .setMediaFormat(supportedFormat) + .setIsEncoder(true) + .setColorFormats( + new int[] {MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible}) + .setProfileLevels( + new MediaCodecInfo.CodecProfileLevel[] {supportedProfileLevel}) + .build()) + .build()); + } + private static class FakeVideoEncoderFactory implements Codec.EncoderFactory { private int fallbackWidth; @@ -132,7 +170,7 @@ public final class VideoEncoderWrapperTest { } @Override - public Codec createForVideoEncoding(Format format, List allowedMimeTypes) { + public Codec createForVideoEncoding(Format format) { Codec mockEncoder = mock(Codec.class); if (fallbackWidth != C.LENGTH_UNSET) { format = format.buildUpon().setWidth(fallbackWidth).build();