Skip to content

Commit

Permalink
Add new consecutiveDroppedFrames callback
Browse files Browse the repository at this point in the history
This is similar to the `droppedFrames` callback, but represents the number of consecutive frames that we dropped before rendering a frame or seeking or stopping the renderer.

While we already have a `maxConsecutiveDroppedFrame` available in the `DecoderCounters`, this doesn't provide enough visibility into the actual statistics of dropped frames.

If we get 200 dropped frames and a `maxConsecutive` of 20, we don't know if we dropped 20 frames in a row once and then dropped a single frame 180 times or if we dropped 20 frames 10 times.

We could add some code on our `OnDroppedFrames` callback to estimate if two calls are for consecutive frames, but that seems very fragile.

There is an open issue currently on how often to notify, and my preference would be to add a new `minConsecutiveDroppedFramesToNotify` similar to the `maxDroppedFramesToNotify` but that would only notify if more than X consecutive frames were dropped.
  • Loading branch information
khouzam committed Nov 25, 2024
1 parent bb20eb4 commit b011836
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3033,6 +3033,11 @@ public void onDroppedFrames(int count, long elapsed) {
analyticsCollector.onDroppedFrames(count, elapsed);
}

@Override
public void onConsecutiveDroppedFrames(int count, long elapsed) {
analyticsCollector.onConsecutiveDroppedFrames(count, elapsed);
}

