Commit 532e0ffd by claincly Committed by Rohit Singh

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
parent 04f031d1
...@@ -478,9 +478,8 @@ public final class AndroidTestUtil { ...@@ -478,9 +478,8 @@ public final class AndroidTestUtil {
} }
@Override @Override
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) public Codec createForVideoEncoding(Format format) throws TransformationException {
throws TransformationException { return encoderFactory.createForVideoEncoding(format);
return encoderFactory.createForVideoEncoding(format, allowedMimeTypes);
} }
@Override @Override
......
...@@ -454,9 +454,8 @@ public class TransformerAndroidTestRunner { ...@@ -454,9 +454,8 @@ public class TransformerAndroidTestRunner {
} }
@Override @Override
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) public Codec createForVideoEncoding(Format format) throws TransformationException {
throws TransformationException { Codec videoEncoder = encoderFactory.createForVideoEncoding(format);
Codec videoEncoder = encoderFactory.createForVideoEncoding(format, allowedMimeTypes);
videoEncoderName = videoEncoder.getName(); videoEncoderName = videoEncoder.getName();
return videoEncoder; return videoEncoder;
} }
......
...@@ -146,8 +146,7 @@ public class TransformerEndToEndTest { ...@@ -146,8 +146,7 @@ public class TransformerEndToEndTest {
} }
@Override @Override
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) public Codec createForVideoEncoding(Format format) throws TransformationException {
throws TransformationException {
throw TransformationException.createForCodec( throw TransformationException.createForCodec(
new IllegalArgumentException(), new IllegalArgumentException(),
/* isVideo= */ true, /* isVideo= */ true,
......
...@@ -87,23 +87,22 @@ public interface Codec { ...@@ -87,23 +87,22 @@ public interface Codec {
/** /**
* Returns a {@link Codec} for video encoding. * Returns a {@link Codec} for video encoding.
* *
* <p>This method must validate that the {@link Codec} is configured to produce one of the * <p>The caller should ensure the {@linkplain Format#sampleMimeType MIME type} is supported on
* {@code allowedMimeTypes}. The {@linkplain Format#sampleMimeType sample MIME type} given in * the device before calling this method. If encoding to HDR, the caller should also ensure the
* {@code format} is not necessarily allowed. * {@linkplain Format#colorInfo color characteristics} are supported.
* *
* @param format The {@link Format} (of the output data) used to determine the underlying * @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} * 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 * 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}, * Format#frameRate} is set to the requested output frame rate, if available. {@link
* therefore the video is always in landscape orientation. {@link Format#frameRate} is set * Format#colorInfo} is set to the requested output color characteristics, if available.
* to the output video's frame rate, if available. * {@link Format#rotationDegrees} is 0 and {@link Format#width} {@code >=} {@link
* @param allowedMimeTypes The non-empty list of allowed output sample {@linkplain MimeTypes * Format#height}, therefore the video is always in landscape orientation.
* MIME types}. * @return A {@link Codec} for encoding video to the requested {@linkplain Format#sampleMimeType
* @return A {@link Codec} for video encoding. * MIME type}.
* @throws TransformationException If no suitable {@link Codec} can be created. * @throws TransformationException If no suitable {@link Codec} can be created.
*/ */
Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) Codec createForVideoEncoding(Format format) throws TransformationException;
throws TransformationException;
/** Returns whether the audio needs to be encoded because of encoder specific configuration. */ /** Returns whether the audio needs to be encoded because of encoder specific configuration. */
default boolean audioNeedsEncoding() { default boolean audioNeedsEncoding() {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.transformer; 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.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; 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.Assertions.checkState;
...@@ -211,21 +212,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { ...@@ -211,21 +212,9 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
* VideoEncoderSettings#bitrate} set to request for a specific encoding bitrate. Bitrate settings * VideoEncoderSettings#bitrate} set to request for a specific encoding bitrate. Bitrate settings
* in {@link Format} are ignored when {@link VideoEncoderSettings#bitrate} or {@link * in {@link Format} are ignored when {@link VideoEncoderSettings#bitrate} or {@link
* VideoEncoderSettings#enableHighQualityTargeting} is set. * 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 @Override
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) public DefaultCodec createForVideoEncoding(Format format) throws TransformationException {
throws TransformationException {
if (format.frameRate == Format.NO_VALUE) { if (format.frameRate == Format.NO_VALUE) {
format = format.buildUpon().setFrameRate(DEFAULT_FRAME_RATE).build(); format = format.buildUpon().setFrameRate(DEFAULT_FRAME_RATE).build();
} }
...@@ -236,17 +225,12 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { ...@@ -236,17 +225,12 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
checkArgument(format.height <= format.width); checkArgument(format.height <= format.width);
checkArgument(format.rotationDegrees == 0); checkArgument(format.rotationDegrees == 0);
checkNotNull(format.sampleMimeType); checkNotNull(format.sampleMimeType);
checkArgument(!allowedMimeTypes.isEmpty());
checkStateNotNull(videoEncoderSelector); checkStateNotNull(videoEncoderSelector);
@Nullable @Nullable
VideoEncoderQueryResult encoderAndClosestFormatSupport = VideoEncoderQueryResult encoderAndClosestFormatSupport =
findEncoderWithClosestSupportedFormat( findEncoderWithClosestSupportedFormat(
format, format, requestedVideoEncoderSettings, videoEncoderSelector, enableFallback);
requestedVideoEncoderSettings,
videoEncoderSelector,
allowedMimeTypes,
enableFallback);
if (encoderAndClosestFormatSupport == null) { if (encoderAndClosestFormatSupport == null) {
throw createTransformationException(format); throw createTransformationException(format);
...@@ -310,13 +294,14 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { ...@@ -310,13 +294,14 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
MediaFormatUtil.maybeSetColorInfo(mediaFormat, encoderSupportedFormat.colorInfo); MediaFormatUtil.maybeSetColorInfo(mediaFormat, encoderSupportedFormat.colorInfo);
if (Util.SDK_INT >= 31 && ColorInfo.isTransferHdr(format.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) if (EncoderUtil.getSupportedColorFormats(encoderInfo, mimeType)
.contains(MediaCodecInfo.CodecCapabilities.COLOR_Format32bitABGR2101010)) { .contains(MediaCodecInfo.CodecCapabilities.COLOR_Format32bitABGR2101010)) {
mediaFormat.setInteger( mediaFormat.setInteger(
MediaFormat.KEY_COLOR_FORMAT, MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_Format32bitABGR2101010); MediaCodecInfo.CodecCapabilities.COLOR_Format32bitABGR2101010);
} else { } else {
throw createTransformationException(format); throw createTransformationException(format, ERROR_CODE_HDR_ENCODING_UNSUPPORTED);
} }
} else { } else {
mediaFormat.setInteger( mediaFormat.setInteger(
...@@ -380,15 +365,8 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { ...@@ -380,15 +365,8 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
Format requestedFormat, Format requestedFormat,
VideoEncoderSettings videoEncoderSettings, VideoEncoderSettings videoEncoderSettings,
EncoderSelector encoderSelector, EncoderSelector encoderSelector,
List<String> allowedMimeTypes,
boolean enableFallback) { boolean enableFallback) {
String requestedMimeType = requestedFormat.sampleMimeType; String mimeType = checkNotNull(requestedFormat.sampleMimeType);
@Nullable
String mimeType = findFallbackMimeType(encoderSelector, requestedMimeType, allowedMimeTypes);
if (mimeType == null || (!enableFallback && !requestedMimeType.equals(mimeType))) {
return null;
}
ImmutableList<MediaCodecInfo> filteredEncoderInfos = ImmutableList<MediaCodecInfo> filteredEncoderInfos =
encoderSelector.selectEncoderInfos(mimeType); encoderSelector.selectEncoderInfos(mimeType);
if (filteredEncoderInfos.isEmpty()) { if (filteredEncoderInfos.isEmpty()) {
...@@ -679,36 +657,6 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { ...@@ -679,36 +657,6 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
} }
/** /**
* 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<String> 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<String> allowedMimeTypes) {
return !encoderSelector.selectEncoderInfos(mimeType).isEmpty()
&& allowedMimeTypes.contains(mimeType);
}
/**
* Computes the video bit rate using the Kush Gauge. * Computes the video bit rate using the Kush Gauge.
* *
* <p>{@code kushGaugeBitrate = height * width * frameRate * 0.07 * motionFactor}. * <p>{@code kushGaugeBitrate = height * width * frameRate * 0.07 * motionFactor}.
...@@ -730,12 +678,19 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory { ...@@ -730,12 +678,19 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
@RequiresNonNull("#1.sampleMimeType") @RequiresNonNull("#1.sampleMimeType")
private static TransformationException createTransformationException(Format format) { 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( return TransformationException.createForCodec(
new IllegalArgumentException("The requested encoding format is not supported."), new IllegalArgumentException("The requested encoding format is not supported."),
MimeTypes.isVideo(format.sampleMimeType), MimeTypes.isVideo(format.sampleMimeType),
/* isDecoder= */ false, /* isDecoder= */ false,
format, format,
/* mediaCodecName= */ null, /* mediaCodecName= */ null,
TransformationException.ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED); errorCode);
} }
} }
...@@ -69,8 +69,8 @@ public final class EncoderUtil { ...@@ -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 * Returns the names of encoders that support HDR editing for the given {@code mimeType} and
* if the format is unknown or not supported for HDR encoding. * {@code ColorInfo}, or an empty list if the format is unknown or not supported for HDR encoding.
*/ */
public static ImmutableList<String> getSupportedEncoderNamesForHdrEditing( public static ImmutableList<String> getSupportedEncoderNamesForHdrEditing(
String mimeType, @Nullable ColorInfo colorInfo) { String mimeType, @Nullable ColorInfo colorInfo) {
...@@ -78,13 +78,12 @@ public final class EncoderUtil { ...@@ -78,13 +78,12 @@ public final class EncoderUtil {
return ImmutableList.of(); return ImmutableList.of();
} }
@ColorTransfer int colorTransfer = colorInfo.colorTransfer; ImmutableList<MediaCodecInfo> encoders = getSupportedEncoders(mimeType);
ImmutableList<Integer> profiles = getCodecProfilesForHdrFormat(mimeType, colorTransfer); ImmutableList<Integer> allowedColorProfiles =
ImmutableList.Builder<String> resultBuilder = ImmutableList.builder(); getCodecProfilesForHdrFormat(mimeType, colorInfo.colorTransfer);
ImmutableList<MediaCodecInfo> mediaCodecInfos = ImmutableList.Builder<String> resultBuilder = new ImmutableList.Builder<>();
EncoderSelector.DEFAULT.selectEncoderInfos(mimeType); for (int i = 0; i < encoders.size(); i++) {
for (int i = 0; i < mediaCodecInfos.size(); i++) { MediaCodecInfo mediaCodecInfo = encoders.get(i);
MediaCodecInfo mediaCodecInfo = mediaCodecInfos.get(i);
if (mediaCodecInfo.isAlias() if (mediaCodecInfo.isAlias()
|| !isFeatureSupported( || !isFeatureSupported(
mediaCodecInfo, mimeType, MediaCodecInfo.CodecCapabilities.FEATURE_HdrEditing)) { mediaCodecInfo, mimeType, MediaCodecInfo.CodecCapabilities.FEATURE_HdrEditing)) {
...@@ -92,7 +91,7 @@ public final class EncoderUtil { ...@@ -92,7 +91,7 @@ public final class EncoderUtil {
} }
for (MediaCodecInfo.CodecProfileLevel codecProfileLevel : for (MediaCodecInfo.CodecProfileLevel codecProfileLevel :
mediaCodecInfo.getCapabilitiesForType(mimeType).profileLevels) { mediaCodecInfo.getCapabilitiesForType(mimeType).profileLevels) {
if (profiles.contains(codecProfileLevel.profile)) { if (allowedColorProfiles.contains(codecProfileLevel.profile)) {
resultBuilder.add(mediaCodecInfo.getName()); resultBuilder.add(mediaCodecInfo.getName());
} }
} }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package com.google.android.exoplayer2.transformer; package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; 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 static com.google.android.exoplayer2.util.Util.SDK_INT;
import android.content.Context; import android.content.Context;
...@@ -36,6 +37,7 @@ import com.google.android.exoplayer2.util.FrameInfo; ...@@ -36,6 +37,7 @@ import com.google.android.exoplayer2.util.FrameInfo;
import com.google.android.exoplayer2.util.FrameProcessingException; import com.google.android.exoplayer2.util.FrameProcessingException;
import com.google.android.exoplayer2.util.FrameProcessor; import com.google.android.exoplayer2.util.FrameProcessor;
import com.google.android.exoplayer2.util.Log; 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.SurfaceInfo;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.ColorInfo; import com.google.android.exoplayer2.video.ColorInfo;
...@@ -369,7 +371,7 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -369,7 +371,7 @@ import org.checkerframework.dataflow.qual.Pure;
private final Codec.EncoderFactory encoderFactory; private final Codec.EncoderFactory encoderFactory;
private final Format inputFormat; private final Format inputFormat;
private final List<String> allowedOutputMimeTypes; private final List<String> muxerSupportedMimeTypes;
private final TransformationRequest transformationRequest; private final TransformationRequest transformationRequest;
private final FallbackListener fallbackListener; private final FallbackListener fallbackListener;
private final String requestedOutputMimeType; private final String requestedOutputMimeType;
...@@ -384,12 +386,12 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -384,12 +386,12 @@ import org.checkerframework.dataflow.qual.Pure;
public EncoderWrapper( public EncoderWrapper(
Codec.EncoderFactory encoderFactory, Codec.EncoderFactory encoderFactory,
Format inputFormat, Format inputFormat,
List<String> allowedOutputMimeTypes, List<String> muxerSupportedMimeTypes,
TransformationRequest transformationRequest, TransformationRequest transformationRequest,
FallbackListener fallbackListener) { FallbackListener fallbackListener) {
this.encoderFactory = encoderFactory; this.encoderFactory = encoderFactory;
this.inputFormat = inputFormat; this.inputFormat = inputFormat;
this.allowedOutputMimeTypes = allowedOutputMimeTypes; this.muxerSupportedMimeTypes = muxerSupportedMimeTypes;
this.transformationRequest = transformationRequest; this.transformationRequest = transformationRequest;
this.fallbackListener = fallbackListener; this.fallbackListener = fallbackListener;
...@@ -454,20 +456,31 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -454,20 +456,31 @@ import org.checkerframework.dataflow.qual.Pure;
.setColorInfo(getSupportedInputColor()) .setColorInfo(getSupportedInputColor())
.build(); .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 = encoder =
encoderFactory.createForVideoEncoding(requestedEncoderFormat, allowedOutputMimeTypes); encoderFactory.createForVideoEncoding(
requestedEncoderFormat.buildUpon().setSampleMimeType(supportedMimeType).build());
Format encoderSupportedFormat = encoder.getConfigurationFormat(); Format encoderSupportedFormat = encoder.getConfigurationFormat();
if (ColorInfo.isTransferHdr(requestedEncoderFormat.colorInfo)) { checkState(supportedMimeType.equals(encoderSupportedFormat.sampleMimeType));
if (!requestedOutputMimeType.equals(encoderSupportedFormat.sampleMimeType)) { if (ColorInfo.isTransferHdr(requestedEncoderFormat.colorInfo)
throw createEncodingException( && !supportedEncoderNamesForHdrEditing.contains(encoder.getName())) {
new IllegalStateException("MIME type fallback unsupported with HDR editing"), throw createEncodingException(
encoderSupportedFormat); new IllegalStateException("Selected encoder doesn't support HDR editing"),
} else if (!supportedEncoderNamesForHdrEditing.contains(encoder.getName())) { encoderSupportedFormat);
throw createEncodingException(
new IllegalStateException("Selected encoder doesn't support HDR editing"),
encoderSupportedFormat);
}
} }
boolean isInputToneMapped = boolean isInputToneMapped =
ColorInfo.isTransferHdr(inputFormat.colorInfo) ColorInfo.isTransferHdr(inputFormat.colorInfo)
...@@ -553,5 +566,48 @@ import org.checkerframework.dataflow.qual.Pure; ...@@ -553,5 +566,48 @@ import org.checkerframework.dataflow.qual.Pure;
checkNotNull(encoder).getName(), checkNotNull(encoder).getName(),
TransformationException.ERROR_CODE_ENCODING_FAILED); 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<String> muxerSupportedMimeTypes,
@Nullable ColorInfo colorInfo) {
ImmutableList<String> mimeTypesToCheck =
new ImmutableList.Builder<String>()
.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<String> 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();
}
} }
} }
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package com.google.android.exoplayer2.transformer; package com.google.android.exoplayer2.transformer;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext; 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 com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
...@@ -84,9 +85,7 @@ public class DefaultEncoderFactoryTest { ...@@ -84,9 +85,7 @@ public class DefaultEncoderFactoryTest {
Format actualVideoFormat = Format actualVideoFormat =
new DefaultEncoderFactory.Builder(context) new DefaultEncoderFactory.Builder(context)
.build() .build()
.createForVideoEncoding( .createForVideoEncoding(requestedVideoFormat)
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat(); .getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264);
...@@ -97,22 +96,15 @@ public class DefaultEncoderFactoryTest { ...@@ -97,22 +96,15 @@ public class DefaultEncoderFactoryTest {
} }
@Test @Test
public void createForVideoEncoding_withFallbackOnAndUnsupportedMimeType_configuresEncoder() public void createForVideoEncoding_withFallbackOnAndUnsupportedMimeType_throws() {
throws Exception {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H265, 1920, 1080, 30); Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H265, 1920, 1080, 30);
Format actualVideoFormat = DefaultEncoderFactory encoderFactory = new DefaultEncoderFactory.Builder(context).build();
new DefaultEncoderFactory.Builder(context)
.build()
.createForVideoEncoding(
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); TransformationException transformationException =
assertThat(actualVideoFormat.width).isEqualTo(1920); assertThrows(
assertThat(actualVideoFormat.height).isEqualTo(1080); TransformationException.class,
// 1920 * 1080 * 30 * 0.07 * 2. () -> encoderFactory.createForVideoEncoding(requestedVideoFormat));
assertThat(actualVideoFormat.averageBitrate).isEqualTo(8_709_120); assertThat(transformationException.errorCode).isEqualTo(ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED);
} }
@Test @Test
...@@ -122,9 +114,7 @@ public class DefaultEncoderFactoryTest { ...@@ -122,9 +114,7 @@ public class DefaultEncoderFactoryTest {
Format actualVideoFormat = Format actualVideoFormat =
new DefaultEncoderFactory.Builder(context) new DefaultEncoderFactory.Builder(context)
.build() .build()
.createForVideoEncoding( .createForVideoEncoding(requestedVideoFormat)
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat(); .getConfigurationFormat();
assertThat(actualVideoFormat.width).isEqualTo(1920); assertThat(actualVideoFormat.width).isEqualTo(1920);
...@@ -142,9 +132,7 @@ public class DefaultEncoderFactoryTest { ...@@ -142,9 +132,7 @@ public class DefaultEncoderFactoryTest {
new DefaultEncoderFactory.Builder(context) new DefaultEncoderFactory.Builder(context)
.setRequestedVideoEncoderSettings(VideoEncoderSettings.DEFAULT) .setRequestedVideoEncoderSettings(VideoEncoderSettings.DEFAULT)
.build() .build()
.createForVideoEncoding( .createForVideoEncoding(requestedVideoFormat)
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat(); .getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264);
...@@ -161,9 +149,7 @@ public class DefaultEncoderFactoryTest { ...@@ -161,9 +149,7 @@ public class DefaultEncoderFactoryTest {
Format actualVideoFormat = Format actualVideoFormat =
new DefaultEncoderFactory.Builder(context) new DefaultEncoderFactory.Builder(context)
.build() .build()
.createForVideoEncoding( .createForVideoEncoding(requestedVideoFormat)
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat(); .getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264);
...@@ -185,9 +171,7 @@ public class DefaultEncoderFactoryTest { ...@@ -185,9 +171,7 @@ public class DefaultEncoderFactoryTest {
.setRequestedVideoEncoderSettings( .setRequestedVideoEncoderSettings(
new VideoEncoderSettings.Builder().setEnableHighQualityTargeting(true).build()) new VideoEncoderSettings.Builder().setEnableHighQualityTargeting(true).build())
.build() .build()
.createForVideoEncoding( .createForVideoEncoding(requestedVideoFormat)
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat(); .getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264);
...@@ -210,9 +194,7 @@ public class DefaultEncoderFactoryTest { ...@@ -210,9 +194,7 @@ public class DefaultEncoderFactoryTest {
.setRequestedVideoEncoderSettings( .setRequestedVideoEncoderSettings(
new VideoEncoderSettings.Builder().setBitrate(10_000_000).build()) new VideoEncoderSettings.Builder().setBitrate(10_000_000).build())
.build() .build()
.createForVideoEncoding( .createForVideoEncoding(requestedVideoFormat)
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264))
.getConfigurationFormat(); .getConfigurationFormat();
assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264); assertThat(actualVideoFormat.sampleMimeType).isEqualTo(MimeTypes.VIDEO_H264);
...@@ -230,9 +212,7 @@ public class DefaultEncoderFactoryTest { ...@@ -230,9 +212,7 @@ public class DefaultEncoderFactoryTest {
Codec videoEncoder = Codec videoEncoder =
new DefaultEncoderFactory.Builder(context) new DefaultEncoderFactory.Builder(context)
.build() .build()
.createForVideoEncoding( .createForVideoEncoding(requestedVideoFormat);
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264));
assertThat(videoEncoder).isInstanceOf(DefaultCodec.class); assertThat(videoEncoder).isInstanceOf(DefaultCodec.class);
MediaFormat configurationMediaFormat = MediaFormat configurationMediaFormat =
...@@ -245,36 +225,15 @@ public class DefaultEncoderFactoryTest { ...@@ -245,36 +225,15 @@ public class DefaultEncoderFactoryTest {
} }
@Test @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() { public void createForVideoEncoding_withNoAvailableEncoderFromEncoderSelector_throws() {
Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30); Format requestedVideoFormat = createVideoFormat(MimeTypes.VIDEO_H264, 1920, 1080, 30);
assertThrows( assertThrows(
TransformationException.class, TransformationException.class,
() -> () ->
new DefaultEncoderFactory.Builder(context) new DefaultEncoderFactory.Builder(context)
.setVideoEncoderSelector(mimeType -> ImmutableList.of()) .setVideoEncoderSelector((mimeType) -> ImmutableList.of())
.build() .build()
.createForVideoEncoding( .createForVideoEncoding(requestedVideoFormat));
requestedVideoFormat,
/* allowedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264)));
} }
private static Format createVideoFormat(String mimeType, int width, int height, int frameRate) { private static Format createVideoFormat(String mimeType, int width, int height, int frameRate) {
......
...@@ -23,11 +23,14 @@ import android.media.MediaFormat; ...@@ -23,11 +23,14 @@ import android.media.MediaFormat;
import android.util.Size; import android.util.Size;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4; 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.util.MimeTypes;
import com.google.android.exoplayer2.video.ColorInfo;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.MediaCodecInfoBuilder; import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodecList; import org.robolectric.shadows.ShadowMediaCodecList;
...@@ -116,4 +119,25 @@ public class EncoderUtilTest { ...@@ -116,4 +119,25 @@ public class EncoderUtilTest {
assertThat(closestSupportedResolution.getWidth()).isEqualTo(1920); assertThat(closestSupportedResolution.getWidth()).isEqualTo(1920);
assertThat(closestSupportedResolution.getHeight()).isEqualTo(1080); 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();
}
} }
...@@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat; ...@@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.net.Uri; import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
...@@ -34,6 +36,8 @@ import java.util.List; ...@@ -34,6 +36,8 @@ import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodecList;
/** Unit tests for {@link VideoTranscodingSamplePipeline.EncoderWrapper}. */ /** Unit tests for {@link VideoTranscodingSamplePipeline.EncoderWrapper}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
...@@ -49,14 +53,15 @@ public final class VideoEncoderWrapperTest { ...@@ -49,14 +53,15 @@ public final class VideoEncoderWrapperTest {
private final VideoTranscodingSamplePipeline.EncoderWrapper encoderWrapper = private final VideoTranscodingSamplePipeline.EncoderWrapper encoderWrapper =
new VideoTranscodingSamplePipeline.EncoderWrapper( new VideoTranscodingSamplePipeline.EncoderWrapper(
fakeEncoderFactory, fakeEncoderFactory,
/* inputFormat= */ new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H265).build(), /* inputFormat= */ new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build(),
/* allowedOutputMimeTypes= */ ImmutableList.of(), /* muxerSupportedMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_H264),
emptyTransformationRequest, emptyTransformationRequest,
fallbackListener); fallbackListener);
@Before @Before
public void registerTrack() { public void registerTrack() {
fallbackListener.registerTrack(); fallbackListener.registerTrack();
createShadowH264Encoder();
} }
@Test @Test
...@@ -111,6 +116,39 @@ public final class VideoEncoderWrapperTest { ...@@ -111,6 +116,39 @@ public final class VideoEncoderWrapperTest {
assertThat(surfaceInfo.height).isEqualTo(fallbackHeight); 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 static class FakeVideoEncoderFactory implements Codec.EncoderFactory {
private int fallbackWidth; private int fallbackWidth;
...@@ -132,7 +170,7 @@ public final class VideoEncoderWrapperTest { ...@@ -132,7 +170,7 @@ public final class VideoEncoderWrapperTest {
} }
@Override @Override
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes) { public Codec createForVideoEncoding(Format format) {
Codec mockEncoder = mock(Codec.class); Codec mockEncoder = mock(Codec.class);
if (fallbackWidth != C.LENGTH_UNSET) { if (fallbackWidth != C.LENGTH_UNSET) {
format = format.buildUpon().setWidth(fallbackWidth).build(); format = format.buildUpon().setWidth(fallbackWidth).build();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment