From 21c5b0bf67c96ddd1031c96e7933506be3b87883 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 11 Sep 2018 05:28:15 -0700 Subject: [PATCH] Add missing AudioSink discontinuity for stream changes. When the stream is changed in the audio renderer, the timestamps of the samples can no longer be expected to match the calculations in the AudioSink. This change tracks the samples at which the stream is changed and notifies the AudioSink of the discontinuity. Issue:#4559 Issue:#3829 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=212435859 --- RELEASENOTES.md | 2 + .../android/exoplayer2/audio/AudioSink.java | 4 +- .../exoplayer2/audio/DefaultAudioSink.java | 5 +- .../audio/MediaCodecAudioRenderer.java | 51 +++++++++++++++++++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0f1c314637f..2a8f91ae20b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -122,6 +122,8 @@ ([#4661](https://github.com/google/ExoPlayer/issues/4661)). * Allow edit lists which do not start with a sync sample. ([#4774](https://github.com/google/ExoPlayer/issues/4774)). +* Fix issue with audio discontinuities at period transitions, e.g. when + looping ([#3829](https://github.com/google/ExoPlayer/issues/3829)). * IMA extension: * Refine the previous fix for empty ad groups to avoid discarding ad breaks unnecessarily ([#4030](https://github.com/google/ExoPlayer/issues/4030)), 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 0db52daa12a..bb7ef22ef42 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 @@ -215,9 +215,7 @@ void configure( */ void play(); - /** - * Signals to the sink that the next buffer is discontinuous with the previous buffer. - */ + /** Signals to the sink that the next buffer may be discontinuous with the previous buffer. */ void handleDiscontinuity(); /** 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 aed4c63c75d..f9a8c8ced5f 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 @@ -642,9 +642,10 @@ public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs) if (startMediaTimeState == START_NEED_SYNC) { // Adjust startMediaTimeUs to be consistent with the current buffer's start time and the // number of bytes submitted. - startMediaTimeUs += (presentationTimeUs - expectedPresentationTimeUs); + long adjustmentUs = presentationTimeUs - expectedPresentationTimeUs; + startMediaTimeUs += adjustmentUs; startMediaTimeState = START_IN_SYNC; - if (listener != null) { + if (listener != null && adjustmentUs != 0) { listener.onPositionDiscontinuity(); } } 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 1d3e65f7ff3..23cf893b8a5 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 @@ -25,6 +25,7 @@ import android.media.audiofx.Virtualizer; import android.os.Handler; import android.support.annotation.Nullable; +import android.util.Log; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; @@ -68,9 +69,19 @@ @TargetApi(16) public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock { + /** + * Maximum number of tracked pending stream change times. Generally there is zero or one pending + * stream change. We track more to allow for pending changes that have fewer samples than the + * codec latency. + */ + private static final int MAX_PENDING_STREAM_CHANGE_COUNT = 10; + + private static final String TAG = "MediaCodecAudioRenderer"; + private final Context context; private final EventDispatcher eventDispatcher; private final AudioSink audioSink; + private final long[] pendingStreamChangeTimesUs; private int codecMaxInputSize; private boolean passthroughEnabled; @@ -83,6 +94,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media private long currentPositionUs; private boolean allowFirstBufferPositionDiscontinuity; private boolean allowPositionDiscontinuity; + private long lastInputTimeUs; + private int pendingStreamChangeCount; /** * @param context A context. @@ -241,6 +254,8 @@ public MediaCodecAudioRenderer( /* assumedMinimumCodecOperatingRate= */ 44100); this.context = context.getApplicationContext(); this.audioSink = audioSink; + lastInputTimeUs = C.TIME_UNSET; + pendingStreamChangeTimesUs = new long[MAX_PENDING_STREAM_CHANGE_COUNT]; eventDispatcher = new EventDispatcher(eventHandler, eventListener); audioSink.setListener(new AudioSinkListener()); } @@ -469,6 +484,22 @@ protected void onEnabled(boolean joining) throws ExoPlaybackException { } } + @Override + protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException { + super.onStreamChanged(formats, offsetUs); + if (lastInputTimeUs != C.TIME_UNSET) { + if (pendingStreamChangeCount == pendingStreamChangeTimesUs.length) { + Log.w( + TAG, + "Too many stream changes, so dropping change at " + + pendingStreamChangeTimesUs[pendingStreamChangeCount - 1]); + } else { + pendingStreamChangeCount++; + } + pendingStreamChangeTimesUs[pendingStreamChangeCount - 1] = lastInputTimeUs; + } + } + @Override protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { super.onPositionReset(positionUs, joining); @@ -476,6 +507,8 @@ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlayb currentPositionUs = positionUs; allowFirstBufferPositionDiscontinuity = true; allowPositionDiscontinuity = true; + lastInputTimeUs = C.TIME_UNSET; + pendingStreamChangeCount = 0; } @Override @@ -494,6 +527,8 @@ protected void onStopped() { @Override protected void onDisabled() { try { + lastInputTimeUs = C.TIME_UNSET; + pendingStreamChangeCount = 0; audioSink.release(); } finally { try { @@ -544,6 +579,22 @@ protected void onQueueInputBuffer(DecoderInputBuffer buffer) { } allowFirstBufferPositionDiscontinuity = false; } + lastInputTimeUs = Math.max(buffer.timeUs, lastInputTimeUs); + } + + @Override + protected void onProcessedOutputBuffer(long presentationTimeUs) { + super.onProcessedOutputBuffer(presentationTimeUs); + while (pendingStreamChangeCount != 0 && presentationTimeUs >= pendingStreamChangeTimesUs[0]) { + audioSink.handleDiscontinuity(); + pendingStreamChangeCount--; + System.arraycopy( + pendingStreamChangeTimesUs, + /* srcPos= */ 1, + pendingStreamChangeTimesUs, + /* destPos= */ 0, + pendingStreamChangeCount); + } } @Override