From c5db69d22cd4daa913e2493a5c76384ad9959530 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 2 Nov 2018 10:32:44 -0700 Subject: [PATCH] Fix further issues with decoder re-use It's no longer guaranteed that onOutputFormatChanged will always be called when the renderer is re-enabled, since the codec may be kept and its output format may not change. Hence we need to avoid resetting state in onDisabled that we need when the renderer is enabled again. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=219821519 --- .../android/exoplayer2/audio/AudioSink.java | 72 +++++++++---------- .../exoplayer2/audio/DefaultAudioSink.java | 18 ++--- .../audio/MediaCodecAudioRenderer.java | 13 +++- .../audio/SimpleDecoderAudioRenderer.java | 4 +- .../video/MediaCodecVideoRenderer.java | 7 +- .../audio/SimpleDecoderAudioRendererTest.java | 2 +- 6 files changed, 59 insertions(+), 57 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java index bb7ef22ef4..bf1dc3ca8a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java @@ -23,29 +23,30 @@ import java.nio.ByteBuffer; /** * A sink that consumes audio data. - *

- * Before starting playback, specify the input audio format by calling - * {@link #configure(int, int, int, int, int[], int, int)}. - *

- * Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()} + * + *

Before starting playback, specify the input audio format by calling {@link #configure(int, + * int, int, int, int[], int, int)}. + * + *

Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()} * when the data being fed is discontinuous. Call {@link #play()} to start playing the written data. - *

- * Call {@link #configure(int, int, int, int, int[], int, int)} whenever the input format changes. - * The sink will be reinitialized on the next call to {@link #handleBuffer(ByteBuffer, long)}. - *

- * Call {@link #reset()} to prepare the sink to receive audio data from a new playback position. - *

- * Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers will - * be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #reset()}. Call - * {@link #release()} when the instance is no longer required. - *

- * The implementation may be backed by a platform {@link AudioTrack}. In this case, - * {@link #setAudioSessionId(int)}, {@link #setAudioAttributes(AudioAttributes)}, - * {@link #enableTunnelingV21(int)} and/or {@link #disableTunneling()} may be called before writing - * data to the sink. These methods may also be called after writing data to the sink, in which case - * it will be reinitialized as required. For implementations that are not based on platform - * {@link AudioTrack}s, calling methods relating to audio sessions, audio attributes, and tunneling - * may have no effect. + * + *

Call {@link #configure(int, int, int, int, int[], int, int)} whenever the input format + * changes. The sink will be reinitialized on the next call to {@link #handleBuffer(ByteBuffer, + * long)}. + * + *

Call {@link #flush()} to prepare the sink to receive audio data from a new playback position. + * + *

Call {@link #playToEndOfStream()} repeatedly to play out all data when no more input buffers + * will be provided via {@link #handleBuffer(ByteBuffer, long)} until the next {@link #flush()}. + * Call {@link #reset()} when the instance is no longer required. + * + *

The implementation may be backed by a platform {@link AudioTrack}. In this case, {@link + * #setAudioSessionId(int)}, {@link #setAudioAttributes(AudioAttributes)}, {@link + * #enableTunnelingV21(int)} and/or {@link #disableTunneling()} may be called before writing data to + * the sink. These methods may also be called after writing data to the sink, in which case it will + * be reinitialized as required. For implementations that are not based on platform {@link + * AudioTrack}s, calling methods relating to audio sessions, audio attributes, and tunneling may + * have no effect. */ public interface AudioSink { @@ -197,7 +198,7 @@ public interface AudioSink { * @param trimStartFrames The number of audio frames to trim from the start of data written to the * sink after this call. * @param trimEndFrames The number of audio frames to trim from data written to the sink - * immediately preceding the next call to {@link #reset()} or this method. + * immediately preceding the next call to {@link #flush()} or this method. * @throws ConfigurationException If an error occurs configuring the sink. */ void configure( @@ -223,11 +224,11 @@ public interface AudioSink { * ending at its limit (exclusive). The position of the {@link ByteBuffer} is advanced by the * number of bytes that were handled. {@link Listener#onPositionDiscontinuity()} will be called if * {@code presentationTimeUs} is discontinuous with the last buffer handled since the last reset. - *

- * Returns whether the data was handled in full. If the data was not handled in full then the same - * {@link ByteBuffer} must be provided to subsequent calls until it has been fully consumed, - * except in the case of an intervening call to {@link #reset()} (or to - * {@link #configure(int, int, int, int, int[], int, int)} that causes the sink to be reset). + * + *

Returns whether the data was handled in full. If the data was not handled in full then the + * same {@link ByteBuffer} must be provided to subsequent calls until it has been fully consumed, + * except in the case of an intervening call to {@link #flush()} (or to {@link #configure(int, + * int, int, int, int[], int, int)} that causes the sink to be flushed). * * @param buffer The buffer containing audio data. * @param presentationTimeUs The presentation timestamp of the buffer in microseconds. @@ -316,15 +317,12 @@ public interface AudioSink { void pause(); /** - * Resets the sink, after which it is ready to receive buffers from a new playback position. - *

- * The audio session may remain active until {@link #release()} is called. + * Flushes the sink, after which it is ready to receive buffers from a new playback position. + * + *

The audio session may remain active until {@link #reset()} is called. */ + void flush(); + + /** Resets the renderer, releasing any resources that it currently holds. */ void reset(); - - /** - * Releases any resources associated with this instance. - */ - void release(); - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index d29659b102..7ba060ad76 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -463,7 +463,7 @@ public final class DefaultAudioSink implements AudioSink { return; } - reset(); + flush(); this.processingEnabled = processingEnabled; outputSampleRate = sampleRate; @@ -681,7 +681,7 @@ public final class DefaultAudioSink implements AudioSink { if (audioTrackPositionTracker.isStalled(getWrittenFrames())) { Log.w(TAG, "Resetting stalled audio track"); - reset(); + flush(); return true; } @@ -871,7 +871,7 @@ public final class DefaultAudioSink implements AudioSink { // The audio attributes are ignored in tunneling mode, so no need to reset. return; } - reset(); + flush(); audioSessionId = C.AUDIO_SESSION_ID_UNSET; } @@ -879,7 +879,7 @@ public final class DefaultAudioSink implements AudioSink { public void setAudioSessionId(int audioSessionId) { if (this.audioSessionId != audioSessionId) { this.audioSessionId = audioSessionId; - reset(); + flush(); } } @@ -907,7 +907,7 @@ public final class DefaultAudioSink implements AudioSink { if (!tunneling || audioSessionId != tunnelingAudioSessionId) { tunneling = true; audioSessionId = tunnelingAudioSessionId; - reset(); + flush(); } } @@ -916,7 +916,7 @@ public final class DefaultAudioSink implements AudioSink { if (tunneling) { tunneling = false; audioSessionId = C.AUDIO_SESSION_ID_UNSET; - reset(); + flush(); } } @@ -947,7 +947,7 @@ public final class DefaultAudioSink implements AudioSink { } @Override - public void reset() { + public void flush() { if (isInitialized()) { submittedPcmBytes = 0; submittedEncodedFrames = 0; @@ -995,8 +995,8 @@ public final class DefaultAudioSink implements AudioSink { } @Override - public void release() { - reset(); + public void reset() { + flush(); releaseKeepSessionIdAudioTrack(); for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) { audioProcessor.reset(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index c90b815347..dbc25c8d42 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -511,7 +511,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media @Override protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { super.onPositionReset(positionUs, joining); - audioSink.reset(); + audioSink.flush(); currentPositionUs = positionUs; allowFirstBufferPositionDiscontinuity = true; allowPositionDiscontinuity = true; @@ -537,7 +537,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media try { lastInputTimeUs = C.TIME_UNSET; pendingStreamChangeCount = 0; - audioSink.release(); + audioSink.flush(); } finally { try { super.onDisabled(); @@ -548,6 +548,15 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } } + @Override + protected void onReset() { + try { + super.onReset(); + } finally { + audioSink.reset(); + } + } + @Override public boolean isEnded() { return super.isEnded() && audioSink.isEnded(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java index cecb17d96c..3df38e3557 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -538,7 +538,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements @Override protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { - audioSink.reset(); + audioSink.flush(); currentPositionUs = positionUs; allowFirstBufferPositionDiscontinuity = true; allowPositionDiscontinuity = true; @@ -567,7 +567,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements waitingForKeys = false; try { releaseDecoder(); - audioSink.release(); + audioSink.reset(); } finally { try { if (drmSession != null) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index a896bc2322..29a75f06e5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -361,18 +361,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { @Override protected void onDisabled() { - currentWidth = Format.NO_VALUE; - currentHeight = Format.NO_VALUE; - currentPixelWidthHeightRatio = Format.NO_VALUE; - pendingPixelWidthHeightRatio = Format.NO_VALUE; - outputStreamOffsetUs = C.TIME_UNSET; lastInputTimeUs = C.TIME_UNSET; + outputStreamOffsetUs = C.TIME_UNSET; pendingOutputStreamOffsetCount = 0; clearReportedVideoSize(); clearRenderedFirstFrame(); frameReleaseTimeHelper.disable(); tunnelingOnFrameRenderedListener = null; - tunneling = false; try { super.onDisabled(); } finally { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java index 48e71c619c..3f28999820 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/SimpleDecoderAudioRendererTest.java @@ -104,7 +104,7 @@ public class SimpleDecoderAudioRendererTest { verify(mockAudioSink, times(1)).playToEndOfStream(); audioRenderer.disable(); audioRenderer.reset(); - verify(mockAudioSink, times(1)).release(); + verify(mockAudioSink, times(1)).reset(); } private static final class FakeDecoder