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 android.annotation.TargetApi; 19 import android.media.MediaCodecInfo; 20 import android.media.MediaCodecList; 21 import android.text.TextUtils; 22 import android.util.Log; 23 import android.util.Pair; 24 25 import com.google.android.exoplayer.util.MimeTypes; 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 DecoderQueryException(Throwable cause)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 */ getSoftwareDecoderInfo(String mimeType, boolean secure)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); 65 } 66 67 /** 68 * Returns the name of the software decoder and its capabilities for the given mimeType. 69 */ 70 private static synchronized Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfo(String mimeType, boolean secure)71 getMediaSoftwareCodecInfo(String mimeType, boolean secure) throws DecoderQueryException { 72 CodecKey key = new CodecKey(mimeType, secure); 73 if (sSwCodecs.containsKey(key)) { 74 return sSwCodecs.get(key); 75 } 76 MediaCodecListCompat mediaCodecList = new MediaCodecListCompatV21(secure); 77 Pair<String, MediaCodecInfo.CodecCapabilities> codecInfo = 78 getMediaSoftwareCodecInfo(key, mediaCodecList); 79 if (secure && codecInfo == null) { 80 // Some devices don't list secure decoders on API level 21. Try the legacy path. 81 mediaCodecList = new MediaCodecListCompatV16(); 82 codecInfo = getMediaSoftwareCodecInfo(key, mediaCodecList); 83 if (codecInfo != null) { 84 Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType 85 + ". Assuming: " + codecInfo.first); 86 } 87 } 88 return codecInfo; 89 } 90 getMediaSoftwareCodecInfo( CodecKey key, MediaCodecListCompat mediaCodecList)91 private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfo( 92 CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { 93 try { 94 return getMediaSoftwareCodecInfoInternal(key, mediaCodecList); 95 } catch (Exception e) { 96 // If the underlying mediaserver is in a bad state, we may catch an 97 // IllegalStateException or an IllegalArgumentException here. 98 throw new DecoderQueryException(e); 99 } 100 } 101 getMediaSoftwareCodecInfoInternal( CodecKey key, MediaCodecListCompat mediaCodecList)102 private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfoInternal( 103 CodecKey key, MediaCodecListCompat mediaCodecList) { 104 String mimeType = key.mimeType; 105 int numberOfCodecs = mediaCodecList.getCodecCount(); 106 boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); 107 // Note: MediaCodecList is sorted by the framework such that the best decoders come first. 108 for (int i = 0; i < numberOfCodecs; i++) { 109 MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); 110 String codecName = info.getName(); 111 if (!info.isEncoder() && codecName.startsWith("OMX.google.") 112 && (secureDecodersExplicit || !codecName.endsWith(".secure"))) { 113 String[] supportedTypes = info.getSupportedTypes(); 114 for (String supportedType : supportedTypes) { 115 if (supportedType.equalsIgnoreCase(mimeType)) { 116 MediaCodecInfo.CodecCapabilities capabilities = 117 info.getCapabilitiesForType(supportedType); 118 boolean secure = mediaCodecList.isSecurePlaybackSupported( 119 key.mimeType, capabilities); 120 if (!secureDecodersExplicit) { 121 // Cache variants for both insecure and (if we think it's supported) 122 // secure playback. 123 sSwCodecs.put(key.secure ? new CodecKey(mimeType, false) : key, 124 Pair.create(codecName, capabilities)); 125 if (secure) { 126 sSwCodecs.put(key.secure ? key : new CodecKey(mimeType, true), 127 Pair.create(codecName + ".secure", capabilities)); 128 } 129 } else { 130 // Only cache this variant. If both insecure and secure decoders are 131 // available, they should both be listed separately. 132 sSwCodecs.put( 133 key.secure == secure ? key: new CodecKey(mimeType, secure), 134 Pair.create(codecName, capabilities)); 135 } 136 if (sSwCodecs.containsKey(key)) { 137 return sSwCodecs.get(key); 138 } 139 } 140 } 141 } 142 } 143 sSwCodecs.put(key, null); 144 return null; 145 } 146 147 private interface MediaCodecListCompat { 148 149 /** 150 * Returns the number of codecs in the list. 151 */ getCodecCount()152 int getCodecCount(); 153 154 /** 155 * Returns the info at the specified index in the list. 156 * 157 * @param index The index. 158 */ getCodecInfoAt(int index)159 MediaCodecInfo getCodecInfoAt(int index); 160 161 /** 162 * Returns whether secure decoders are explicitly listed, if present. 163 */ secureDecodersExplicit()164 boolean secureDecodersExplicit(); 165 166 /** 167 * Returns true if secure playback is supported for the given 168 * {@link android.media.MediaCodecInfo.CodecCapabilities}, which should 169 * have been obtained from a {@link MediaCodecInfo} obtained from this list. 170 */ isSecurePlaybackSupported(String mimeType, MediaCodecInfo.CodecCapabilities capabilities)171 boolean isSecurePlaybackSupported(String mimeType, 172 MediaCodecInfo.CodecCapabilities capabilities); 173 174 } 175 176 @TargetApi(21) 177 private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { 178 179 private final int codecKind; 180 181 private MediaCodecInfo[] mediaCodecInfos; 182 MediaCodecListCompatV21(boolean includeSecure)183 public MediaCodecListCompatV21(boolean includeSecure) { 184 codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; 185 } 186 187 @Override getCodecCount()188 public int getCodecCount() { 189 ensureMediaCodecInfosInitialized(); 190 return mediaCodecInfos.length; 191 } 192 193 @Override getCodecInfoAt(int index)194 public MediaCodecInfo getCodecInfoAt(int index) { 195 ensureMediaCodecInfosInitialized(); 196 return mediaCodecInfos[index]; 197 } 198 199 @Override secureDecodersExplicit()200 public boolean secureDecodersExplicit() { 201 return true; 202 } 203 204 @Override isSecurePlaybackSupported(String mimeType, MediaCodecInfo.CodecCapabilities capabilities)205 public boolean isSecurePlaybackSupported(String mimeType, 206 MediaCodecInfo.CodecCapabilities capabilities) { 207 return capabilities.isFeatureSupported( 208 MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); 209 } 210 ensureMediaCodecInfosInitialized()211 private void ensureMediaCodecInfosInitialized() { 212 if (mediaCodecInfos == null) { 213 mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); 214 } 215 } 216 217 } 218 219 @SuppressWarnings("deprecation") 220 private static final class MediaCodecListCompatV16 implements MediaCodecListCompat { 221 222 @Override getCodecCount()223 public int getCodecCount() { 224 return MediaCodecList.getCodecCount(); 225 } 226 227 @Override getCodecInfoAt(int index)228 public MediaCodecInfo getCodecInfoAt(int index) { 229 return MediaCodecList.getCodecInfoAt(index); 230 } 231 232 @Override secureDecodersExplicit()233 public boolean secureDecodersExplicit() { 234 return false; 235 } 236 237 @Override isSecurePlaybackSupported(String mimeType, MediaCodecInfo.CodecCapabilities capabilities)238 public boolean isSecurePlaybackSupported(String mimeType, 239 MediaCodecInfo.CodecCapabilities capabilities) { 240 // Secure decoders weren't explicitly listed prior to API level 21. We assume that 241 // a secure H264 decoder exists. 242 return MimeTypes.VIDEO_H264.equals(mimeType); 243 } 244 245 } 246 247 private static final class CodecKey { 248 249 public final String mimeType; 250 public final boolean secure; 251 CodecKey(String mimeType, boolean secure)252 public CodecKey(String mimeType, boolean secure) { 253 this.mimeType = mimeType; 254 this.secure = secure; 255 } 256 257 @Override hashCode()258 public int hashCode() { 259 final int prime = 31; 260 int result = 1; 261 result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); 262 result = 2 * result + (secure ? 0 : 1); 263 return result; 264 } 265 266 @Override equals(Object obj)267 public boolean equals(Object obj) { 268 if (this == obj) { 269 return true; 270 } 271 if (!(obj instanceof CodecKey)) { 272 return false; 273 } 274 CodecKey other = (CodecKey) obj; 275 return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; 276 } 277 278 } 279 280 } 281