Skip to content

Commit

Permalink
Keep AudioTrack on flush as default
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 444264961
  • Loading branch information
Samrobbo authored and icbaker committed Apr 26, 2022
1 parent 0760520 commit 8da6c35
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 122 deletions.
Expand Up @@ -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.
*
* <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. */
void reset();
}
Expand Up @@ -125,7 +125,6 @@ public abstract class DecoderAudioRenderer<
private int encoderDelay;
private int encoderPadding;

private boolean experimentalKeepAudioTrackOnSeek;
private boolean firstStreamSampleRead;

@Nullable private T decoder;
Expand Down Expand Up @@ -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.
*
* <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
@Nullable
public MediaClock getMediaClock() {
Expand Down Expand Up @@ -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;
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -1025,7 +1025,7 @@ public boolean handleBuffer(

if (audioTrackPositionTracker.isStalled(getWrittenFrames())) {
Log.w(TAG, "Resetting stalled audio track");
flush();
flushAndReleaseAudioTrack();
return true;
}

Expand Down Expand Up @@ -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
Expand All @@ -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();
}
}

Expand Down Expand Up @@ -1356,15 +1358,15 @@ public void enableTunnelingV21() {
Assertions.checkState(externalAudioSessionIdProvided);
if (!tunneling) {
tunneling = true;
flush();
flushAndReleaseAudioTrack();
}
}

@Override
public void disableTunneling() {
if (tunneling) {
tunneling = false;
flush();
flushAndReleaseAudioTrack();
}
}

Expand Down Expand Up @@ -1396,84 +1398,39 @@ 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,
/* isPassthrough= */ configuration.outputMode == OUTPUT_MODE_PASSTHROUGH,
configuration.outputEncoding,
configuration.outputPcmFrameSize,
configuration.bufferSize);

startMediaTimeUsNeedsInit = true;
}

@Override
public void reset() {
flush();
flushAndReleaseAudioTrack();
for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) {
audioProcessor.reset();
}
Expand All @@ -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;
Expand Down
Expand Up @@ -159,11 +159,6 @@ public void flush() {
sink.flush();
}

@Override
public void experimentalFlushWithoutAudioTrackRelease() {
sink.experimentalFlushWithoutAudioTrackRelease();
}

@Override
public void reset() {
sink.reset();
Expand Down
Expand Up @@ -105,8 +105,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private boolean allowPositionDiscontinuity;
private boolean audioSinkNeedsConfigure;

private boolean experimentalKeepAudioTrackOnSeek;

@Nullable private WakeupListener wakeupListener;

/**
Expand Down Expand Up @@ -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.
*
* <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
protected @Capabilities int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
throws DecoderQueryException {
Expand Down Expand Up @@ -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;
Expand Down
Expand Up @@ -265,29 +265,29 @@ 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,
/* encodedAccessUnitCount= */ 1);
}

@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);
}
Expand Down

0 comments on commit 8da6c35

Please sign in to comment.