Commit 739fd5f5 by tonihei Committed by Ian Baker

Include startMediaTime in media position checkpoints.

We currently apply new parameter checkpoints from an absolute media
time and then substract the current media start time again to retrieve
the media time offset for this playback parameter checkpoint.

However, the media start time may change when unexpected discontinuities
happen (the start time doesn't actually change, but we change it to
correct for this discontinuity). This then invalidates the absolute
media time in the playback parameter checkpoints (which should have been
corrected as well).

Avoid this problem by also only applying the new start position
from the checkpoint. We don't have to save the start position anymore
because it will cancel itself out.

Also add some documentation and code clarification for improved
readability.

PiperOrigin-RevId: 291923069
parent 00fe2eb4
...@@ -244,7 +244,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -244,7 +244,7 @@ public final class DefaultAudioSink implements AudioSink {
private final AudioProcessor[] toFloatPcmAvailableAudioProcessors; private final AudioProcessor[] toFloatPcmAvailableAudioProcessors;
private final ConditionVariable releasingConditionVariable; private final ConditionVariable releasingConditionVariable;
private final AudioTrackPositionTracker audioTrackPositionTracker; private final AudioTrackPositionTracker audioTrackPositionTracker;
private final ArrayDeque<PlaybackParametersCheckpoint> playbackParametersCheckpoints; private final ArrayDeque<MediaPositionParameters> mediaPositionParametersCheckpoints;
@Nullable private Listener listener; @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 #initialize(long)}). */
...@@ -256,9 +256,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -256,9 +256,7 @@ public final class DefaultAudioSink implements AudioSink {
private AudioAttributes audioAttributes; private AudioAttributes audioAttributes;
@Nullable private PlaybackParameters afterDrainPlaybackParameters; @Nullable private PlaybackParameters afterDrainPlaybackParameters;
private PlaybackParameters playbackParameters; private MediaPositionParameters mediaPositionParameters;
private long playbackParametersOffsetUs;
private long playbackParametersPositionUs;
@Nullable private ByteBuffer avSyncHeader; @Nullable private ByteBuffer avSyncHeader;
private int bytesUntilNextAvSync; private int bytesUntilNextAvSync;
...@@ -361,11 +359,13 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -361,11 +359,13 @@ public final class DefaultAudioSink implements AudioSink {
audioAttributes = AudioAttributes.DEFAULT; audioAttributes = AudioAttributes.DEFAULT;
audioSessionId = C.AUDIO_SESSION_ID_UNSET; audioSessionId = C.AUDIO_SESSION_ID_UNSET;
auxEffectInfo = new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, 0f); auxEffectInfo = new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, 0f);
playbackParameters = PlaybackParameters.DEFAULT; mediaPositionParameters =
new MediaPositionParameters(
PlaybackParameters.DEFAULT, /* mediaTimeUs= */ 0, /* audioTrackPositionUs= */ 0);
drainingAudioProcessorIndex = C.INDEX_UNSET; drainingAudioProcessorIndex = C.INDEX_UNSET;
activeAudioProcessors = new AudioProcessor[0]; activeAudioProcessors = new AudioProcessor[0];
outputBuffers = new ByteBuffer[0]; outputBuffers = new ByteBuffer[0];
playbackParametersCheckpoints = new ArrayDeque<>(); mediaPositionParametersCheckpoints = new ArrayDeque<>();
} }
// AudioSink implementation. // AudioSink implementation.
...@@ -398,7 +398,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -398,7 +398,7 @@ public final class DefaultAudioSink implements AudioSink {
} }
long positionUs = audioTrackPositionTracker.getCurrentPositionUs(sourceEnded); long positionUs = audioTrackPositionTracker.getCurrentPositionUs(sourceEnded);
positionUs = Math.min(positionUs, configuration.framesToDurationUs(getWrittenFrames())); positionUs = Math.min(positionUs, configuration.framesToDurationUs(getWrittenFrames()));
return startMediaTimeUs + applySkipping(applySpeedup(positionUs)); return applySkipping(applyMediaPositionParameters(positionUs));
} }
@Override @Override
...@@ -540,7 +540,10 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -540,7 +540,10 @@ public final class DefaultAudioSink implements AudioSink {
} }
} }
applyPlaybackParameters(playbackParameters, presentationTimeUs); startMediaTimeUs = Math.max(0, presentationTimeUs);
startMediaTimeState = START_IN_SYNC;
applyPlaybackParameters(getPlaybackParameters(), presentationTimeUs);
audioTrackPositionTracker.setAudioTrack( audioTrackPositionTracker.setAudioTrack(
audioTrack, audioTrack,
...@@ -595,7 +598,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -595,7 +598,7 @@ public final class DefaultAudioSink implements AudioSink {
pendingConfiguration = null; pendingConfiguration = null;
} }
// Re-apply playback parameters. // Re-apply playback parameters.
applyPlaybackParameters(playbackParameters, presentationTimeUs); applyPlaybackParameters(getPlaybackParameters(), presentationTimeUs);
} }
if (!isInitialized()) { if (!isInitialized()) {
...@@ -638,30 +641,36 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -638,30 +641,36 @@ public final class DefaultAudioSink implements AudioSink {
applyPlaybackParameters(newPlaybackParameters, presentationTimeUs); applyPlaybackParameters(newPlaybackParameters, presentationTimeUs);
} }
if (startMediaTimeState == START_NOT_SET) { // Sanity check that presentationTimeUs is consistent with the expected value.
startMediaTimeUs = Math.max(0, presentationTimeUs); long expectedPresentationTimeUs =
startMediaTimeState = START_IN_SYNC; startMediaTimeUs
} else { + configuration.inputFramesToDurationUs(
// Sanity check that presentationTimeUs is consistent with the expected value. getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount());
long expectedPresentationTimeUs = if (startMediaTimeState == START_IN_SYNC
startMediaTimeUs && Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) {
+ configuration.inputFramesToDurationUs( Log.e(
getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount()); TAG,
if (startMediaTimeState == START_IN_SYNC "Discontinuity detected [expected "
&& Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) { + expectedPresentationTimeUs
Log.e(TAG, "Discontinuity detected [expected " + expectedPresentationTimeUs + ", got " + ", got "
+ presentationTimeUs + "]"); + presentationTimeUs
startMediaTimeState = START_NEED_SYNC; + "]");
startMediaTimeState = START_NEED_SYNC;
}
if (startMediaTimeState == START_NEED_SYNC) {
if (!drainAudioProcessorsToEndOfStream()) {
// Don't update timing until pending AudioProcessor buffers are completely drained.
return false;
} }
if (startMediaTimeState == START_NEED_SYNC) { // Adjust startMediaTimeUs to be consistent with the current buffer's start time and the
// Adjust startMediaTimeUs to be consistent with the current buffer's start time and the // number of bytes submitted.
// number of bytes submitted. long adjustmentUs = presentationTimeUs - expectedPresentationTimeUs;
long adjustmentUs = presentationTimeUs - expectedPresentationTimeUs; startMediaTimeUs += adjustmentUs;
startMediaTimeUs += adjustmentUs; startMediaTimeState = START_IN_SYNC;
startMediaTimeState = START_IN_SYNC; // Re-apply playback parameters because the startMediaTimeUs changed.
if (listener != null && adjustmentUs != 0) { applyPlaybackParameters(getPlaybackParameters(), presentationTimeUs);
listener.onPositionDiscontinuity(); if (listener != null && adjustmentUs != 0) {
} listener.onPositionDiscontinuity();
} }
} }
...@@ -834,8 +843,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -834,8 +843,7 @@ public final class DefaultAudioSink implements AudioSink {
@Override @Override
public void setPlaybackParameters(PlaybackParameters playbackParameters) { public void setPlaybackParameters(PlaybackParameters playbackParameters) {
if (configuration != null && !configuration.canApplyPlaybackParameters) { if (configuration != null && !configuration.canApplyPlaybackParameters) {
this.playbackParameters = PlaybackParameters.DEFAULT; playbackParameters = PlaybackParameters.DEFAULT;
return;
} }
PlaybackParameters lastSetPlaybackParameters = getPlaybackParameters(); PlaybackParameters lastSetPlaybackParameters = getPlaybackParameters();
if (!playbackParameters.equals(lastSetPlaybackParameters)) { if (!playbackParameters.equals(lastSetPlaybackParameters)) {
...@@ -846,7 +854,9 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -846,7 +854,9 @@ public final class DefaultAudioSink implements AudioSink {
} else { } else {
// Update the playback parameters now. They will be applied to the audio processors during // Update the playback parameters now. They will be applied to the audio processors during
// initialization. // initialization.
this.playbackParameters = playbackParameters; mediaPositionParameters =
new MediaPositionParameters(
playbackParameters, /* mediaTimeUs= */ 0, /* audioTrackPositionUs= */ 0);
} }
} }
} }
...@@ -856,9 +866,9 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -856,9 +866,9 @@ public final class DefaultAudioSink implements AudioSink {
// Mask the already set parameters. // Mask the already set parameters.
return afterDrainPlaybackParameters != null return afterDrainPlaybackParameters != null
? afterDrainPlaybackParameters ? afterDrainPlaybackParameters
: !playbackParametersCheckpoints.isEmpty() : !mediaPositionParametersCheckpoints.isEmpty()
? playbackParametersCheckpoints.getLast().playbackParameters ? mediaPositionParametersCheckpoints.getLast().playbackParameters
: playbackParameters; : mediaPositionParameters.playbackParameters;
} }
@Override @Override
...@@ -954,15 +964,13 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -954,15 +964,13 @@ public final class DefaultAudioSink implements AudioSink {
writtenPcmBytes = 0; writtenPcmBytes = 0;
writtenEncodedFrames = 0; writtenEncodedFrames = 0;
framesPerEncodedSample = 0; framesPerEncodedSample = 0;
if (afterDrainPlaybackParameters != null) { mediaPositionParameters =
playbackParameters = afterDrainPlaybackParameters; new MediaPositionParameters(
afterDrainPlaybackParameters = null; getPlaybackParameters(), /* mediaTimeUs= */ 0, /* audioTrackPositionUs= */ 0);
} else if (!playbackParametersCheckpoints.isEmpty()) { startMediaTimeUs = 0;
playbackParameters = playbackParametersCheckpoints.getLast().playbackParameters; startMediaTimeState = START_NOT_SET;
} afterDrainPlaybackParameters = null;
playbackParametersCheckpoints.clear(); mediaPositionParametersCheckpoints.clear();
playbackParametersOffsetUs = 0;
playbackParametersPositionUs = 0;
trimmingAudioProcessor.resetTrimmedFrameCount(); trimmingAudioProcessor.resetTrimmedFrameCount();
flushAudioProcessors(); flushAudioProcessors();
inputBuffer = null; inputBuffer = null;
...@@ -972,7 +980,6 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -972,7 +980,6 @@ public final class DefaultAudioSink implements AudioSink {
drainingAudioProcessorIndex = C.INDEX_UNSET; drainingAudioProcessorIndex = C.INDEX_UNSET;
avSyncHeader = null; avSyncHeader = null;
bytesUntilNextAvSync = 0; bytesUntilNextAvSync = 0;
startMediaTimeState = START_NOT_SET;
if (audioTrackPositionTracker.isPlaying()) { if (audioTrackPositionTracker.isPlaying()) {
audioTrack.pause(); audioTrack.pause();
} }
...@@ -1038,41 +1045,42 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1038,41 +1045,42 @@ public final class DefaultAudioSink implements AudioSink {
configuration.canApplyPlaybackParameters configuration.canApplyPlaybackParameters
? audioProcessorChain.applyPlaybackParameters(playbackParameters) ? audioProcessorChain.applyPlaybackParameters(playbackParameters)
: PlaybackParameters.DEFAULT; : PlaybackParameters.DEFAULT;
// Store the position and corresponding media time from which the parameters will apply. mediaPositionParametersCheckpoints.add(
playbackParametersCheckpoints.add( new MediaPositionParameters(
new PlaybackParametersCheckpoint(
newPlaybackParameters, newPlaybackParameters,
/* mediaTimeUs= */ Math.max(0, presentationTimeUs), /* mediaTimeUs= */ Math.max(0, presentationTimeUs),
/* positionUs= */ configuration.framesToDurationUs(getWrittenFrames()))); /* audioTrackPositionUs= */ configuration.framesToDurationUs(getWrittenFrames())));
setupAudioProcessors(); setupAudioProcessors();
} }
private long applySpeedup(long positionUs) { /**
@Nullable PlaybackParametersCheckpoint checkpoint = null; * Applies and updates media position parameters.
while (!playbackParametersCheckpoints.isEmpty() *
&& positionUs >= playbackParametersCheckpoints.getFirst().positionUs) { * @param positionUs The current audio track position, in microseconds.
checkpoint = playbackParametersCheckpoints.remove(); * @return The current media time, in microseconds.
} */
if (checkpoint != null) { private long applyMediaPositionParameters(long positionUs) {
// We are playing (or about to play) media with the new playback parameters, so update them. while (!mediaPositionParametersCheckpoints.isEmpty()
playbackParameters = checkpoint.playbackParameters; && positionUs >= mediaPositionParametersCheckpoints.getFirst().audioTrackPositionUs) {
playbackParametersPositionUs = checkpoint.positionUs; // We are playing (or about to play) media with the new parameters, so update them.
playbackParametersOffsetUs = checkpoint.mediaTimeUs - startMediaTimeUs; mediaPositionParameters = mediaPositionParametersCheckpoints.remove();
} }
if (playbackParameters.speed == 1f) { long playoutDurationSinceLastCheckpoint =
return positionUs + playbackParametersOffsetUs - playbackParametersPositionUs; positionUs - mediaPositionParameters.audioTrackPositionUs;
} if (mediaPositionParameters.playbackParameters.speed != 1f) {
if (mediaPositionParametersCheckpoints.isEmpty()) {
if (playbackParametersCheckpoints.isEmpty()) { playoutDurationSinceLastCheckpoint =
return playbackParametersOffsetUs audioProcessorChain.getMediaDuration(playoutDurationSinceLastCheckpoint);
+ audioProcessorChain.getMediaDuration(positionUs - playbackParametersPositionUs); } else {
// Playing data at a previous playback speed, so fall back to multiplying by the speed.
playoutDurationSinceLastCheckpoint =
Util.getMediaDurationForPlayoutDuration(
playoutDurationSinceLastCheckpoint,
mediaPositionParameters.playbackParameters.speed);
}
} }
return mediaPositionParameters.mediaTimeUs + playoutDurationSinceLastCheckpoint;
// We are playing data at a previous playback speed, so fall back to multiplying by the speed.
return playbackParametersOffsetUs
+ Util.getMediaDurationForPlayoutDuration(
positionUs - playbackParametersPositionUs, playbackParameters.speed);
} }
private long applySkipping(long positionUs) { private long applySkipping(long positionUs) {
...@@ -1240,20 +1248,22 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1240,20 +1248,22 @@ public final class DefaultAudioSink implements AudioSink {
} }
} }
/** Stores playback parameters with the position and media time at which they apply. */ /** Stores parameters used to calculate the current media position. */
private static final class PlaybackParametersCheckpoint { private static final class MediaPositionParameters {
private final PlaybackParameters playbackParameters; /** The playback parameters. */
private final long mediaTimeUs; public final PlaybackParameters playbackParameters;
private final long positionUs; /** The media time from which the playback parameters apply, in microseconds. */
public final long mediaTimeUs;
/** The audio track position from which the playback parameters apply, in microseconds. */
public final long audioTrackPositionUs;
private PlaybackParametersCheckpoint(PlaybackParameters playbackParameters, long mediaTimeUs, private MediaPositionParameters(
long positionUs) { PlaybackParameters playbackParameters, long mediaTimeUs, long audioTrackPositionUs) {
this.playbackParameters = playbackParameters; this.playbackParameters = playbackParameters;
this.mediaTimeUs = mediaTimeUs; this.mediaTimeUs = mediaTimeUs;
this.positionUs = positionUs; this.audioTrackPositionUs = audioTrackPositionUs;
} }
} }
private final class PositionTrackerListener implements AudioTrackPositionTracker.Listener { private final class PositionTrackerListener implements AudioTrackPositionTracker.Listener {
......
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