summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java')
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java397
1 files changed, 397 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
new file mode 100644
index 0000000000..7e302a7c3d
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
@@ -0,0 +1,397 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.util;
+
+import android.annotation.SuppressLint;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.os.Build;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import org.mozilla.gecko.annotation.WrapForJNI;
+
+public final class HardwareCodecCapabilityUtils {
+ private static final String LOGTAG = "HardwareCodecCapability";
+
+ // List of supported HW VP8 encoders.
+ private static final String[] supportedVp8HwEncCodecPrefixes = {"OMX.qcom.", "OMX.Intel."};
+ // List of supported HW VP8 decoders.
+ private static final String[] supportedVp8HwDecCodecPrefixes = {
+ "OMX.qcom.", "OMX.Nvidia.", "OMX.Exynos.", "c2.exynos", "OMX.Intel."
+ };
+ private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8";
+ // List of supported HW VP9 codecs.
+ private static final String[] supportedVp9HwCodecPrefixes = {
+ "OMX.qcom.", "OMX.Exynos.", "c2.exynos"
+ };
+ private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
+ // List of supported HW H.264 codecs.
+ private static final String[] supportedH264HwCodecPrefixes = {
+ "OMX.qcom.",
+ "OMX.Intel.",
+ "OMX.Exynos.",
+ "c2.exynos",
+ "OMX.Nvidia",
+ "OMX.SEC.",
+ "OMX.IMG.",
+ "OMX.k3.",
+ "OMX.hisi.",
+ "OMX.TI.",
+ "OMX.MTK."
+ };
+ private static final String H264_MIME_TYPE = "video/avc";
+ // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
+ // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
+ private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
+ // Allowable color formats supported by codec - in order of preference.
+ private static final int[] supportedColorList = {
+ CodecCapabilities.COLOR_FormatYUV420Planar,
+ CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
+ CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
+ COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
+ };
+ private static final int COLOR_FORMAT_NOT_SUPPORTED = -1;
+ private static final String[] adaptivePlaybackBlacklist = {
+ "GT-I9300", // S3 (I9300 / I9300I)
+ "SCH-I535", // S3
+ "SGH-T999", // S3 (T-Mobile)
+ "SAMSUNG-SGH-T999", // S3 (T-Mobile)
+ "SGH-M919", // S4
+ "GT-I9505", // S4
+ "GT-I9515", // S4
+ "SCH-R970", // S4
+ "SGH-I337", // S4
+ "SPH-L720", // S4 (Sprint)
+ "SAMSUNG-SGH-I337", // S4
+ "GT-I9195", // S4 Mini
+ "300E5EV/300E4EV/270E5EV/270E4EV/2470EV/2470EE",
+ "LG-D605" // LG Optimus L9 II
+ };
+
+ private static MediaCodecInfo[] getCodecListWithOldAPI() {
+ int numCodecs = 0;
+ try {
+ numCodecs = MediaCodecList.getCodecCount();
+ } catch (final RuntimeException e) {
+ Log.e(LOGTAG, "Failed to retrieve media codec count", e);
+ return new MediaCodecInfo[numCodecs];
+ }
+
+ final MediaCodecInfo[] codecList = new MediaCodecInfo[numCodecs];
+
+ for (int i = 0; i < numCodecs; ++i) {
+ final MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+ codecList[i] = info;
+ }
+
+ return codecList;
+ }
+
+ // Return list of all codecs (decode + encode).
+ private static MediaCodecInfo[] getCodecList() {
+ final MediaCodecInfo[] codecList;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ codecList = getCodecListWithOldAPI();
+ } else {
+ final MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ codecList = list.getCodecInfos();
+ }
+ return codecList;
+ }
+
+ // Return list of all decoders.
+ private static MediaCodecInfo[] getDecoderInfos() {
+ final ArrayList<MediaCodecInfo> decoderList = new ArrayList<MediaCodecInfo>();
+ for (final MediaCodecInfo info : getCodecList()) {
+ if (!info.isEncoder()) {
+ decoderList.add(info);
+ }
+ }
+ return decoderList.toArray(new MediaCodecInfo[0]);
+ }
+
+ // Return list of all encoders.
+ private static MediaCodecInfo[] getEncoderInfos() {
+ final ArrayList<MediaCodecInfo> encoderList = new ArrayList<MediaCodecInfo>();
+ for (final MediaCodecInfo info : getCodecList()) {
+ if (info.isEncoder()) {
+ encoderList.add(info);
+ }
+ }
+ return encoderList.toArray(new MediaCodecInfo[0]);
+ }
+
+ // Return list of all decoder-supported MIME types without distinguishing
+ // between SW/HW support.
+ @WrapForJNI
+ public static String[] getDecoderSupportedMimeTypes() {
+ final Set<String> mimeTypes = new HashSet<>();
+ for (final MediaCodecInfo info : getDecoderInfos()) {
+ mimeTypes.addAll(Arrays.asList(info.getSupportedTypes()));
+ }
+ return mimeTypes.toArray(new String[0]);
+ }
+
+ // Return list of all decoder-supported MIME types, each prefixed with
+ // either SW or HW indicating software or hardware support.
+ @WrapForJNI
+ public static String[] getDecoderSupportedMimeTypesWithAccelInfo() {
+ final Set<String> mimeTypes = new HashSet<>();
+ final String[] hwPrefixes = getAllSupportedHWCodecPrefixes(false);
+
+ for (final MediaCodecInfo info : getDecoderInfos()) {
+ final String[] supportedTypes = info.getSupportedTypes();
+ for (final String mimeType : info.getSupportedTypes()) {
+ boolean isHwPrefix = false;
+ for (final String prefix : hwPrefixes) {
+ if (info.getName().startsWith(prefix)) {
+ isHwPrefix = true;
+ break;
+ }
+ }
+ if (!isHwPrefix) {
+ mimeTypes.add("SW " + mimeType);
+ continue;
+ }
+ final CodecCapabilities caps = info.getCapabilitiesForType(mimeType);
+ if (getSupportsYUV420orNV12(caps) != COLOR_FORMAT_NOT_SUPPORTED) {
+ mimeTypes.add("HW " + mimeType);
+ }
+ }
+ }
+ for (final String typeit : mimeTypes) {
+ Log.d(LOGTAG, "MIME support: " + typeit);
+ }
+ return mimeTypes.toArray(new String[0]);
+ }
+
+ public static boolean checkSupportsAdaptivePlayback(
+ final MediaCodec aCodec, final String aMimeType) {
+ // isFeatureSupported supported on API level >= 19.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT
+ || isAdaptivePlaybackBlacklisted(aMimeType)) {
+ return false;
+ }
+
+ try {
+ final MediaCodecInfo info = aCodec.getCodecInfo();
+ final MediaCodecInfo.CodecCapabilities capabilities = info.getCapabilitiesForType(aMimeType);
+ return capabilities != null
+ && capabilities.isFeatureSupported(
+ MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback);
+ } catch (final IllegalArgumentException e) {
+ Log.e(LOGTAG, "Retrieve codec information failed", e);
+ }
+ return false;
+ }
+
+ // See Bug1360626 and
+ // https://codereview.chromium.org/1869103002 for details.
+ private static boolean isAdaptivePlaybackBlacklisted(final String aMimeType) {
+ Log.d(LOGTAG, "The device ModelID is " + Build.MODEL);
+ if (!aMimeType.equals("video/avc") && !aMimeType.equals("video/avc1")) {
+ return false;
+ }
+
+ if (!Build.MANUFACTURER.toLowerCase(Locale.ROOT).equals("samsung")) {
+ return false;
+ }
+
+ for (final String model : adaptivePlaybackBlacklist) {
+ if (Build.MODEL.startsWith(model)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Check if a given MIME Type has HW decode or encode support.
+ public static boolean getHWCodecCapability(final String aMimeType, final boolean aIsEncoder) {
+ if (Build.VERSION.SDK_INT >= 20) {
+ for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
+ final MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+ if (info.isEncoder() != aIsEncoder) {
+ continue;
+ }
+ String name = null;
+ for (final String mimeType : info.getSupportedTypes()) {
+ if (mimeType.equals(aMimeType)) {
+ name = info.getName();
+ break;
+ }
+ }
+ if (name == null) {
+ continue; // No HW support in this codec; try the next one.
+ }
+ Log.d(LOGTAG, "Found candidate" + (aIsEncoder ? " encoder " : " decoder ") + name);
+
+ // Check if this is supported codec.
+ final String[] hwList = getSupportedHWCodecPrefixes(aMimeType, aIsEncoder);
+ if (hwList == null) {
+ continue;
+ }
+ boolean supportedCodec = false;
+ for (final String codecPrefix : hwList) {
+ if (name.startsWith(codecPrefix)) {
+ supportedCodec = true;
+ break;
+ }
+ }
+ if (!supportedCodec) {
+ continue;
+ }
+
+ // Check if codec supports either yuv420 or nv12.
+ final CodecCapabilities capabilities = info.getCapabilitiesForType(aMimeType);
+ for (final int colorFormat : capabilities.colorFormats) {
+ Log.v(LOGTAG, " Color: 0x" + Integer.toHexString(colorFormat));
+ }
+ if (Build.VERSION.SDK_INT >= 24) {
+ for (final MediaCodecInfo.CodecProfileLevel pl : capabilities.profileLevels) {
+ Log.v(
+ LOGTAG,
+ " Profile: 0x"
+ + Integer.toHexString(pl.profile)
+ + "/Level=0x"
+ + Integer.toHexString(pl.level));
+ }
+ }
+ final int codecColorFormat = getSupportsYUV420orNV12(capabilities);
+ if (codecColorFormat != COLOR_FORMAT_NOT_SUPPORTED) {
+ Log.d(
+ LOGTAG,
+ "Found target"
+ + (aIsEncoder ? " encoder " : " decoder ")
+ + name
+ + ". Color: 0x"
+ + Integer.toHexString(codecColorFormat));
+ return true;
+ }
+ }
+ }
+ // No HW codec.
+ return false;
+ }
+
+ // Check if codec supports YUV420 or NV12
+ private static int getSupportsYUV420orNV12(final CodecCapabilities aCodecCaps) {
+ for (final int supportedColorFormat : supportedColorList) {
+ for (final int codecColorFormat : aCodecCaps.colorFormats) {
+ if (codecColorFormat == supportedColorFormat) {
+ return codecColorFormat;
+ }
+ }
+ }
+ return COLOR_FORMAT_NOT_SUPPORTED;
+ }
+
+ // Check if MIME type string has HW prefix (encode or decode, VP8, VP9, and H264)
+ private static String[] getSupportedHWCodecPrefixes(
+ final String aMimeType, final boolean aIsEncoder) {
+ if (aMimeType.equals(H264_MIME_TYPE)) {
+ return supportedH264HwCodecPrefixes;
+ }
+ if (aMimeType.equals(VP9_MIME_TYPE)) {
+ return supportedVp9HwCodecPrefixes;
+ }
+ if (aMimeType.equals(VP8_MIME_TYPE)) {
+ return aIsEncoder ? supportedVp8HwEncCodecPrefixes : supportedVp8HwDecCodecPrefixes;
+ }
+ return null;
+ }
+
+ // Return list of HW codec prefixes (encode or decode, VP8, VP9, and H264)
+ private static String[] getAllSupportedHWCodecPrefixes(final boolean aIsEncoder) {
+ final Set<String> prefixes = new HashSet<>();
+ final String[] mimeTypes = {H264_MIME_TYPE, VP8_MIME_TYPE, VP9_MIME_TYPE};
+ for (final String mt : mimeTypes) {
+ prefixes.addAll(Arrays.asList(getSupportedHWCodecPrefixes(mt, aIsEncoder)));
+ }
+ return prefixes.toArray(new String[0]);
+ }
+
+ @WrapForJNI
+ public static boolean hasHWVP8(final boolean aIsEncoder) {
+ return getHWCodecCapability(VP8_MIME_TYPE, aIsEncoder);
+ }
+
+ @WrapForJNI
+ public static boolean hasHWVP9(final boolean aIsEncoder) {
+ return getHWCodecCapability(VP9_MIME_TYPE, aIsEncoder);
+ }
+
+ @WrapForJNI
+ public static boolean hasHWH264(final boolean aIsEncoder) {
+ return getHWCodecCapability(H264_MIME_TYPE, aIsEncoder);
+ }
+
+ @WrapForJNI(calledFrom = "gecko")
+ public static boolean hasHWH264() {
+ return getHWCodecCapability(H264_MIME_TYPE, true)
+ && getHWCodecCapability(H264_MIME_TYPE, false);
+ }
+
+ @WrapForJNI
+ @SuppressLint("NewApi")
+ public static boolean decodes10Bit(final String aMimeType) {
+ if (Build.VERSION.SDK_INT < 24) {
+ // Be conservative when we cannot get supported profile.
+ return false;
+ }
+
+ final MediaCodecList codecs = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ for (final MediaCodecInfo info : codecs.getCodecInfos()) {
+ if (info.isEncoder()) {
+ continue;
+ }
+ try {
+ for (final MediaCodecInfo.CodecProfileLevel pl :
+ info.getCapabilitiesForType(aMimeType).profileLevels) {
+ if ((aMimeType.equals(H264_MIME_TYPE)
+ && pl.profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh10)
+ || (aMimeType.equals(VP9_MIME_TYPE) && is10BitVP9Profile(pl.profile))) {
+ return true;
+ }
+ }
+ } catch (final IllegalArgumentException e) {
+ // Type not supported.
+ continue;
+ }
+ }
+
+ return false;
+ }
+
+ @SuppressLint("NewApi")
+ private static boolean is10BitVP9Profile(final int profile) {
+ if (Build.VERSION.SDK_INT < 24) {
+ // Be conservative when we cannot get supported profile.
+ return false;
+ }
+
+ if ((profile == MediaCodecInfo.CodecProfileLevel.VP9Profile2)
+ || (profile == MediaCodecInfo.CodecProfileLevel.VP9Profile3)
+ || (profile == MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR)
+ || (profile == MediaCodecInfo.CodecProfileLevel.VP9Profile3HDR)) {
+ return true;
+ }
+
+ if (Build.VERSION.SDK_INT >= 29
+ && ((profile == MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR10Plus)
+ || (profile == MediaCodecInfo.CodecProfileLevel.VP9Profile3HDR10Plus))) {
+ return true;
+ }
+
+ return false;
+ }
+}