@Override
public void onVideoSizeChanged(VideoSize newVideoSize) {
videoSize = newVideoSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,19 @@ void onVideoInputFormatChanged(
*/
void onDroppedFrames(int count, long elapsedMs);

/**
* Called to report the number of consecutive frames dropped by the video renderer. Consecutive
* dropped frames are reported when a frame is renderered after a the previous consecutive frames
* were not rendered optionally, whenever the consecutive dropped frame count is above a specified
* threshold whilst the renderer is started.
*
* @param count The number of consecutive dropped frames.
* @param elapsedMs The duration in milliseconds over which the consecutive frames were dropped.
* This duration is timed from the first dropped frame occured, until the time the renderer
* rendered a frame.
*/
void onConsecutiveDroppedFrames(int count, long elapsedMs);

/**
* Called when a video decoder is released.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,8 @@ public int size() {
EVENT_VIDEO_CODEC_ERROR,
EVENT_AUDIO_TRACK_INITIALIZED,
EVENT_AUDIO_TRACK_RELEASED,
EVENT_RENDERER_READY_CHANGED
EVENT_RENDERER_READY_CHANGED,
EVENT_CONSECUTIVE_DROPPED_VIDEO_FRAMES,
})
@interface EventFlags {}

Expand Down Expand Up @@ -448,6 +449,9 @@ public int size() {
/** A renderer changed its readiness for playback. */
@UnstableApi int EVENT_RENDERER_READY_CHANGED = 1033;

/** Consecutive video frames have been dropped. */
@UnstableApi int EVENT_CONSECUTIVE_DROPPED_VIDEO_FRAMES = 1034;

/** Time information of an event. */
@UnstableApi
final class EventTime {
Expand Down Expand Up @@ -1244,6 +1248,18 @@ default void onVideoInputFormatChanged(
@UnstableApi
default void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {}

/**
* Called after consecutive video frames have been dropped.
*
* @param eventTime The event time.
* @param consecutiveDroppedFrames The number of consecutive frames that have been dropped before the
* last rendered frame.
* @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration
* is timed from the last rendered frame.
*/
@UnstableApi
default void onConsecutiveDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {}

/**
* Called when a video renderer releases a decoder.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,15 @@ public final void onDroppedFrames(int count, long elapsedMs) {
listener -> listener.onDroppedVideoFrames(eventTime, count, elapsedMs));
}

@Override
public final void onConsecutiveDroppedFrames(int count, long elapsedMs) {
EventTime eventTime = generatePlayingMediaPeriodEventTime();
sendEvent(
eventTime,
AnalyticsListener.EVENT_CONSECUTIVE_DROPPED_VIDEO_FRAMES,
listener -> listener.onConsecutiveDroppedVideoFrames(eventTime, count, elapsedMs));
}

@Override
public final void onVideoDecoderReleased(String decoderName) {
EventTime eventTime = generateReadingMediaPeriodEventTime();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,13 @@ public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long el
logd(eventTime, "droppedFrames", Integer.toString(droppedFrames));
}

@UnstableApi
@Override
public void onConsecutiveDroppedVideoFrames(
EventTime eventTime, int consecutiveDroppedFrames, long elapsedMs) {
logd(eventTime, "consecutiveDroppedFrames", Integer.toString(consecutiveDroppedFrames));
}

@UnstableApi
@Override
public void onVideoDecoderReleased(EventTime eventTime, String decoderName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
private @C.VideoChangeFrameRateStrategy int changeFrameRateStrategy;
private long droppedFrameAccumulationStartTimeMs;
private int droppedFrames;
private long consecutiveDroppedFrameAccumulationStartTimeMs;
private int consecutiveDroppedFrameCount;
private int buffersInCodecCount;
private long totalVideoFrameProcessingOffsetUs;
Expand Down Expand Up @@ -831,7 +832,7 @@ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlayb
}
}
maybeSetupTunnelingForFirstFrame();
consecutiveDroppedFrameCount = 0;
maybeNotifyConsecutiveDroppedFrames();
}

@Override
Expand Down Expand Up @@ -870,6 +871,7 @@ protected void onStarted() {
@Override
protected void onStopped() {
maybeNotifyDroppedFrames();
maybeNotifyConsecutiveDroppedFrames();
maybeNotifyVideoFrameProcessingOffset();
if (videoSink != null) {
videoSink.onRendererStopped();
Expand Down Expand Up @@ -1789,7 +1791,7 @@ protected void renderOutputBuffer(MediaCodecAdapter codec, int index, long prese
codec.releaseOutputBuffer(index, true);
TraceUtil.endSection();
decoderCounters.renderedOutputBufferCount++;
consecutiveDroppedFrameCount = 0;
maybeNotifyConsecutiveDroppedFrames();
if (videoSink == null) {
maybeNotifyVideoSizeChanged(decodedVideoSize);
maybeNotifyRenderedFirstFrame();
Expand All @@ -1810,7 +1812,7 @@ protected void renderOutputBufferV21(
codec.releaseOutputBuffer(index, releaseTimeNs);
TraceUtil.endSection();
decoderCounters.renderedOutputBufferCount++;
consecutiveDroppedFrameCount = 0;
maybeNotifyConsecutiveDroppedFrames();
if (videoSink == null) {
maybeNotifyVideoSizeChanged(decodedVideoSize);
maybeNotifyRenderedFirstFrame();
Expand Down Expand Up @@ -1945,6 +1947,17 @@ private void maybeNotifyDroppedFrames() {
}
}

private void maybeNotifyConsecutiveDroppedFrames() {
// TODO: Figure out the notification threshold for consecutive dropped frames.
if (consecutiveDroppedFrameCount > 0) {
long elapsed = getClock().elapsedRealtime() - consecutiveDroppedFrameAccumulationStartTimeMs ;
eventDispatcher.consecutiveDroppedFrames(consecutiveDroppedFrameCount, elapsed);
}
// Always reset the counter to 0, even if the threshold is not reached.
consecutiveDroppedFrameCount = 0;
consecutiveDroppedFrameAccumulationStartTimeMs = getClock().elapsedRealtime();
}

private void maybeNotifyVideoFrameProcessingOffset() {
if (videoFrameProcessingOffsetCount != 0) {
eventDispatcher.reportVideoFrameProcessingOffset(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@ default void onVideoInputFormatChanged(
*/
default void onDroppedFrames(int count, long elapsedMs) {}

/**
* Called to report the number of consecutive frames dropped by the video renderer. Consecutive
* dropped frames are reported when a frame is renderered after a the previous consecutive frames
* were not rendered optionally, whenever the consecutive dropped frame count is above a specified
* threshold whilst the renderer is started.
*
* @param count The number of consecutive dropped frames.
* @param elapsedMs The duration in milliseconds over which the consecutive frames were dropped.
* This duration is timed from the first dropped frame occured, until the time the renderer
* rendered a frame.
*/
default void onConsecutiveDroppedFrames(int count, long elapsedMs) {}

/**
* Called to report the video processing offset of video frames processed by the video renderer.
*
Expand Down Expand Up @@ -205,6 +218,16 @@ public void droppedFrames(int droppedFrameCount, long elapsedMs) {
}
}

/** Invokes {@link VideoRendererEventListener#onConsecutiveDroppedFrames(int, long)}. */
public void consecutiveDroppedFrames(int consecutiveDroppedFrameCount, long elapsedMs) {
if (handler != null) {
handler.post(
() ->
castNonNull(listener)
.onConsecutiveDroppedFrames(consecutiveDroppedFrameCount, elapsedMs));
}
}

/** Invokes {@link VideoRendererEventListener#onVideoFrameProcessingOffset}. */
public void reportVideoFrameProcessingOffset(long totalProcessingOffsetUs, int frameCount) {
if (handler != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2393,6 +2393,12 @@ public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long el
reportedEvents.add(new ReportedEvent(EVENT_DROPPED_VIDEO_FRAMES, eventTime));
}

@Override
public void onDroppedVideoFrames(
EventTime eventTime, int consecutiveDroppedFrames, long elapsedMs) {
reportedEvents.add(new ReportedEvent(EVENT_CONSECUTIVE_DROPPED_VIDEO_FRAMES, eventTime));
}

@Override
public void onVideoDisabled(EventTime eventTime, DecoderCounters decoderCounters) {
reportedEvents.add(new ReportedEvent(EVENT_VIDEO_DISABLED, eventTime));
Expand Down

0 comments on commit b011836

Please sign in to comment.