1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 2 * * This Source Code Form is subject to the terms of the Mozilla Public 3 * * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 7 package org.mozilla.gecko.util; 8 9 import org.mozilla.gecko.annotation.WrapForJNI; 10 11 import android.media.MediaCodec; 12 import android.media.MediaCodecInfo; 13 import android.media.MediaCodecInfo.CodecCapabilities; 14 import android.media.MediaCodecList; 15 import android.os.Build; 16 import android.util.Log; 17 18 import java.util.Arrays; 19 import java.util.HashSet; 20 import java.util.Locale; 21 import java.util.Set; 22 23 public final class HardwareCodecCapabilityUtils { 24 private static final String LOGTAG = "HardwareCodecCapability"; 25 26 // List of supported HW VP8 encoders. 27 private static final String[] supportedVp8HwEncCodecPrefixes = { 28 "OMX.qcom.", "OMX.Intel." 29 }; 30 // List of supported HW VP8 decoders. 31 private static final String[] supportedVp8HwDecCodecPrefixes = { 32 "OMX.qcom.", "OMX.Nvidia.", "OMX.Exynos.", "OMX.Intel." 33 }; 34 private static final String VP8_MIME_TYPE = "video/x-vnd.on2.vp8"; 35 // List of supported HW VP9 codecs. 36 private static final String[] supportedVp9HwCodecPrefixes = { 37 "OMX.qcom.", "OMX.Exynos." 38 }; 39 private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9"; 40 // List of supported HW H.264 codecs. 41 private static final String[] supportedH264HwCodecPrefixes = { 42 "OMX.qcom.", "OMX.Intel.", "OMX.Exynos.", "OMX.Nvidia", "OMX.SEC.", 43 "OMX.IMG.", "OMX.k3.", "OMX.hisi.", "OMX.TI.", "OMX.MTK." 44 }; 45 private static final String H264_MIME_TYPE = "video/avc"; 46 // NV12 color format supported by QCOM codec, but not declared in MediaCodec - 47 // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h 48 private static final int 49 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04; 50 // Allowable color formats supported by codec - in order of preference. 51 private static final int[] supportedColorList = { 52 CodecCapabilities.COLOR_FormatYUV420Planar, 53 CodecCapabilities.COLOR_FormatYUV420SemiPlanar, 54 CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar, 55 COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m 56 }; 57 private static final String[] adaptivePlaybackBlacklist = { 58 "GT-I9300", // S3 (I9300 / I9300I) 59 "SCH-I535", // S3 60 "SGH-T999", // S3 (T-Mobile) 61 "SAMSUNG-SGH-T999", // S3 (T-Mobile) 62 "SGH-M919", // S4 63 "GT-I9505", // S4 64 "GT-I9515", // S4 65 "SCH-R970", // S4 66 "SGH-I337", // S4 67 "SPH-L720", // S4 (Sprint) 68 "SAMSUNG-SGH-I337", // S4 69 "GT-I9195", // S4 Mini 70 "300E5EV/300E4EV/270E5EV/270E4EV/2470EV/2470EE", 71 "LG-D605" // LG Optimus L9 II 72 }; 73 getCodecListWithOldAPI()74 private static MediaCodecInfo[] getCodecListWithOldAPI() { 75 int numCodecs = 0; 76 try { 77 numCodecs = MediaCodecList.getCodecCount(); 78 } catch (final RuntimeException e) { 79 Log.e(LOGTAG, "Failed to retrieve media codec count", e); 80 return new MediaCodecInfo[numCodecs]; 81 } 82 83 final MediaCodecInfo[] codecList = new MediaCodecInfo[numCodecs]; 84 85 for (int i = 0; i < numCodecs; ++i) { 86 final MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); 87 codecList[i] = info; 88 } 89 90 return codecList; 91 } 92 93 @WrapForJNI getDecoderSupportedMimeTypes()94 public static String[] getDecoderSupportedMimeTypes() { 95 final MediaCodecInfo[] codecList; 96 97 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 98 codecList = getCodecListWithOldAPI(); 99 } else { 100 final MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 101 codecList = list.getCodecInfos(); 102 } 103 104 final Set<String> supportedTypes = new HashSet<>(); 105 106 for (final MediaCodecInfo codec : codecList) { 107 if (codec.isEncoder()) { 108 continue; 109 } 110 supportedTypes.addAll(Arrays.asList(codec.getSupportedTypes())); 111 } 112 113 return supportedTypes.toArray(new String[0]); 114 } 115 116 @WrapForJNI checkSupportsAdaptivePlayback(final MediaCodec aCodec, final String aMimeType)117 public static boolean checkSupportsAdaptivePlayback(final MediaCodec aCodec, 118 final String aMimeType) { 119 // isFeatureSupported supported on API level >= 19. 120 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || 121 isAdaptivePlaybackBlacklisted(aMimeType)) { 122 return false; 123 } 124 125 try { 126 final MediaCodecInfo info = aCodec.getCodecInfo(); 127 final MediaCodecInfo.CodecCapabilities capabilities = info.getCapabilitiesForType(aMimeType); 128 return capabilities != null && 129 capabilities.isFeatureSupported( 130 MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback); 131 } catch (final IllegalArgumentException e) { 132 Log.e(LOGTAG, "Retrieve codec information failed", e); 133 } 134 return false; 135 } 136 137 // See Bug1360626 and 138 // https://codereview.chromium.org/1869103002 for details. isAdaptivePlaybackBlacklisted(final String aMimeType)139 private static boolean isAdaptivePlaybackBlacklisted(final String aMimeType) { 140 Log.d(LOGTAG, "The device ModelID is " + Build.MODEL); 141 if (!aMimeType.equals("video/avc") && !aMimeType.equals("video/avc1")) { 142 return false; 143 } 144 145 if (!Build.MANUFACTURER.toLowerCase(Locale.ROOT).equals("samsung")) { 146 return false; 147 } 148 149 for (final String model : adaptivePlaybackBlacklist) { 150 if (Build.MODEL.startsWith(model)) { 151 return true; 152 } 153 } 154 return false; 155 } 156 getHWCodecCapability(final String aMimeType, final boolean aIsEncoder)157 public static boolean getHWCodecCapability(final String aMimeType, final boolean aIsEncoder) { 158 if (Build.VERSION.SDK_INT >= 20) { 159 for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) { 160 final MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); 161 if (info.isEncoder() != aIsEncoder) { 162 continue; 163 } 164 String name = null; 165 for (final String mimeType : info.getSupportedTypes()) { 166 if (mimeType.equals(aMimeType)) { 167 name = info.getName(); 168 break; 169 } 170 } 171 if (name == null) { 172 continue; // No HW support in this codec; try the next one. 173 } 174 Log.d(LOGTAG, "Found candidate" + (aIsEncoder ? " encoder " : " decoder ") + name); 175 176 // Check if this is supported codec. 177 final String[] hwList = getSupportedHWCodecPrefixes(aMimeType, aIsEncoder); 178 if (hwList == null) { 179 continue; 180 } 181 boolean supportedCodec = false; 182 for (final String codecPrefix : hwList) { 183 if (name.startsWith(codecPrefix)) { 184 supportedCodec = true; 185 break; 186 } 187 } 188 if (!supportedCodec) { 189 continue; 190 } 191 192 // Check if codec supports either yuv420 or nv12. 193 final CodecCapabilities capabilities = 194 info.getCapabilitiesForType(aMimeType); 195 for (final int colorFormat : capabilities.colorFormats) { 196 Log.v(LOGTAG, " Color: 0x" + Integer.toHexString(colorFormat)); 197 } 198 for (final int supportedColorFormat : supportedColorList) { 199 for (final int codecColorFormat : capabilities.colorFormats) { 200 if (codecColorFormat == supportedColorFormat) { 201 // Found supported HW Codec. 202 Log.d(LOGTAG, "Found target" + 203 (aIsEncoder ? " encoder " : " decoder ") + name + 204 ". Color: 0x" + Integer.toHexString(codecColorFormat)); 205 return true; 206 } 207 } 208 } 209 } 210 } 211 // No HW codec. 212 return false; 213 } 214 getSupportedHWCodecPrefixes(final String aMimeType, final boolean aIsEncoder)215 private static String[] getSupportedHWCodecPrefixes(final String aMimeType, final boolean aIsEncoder) { 216 if (aMimeType.equals(H264_MIME_TYPE)) { 217 return supportedH264HwCodecPrefixes; 218 } 219 if (aMimeType.equals(VP9_MIME_TYPE)) { 220 return supportedVp9HwCodecPrefixes; 221 } 222 if (aMimeType.equals(VP8_MIME_TYPE)) { 223 return aIsEncoder ? supportedVp8HwEncCodecPrefixes : supportedVp8HwDecCodecPrefixes; 224 } 225 return null; 226 } 227 hasHWVP8(final boolean aIsEncoder)228 public static boolean hasHWVP8(final boolean aIsEncoder) { 229 return getHWCodecCapability(VP8_MIME_TYPE, aIsEncoder); 230 } 231 232 @WrapForJNI hasHWVP9(final boolean aIsEncoder)233 public static boolean hasHWVP9(final boolean aIsEncoder) { 234 return getHWCodecCapability(VP9_MIME_TYPE, aIsEncoder); 235 } 236 237 @WrapForJNI(calledFrom = "gecko") hasHWH264()238 public static boolean hasHWH264() { 239 return getHWCodecCapability(H264_MIME_TYPE, true) && 240 getHWCodecCapability(H264_MIME_TYPE, false); 241 } 242 } 243