mirror of
https://github.com/androidx/media.git
synced 2025-05-17 04:29:55 +08:00
Keep AudioTrack on flush as default
PiperOrigin-RevId: 444264961
This commit is contained in:
parent
0760520b23
commit
8da6c35bbb
@ -453,18 +453,6 @@ 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 sink, releasing any resources that it currently holds. */
|
||||
void reset();
|
||||
}
|
||||
|
@ -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 abstract class 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() {
|
||||
@ -551,12 +537,7 @@ public abstract class DecoderAudioRenderer<
|
||||
|
||||
@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;
|
||||
|
@ -898,7 +898,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
// 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 final class DefaultAudioSink implements AudioSink {
|
||||
|
||||
if (audioTrackPositionTracker.isStalled(getWrittenFrames())) {
|
||||
Log.w(TAG, "Resetting stalled audio track");
|
||||
flush();
|
||||
flushAndReleaseAudioTrack();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1315,7 +1315,8 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
// 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 final class DefaultAudioSink implements AudioSink {
|
||||
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 final class DefaultAudioSink implements AudioSink {
|
||||
Assertions.checkState(externalAudioSessionIdProvided);
|
||||
if (!tunneling) {
|
||||
tunneling = true;
|
||||
flush();
|
||||
flushAndReleaseAudioTrack();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1364,7 +1366,7 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
public void disableTunneling() {
|
||||
if (tunneling) {
|
||||
tunneling = false;
|
||||
flush();
|
||||
flushAndReleaseAudioTrack();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1396,70 +1398,26 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
|
||||
@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();
|
||||
}
|
||||
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)
|
||||
if (Util.SDK_INT < 25) {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
|
||||
writeExceptionPendingExceptionHolder.clear();
|
||||
initializationExceptionPendingExceptionHolder.clear();
|
||||
|
||||
if (!isAudioTrackInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
flushAndReleaseAudioTrack();
|
||||
return;
|
||||
}
|
||||
|
||||
writeExceptionPendingExceptionHolder.clear();
|
||||
initializationExceptionPendingExceptionHolder.clear();
|
||||
|
||||
resetSinkStateForFlush();
|
||||
if (audioTrackPositionTracker.isPlaying()) {
|
||||
audioTrack.pause();
|
||||
}
|
||||
audioTrack.flush();
|
||||
|
||||
audioTrack.flush();
|
||||
audioTrackPositionTracker.reset();
|
||||
audioTrackPositionTracker.setAudioTrack(
|
||||
audioTrack,
|
||||
@ -1467,13 +1425,12 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
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 final class DefaultAudioSink implements AudioSink {
|
||||
|
||||
// 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;
|
||||
|
@ -159,11 +159,6 @@ public class ForwardingAudioSink implements AudioSink {
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void experimentalFlushWithoutAudioTrackRelease() {
|
||||
sink.experimentalFlushWithoutAudioTrackRelease();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
sink.reset();
|
||||
|
@ -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 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
|
||||
protected @Capabilities int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
|
||||
throws DecoderQueryException {
|
||||
@ -527,11 +512,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||
@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;
|
||||
|
@ -265,15 +265,15 @@ public final class DefaultAudioSinkTest {
|
||||
}
|
||||
|
||||
@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 final class DefaultAudioSinkTest {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user