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 0f1c314637..2a8f91ae20 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 0db52daa12..bb7ef22ef4 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 @@ public interface AudioSink { */ 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 aed4c63c75..f9a8c8ced5 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 final class DefaultAudioSink implements AudioSink { 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 1d3e65f7ff..23cf893b8a 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.MediaFormat; 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 @@ import java.util.List; @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 class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media /* 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 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } } + @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 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media currentPositionUs = positionUs; allowFirstBufferPositionDiscontinuity = true; allowPositionDiscontinuity = true; + lastInputTimeUs = C.TIME_UNSET; + pendingStreamChangeCount = 0; } @Override @@ -494,6 +527,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media @Override protected void onDisabled() { try { + lastInputTimeUs = C.TIME_UNSET; + pendingStreamChangeCount = 0; audioSink.release(); } finally { try { @@ -544,6 +579,22 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } 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