diff --git a/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java b/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java index 04469dedc3..f409cfbd07 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/RendererCapabilities.java @@ -140,13 +140,13 @@ public interface RendererCapabilities { /** A mask to apply to {@link Capabilities} to obtain {@link DecoderSupport} only. */ int MODE_SUPPORT_MASK = 0b11 << 7; /** - * The renderer will use a decoder for fallback mimetype if possible as format's MIME type is - * unsupported + * The format's MIME type is unsupported and the renderer may use a decoder for a fallback MIME + * type. */ int DECODER_SUPPORT_FALLBACK_MIMETYPE = 0b10 << 7; /** The renderer is able to use the primary decoder for the format's MIME type. */ 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; /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java index 61230b2d0c..d60fde32fc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java @@ -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. * @return Whether the decoder may support decoding the given {@code format}. @@ -254,7 +255,7 @@ public final class MediaCodecInfo { return false; } - if (!isCodecProfileAndLevelSupported(format)) { + if (!isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ true)) { 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) { return mimeType.equals(format.sampleMimeType) || mimeType.equals(MediaCodecUtil.getAlternativeCodecMimeType(format)); } - private boolean isCodecProfileAndLevelSupported(Format format) { - if (format.codecs == null) { - return true; - } + private boolean isCodecProfileAndLevelSupported( + Format format, boolean checkPerformanceCapabilities) { Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format); if (codecProfileAndLevel == null) { // 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) { if (profileLevel.profile == profile - && profileLevel.level >= level + && (profileLevel.level >= level || !checkPerformanceCapabilities) && !needsProfileExcludedWorkaround(mimeType, profile)) { return true; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 0f6bbb3120..161166a1cf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -1111,6 +1111,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } 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.codecOperatingRate = codecOperatingRate; codecInputFormat = inputFormat; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index 9ef5475bb1..16583004fc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -188,22 +188,15 @@ public final class MediaCodecUtil { } /** - * Returns a copy of the provided decoder list sorted such that decoders with format support are - * listed first. The returned list is modifiable for convenience. + * Returns a copy of the provided decoder list sorted such that decoders with functional format + * support are listed first. The returned list is modifiable for convenience. */ @CheckResult public static List getDecoderInfosSortedByFormatSupport( List decoderInfos, Format format) { decoderInfos = new ArrayList<>(decoderInfos); sortByScore( - decoderInfos, - decoderInfo -> { - try { - return decoderInfo.isFormatSupported(format) ? 1 : 0; - } catch (DecoderQueryException e) { - return -1; - } - }); + decoderInfos, decoderInfo -> decoderInfo.isFormatFunctionallySupported(format) ? 1 : 0); return decoderInfos; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java index fc2fa8fc2e..f8fca4c1bb 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/video/MediaCodecVideoRendererTest.java @@ -57,6 +57,7 @@ import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.common.collect.ImmutableList; import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; @@ -83,6 +84,32 @@ public class MediaCodecVideoRendererTest { .setHeight(1080) .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 Surface surface; private MediaCodecVideoRenderer mediaCodecVideoRenderer; @@ -710,6 +737,100 @@ public class MediaCodecVideoRendererTest { .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 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 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 public void getCodecMaxInputSize_videoH263() { MediaCodecInfo codecInfo = createMediaCodecInfo(MimeTypes.VIDEO_H263);