diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java index 745cdc5474..72733a90e9 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java @@ -28,6 +28,7 @@ import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO; import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION; import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_FLUSH; import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_RECONFIGURATION; +import static java.lang.annotation.ElementType.TYPE_USE; import android.graphics.Point; import android.media.MediaCodec; @@ -35,7 +36,10 @@ import android.media.MediaCodecInfo.AudioCapabilities; import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCodecInfo.VideoCapabilities; +import android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint; import android.util.Pair; +import androidx.annotation.DoNotInline; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; @@ -48,6 +52,11 @@ import androidx.media3.common.util.Util; import androidx.media3.exoplayer.DecoderReuseEvaluation; import androidx.media3.exoplayer.DecoderReuseEvaluation.DecoderDiscardReasons; import androidx.media3.exoplayer.DecoderReuseEvaluation.DecoderReuseResult; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; /** Information about a {@link MediaCodec} for a given mime type. */ @SuppressWarnings("InlinedApi") @@ -482,8 +491,6 @@ public final class MediaCodecInfo { /** * Whether the decoder supports video with a given width, height and frame rate. * - *

Must not be called if the device SDK version is less than 21. - * * @param width Width in pixels. * @param height Height in pixels. * @param frameRate Optional frame rate in frames per second. Ignored if set to {@link @@ -501,14 +508,28 @@ public final class MediaCodecInfo { logNoSupport("sizeAndRate.vCaps"); return false; } + + if (Util.SDK_INT >= 29) { + @PerformancePointCoverageResult + int evaluation = + Api29.areResolutionAndFrameRateCovered(videoCapabilities, width, height, frameRate); + if (evaluation == COVERAGE_RESULT_YES) { + return true; + } else if (evaluation == COVERAGE_RESULT_NO) { + logNoSupport("sizeAndRate.cover, " + width + "x" + height + "@" + frameRate); + return false; + } + // COVERAGE_RESULT_NO_EMPTY_LIST falls through to API 21+ code below + } + if (!areSizeAndRateSupportedV21(videoCapabilities, width, height, frameRate)) { if (width >= height || !needsRotatedVerticalResolutionWorkaround(name) || !areSizeAndRateSupportedV21(videoCapabilities, height, width, frameRate)) { - logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate); + logNoSupport("sizeAndRate.support, " + width + "x" + height + "@" + frameRate); return false; } - logAssumedSupport("sizeAndRate.rotated, " + width + "x" + height + "x" + frameRate); + logAssumedSupport("sizeAndRate.rotated, " + width + "x" + height + "@" + frameRate); } return true; } @@ -844,4 +865,47 @@ public final class MediaCodecInfo { && CodecProfileLevel.HEVCProfileMain10 == profile && ("sailfish".equals(Util.DEVICE) || "marlin".equals(Util.DEVICE)); } + + /** Possible outcomes of evaluating PerformancePoint coverage */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef({COVERAGE_RESULT_YES, COVERAGE_RESULT_NO, COVERAGE_RESULT_NO_EMPTY_LIST}) + private @interface PerformancePointCoverageResult {} + + /** The decoder has a PerformancePoint that covers the resolution and frame rate */ + private static final int COVERAGE_RESULT_YES = 2; + /** + * The decoder has at least one PerformancePoint, but none of them cover the resolution and frame + * rate + */ + private static final int COVERAGE_RESULT_NO = 1; + /** The VideoCapabilities does not contain any PerformancePoints */ + private static final int COVERAGE_RESULT_NO_EMPTY_LIST = 0; + + @RequiresApi(29) + private static final class Api29 { + @DoNotInline + public static @PerformancePointCoverageResult int areResolutionAndFrameRateCovered( + VideoCapabilities videoCapabilities, int width, int height, double frameRate) { + List performancePointList = + videoCapabilities.getSupportedPerformancePoints(); + if (performancePointList == null || performancePointList.isEmpty()) { + return COVERAGE_RESULT_NO_EMPTY_LIST; + } + + // Round frame rate down to to avoid situations where a range check in + // covers fails due to slightly exceeding the limits for a standard format + // (e.g., 1080p at 30 fps). [Internal ref: b/134706676] + PerformancePoint targetPerformancePoint = + new PerformancePoint(width, height, (int) frameRate); + + for (int i = 0; i < performancePointList.size(); i++) { + if (performancePointList.get(i).covers(targetPerformancePoint)) { + return COVERAGE_RESULT_YES; + } + } + return COVERAGE_RESULT_NO; + } + } }