1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer; 17 18 import com.google.android.exoplayer.util.MimeTypes; 19 20 import android.annotation.TargetApi; 21 import android.media.MediaCodecInfo; 22 import android.media.MediaCodecList; 23 import android.text.TextUtils; 24 import android.util.Log; 25 import android.util.Pair; 26 27 import java.util.HashMap; 28 29 /** 30 * Mostly copied from {@link com.google.android.exoplayer.MediaCodecUtil} in order to choose 31 * software codec over hardware codec. 32 */ 33 public class MediaSoftwareCodecUtil { 34 private static final String TAG = "MediaSoftwareCodecUtil"; 35 36 /** 37 * Thrown when an error occurs querying the device for its underlying media capabilities. 38 * <p> 39 * Such failures are not expected in normal operation and are normally temporary (e.g. if the 40 * mediaserver process has crashed and is yet to restart). 41 */ 42 public static class DecoderQueryException extends Exception { 43 44 private DecoderQueryException(Throwable cause) { 45 super("Failed to query underlying media codecs", cause); 46 } 47 48 } 49 50 private static final HashMap<CodecKey, Pair<String, MediaCodecInfo.CodecCapabilities>> 51 sSwCodecs = new HashMap<>(); 52 53 /** 54 * Gets information about the software decoder that will be used for a given mime type. 55 */ 56 public static DecoderInfo getSoftwareDecoderInfo(String mimeType, boolean secure) 57 throws DecoderQueryException { 58 // TODO: Add a test for this method. 59 Pair<String, MediaCodecInfo.CodecCapabilities> info = 60 getMediaSoftwareCodecInfo(mimeType, secure); 61 if (info == null) { 62 return null; 63 } 64 return new DecoderInfo(info.first, info.second.isFeatureSupported( 65 MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback)); 66 } 67 68 /** 69 * Returns the name of the software decoder and its capabilities for the given mimeType. 70 */ 71 private static synchronized Pair<String, MediaCodecInfo.CodecCapabilities> 72 getMediaSoftwareCodecInfo(String mimeType, boolean secure) throws DecoderQueryException { 73 CodecKey key = new CodecKey(mimeType, secure); 74 if (sSwCodecs.containsKey(key)) { 75 return sSwCodecs.get(key); 76 } 77 MediaCodecListCompat mediaCodecList = new MediaCodecListCompatV21(secure); 78 Pair<String, MediaCodecInfo.CodecCapabilities> codecInfo = 79 getMediaSoftwareCodecInfo(key, mediaCodecList); 80 if (secure && codecInfo == null) { 81 // Some devices don't list secure decoders on API level 21. Try the legacy path. 82 mediaCodecList = new MediaCodecListCompatV16(); 83 codecInfo = getMediaSoftwareCodecInfo(key, mediaCodecList); 84 if (codecInfo != null) { 85 Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType 86 + ". Assuming: " + codecInfo.first); 87 } 88 } 89 return codecInfo; 90 } 91 92 private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfo( 93 CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { 94 try { 95 return getMediaSoftwareCodecInfoInternal(key, mediaCodecList); 96 } catch (Exception e) { 97 // If the underlying mediaserver is in a bad state, we may catch an 98 // IllegalStateException or an IllegalArgumentException here. 99 throw new DecoderQueryException(e); 100 } 101 } 102 103 private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfoInternal( 104 CodecKey key, MediaCodecListCompat mediaCodecList) { 105 String mimeType = key.mimeType; 106 int numberOfCodecs = mediaCodecList.getCodecCount(); 107 boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); 108 // Note: MediaCodecList is sorted by the framework such that the best decoders come first. 109 for (int i = 0; i < numberOfCodecs; i++) { 110 MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); 111 String codecName = info.getName(); 112 if (!info.isEncoder() && codecName.startsWith("OMX.google.") 113 && (secureDecodersExplicit || !codecName.endsWith(".secure"))) { 114 String[] supportedTypes = info.getSupportedTypes(); 115 for (int j = 0; j < supportedTypes.length; j++) { 116 String supportedType = supportedTypes[j]; 117 if (supportedType.equalsIgnoreCase(mimeType)) { 118 MediaCodecInfo.CodecCapabilities capabilities = 119 info.getCapabilitiesForType(supportedType); 120 boolean secure = mediaCodecList.isSecurePlaybackSupported( 121 key.mimeType, capabilities); 122 if (!secureDecodersExplicit) { 123 // Cache variants for both insecure and (if we think it's supported) 124 // secure playback. 125 sSwCodecs.put(key.secure ? new CodecKey(mimeType, false) : key, 126 Pair.create(codecName, capabilities)); 127 if (secure) { 128 sSwCodecs.put(key.secure ? key : new CodecKey(mimeType, true), 129 Pair.create(codecName + ".secure", capabilities)); 130 } 131 } else { 132 // Only cache this variant. If both insecure and secure decoders are 133 // available, they should both be listed separately. 134 sSwCodecs.put( 135 key.secure == secure ? key : new CodecKey(mimeType, secure), 136 Pair.create(codecName, capabilities)); 137 } 138 if (sSwCodecs.containsKey(key)) { 139 return sSwCodecs.get(key); 140 } 141 } 142 } 143 } 144 } 145 sSwCodecs.put(key, null); 146 return null; 147 } 148 149 private interface MediaCodecListCompat { 150 151 /** 152 * Returns the number of codecs in the list. 153 */ 154 public int getCodecCount(); 155 156 /** 157 * Returns the info at the specified index in the list. 158 * 159 * @param index The index. 160 */ 161 public MediaCodecInfo getCodecInfoAt(int index); 162 163 /** 164 * Returns whether secure decoders are explicitly listed, if present. 165 */ 166 public boolean secureDecodersExplicit(); 167 168 /** 169 * Returns true if secure playback is supported for the given 170 * {@link android.media.MediaCodecInfo.CodecCapabilities}, which should 171 * have been obtained from a {@link MediaCodecInfo} obtained from this list. 172 */ 173 public boolean isSecurePlaybackSupported(String mimeType, 174 MediaCodecInfo.CodecCapabilities capabilities); 175 176 } 177 178 @TargetApi(21) 179 private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { 180 181 private final int codecKind; 182 183 private MediaCodecInfo[] mediaCodecInfos; 184 185 public MediaCodecListCompatV21(boolean includeSecure) { 186 codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; 187 } 188 189 @Override 190 public int getCodecCount() { 191 ensureMediaCodecInfosInitialized(); 192 return mediaCodecInfos.length; 193 } 194 195 @Override 196 public MediaCodecInfo getCodecInfoAt(int index) { 197 ensureMediaCodecInfosInitialized(); 198 return mediaCodecInfos[index]; 199 } 200 201 @Override 202 public boolean secureDecodersExplicit() { 203 return true; 204 } 205 206 @Override 207 public boolean isSecurePlaybackSupported(String mimeType, 208 MediaCodecInfo.CodecCapabilities capabilities) { 209 return capabilities.isFeatureSupported( 210 MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); 211 } 212 213 private void ensureMediaCodecInfosInitialized() { 214 if (mediaCodecInfos == null) { 215 mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); 216 } 217 } 218 219 } 220 221 @SuppressWarnings("deprecation") 222 private static final class MediaCodecListCompatV16 implements MediaCodecListCompat { 223 224 @Override 225 public int getCodecCount() { 226 return MediaCodecList.getCodecCount(); 227 } 228 229 @Override 230 public MediaCodecInfo getCodecInfoAt(int index) { 231 return MediaCodecList.getCodecInfoAt(index); 232 } 233 234 @Override 235 public boolean secureDecodersExplicit() { 236 return false; 237 } 238 239 @Override 240 public boolean isSecurePlaybackSupported(String mimeType, 241 MediaCodecInfo.CodecCapabilities capabilities) { 242 // Secure decoders weren't explicitly listed prior to API level 21. We assume that 243 // a secure H264 decoder exists. 244 return MimeTypes.VIDEO_H264.equals(mimeType); 245 } 246 247 } 248 249 private static final class CodecKey { 250 251 public final String mimeType; 252 public final boolean secure; 253 254 public CodecKey(String mimeType, boolean secure) { 255 this.mimeType = mimeType; 256 this.secure = secure; 257 } 258 259 @Override 260 public int hashCode() { 261 final int prime = 31; 262 int result = 1; 263 result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); 264 result = 2 * result + (secure ? 0 : 1); 265 return result; 266 } 267 268 @Override 269 public boolean equals(Object obj) { 270 if (this == obj) { 271 return true; 272 } 273 if (!(obj instanceof CodecKey)) { 274 return false; 275 } 276 CodecKey other = (CodecKey) obj; 277 return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; 278 } 279 280 } 281 282 } 283