From ec98bd9ea1d230c8b641c1d0dcff405b5677dfbd Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Wed, 15 Feb 2017 10:30:40 -0800 Subject: [PATCH] Work around broken AAC decoder EoS handling on L. SoftAAC2 would cause an exception to be thrown from dequeueOutputBuffer/releaseOutputBuffer after queueing an end-of-stream buffer for certain streams. The bug was introduced in L and fixed in L MR1, so the workaround is targeted to API 21. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=147613659 --- .../mediacodec/MediaCodecRenderer.java | 63 +++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 0330b13eb6..9baf974b37 100644 --- a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -183,6 +183,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean codecNeedsAdaptationWorkaround; private boolean codecNeedsEosPropagationWorkaround; private boolean codecNeedsEosFlushWorkaround; + private boolean codecNeedsEosOutputExceptionWorkaround; private boolean codecNeedsMonoChannelCountWorkaround; private boolean codecNeedsAdaptationWorkaroundBuffer; private boolean shouldSkipAdaptationWorkaroundOutputBuffer; @@ -342,6 +343,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName); codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); + codecNeedsEosOutputExceptionWorkaround = codecNeedsEosOutputExceptionWorkaround(codecName); codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); try { long codecInitializingTimestamp = SystemClock.elapsedRealtime(); @@ -513,7 +515,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { codecNeedsAdaptationWorkaroundBuffer = false; shouldSkipAdaptationWorkaroundOutputBuffer = false; if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) { - // Workaround framework bugs. See [Internal: b/8347958, b/8578467, b/8543366, b/23361053]. releaseCodec(); maybeInitCodec(); } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { @@ -867,7 +868,22 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { if (outputIndex < 0) { - outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs()); + if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) { + try { + outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, + getDequeueOutputBufferTimeoutUs()); + } catch (IllegalStateException e) { + processEndOfStream(); + if (outputStreamEnded) { + // Release the codec, as it's in an error state. + releaseCodec(); + } + return false; + } + } else { + outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, + getDequeueOutputBufferTimeoutUs()); + } if (outputIndex >= 0) { // We've dequeued a buffer. if (shouldSkipAdaptationWorkaroundOutputBuffer) { @@ -906,9 +922,27 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } } - if (processOutputBuffer(positionUs, elapsedRealtimeUs, codec, outputBuffers[outputIndex], - outputIndex, outputBufferInfo.flags, outputBufferInfo.presentationTimeUs, - shouldSkipOutputBuffer)) { + boolean processedOutputBuffer; + if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) { + try { + processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs, codec, + outputBuffers[outputIndex], outputIndex, outputBufferInfo.flags, + outputBufferInfo.presentationTimeUs, shouldSkipOutputBuffer); + } catch (IllegalStateException e) { + processEndOfStream(); + if (outputStreamEnded) { + // Release the codec, as it's in an error state. + releaseCodec(); + } + return false; + } + } else { + processedOutputBuffer = processOutputBuffer(positionUs, elapsedRealtimeUs, codec, + outputBuffers[outputIndex], outputIndex, outputBufferInfo.flags, + outputBufferInfo.presentationTimeUs, shouldSkipOutputBuffer); + } + + if (processedOutputBuffer) { onProcessedOutputBuffer(outputBufferInfo.presentationTimeUs); outputIndex = C.INDEX_UNSET; return true; @@ -1010,6 +1044,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { *

* If true is returned, the renderer will work around the issue by releasing the decoder and * instantiating a new one rather than flushing the current instance. + *

+ * See [Internal: b/8347958, b/8543366]. * * @param name The name of the decoder. * @return True if the decoder is known to fail when flushed. @@ -1079,6 +1115,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { *

* If true is returned, the renderer will work around the issue by instantiating a new decoder * when this case occurs. + *

+ * See [Internal: b/8578467, b/23361053]. * * @param name The name of the decoder. * @return True if the decoder is known to behave incorrectly if flushed after receiving an input @@ -1091,6 +1129,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer { || "OMX.amlogic.avc.decoder.awesome.secure".equals(name))); } + /** + * Returns whether the decoder may throw an {@link IllegalStateException} from + * {@link MediaCodec#dequeueOutputBuffer(MediaCodec.BufferInfo, long)} or + * {@link MediaCodec#releaseOutputBuffer(int, boolean)} after receiving an input + * buffer with {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} set. + *

+ * See [Internal: b/17933838]. + * + * @param name The name of the decoder. + * @return True if the decoder may throw an exception after receiving an end-of-stream buffer. + */ + private static boolean codecNeedsEosOutputExceptionWorkaround(String name) { + return Util.SDK_INT == 21 && "OMX.google.aac.decoder".equals(name); + } + /** * Returns whether the decoder is known to set the number of audio channels in the output format * to 2 for the given input format, whilst only actually outputting a single channel.