Commit 0b631b05 by andrewlewis Committed by Oliver Woodman

Optionally fall back to other decoders if init fails

Codec initialization may fail in creation, configuration or when start()ing the
codec. If codec initialization fails, there may be other codecs available that
could handle the same format, but currently ExoPlayer can only try to use the
first listed codec for the input format and gives up if it fails to initialize.

This change implements support for optionally falling back to alternative
decoders if initialization fails. MediaCodecSelector can now return a list of
decoders to try in priority order, and use the Format when choosing a codec.
With the default implementation, the codecs and order come from MediaCodecList,
and matches the order used internally by MediaCodec.createDecoderByType (which
implements the same kind of fallback though only to the creation step, without
configuring/starting the codec).

This feature is useful for apps that want to play several videos concurrently on
devices that have software decoders (like OMX.google.h264.decoder), as the new
behavior allows new codecs to be created when no hardware-accelerated decoders
are available.

The list of available codecs is queried when initializing the codec after a
format change that requires a new codec to be instantiated. When a decoder fails
to initialize it is removed from the list of available decoders and won't be
tried again until the next format change (or until the renderer is disabled).

Note: this change does not affect the renderer capabilities API, as when
checking format support we don't know which codec will be used.

Issue: #273

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=203242285
parent 93083362
...@@ -71,6 +71,10 @@ ...@@ -71,6 +71,10 @@
`AnalyticsListener` callbacks. This uri is the redirected uri if redirection `AnalyticsListener` callbacks. This uri is the redirected uri if redirection
occurred ([#2054](https://github.com/google/ExoPlayer/issues/2054)). occurred ([#2054](https://github.com/google/ExoPlayer/issues/2054)).
* Improved compatibility with FireOS devices. * Improved compatibility with FireOS devices.
* Allow `MediaCodecSelector`s to return multiple compatible decoders for
`MediaCodecRenderer`, and provide an (optional) `MediaCodecSelector` that
falls back to less preferred decoders like `MediaCodec.createDecoderByType`
([#273](https://github.com/google/ExoPlayer/issues/273)).
### 2.8.2 ### ### 2.8.2 ###
......
...@@ -45,6 +45,8 @@ import com.google.android.exoplayer2.util.MediaClock; ...@@ -45,6 +45,8 @@ import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
/** /**
* Decodes and renders audio using {@link MediaCodec} and an {@link AudioSink}. * Decodes and renders audio using {@link MediaCodec} and an {@link AudioSink}.
...@@ -262,15 +264,21 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -262,15 +264,21 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
requiresSecureDecryption |= drmInitData.get(i).requiresSecureDecryption; requiresSecureDecryption |= drmInitData.get(i).requiresSecureDecryption;
} }
} }
MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, List<MediaCodecInfo> decoderInfos =
requiresSecureDecryption); mediaCodecSelector.getDecoderInfos(format, requiresSecureDecryption);
if (decoderInfo == null) { if (decoderInfos.isEmpty()) {
return requiresSecureDecryption && mediaCodecSelector.getDecoderInfo(mimeType, false) != null return requiresSecureDecryption
? FORMAT_UNSUPPORTED_DRM : FORMAT_UNSUPPORTED_SUBTYPE; && !mediaCodecSelector
.getDecoderInfos(format, /* requiresSecureDecoder= */ false)
.isEmpty()
? FORMAT_UNSUPPORTED_DRM
: FORMAT_UNSUPPORTED_SUBTYPE;
} }
if (!supportsFormatDrm) { if (!supportsFormatDrm) {
return FORMAT_UNSUPPORTED_DRM; return FORMAT_UNSUPPORTED_DRM;
} }
// Check capabilities for the first decoder in the list, which takes priority.
MediaCodecInfo decoderInfo = decoderInfos.get(0);
// Note: We assume support for unknown sampleRate and channelCount. // Note: We assume support for unknown sampleRate and channelCount.
boolean decoderCapable = Util.SDK_INT < 21 boolean decoderCapable = Util.SDK_INT < 21
|| ((format.sampleRate == Format.NO_VALUE || ((format.sampleRate == Format.NO_VALUE
...@@ -282,15 +290,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -282,15 +290,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected MediaCodecInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, protected List<MediaCodecInfo> getDecoderInfos(
Format format, boolean requiresSecureDecoder) throws DecoderQueryException { MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
throws DecoderQueryException {
if (allowPassthrough(format.sampleMimeType)) { if (allowPassthrough(format.sampleMimeType)) {
MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo(); MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo();
if (passthroughDecoderInfo != null) { if (passthroughDecoderInfo != null) {
return passthroughDecoderInfo; return Collections.singletonList(passthroughDecoderInfo);
} }
} }
return super.getDecoderInfo(mediaCodecSelector, format, requiresSecureDecoder); return super.getDecoderInfos(mediaCodecSelector, format, requiresSecureDecoder);
} }
/** /**
......
...@@ -159,6 +159,11 @@ public final class MediaCodecInfo { ...@@ -159,6 +159,11 @@ public final class MediaCodecInfo {
secure = forceSecure || (capabilities != null && isSecure(capabilities)); secure = forceSecure || (capabilities != null && isSecure(capabilities));
} }
@Override
public String toString() {
return name;
}
/** /**
* The profile levels supported by the decoder. * The profile levels supported by the decoder.
* *
......
...@@ -23,6 +23,7 @@ import android.media.MediaCrypto; ...@@ -23,6 +23,7 @@ import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.CheckResult;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
...@@ -46,6 +47,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -46,6 +47,7 @@ import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -84,22 +86,64 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -84,22 +86,64 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/ */
public final String diagnosticInfo; public final String diagnosticInfo;
/**
* If the decoder failed to initialize and another decoder being used as a fallback also failed
* to initialize, the {@link DecoderInitializationException} for the fallback decoder. Null if
* there was no fallback decoder or no suitable decoders were found.
*/
public final @Nullable DecoderInitializationException fallbackDecoderInitializationException;
public DecoderInitializationException(Format format, Throwable cause, public DecoderInitializationException(Format format, Throwable cause,
boolean secureDecoderRequired, int errorCode) { boolean secureDecoderRequired, int errorCode) {
super("Decoder init failed: [" + errorCode + "], " + format, cause); this(
this.mimeType = format.sampleMimeType; "Decoder init failed: [" + errorCode + "], " + format,
this.secureDecoderRequired = secureDecoderRequired; cause,
this.decoderName = null; format.sampleMimeType,
this.diagnosticInfo = buildCustomDiagnosticInfo(errorCode); secureDecoderRequired,
/* decoderName= */ null,
buildCustomDiagnosticInfo(errorCode),
/* fallbackDecoderInitializationException= */ null);
} }
public DecoderInitializationException(Format format, Throwable cause, public DecoderInitializationException(Format format, Throwable cause,
boolean secureDecoderRequired, String decoderName) { boolean secureDecoderRequired, String decoderName) {
super("Decoder init failed: " + decoderName + ", " + format, cause); this(
this.mimeType = format.sampleMimeType; "Decoder init failed: " + decoderName + ", " + format,
cause,
format.sampleMimeType,
secureDecoderRequired,
decoderName,
Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null,
/* fallbackDecoderInitializationException= */ null);
}
private DecoderInitializationException(
String message,
Throwable cause,
String mimeType,
boolean secureDecoderRequired,
@Nullable String decoderName,
@Nullable String diagnosticInfo,
@Nullable DecoderInitializationException fallbackDecoderInitializationException) {
super(message, cause);
this.mimeType = mimeType;
this.secureDecoderRequired = secureDecoderRequired; this.secureDecoderRequired = secureDecoderRequired;
this.decoderName = decoderName; this.decoderName = decoderName;
this.diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null; this.diagnosticInfo = diagnosticInfo;
this.fallbackDecoderInitializationException = fallbackDecoderInitializationException;
}
@CheckResult
private DecoderInitializationException copyWithFallbackException(
DecoderInitializationException fallbackException) {
return new DecoderInitializationException(
getMessage(),
getCause(),
mimeType,
secureDecoderRequired,
decoderName,
diagnosticInfo,
fallbackException);
} }
@TargetApi(21) @TargetApi(21)
...@@ -230,7 +274,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -230,7 +274,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private DrmSession<FrameworkMediaCrypto> drmSession; private DrmSession<FrameworkMediaCrypto> drmSession;
private DrmSession<FrameworkMediaCrypto> pendingDrmSession; private DrmSession<FrameworkMediaCrypto> pendingDrmSession;
private MediaCodec codec; private MediaCodec codec;
private MediaCodecInfo codecInfo; private @Nullable ArrayDeque<MediaCodecInfo> availableCodecInfos;
private @Nullable DecoderInitializationException preferredDecoderInitializationException;
private @Nullable MediaCodecInfo codecInfo;
private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode; private @AdaptationWorkaroundMode int codecAdaptationWorkaroundMode;
private boolean codecNeedsDiscardToSpsWorkaround; private boolean codecNeedsDiscardToSpsWorkaround;
private boolean codecNeedsFlushWorkaround; private boolean codecNeedsFlushWorkaround;
...@@ -318,18 +364,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -318,18 +364,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
throws DecoderQueryException; throws DecoderQueryException;
/** /**
* Returns a {@link MediaCodecInfo} for a given format. * Returns a list of decoders that can decode media in the specified format, in priority order.
* *
* @param mediaCodecSelector The decoder selector. * @param mediaCodecSelector The decoder selector.
* @param format The format for which a decoder is required. * @param format The format for which a decoder is required.
* @param requiresSecureDecoder Whether a secure decoder is required. * @param requiresSecureDecoder Whether a secure decoder is required.
* @return A {@link MediaCodecInfo} describing the decoder to instantiate, or null if no * @return A list of {@link MediaCodecInfo}s corresponding to decoders. May be empty.
* suitable decoder exists.
* @throws DecoderQueryException Thrown if there was an error querying decoders. * @throws DecoderQueryException Thrown if there was an error querying decoders.
*/ */
protected MediaCodecInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, protected List<MediaCodecInfo> getDecoderInfos(
Format format, boolean requiresSecureDecoder) throws DecoderQueryException { MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
return mediaCodecSelector.getDecoderInfo(format.sampleMimeType, requiresSecureDecoder); throws DecoderQueryException {
return mediaCodecSelector.getDecoderInfos(format, requiresSecureDecoder);
} }
/** /**
...@@ -380,34 +426,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -380,34 +426,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
} }
if (codecInfo == null) { try {
try { if (!initCodecWithFallback(wrappedMediaCrypto, drmSessionRequiresSecureDecoder)) {
codecInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder); // We can't initialize a codec yet.
if (codecInfo == null && drmSessionRequiresSecureDecoder) { return;
// The drm session indicates that a secure decoder is required, but the device does not
// have one. Assuming that supportsFormat indicated support for the media being played, we
// know that it does not require a secure output path. Most CDM implementations allow
// playback to proceed with a non-secure decoder in this case, so we try our luck.
codecInfo = getDecoderInfo(mediaCodecSelector, format, false);
if (codecInfo != null) {
Log.w(TAG, "Drm session requires secure decoder for " + mimeType + ", but "
+ "no secure decoder available. Trying to proceed with " + codecInfo.name + ".");
}
}
} catch (DecoderQueryException e) {
throwDecoderInitError(new DecoderInitializationException(format, e,
drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR));
}
if (codecInfo == null) {
throwDecoderInitError(new DecoderInitializationException(format, null,
drmSessionRequiresSecureDecoder,
DecoderInitializationException.NO_SUITABLE_DECODER_ERROR));
} }
} } catch (DecoderInitializationException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex());
if (!shouldInitCodec(codecInfo)) {
return;
} }
String codecName = codecInfo.name; String codecName = codecInfo.name;
...@@ -418,38 +443,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -418,38 +443,16 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName);
codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName);
codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format);
try { codecHotswapDeadlineMs =
long codecInitializingTimestamp = SystemClock.elapsedRealtime(); getState() == STATE_STARTED
TraceUtil.beginSection("createCodec:" + codecName); ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS)
codec = MediaCodec.createByCodecName(codecName); : C.TIME_UNSET;
TraceUtil.endSection();
TraceUtil.beginSection("configureCodec");
configureCodec(codecInfo, codec, format, wrappedMediaCrypto);
TraceUtil.endSection();
TraceUtil.beginSection("startCodec");
codec.start();
TraceUtil.endSection();
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
onCodecInitialized(codecName, codecInitializedTimestamp,
codecInitializedTimestamp - codecInitializingTimestamp);
getCodecBuffers();
} catch (Exception e) {
throwDecoderInitError(new DecoderInitializationException(format, e,
drmSessionRequiresSecureDecoder, codecName));
}
codecHotswapDeadlineMs = getState() == STATE_STARTED
? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : C.TIME_UNSET;
resetInputBuffer(); resetInputBuffer();
resetOutputBuffer(); resetOutputBuffer();
waitingForFirstSyncFrame = true; waitingForFirstSyncFrame = true;
decoderCounters.decoderInitCount++; decoderCounters.decoderInitCount++;
} }
private void throwDecoderInitError(DecoderInitializationException e)
throws ExoPlaybackException {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
protected boolean shouldInitCodec(MediaCodecInfo codecInfo) { protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
return true; return true;
} }
...@@ -479,6 +482,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -479,6 +482,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Override @Override
protected void onDisabled() { protected void onDisabled() {
format = null; format = null;
availableCodecInfos = null;
try { try {
releaseCodec(); releaseCodec();
} finally { } finally {
...@@ -631,6 +635,158 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -631,6 +635,158 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
} }
private boolean initCodecWithFallback(MediaCrypto crypto, boolean drmSessionRequiresSecureDecoder)
throws DecoderInitializationException {
if (availableCodecInfos == null) {
try {
availableCodecInfos =
new ArrayDeque<>(getAvailableCodecInfos(drmSessionRequiresSecureDecoder));
preferredDecoderInitializationException = null;
} catch (DecoderQueryException e) {
throw new DecoderInitializationException(
format,
e,
drmSessionRequiresSecureDecoder,
DecoderInitializationException.DECODER_QUERY_ERROR);
}
}
if (availableCodecInfos.isEmpty()) {
throw new DecoderInitializationException(
format,
/* cause= */ null,
drmSessionRequiresSecureDecoder,
DecoderInitializationException.NO_SUITABLE_DECODER_ERROR);
}
while (true) {
MediaCodecInfo codecInfo = availableCodecInfos.peekFirst();
if (!shouldInitCodec(codecInfo)) {
return false;
}
try {
initCodec(codecInfo, crypto);
return true;
} catch (Exception e) {
Log.w(TAG, "Failed to initialize decoder: " + codecInfo, e);
// This codec failed to initialize, so fall back to the next codec in the list (if any). We
// won't try to use this codec again unless there's a format change or the renderer is
// disabled and re-enabled.
availableCodecInfos.removeFirst();
DecoderInitializationException exception =
new DecoderInitializationException(
format, e, drmSessionRequiresSecureDecoder, codecInfo.name);
if (preferredDecoderInitializationException == null) {
preferredDecoderInitializationException = exception;
} else {
preferredDecoderInitializationException =
preferredDecoderInitializationException.copyWithFallbackException(exception);
}
if (availableCodecInfos.isEmpty()) {
throw preferredDecoderInitializationException;
}
}
}
}
private List<MediaCodecInfo> getAvailableCodecInfos(boolean drmSessionRequiresSecureDecoder)
throws DecoderQueryException {
List<MediaCodecInfo> codecInfos =
getDecoderInfos(mediaCodecSelector, format, drmSessionRequiresSecureDecoder);
if (codecInfos.isEmpty() && drmSessionRequiresSecureDecoder) {
// The drm session indicates that a secure decoder is required, but the device does not
// have one. Assuming that supportsFormat indicated support for the media being played, we
// know that it does not require a secure output path. Most CDM implementations allow
// playback to proceed with a non-secure decoder in this case, so we try our luck.
codecInfos = getDecoderInfos(mediaCodecSelector, format, /* requiresSecureDecoder= */ false);
if (!codecInfos.isEmpty()) {
Log.w(
TAG,
"Drm session requires secure decoder for "
+ format.sampleMimeType
+ ", but no secure decoder available. Trying to proceed with "
+ codecInfos
+ ".");
}
}
return codecInfos;
}
private void initCodec(MediaCodecInfo codecInfo, MediaCrypto crypto) throws Exception {
long codecInitializingTimestamp;
long codecInitializedTimestamp;
MediaCodec codec = null;
String name = codecInfo.name;
try {
codecInitializingTimestamp = SystemClock.elapsedRealtime();
TraceUtil.beginSection("createCodec:" + name);
codec = MediaCodec.createByCodecName(name);
TraceUtil.endSection();
TraceUtil.beginSection("configureCodec");
configureCodec(codecInfo, codec, format, crypto);
TraceUtil.endSection();
TraceUtil.beginSection("startCodec");
codec.start();
TraceUtil.endSection();
codecInitializedTimestamp = SystemClock.elapsedRealtime();
getCodecBuffers();
} catch (Exception e) {
if (codec != null) {
resetCodecBuffers();
codec.release();
}
throw e;
}
this.codec = codec;
this.codecInfo = codecInfo;
long elapsed = codecInitializedTimestamp - codecInitializingTimestamp;
onCodecInitialized(name, codecInitializedTimestamp, elapsed);
}
private void getCodecBuffers() {
if (Util.SDK_INT < 21) {
inputBuffers = codec.getInputBuffers();
outputBuffers = codec.getOutputBuffers();
}
}
private void resetCodecBuffers() {
if (Util.SDK_INT < 21) {
inputBuffers = null;
outputBuffers = null;
}
}
private ByteBuffer getInputBuffer(int inputIndex) {
if (Util.SDK_INT >= 21) {
return codec.getInputBuffer(inputIndex);
} else {
return inputBuffers[inputIndex];
}
}
private ByteBuffer getOutputBuffer(int outputIndex) {
if (Util.SDK_INT >= 21) {
return codec.getOutputBuffer(outputIndex);
} else {
return outputBuffers[outputIndex];
}
}
private boolean hasOutputBuffer() {
return outputIndex >= 0;
}
private void resetInputBuffer() {
inputIndex = C.INDEX_UNSET;
buffer.data = null;
}
private void resetOutputBuffer() {
outputIndex = C.INDEX_UNSET;
outputBuffer = null;
}
/** /**
* @return Whether it may be possible to feed more input data. * @return Whether it may be possible to feed more input data.
* @throws ExoPlaybackException If an error occurs feeding the input buffer. * @throws ExoPlaybackException If an error occurs feeding the input buffer.
...@@ -782,66 +938,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -782,66 +938,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return true; return true;
} }
private void getCodecBuffers() {
if (Util.SDK_INT < 21) {
inputBuffers = codec.getInputBuffers();
outputBuffers = codec.getOutputBuffers();
}
}
private void resetCodecBuffers() {
if (Util.SDK_INT < 21) {
inputBuffers = null;
outputBuffers = null;
}
}
private ByteBuffer getInputBuffer(int inputIndex) {
if (Util.SDK_INT >= 21) {
return codec.getInputBuffer(inputIndex);
} else {
return inputBuffers[inputIndex];
}
}
private ByteBuffer getOutputBuffer(int outputIndex) {
if (Util.SDK_INT >= 21) {
return codec.getOutputBuffer(outputIndex);
} else {
return outputBuffers[outputIndex];
}
}
private boolean hasOutputBuffer() {
return outputIndex >= 0;
}
private void resetInputBuffer() {
inputIndex = C.INDEX_UNSET;
buffer.data = null;
}
private void resetOutputBuffer() {
outputIndex = C.INDEX_UNSET;
outputBuffer = null;
}
private static MediaCodec.CryptoInfo getFrameworkCryptoInfo(DecoderInputBuffer buffer,
int adaptiveReconfigurationBytes) {
MediaCodec.CryptoInfo cryptoInfo = buffer.cryptoInfo.getFrameworkCryptoInfoV16();
if (adaptiveReconfigurationBytes == 0) {
return cryptoInfo;
}
// There must be at least one sub-sample, although numBytesOfClearData is permitted to be
// null if it contains no clear data. Instantiate it if needed, and add the reconfiguration
// bytes to the clear byte count of the first sub-sample.
if (cryptoInfo.numBytesOfClearData == null) {
cryptoInfo.numBytesOfClearData = new int[1];
}
cryptoInfo.numBytesOfClearData[0] += adaptiveReconfigurationBytes;
return cryptoInfo;
}
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) { if (drmSession == null || (!bufferEncrypted && playClearSamplesWithoutKeys)) {
return false; return false;
...@@ -920,6 +1016,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -920,6 +1016,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
if (!keepingCodec) { if (!keepingCodec) {
availableCodecInfos = null;
if (codecReceivedBuffers) { if (codecReceivedBuffers) {
// Signal end of stream and wait for any final output buffers before re-initialization. // Signal end of stream and wait for any final output buffers before re-initialization.
codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
...@@ -1218,6 +1315,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1218,6 +1315,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return false; return false;
} }
private static MediaCodec.CryptoInfo getFrameworkCryptoInfo(
DecoderInputBuffer buffer, int adaptiveReconfigurationBytes) {
MediaCodec.CryptoInfo cryptoInfo = buffer.cryptoInfo.getFrameworkCryptoInfoV16();
if (adaptiveReconfigurationBytes == 0) {
return cryptoInfo;
}
// There must be at least one sub-sample, although numBytesOfClearData is permitted to be
// null if it contains no clear data. Instantiate it if needed, and add the reconfiguration
// bytes to the clear byte count of the first sub-sample.
if (cryptoInfo.numBytesOfClearData == null) {
cryptoInfo.numBytesOfClearData = new int[1];
}
cryptoInfo.numBytesOfClearData[0] += adaptiveReconfigurationBytes;
return cryptoInfo;
}
/** /**
* Returns whether the device needs keys to have been loaded into the {@link DrmSession} before * Returns whether the device needs keys to have been loaded into the {@link DrmSession} before
* codec configuration. * codec configuration.
......
...@@ -16,7 +16,10 @@ ...@@ -16,7 +16,10 @@
package com.google.android.exoplayer2.mediacodec; package com.google.android.exoplayer2.mediacodec;
import android.media.MediaCodec; import android.media.MediaCodec;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import java.util.Collections;
import java.util.List;
/** /**
* Selector of {@link MediaCodec} instances. * Selector of {@link MediaCodec} instances.
...@@ -24,32 +27,58 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep ...@@ -24,32 +27,58 @@ import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryExcep
public interface MediaCodecSelector { public interface MediaCodecSelector {
/** /**
* Default implementation of {@link MediaCodecSelector}. * Default implementation of {@link MediaCodecSelector}, which returns the preferred decoder for
* the given format.
*/ */
MediaCodecSelector DEFAULT = new MediaCodecSelector() { MediaCodecSelector DEFAULT =
new MediaCodecSelector() {
@Override
public List<MediaCodecInfo> getDecoderInfos(Format format, boolean requiresSecureDecoder)
throws DecoderQueryException {
List<MediaCodecInfo> decoderInfos =
MediaCodecUtil.getDecoderInfos(format.sampleMimeType, requiresSecureDecoder);
return decoderInfos.isEmpty()
? Collections.<MediaCodecInfo>emptyList()
: Collections.singletonList(decoderInfos.get(0));
}
@Override @Override
public MediaCodecInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) public MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException {
throws DecoderQueryException { return MediaCodecUtil.getPassthroughDecoderInfo();
return MediaCodecUtil.getDecoderInfo(mimeType, requiresSecureDecoder); }
} };
@Override /**
public MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException { * A {@link MediaCodecSelector} that returns a list of decoders in priority order, allowing
return MediaCodecUtil.getPassthroughDecoderInfo(); * fallback to less preferred decoders if initialization fails.
} *
* <p>Note: if a hardware-accelerated video decoder fails to initialize, this selector may provide
* a software video decoder to use as a fallback. Using software decoding can be inefficient, and
* the decoder may be too slow to keep up with the playback position.
*/
MediaCodecSelector DEFAULT_WITH_FALLBACK =
new MediaCodecSelector() {
@Override
public List<MediaCodecInfo> getDecoderInfos(Format format, boolean requiresSecureDecoder)
throws DecoderQueryException {
return MediaCodecUtil.getDecoderInfos(format.sampleMimeType, requiresSecureDecoder);
}
}; @Override
public MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException {
return MediaCodecUtil.getPassthroughDecoderInfo();
}
};
/** /**
* Selects a decoder to instantiate for a given mime type. * Returns a list of decoders that can decode media in the specified format, in priority order.
* *
* @param mimeType The mime type for which a decoder is required. * @param format The format for which a decoder is required.
* @param requiresSecureDecoder Whether a secure decoder is required. * @param requiresSecureDecoder Whether a secure decoder is required.
* @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder exists. * @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.
*/ */
MediaCodecInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) List<MediaCodecInfo> getDecoderInfos(Format format, boolean requiresSecureDecoder)
throws DecoderQueryException; throws DecoderQueryException;
/** /**
......
...@@ -114,11 +114,10 @@ public final class MediaCodecUtil { ...@@ -114,11 +114,10 @@ public final class MediaCodecUtil {
/** /**
* Returns information about the preferred decoder for a given mime type. * Returns information about the preferred decoder for a given mime type.
* *
* @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.
* @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder exists.
* exists.
* @throws DecoderQueryException If there was an error querying the available decoders. * @throws DecoderQueryException If there was an error querying the available decoders.
*/ */
public static @Nullable MediaCodecInfo getDecoderInfo(String mimeType, boolean secure) public static @Nullable MediaCodecInfo getDecoderInfo(String mimeType, boolean secure)
...@@ -128,18 +127,18 @@ public final class MediaCodecUtil { ...@@ -128,18 +127,18 @@ public final class MediaCodecUtil {
} }
/** /**
* Returns all {@link MediaCodecInfo}s for the given mime type, in the order given by * Returns all {@link MediaCodecInfo}s for the given mime type, in the order given by {@link
* {@link MediaCodecList}. * MediaCodecList}.
* *
* @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.
* @return A list of all @{link MediaCodecInfo}s for the given mime type, in the order * @return A list of all {@link MediaCodecInfo}s for the given mime type, in the order given by
* 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, public static synchronized List<MediaCodecInfo> getDecoderInfos(String mimeType, boolean secure)
boolean secure) throws DecoderQueryException { throws DecoderQueryException {
CodecKey key = new CodecKey(mimeType, secure); CodecKey key = new CodecKey(mimeType, secure);
List<MediaCodecInfo> cachedDecoderInfos = decoderInfosCache.get(key); List<MediaCodecInfo> cachedDecoderInfos = decoderInfosCache.get(key);
if (cachedDecoderInfos != null) { if (cachedDecoderInfos != null) {
......
...@@ -51,6 +51,7 @@ import com.google.android.exoplayer2.util.TraceUtil; ...@@ -51,6 +51,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.List;
/** /**
* Decodes and renders video using {@link MediaCodec}. * Decodes and renders video using {@link MediaCodec}.
...@@ -236,15 +237,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -236,15 +237,21 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
requiresSecureDecryption |= drmInitData.get(i).requiresSecureDecryption; requiresSecureDecryption |= drmInitData.get(i).requiresSecureDecryption;
} }
} }
MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, List<MediaCodecInfo> decoderInfos =
requiresSecureDecryption); mediaCodecSelector.getDecoderInfos(format, requiresSecureDecryption);
if (decoderInfo == null) { if (decoderInfos.isEmpty()) {
return requiresSecureDecryption && mediaCodecSelector.getDecoderInfo(mimeType, false) != null return requiresSecureDecryption
? FORMAT_UNSUPPORTED_DRM : FORMAT_UNSUPPORTED_SUBTYPE; && !mediaCodecSelector
.getDecoderInfos(format, /* requiresSecureDecoder= */ false)
.isEmpty()
? FORMAT_UNSUPPORTED_DRM
: FORMAT_UNSUPPORTED_SUBTYPE;
} }
if (!supportsFormatDrm(drmSessionManager, drmInitData)) { if (!supportsFormatDrm(drmSessionManager, drmInitData)) {
return FORMAT_UNSUPPORTED_DRM; return FORMAT_UNSUPPORTED_DRM;
} }
// Check capabilities for the first decoder in the list, which takes priority.
MediaCodecInfo decoderInfo = decoderInfos.get(0);
boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs); boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs);
if (decoderCapable && format.width > 0 && format.height > 0) { if (decoderCapable && format.width > 0 && format.height > 0) {
if (Util.SDK_INT >= 21) { if (Util.SDK_INT >= 21) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment