-
Notifications
You must be signed in to change notification settings - Fork 468
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fallback to legacy sizerate check if CDD H264 PerfPoint check fails
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: google/ExoPlayer#10898 Issue: #693 Issue: #966 PiperOrigin-RevId: 609740128
- Loading branch information
1 parent
284d17c
commit 23a301f
Showing
3 changed files
with
192 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
183 changes: 183 additions & 0 deletions
183
...java/androidx/media3/exoplayer/mediacodec/MediaCodecPerformancePointCoverageProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
/* | ||
* 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 androidx.media3.exoplayer.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 androidx.media3.common.Format; | ||
import androidx.media3.common.MimeTypes; | ||
import androidx.media3.common.util.UnstableApi; | ||
import androidx.media3.common.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. */ | ||
@UnstableApi | ||
/* 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; | ||
} | ||
} | ||
} |