Add an event for the audio position advancing

Currently the audio renderer can become ready before the AudioTrack
actually has enough data to play something, which means that the
player may transition to the ready state before audio starts
playing. This makes the player's current state transition not very
useful for detecting when audio actually starts playing.

This change adds a new event to notify apps when the audio position
is increasing after a pause or seek/flush/reset event, and includes
an estimate of the system time at which audio playout started.

Issue: #7577
PiperOrigin-RevId: 327810040
This commit is contained in:
andrewlewis 2020-08-21 16:01:07 +01:00 committed by kim-vde
parent 6960dde8a8
commit 4b0e39e4b9
13 changed files with 152 additions and 42 deletions

View File

@ -2,6 +2,9 @@
### dev-v2 (not yet released)
* Audio: Add an event for the audio position starting to advance, to make it
easier for apps to determine when audio playout started
([#7577](https://github.com/google/ExoPlayer/issues/7577)).
### 2.12.0 (not yet released - targeted for 2020-08-TBD) ###

View File

@ -2231,6 +2231,13 @@ public class SimpleExoPlayer extends BasePlayer
}
}
@Override
public void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
audioDebugListener.onAudioPositionAdvancing(playoutStartSystemTimeMs);
}
}
@Override
public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {

View File

@ -205,6 +205,14 @@ public class AnalyticsCollector
}
}
@Override
public final void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
EventTime eventTime = generateReadingMediaPeriodEventTime();
for (AnalyticsListener listener : listeners) {
listener.onAudioPositionAdvancing(eventTime, playoutStartSystemTimeMs);
}
}
@Override
public final void onAudioUnderrun(
int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {

View File

@ -479,6 +479,16 @@ public interface AnalyticsListener {
*/
default void onAudioInputFormatChanged(EventTime eventTime, Format format) {}
/**
* Called when the audio position has increased for the first time since the last pause or
* position reset.
*
* @param eventTime The event time.
* @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
* which playout started.
*/
default void onAudioPositionAdvancing(EventTime eventTime, long playoutStartSystemTimeMs) {}
/**
* Called when an audio underrun occurs.
*

View File

@ -65,6 +65,15 @@ public interface AudioRendererEventListener {
*/
default void onAudioInputFormatChanged(Format format) {}
/**
* Called when the audio position has increased for the first time since the last pause or
* position reset.
*
* @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
* which playout started.
*/
default void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {}
/**
* Called when an audio underrun occurs.
*
@ -89,7 +98,7 @@ public interface AudioRendererEventListener {
*/
default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}
/** Dispatches events to a {@link AudioRendererEventListener}. */
/** Dispatches events to an {@link AudioRendererEventListener}. */
final class EventDispatcher {
@Nullable private final Handler handler;
@ -106,20 +115,16 @@ public interface AudioRendererEventListener {
this.listener = listener;
}
/**
* Invokes {@link AudioRendererEventListener#onAudioEnabled(DecoderCounters)}.
*/
public void enabled(final DecoderCounters decoderCounters) {
/** Invokes {@link AudioRendererEventListener#onAudioEnabled(DecoderCounters)}. */
public void enabled(DecoderCounters decoderCounters) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onAudioEnabled(decoderCounters));
}
}
/**
* Invokes {@link AudioRendererEventListener#onAudioDecoderInitialized(String, long, long)}.
*/
public void decoderInitialized(final String decoderName,
final long initializedTimestampMs, final long initializationDurationMs) {
/** Invokes {@link AudioRendererEventListener#onAudioDecoderInitialized(String, long, long)}. */
public void decoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
if (handler != null) {
handler.post(
() ->
@ -129,18 +134,23 @@ public interface AudioRendererEventListener {
}
}
/**
* Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}.
*/
public void inputFormatChanged(final Format format) {
/** Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. */
public void inputFormatChanged(Format format) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onAudioInputFormatChanged(format));
}
}
/** Invokes {@link AudioRendererEventListener#onAudioPositionAdvancing(long)}. */
public void positionAdvancing(long playoutStartSystemTimeMs) {
if (handler != null) {
handler.post(
() -> castNonNull(listener).onAudioPositionAdvancing(playoutStartSystemTimeMs));
}
}
/** Invokes {@link AudioRendererEventListener#onAudioUnderrun(int, long, long)}. */
public void underrun(
final int bufferSize, final long bufferSizeMs, final long elapsedSinceLastFeedMs) {
public void underrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
if (handler != null) {
handler.post(
() ->
@ -149,10 +159,8 @@ public interface AudioRendererEventListener {
}
}
/**
* Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}.
*/
public void disabled(final DecoderCounters counters) {
/** Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}. */
public void disabled(DecoderCounters counters) {
counters.ensureUpdated();
if (handler != null) {
handler.post(
@ -163,17 +171,15 @@ public interface AudioRendererEventListener {
}
}
/**
* Invokes {@link AudioRendererEventListener#onAudioSessionId(int)}.
*/
public void audioSessionId(final int audioSessionId) {
/** Invokes {@link AudioRendererEventListener#onAudioSessionId(int)}. */
public void audioSessionId(int audioSessionId) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onAudioSessionId(audioSessionId));
}
}
/** Invokes {@link AudioRendererEventListener#onSkipSilenceEnabledChanged(boolean)}. */
public void skipSilenceEnabledChanged(final boolean skipSilenceEnabled) {
public void skipSilenceEnabledChanged(boolean skipSilenceEnabled) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onSkipSilenceEnabledChanged(skipSilenceEnabled));
}

View File

@ -72,10 +72,19 @@ public interface AudioSink {
*/
void onPositionDiscontinuity();
/**
* Called when the audio sink's position has increased for the first time since it was last
* paused or flushed.
*
* @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
* which playout started. Only valid if the audio track has not underrun.
*/
default void onPositionAdvancing(long playoutStartSystemTimeMs) {}
/**
* Called when the audio sink runs out of data.
* <p>
* An audio sink implementation may never call this method (for example, if audio data is
*
* <p>An audio sink implementation may never call this method (for example, if audio data is
* consumed in batches rather than based on the sink's own clock).
*
* @param bufferSize The size of the sink's buffer, in bytes.

View File

@ -48,6 +48,15 @@ import java.lang.reflect.Method;
/** Listener for position tracker events. */
public interface Listener {
/**
* Called when the position tracker's position has increased for the first time since it was
* last paused or reset.
*
* @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
* which playout started.
*/
void onPositionAdvancing(long playoutStartSystemTimeMs);
/**
* Called when the frame position is too far from the expected frame position.
*
@ -145,6 +154,7 @@ import java.lang.reflect.Method;
private boolean needsPassthroughWorkarounds;
private long bufferSizeUs;
private float audioTrackPlaybackSpeed;
private boolean notifiedPositionIncreasing;
private long smoothedPlayheadOffsetUs;
private long lastPlayheadSampleTimeUs;
@ -287,9 +297,21 @@ import java.lang.reflect.Method;
positionUs /= 1000;
}
if (!notifiedPositionIncreasing && positionUs > lastPositionUs) {
notifiedPositionIncreasing = true;
long mediaDurationSinceLastPositionUs = C.usToMs(positionUs - lastPositionUs);
long playoutDurationSinceLastPositionUs =
Util.getPlayoutDurationForMediaDuration(
mediaDurationSinceLastPositionUs, audioTrackPlaybackSpeed);
long playoutStartSystemTimeMs =
System.currentTimeMillis() - C.usToMs(playoutDurationSinceLastPositionUs);
listener.onPositionAdvancing(playoutStartSystemTimeMs);
}
lastSystemTimeUs = systemTimeUs;
lastPositionUs = positionUs;
lastSampleUsedGetTimestampMode = useGetTimestampMode;
return positionUs;
}
@ -512,6 +534,7 @@ import java.lang.reflect.Method;
lastPlayheadSampleTimeUs = 0;
lastSystemTimeUs = 0;
previousModeSystemTimeUs = 0;
notifiedPositionIncreasing = false;
}
/**

View File

@ -708,6 +708,11 @@ public abstract class DecoderAudioRenderer<
DecoderAudioRenderer.this.onPositionDiscontinuity();
}
@Override
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
}
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.underrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);

View File

@ -1776,6 +1776,13 @@ public final class DefaultAudioSink implements AudioSink {
Log.w(TAG, "Ignoring impossibly large audio latency: " + latencyUs);
}
@Override
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
if (listener != null) {
listener.onPositionAdvancing(playoutStartSystemTimeMs);
}
}
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs) {
if (listener != null) {

View File

@ -828,6 +828,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
MediaCodecAudioRenderer.this.onPositionDiscontinuity();
}
@Override
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
}
@Override
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
eventDispatcher.underrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);

View File

@ -316,6 +316,12 @@ public class EventLogger implements AnalyticsListener {
logd(eventTime, "audioInputFormat", Format.toLogString(format));
}
@Override
public void onAudioPositionAdvancing(EventTime eventTime, long playoutStartSystemTimeMs) {
long timeSincePlayoutStartMs = System.currentTimeMillis() - playoutStartSystemTimeMs;
logd(eventTime, "audioPositionAdvancing", "timeSincePlayoutStartMs=" + timeSincePlayoutStartMs);
}
@Override
public void onAudioUnderrun(
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {

View File

@ -106,21 +106,22 @@ public final class AnalyticsCollectorTest {
private static final int EVENT_AUDIO_INPUT_FORMAT_CHANGED = 26;
private static final int EVENT_AUDIO_DISABLED = 27;
private static final int EVENT_AUDIO_SESSION_ID = 28;
private static final int EVENT_AUDIO_UNDERRUN = 29;
private static final int EVENT_VIDEO_ENABLED = 30;
private static final int EVENT_VIDEO_DECODER_INIT = 31;
private static final int EVENT_VIDEO_INPUT_FORMAT_CHANGED = 32;
private static final int EVENT_DROPPED_FRAMES = 33;
private static final int EVENT_VIDEO_DISABLED = 34;
private static final int EVENT_RENDERED_FIRST_FRAME = 35;
private static final int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 36;
private static final int EVENT_VIDEO_SIZE_CHANGED = 37;
private static final int EVENT_DRM_KEYS_LOADED = 38;
private static final int EVENT_DRM_ERROR = 39;
private static final int EVENT_DRM_KEYS_RESTORED = 40;
private static final int EVENT_DRM_KEYS_REMOVED = 41;
private static final int EVENT_DRM_SESSION_ACQUIRED = 42;
private static final int EVENT_DRM_SESSION_RELEASED = 43;
private static final int EVENT_AUDIO_POSITION_ADVANCING_ID = 29;
private static final int EVENT_AUDIO_UNDERRUN = 30;
private static final int EVENT_VIDEO_ENABLED = 31;
private static final int EVENT_VIDEO_DECODER_INIT = 32;
private static final int EVENT_VIDEO_INPUT_FORMAT_CHANGED = 33;
private static final int EVENT_DROPPED_FRAMES = 34;
private static final int EVENT_VIDEO_DISABLED = 35;
private static final int EVENT_RENDERED_FIRST_FRAME = 36;
private static final int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 37;
private static final int EVENT_VIDEO_SIZE_CHANGED = 38;
private static final int EVENT_DRM_KEYS_LOADED = 39;
private static final int EVENT_DRM_ERROR = 40;
private static final int EVENT_DRM_KEYS_RESTORED = 41;
private static final int EVENT_DRM_KEYS_REMOVED = 42;
private static final int EVENT_DRM_SESSION_ACQUIRED = 43;
private static final int EVENT_DRM_SESSION_RELEASED = 44;
private static final UUID DRM_SCHEME_UUID =
UUID.nameUUIDFromBytes(TestUtil.createByteArray(7, 8, 9));
@ -226,6 +227,7 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING_ID)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
@ -305,6 +307,7 @@ public final class AnalyticsCollectorTest {
.containsExactly(period0, period1)
.inOrder();
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING_ID)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
.containsExactly(period0, period1)
@ -380,6 +383,7 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period1);
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period1);
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1);
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING_ID)).containsExactly(period1);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
@ -476,6 +480,9 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
.containsExactly(period0, period1)
.inOrder();
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING_ID))
.containsExactly(period0, period1)
.inOrder();
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
@ -576,6 +583,9 @@ public final class AnalyticsCollectorTest {
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
.containsExactly(period1Seq1, period1Seq2)
.inOrder();
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING_ID))
.containsExactly(period1Seq1, period1Seq2)
.inOrder();
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0, period0);
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
@ -1922,6 +1932,11 @@ public final class AnalyticsCollectorTest {
reportedEvents.add(new ReportedEvent(EVENT_AUDIO_SESSION_ID, eventTime));
}
@Override
public void onAudioPositionAdvancing(EventTime eventTime, long playoutStartSystemTimeMs) {
reportedEvents.add(new ReportedEvent(EVENT_AUDIO_POSITION_ADVANCING_ID, eventTime));
}
@Override
public void onAudioUnderrun(
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {

View File

@ -30,6 +30,7 @@ public class FakeAudioRenderer extends FakeRenderer {
private final AudioRendererEventListener.EventDispatcher eventDispatcher;
private final DecoderCounters decoderCounters;
private boolean notifiedAudioSessionId;
private boolean notifiedPositionAdvancing;
public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) {
super(C.TRACK_TYPE_AUDIO);
@ -43,6 +44,7 @@ public class FakeAudioRenderer extends FakeRenderer {
super.onEnabled(joining, mayRenderStartOfStream);
eventDispatcher.enabled(decoderCounters);
notifiedAudioSessionId = false;
notifiedPositionAdvancing = false;
}
@Override
@ -67,6 +69,10 @@ public class FakeAudioRenderer extends FakeRenderer {
eventDispatcher.audioSessionId(/* audioSessionId= */ 1);
notifiedAudioSessionId = true;
}
if (shouldProcess && !notifiedPositionAdvancing) {
eventDispatcher.positionAdvancing(System.currentTimeMillis());
notifiedPositionAdvancing = true;
}
return shouldProcess;
}
}