From dbabb7c9a3ba6640c51230bc7932c7ab471284d8 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 4 Jul 2019 12:54:56 +0100 Subject: [PATCH] Apply playback parameters in a consistent way. Currently, we sometimes apply new playback parameters directly and sometimes through the list of playbackParameterCheckpoints. Only when using the checkpoints, we also reset the offset and corresponding position for speedup position calculation. However, these offsets need to be changed in all cases to prevent calculation errors during speedup calculation[1]. This change channels all playback parameters changes through the checkpoints to ensure the offsets get updated accordingly. This fixes an issue introduced in https://github.com/google/ExoPlayer/commit/31911ca54a13b0003d6cf902b95c2ed445afa930. [1] - The speed up is calculated using the ratio of input and output bytes in SonicAudioProcessor.scaleDurationForSpeedUp. Whenever we set new playback parameters to the audio processor these two counts are reset. If we don't reset the offsets too, the scaled timestamp can be a large value compared to the input and output bytes causing massive inaccuracies (like the +20 seconds in the linked issue). Issue:#6117 PiperOrigin-RevId: 256533780 --- RELEASENOTES.md | 2 + .../exoplayer2/audio/DefaultAudioSink.java | 47 ++++++++++--------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4b9bda112a6..e666f3ff399 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,8 @@ * 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). + * Fix an issue where playback speed was applied inaccurately in playlists + ([#6117](https://github.com/google/ExoPlayer/issues/6117)). * 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 a65a94d9658..be1b7d3d53d 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 @@ -501,7 +501,7 @@ private void flushAudioProcessors() { } } - private void initialize() throws InitializationException { + private void initialize(long presentationTimeUs) throws InitializationException { // If we're asynchronously releasing a previous audio track then we block until it has been // released. This guarantees that we cannot end up in a state where we have multiple audio // track instances. Without this guarantee it would be possible, in extreme cases, to exhaust @@ -533,11 +533,7 @@ private void initialize() throws InitializationException { } } - playbackParameters = - configuration.canApplyPlaybackParameters - ? audioProcessorChain.applyPlaybackParameters(playbackParameters) - : PlaybackParameters.DEFAULT; - setupAudioProcessors(); + applyPlaybackParameters(playbackParameters, presentationTimeUs); audioTrackPositionTracker.setAudioTrack( audioTrack, @@ -591,15 +587,12 @@ public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs) configuration = pendingConfiguration; pendingConfiguration = null; } - playbackParameters = - configuration.canApplyPlaybackParameters - ? audioProcessorChain.applyPlaybackParameters(playbackParameters) - : PlaybackParameters.DEFAULT; - setupAudioProcessors(); + // Re-apply playback parameters. + applyPlaybackParameters(playbackParameters, presentationTimeUs); } if (!isInitialized()) { - initialize(); + initialize(presentationTimeUs); if (playing) { play(); } @@ -635,15 +628,7 @@ public boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs) } PlaybackParameters newPlaybackParameters = afterDrainPlaybackParameters; afterDrainPlaybackParameters = null; - newPlaybackParameters = audioProcessorChain.applyPlaybackParameters(newPlaybackParameters); - // Store the position and corresponding media time from which the parameters will apply. - playbackParametersCheckpoints.add( - new PlaybackParametersCheckpoint( - newPlaybackParameters, - Math.max(0, presentationTimeUs), - configuration.framesToDurationUs(getWrittenFrames()))); - // Update the set of active audio processors to take into account the new parameters. - setupAudioProcessors(); + applyPlaybackParameters(newPlaybackParameters, presentationTimeUs); } if (startMediaTimeState == START_NOT_SET) { @@ -857,8 +842,9 @@ public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParam // parameters apply. afterDrainPlaybackParameters = playbackParameters; } else { - // Update the playback parameters now. - this.playbackParameters = audioProcessorChain.applyPlaybackParameters(playbackParameters); + // Update the playback parameters now. They will be applied to the audio processors during + // initialization. + this.playbackParameters = playbackParameters; } } return this.playbackParameters; @@ -1040,6 +1026,21 @@ public void run() { }.start(); } + private void applyPlaybackParameters( + PlaybackParameters playbackParameters, long presentationTimeUs) { + PlaybackParameters newPlaybackParameters = + configuration.canApplyPlaybackParameters + ? audioProcessorChain.applyPlaybackParameters(playbackParameters) + : PlaybackParameters.DEFAULT; + // Store the position and corresponding media time from which the parameters will apply. + playbackParametersCheckpoints.add( + new PlaybackParametersCheckpoint( + newPlaybackParameters, + /* mediaTimeUs= */ Math.max(0, presentationTimeUs), + /* positionUs= */ configuration.framesToDurationUs(getWrittenFrames()))); + setupAudioProcessors(); + } + private long applySpeedup(long positionUs) { @Nullable PlaybackParametersCheckpoint checkpoint = null; while (!playbackParametersCheckpoints.isEmpty()