Changed decoder list sort to order by functional support of format

Added new method to check if codec just functionally supports a format. Changed getDecoderInfosSortedByFormatSupport to use new function to order by functional support. This allows decoders that only support functionally and are more preferred by the MediaCodecSelector to keep their preferred position in the sorted list.

UnitTests included
-Two MediaCodecVideoRenderer tests that verify hw vs sw does not have an effect on sort of the decoder list, it is only based on functional support

Issue: google/ExoPlayer#10604
PiperOrigin-RevId: 487779284
This commit is contained in:
michaelkatz 2022-11-11 11:23:41 +00:00 committed by Ian Baker
parent a34fdcf168
commit 1eb8a6b36e
5 changed files with 152 additions and 20 deletions

View File

@ -140,13 +140,13 @@ public interface RendererCapabilities {
/** A mask to apply to {@link Capabilities} to obtain {@link DecoderSupport} only. */ /** A mask to apply to {@link Capabilities} to obtain {@link DecoderSupport} only. */
int MODE_SUPPORT_MASK = 0b11 << 7; int MODE_SUPPORT_MASK = 0b11 << 7;
/** /**
* The renderer will use a decoder for fallback mimetype if possible as format's MIME type is * The format's MIME type is unsupported and the renderer may use a decoder for a fallback MIME
* unsupported * type.
*/ */
int DECODER_SUPPORT_FALLBACK_MIMETYPE = 0b10 << 7; int DECODER_SUPPORT_FALLBACK_MIMETYPE = 0b10 << 7;
/** The renderer is able to use the primary decoder for the format's MIME type. */ /** The renderer is able to use the primary decoder for the format's MIME type. */
int DECODER_SUPPORT_PRIMARY = 0b1 << 7; int DECODER_SUPPORT_PRIMARY = 0b1 << 7;
/** The renderer will use a fallback decoder. */ /** The format exceeds the primary decoder's capabilities but is supported by fallback decoder */
int DECODER_SUPPORT_FALLBACK = 0; int DECODER_SUPPORT_FALLBACK = 0;
/** /**

View File

@ -243,7 +243,8 @@ public final class MediaCodecInfo {
} }
/** /**
* Returns whether the decoder may support decoding the given {@code format}. * Returns whether the decoder may support decoding the given {@code format} both functionally and
* performantly.
* *
* @param format The input media format. * @param format The input media format.
* @return Whether the decoder may support decoding the given {@code format}. * @return Whether the decoder may support decoding the given {@code format}.
@ -254,7 +255,7 @@ public final class MediaCodecInfo {
return false; return false;
} }
if (!isCodecProfileAndLevelSupported(format)) { if (!isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ true)) {
return false; return false;
} }
@ -281,15 +282,24 @@ public final class MediaCodecInfo {
} }
} }
/**
* Returns whether the decoder may functionally support decoding the given {@code format}.
*
* @param format The input media format.
* @return Whether the decoder may functionally support decoding the given {@code format}.
*/
public boolean isFormatFunctionallySupported(Format format) {
return isSampleMimeTypeSupported(format)
&& isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ false);
}
private boolean isSampleMimeTypeSupported(Format format) { private boolean isSampleMimeTypeSupported(Format format) {
return mimeType.equals(format.sampleMimeType) return mimeType.equals(format.sampleMimeType)
|| mimeType.equals(MediaCodecUtil.getAlternativeCodecMimeType(format)); || mimeType.equals(MediaCodecUtil.getAlternativeCodecMimeType(format));
} }
private boolean isCodecProfileAndLevelSupported(Format format) { private boolean isCodecProfileAndLevelSupported(
if (format.codecs == null) { Format format, boolean checkPerformanceCapabilities) {
return true;
}
Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
if (codecProfileAndLevel == null) { if (codecProfileAndLevel == null) {
// If we don't know any better, we assume that the profile and level are supported. // If we don't know any better, we assume that the profile and level are supported.
@ -325,7 +335,7 @@ public final class MediaCodecInfo {
for (CodecProfileLevel profileLevel : profileLevels) { for (CodecProfileLevel profileLevel : profileLevels) {
if (profileLevel.profile == profile if (profileLevel.profile == profile
&& profileLevel.level >= level && (profileLevel.level >= level || !checkPerformanceCapabilities)
&& !needsProfileExcludedWorkaround(mimeType, profile)) { && !needsProfileExcludedWorkaround(mimeType, profile)) {
return true; return true;
} }

View File

@ -1111,6 +1111,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
codecInitializedTimestamp = SystemClock.elapsedRealtime(); codecInitializedTimestamp = SystemClock.elapsedRealtime();
if (!codecInfo.isFormatSupported(inputFormat)) {
Log.w(
TAG,
Util.formatInvariant(
"Format exceeds selected codec's capabilities [%s, %s]",
Format.toLogString(inputFormat), codecName));
}
this.codecInfo = codecInfo; this.codecInfo = codecInfo;
this.codecOperatingRate = codecOperatingRate; this.codecOperatingRate = codecOperatingRate;
codecInputFormat = inputFormat; codecInputFormat = inputFormat;

View File

@ -188,22 +188,15 @@ public final class MediaCodecUtil {
} }
/** /**
* Returns a copy of the provided decoder list sorted such that decoders with format support are * Returns a copy of the provided decoder list sorted such that decoders with functional format
* listed first. The returned list is modifiable for convenience. * support are listed first. The returned list is modifiable for convenience.
*/ */
@CheckResult @CheckResult
public static List<MediaCodecInfo> getDecoderInfosSortedByFormatSupport( public static List<MediaCodecInfo> getDecoderInfosSortedByFormatSupport(
List<MediaCodecInfo> decoderInfos, Format format) { List<MediaCodecInfo> decoderInfos, Format format) {
decoderInfos = new ArrayList<>(decoderInfos); decoderInfos = new ArrayList<>(decoderInfos);
sortByScore( sortByScore(
decoderInfos, decoderInfos, decoderInfo -> decoderInfo.isFormatFunctionallySupported(format) ? 1 : 0);
decoderInfo -> {
try {
return decoderInfo.isFormatSupported(format) ? 1 : 0;
} catch (DecoderQueryException e) {
return -1;
}
});
return decoderInfos; return decoderInfos;
} }

View File

@ -57,6 +57,7 @@ import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -83,6 +84,32 @@ public class MediaCodecVideoRendererTest {
.setHeight(1080) .setHeight(1080)
.build(); .build();
private static final MediaCodecInfo H264_PROFILE8_LEVEL4_HW_MEDIA_CODEC_INFO =
MediaCodecInfo.newInstance(
/* name= */ "h264-codec-hw",
/* mimeType= */ MimeTypes.VIDEO_H264,
/* codecMimeType= */ MimeTypes.VIDEO_H264,
/* capabilities= */ createCodecCapabilities(
CodecProfileLevel.AVCProfileHigh, CodecProfileLevel.AVCLevel4),
/* hardwareAccelerated= */ true,
/* softwareOnly= */ false,
/* vendor= */ false,
/* forceDisableAdaptive= */ false,
/* forceSecure= */ false);
private static final MediaCodecInfo H264_PROFILE8_LEVEL5_SW_MEDIA_CODEC_INFO =
MediaCodecInfo.newInstance(
/* name= */ "h264-codec-sw",
/* mimeType= */ MimeTypes.VIDEO_H264,
/* codecMimeType= */ MimeTypes.VIDEO_H264,
/* capabilities= */ createCodecCapabilities(
CodecProfileLevel.AVCProfileHigh, CodecProfileLevel.AVCLevel5),
/* hardwareAccelerated= */ false,
/* softwareOnly= */ true,
/* vendor= */ false,
/* forceDisableAdaptive= */ false,
/* forceSecure= */ false);
private Looper testMainLooper; private Looper testMainLooper;
private Surface surface; private Surface surface;
private MediaCodecVideoRenderer mediaCodecVideoRenderer; private MediaCodecVideoRenderer mediaCodecVideoRenderer;
@ -710,6 +737,100 @@ public class MediaCodecVideoRendererTest {
.isEqualTo(RendererCapabilities.DECODER_SUPPORT_PRIMARY); .isEqualTo(RendererCapabilities.DECODER_SUPPORT_PRIMARY);
} }
@Test
public void getDecoderInfo_withNonPerformantHardwareDecoder_returnsHardwareDecoderFirst()
throws Exception {
// AVC Format, Profile: 8, Level: 8192
Format avcFormat =
new Format.Builder()
.setSampleMimeType(MimeTypes.VIDEO_H264)
.setCodecs("avc1.64002a")
.build();
// Provide hardware and software AVC decoders
MediaCodecSelector mediaCodecSelector =
(mimeType, requiresSecureDecoder, requiresTunnelingDecoder) -> {
if (!mimeType.equals(MimeTypes.VIDEO_H264)) {
return ImmutableList.of();
}
// Hardware decoder supports above format functionally but not performantly as
// it supports MIME type & Profile but not Level
// Software decoder supports format functionally and peformantly as it supports
// MIME type, Profile, and Level(assuming resolution/frame rate support too)
return ImmutableList.of(
H264_PROFILE8_LEVEL4_HW_MEDIA_CODEC_INFO, H264_PROFILE8_LEVEL5_SW_MEDIA_CODEC_INFO);
};
MediaCodecVideoRenderer renderer =
new MediaCodecVideoRenderer(
ApplicationProvider.getApplicationContext(),
mediaCodecSelector,
/* allowedJoiningTimeMs= */ 0,
/* eventHandler= */ new Handler(testMainLooper),
/* eventListener= */ eventListener,
/* maxDroppedFramesToNotify= */ 1);
renderer.init(/* index= */ 0, PlayerId.UNSET);
List<MediaCodecInfo> mediaCodecInfoList =
renderer.getDecoderInfos(mediaCodecSelector, avcFormat, false);
@Capabilities int capabilities = renderer.supportsFormat(avcFormat);
assertThat(mediaCodecInfoList).hasSize(2);
assertThat(mediaCodecInfoList.get(0).hardwareAccelerated).isTrue();
assertThat(RendererCapabilities.getFormatSupport(capabilities)).isEqualTo(C.FORMAT_HANDLED);
assertThat(RendererCapabilities.getDecoderSupport(capabilities))
.isEqualTo(RendererCapabilities.DECODER_SUPPORT_FALLBACK);
}
@Test
public void getDecoderInfo_softwareDecoderPreferred_returnsSoftwareDecoderFirst()
throws Exception {
// AVC Format, Profile: 8, Level: 8192
Format avcFormat =
new Format.Builder()
.setSampleMimeType(MimeTypes.VIDEO_H264)
.setCodecs("avc1.64002a")
.build();
// Provide software and hardware AVC decoders
MediaCodecSelector mediaCodecSelector =
(mimeType, requiresSecureDecoder, requiresTunnelingDecoder) -> {
if (!mimeType.equals(MimeTypes.VIDEO_H264)) {
return ImmutableList.of();
}
// Hardware decoder supports above format functionally but not performantly as
// it supports MIME type & Profile but not Level
// Software decoder supports format functionally and peformantly as it supports
// MIME type, Profile, and Level(assuming resolution/frame rate support too)
return ImmutableList.of(
H264_PROFILE8_LEVEL5_SW_MEDIA_CODEC_INFO, H264_PROFILE8_LEVEL4_HW_MEDIA_CODEC_INFO);
};
MediaCodecVideoRenderer renderer =
new MediaCodecVideoRenderer(
ApplicationProvider.getApplicationContext(),
mediaCodecSelector,
/* allowedJoiningTimeMs= */ 0,
/* eventHandler= */ new Handler(testMainLooper),
/* eventListener= */ eventListener,
/* maxDroppedFramesToNotify= */ 1);
renderer.init(/* index= */ 0, PlayerId.UNSET);
List<MediaCodecInfo> mediaCodecInfoList =
renderer.getDecoderInfos(mediaCodecSelector, avcFormat, false);
@Capabilities int capabilities = renderer.supportsFormat(avcFormat);
assertThat(mediaCodecInfoList).hasSize(2);
assertThat(mediaCodecInfoList.get(0).hardwareAccelerated).isFalse();
assertThat(RendererCapabilities.getFormatSupport(capabilities)).isEqualTo(C.FORMAT_HANDLED);
assertThat(RendererCapabilities.getDecoderSupport(capabilities))
.isEqualTo(RendererCapabilities.DECODER_SUPPORT_PRIMARY);
}
private static CodecCapabilities createCodecCapabilities(int profile, int level) {
CodecCapabilities capabilities = new CodecCapabilities();
capabilities.profileLevels = new CodecProfileLevel[] {new CodecProfileLevel()};
capabilities.profileLevels[0].profile = profile;
capabilities.profileLevels[0].level = level;
return capabilities;
}
@Test @Test
public void getCodecMaxInputSize_videoH263() { public void getCodecMaxInputSize_videoH263() {
MediaCodecInfo codecInfo = createMediaCodecInfo(MimeTypes.VIDEO_H263); MediaCodecInfo codecInfo = createMediaCodecInfo(MimeTypes.VIDEO_H263);