diff --git a/library/src/main/java/com/google/android/exoplayer/DecoderInfo.java b/library/src/main/java/com/google/android/exoplayer/DecoderInfo.java index 8b2a0dd4a8..440148a556 100644 --- a/library/src/main/java/com/google/android/exoplayer/DecoderInfo.java +++ b/library/src/main/java/com/google/android/exoplayer/DecoderInfo.java @@ -29,7 +29,7 @@ public final class DecoderInfo { public final String name; /** - * Whether the decoder is adaptive. + * Whether the decoder supports seamless resolution switches. * * @see android.media.MediaCodecInfo.CodecCapabilities#isFeatureSupported(String) * @see android.media.MediaCodecInfo.CodecCapabilities#FEATURE_AdaptivePlayback diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index 1fa103f395..fdd4020769 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -280,11 +280,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { } } - DecoderInfo selectedDecoderInfo = MediaCodecUtil.getDecoderInfo(mimeType); + DecoderInfo selectedDecoderInfo = MediaCodecUtil.getDecoderInfo(mimeType, + requiresSecureDecoder); String selectedDecoderName = selectedDecoderInfo.name; - if (requiresSecureDecoder) { - selectedDecoderName = getSecureDecoderName(selectedDecoderName); - } codecIsAdaptive = selectedDecoderInfo.adaptive; try { codec = MediaCodec.createByCodecName(selectedDecoderName); @@ -765,13 +763,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { MediaCodec codec, ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip) throws ExoPlaybackException; - /** - * Returns the name of the secure variant of a given decoder. - */ - private static String getSecureDecoderName(String rawDecoderName) { - return rawDecoderName + ".secure"; - } - private void notifyDecoderInitializationError(final DecoderInitializationException e) { if (eventHandler != null && eventListener != null) { eventHandler.post(new Runnable() { diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java index 670666abc9..3dfe5f1afe 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecUtil.java @@ -23,6 +23,7 @@ import android.media.MediaCodecInfo; import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCodecList; +import android.text.TextUtils; import android.util.Pair; import java.util.HashMap; @@ -33,60 +34,79 @@ import java.util.HashMap; @TargetApi(16) public class MediaCodecUtil { - private static final HashMap> codecs = - new HashMap>(); + private static final HashMap> codecs = + new HashMap>(); /** - * Get information about the decoder that will be used for a given mime type. If no decoder - * exists for the mime type then null is returned. + * 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) { - Pair info = getMediaCodecInfo(mimeType); + public static DecoderInfo getDecoderInfo(String mimeType, boolean secure) { + Pair info = getMediaCodecInfo(mimeType, secure); if (info == null) { return null; } - return new DecoderInfo(info.first.getName(), isAdaptive(info.second)); + return new DecoderInfo(info.first, isAdaptive(info.second)); } /** - * Optional call to warm the codec cache. Call from any appropriate - * place to hide latency. - */ - public static synchronized void warmCodecs(String[] mimeTypes) { - for (int i = 0; i < mimeTypes.length; i++) { - getMediaCodecInfo(mimeTypes[i]); - } - } - - /** - * Returns the best decoder and its capabilities for the given mimeType. If there's no decoder - * returns null. + * Optional call to warm the codec cache for a given mime type. + *

+ * Calling this method may speed up subsequent calls to {@link #getDecoderInfo(String, boolean)}. * - * TODO: We need to use the new object based MediaCodecList API. + * @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. */ - @SuppressWarnings("deprecation") - private static synchronized Pair getMediaCodecInfo( - String mimeType) { - Pair result = codecs.get(mimeType); - if (result != null) { - return result; + public static synchronized void warmCodec(String mimeType, boolean secure) { + getMediaCodecInfo(mimeType, secure); + } + + /** + * Returns the name of the best decoder and its capabilities for the given mimeType. + */ + private static synchronized Pair getMediaCodecInfo( + String mimeType, boolean secure) { + CodecKey key = new CodecKey(mimeType, secure); + if (codecs.containsKey(key)) { + return codecs.get(key); } - int numberOfCodecs = MediaCodecList.getCodecCount(); + MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21 + ? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16(); + 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); + MediaCodecInfo info = mediaCodecList.getCodecInfoAt(i); String codecName = info.getName(); - if (!info.isEncoder() && codecName.startsWith("OMX.") && !codecName.endsWith(".secure")) { + 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)) { - result = Pair.create(info, info.getCapabilitiesForType(supportedType)); - codecs.put(mimeType, result); - return result; + CodecCapabilities capabilities = info.getCapabilitiesForType(supportedType); + if (!secureDecodersExplicit) { + // Cache variants for secure and insecure playback. Note that the secure decoder is + // inferred, and may not actually exist. + codecs.put(key.secure ? new CodecKey(mimeType, false) : key, + Pair.create(codecName, capabilities)); + codecs.put(key.secure ? key : new CodecKey(mimeType, true), + Pair.create(codecName + ".secure", capabilities)); + } else { + // We can only cache this variant. The other should be listed explicitly. + boolean codecSecure = mediaCodecList.isSecurePlaybackSupported( + info.getCapabilitiesForType(supportedType)); + codecs.put(key.secure == codecSecure ? key : new CodecKey(mimeType, codecSecure), + Pair.create(codecName, capabilities)); + } + if (codecs.containsKey(key)) { + return codecs.get(key); + } } } } @@ -113,7 +133,7 @@ public class MediaCodecUtil { * @return Whether the specified profile is supported at the specified level. */ public static boolean isH264ProfileSupported(int profile, int level) { - Pair info = getMediaCodecInfo(MimeTypes.VIDEO_H264); + Pair info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false); if (info == null) { return false; } @@ -133,7 +153,7 @@ public class MediaCodecUtil { * @return the maximum frame size for an H264 stream that can be decoded on the device. */ public static int maxH264DecodableFrameSize() { - Pair info = getMediaCodecInfo(MimeTypes.VIDEO_H264); + Pair info = getMediaCodecInfo(MimeTypes.VIDEO_H264, false); if (info == null) { return 0; } @@ -177,4 +197,123 @@ public class MediaCodecUtil { } } + private interface MediaCodecListCompat { + + /** + * The number of codecs in the list. + */ + public int getCodecCount(); + + /** + * The info at the specified index in the list. + * + * @param index The index. + */ + public MediaCodecInfo getCodecInfoAt(int index); + + /** + * @return Returns whether secure decoders are explicitly listed, if present. + */ + public boolean secureDecodersExplicit(); + + /** + * Whether secure playback is supported for the given {@link CodecCapabilities}, which should + * have been obtained from a {@link MediaCodecInfo} obtained from this list. + *

+ * May only be called if {@link #secureDecodersExplicit()} returns true. + */ + public boolean isSecurePlaybackSupported(CodecCapabilities capabilities); + + } + + @TargetApi(21) + private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { + + private final MediaCodecInfo[] mediaCodecInfos; + + public MediaCodecListCompatV21(boolean includeSecure) { + int codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; + mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); + } + + @Override + public int getCodecCount() { + return mediaCodecInfos.length; + } + + @Override + public MediaCodecInfo getCodecInfoAt(int index) { + return mediaCodecInfos[index]; + } + + @Override + public boolean secureDecodersExplicit() { + return true; + } + + @Override + public boolean isSecurePlaybackSupported(CodecCapabilities capabilities) { + return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); + } + + } + + @SuppressWarnings("deprecation") + private static final class MediaCodecListCompatV16 implements MediaCodecListCompat { + + @Override + public int getCodecCount() { + return MediaCodecList.getCodecCount(); + } + + @Override + public MediaCodecInfo getCodecInfoAt(int index) { + return MediaCodecList.getCodecInfoAt(index); + } + + @Override + public boolean secureDecodersExplicit() { + return false; + } + + @Override + public boolean isSecurePlaybackSupported(CodecCapabilities capabilities) { + throw new UnsupportedOperationException(); + } + + } + + private static final class CodecKey { + + public final String mimeType; + public final boolean secure; + + public CodecKey(String mimeType, boolean secure) { + this.mimeType = mimeType; + this.secure = secure; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); + result = prime * result + (secure ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != CodecKey.class) { + return false; + } + CodecKey other = (CodecKey) obj; + return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; + } + + } + }