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:
parent
0de79209c4
commit
7e873b5121
@ -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()) {
|
|
||||||
mimeType =
|
|
||||||
allowedMimeTypes.contains(DEFAULT_FALLBACK_MIME_TYPE)
|
|
||||||
? DEFAULT_FALLBACK_MIME_TYPE
|
|
||||||
: allowedMimeTypes.get(0);
|
|
||||||
encodersForMimeType = encoderSelector.selectEncoderInfos(mimeType);
|
|
||||||
if (encodersForMimeType.isEmpty()) {
|
|
||||||
return null;
|
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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user