/** * Returns the name of the best decoder and its capabilities for the given mimeType. */ private static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo( String mimeType, boolean secure) throws DecoderQueryException { CodecKey key = new CodecKey(mimeType, secure); if (codecs.containsKey(key)) { return codecs.get(key); } MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21 ? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16(); Pair<String, CodecCapabilities> codecInfo = getMediaCodecInfo(key, mediaCodecList); // TODO: Verify this cannot occur on v22, and change >= to == [Internal: b/18678462]. if (secure && codecInfo == null && Util.SDK_INT >= 21) { // Some devices don't list secure decoders on API level 21. Try the legacy path. mediaCodecList = new MediaCodecListCompatV16(); codecInfo = getMediaCodecInfo(key, mediaCodecList); if (codecInfo != null) { Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType + ". Assuming: " + codecInfo.first); } } return codecInfo; }
/** * @param profile An AVC profile constant from {@link CodecProfileLevel}. * @param level An AVC profile level from {@link CodecProfileLevel}. * @return Whether the specified profile is supported at the specified level. */ public static boolean isH264ProfileSupported(int profile, int level) throws DecoderQueryException { Pair<String, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false); if (info == null) { return false; } CodecCapabilities capabilities = info.second; for (int i = 0; i < capabilities.profileLevels.length; i++) { CodecProfileLevel profileLevel = capabilities.profileLevels[i]; if (profileLevel.profile == profile && profileLevel.level >= level) { return true; } } return false; }
/** * Returns the name of the best decoder and its capabilities for the given mimeType. */ private static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo( String mimeType, boolean secure) { CodecKey key = new CodecKey(mimeType, secure); if (codecs.containsKey(key)) { return codecs.get(key); } MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21 ? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16(); Pair<String, CodecCapabilities> codecInfo = getMediaCodecInfo(key, mediaCodecList); // TODO: Verify this cannot occur on v22, and change >= to == [Internal: b/18678462]. if (secure && codecInfo == null && Util.SDK_INT >= 21) { // Some devices don't list secure decoders on API level 21. Try the legacy path. mediaCodecList = new MediaCodecListCompatV16(); codecInfo = getMediaCodecInfo(key, mediaCodecList); if (codecInfo != null) { Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType + ". Assuming: " + codecInfo.first); } } return codecInfo; }
/** * @param profile An AVC profile constant from {@link CodecProfileLevel}. * @param level An AVC profile level from {@link CodecProfileLevel}. * @return Whether the specified profile is supported at the specified level. */ public static boolean isH264ProfileSupported(int profile, int level) { Pair<String, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false); if (info == null) { return false; } CodecCapabilities capabilities = info.second; for (int i = 0; i < capabilities.profileLevels.length; i++) { CodecProfileLevel profileLevel = capabilities.profileLevels[i]; if (profileLevel.profile == profile && profileLevel.level >= level) { return true; } } return false; }
/** * Needed on M and older to get correct information about VP9 support. * @param profileLevels The CodecProfileLevelList to add supported profile levels to. * @param videoCapabilities The MediaCodecInfo.VideoCapabilities used to infer support. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static void addVp9CodecProfileLevels(CodecProfileLevelList profileLevels, MediaCodecInfo.CodecCapabilities codecCapabilities) { // https://www.webmproject.org/vp9/levels final int[][] bitrateMapping = { {200, 10}, {800, 11}, {1800, 20}, {3600, 21}, {7200, 30}, {12000, 31}, {18000, 40}, {30000, 41}, {60000, 50}, {120000, 51}, {180000, 52}, }; VideoCapabilities videoCapabilities = codecCapabilities.getVideoCapabilities(); for (int[] entry : bitrateMapping) { int bitrate = entry[0]; int level = entry[1]; if (videoCapabilities.getBitrateRange().contains(bitrate)) { // Assume all platforms before N only support VP9 profile 0. profileLevels.addCodecProfileLevel( VideoCodec.CODEC_VP9, VideoCodecProfile.VP9PROFILE_PROFILE0, level); } } }
/** * Return an array of supported codecs and profiles. */ @CalledByNative private static Object[] getSupportedCodecProfileLevels() { CodecProfileLevelList profileLevels = new CodecProfileLevelList(); MediaCodecListHelper codecListHelper = new MediaCodecListHelper(); for (MediaCodecInfo info : codecListHelper) { for (String mime : info.getSupportedTypes()) { // On versions L and M, VP9 codecCapabilities do not advertise profile level // support. In this case, estimate the level from MediaCodecInfo.VideoCapabilities // instead. Assume VP9 is not supported before L. For more information, consult // https://developer.android.com/reference/android/media/MediaCodecInfo.CodecProfileLevel.html CodecCapabilities codecCapabilities = info.getCapabilitiesForType(mime); if (mime.endsWith("vp9") && Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { addVp9CodecProfileLevels(profileLevels, codecCapabilities); continue; } for (CodecProfileLevel profileLevel : codecCapabilities.profileLevels) { profileLevels.addCodecProfileLevel(mime, profileLevel); } } } return profileLevels.toArray(); }
/** * Returns true if the given codec supports adaptive playback (dynamic resolution change). * @param mediaCodec the codec. * @param mime MIME type that corresponds to the codec creation. * @return true if this codec and mime type combination supports adaptive playback. */ @TargetApi(Build.VERSION_CODES.KITKAT) private static boolean codecSupportsAdaptivePlayback(MediaCodec mediaCodec, String mime) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || mediaCodec == null) { return false; } try { MediaCodecInfo info = mediaCodec.getCodecInfo(); if (info.isEncoder()) { return false; } if (isAdaptivePlaybackBlacklisted(mime)) { return false; } MediaCodecInfo.CodecCapabilities capabilities = info.getCapabilitiesForType(mime); return (capabilities != null) && capabilities.isFeatureSupported( MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback); } catch (IllegalArgumentException e) { Log.e(TAG, "Cannot retrieve codec information", e); } return false; }
/** * Returns the best decoder and its capabilities for the given mimeType. If there's no decoder * returns null. */ private static synchronized Pair<MediaCodecInfo, CodecCapabilities> getMediaCodecInfo( String mimeType) { Pair<MediaCodecInfo, CodecCapabilities> result = codecs.get(mimeType); if (result != null) { return result; } int numberOfCodecs = MediaCodecList.getCodecCount(); // Note: MediaCodecList is sorted by the framework such that the best decoders come first. for (int i = 0; i < numberOfCodecs; i++) { MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); String codecName = info.getName(); if (!info.isEncoder() && isOmxCodec(codecName)) { String[] supportedTypes = info.getSupportedTypes(); for (int j = 0; j < supportedTypes.length; j++) { String supportedType = supportedTypes[j]; if (supportedType.equalsIgnoreCase(mimeType)) { result = Pair.create(info, info.getCapabilitiesForType(supportedType)); codecs.put(mimeType, result); return result; } } } } return null; }
/** * @param profile An AVC profile constant from {@link CodecProfileLevel}. * @param level An AVC profile level from {@link CodecProfileLevel}. * @return Whether the specified profile is supported at the specified level. */ public static boolean isH264ProfileSupported(int profile, int level) { Pair<MediaCodecInfo, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264); if (info == null) { return false; } CodecCapabilities capabilities = info.second; for (int i = 0; i < capabilities.profileLevels.length; i++) { CodecProfileLevel profileLevel = capabilities.profileLevels[i]; if (profileLevel.profile == profile && profileLevel.level >= level) { return true; } } return false; }
public static boolean decoderSupportsAdaptivePlayback(MediaCodecInfo decoderInfo) { // Possibly enable adaptive playback on KitKat and above if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (isDecoderInList(blacklistedAdaptivePlaybackPrefixes, decoderInfo.getName())) { LimeLog.info("Decoder blacklisted for adaptive playback"); return false; } try { if (decoderInfo.getCapabilitiesForType("video/avc"). isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback)) { // This will make getCapabilities() return that adaptive playback is supported LimeLog.info("Adaptive playback supported (FEATURE_AdaptivePlayback)"); return true; } } catch (Exception e) { // Tolerate buggy codecs } } return false; }
@SuppressWarnings("RedundantThrows") public static String dumpDecoders() throws Exception { String str = ""; for (MediaCodecInfo codecInfo : getMediaCodecList()) { // Skip encoders if (codecInfo.isEncoder()) { continue; } str += "Decoder: "+codecInfo.getName()+"\n"; for (String type : codecInfo.getSupportedTypes()) { str += "\t"+type+"\n"; CodecCapabilities caps = codecInfo.getCapabilitiesForType(type); for (CodecProfileLevel profile : caps.profileLevels) { str += "\t\t"+profile.profile+" "+profile.level+"\n"; } } } return str; }
public static int getAVCLevel() { int maxAVCLevel = 0; if (VERSION.SDK_INT >= 16) { int mediaCodecListCount = MediaCodecList.getCodecCount(); for (int i = 0; i < mediaCodecListCount; i++) { MediaCodecInfo mediaCodecInfo = MediaCodecList.getCodecInfoAt(i); if (!(mediaCodecInfo.isEncoder() || mediaCodecInfo.getName().startsWith("OMX.google") || mediaCodecInfo.getName().startsWith("OMX.TI."))) { Log.d(LOG_TAG, "getAVCLevel()->name:" + mediaCodecInfo.getName()); for (String type : mediaCodecInfo.getSupportedTypes()) { if (type.contains("avc")) { Log.d(LOG_TAG, "getAVCLevel()->type:" + type); try { CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(type); for (int colorFormat : codecCapabilities.colorFormats) { Log.d(LOG_TAG, "getAVCLevel()->Color Format: " + colorFormat + " " + colorFormatToString(colorFormat)); } for (CodecProfileLevel codecProfileLevel : codecCapabilities.profileLevels) { String level = "unknown type"; String sprofile = "unknown type"; Log.d(LOG_TAG, "getAVCLevel()->Codec Profile Level:" + avcLevelToString(codecProfileLevel.level) + " profile:" + avcProfileToString(codecProfileLevel.profile)); if (codecProfileLevel.level > maxAVCLevel) { maxAVCLevel = codecProfileLevel.level; } } } catch (Exception e) { e.printStackTrace(); } } } } } Log.d(LOG_TAG, "getAVCLevel()->Max AVCLevel:" + maxAVCLevel + " " + avcProfileToString(maxAVCLevel)); } return maxAVCLevel; }
public static int getProfile() { int maxProfile = 0; Log.d(LOG_TAG, "getProfile()->Build.VERSION.SDK_INT:" + String.valueOf(VERSION.SDK_INT)); if (VERSION.SDK_INT >= 16) { int mediaCodecListCount = MediaCodecList.getCodecCount(); for (int i = 0; i < mediaCodecListCount; i++) { MediaCodecInfo mediaCodecInfo = MediaCodecList.getCodecInfoAt(i); if (!(mediaCodecInfo.isEncoder() || mediaCodecInfo.getName().startsWith("OMX.google") || mediaCodecInfo.getName().startsWith("OMX.TI."))) { Log.d(LOG_TAG, "getProfile()->name:" + mediaCodecInfo.getName()); for (String type : mediaCodecInfo.getSupportedTypes()) { if (type.contains("avc")) { Log.d(LOG_TAG, "getProfile()->type:" + type); try { CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(type); for (int colorFormat : codecCapabilities.colorFormats) { Log.d(LOG_TAG, "getProfile()->Color Format: " + colorFormat + " " + colorFormatToString(colorFormat)); } for (CodecProfileLevel codecProfileLevel : codecCapabilities.profileLevels) { String level = "unknown type"; String sprofile = "unknown type"; Log.d(LOG_TAG, "getProfile()->Codec Profile Level:" + avcLevelToString(codecProfileLevel.level) + " profile:" + avcProfileToString(codecProfileLevel.profile)); if (codecProfileLevel.profile > maxProfile) { maxProfile = codecProfileLevel.profile; } } } catch (Exception e) { e.printStackTrace(); } } } } } Log.d(LOG_TAG, "getProfile()->Max profile:" + maxProfile + " " + avcProfileToString(maxProfile)); } return maxProfile; }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN) public void dumpProfileLevels(String mimeType) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return; try { CodecCapabilities caps = mCodecInfo .getCapabilitiesForType(mimeType); int maxProfile = 0; int maxLevel = 0; if (caps != null) { if (caps.profileLevels != null) { for (CodecProfileLevel profileLevel : caps.profileLevels) { if (profileLevel == null) continue; maxProfile = Math.max(maxProfile, profileLevel.profile); maxLevel = Math.max(maxLevel, profileLevel.level); } } } Log.i(TAG, String.format(Locale.US, "%s", getProfileLevelName(maxProfile, maxLevel))); } catch (Throwable e) { Log.i(TAG, "profile-level: exception"); } }
static Integer selectColorFormat(int[] supportedColorFormats, CodecCapabilities capabilities) { for (int supportedColorFormat : supportedColorFormats) { for (int codecColorFormat : capabilities.colorFormats) { if (codecColorFormat == supportedColorFormat) { return codecColorFormat; } } } return null; }
@Override public VideoDecoder createDecoder(String codecType) { VideoCodecType type = VideoCodecType.valueOf(codecType); MediaCodecInfo info = findCodecForType(type); if (info == null) { return null; // No support for this codec type. } CodecCapabilities capabilities = info.getCapabilitiesForType(type.mimeType()); return new HardwareVideoDecoder(info.getName(), type, MediaCodecUtils.selectColorFormat(MediaCodecUtils.DECODER_COLOR_FORMATS, capabilities), sharedContext); }
/** * @param name The name of the decoder. * @param capabilities The capabilities of the decoder. */ private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities) { this.name = Assertions.checkNotNull(name); this.mimeType = mimeType; this.capabilities = capabilities; adaptive = capabilities != null && isAdaptive(capabilities); tunneling = capabilities != null && isTunneling(capabilities); }
@TargetApi(21) private static void configureTunnelingV21(MediaFormat mediaFormat, int tunnelingAudioSessionId) { mediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true); mediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId); }
/** * Get information about the decoder that will be used for a given mime type. * * @param mimeType The mime type. * @param secure Whether the decoder is required to support secure decryption. Always pass false * unless secure decryption really is required. * @return Information about the decoder that will be used, or null if no decoder exists. */ public static DecoderInfo getDecoderInfo(String mimeType, boolean secure) throws DecoderQueryException { Pair<String, CodecCapabilities> info = getMediaCodecInfo(mimeType, secure); if (info == null) { return null; } return new DecoderInfo(info.first, isAdaptive(info.second)); }
private static Pair<String, CodecCapabilities> getMediaCodecInfo(CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { try { return getMediaCodecInfoInternal(key, mediaCodecList); } catch (Exception e) { // If the underlying mediaserver is in a bad state, we may catch an IllegalStateException // or an IllegalArgumentException here. throw new DecoderQueryException(e); } }
private static Pair<String, CodecCapabilities> getMediaCodecInfoInternal(CodecKey key, MediaCodecListCompat mediaCodecList) { String mimeType = key.mimeType; int numberOfCodecs = mediaCodecList.getCodecCount(); boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); // Note: MediaCodecList is sorted by the framework such that the best decoders come first. for (int i = 0; i < numberOfCodecs; i++) { MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); String codecName = info.getName(); if (isCodecUsableDecoder(info, codecName, secureDecodersExplicit)) { String[] supportedTypes = info.getSupportedTypes(); for (int j = 0; j < supportedTypes.length; j++) { String supportedType = supportedTypes[j]; if (supportedType.equalsIgnoreCase(mimeType)) { CodecCapabilities capabilities = info.getCapabilitiesForType(supportedType); boolean secure = mediaCodecList.isSecurePlaybackSupported(key.mimeType, capabilities); if (!secureDecodersExplicit) { // Cache variants for both insecure and (if we think it's supported) secure playback. codecs.put(key.secure ? new CodecKey(mimeType, false) : key, Pair.create(codecName, capabilities)); if (secure) { codecs.put(key.secure ? key : new CodecKey(mimeType, true), Pair.create(codecName + ".secure", capabilities)); } } else { // Only cache this variant. If both insecure and secure decoders are available, they // should both be listed separately. codecs.put(key.secure == secure ? key : new CodecKey(mimeType, secure), Pair.create(codecName, capabilities)); } if (codecs.containsKey(key)) { return codecs.get(key); } } } } } return null; }
private static boolean isAdaptive(CodecCapabilities capabilities) { if (Util.SDK_INT >= 19) { return isAdaptiveV19(capabilities); } else { return false; } }
/** * @param name The name of the decoder. * @param capabilities The capabilities of the decoder. */ private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities) { this.name = Assertions.checkNotNull(name); this.mimeType = mimeType; this.capabilities = capabilities; adaptive = capabilities != null && isAdaptive(capabilities); }
private static Pair<String, CodecCapabilities> getMediaCodecInfo(CodecKey key, MediaCodecListCompat mediaCodecList) { String mimeType = key.mimeType; int numberOfCodecs = mediaCodecList.getCodecCount(); boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); // Note: MediaCodecList is sorted by the framework such that the best decoders come first. for (int i = 0; i < numberOfCodecs; i++) { MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); String codecName = info.getName(); if (!info.isEncoder() && codecName.startsWith("OMX.") && (secureDecodersExplicit || !codecName.endsWith(".secure"))) { String[] supportedTypes = info.getSupportedTypes(); for (int j = 0; j < supportedTypes.length; j++) { String supportedType = supportedTypes[j]; if (supportedType.equalsIgnoreCase(mimeType)) { CodecCapabilities capabilities = info.getCapabilitiesForType(supportedType); boolean secure = mediaCodecList.isSecurePlaybackSupported(key.mimeType, capabilities); if (!secureDecodersExplicit) { // Cache variants for both insecure and (if we think it's supported) secure playback. codecs.put(key.secure ? new CodecKey(mimeType, false) : key, Pair.create(codecName, capabilities)); if (secure) { codecs.put(key.secure ? key : new CodecKey(mimeType, true), Pair.create(codecName + ".secure", capabilities)); } } else { // Only cache this variant. If both insecure and secure decoders are available, they // should both be listed separately. codecs.put(key.secure == secure ? key : new CodecKey(mimeType, secure), Pair.create(codecName, capabilities)); } if (codecs.containsKey(key)) { return codecs.get(key); } } } } } return null; }