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 3f8a86f936e..b27c6ed1ba1 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 @@ -28,7 +28,8 @@ import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_FLUSH; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_RECONFIGURATION; -import static java.lang.annotation.ElementType.TYPE_USE; +import static com.google.android.exoplayer2.mediacodec.MediaCodecPerformancePointCoverageProvider.COVERAGE_RESULT_NO; +import static com.google.android.exoplayer2.mediacodec.MediaCodecPerformancePointCoverageProvider.COVERAGE_RESULT_YES; import android.graphics.Point; import android.media.MediaCodec; @@ -36,10 +37,7 @@ 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; @@ -51,11 +49,6 @@ import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; -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. @@ -526,10 +519,10 @@ public boolean isVideoSizeAndRateSupportedV21(int width, int height, double fram } if (Util.SDK_INT >= 29) { - @PerformancePointCoverageResult + @MediaCodecPerformancePointCoverageProvider.PerformancePointCoverageResult int evaluation = - Api29.areResolutionAndFrameRateCovered( - videoCapabilities, mimeType, width, height, frameRate); + MediaCodecPerformancePointCoverageProvider.areResolutionAndFrameRateCovered( + videoCapabilities, width, height, frameRate); if (evaluation == COVERAGE_RESULT_YES) { return true; } else if (evaluation == COVERAGE_RESULT_NO) { @@ -878,80 +871,4 @@ private static boolean needsProfileExcludedWorkaround(String mimeType, int profi && 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_NO_PERFORMANCE_POINTS_UNSUPPORTED, - COVERAGE_RESULT_NO, - COVERAGE_RESULT_YES - }) - private @interface PerformancePointCoverageResult {} - - /** - * The VideoCapabilities does not contain any PerformancePoints or its PerformancePoints do not - * cover CDD requirements. - */ - private static final int COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED = 0; - - /** - * The decoder has at least one PerformancePoint, but none cover the resolution and frame rate. - */ - private static final int COVERAGE_RESULT_NO = 1; - - /** The decoder has a PerformancePoint that covers the resolution and frame rate. */ - private static final int COVERAGE_RESULT_YES = 2; - - @RequiresApi(29) - private static final class Api29 { - @DoNotInline - public static @PerformancePointCoverageResult int areResolutionAndFrameRateCovered( - VideoCapabilities videoCapabilities, - String mimeType, - int width, - int height, - double frameRate) { - List performancePointList = - videoCapabilities.getSupportedPerformancePoints(); - if (performancePointList == null || performancePointList.isEmpty()) { - return COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED; - } - - // 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); - - @PerformancePointCoverageResult - int performancePointCoverageResult = - evaluatePerformancePointCoverage(performancePointList, targetPerformancePoint); - - if (performancePointCoverageResult == COVERAGE_RESULT_NO - && mimeType.equals(MimeTypes.VIDEO_H264)) { - if (evaluatePerformancePointCoverage( - performancePointList, - new PerformancePoint(/* width= */ 1280, /* height= */ 720, /* frameRate= */ 60)) - != COVERAGE_RESULT_YES) { - // See https://github.com/google/ExoPlayer/issues/10898, - // https://github.com/androidx/media/issues/693 and [internal ref: b/267324685]. - return COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED; - } - } - - return performancePointCoverageResult; - } - - private static @PerformancePointCoverageResult int evaluatePerformancePointCoverage( - List performancePointList, PerformancePoint targetPerformancePoint) { - for (int i = 0; i < performancePointList.size(); i++) { - if (performancePointList.get(i).covers(targetPerformancePoint)) { - return COVERAGE_RESULT_YES; - } - } - return COVERAGE_RESULT_NO; - } - } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecPerformancePointCoverageProvider.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecPerformancePointCoverageProvider.java new file mode 100644 index 00000000000..41ca6ccf4dd --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecPerformancePointCoverageProvider.java @@ -0,0 +1,189 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.mediacodec; + +import static java.lang.annotation.ElementType.TYPE_USE; + +import android.media.MediaCodecInfo.VideoCapabilities; +import android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint; +import androidx.annotation.DoNotInline; +import androidx.annotation.IntDef; +import androidx.annotation.RequiresApi; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.Util; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** + * Utility class checking media codec support through PerformancePoints. + * + * @deprecated com.google.android.exoplayer2 is deprecated. Please migrate to androidx.media3 (which + * contains the same ExoPlayer code). See the + * migration guide for more details, including a script to help with the migration. + */ +@Deprecated +/* package */ final class MediaCodecPerformancePointCoverageProvider { + + /** + * Whether if the device provides a PerformancePoints and coverage results should be ignored as + * the PerformancePoints do not cover CDD requirements. + */ + @SuppressWarnings("NonFinalStaticField") + private static @MonotonicNonNull Boolean shouldIgnorePerformancePoints; + + private MediaCodecPerformancePointCoverageProvider() {} + + /** Possible outcomes of evaluating {@link PerformancePoint} coverage. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef({ + COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED, + COVERAGE_RESULT_NO, + COVERAGE_RESULT_YES + }) + @interface PerformancePointCoverageResult {} + + /** + * The {@link VideoCapabilities} do not contain any valid {@linkplain PerformancePoint + * PerformancePoints}. + */ + /* package */ static final int COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED = 0; + + /** + * The decoder has at least one PerformancePoint, but none cover the resolution and frame rate. + */ + /* package */ static final int COVERAGE_RESULT_NO = 1; + + /** The decoder has a PerformancePoint that covers the resolution and frame rate. */ + /* package */ static final int COVERAGE_RESULT_YES = 2; + + /** + * This method returns if a decoder's {@link VideoCapabilities} cover a resolution and frame rate + * with its {@link PerformancePoint} list. + * + * @param videoCapabilities A decoder's {@link VideoCapabilities} + * @param width Width in pixels. + * @param height Height in pixels. + * @param frameRate Optional frame rate in frames per second. Ignored if set to {@link + * Format#NO_VALUE} or any value less than or equal to 0. + * @return {@link #COVERAGE_RESULT_YES} if the {@link VideoCapabilities} has a {@link + * PerformancePoint} list that covers the resolution and frame rate or {@link + * #COVERAGE_RESULT_NO} if the list does not provide coverage. {@link + * #COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED} is returned if the {@link + * VideoCapabilities} does not contain a list of valid {@code PerformancePoints} + */ + public static @PerformancePointCoverageResult int areResolutionAndFrameRateCovered( + VideoCapabilities videoCapabilities, int width, int height, double frameRate) { + if (Util.SDK_INT < 29 + || (shouldIgnorePerformancePoints != null && shouldIgnorePerformancePoints)) { + return COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED; + } + + return Api29.areResolutionAndFrameRateCovered(videoCapabilities, width, height, frameRate); + } + + @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_PERFORMANCE_POINTS_UNSUPPORTED; + } + + // 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); + + @PerformancePointCoverageResult + int performancePointCoverageResult = + evaluatePerformancePointCoverage(performancePointList, targetPerformancePoint); + + if (performancePointCoverageResult == COVERAGE_RESULT_NO + && shouldIgnorePerformancePoints == null) { + // See https://github.com/google/ExoPlayer/issues/10898, + // https://github.com/androidx/media/issues/693, + // https://github.com/androidx/media/issues/966 and [internal ref: b/267324685]. + shouldIgnorePerformancePoints = shouldIgnorePerformancePoints(); + if (shouldIgnorePerformancePoints) { + return COVERAGE_RESULT_NO_PERFORMANCE_POINTS_UNSUPPORTED; + } + } + + return performancePointCoverageResult; + } + + /** + * Checks if the CDD-requirement to support H264 720p at 60 fps is covered by PerformancePoints. + */ + private static boolean shouldIgnorePerformancePoints() { + try { + Format formatH264 = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build(); + // Null check required to pass RequiresNonNull annotation on getDecoderInfosSoftMatch. + if (formatH264.sampleMimeType != null) { + List decoderInfos = + MediaCodecUtil.getDecoderInfosSoftMatch( + MediaCodecSelector.DEFAULT, + formatH264, + /* requiresSecureDecoder= */ false, + /* requiresTunnelingDecoder= */ false); + for (int i = 0; i < decoderInfos.size(); i++) { + if (decoderInfos.get(i).capabilities != null + && decoderInfos.get(i).capabilities.getVideoCapabilities() != null) { + List performancePointListH264 = + decoderInfos + .get(i) + .capabilities + .getVideoCapabilities() + .getSupportedPerformancePoints(); + if (performancePointListH264 != null && !performancePointListH264.isEmpty()) { + PerformancePoint targetPerformancePointH264 = + new PerformancePoint(/* width= */ 1280, /* height= */ 720, /* frameRate= */ 60); + return evaluatePerformancePointCoverage( + performancePointListH264, targetPerformancePointH264) + == COVERAGE_RESULT_NO; + } + } + } + } + return true; + } catch (MediaCodecUtil.DecoderQueryException ignored) { + return true; + } + } + + private static @PerformancePointCoverageResult int evaluatePerformancePointCoverage( + List performancePointList, PerformancePoint targetPerformancePoint) { + for (int i = 0; i < performancePointList.size(); i++) { + if (performancePointList.get(i).covers(targetPerformancePoint)) { + return COVERAGE_RESULT_YES; + } + } + return COVERAGE_RESULT_NO; + } + } +}