Improve MIME type fallback logic.

- The MIME type should ideally default to HEVC if there is an encoder for it.
- Next, check if AVC is supported.
- If there is no encoder for AVC, then we should pick an encoder in the list of
existing encoders instead of abandoning the transformation.

PiperOrigin-RevId: 425900638
This commit is contained in:
claincly 2022-02-02 17:14:56 +00:00 committed by Ian Baker
parent 0de79209c4
commit 7e873b5121

View File

@ -18,6 +18,7 @@ package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull;
import static androidx.media3.common.util.Util.SDK_INT; import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.transformer.CodecFactoryUtil.createCodec; import static androidx.media3.transformer.CodecFactoryUtil.createCodec;
@ -41,8 +42,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@UnstableApi @UnstableApi
public final class DefaultEncoderFactory implements Codec.EncoderFactory { public final class DefaultEncoderFactory implements Codec.EncoderFactory {
// TODO(b/214973843): Add option to disable fallback. // TODO(b/214973843): Add option to disable fallback.
// TODO(b/210591626): Fall back adaptively to H265 if possible.
private static final String DEFAULT_FALLBACK_MIME_TYPE = MimeTypes.VIDEO_H264;
private static final int DEFAULT_COLOR_FORMAT = private static final int DEFAULT_COLOR_FORMAT =
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
private static final int DEFAULT_FRAME_RATE = 60; private static final int DEFAULT_FRAME_RATE = 60;
@ -147,22 +146,15 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
@Nullable @Nullable
private static Pair<MediaCodecInfo, Format> findEncoderWithClosestFormatSupport( private static Pair<MediaCodecInfo, Format> findEncoderWithClosestFormatSupport(
Format requestedFormat, EncoderSelector encoderSelector, List<String> allowedMimeTypes) { Format requestedFormat, EncoderSelector encoderSelector, List<String> allowedMimeTypes) {
String mimeType = requestedFormat.sampleMimeType; @Nullable
String mimeType =
// TODO(b/210591626) Improve MIME type selection. findFallbackMimeType(encoderSelector, requestedFormat.sampleMimeType, allowedMimeTypes);
List<MediaCodecInfo> encodersForMimeType = encoderSelector.selectEncoderInfos(mimeType); if (mimeType == null) {
if (!allowedMimeTypes.contains(mimeType) || encodersForMimeType.isEmpty()) { return null;
mimeType =
allowedMimeTypes.contains(DEFAULT_FALLBACK_MIME_TYPE)
? DEFAULT_FALLBACK_MIME_TYPE
: allowedMimeTypes.get(0);
encodersForMimeType = encoderSelector.selectEncoderInfos(mimeType);
if (encodersForMimeType.isEmpty()) {
return null;
}
} }
String finalMimeType = mimeType; List<MediaCodecInfo> encodersForMimeType = encoderSelector.selectEncoderInfos(mimeType);
checkState(!encodersForMimeType.isEmpty());
ImmutableList<MediaCodecInfo> filteredEncoders = ImmutableList<MediaCodecInfo> filteredEncoders =
filterEncoders( filterEncoders(
encodersForMimeType, encodersForMimeType,
@ -170,7 +162,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
@Nullable @Nullable
Pair<Integer, Integer> closestSupportedResolution = Pair<Integer, Integer> closestSupportedResolution =
EncoderUtil.getClosestSupportedResolution( EncoderUtil.getClosestSupportedResolution(
encoderInfo, finalMimeType, requestedFormat.width, requestedFormat.height); encoderInfo, mimeType, requestedFormat.width, requestedFormat.height);
if (closestSupportedResolution == null) { if (closestSupportedResolution == null) {
// Drops encoder. // Drops encoder.
return Integer.MAX_VALUE; return Integer.MAX_VALUE;
@ -186,10 +178,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
Pair<Integer, Integer> finalResolution = Pair<Integer, Integer> finalResolution =
checkNotNull( checkNotNull(
EncoderUtil.getClosestSupportedResolution( EncoderUtil.getClosestSupportedResolution(
filteredEncoders.get(0), filteredEncoders.get(0), mimeType, requestedFormat.width, requestedFormat.height));
finalMimeType,
requestedFormat.width,
requestedFormat.height));
int requestedBitrate = int requestedBitrate =
requestedFormat.averageBitrate == Format.NO_VALUE requestedFormat.averageBitrate == Format.NO_VALUE
@ -205,8 +194,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
filteredEncoders, filteredEncoders,
/* cost= */ (encoderInfo) -> { /* cost= */ (encoderInfo) -> {
int achievableBitrate = int achievableBitrate =
EncoderUtil.getClosestSupportedBitrate( EncoderUtil.getClosestSupportedBitrate(encoderInfo, mimeType, requestedBitrate);
encoderInfo, finalMimeType, requestedBitrate);
return abs(achievableBitrate - requestedBitrate); return abs(achievableBitrate - requestedBitrate);
}); });
if (filteredEncoders.isEmpty()) { if (filteredEncoders.isEmpty()) {
@ -218,10 +206,10 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
Pair<Integer, Integer> profileLevel = MediaCodecUtil.getCodecProfileAndLevel(requestedFormat); Pair<Integer, Integer> profileLevel = MediaCodecUtil.getCodecProfileAndLevel(requestedFormat);
@Nullable String codecs = null; @Nullable String codecs = null;
if (profileLevel != null if (profileLevel != null
&& requestedFormat.sampleMimeType.equals(finalMimeType) && requestedFormat.sampleMimeType.equals(mimeType)
&& EncoderUtil.isProfileLevelSupported( && EncoderUtil.isProfileLevelSupported(
pickedEncoder, pickedEncoder,
finalMimeType, mimeType,
/* profile= */ profileLevel.first, /* profile= */ profileLevel.first,
/* level= */ profileLevel.second)) { /* level= */ profileLevel.second)) {
codecs = requestedFormat.codecs; codecs = requestedFormat.codecs;
@ -230,7 +218,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
Format encoderSupportedFormat = Format encoderSupportedFormat =
requestedFormat requestedFormat
.buildUpon() .buildUpon()
.setSampleMimeType(finalMimeType) .setSampleMimeType(mimeType)
.setCodecs(codecs) .setCodecs(codecs)
.setWidth(finalResolution.first) .setWidth(finalResolution.first)
.setHeight(finalResolution.second) .setHeight(finalResolution.second)
@ -239,8 +227,7 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
? requestedFormat.frameRate ? requestedFormat.frameRate
: DEFAULT_FRAME_RATE) : DEFAULT_FRAME_RATE)
.setAverageBitrate( .setAverageBitrate(
EncoderUtil.getClosestSupportedBitrate( EncoderUtil.getClosestSupportedBitrate(pickedEncoder, mimeType, requestedBitrate))
pickedEncoder, finalMimeType, requestedBitrate))
.build(); .build();
return Pair.create(pickedEncoder, encoderSupportedFormat); return Pair.create(pickedEncoder, encoderSupportedFormat);
} }
@ -279,6 +266,32 @@ public final class DefaultEncoderFactory implements Codec.EncoderFactory {
return ImmutableList.copyOf(filteredEncoders); return ImmutableList.copyOf(filteredEncoders);
} }
@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. */
private static int getSuggestedBitrate(int width, int height, float frameRate) { private static int getSuggestedBitrate(int width, int height, float frameRate) {
// TODO(b/210591626) Implement bitrate estimation. // TODO(b/210591626) Implement bitrate estimation.