Select non-primary tunneling decoder

Issue: #3100
Issue: #5547
PiperOrigin-RevId: 243181217
This commit is contained in:
andrewlewis 2019-04-12 02:12:55 +01:00 committed by Oliver Woodman
parent b84c51434f
commit 53934c13b2
9 changed files with 202 additions and 90 deletions

View File

@ -61,6 +61,14 @@
present or newly absent. present or newly absent.
* Add support for reading AC-4 streams * Add support for reading AC-4 streams
([#5303](https://github.com/google/ExoPlayer/pull/5303)). ([#5303](https://github.com/google/ExoPlayer/pull/5303)).
* Video:
* Remove `MediaCodecSelector.DEFAULT_WITH_FALLBACK`. Apps should instead
signal that fallback should be used by passing `true` as the
`enableDecoderFallback` parameter when instantiating the video renderer.
* Support video tunneling when the decoder is not listed first for the MIME
type ([#3100](https://github.com/google/ExoPlayer/issues/3100)).
* Query `MediaCodecList.ALL_CODECS` when selecting a tunneling decoder
([#5547](https://github.com/google/ExoPlayer/issues/5547)).
* Add support for SHOUTcast ICY metadata * Add support for SHOUTcast ICY metadata
([#3735](https://github.com/google/ExoPlayer/issues/3735)). ([#3735](https://github.com/google/ExoPlayer/issues/3735)).
* CEA-608: Improved conformance to the specification * CEA-608: Improved conformance to the specification
@ -102,9 +110,6 @@
([#5698](https://github.com/google/ExoPlayer/issues/5698), ([#5698](https://github.com/google/ExoPlayer/issues/5698),
[#5694](https://github.com/google/ExoPlayer/issues/5694)). [#5694](https://github.com/google/ExoPlayer/issues/5694)).
* Move `PriorityTaskManager` from `DefaultLoadControl` to `SimpleExoPlayer`. * Move `PriorityTaskManager` from `DefaultLoadControl` to `SimpleExoPlayer`.
* Remove `MediaCodecSelector.DEFAULT_WITH_FALLBACK`. Apps should instead signal
that fallback should be used by passing `true` as the `enableDecoderFallback`
parameter when instantiating the video renderer.
### 2.9.6 ### ### 2.9.6 ###

View File

@ -289,11 +289,15 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
} }
List<MediaCodecInfo> decoderInfos = List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(format.sampleMimeType, requiresSecureDecryption); mediaCodecSelector.getDecoderInfos(
format.sampleMimeType, requiresSecureDecryption, /* requiresTunnelingDecoder= */ false);
if (decoderInfos.isEmpty()) { if (decoderInfos.isEmpty()) {
return requiresSecureDecryption return requiresSecureDecryption
&& !mediaCodecSelector && !mediaCodecSelector
.getDecoderInfos(format.sampleMimeType, /* requiresSecureDecoder= */ false) .getDecoderInfos(
format.sampleMimeType,
/* requiresSecureDecoder= */ false,
/* requiresTunnelingDecoder= */ false)
.isEmpty() .isEmpty()
? FORMAT_UNSUPPORTED_DRM ? FORMAT_UNSUPPORTED_DRM
: FORMAT_UNSUPPORTED_SUBTYPE; : FORMAT_UNSUPPORTED_SUBTYPE;
@ -322,7 +326,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return Collections.singletonList(passthroughDecoderInfo); return Collections.singletonList(passthroughDecoderInfo);
} }
} }
return super.getDecoderInfos(mediaCodecSelector, format, requiresSecureDecoder); return mediaCodecSelector.getDecoderInfos(
format.sampleMimeType, requiresSecureDecoder, /* requiresTunnelingDecoder= */ false);
} }
/** /**

View File

@ -441,11 +441,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* @return A list of {@link MediaCodecInfo}s corresponding to decoders. May be empty. * @return A list of {@link MediaCodecInfo}s corresponding to decoders. May be empty.
* @throws DecoderQueryException Thrown if there was an error querying decoders. * @throws DecoderQueryException Thrown if there was an error querying decoders.
*/ */
protected List<MediaCodecInfo> getDecoderInfos( protected abstract List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder) MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
throws DecoderQueryException { throws DecoderQueryException;
return mediaCodecSelector.getDecoderInfos(format.sampleMimeType, requiresSecureDecoder);
}
/** /**
* Configures a newly created {@link MediaCodec}. * Configures a newly created {@link MediaCodec}.

View File

@ -32,9 +32,11 @@ public interface MediaCodecSelector {
MediaCodecSelector DEFAULT = MediaCodecSelector DEFAULT =
new MediaCodecSelector() { new MediaCodecSelector() {
@Override @Override
public List<MediaCodecInfo> getDecoderInfos(String mimeType, boolean requiresSecureDecoder) public List<MediaCodecInfo> getDecoderInfos(
String mimeType, boolean requiresSecureDecoder, boolean requiresTunnelingDecoder)
throws DecoderQueryException { throws DecoderQueryException {
return MediaCodecUtil.getDecoderInfos(mimeType, requiresSecureDecoder); return MediaCodecUtil.getDecoderInfos(
mimeType, requiresSecureDecoder, requiresTunnelingDecoder);
} }
@Override @Override
@ -48,10 +50,12 @@ public interface MediaCodecSelector {
* *
* @param mimeType The MIME type for which a decoder is required. * @param mimeType The MIME type for which a decoder is required.
* @param requiresSecureDecoder Whether a secure decoder is required. * @param requiresSecureDecoder Whether a secure decoder is required.
* @param requiresTunnelingDecoder Whether a tunneling decoder is required.
* @return A list of {@link MediaCodecInfo}s corresponding to decoders. May be empty. * @return A list of {@link MediaCodecInfo}s corresponding to decoders. May be empty.
* @throws DecoderQueryException Thrown if there was an error querying decoders. * @throws DecoderQueryException Thrown if there was an error querying decoders.
*/ */
List<MediaCodecInfo> getDecoderInfos(String mimeType, boolean requiresSecureDecoder) List<MediaCodecInfo> getDecoderInfos(
String mimeType, boolean requiresSecureDecoder, boolean requiresTunnelingDecoder)
throws DecoderQueryException; throws DecoderQueryException;
/** /**

View File

@ -87,17 +87,19 @@ public final class MediaCodecUtil {
/** /**
* Optional call to warm the codec cache for a given mime type. * Optional call to warm the codec cache for a given mime type.
* <p> *
* Calling this method may speed up subsequent calls to {@link #getDecoderInfo(String, boolean)} * <p>Calling this method may speed up subsequent calls to {@link #getDecoderInfo(String, boolean,
* and {@link #getDecoderInfos(String, boolean)}. * boolean)} and {@link #getDecoderInfos(String, boolean, boolean)}.
* *
* @param mimeType The mime type. * @param mimeType The mime type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false * @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required. * unless secure decryption really is required.
* @param tunneling Whether the decoder is required to support tunneling. Always pass false unless
* tunneling really is required.
*/ */
public static void warmDecoderInfoCache(String mimeType, boolean secure) { public static void warmDecoderInfoCache(String mimeType, boolean secure, boolean tunneling) {
try { try {
getDecoderInfos(mimeType, secure); getDecoderInfos(mimeType, secure, tunneling);
} catch (DecoderQueryException e) { } catch (DecoderQueryException e) {
// Codec warming is best effort, so we can swallow the exception. // Codec warming is best effort, so we can swallow the exception.
Log.e(TAG, "Codec warming failed", e); Log.e(TAG, "Codec warming failed", e);
@ -112,7 +114,8 @@ public final class MediaCodecUtil {
*/ */
@Nullable @Nullable
public static MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException { public static MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException {
MediaCodecInfo decoderInfo = getDecoderInfo(MimeTypes.AUDIO_RAW, /* secure= */ false); MediaCodecInfo decoderInfo =
getDecoderInfo(MimeTypes.AUDIO_RAW, /* secure= */ false, /* tunneling= */ false);
return decoderInfo == null ? null : MediaCodecInfo.newPassthroughInstance(decoderInfo.name); return decoderInfo == null ? null : MediaCodecInfo.newPassthroughInstance(decoderInfo.name);
} }
@ -122,13 +125,15 @@ public final class MediaCodecUtil {
* @param mimeType The MIME type. * @param mimeType The MIME type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false * @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required. * unless secure decryption really is required.
* @param tunneling Whether the decoder is required to support tunneling. Always pass false unless
* tunneling really is required.
* @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder exists. * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder exists.
* @throws DecoderQueryException If there was an error querying the available decoders. * @throws DecoderQueryException If there was an error querying the available decoders.
*/ */
@Nullable @Nullable
public static MediaCodecInfo getDecoderInfo(String mimeType, boolean secure) public static MediaCodecInfo getDecoderInfo(String mimeType, boolean secure, boolean tunneling)
throws DecoderQueryException { throws DecoderQueryException {
List<MediaCodecInfo> decoderInfos = getDecoderInfos(mimeType, secure); List<MediaCodecInfo> decoderInfos = getDecoderInfos(mimeType, secure, tunneling);
return decoderInfos.isEmpty() ? null : decoderInfos.get(0); return decoderInfos.isEmpty() ? null : decoderInfos.get(0);
} }
@ -139,19 +144,23 @@ public final class MediaCodecUtil {
* @param mimeType The MIME type. * @param mimeType The MIME type.
* @param secure Whether the decoder is required to support secure decryption. Always pass false * @param secure Whether the decoder is required to support secure decryption. Always pass false
* unless secure decryption really is required. * unless secure decryption really is required.
* @param tunneling Whether the decoder is required to support tunneling. Always pass false unless
* tunneling really is required.
* @return A list of all {@link MediaCodecInfo}s for the given mime type, in the order given by * @return A list of all {@link MediaCodecInfo}s for the given mime type, in the order given by
* {@link MediaCodecList}. * {@link MediaCodecList}.
* @throws DecoderQueryException If there was an error querying the available decoders. * @throws DecoderQueryException If there was an error querying the available decoders.
*/ */
public static synchronized List<MediaCodecInfo> getDecoderInfos(String mimeType, boolean secure) public static synchronized List<MediaCodecInfo> getDecoderInfos(
throws DecoderQueryException { String mimeType, boolean secure, boolean tunneling) throws DecoderQueryException {
CodecKey key = new CodecKey(mimeType, secure); CodecKey key = new CodecKey(mimeType, secure, tunneling);
List<MediaCodecInfo> cachedDecoderInfos = decoderInfosCache.get(key); List<MediaCodecInfo> cachedDecoderInfos = decoderInfosCache.get(key);
if (cachedDecoderInfos != null) { if (cachedDecoderInfos != null) {
return cachedDecoderInfos; return cachedDecoderInfos;
} }
MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21 MediaCodecListCompat mediaCodecList =
? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16(); Util.SDK_INT >= 21
? new MediaCodecListCompatV21(secure, tunneling)
: new MediaCodecListCompatV16();
ArrayList<MediaCodecInfo> decoderInfos = getDecoderInfosInternal(key, mediaCodecList, mimeType); ArrayList<MediaCodecInfo> decoderInfos = getDecoderInfosInternal(key, mediaCodecList, mimeType);
if (secure && decoderInfos.isEmpty() && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) { if (secure && decoderInfos.isEmpty() && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) {
// Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the // Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the
@ -165,7 +174,7 @@ public final class MediaCodecUtil {
} }
if (MimeTypes.AUDIO_E_AC3_JOC.equals(mimeType)) { if (MimeTypes.AUDIO_E_AC3_JOC.equals(mimeType)) {
// E-AC3 decoders can decode JOC streams, but in 2-D rather than 3-D. // E-AC3 decoders can decode JOC streams, but in 2-D rather than 3-D.
CodecKey eac3Key = new CodecKey(MimeTypes.AUDIO_E_AC3, key.secure); CodecKey eac3Key = new CodecKey(MimeTypes.AUDIO_E_AC3, key.secure, key.tunneling);
ArrayList<MediaCodecInfo> eac3DecoderInfos = ArrayList<MediaCodecInfo> eac3DecoderInfos =
getDecoderInfosInternal(eac3Key, mediaCodecList, mimeType); getDecoderInfosInternal(eac3Key, mediaCodecList, mimeType);
decoderInfos.addAll(eac3DecoderInfos); decoderInfos.addAll(eac3DecoderInfos);
@ -184,7 +193,8 @@ public final class MediaCodecUtil {
public static int maxH264DecodableFrameSize() throws DecoderQueryException { public static int maxH264DecodableFrameSize() throws DecoderQueryException {
if (maxH264DecodableFrameSize == -1) { if (maxH264DecodableFrameSize == -1) {
int result = 0; int result = 0;
MediaCodecInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false); MediaCodecInfo decoderInfo =
getDecoderInfo(MimeTypes.VIDEO_H264, /* secure= */ false, /* tunneling= */ false);
if (decoderInfo != null) { if (decoderInfo != null) {
for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) { for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) {
result = Math.max(avcLevelToMaxFrameSize(profileLevel.level), result); result = Math.max(avcLevelToMaxFrameSize(profileLevel.level), result);
@ -202,12 +212,12 @@ public final class MediaCodecUtil {
* Returns profile and level (as defined by {@link CodecProfileLevel}) corresponding to the given * Returns profile and level (as defined by {@link CodecProfileLevel}) corresponding to the given
* codec description string (as defined by RFC 6381). * codec description string (as defined by RFC 6381).
* *
* @param codec A codec description string, as defined by RFC 6381. * @param codec A codec description string, as defined by RFC 6381, or {@code null} if not known.
* @return A pair (profile constant, level constant) if {@code codec} is well-formed and * @return A pair (profile constant, level constant) if {@code codec} is well-formed and
* recognized, or null otherwise * recognized, or null otherwise
*/ */
@Nullable @Nullable
public static Pair<Integer, Integer> getCodecProfileAndLevel(String codec) { public static Pair<Integer, Integer> getCodecProfileAndLevel(@Nullable String codec) {
if (codec == null) { if (codec == null) {
return null; return null;
} }
@ -254,36 +264,58 @@ public final class MediaCodecUtil {
// Note: MediaCodecList is sorted by the framework such that the best decoders come first. // Note: MediaCodecList is sorted by the framework such that the best decoders come first.
for (int i = 0; i < numberOfCodecs; i++) { for (int i = 0; i < numberOfCodecs; i++) {
android.media.MediaCodecInfo codecInfo = mediaCodecList.getCodecInfoAt(i); android.media.MediaCodecInfo codecInfo = mediaCodecList.getCodecInfoAt(i);
String codecName = codecInfo.getName(); String name = codecInfo.getName();
String codecSupportedType = String supportedType =
getCodecSupportedType(codecInfo, codecName, secureDecodersExplicit, requestedMimeType); getCodecSupportedType(codecInfo, name, secureDecodersExplicit, requestedMimeType);
if (codecSupportedType != null) { if (supportedType == null) {
try { continue;
CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(codecSupportedType); }
boolean secure = mediaCodecList.isSecurePlaybackSupported(mimeType, capabilities); try {
boolean forceDisableAdaptive = codecNeedsDisableAdaptationWorkaround(codecName); CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(supportedType);
if ((secureDecodersExplicit && key.secure == secure) boolean tunnelingSupported =
|| (!secureDecodersExplicit && !key.secure)) { mediaCodecList.isFeatureSupported(
decoderInfos.add( CodecCapabilities.FEATURE_TunneledPlayback, supportedType, capabilities);
MediaCodecInfo.newInstance( boolean tunnelingRequired =
codecName, mimeType, capabilities, forceDisableAdaptive, false)); mediaCodecList.isFeatureRequired(
} else if (!secureDecodersExplicit && secure) { CodecCapabilities.FEATURE_TunneledPlayback, supportedType, capabilities);
decoderInfos.add( if ((!key.tunneling && tunnelingRequired) || (key.tunneling && !tunnelingSupported)) {
MediaCodecInfo.newInstance( continue;
codecName + ".secure", mimeType, capabilities, forceDisableAdaptive, true)); }
// It only makes sense to have one synthesized secure decoder, return immediately. boolean secureSupported =
return decoderInfos; mediaCodecList.isFeatureSupported(
} CodecCapabilities.FEATURE_SecurePlayback, supportedType, capabilities);
} catch (Exception e) { boolean secureRequired =
if (Util.SDK_INT <= 23 && !decoderInfos.isEmpty()) { mediaCodecList.isFeatureRequired(
// Suppress error querying secondary codec capabilities up to API level 23. CodecCapabilities.FEATURE_SecurePlayback, supportedType, capabilities);
Log.e(TAG, "Skipping codec " + codecName + " (failed to query capabilities)"); if ((!key.secure && secureRequired) || (key.secure && !secureSupported)) {
} else { continue;
// Rethrow error querying primary codec capabilities, or secondary codec }
// capabilities if API level is greater than 23. boolean forceDisableAdaptive = codecNeedsDisableAdaptationWorkaround(name);
Log.e(TAG, "Failed to query codec " + codecName + " (" + codecSupportedType + ")"); if ((secureDecodersExplicit && key.secure == secureSupported)
throw e; || (!secureDecodersExplicit && !key.secure)) {
} decoderInfos.add(
MediaCodecInfo.newInstance(
name, mimeType, capabilities, forceDisableAdaptive, /* forceSecure= */ false));
} else if (!secureDecodersExplicit && secureSupported) {
decoderInfos.add(
MediaCodecInfo.newInstance(
name + ".secure",
mimeType,
capabilities,
forceDisableAdaptive,
/* forceSecure= */ true));
// It only makes sense to have one synthesized secure decoder, return immediately.
return decoderInfos;
}
} catch (Exception e) {
if (Util.SDK_INT <= 23 && !decoderInfos.isEmpty()) {
// Suppress error querying secondary codec capabilities up to API level 23.
Log.e(TAG, "Skipping codec " + name + " (failed to query capabilities)");
} else {
// Rethrow error querying primary codec capabilities, or secondary codec
// capabilities if API level is greater than 23.
Log.e(TAG, "Failed to query codec " + name + " (" + supportedType + ")");
throw e;
} }
} }
} }
@ -364,7 +396,8 @@ public final class MediaCodecUtil {
// Work around https://github.com/google/ExoPlayer/issues/1528 and // Work around https://github.com/google/ExoPlayer/issues/1528 and
// https://github.com/google/ExoPlayer/issues/3171. // https://github.com/google/ExoPlayer/issues/3171.
if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name) if (Util.SDK_INT < 18
&& "OMX.MTK.AUDIO.DECODER.AAC".equals(name)
&& ("a70".equals(Util.DEVICE) && ("a70".equals(Util.DEVICE)
|| ("Xiaomi".equals(Util.MANUFACTURER) && Util.DEVICE.startsWith("HM")))) { || ("Xiaomi".equals(Util.MANUFACTURER) && Util.DEVICE.startsWith("HM")))) {
return false; return false;
@ -417,9 +450,12 @@ public final class MediaCodecUtil {
// Work around https://github.com/google/ExoPlayer/issues/548. // Work around https://github.com/google/ExoPlayer/issues/548.
// VP8 decoder on Samsung Galaxy S3/S4/S4 Mini/Tab 3/Note 2 does not render video. // VP8 decoder on Samsung Galaxy S3/S4/S4 Mini/Tab 3/Note 2 does not render video.
if (Util.SDK_INT <= 19 if (Util.SDK_INT <= 19
&& "OMX.SEC.vp8.dec".equals(name) && "samsung".equals(Util.MANUFACTURER) && "OMX.SEC.vp8.dec".equals(name)
&& (Util.DEVICE.startsWith("d2") || Util.DEVICE.startsWith("serrano") && "samsung".equals(Util.MANUFACTURER)
|| Util.DEVICE.startsWith("jflte") || Util.DEVICE.startsWith("santos") && (Util.DEVICE.startsWith("d2")
|| Util.DEVICE.startsWith("serrano")
|| Util.DEVICE.startsWith("jflte")
|| Util.DEVICE.startsWith("santos")
|| Util.DEVICE.startsWith("t0"))) { || Util.DEVICE.startsWith("t0"))) {
return false; return false;
} }
@ -659,12 +695,11 @@ public final class MediaCodecUtil {
*/ */
boolean secureDecodersExplicit(); boolean secureDecodersExplicit();
/** /** Whether the specified {@link CodecCapabilities} {@code feature} is supported. */
* Whether secure playback is supported for the given {@link CodecCapabilities}, which should boolean isFeatureSupported(String feature, String mimeType, CodecCapabilities capabilities);
* have been obtained from a {@link android.media.MediaCodecInfo} obtained from this list.
*/
boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities);
/** Whether the specified {@link CodecCapabilities} {@code feature} is required. */
boolean isFeatureRequired(String feature, String mimeType, CodecCapabilities capabilities);
} }
@TargetApi(21) @TargetApi(21)
@ -674,8 +709,11 @@ public final class MediaCodecUtil {
private android.media.MediaCodecInfo[] mediaCodecInfos; private android.media.MediaCodecInfo[] mediaCodecInfos;
public MediaCodecListCompatV21(boolean includeSecure) { public MediaCodecListCompatV21(boolean includeSecure, boolean includeTunneling) {
codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; codecKind =
includeSecure || includeTunneling
? MediaCodecList.ALL_CODECS
: MediaCodecList.REGULAR_CODECS;
} }
@Override @Override
@ -696,8 +734,15 @@ public final class MediaCodecUtil {
} }
@Override @Override
public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) { public boolean isFeatureSupported(
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); String feature, String mimeType, CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(feature);
}
@Override
public boolean isFeatureRequired(
String feature, String mimeType, CodecCapabilities capabilities) {
return capabilities.isFeatureRequired(feature);
} }
private void ensureMediaCodecInfosInitialized() { private void ensureMediaCodecInfosInitialized() {
@ -727,10 +772,18 @@ public final class MediaCodecUtil {
} }
@Override @Override
public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) { public boolean isFeatureSupported(
String feature, String mimeType, CodecCapabilities capabilities) {
// Secure decoders weren't explicitly listed prior to API level 21. We assume that a secure // Secure decoders weren't explicitly listed prior to API level 21. We assume that a secure
// H264 decoder exists. // H264 decoder exists.
return MimeTypes.VIDEO_H264.equals(mimeType); return CodecCapabilities.FEATURE_SecurePlayback.equals(feature)
&& MimeTypes.VIDEO_H264.equals(mimeType);
}
@Override
public boolean isFeatureRequired(
String feature, String mimeType, CodecCapabilities capabilities) {
return false;
} }
} }
@ -739,10 +792,12 @@ public final class MediaCodecUtil {
public final String mimeType; public final String mimeType;
public final boolean secure; public final boolean secure;
public final boolean tunneling;
public CodecKey(String mimeType, boolean secure) { public CodecKey(String mimeType, boolean secure, boolean tunneling) {
this.mimeType = mimeType; this.mimeType = mimeType;
this.secure = secure; this.secure = secure;
this.tunneling = tunneling;
} }
@Override @Override
@ -751,6 +806,7 @@ public final class MediaCodecUtil {
int result = 1; int result = 1;
result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode());
result = prime * result + (secure ? 1231 : 1237); result = prime * result + (secure ? 1231 : 1237);
result = prime * result + (tunneling ? 1231 : 1237);
return result; return result;
} }
@ -763,7 +819,9 @@ public final class MediaCodecUtil {
return false; return false;
} }
CodecKey other = (CodecKey) obj; CodecKey other = (CodecKey) obj;
return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; return TextUtils.equals(mimeType, other.mimeType)
&& secure == other.secure
&& tunneling == other.tunneling;
} }
} }
@ -901,5 +959,4 @@ public final class MediaCodecUtil {
MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(39, CodecProfileLevel.AACObjectELD); MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(39, CodecProfileLevel.AACObjectELD);
MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(42, CodecProfileLevel.AACObjectXHE); MP4A_AUDIO_OBJECT_TYPE_TO_PROFILE.put(42, CodecProfileLevel.AACObjectXHE);
} }
} }

View File

@ -52,6 +52,7 @@ import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher; import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
@ -304,11 +305,14 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
} }
List<MediaCodecInfo> decoderInfos = List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(format.sampleMimeType, requiresSecureDecryption); getDecoderInfos(mediaCodecSelector, format, requiresSecureDecryption);
if (decoderInfos.isEmpty()) { if (decoderInfos.isEmpty()) {
return requiresSecureDecryption return requiresSecureDecryption
&& !mediaCodecSelector && !mediaCodecSelector
.getDecoderInfos(format.sampleMimeType, /* requiresSecureDecoder= */ false) .getDecoderInfos(
format.sampleMimeType,
/* requiresSecureDecoder= */ false,
/* requiresTunnelingDecoder= */ false)
.isEmpty() .isEmpty()
? FORMAT_UNSUPPORTED_DRM ? FORMAT_UNSUPPORTED_DRM
: FORMAT_UNSUPPORTED_SUBTYPE; : FORMAT_UNSUPPORTED_SUBTYPE;
@ -323,11 +327,34 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
decoderInfo.isSeamlessAdaptationSupported(format) decoderInfo.isSeamlessAdaptationSupported(format)
? ADAPTIVE_SEAMLESS ? ADAPTIVE_SEAMLESS
: ADAPTIVE_NOT_SEAMLESS; : ADAPTIVE_NOT_SEAMLESS;
int tunnelingSupport = decoderInfo.tunneling ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED; int tunnelingSupport = TUNNELING_NOT_SUPPORTED;
if (isFormatSupported) {
List<MediaCodecInfo> tunnelingDecoderInfos =
mediaCodecSelector.getDecoderInfos(
format.sampleMimeType,
requiresSecureDecryption,
/* requiresTunnelingDecoder= */ true);
if (!tunnelingDecoderInfos.isEmpty()) {
MediaCodecInfo tunnelingDecoderInfo = tunnelingDecoderInfos.get(0);
if (tunnelingDecoderInfo.isFormatSupported(format)
&& tunnelingDecoderInfo.isSeamlessAdaptationSupported(format)) {
tunnelingSupport = TUNNELING_SUPPORTED;
}
}
}
int formatSupport = isFormatSupported ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; int formatSupport = isFormatSupported ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES;
return adaptiveSupport | tunnelingSupport | formatSupport; return adaptiveSupport | tunnelingSupport | formatSupport;
} }
@Override
protected List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
throws DecoderQueryException {
List<MediaCodecInfo> decoderInfos =
mediaCodecSelector.getDecoderInfos(format.sampleMimeType, requiresSecureDecoder, tunneling);
return Collections.unmodifiableList(decoderInfos);
}
@Override @Override
protected void onEnabled(boolean joining) throws ExoPlaybackException { protected void onEnabled(boolean joining) throws ExoPlaybackException {
super.onEnabled(joining); super.onEnabled(joining);

View File

@ -628,7 +628,9 @@ public final class DashStreamingTest {
@Test @Test
public void testDecoderInfoH264() throws DecoderQueryException { public void testDecoderInfoH264() throws DecoderQueryException {
MediaCodecInfo decoderInfo = MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H264, false); MediaCodecInfo decoderInfo =
MediaCodecUtil.getDecoderInfo(
MimeTypes.VIDEO_H264, /* secure= */ false, /* tunneling= */ false);
assertThat(decoderInfo).isNotNull(); assertThat(decoderInfo).isNotNull();
assertThat(Util.SDK_INT < 21 || decoderInfo.adaptive).isTrue(); assertThat(Util.SDK_INT < 21 || decoderInfo.adaptive).isTrue();
} }
@ -639,7 +641,11 @@ public final class DashStreamingTest {
// Pass. // Pass.
return; return;
} }
assertThat(MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_H265, false).adaptive).isTrue(); assertThat(
MediaCodecUtil.getDecoderInfo(
MimeTypes.VIDEO_H265, /* secure= */ false, /* tunneling= */ false)
.adaptive)
.isTrue();
} }
@Test @Test
@ -648,13 +654,18 @@ public final class DashStreamingTest {
// Pass. // Pass.
return; return;
} }
assertThat(MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_VP9, false).adaptive).isTrue(); assertThat(
MediaCodecUtil.getDecoderInfo(
MimeTypes.VIDEO_VP9, /* secure= */ false, /* tunneling= */ false)
.adaptive)
.isTrue();
} }
// Internal. // Internal.
private static boolean shouldSkipAdaptiveTest(String mimeType) throws DecoderQueryException { private static boolean shouldSkipAdaptiveTest(String mimeType) throws DecoderQueryException {
MediaCodecInfo decoderInfo = MediaCodecUtil.getDecoderInfo(mimeType, false); MediaCodecInfo decoderInfo =
MediaCodecUtil.getDecoderInfo(mimeType, /* secure= */ false, /* tunneling= */ false);
return decoderInfo == null || !decoderInfo.adaptive; return decoderInfo == null || !decoderInfo.adaptive;
} }

View File

@ -106,7 +106,8 @@ public final class DashTestRunner {
if (Util.SDK_INT >= 18) { if (Util.SDK_INT >= 18) {
try { try {
// Force L3 if secure decoder is not available. // Force L3 if secure decoder is not available.
if (MediaCodecUtil.getDecoderInfo(mimeType, true) == null) { if (MediaCodecUtil.getDecoderInfo(mimeType, /* secure= */ true, /* tunneling= */ false)
== null) {
return false; return false;
} }
MediaDrm mediaDrm = MediaDrmBuilder.build(); MediaDrm mediaDrm = MediaDrmBuilder.build();

View File

@ -82,12 +82,16 @@ public class EnumerateDecodersTest {
} }
private void enumerateDecoders(String mimeType) throws DecoderQueryException { private void enumerateDecoders(String mimeType) throws DecoderQueryException {
logDecoderInfos(mimeType, /* secure= */ false); logDecoderInfos(mimeType, /* secure= */ false, /* tunneling= */ false);
logDecoderInfos(mimeType, /* secure= */ true); logDecoderInfos(mimeType, /* secure= */ true, /* tunneling= */ false);
logDecoderInfos(mimeType, /* secure= */ false, /* tunneling= */ true);
logDecoderInfos(mimeType, /* secure= */ true, /* tunneling= */ true);
} }
private void logDecoderInfos(String mimeType, boolean secure) throws DecoderQueryException { private void logDecoderInfos(String mimeType, boolean secure, boolean tunneling)
List<MediaCodecInfo> mediaCodecInfos = MediaCodecUtil.getDecoderInfos(mimeType, secure); throws DecoderQueryException {
List<MediaCodecInfo> mediaCodecInfos =
MediaCodecUtil.getDecoderInfos(mimeType, secure, tunneling);
for (MediaCodecInfo mediaCodecInfo : mediaCodecInfos) { for (MediaCodecInfo mediaCodecInfo : mediaCodecInfos) {
CodecCapabilities capabilities = Assertions.checkNotNull(mediaCodecInfo.capabilities); CodecCapabilities capabilities = Assertions.checkNotNull(mediaCodecInfo.capabilities);
metricsLogger.logMetric( metricsLogger.logMetric(