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
This commit is contained in:
parent
04f031d168
commit
532e0ffdc3
@ -478,9 +478,8 @@ public final class AndroidTestUtil {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException {
|
||||
return encoderFactory.createForVideoEncoding(format, allowedMimeTypes);
|
||||
public Codec createForVideoEncoding(Format format) throws TransformationException {
|
||||
return encoderFactory.createForVideoEncoding(format);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -454,9 +454,8 @@ public class TransformerAndroidTestRunner {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForVideoEncoding(Format format, List<String> 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;
|
||||
}
|
||||
|
@ -146,8 +146,7 @@ public class TransformerEndToEndTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Codec createForVideoEncoding(Format format, List<String> allowedMimeTypes)
|
||||
throws TransformationException {
|
||||
public Codec createForVideoEncoding(Format format) throws TransformationException {
|
||||
throw TransformationException.createForCodec(
|
||||
new IllegalArgumentException(),
|
||||
/* isVideo= */ true,
|
||||
|
@ -87,23 +87,22 @@ public interface Codec {
|
||||
/**
|
||||
* Returns a {@link Codec} for video encoding.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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<String> 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() {
|
||||
|
@ -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<String> 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<String> 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<MediaCodecInfo> 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<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.
|
||||
*
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<String> getSupportedEncoderNamesForHdrEditing(
|
||||
String mimeType, @Nullable ColorInfo colorInfo) {
|
||||
@ -78,13 +78,12 @@ public final class EncoderUtil {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
@ColorTransfer int colorTransfer = colorInfo.colorTransfer;
|
||||
ImmutableList<Integer> profiles = getCodecProfilesForHdrFormat(mimeType, colorTransfer);
|
||||
ImmutableList.Builder<String> resultBuilder = ImmutableList.builder();
|
||||
ImmutableList<MediaCodecInfo> mediaCodecInfos =
|
||||
EncoderSelector.DEFAULT.selectEncoderInfos(mimeType);
|
||||
for (int i = 0; i < mediaCodecInfos.size(); i++) {
|
||||
MediaCodecInfo mediaCodecInfo = mediaCodecInfos.get(i);
|
||||
ImmutableList<MediaCodecInfo> encoders = getSupportedEncoders(mimeType);
|
||||
ImmutableList<Integer> allowedColorProfiles =
|
||||
getCodecProfilesForHdrFormat(mimeType, colorInfo.colorTransfer);
|
||||
ImmutableList.Builder<String> 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());
|
||||
}
|
||||
}
|
||||
|
@ -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<String> allowedOutputMimeTypes;
|
||||
private final List<String> 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<String> allowedOutputMimeTypes,
|
||||
List<String> 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<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 @@
|
||||
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) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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<String> allowedMimeTypes) {
|
||||
public Codec createForVideoEncoding(Format format) {
|
||||
Codec mockEncoder = mock(Codec.class);
|
||||
if (fallbackWidth != C.LENGTH_UNSET) {
|
||||
format = format.buildUpon().setWidth(fallbackWidth).build();
|
||||
|
Loading…
x
Reference in New Issue
Block a user