From 1d766c4603a9767f8ef69c8db2445af709c71547 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Tue, 23 Apr 2019 09:18:03 +0100 Subject: [PATCH] Play out remaining data on reconfiguration Before this change we'd release the audio track and create a new one as soon as audio processors had drained when reconfiguring. Fix this behavior by stop()ing the AudioTrack to play out all written data. Issue: #2446 PiperOrigin-RevId: 244812402 --- RELEASENOTES.md | 3 + .../exoplayer2/audio/DefaultAudioSink.java | 57 +++++++++++-------- library/ui/build.gradle | 2 +- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c6d3faadc9..4b9bda112a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -4,6 +4,9 @@ * Display last frame when seeking to end of stream ([#2568](https://github.com/google/ExoPlayer/issues/2568)). +* Audio: + * Fix an issue where not all audio was played out when the configuration + for the underlying track was changing (e.g., at some period transitions). * UI: Fix `PlayerView` incorrectly consuming touch events if no controller is attached ([#6109](https://github.com/google/ExoPlayer/issues/6133)). * CEA608: Fix repetition of special North American characters 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 a3c0990366..a65a94d965 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 @@ -272,6 +272,7 @@ public final class DefaultAudioSink implements AudioSink { private int preV21OutputBufferOffset; private int drainingAudioProcessorIndex; private boolean handledEndOfStream; + private boolean stoppedAudioTrack; private boolean playing; private int audioSessionId; @@ -465,19 +466,15 @@ public final class DefaultAudioSink implements AudioSink { processingEnabled, canApplyPlaybackParameters, availableAudioProcessors); - if (isInitialized()) { - if (!pendingConfiguration.canReuseAudioTrack(configuration)) { - // We need a new AudioTrack before we can handle more input. We should first stop() the - // track and wait for audio to play out (tracked by [Internal: b/33161961]), but for now we - // discard the audio track immediately. - flush(); - } else if (flushAudioProcessors) { - // We don't need a new AudioTrack but audio processors need to be drained and flushed. - this.pendingConfiguration = pendingConfiguration; - return; - } + // If we have a pending configuration already, we always drain audio processors as the preceding + // configuration may have required it (even if this one doesn't). + boolean drainAudioProcessors = flushAudioProcessors || this.pendingConfiguration != null; + if (isInitialized() + && (!pendingConfiguration.canReuseAudioTrack(configuration) || drainAudioProcessors)) { + this.pendingConfiguration = pendingConfiguration; + } else { + configuration = pendingConfiguration; } - configuration = pendingConfiguration; } private void setupAudioProcessors() { @@ -579,12 +576,21 @@ public final class DefaultAudioSink implements AudioSink { Assertions.checkArgument(inputBuffer == null || buffer == inputBuffer); if (pendingConfiguration != null) { - // We are waiting for audio processors to drain before applying a the new configuration. if (!drainAudioProcessorsToEndOfStream()) { + // There's still pending data in audio processors to write to the track. return false; + } else if (!pendingConfiguration.canReuseAudioTrack(configuration)) { + playPendingData(); + if (hasPendingData()) { + // We're waiting for playout on the current audio track to finish. + return false; + } + flush(); + } else { + // The current audio track can be reused for the new configuration. + configuration = pendingConfiguration; + pendingConfiguration = null; } - configuration = pendingConfiguration; - pendingConfiguration = null; playbackParameters = configuration.canApplyPlaybackParameters ? audioProcessorChain.applyPlaybackParameters(playbackParameters) @@ -786,15 +792,8 @@ public final class DefaultAudioSink implements AudioSink { @Override public void playToEndOfStream() throws WriteException { - if (handledEndOfStream || !isInitialized()) { - return; - } - - if (drainAudioProcessorsToEndOfStream()) { - // The audio processors have drained, so drain the underlying audio track. - audioTrackPositionTracker.handleEndOfStream(getWrittenFrames()); - audioTrack.stop(); - bytesUntilNextAvSync = 0; + if (!handledEndOfStream && isInitialized() && drainAudioProcessorsToEndOfStream()) { + playPendingData(); handledEndOfStream = true; } } @@ -976,6 +975,7 @@ public final class DefaultAudioSink implements AudioSink { flushAudioProcessors(); inputBuffer = null; outputBuffer = null; + stoppedAudioTrack = false; handledEndOfStream = false; drainingAudioProcessorIndex = C.INDEX_UNSET; avSyncHeader = null; @@ -1223,6 +1223,15 @@ public final class DefaultAudioSink implements AudioSink { audioTrack.setStereoVolume(volume, volume); } + private void playPendingData() { + if (!stoppedAudioTrack) { + stoppedAudioTrack = true; + audioTrackPositionTracker.handleEndOfStream(getWrittenFrames()); + audioTrack.stop(); + bytesUntilNextAvSync = 0; + } + } + /** Stores playback parameters with the position and media time at which they apply. */ private static final class PlaybackParametersCheckpoint { diff --git a/library/ui/build.gradle b/library/ui/build.gradle index 49446b25de..6384bf920f 100644 --- a/library/ui/build.gradle +++ b/library/ui/build.gradle @@ -40,7 +40,7 @@ android { dependencies { implementation project(modulePrefix + 'library-core') - implementation 'androidx.media:media:1.0.0' + implementation 'androidx.media:media:1.0.1' implementation 'androidx.annotation:annotation:1.0.2' compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion testImplementation project(modulePrefix + 'testutils-robolectric')