NekoX/TMessagesProj/src/main/java/com/google/android/exoplayer2/audio/AudioTimestampPoller.java

310 lines
12 KiB
Java

/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.audio;
import android.annotation.TargetApi;
import android.media.AudioTimestamp;
import android.media.AudioTrack;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Polls the {@link AudioTrack} timestamp, if the platform supports it, taking care of polling at
* the appropriate rate to detect when the timestamp starts to advance.
*
* <p>When the audio track isn't paused, call {@link #maybePollTimestamp(long)} regularly to check
* for timestamp updates. If it returns {@code true}, call {@link #getTimestampPositionFrames()} and
* {@link #getTimestampSystemTimeUs()} to access the updated timestamp, then call {@link
* #acceptTimestamp()} or {@link #rejectTimestamp()} to accept or reject it.
*
* <p>If {@link #hasTimestamp()} returns {@code true}, call {@link #getTimestampSystemTimeUs()} to
* get the system time at which the latest timestamp was sampled and {@link
* #getTimestampPositionFrames()} to get its position in frames. If {@link #hasAdvancingTimestamp()}
* returns {@code true}, the caller should assume that the timestamp has been increasing in real
* time since it was sampled. Otherwise, it may be stationary.
*
* <p>Call {@link #reset()} when pausing or resuming the track.
*/
/* package */ final class AudioTimestampPoller {
/** Timestamp polling states. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STATE_INITIALIZING,
STATE_TIMESTAMP,
STATE_TIMESTAMP_ADVANCING,
STATE_NO_TIMESTAMP,
STATE_ERROR
})
private @interface State {}
/** State when first initializing. */
private static final int STATE_INITIALIZING = 0;
/** State when we have a timestamp and we don't know if it's advancing. */
private static final int STATE_TIMESTAMP = 1;
/** State when we have a timestamp and we know it is advancing. */
private static final int STATE_TIMESTAMP_ADVANCING = 2;
/** State when the no timestamp is available. */
private static final int STATE_NO_TIMESTAMP = 3;
/** State when the last timestamp was rejected as invalid. */
private static final int STATE_ERROR = 4;
/** The polling interval for {@link #STATE_INITIALIZING} and {@link #STATE_TIMESTAMP}. */
private static final int FAST_POLL_INTERVAL_US = 10_000;
/**
* The polling interval for {@link #STATE_TIMESTAMP_ADVANCING} and {@link #STATE_NO_TIMESTAMP}.
*/
private static final int SLOW_POLL_INTERVAL_US = 10_000_000;
/** The polling interval for {@link #STATE_ERROR}. */
private static final int ERROR_POLL_INTERVAL_US = 500_000;
/**
* The minimum duration to remain in {@link #STATE_INITIALIZING} if no timestamps are being
* returned before transitioning to {@link #STATE_NO_TIMESTAMP}.
*/
private static final int INITIALIZING_DURATION_US = 500_000;
@Nullable private final AudioTimestampV19 audioTimestamp;
private @State int state;
private long initializeSystemTimeUs;
private long sampleIntervalUs;
private long lastTimestampSampleTimeUs;
private long initialTimestampPositionFrames;
/**
* Creates a new audio timestamp poller.
*
* @param audioTrack The audio track that will provide timestamps, if the platform supports it.
*/
public AudioTimestampPoller(AudioTrack audioTrack) {
if (Util.SDK_INT >= 19) {
audioTimestamp = new AudioTimestampV19(audioTrack);
reset();
} else {
audioTimestamp = null;
updateState(STATE_NO_TIMESTAMP);
}
}
/**
* Polls the timestamp if required and returns whether it was updated. If {@code true}, the latest
* timestamp is available via {@link #getTimestampSystemTimeUs()} and {@link
* #getTimestampPositionFrames()}, and the caller should call {@link #acceptTimestamp()} if the
* timestamp was valid, or {@link #rejectTimestamp()} otherwise. The values returned by {@link
* #hasTimestamp()} and {@link #hasAdvancingTimestamp()} may be updated.
*
* @param systemTimeUs The current system time, in microseconds.
* @return Whether the timestamp was updated.
*/
public boolean maybePollTimestamp(long systemTimeUs) {
if (audioTimestamp == null || (systemTimeUs - lastTimestampSampleTimeUs) < sampleIntervalUs) {
return false;
}
lastTimestampSampleTimeUs = systemTimeUs;
boolean updatedTimestamp = audioTimestamp.maybeUpdateTimestamp();
switch (state) {
case STATE_INITIALIZING:
if (updatedTimestamp) {
if (audioTimestamp.getTimestampSystemTimeUs() >= initializeSystemTimeUs) {
// We have an initial timestamp, but don't know if it's advancing yet.
initialTimestampPositionFrames = audioTimestamp.getTimestampPositionFrames();
updateState(STATE_TIMESTAMP);
} else {
// Drop the timestamp, as it was sampled before the last reset.
updatedTimestamp = false;
}
} else if (systemTimeUs - initializeSystemTimeUs > INITIALIZING_DURATION_US) {
// We haven't received a timestamp for a while, so they probably aren't available for the
// current audio route. Poll infrequently in case the route changes later.
// TODO: Ideally we should listen for audio route changes in order to detect when a
// timestamp becomes available again.
updateState(STATE_NO_TIMESTAMP);
}
break;
case STATE_TIMESTAMP:
if (updatedTimestamp) {
long timestampPositionFrames = audioTimestamp.getTimestampPositionFrames();
if (timestampPositionFrames > initialTimestampPositionFrames) {
updateState(STATE_TIMESTAMP_ADVANCING);
}
} else {
reset();
}
break;
case STATE_TIMESTAMP_ADVANCING:
if (!updatedTimestamp) {
// The audio route may have changed, so reset polling.
reset();
}
break;
case STATE_NO_TIMESTAMP:
if (updatedTimestamp) {
// The audio route may have changed, so reset polling.
reset();
}
break;
case STATE_ERROR:
// Do nothing. If the caller accepts any new timestamp we'll reset polling.
break;
default:
throw new IllegalStateException();
}
return updatedTimestamp;
}
/**
* Rejects the timestamp last polled in {@link #maybePollTimestamp(long)}. The instance will enter
* the error state and poll timestamps infrequently until the next call to {@link
* #acceptTimestamp()}.
*/
public void rejectTimestamp() {
updateState(STATE_ERROR);
}
/**
* Accepts the timestamp last polled in {@link #maybePollTimestamp(long)}. If the instance is in
* the error state, it will begin to poll timestamps frequently again.
*/
public void acceptTimestamp() {
if (state == STATE_ERROR) {
reset();
}
}
/**
* Returns whether this instance has a timestamp that can be used to calculate the audio track
* position. If {@code true}, call {@link #getTimestampSystemTimeUs()} and {@link
* #getTimestampSystemTimeUs()} to access the timestamp.
*/
public boolean hasTimestamp() {
return state == STATE_TIMESTAMP || state == STATE_TIMESTAMP_ADVANCING;
}
/**
* Returns whether this instance has an advancing timestamp. If {@code true}, call {@link
* #getTimestampSystemTimeUs()} and {@link #getTimestampSystemTimeUs()} to access the timestamp. A
* current position for the track can be extrapolated based on elapsed real time since the system
* time at which the timestamp was sampled.
*/
public boolean hasAdvancingTimestamp() {
return state == STATE_TIMESTAMP_ADVANCING;
}
/** Resets polling. Should be called whenever the audio track is paused or resumed. */
public void reset() {
if (audioTimestamp != null) {
updateState(STATE_INITIALIZING);
}
}
/**
* If {@link #maybePollTimestamp(long)} or {@link #hasTimestamp()} returned {@code true}, returns
* the system time at which the latest timestamp was sampled, in microseconds.
*/
public long getTimestampSystemTimeUs() {
return audioTimestamp != null ? audioTimestamp.getTimestampSystemTimeUs() : C.TIME_UNSET;
}
/**
* If {@link #maybePollTimestamp(long)} or {@link #hasTimestamp()} returned {@code true}, returns
* the latest timestamp's position in frames.
*/
public long getTimestampPositionFrames() {
return audioTimestamp != null ? audioTimestamp.getTimestampPositionFrames() : C.POSITION_UNSET;
}
private void updateState(@State int state) {
this.state = state;
switch (state) {
case STATE_INITIALIZING:
// Force polling a timestamp immediately, and poll quickly.
lastTimestampSampleTimeUs = 0;
initialTimestampPositionFrames = C.POSITION_UNSET;
initializeSystemTimeUs = System.nanoTime() / 1000;
sampleIntervalUs = FAST_POLL_INTERVAL_US;
break;
case STATE_TIMESTAMP:
sampleIntervalUs = FAST_POLL_INTERVAL_US;
break;
case STATE_TIMESTAMP_ADVANCING:
case STATE_NO_TIMESTAMP:
sampleIntervalUs = SLOW_POLL_INTERVAL_US;
break;
case STATE_ERROR:
sampleIntervalUs = ERROR_POLL_INTERVAL_US;
break;
default:
throw new IllegalStateException();
}
}
@TargetApi(19)
private static final class AudioTimestampV19 {
private final AudioTrack audioTrack;
private final AudioTimestamp audioTimestamp;
private long rawTimestampFramePositionWrapCount;
private long lastTimestampRawPositionFrames;
private long lastTimestampPositionFrames;
/**
* Creates a new {@link AudioTimestamp} wrapper.
*
* @param audioTrack The audio track that will provide timestamps.
*/
public AudioTimestampV19(AudioTrack audioTrack) {
this.audioTrack = audioTrack;
audioTimestamp = new AudioTimestamp();
}
/**
* Attempts to update the audio track timestamp. Returns {@code true} if the timestamp was
* updated, in which case the updated timestamp system time and position can be accessed with
* {@link #getTimestampSystemTimeUs()} and {@link #getTimestampPositionFrames()}. Returns {@code
* false} if no timestamp is available, in which case those methods should not be called.
*/
public boolean maybeUpdateTimestamp() {
boolean updated = audioTrack.getTimestamp(audioTimestamp);
if (updated) {
long rawPositionFrames = audioTimestamp.framePosition;
if (lastTimestampRawPositionFrames > rawPositionFrames) {
// The value must have wrapped around.
rawTimestampFramePositionWrapCount++;
}
lastTimestampRawPositionFrames = rawPositionFrames;
lastTimestampPositionFrames =
rawPositionFrames + (rawTimestampFramePositionWrapCount << 32);
}
return updated;
}
public long getTimestampSystemTimeUs() {
return audioTimestamp.nanoTime / 1000;
}
public long getTimestampPositionFrames() {
return lastTimestampPositionFrames;
}
}
}