From 8da6c35bbbae84c91c49fc2d723224642d1670f9 Mon Sep 17 00:00:00 2001 From: samrobinson Date: Mon, 25 Apr 2022 15:38:36 +0100 Subject: [PATCH] Keep AudioTrack on flush as default PiperOrigin-RevId: 444264961 --- .../android/exoplayer2/audio/AudioSink.java | 12 -- .../audio/DecoderAudioRenderer.java | 21 +--- .../exoplayer2/audio/DefaultAudioSink.java | 118 +++++++++--------- .../exoplayer2/audio/ForwardingAudioSink.java | 5 - .../audio/MediaCodecAudioRenderer.java | 21 +--- .../audio/DefaultAudioSinkTest.java | 14 +-- 6 files changed, 69 insertions(+), 122 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 e7ff1b0ec6d..30e41c731ee 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 @@ -453,18 +453,6 @@ boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs, int encodedAcce */ void flush(); - /** - * Flushes the sink, after which it is ready to receive buffers from a new playback position. - * - *

Does not release the {@link AudioTrack} held by the sink. - * - *

This method is experimental, and will be renamed or removed in a future release. - * - *

Only for experimental use as part of {@link - * MediaCodecAudioRenderer#experimentalSetEnableKeepAudioTrackOnSeek(boolean)}. - */ - void experimentalFlushWithoutAudioTrackRelease(); - /** Resets the sink, releasing any resources that it currently holds. */ void reset(); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java index aabd3a04bdc..281c3af2bf3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java @@ -125,7 +125,6 @@ public abstract class DecoderAudioRenderer< private int encoderDelay; private int encoderPadding; - private boolean experimentalKeepAudioTrackOnSeek; private boolean firstStreamSampleRead; @Nullable private T decoder; @@ -205,19 +204,6 @@ public DecoderAudioRenderer( audioSinkNeedsConfigure = true; } - /** - * Sets whether to enable the experimental feature that keeps and flushes the {@link - * android.media.AudioTrack} when a seek occurs, as opposed to releasing and reinitialising. Off - * by default. - * - *

This method is experimental, and will be renamed or removed in a future release. - * - * @param enableKeepAudioTrackOnSeek Whether to keep the {@link android.media.AudioTrack} on seek. - */ - public void experimentalSetEnableKeepAudioTrackOnSeek(boolean enableKeepAudioTrackOnSeek) { - this.experimentalKeepAudioTrackOnSeek = enableKeepAudioTrackOnSeek; - } - @Override @Nullable public MediaClock getMediaClock() { @@ -551,12 +537,7 @@ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) @Override protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { - if (experimentalKeepAudioTrackOnSeek) { - audioSink.experimentalFlushWithoutAudioTrackRelease(); - } else { - audioSink.flush(); - } - + audioSink.flush(); currentPositionUs = positionUs; allowFirstBufferPositionDiscontinuity = true; allowPositionDiscontinuity = true; 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 38d03243cef..011c27f9dfd 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 @@ -898,7 +898,7 @@ public boolean handleBuffer( // We're waiting for playout on the current audio track to finish. return false; } - flush(); + flushAndReleaseAudioTrack(); } else { // The current audio track can be reused for the new configuration. configuration = pendingConfiguration; @@ -1025,7 +1025,7 @@ public boolean handleBuffer( if (audioTrackPositionTracker.isStalled(getWrittenFrames())) { Log.w(TAG, "Resetting stalled audio track"); - flush(); + flushAndReleaseAudioTrack(); return true; } @@ -1315,7 +1315,8 @@ public void setAudioAttributes(AudioAttributes audioAttributes) { // The audio attributes are ignored in tunneling mode, so no need to reset. return; } - flush(); + // audioAttributes change requires the audioTrack to be recreated. + flushAndReleaseAudioTrack(); } @Override @@ -1328,7 +1329,8 @@ public void setAudioSessionId(int audioSessionId) { if (this.audioSessionId != audioSessionId) { this.audioSessionId = audioSessionId; externalAudioSessionIdProvided = audioSessionId != C.AUDIO_SESSION_ID_UNSET; - flush(); + // audioSessionId change requires the audioTrack to be recreated. + flushAndReleaseAudioTrack(); } } @@ -1356,7 +1358,7 @@ public void enableTunnelingV21() { Assertions.checkState(externalAudioSessionIdProvided); if (!tunneling) { tunneling = true; - flush(); + flushAndReleaseAudioTrack(); } } @@ -1364,7 +1366,7 @@ public void enableTunnelingV21() { public void disableTunneling() { if (tunneling) { tunneling = false; - flush(); + flushAndReleaseAudioTrack(); } } @@ -1396,70 +1398,26 @@ public void pause() { @Override public void flush() { - if (isAudioTrackInitialized()) { - resetSinkStateForFlush(); - - if (audioTrackPositionTracker.isPlaying()) { - audioTrack.pause(); - } - if (isOffloadedPlayback(audioTrack)) { - checkNotNull(offloadStreamEventCallbackV29).unregister(audioTrack); - } - // AudioTrack.release can take some time, so we call it on a background thread. - final AudioTrack toRelease = audioTrack; - audioTrack = null; - if (Util.SDK_INT < 21 && !externalAudioSessionIdProvided) { - // Prior to API level 21, audio sessions are not kept alive once there are no components - // associated with them. If we generated the session ID internally, the only component - // associated with the session is the audio track that's being released, and therefore - // the session will not be kept alive. As a result, we need to generate a new session when - // we next create an audio track. - audioSessionId = C.AUDIO_SESSION_ID_UNSET; - } - if (pendingConfiguration != null) { - configuration = pendingConfiguration; - pendingConfiguration = null; - } - audioTrackPositionTracker.reset(); - releasingConditionVariable.close(); - new Thread("ExoPlayer:AudioTrackReleaseThread") { - @Override - public void run() { - try { - toRelease.flush(); - toRelease.release(); - } finally { - releasingConditionVariable.open(); - } - } - }.start(); + if (!isAudioTrackInitialized()) { + return; } - writeExceptionPendingExceptionHolder.clear(); - initializationExceptionPendingExceptionHolder.clear(); - } - @Override - public void experimentalFlushWithoutAudioTrackRelease() { - // Prior to SDK 25, AudioTrack flush does not work as intended, and therefore it must be - // released and reinitialized. (Internal reference: b/143500232) + // Prior to SDK 25, AudioTrack flush does not work as intended, so it must be released and + // reinitialized. (Internal reference: b/143500232) if (Util.SDK_INT < 25) { - flush(); + flushAndReleaseAudioTrack(); return; } writeExceptionPendingExceptionHolder.clear(); initializationExceptionPendingExceptionHolder.clear(); - if (!isAudioTrackInitialized()) { - return; - } - resetSinkStateForFlush(); if (audioTrackPositionTracker.isPlaying()) { audioTrack.pause(); } - audioTrack.flush(); + audioTrack.flush(); audioTrackPositionTracker.reset(); audioTrackPositionTracker.setAudioTrack( audioTrack, @@ -1467,13 +1425,12 @@ public void experimentalFlushWithoutAudioTrackRelease() { configuration.outputEncoding, configuration.outputPcmFrameSize, configuration.bufferSize); - startMediaTimeUsNeedsInit = true; } @Override public void reset() { - flush(); + flushAndReleaseAudioTrack(); for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) { audioProcessor.reset(); } @@ -1486,6 +1443,51 @@ public void reset() { // Internal methods. + private void flushAndReleaseAudioTrack() { + if (!isAudioTrackInitialized()) { + return; + } + + writeExceptionPendingExceptionHolder.clear(); + initializationExceptionPendingExceptionHolder.clear(); + + resetSinkStateForFlush(); + if (audioTrackPositionTracker.isPlaying()) { + audioTrack.pause(); + } + if (isOffloadedPlayback(audioTrack)) { + checkNotNull(offloadStreamEventCallbackV29).unregister(audioTrack); + } + // AudioTrack.release can take some time, so we call it on a background thread. + final AudioTrack toRelease = audioTrack; + audioTrack = null; + if (Util.SDK_INT < 21 && !externalAudioSessionIdProvided) { + // Prior to API level 21, audio sessions are not kept alive once there are no components + // associated with them. If we generated the session ID internally, the only component + // associated with the session is the audio track that's being released, and therefore + // the session will not be kept alive. As a result, we need to generate a new session when + // we next create an audio track. + audioSessionId = C.AUDIO_SESSION_ID_UNSET; + } + if (pendingConfiguration != null) { + configuration = pendingConfiguration; + pendingConfiguration = null; + } + audioTrackPositionTracker.reset(); + releasingConditionVariable.close(); + new Thread("ExoPlayer:AudioTrackReleaseThread") { + @Override + public void run() { + try { + toRelease.flush(); + toRelease.release(); + } finally { + releasingConditionVariable.open(); + } + } + }.start(); + } + private void resetSinkStateForFlush() { submittedPcmBytes = 0; submittedEncodedFrames = 0; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java index e8e5edd1cdf..5440a7eca25 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java @@ -159,11 +159,6 @@ public void flush() { sink.flush(); } - @Override - public void experimentalFlushWithoutAudioTrackRelease() { - sink.experimentalFlushWithoutAudioTrackRelease(); - } - @Override public void reset() { sink.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 af47dff3828..4f5c0d88aa5 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 @@ -105,8 +105,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media private boolean allowPositionDiscontinuity; private boolean audioSinkNeedsConfigure; - private boolean experimentalKeepAudioTrackOnSeek; - @Nullable private WakeupListener wakeupListener; /** @@ -262,19 +260,6 @@ public String getName() { return TAG; } - /** - * Sets whether to enable the experimental feature that keeps and flushes the {@link - * android.media.AudioTrack} when a seek occurs, as opposed to releasing and reinitialising. Off - * by default. - * - *

This method is experimental, and will be renamed or removed in a future release. - * - * @param enableKeepAudioTrackOnSeek Whether to keep the {@link android.media.AudioTrack} on seek. - */ - public void experimentalSetEnableKeepAudioTrackOnSeek(boolean enableKeepAudioTrackOnSeek) { - this.experimentalKeepAudioTrackOnSeek = enableKeepAudioTrackOnSeek; - } - @Override protected @Capabilities int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) throws DecoderQueryException { @@ -527,11 +512,7 @@ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream) @Override protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { super.onPositionReset(positionUs, joining); - if (experimentalKeepAudioTrackOnSeek) { - audioSink.experimentalFlushWithoutAudioTrackRelease(); - } else { - audioSink.flush(); - } + audioSink.flush(); currentPositionUs = positionUs; allowFirstBufferPositionDiscontinuity = true; diff --git a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java index 689519ce682..1082adf54bc 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/audio/DefaultAudioSinkTest.java @@ -265,15 +265,15 @@ public void audioSinkWithAacAudioCapabilitiesWithoutOffload_doesNotSupportAac() } @Test - public void handlesBufferAfterExperimentalFlush() throws Exception { - // This is demonstrating that no Exceptions are thrown as a result of handling a buffer after an - // experimental flush. + public void handleBuffer_afterFlush_doesntThrow() throws Exception { + // This is demonstrating that no Exceptions are thrown as a result of handling a buffer after a + // flush. configureDefaultAudioSink(CHANNEL_COUNT_STEREO); defaultAudioSink.handleBuffer( createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); - // After the experimental flush we can successfully queue more input. - defaultAudioSink.experimentalFlushWithoutAudioTrackRelease(); + // After the flush we can successfully queue more input. + defaultAudioSink.flush(); defaultAudioSink.handleBuffer( createDefaultSilenceBuffer(), /* presentationTimeUs= */ 5_000, @@ -281,13 +281,13 @@ public void handlesBufferAfterExperimentalFlush() throws Exception { } @Test - public void getCurrentPosition_returnsUnset_afterExperimentalFlush() throws Exception { + public void getCurrentPosition_afterFlush_returnsUnset() throws Exception { configureDefaultAudioSink(CHANNEL_COUNT_STEREO); defaultAudioSink.handleBuffer( createDefaultSilenceBuffer(), /* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND, /* encodedAccessUnitCount= */ 1); - defaultAudioSink.experimentalFlushWithoutAudioTrackRelease(); + defaultAudioSink.flush(); assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false)) .isEqualTo(CURRENT_POSITION_NOT_SET); }