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:
claincly 2022-11-29 13:49:15 +00:00 committed by Rohit Singh
parent 04f031d168
commit 532e0ffdc3
10 changed files with 190 additions and 163 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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,

View File

@ -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() {

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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();
}
}
}

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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();