Skip to content

Commit

Permalink
Fallback to legacy sizerate check if CDD H264 PerfPoint check fails
Browse files Browse the repository at this point in the history
Some devices supporting Performance Points for decoder coverage are missing coverage over the CDD requirements for H264. For these cases ExoPlayer should fall back to legacy resolution and frame rate support checks. If there is a stream evaluated as a PerformancePointCoverageResult of COVERAGE_RESULT_NO, then ExoPlayer checks for coverage of the 720p H264 CDD requirement.

Issue: #10898

Issue: androidx/media#693

Issue: androidx/media#966
PiperOrigin-RevId: 609740128
  • Loading branch information
microkatz authored and Copybara-Service committed Feb 23, 2024
1 parent 68d0fe5 commit 0515104
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 88 deletions.
Expand Up @@ -28,18 +28,16 @@
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;
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;
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<PerformancePoint> 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<PerformancePoint> 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;
}
}
}
@@ -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 <a
* href="https://developer.android.com/guide/topics/media/media3/getting-started/migration-guide">the
* migration guide</a> 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<PerformancePoint> 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<MediaCodecInfo> 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<PerformancePoint> 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<PerformancePoint> 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;
}
}
}

0 comments on commit 0515104

Please sign in to comment.