mirror of
https://github.com/androidx/media.git
synced 2025-05-17 12:39:52 +08:00
Rollback of 8da6c35bbb
*** Original commit *** Keep AudioTrack on flush as default *** PiperOrigin-RevId: 444327724
This commit is contained in:
parent
8da6c35bbb
commit
d536fe35c6
@ -453,6 +453,18 @@ 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();
|
||||||
}
|
}
|
||||||
|
@ -125,6 +125,7 @@ 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;
|
||||||
@ -204,6 +205,19 @@ 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() {
|
||||||
@ -537,7 +551,12 @@ public abstract class DecoderAudioRenderer<
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
||||||
audioSink.flush();
|
if (experimentalKeepAudioTrackOnSeek) {
|
||||||
|
audioSink.experimentalFlushWithoutAudioTrackRelease();
|
||||||
|
} else {
|
||||||
|
audioSink.flush();
|
||||||
|
}
|
||||||
|
|
||||||
currentPositionUs = positionUs;
|
currentPositionUs = positionUs;
|
||||||
allowFirstBufferPositionDiscontinuity = true;
|
allowFirstBufferPositionDiscontinuity = true;
|
||||||
allowPositionDiscontinuity = 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.
|
// We're waiting for playout on the current audio track to finish.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
flushAndReleaseAudioTrack();
|
flush();
|
||||||
} 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;
|
||||||
@ -1025,7 +1025,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");
|
||||||
flushAndReleaseAudioTrack();
|
flush();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1315,8 +1315,7 @@ 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;
|
||||||
}
|
}
|
||||||
// audioAttributes change requires the audioTrack to be recreated.
|
flush();
|
||||||
flushAndReleaseAudioTrack();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1329,8 +1328,7 @@ 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;
|
||||||
// audioSessionId change requires the audioTrack to be recreated.
|
flush();
|
||||||
flushAndReleaseAudioTrack();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1358,7 +1356,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
Assertions.checkState(externalAudioSessionIdProvided);
|
Assertions.checkState(externalAudioSessionIdProvided);
|
||||||
if (!tunneling) {
|
if (!tunneling) {
|
||||||
tunneling = true;
|
tunneling = true;
|
||||||
flushAndReleaseAudioTrack();
|
flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1366,7 +1364,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
public void disableTunneling() {
|
public void disableTunneling() {
|
||||||
if (tunneling) {
|
if (tunneling) {
|
||||||
tunneling = false;
|
tunneling = false;
|
||||||
flushAndReleaseAudioTrack();
|
flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1398,26 +1396,70 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() {
|
public void flush() {
|
||||||
if (!isAudioTrackInitialized()) {
|
if (isAudioTrackInitialized()) {
|
||||||
return;
|
resetSinkStateForFlush();
|
||||||
}
|
|
||||||
|
|
||||||
// Prior to SDK 25, AudioTrack flush does not work as intended, so it must be released and
|
if (audioTrackPositionTracker.isPlaying()) {
|
||||||
// reinitialized. (Internal reference: b/143500232)
|
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) {
|
if (Util.SDK_INT < 25) {
|
||||||
flushAndReleaseAudioTrack();
|
flush();
|
||||||
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,
|
||||||
@ -1425,12 +1467,13 @@ 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() {
|
||||||
flushAndReleaseAudioTrack();
|
flush();
|
||||||
for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) {
|
for (AudioProcessor audioProcessor : toIntPcmAvailableAudioProcessors) {
|
||||||
audioProcessor.reset();
|
audioProcessor.reset();
|
||||||
}
|
}
|
||||||
@ -1443,51 +1486,6 @@ 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;
|
||||||
|
@ -159,6 +159,11 @@ public class ForwardingAudioSink implements AudioSink {
|
|||||||
sink.flush();
|
sink.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void experimentalFlushWithoutAudioTrackRelease() {
|
||||||
|
sink.experimentalFlushWithoutAudioTrackRelease();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
sink.reset();
|
sink.reset();
|
||||||
|
@ -105,6 +105,8 @@ 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -260,6 +262,19 @@ 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 {
|
||||||
@ -512,7 +527,11 @@ 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);
|
||||||
audioSink.flush();
|
if (experimentalKeepAudioTrackOnSeek) {
|
||||||
|
audioSink.experimentalFlushWithoutAudioTrackRelease();
|
||||||
|
} else {
|
||||||
|
audioSink.flush();
|
||||||
|
}
|
||||||
|
|
||||||
currentPositionUs = positionUs;
|
currentPositionUs = positionUs;
|
||||||
allowFirstBufferPositionDiscontinuity = true;
|
allowFirstBufferPositionDiscontinuity = true;
|
||||||
|
@ -265,15 +265,15 @@ public final class DefaultAudioSinkTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handleBuffer_afterFlush_doesntThrow() throws Exception {
|
public void handlesBufferAfterExperimentalFlush() throws Exception {
|
||||||
// This is demonstrating that no Exceptions are thrown as a result of handling a buffer after a
|
// This is demonstrating that no Exceptions are thrown as a result of handling a buffer after an
|
||||||
// flush.
|
// experimental 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 flush we can successfully queue more input.
|
// After the experimental flush we can successfully queue more input.
|
||||||
defaultAudioSink.flush();
|
defaultAudioSink.experimentalFlushWithoutAudioTrackRelease();
|
||||||
defaultAudioSink.handleBuffer(
|
defaultAudioSink.handleBuffer(
|
||||||
createDefaultSilenceBuffer(),
|
createDefaultSilenceBuffer(),
|
||||||
/* presentationTimeUs= */ 5_000,
|
/* presentationTimeUs= */ 5_000,
|
||||||
@ -281,13 +281,13 @@ public final class DefaultAudioSinkTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getCurrentPosition_afterFlush_returnsUnset() throws Exception {
|
public void getCurrentPosition_returnsUnset_afterExperimentalFlush() 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.flush();
|
defaultAudioSink.experimentalFlushWithoutAudioTrackRelease();
|
||||||
assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false))
|
assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false))
|
||||||
.isEqualTo(CURRENT_POSITION_NOT_SET);
|
.isEqualTo(CURRENT_POSITION_NOT_SET);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user