Improve detection of advancing timestamp in AudioTimestampPoller

We currently accept any difference in reported frame position
as an "advancing timestamp". However, we don't check if the
resulting position is actually advancing or was only slightly
corrected, but is not yet advancing reliably.

This results in AV sync issues if the timestamp is used too early
while it's not actually advancing yet, and the potential correction
is only read 10 seconds later when we query the timestamp again.

The fix is to check whether the timestamp is actually advancing
as expected (the resulting position from the previous and the
current snapshot don't differ by more than 1ms). To avoid
permanent polling in 10ms intervals for devices that never report
a reliable timestamp, we introduce another timeout after which we
give up.

PiperOrigin-RevId: 755373666
This commit is contained in:
tonihei 2025-05-06 08:02:21 -07:00 committed by Copybara-Service
parent 0dc5ed19e2
commit fbee549f74
2 changed files with 58 additions and 4 deletions

View File

@ -40,6 +40,8 @@
estimation.
* Improve audio timestamp smoothing for unexpected position drift from the
audio output device.
* Fix bug where A/V sync is broken for the first 10 seconds after resuming
from pause when connected to Bluetooth devices.
* Video:
* Add experimental `ExoPlayer` API to include the
`MediaCodec.BUFFER_FLAG_DECODE_ONLY` flag when queuing decode-only input

View File

@ -87,6 +87,18 @@ import java.lang.annotation.Target;
*/
private static final int INITIALIZING_DURATION_US = 500_000;
/**
* The maximum difference in calculated current position between two reported timestamps to
* consider the timestamps advancing correctly.
*/
private static final long MAX_POSITION_DRIFT_ADVANCING_TIMESTAMP_US = 1000;
/**
* The minimum duration to remain in {@link #STATE_INITIALIZING} and {@link #STATE_TIMESTAMP} if
* no correctly advancing timestamp is returned before transitioning to {@link #STATE_ERROR}.
*/
private static final int WAIT_FOR_ADVANCE_DURATION_US = 2_000_000;
/**
* AudioTrack timestamps are deemed spurious if they are offset from the system clock by more than
* this amount.
@ -104,6 +116,7 @@ import java.lang.annotation.Target;
private long sampleIntervalUs;
private long lastTimestampSampleTimeUs;
private long initialTimestampPositionFrames;
private long initialTimestampSystemTimeUs;
/**
* Creates a new audio timestamp poller.
@ -146,6 +159,7 @@ import java.lang.annotation.Target;
if (audioTimestamp.getTimestampSystemTimeUs() >= initializeSystemTimeUs) {
// We have an initial timestamp, but don't know if it's advancing yet.
initialTimestampPositionFrames = audioTimestamp.getTimestampPositionFrames();
initialTimestampSystemTimeUs = audioTimestamp.getTimestampSystemTimeUs();
updateState(STATE_TIMESTAMP);
}
} else if (systemTimeUs - initializeSystemTimeUs > INITIALIZING_DURATION_US) {
@ -158,9 +172,16 @@ import java.lang.annotation.Target;
break;
case STATE_TIMESTAMP:
if (updatedTimestamp) {
long timestampPositionFrames = audioTimestamp.getTimestampPositionFrames();
if (timestampPositionFrames > initialTimestampPositionFrames) {
if (isTimestampAdvancingFromInitialTimestamp(systemTimeUs, audioTrackPlaybackSpeed)) {
updateState(STATE_TIMESTAMP_ADVANCING);
} else if (systemTimeUs - initializeSystemTimeUs > WAIT_FOR_ADVANCE_DURATION_US) {
// Failed to find a correctly advancing timestamp. Only try again later after waiting
// for SLOW_POLL_INTERVAL_US.
updateState(STATE_NO_TIMESTAMP);
} else {
// Not yet advancing, try again with the latest timestamp as the initial one.
initialTimestampPositionFrames = audioTimestamp.getTimestampPositionFrames();
initialTimestampSystemTimeUs = audioTimestamp.getTimestampSystemTimeUs();
}
} else {
reset();
@ -225,6 +246,7 @@ import java.lang.annotation.Target;
// Force polling a timestamp immediately, and poll quickly.
lastTimestampSampleTimeUs = 0;
initialTimestampPositionFrames = C.INDEX_UNSET;
initialTimestampSystemTimeUs = C.TIME_UNSET;
initializeSystemTimeUs = System.nanoTime() / 1000;
sampleIntervalUs = FAST_POLL_INTERVAL_US;
break;
@ -243,10 +265,40 @@ import java.lang.annotation.Target;
}
}
private boolean isTimestampAdvancingFromInitialTimestamp(
long systemTimeUs, float audioTrackPlaybackSpeed) {
if (audioTimestamp.getTimestampPositionFrames() <= initialTimestampPositionFrames) {
// Reported timestamp hasn't been updated.
return false;
}
long positionEstimateUsingInitialTimestampUs =
computeTimestampPositionUs(
initialTimestampPositionFrames,
initialTimestampSystemTimeUs,
systemTimeUs,
audioTrackPlaybackSpeed);
long positionEstimateUsingCurrentTimestampUs =
computeTimestampPositionUs(systemTimeUs, audioTrackPlaybackSpeed);
long positionDriftUs =
Math.abs(positionEstimateUsingCurrentTimestampUs - positionEstimateUsingInitialTimestampUs);
return positionDriftUs < MAX_POSITION_DRIFT_ADVANCING_TIMESTAMP_US;
}
private long computeTimestampPositionUs(long systemTimeUs, float audioTrackPlaybackSpeed) {
long timestampPositionFrames = audioTimestamp.getTimestampPositionFrames();
return computeTimestampPositionUs(
audioTimestamp.getTimestampPositionFrames(),
audioTimestamp.getTimestampSystemTimeUs(),
systemTimeUs,
audioTrackPlaybackSpeed);
}
private long computeTimestampPositionUs(
long timestampPositionFrames,
long timestampSystemTimeUs,
long systemTimeUs,
float audioTrackPlaybackSpeed) {
long timestampPositionUs = sampleCountToDurationUs(timestampPositionFrames, sampleRate);
long elapsedSinceTimestampUs = systemTimeUs - audioTimestamp.getTimestampSystemTimeUs();
long elapsedSinceTimestampUs = systemTimeUs - timestampSystemTimeUs;
elapsedSinceTimestampUs =
Util.getMediaDurationForPlayoutDuration(elapsedSinceTimestampUs, audioTrackPlaybackSpeed);
return timestampPositionUs + elapsedSinceTimestampUs;