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