Commit 3bff79f5 by tonihei Committed by Toni

Wrap MediaCodec exceptions in DecoderException and report as renderer error.

We currently report MediaCodec exceptions as unexpected exceptions instead of
as renderer error. All such exceptions are now wrapped in a new DecoderException
to allow adding more details to the exception.

PiperOrigin-RevId: 252054486
parent 8a2871ed
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
* Add a workaround for broken raw audio decoding on Oppo R9 * Add a workaround for broken raw audio decoding on Oppo R9
([#5782](https://github.com/google/ExoPlayer/issues/5782)). ([#5782](https://github.com/google/ExoPlayer/issues/5782)).
* Add VR player demo. * Add VR player demo.
* Wrap decoder exceptions in a new `DecoderException` class and report as
renderer error.
### 2.10.2 ### ### 2.10.2 ###
......
...@@ -680,7 +680,7 @@ public class PlayerActivity extends AppCompatActivity ...@@ -680,7 +680,7 @@ public class PlayerActivity extends AppCompatActivity
// Special case for decoder initialization failures. // Special case for decoder initialization failures.
DecoderInitializationException decoderInitializationException = DecoderInitializationException decoderInitializationException =
(DecoderInitializationException) cause; (DecoderInitializationException) cause;
if (decoderInitializationException.decoderName == null) { if (decoderInitializationException.codecInfo == null) {
if (decoderInitializationException.getCause() instanceof DecoderQueryException) { if (decoderInitializationException.getCause() instanceof DecoderQueryException) {
errorString = getString(R.string.error_querying_decoders); errorString = getString(R.string.error_querying_decoders);
} else if (decoderInitializationException.secureDecoderRequired) { } else if (decoderInitializationException.secureDecoderRequired) {
...@@ -695,7 +695,7 @@ public class PlayerActivity extends AppCompatActivity ...@@ -695,7 +695,7 @@ public class PlayerActivity extends AppCompatActivity
errorString = errorString =
getString( getString(
R.string.error_instantiating_decoder, R.string.error_instantiating_decoder,
decoderInitializationException.decoderName); decoderInitializationException.codecInfo.name);
} }
} }
} }
......
...@@ -80,14 +80,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -80,14 +80,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
public final boolean secureDecoderRequired; public final boolean secureDecoderRequired;
/** /**
* The name of the decoder that failed to initialize. Null if no suitable decoder was found. * The {@link MediaCodecInfo} of the decoder that failed to initialize. Null if no suitable
* decoder was found.
*/ */
public final String decoderName; @Nullable public final MediaCodecInfo codecInfo;
/** /** An optional developer-readable diagnostic information string. May be null. */
* An optional developer-readable diagnostic information string. May be null. @Nullable public final String diagnosticInfo;
*/
public final String diagnosticInfo;
/** /**
* If the decoder failed to initialize and another decoder being used as a fallback also failed * If the decoder failed to initialize and another decoder being used as a fallback also failed
...@@ -103,19 +102,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -103,19 +102,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
cause, cause,
format.sampleMimeType, format.sampleMimeType,
secureDecoderRequired, secureDecoderRequired,
/* decoderName= */ null, /* mediaCodecInfo= */ null,
buildCustomDiagnosticInfo(errorCode), buildCustomDiagnosticInfo(errorCode),
/* fallbackDecoderInitializationException= */ null); /* fallbackDecoderInitializationException= */ null);
} }
public DecoderInitializationException(Format format, Throwable cause, public DecoderInitializationException(
boolean secureDecoderRequired, String decoderName) { Format format,
Throwable cause,
boolean secureDecoderRequired,
MediaCodecInfo mediaCodecInfo) {
this( this(
"Decoder init failed: " + decoderName + ", " + format, "Decoder init failed: " + mediaCodecInfo.name + ", " + format,
cause, cause,
format.sampleMimeType, format.sampleMimeType,
secureDecoderRequired, secureDecoderRequired,
decoderName, mediaCodecInfo,
Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null, Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null,
/* fallbackDecoderInitializationException= */ null); /* fallbackDecoderInitializationException= */ null);
} }
...@@ -125,13 +127,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -125,13 +127,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
Throwable cause, Throwable cause,
String mimeType, String mimeType,
boolean secureDecoderRequired, boolean secureDecoderRequired,
@Nullable String decoderName, @Nullable MediaCodecInfo mediaCodecInfo,
@Nullable String diagnosticInfo, @Nullable String diagnosticInfo,
@Nullable DecoderInitializationException fallbackDecoderInitializationException) { @Nullable DecoderInitializationException fallbackDecoderInitializationException) {
super(message, cause); super(message, cause);
this.mimeType = mimeType; this.mimeType = mimeType;
this.secureDecoderRequired = secureDecoderRequired; this.secureDecoderRequired = secureDecoderRequired;
this.decoderName = decoderName; this.codecInfo = mediaCodecInfo;
this.diagnosticInfo = diagnosticInfo; this.diagnosticInfo = diagnosticInfo;
this.fallbackDecoderInitializationException = fallbackDecoderInitializationException; this.fallbackDecoderInitializationException = fallbackDecoderInitializationException;
} }
...@@ -144,7 +146,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -144,7 +146,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
getCause(), getCause(),
mimeType, mimeType,
secureDecoderRequired, secureDecoderRequired,
decoderName, codecInfo,
diagnosticInfo, diagnosticInfo,
fallbackException); fallbackException);
} }
...@@ -159,9 +161,34 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -159,9 +161,34 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private static String buildCustomDiagnosticInfo(int errorCode) { private static String buildCustomDiagnosticInfo(int errorCode) {
String sign = errorCode < 0 ? "neg_" : ""; String sign = errorCode < 0 ? "neg_" : "";
return "com.google.android.exoplayer.MediaCodecTrackRenderer_" + sign + Math.abs(errorCode); return "com.google.android.exoplayer2.mediacodec.MediaCodecRenderer_"
+ sign
+ Math.abs(errorCode);
}
}
/** Thrown when a failure occurs in the decoder. */
public static class DecoderException extends Exception {
/** The {@link MediaCodecInfo} of the decoder that failed. Null if unknown. */
@Nullable public final MediaCodecInfo codecInfo;
/** An optional developer-readable diagnostic information string. May be null. */
@Nullable public final String diagnosticInfo;
public DecoderException(Throwable cause, @Nullable MediaCodecInfo codecInfo) {
super("Decoder failed: " + (codecInfo == null ? null : codecInfo.name), cause);
this.codecInfo = codecInfo;
diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null;
} }
@TargetApi(21)
private static String getDiagnosticInfoV21(Throwable cause) {
if (cause instanceof CodecException) {
return ((CodecException) cause).getDiagnosticInfo();
}
return null;
}
} }
/** Indicates no codec operating rate should be set. */ /** Indicates no codec operating rate should be set. */
...@@ -637,31 +664,40 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -637,31 +664,40 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Override @Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (outputStreamEnded) { try {
renderToEndOfStream(); if (outputStreamEnded) {
return; renderToEndOfStream();
} return;
if (inputFormat == null && !readToFlagsOnlyBuffer(/* requireFormat= */ true)) { }
if (inputFormat == null && !readToFlagsOnlyBuffer(/* requireFormat= */ true)) {
// We still don't have a format and can't make progress without one. // We still don't have a format and can't make progress without one.
return; return;
}
// We have a format.
maybeInitCodec();
if (codec != null) {
long drainStartTimeMs = SystemClock.elapsedRealtime();
TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
while (feedInputBuffer() && shouldContinueFeeding(drainStartTimeMs)) {}
TraceUtil.endSection();
} else {
decoderCounters.skippedInputBufferCount += skipSource(positionUs);
// We need to read any format changes despite not having a codec so that drmSession can be
// updated, and so that we have the most recent format should the codec be initialized. We
// may
// also reach the end of the stream. Note that readSource will not read a sample into a
// flags-only buffer.
readToFlagsOnlyBuffer(/* requireFormat= */ false);
}
decoderCounters.ensureUpdated();
} catch (IllegalStateException e) {
if (isMediaCodecException(e)) {
throw ExoPlaybackException.createForRenderer(
createDecoderException(e, getCodecInfo()), getIndex());
}
throw e;
} }
// We have a format.
maybeInitCodec();
if (codec != null) {
long drainStartTimeMs = SystemClock.elapsedRealtime();
TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
while (feedInputBuffer() && shouldContinueFeeding(drainStartTimeMs)) {}
TraceUtil.endSection();
} else {
decoderCounters.skippedInputBufferCount += skipSource(positionUs);
// We need to read any format changes despite not having a codec so that drmSession can be
// updated, and so that we have the most recent format should the codec be initialized. We may
// also reach the end of the stream. Note that readSource will not read a sample into a
// flags-only buffer.
readToFlagsOnlyBuffer(/* requireFormat= */ false);
}
decoderCounters.ensureUpdated();
} }
/** /**
...@@ -725,6 +761,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -725,6 +761,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return false; return false;
} }
protected DecoderException createDecoderException(
Throwable cause, @Nullable MediaCodecInfo codecInfo) {
return new DecoderException(cause, codecInfo);
}
/** Reads into {@link #flagsOnlyBuffer} and returns whether a format was read. */ /** Reads into {@link #flagsOnlyBuffer} and returns whether a format was read. */
private boolean readToFlagsOnlyBuffer(boolean requireFormat) throws ExoPlaybackException { private boolean readToFlagsOnlyBuffer(boolean requireFormat) throws ExoPlaybackException {
flagsOnlyBuffer.clear(); flagsOnlyBuffer.clear();
...@@ -785,7 +826,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -785,7 +826,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
availableCodecInfos.removeFirst(); availableCodecInfos.removeFirst();
DecoderInitializationException exception = DecoderInitializationException exception =
new DecoderInitializationException( new DecoderInitializationException(
inputFormat, e, mediaCryptoRequiresSecureDecoder, codecInfo.name); inputFormat, e, mediaCryptoRequiresSecureDecoder, codecInfo);
if (preferredDecoderInitializationException == null) { if (preferredDecoderInitializationException == null) {
preferredDecoderInitializationException = exception; preferredDecoderInitializationException = exception;
} else { } else {
...@@ -1701,6 +1742,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1701,6 +1742,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return cryptoInfo; return cryptoInfo;
} }
private static boolean isMediaCodecException(IllegalStateException error) {
if (Util.SDK_INT >= 21) {
return isMediaCodecExceptionV21(error);
}
StackTraceElement[] stackTrace = error.getStackTrace();
return stackTrace.length > 0 && stackTrace[0].getClassName().equals("android.media.MediaCodec");
}
@TargetApi(21)
private static boolean isMediaCodecExceptionV21(IllegalStateException error) {
return error instanceof MediaCodec.CodecException;
}
/** /**
* 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.
......
...@@ -92,6 +92,23 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -92,6 +92,23 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
*/ */
private static final float INITIAL_FORMAT_MAX_INPUT_SIZE_SCALE_FACTOR = 1.5f; private static final float INITIAL_FORMAT_MAX_INPUT_SIZE_SCALE_FACTOR = 1.5f;
/** A {@link DecoderException} with additional surface information. */
public static final class VideoDecoderException extends DecoderException {
/** The {@link System#identityHashCode(Object)} of the surface when the exception occurred. */
public final int surfaceIdentityHashCode;
/** Whether the surface was valid when the exception occurred. */
public final boolean isSurfaceValid;
public VideoDecoderException(
Throwable cause, @Nullable MediaCodecInfo codecInfo, @Nullable Surface surface) {
super(cause, codecInfo);
surfaceIdentityHashCode = System.identityHashCode(surface);
isSurfaceValid = surface == null || surface.isValid();
}
}
private static boolean evaluatedDeviceNeedsSetOutputSurfaceWorkaround; private static boolean evaluatedDeviceNeedsSetOutputSurfaceWorkaround;
private static boolean deviceNeedsSetOutputSurfaceWorkaround; private static boolean deviceNeedsSetOutputSurfaceWorkaround;
...@@ -1260,6 +1277,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -1260,6 +1277,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); return new CodecMaxValues(maxWidth, maxHeight, maxInputSize);
} }
@Override
protected DecoderException createDecoderException(
Throwable cause, @Nullable MediaCodecInfo codecInfo) {
return new VideoDecoderException(cause, codecInfo, surface);
}
/** /**
* Returns a maximum video size to use when configuring a codec for {@code format} in a way that * Returns a maximum video size to use when configuring a codec for {@code format} in a way that
* will allow possible adaptation to other compatible formats that are expected to have the same * will allow possible adaptation to other compatible formats that are expected to have the same
......
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