From e8a8c49a97d5cc56e2e8fb4c5e9b6cb00fcc77c1 Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Tue, 23 Dec 2014 13:47:50 +0100 Subject: [PATCH 1/5] better handling of input format change for non-adaptive codecs * this fixes a bug when switching from HE-AAC 22050Hz to AAC 44100Hz (the AudioTrack was not reset and we were trying to send a bad number of bytes, triggering a "AudioTrack.write() called with invalid size" error) * this also improves quality switches, making it almost seamless --- .../exoplayer/MediaCodecTrackRenderer.java | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) 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 271e8ff461..8c4ad83992 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -135,6 +135,25 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { */ private static final int RECONFIGURATION_STATE_QUEUE_PENDING = 2; + /** + * No reinit is needed + */ + private static final int REINIT_STATE_NONE = 0; + /** + * The input format has just changed. Signal an end of stream to the codec so that we can + * retrieve the very last decoded samples from the previous format. + */ + private static final int REINIT_STATE_SIGNAL_END_OF_STREAM = 1; + /** + * The end of stream has been sent, wait for the codec to acknowledge it. + */ + private static final int REINIT_STATE_WAIT_END_OF_STREAM = 2; + /** + * The last sample has been processed, we can safely reinit now. + */ + private static final int REINIT_STATE_DO_REINIT_NOW = 3; + + public final CodecCounters codecCounters; private final DrmSessionManager drmSessionManager; @@ -159,6 +178,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { private boolean openedDrmSession; private boolean codecReconfigured; private int codecReconfigurationState; + private int codecReinitState; private int trackIndex; private int sourceState; @@ -166,6 +186,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { private boolean outputStreamEnded; private boolean waitingForKeys; private boolean waitingForFirstSyncFrame; + private boolean hasQueuedOneInputBuffer; private long currentPositionUs; /** @@ -194,6 +215,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { formatHolder = new MediaFormatHolder(); decodeOnlyPresentationTimestamps = new ArrayList(); outputBufferInfo = new MediaCodec.BufferInfo(); + codecReinitState = REINIT_STATE_NONE; } @Override @@ -302,6 +324,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { inputIndex = -1; outputIndex = -1; waitingForFirstSyncFrame = true; + hasQueuedOneInputBuffer = false; codecCounters.codecInitCount++; } @@ -346,6 +369,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { codecReconfigured = false; codecIsAdaptive = false; codecReconfigurationState = RECONFIGURATION_STATE_NONE; + codecReinitState = REINIT_STATE_NONE; codecCounters.codecReleaseCount++; try { codec.stop(); @@ -493,6 +517,13 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { if (inputStreamEnded) { return false; } + + if (codecReinitState == REINIT_STATE_DO_REINIT_NOW) { + releaseCodec(); + maybeInitCodec(); + return false; + } + if (inputIndex < 0) { inputIndex = codec.dequeueInputBuffer(0); if (inputIndex < 0) { @@ -502,6 +533,16 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { sampleHolder.data.clear(); } + if (codecReinitState == REINIT_STATE_SIGNAL_END_OF_STREAM) { + codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + inputIndex = -1; + codecReinitState = REINIT_STATE_WAIT_END_OF_STREAM; + return false; + } else if (codecReinitState != REINIT_STATE_NONE) { + // we are still waiting for the last samples to be output + return false; + } + int result; if (waitingForKeys) { // We've already read an encrypted sample into sampleHolder, and are waiting for keys. @@ -591,6 +632,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { codec.queueInputBuffer(inputIndex, 0 , bufferSize, presentationTimeUs, 0); } inputIndex = -1; + hasQueuedOneInputBuffer = true; codecReconfigurationState = RECONFIGURATION_STATE_NONE; } catch (CryptoException e) { notifyCryptoError(e); @@ -644,8 +686,12 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { codecReconfigured = true; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; } else { - releaseCodec(); - maybeInitCodec(); + if (!hasQueuedOneInputBuffer) { + // no need to signal end of stream if nothing has been queued, we can just reinit asap + codecReinitState = REINIT_STATE_DO_REINIT_NOW; + } else { + codecReinitState = REINIT_STATE_SIGNAL_END_OF_STREAM; + } } } @@ -733,7 +779,12 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { } if ((outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { - outputStreamEnded = true; + if (codecReinitState == REINIT_STATE_WAIT_END_OF_STREAM) { + codecReinitState = REINIT_STATE_DO_REINIT_NOW; + } else { + outputStreamEnded = true; + } + return false; } From 79708f43f8d9665a3c984e618899e3b12eff8f9b Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Thu, 22 Jan 2015 10:24:51 +0100 Subject: [PATCH 2/5] handle the case when flushCodec() is called while reiniting the decoders --- .../google/android/exoplayer/MediaCodecTrackRenderer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 8c4ad83992..5f28fab835 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -493,9 +493,10 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { decodeOnlyPresentationTimestamps.clear(); // Workaround for framework bugs. // See [Internal: b/8347958], [Internal: b/8578467], [Internal: b/8543366]. - if (Util.SDK_INT >= 18) { + if (Util.SDK_INT >= 18 && codecReinitState == REINIT_STATE_NONE) { codec.flush(); } else { + codecReinitState = REINIT_STATE_NONE; releaseCodec(); maybeInitCodec(); } @@ -504,6 +505,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { // avoid this issue by sending reconfiguration data following every flush. codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; } + + hasQueuedOneInputBuffer = false; } /** From 424b29f996fd5211625ff407689ddb337b6e05a1 Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Tue, 27 Jan 2015 10:44:02 +0100 Subject: [PATCH 3/5] remove REINIT_STATE_DO_REINIT_NOW state, reinit directly when needed. --- .../exoplayer/MediaCodecTrackRenderer.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) 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 5f28fab835..6b0d92f525 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -148,11 +148,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { * The end of stream has been sent, wait for the codec to acknowledge it. */ private static final int REINIT_STATE_WAIT_END_OF_STREAM = 2; - /** - * The last sample has been processed, we can safely reinit now. - */ - private static final int REINIT_STATE_DO_REINIT_NOW = 3; - public final CodecCounters codecCounters; @@ -521,12 +516,6 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { return false; } - if (codecReinitState == REINIT_STATE_DO_REINIT_NOW) { - releaseCodec(); - maybeInitCodec(); - return false; - } - if (inputIndex < 0) { inputIndex = codec.dequeueInputBuffer(0); if (inputIndex < 0) { @@ -691,7 +680,8 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { } else { if (!hasQueuedOneInputBuffer) { // no need to signal end of stream if nothing has been queued, we can just reinit asap - codecReinitState = REINIT_STATE_DO_REINIT_NOW; + releaseCodec(); + maybeInitCodec(); } else { codecReinitState = REINIT_STATE_SIGNAL_END_OF_STREAM; } @@ -783,7 +773,9 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { if ((outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { if (codecReinitState == REINIT_STATE_WAIT_END_OF_STREAM) { - codecReinitState = REINIT_STATE_DO_REINIT_NOW; + // The last sample has been processed, we can safely reinit now + releaseCodec(); + maybeInitCodec(); } else { outputStreamEnded = true; } From c228017fe4a4b2024c2a2515d785876e0fe43482 Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Tue, 27 Jan 2015 18:35:40 +0100 Subject: [PATCH 4/5] rename hasQueuedOneInputBuffer to hasQueuedInputBuffer --- .../android/exoplayer/MediaCodecTrackRenderer.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 6b0d92f525..ef5052c398 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -181,7 +181,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { private boolean outputStreamEnded; private boolean waitingForKeys; private boolean waitingForFirstSyncFrame; - private boolean hasQueuedOneInputBuffer; + private boolean hasQueuedInputBuffer; private long currentPositionUs; /** @@ -319,7 +319,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { inputIndex = -1; outputIndex = -1; waitingForFirstSyncFrame = true; - hasQueuedOneInputBuffer = false; + hasQueuedInputBuffer = false; codecCounters.codecInitCount++; } @@ -501,7 +501,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; } - hasQueuedOneInputBuffer = false; + hasQueuedInputBuffer = false; } /** @@ -624,7 +624,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { codec.queueInputBuffer(inputIndex, 0 , bufferSize, presentationTimeUs, 0); } inputIndex = -1; - hasQueuedOneInputBuffer = true; + hasQueuedInputBuffer = true; codecReconfigurationState = RECONFIGURATION_STATE_NONE; } catch (CryptoException e) { notifyCryptoError(e); @@ -678,7 +678,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { codecReconfigured = true; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; } else { - if (!hasQueuedOneInputBuffer) { + if (!hasQueuedInputBuffer) { // no need to signal end of stream if nothing has been queued, we can just reinit asap releaseCodec(); maybeInitCodec(); From e33e1d79227913b2b234364f53c946679af50722 Mon Sep 17 00:00:00 2001 From: Martin Bonnin Date: Tue, 27 Jan 2015 18:37:27 +0100 Subject: [PATCH 5/5] cleaner test --- .../com/google/android/exoplayer/MediaCodecTrackRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ef5052c398..b8c33ea110 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -530,7 +530,7 @@ public abstract class MediaCodecTrackRenderer extends TrackRenderer { inputIndex = -1; codecReinitState = REINIT_STATE_WAIT_END_OF_STREAM; return false; - } else if (codecReinitState != REINIT_STATE_NONE) { + } else if (codecReinitState == REINIT_STATE_WAIT_END_OF_STREAM) { // we are still waiting for the last samples to be output return false; }