Keep AudioTrack on seek - experimental.

PiperOrigin-RevId: 326622573
This commit is contained in:
samrobinson 2020-08-14 11:12:27 +01:00 committed by kim-vde
parent c2ac33af1b
commit eabc486b58
6 changed files with 153 additions and 27 deletions

View File

@ -362,6 +362,18 @@ public interface AudioSink {
*/
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 renderer, releasing any resources that it currently holds. */
void reset();
}

View File

@ -112,6 +112,8 @@ public abstract class DecoderAudioRenderer<
private int encoderDelay;
private int encoderPadding;
private boolean experimentalKeepAudioTrackOnSeek;
@Nullable private T decoder;
@Nullable private DecoderInputBuffer inputBuffer;
@ -185,6 +187,19 @@ public abstract class DecoderAudioRenderer<
audioTrackNeedsConfigure = 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() {
@ -507,7 +522,12 @@ public abstract class DecoderAudioRenderer<
@Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
audioSink.flush();
if (experimentalKeepAudioTrackOnSeek) {
audioSink.experimentalFlushWithoutAudioTrackRelease();
} else {
audioSink.flush();
}
currentPositionUs = positionUs;
allowFirstBufferPositionDiscontinuity = true;
allowPositionDiscontinuity = true;

View File

@ -279,7 +279,9 @@ public final class DefaultAudioSink implements AudioSink {
@MonotonicNonNull private StreamEventCallbackV29 offloadStreamEventCallbackV29;
@Nullable private Listener listener;
/** Used to keep the audio session active on pre-V21 builds (see {@link #initialize(long)}). */
/**
* Used to keep the audio session active on pre-V21 builds (see {@link #initializeAudioTrack()}).
*/
@Nullable private AudioTrack keepSessionIdAudioTrack;
@Nullable private Configuration pendingConfiguration;
@ -300,6 +302,7 @@ public final class DefaultAudioSink implements AudioSink {
private long writtenEncodedFrames;
private int framesPerEncodedSample;
private boolean startMediaTimeUsNeedsSync;
private boolean startMediaTimeUsNeedsInit;
private long startMediaTimeUs;
private float volume;
@ -470,7 +473,7 @@ public final class DefaultAudioSink implements AudioSink {
@Override
public long getCurrentPositionUs(boolean sourceEnded) {
if (!isInitialized()) {
if (!isAudioTrackInitialized() || startMediaTimeUsNeedsInit) {
return CURRENT_POSITION_NOT_SET;
}
long positionUs = audioTrackPositionTracker.getCurrentPositionUs(sourceEnded);
@ -581,7 +584,7 @@ public final class DefaultAudioSink implements AudioSink {
specifiedBufferSize,
canApplyPlaybackParameters,
availableAudioProcessors);
if (isInitialized()) {
if (isAudioTrackInitialized()) {
this.pendingConfiguration = pendingConfiguration;
} else {
configuration = pendingConfiguration;
@ -612,7 +615,7 @@ public final class DefaultAudioSink implements AudioSink {
}
}
private void initialize(long presentationTimeUs) throws InitializationException {
private void initializeAudioTrack() 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
@ -647,17 +650,9 @@ public final class DefaultAudioSink implements AudioSink {
}
}
startMediaTimeUs = max(0, presentationTimeUs);
startMediaTimeUsNeedsSync = false;
if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) {
setAudioTrackPlaybackSpeedV23(audioTrackPlaybackSpeed);
}
applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
audioTrackPositionTracker.setAudioTrack(
audioTrack,
configuration.outputMode == OUTPUT_MODE_PASSTHROUGH,
/* isPassthrough= */ configuration.outputMode == OUTPUT_MODE_PASSTHROUGH,
configuration.outputEncoding,
configuration.outputPcmFrameSize,
configuration.bufferSize);
@ -667,12 +662,14 @@ public final class DefaultAudioSink implements AudioSink {
audioTrack.attachAuxEffect(auxEffectInfo.effectId);
audioTrack.setAuxEffectSendLevel(auxEffectInfo.sendLevel);
}
startMediaTimeUsNeedsInit = true;
}
@Override
public void play() {
playing = true;
if (isInitialized()) {
if (isAudioTrackInitialized()) {
audioTrackPositionTracker.start();
audioTrack.play();
}
@ -716,8 +713,20 @@ public final class DefaultAudioSink implements AudioSink {
applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
}
if (!isInitialized()) {
initialize(presentationTimeUs);
if (!isAudioTrackInitialized()) {
initializeAudioTrack();
}
if (startMediaTimeUsNeedsInit) {
startMediaTimeUs = max(0, presentationTimeUs);
startMediaTimeUsNeedsSync = false;
startMediaTimeUsNeedsInit = false;
if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) {
setAudioTrackPlaybackSpeedV23(audioTrackPlaybackSpeed);
}
applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
if (playing) {
play();
}
@ -945,7 +954,7 @@ public final class DefaultAudioSink implements AudioSink {
@Override
public void playToEndOfStream() throws WriteException {
if (!handledEndOfStream && isInitialized() && drainToEndOfStream()) {
if (!handledEndOfStream && isAudioTrackInitialized() && drainToEndOfStream()) {
playPendingData();
handledEndOfStream = true;
}
@ -987,12 +996,13 @@ public final class DefaultAudioSink implements AudioSink {
@Override
public boolean isEnded() {
return !isInitialized() || (handledEndOfStream && !hasPendingData());
return !isAudioTrackInitialized() || (handledEndOfStream && !hasPendingData());
}
@Override
public boolean hasPendingData() {
return isInitialized() && audioTrackPositionTracker.hasPendingData(getWrittenFrames());
return isAudioTrackInitialized()
&& audioTrackPositionTracker.hasPendingData(getWrittenFrames());
}
@Override
@ -1090,7 +1100,7 @@ public final class DefaultAudioSink implements AudioSink {
}
private void setVolumeInternal() {
if (!isInitialized()) {
if (!isAudioTrackInitialized()) {
// Do nothing.
} else if (Util.SDK_INT >= 21) {
setVolumeInternalV21(audioTrack, volume);
@ -1102,14 +1112,14 @@ public final class DefaultAudioSink implements AudioSink {
@Override
public void pause() {
playing = false;
if (isInitialized() && audioTrackPositionTracker.pause()) {
if (isAudioTrackInitialized() && audioTrackPositionTracker.pause()) {
audioTrack.pause();
}
}
@Override
public void flush() {
if (isInitialized()) {
if (isAudioTrackInitialized()) {
resetSinkStateForFlush();
if (audioTrackPositionTracker.isPlaying()) {
@ -1141,6 +1151,36 @@ public final class DefaultAudioSink implements AudioSink {
}
}
@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)
if (Util.SDK_INT < 25) {
flush();
return;
}
if (!isAudioTrackInitialized()) {
return;
}
resetSinkStateForFlush();
if (audioTrackPositionTracker.isPlaying()) {
audioTrack.pause();
}
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();
@ -1204,7 +1244,7 @@ public final class DefaultAudioSink implements AudioSink {
@RequiresApi(23)
private void setAudioTrackPlaybackSpeedV23(float audioTrackPlaybackSpeed) {
if (isInitialized()) {
if (isAudioTrackInitialized()) {
PlaybackParams playbackParams =
new PlaybackParams()
.allowDefaults()
@ -1233,7 +1273,7 @@ public final class DefaultAudioSink implements AudioSink {
skipSilence,
/* mediaTimeUs= */ C.TIME_UNSET,
/* audioTrackPositionUs= */ C.TIME_UNSET);
if (isInitialized()) {
if (isAudioTrackInitialized()) {
// Drain the audio processors so we can determine the frame position at which the new
// parameters apply.
this.afterDrainParameters = mediaPositionParameters;
@ -1313,7 +1353,7 @@ public final class DefaultAudioSink implements AudioSink {
+ configuration.framesToDurationUs(audioProcessorChain.getSkippedOutputFrameCount());
}
private boolean isInitialized() {
private boolean isAudioTrackInitialized() {
return audioTrack != null;
}

View File

@ -147,6 +147,11 @@ public class ForwardingAudioSink implements AudioSink {
sink.flush();
}
@Override
public void experimentalFlushWithoutAudioTrackRelease() {
sink.experimentalFlushWithoutAudioTrackRelease();
}
@Override
public void reset() {
sink.reset();

View File

@ -97,6 +97,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private boolean allowFirstBufferPositionDiscontinuity;
private boolean allowPositionDiscontinuity;
private boolean experimentalKeepAudioTrackOnSeek;
@Nullable private WakeupListener wakeupListener;
/**
@ -205,6 +207,19 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
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
@Capabilities
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
@ -465,7 +480,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
super.onPositionReset(positionUs, joining);
audioSink.flush();
if (experimentalKeepAudioTrackOnSeek) {
audioSink.experimentalFlushWithoutAudioTrackRelease();
} else {
audioSink.flush();
}
currentPositionUs = positionUs;
allowFirstBufferPositionDiscontinuity = true;
allowPositionDiscontinuity = true;

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.audio;
import static com.google.android.exoplayer2.audio.AudioSink.CURRENT_POSITION_NOT_SET;
import static com.google.android.exoplayer2.audio.AudioSink.SINK_FORMAT_SUPPORTED_DIRECTLY;
import static com.google.android.exoplayer2.audio.AudioSink.SINK_FORMAT_SUPPORTED_WITH_TRANSCODING;
import static com.google.common.truth.Truth.assertThat;
@ -275,6 +276,34 @@ public final class DefaultAudioSinkTest {
assertThat(defaultAudioSink.supportsFormat(aacLcFormat)).isFalse();
}
@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.
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
// After the experimental flush we can successfully queue more input.
defaultAudioSink.experimentalFlushWithoutAudioTrackRelease();
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
/* presentationTimeUs= */ 5_000,
/* encodedAccessUnitCount= */ 1);
}
@Test
public void getCurrentPosition_returnsUnset_afterExperimentalFlush() throws Exception {
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
/* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND,
/* encodedAccessUnitCount= */ 1);
defaultAudioSink.experimentalFlushWithoutAudioTrackRelease();
assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false))
.isEqualTo(CURRENT_POSITION_NOT_SET);
}
private void configureDefaultAudioSink(int channelCount) throws AudioSink.ConfigurationException {
configureDefaultAudioSink(channelCount, /* trimStartFrames= */ 0, /* trimEndFrames= */ 0);
}