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 import com.google.android.exoplayer.util.MimeTypes; 25 import java.util.HashMap; 26 27 /** 28 * Mostly copied from {@link com.google.android.exoplayer.MediaCodecUtil} in order to choose 29 * software codec over hardware codec. 30 */ 31 public class MediaSoftwareCodecUtil { 32 private static final String TAG = "MediaSoftwareCodecUtil"; 33 34 /** 35 * Thrown when an error occurs querying the device for its underlying media capabilities. 36 * 37 * <p>Such failures are not expected in normal operation and are normally temporary (e.g. if the 38 * mediaserver process has crashed and is yet to restart). 39 */ 40 public static class DecoderQueryException extends Exception { 41 DecoderQueryException(Throwable cause)42 private DecoderQueryException(Throwable cause) { 43 super("Failed to query underlying media codecs", cause); 44 } 45 } 46 47 private static final HashMap<CodecKey, Pair<String, MediaCodecInfo.CodecCapabilities>> 48 sSwCodecs = new HashMap<>(); 49 50 /** Gets information about the software decoder that will be used for a given mime type. */ getSoftwareDecoderInfo(String mimeType, boolean secure)51 public static DecoderInfo getSoftwareDecoderInfo(String mimeType, boolean secure) 52 throws DecoderQueryException { 53 // TODO: Add a test for this method. 54 Pair<String, MediaCodecInfo.CodecCapabilities> info = 55 getMediaSoftwareCodecInfo(mimeType, secure); 56 if (info == null) { 57 return null; 58 } 59 return new DecoderInfo(info.first, info.second); 60 } 61 62 /** Returns the name of the software decoder and its capabilities for the given mimeType. */ 63 private static synchronized Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfo(String mimeType, boolean secure)64 getMediaSoftwareCodecInfo(String mimeType, boolean secure) 65 throws DecoderQueryException { 66 CodecKey key = new CodecKey(mimeType, secure); 67 if (sSwCodecs.containsKey(key)) { 68 return sSwCodecs.get(key); 69 } 70 MediaCodecListCompat mediaCodecList = new MediaCodecListCompatV21(secure); 71 Pair<String, MediaCodecInfo.CodecCapabilities> codecInfo = 72 getMediaSoftwareCodecInfo(key, mediaCodecList); 73 if (secure && codecInfo == null) { 74 // Some devices don't list secure decoders on API level 21. Try the legacy path. 75 mediaCodecList = new MediaCodecListCompatV16(); 76 codecInfo = getMediaSoftwareCodecInfo(key, mediaCodecList); 77 if (codecInfo != null) { 78 Log.w( 79 TAG, 80 "MediaCodecList API didn't list secure decoder for: " 81 + mimeType 82 + ". Assuming: " 83 + codecInfo.first); 84 } 85 } 86 return codecInfo; 87 } 88 getMediaSoftwareCodecInfo( CodecKey key, MediaCodecListCompat mediaCodecList)89 private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfo( 90 CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { 91 try { 92 return getMediaSoftwareCodecInfoInternal(key, mediaCodecList); 93 } catch (Exception e) { 94 // If the underlying mediaserver is in a bad state, we may catch an 95 // IllegalStateException or an IllegalArgumentException here. 96 throw new DecoderQueryException(e); 97 } 98 } 99 getMediaSoftwareCodecInfoInternal( CodecKey key, MediaCodecListCompat mediaCodecList)100 private static Pair<String, MediaCodecInfo.CodecCapabilities> getMediaSoftwareCodecInfoInternal( 101 CodecKey key, MediaCodecListCompat mediaCodecList) { 102 String mimeType = key.mimeType; 103 int numberOfCodecs = mediaCodecList.getCodecCount(); 104 boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); 105 // Note: MediaCodecList is sorted by the framework such that the best decoders come first. 106 for (int i = 0; i < numberOfCodecs; i++) { 107 MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); 108 String codecName = info.getName(); 109 if (!info.isEncoder() 110 && codecName.startsWith("OMX.google.") 111 && (secureDecodersExplicit || !codecName.endsWith(".secure"))) { 112 String[] supportedTypes = info.getSupportedTypes(); 113 for (String supportedType : supportedTypes) { 114 if (supportedType.equalsIgnoreCase(mimeType)) { 115 MediaCodecInfo.CodecCapabilities capabilities = 116 info.getCapabilitiesForType(supportedType); 117 boolean secure = 118 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( 124 key.secure ? new CodecKey(mimeType, false) : key, 125 Pair.create(codecName, capabilities)); 126 if (secure) { 127 sSwCodecs.put( 128 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 /** Returns the number of codecs in the list. */ 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 /** Returns whether secure decoders are explicitly listed, if present. */ secureDecodersExplicit()162 boolean secureDecodersExplicit(); 163 164 /** 165 * Returns true if secure playback is supported for the given {@link 166 * android.media.MediaCodecInfo.CodecCapabilities}, which should have been obtained from a 167 * {@link MediaCodecInfo} obtained from this list. 168 */ isSecurePlaybackSupported( String mimeType, MediaCodecInfo.CodecCapabilities capabilities)169 boolean isSecurePlaybackSupported( 170 String mimeType, MediaCodecInfo.CodecCapabilities capabilities); 171 } 172 173 @TargetApi(21) 174 private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { 175 176 private final int codecKind; 177 178 private MediaCodecInfo[] mediaCodecInfos; 179 MediaCodecListCompatV21(boolean includeSecure)180 public MediaCodecListCompatV21(boolean includeSecure) { 181 codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; 182 } 183 184 @Override getCodecCount()185 public int getCodecCount() { 186 ensureMediaCodecInfosInitialized(); 187 return mediaCodecInfos.length; 188 } 189 190 @Override getCodecInfoAt(int index)191 public MediaCodecInfo getCodecInfoAt(int index) { 192 ensureMediaCodecInfosInitialized(); 193 return mediaCodecInfos[index]; 194 } 195 196 @Override secureDecodersExplicit()197 public boolean secureDecodersExplicit() { 198 return true; 199 } 200 201 @Override isSecurePlaybackSupported( String mimeType, MediaCodecInfo.CodecCapabilities capabilities)202 public boolean isSecurePlaybackSupported( 203 String mimeType, MediaCodecInfo.CodecCapabilities capabilities) { 204 return capabilities.isFeatureSupported( 205 MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback); 206 } 207 ensureMediaCodecInfosInitialized()208 private void ensureMediaCodecInfosInitialized() { 209 if (mediaCodecInfos == null) { 210 mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); 211 } 212 } 213 } 214 215 @SuppressWarnings("deprecation") 216 private static final class MediaCodecListCompatV16 implements MediaCodecListCompat { 217 218 @Override getCodecCount()219 public int getCodecCount() { 220 return MediaCodecList.getCodecCount(); 221 } 222 223 @Override getCodecInfoAt(int index)224 public MediaCodecInfo getCodecInfoAt(int index) { 225 return MediaCodecList.getCodecInfoAt(index); 226 } 227 228 @Override secureDecodersExplicit()229 public boolean secureDecodersExplicit() { 230 return false; 231 } 232 233 @Override isSecurePlaybackSupported( String mimeType, MediaCodecInfo.CodecCapabilities capabilities)234 public boolean isSecurePlaybackSupported( 235 String mimeType, MediaCodecInfo.CodecCapabilities capabilities) { 236 // Secure decoders weren't explicitly listed prior to API level 21. We assume that 237 // a secure H264 decoder exists. 238 return MimeTypes.VIDEO_H264.equals(mimeType); 239 } 240 } 241 242 private static final class CodecKey { 243 244 public final String mimeType; 245 public final boolean secure; 246 CodecKey(String mimeType, boolean secure)247 public CodecKey(String mimeType, boolean secure) { 248 this.mimeType = mimeType; 249 this.secure = secure; 250 } 251 252 @Override hashCode()253 public int hashCode() { 254 final int prime = 31; 255 int result = 1; 256 result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); 257 result = 2 * result + (secure ? 0 : 1); 258 return result; 259 } 260 261 @Override equals(Object obj)262 public boolean equals(Object obj) { 263 if (this == obj) { 264 return true; 265 } 266 if (!(obj instanceof CodecKey)) { 267 return false; 268 } 269 CodecKey other = (CodecKey) obj; 270 return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; 271 } 272 } 273 } 274