Commit b816a3fe by samrobinson Committed by Ian Baker

Keep AudioTrack on flush as default

PiperOrigin-RevId: 444264961
parent f1cda5fe
...@@ -457,18 +457,6 @@ public interface AudioSink { ...@@ -457,18 +457,6 @@ public interface AudioSink {
*/ */
void flush(); void flush();
/**
* Flushes the sink, after which it is ready to receive buffers from a new playback position.
*
* <p>Does not release the {@link AudioTrack} held by the sink.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*
* <p>Only for experimental use as part of {@link
* MediaCodecAudioRenderer#experimentalSetEnableKeepAudioTrackOnSeek(boolean)}.
*/
void experimentalFlushWithoutAudioTrackRelease();
/** Resets the sink, releasing any resources that it currently holds. */ /** Resets the sink, releasing any resources that it currently holds. */
void reset(); void reset();
} }
...@@ -129,7 +129,6 @@ public abstract class DecoderAudioRenderer< ...@@ -129,7 +129,6 @@ public abstract class DecoderAudioRenderer<
private int encoderDelay; private int encoderDelay;
private int encoderPadding; private int encoderPadding;
private boolean experimentalKeepAudioTrackOnSeek;
private boolean firstStreamSampleRead; private boolean firstStreamSampleRead;
@Nullable private T decoder; @Nullable private T decoder;
...@@ -209,19 +208,6 @@ public abstract class DecoderAudioRenderer< ...@@ -209,19 +208,6 @@ public abstract class DecoderAudioRenderer<
audioSinkNeedsConfigure = true; 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.
*
* <p>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 @Override
@Nullable @Nullable
public MediaClock getMediaClock() { public MediaClock getMediaClock() {
...@@ -555,12 +541,7 @@ public abstract class DecoderAudioRenderer< ...@@ -555,12 +541,7 @@ public abstract class DecoderAudioRenderer<
@Override @Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
if (experimentalKeepAudioTrackOnSeek) { audioSink.flush();
audioSink.experimentalFlushWithoutAudioTrackRelease();
} else {
audioSink.flush();
}
currentPositionUs = positionUs; currentPositionUs = positionUs;
allowFirstBufferPositionDiscontinuity = true; allowFirstBufferPositionDiscontinuity = true;
allowPositionDiscontinuity = true; allowPositionDiscontinuity = true;
......
...@@ -907,7 +907,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -907,7 +907,7 @@ public final class DefaultAudioSink implements AudioSink {
// We're waiting for playout on the current audio track to finish. // We're waiting for playout on the current audio track to finish.
return false; return false;
} }
flush(); flushAndReleaseAudioTrack();
} else { } else {
// The current audio track can be reused for the new configuration. // The current audio track can be reused for the new configuration.
configuration = pendingConfiguration; configuration = pendingConfiguration;
...@@ -1034,7 +1034,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1034,7 +1034,7 @@ public final class DefaultAudioSink implements AudioSink {
if (audioTrackPositionTracker.isStalled(getWrittenFrames())) { if (audioTrackPositionTracker.isStalled(getWrittenFrames())) {
Log.w(TAG, "Resetting stalled audio track"); Log.w(TAG, "Resetting stalled audio track");
flush(); flushAndReleaseAudioTrack();
return true; return true;
} }
...@@ -1324,7 +1324,8 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1324,7 +1324,8 @@ public final class DefaultAudioSink implements AudioSink {
// The audio attributes are ignored in tunneling mode, so no need to reset. // The audio attributes are ignored in tunneling mode, so no need to reset.
return; return;
} }
flush(); // audioAttributes change requires the audioTrack to be recreated.
flushAndReleaseAudioTrack();
} }
@Override @Override
...@@ -1337,7 +1338,8 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1337,7 +1338,8 @@ public final class DefaultAudioSink implements AudioSink {
if (this.audioSessionId != audioSessionId) { if (this.audioSessionId != audioSessionId) {
this.audioSessionId = audioSessionId; this.audioSessionId = audioSessionId;
externalAudioSessionIdProvided = audioSessionId != C.AUDIO_SESSION_ID_UNSET; externalAudioSessionIdProvided = audioSessionId != C.AUDIO_SESSION_ID_UNSET;
flush(); // audioSessionId change requires the audioTrack to be recreated.
flushAndReleaseAudioTrack();
} }
} }
...@@ -1365,7 +1367,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1365,7 +1367,7 @@ public final class DefaultAudioSink implements AudioSink {
Assertions.checkState(externalAudioSessionIdProvided); Assertions.checkState(externalAudioSessionIdProvided);
if (!tunneling) { if (!tunneling) {
tunneling = true; tunneling = true;
flush(); flushAndReleaseAudioTrack();
} }
} }
...@@ -1373,7 +1375,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1373,7 +1375,7 @@ public final class DefaultAudioSink implements AudioSink {
public void disableTunneling() { public void disableTunneling() {
if (tunneling) { if (tunneling) {
tunneling = false; tunneling = false;
flush(); flushAndReleaseAudioTrack();
} }
} }
...@@ -1405,70 +1407,26 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1405,70 +1407,26 @@ public final class DefaultAudioSink implements AudioSink {
@Override @Override
public void flush() { public void flush() {
if (isAudioTrackInitialized()) { if (!isAudioTrackInitialized()) {
resetSinkStateForFlush(); return;
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();
} }
writeExceptionPendingExceptionHolder.clear();
initializationExceptionPendingExceptionHolder.clear();
}
@Override // Prior to SDK 25, AudioTrack flush does not work as intended, so it must be released and
public void experimentalFlushWithoutAudioTrackRelease() { // reinitialized. (Internal reference: b/143500232)
// Prior to SDK 25, AudioTrack flush does not work as intended, and therefore it must be
// released and reinitialized. (Internal reference: b/143500232)
if (Util.SDK_INT < 25) { if (Util.SDK_INT < 25) {
flush(); flushAndReleaseAudioTrack();
return; return;
} }
writeExceptionPendingExceptionHolder.clear(); writeExceptionPendingExceptionHolder.clear();
initializationExceptionPendingExceptionHolder.clear(); initializationExceptionPendingExceptionHolder.clear();
if (!isAudioTrackInitialized()) {
return;
}
resetSinkStateForFlush(); resetSinkStateForFlush();
if (audioTrackPositionTracker.isPlaying()) { if (audioTrackPositionTracker.isPlaying()) {
audioTrack.pause(); audioTrack.pause();
} }
audioTrack.flush();
audioTrack.flush();
audioTrackPositionTracker.reset(); audioTrackPositionTracker.reset();
audioTrackPositionTracker.setAudioTrack( audioTrackPositionTracker.setAudioTrack(
audioTrack, audioTrack,
...@@ -1476,13 +1434,12 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1476,13 +1434,12 @@ public final class DefaultAudioSink implements AudioSink {
configuration.outputEncoding, configuration.outputEncoding,
configuration.outputPcmFrameSize, configuration.outputPcmFrameSize,
configuration.bufferSize); configuration.bufferSize);
startMediaTimeUsNeedsInit = true; startMediaTimeUsNeedsInit = true;
} }
@Override @Override
public void reset() { public void reset() {
flush(); flushAndReleaseAudioTrack();
for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) { for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) {
audioProcessor.reset(); audioProcessor.reset();
} }
...@@ -1495,6 +1452,51 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1495,6 +1452,51 @@ public final class DefaultAudioSink implements AudioSink {
// Internal methods. // 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() { private void resetSinkStateForFlush() {
submittedPcmBytes = 0; submittedPcmBytes = 0;
submittedEncodedFrames = 0; submittedEncodedFrames = 0;
......
...@@ -164,11 +164,6 @@ public class ForwardingAudioSink implements AudioSink { ...@@ -164,11 +164,6 @@ public class ForwardingAudioSink implements AudioSink {
} }
@Override @Override
public void experimentalFlushWithoutAudioTrackRelease() {
sink.experimentalFlushWithoutAudioTrackRelease();
}
@Override
public void reset() { public void reset() {
sink.reset(); sink.reset();
} }
......
...@@ -109,8 +109,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -109,8 +109,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private boolean allowPositionDiscontinuity; private boolean allowPositionDiscontinuity;
private boolean audioSinkNeedsConfigure; private boolean audioSinkNeedsConfigure;
private boolean experimentalKeepAudioTrackOnSeek;
@Nullable private WakeupListener wakeupListener; @Nullable private WakeupListener wakeupListener;
/** /**
...@@ -266,19 +264,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -266,19 +264,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return TAG; 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.
*
* <p>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 @Override
protected @Capabilities int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) protected @Capabilities int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
throws DecoderQueryException { throws DecoderQueryException {
...@@ -531,11 +516,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media ...@@ -531,11 +516,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override @Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
super.onPositionReset(positionUs, joining); super.onPositionReset(positionUs, joining);
if (experimentalKeepAudioTrackOnSeek) { audioSink.flush();
audioSink.experimentalFlushWithoutAudioTrackRelease();
} else {
audioSink.flush();
}
currentPositionUs = positionUs; currentPositionUs = positionUs;
allowFirstBufferPositionDiscontinuity = true; allowFirstBufferPositionDiscontinuity = true;
......
...@@ -265,15 +265,15 @@ public final class DefaultAudioSinkTest { ...@@ -265,15 +265,15 @@ public final class DefaultAudioSinkTest {
} }
@Test @Test
public void handlesBufferAfterExperimentalFlush() throws Exception { public void handleBuffer_afterFlush_doesntThrow() throws Exception {
// This is demonstrating that no Exceptions are thrown as a result of handling a buffer after an // This is demonstrating that no Exceptions are thrown as a result of handling a buffer after a
// experimental flush. // flush.
configureDefaultAudioSink(CHANNEL_COUNT_STEREO); configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
defaultAudioSink.handleBuffer( defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1); createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
// After the experimental flush we can successfully queue more input. // After the flush we can successfully queue more input.
defaultAudioSink.experimentalFlushWithoutAudioTrackRelease(); defaultAudioSink.flush();
defaultAudioSink.handleBuffer( defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(), createDefaultSilenceBuffer(),
/* presentationTimeUs= */ 5_000, /* presentationTimeUs= */ 5_000,
...@@ -281,13 +281,13 @@ public final class DefaultAudioSinkTest { ...@@ -281,13 +281,13 @@ public final class DefaultAudioSinkTest {
} }
@Test @Test
public void getCurrentPosition_returnsUnset_afterExperimentalFlush() throws Exception { public void getCurrentPosition_afterFlush_returnsUnset() throws Exception {
configureDefaultAudioSink(CHANNEL_COUNT_STEREO); configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
defaultAudioSink.handleBuffer( defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(), createDefaultSilenceBuffer(),
/* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND, /* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND,
/* encodedAccessUnitCount= */ 1); /* encodedAccessUnitCount= */ 1);
defaultAudioSink.experimentalFlushWithoutAudioTrackRelease(); defaultAudioSink.flush();
assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false)) assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false))
.isEqualTo(CURRENT_POSITION_NOT_SET); .isEqualTo(CURRENT_POSITION_NOT_SET);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment