Skip to content

Commit a20a1d8

Browse files
committed
Add new consecutiveDroppedFrames callback
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. Specifying when to invoke the callback is controlled by `minConsecutiveDroppedFramesToNotify` similar to the `maxDroppedFramesToNotify` but that would only notify if more than X consecutive frames were dropped. Adding support for both `MediaCodecVideoRenderer` and `DecoderVideoRenderer`.
1 parent 6193f7c commit a20a1d8

File tree

19 files changed

+341
-38
lines changed

19 files changed

+341
-38
lines changed

libraries/decoder_av1/src/main/java/androidx/media3/decoder/av1/Libgav1VideoRenderer.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,22 @@ public class Libgav1VideoRenderer extends DecoderVideoRenderer {
7878
* @param eventListener A listener of events. May be null if delivery of events is not required.
7979
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
8080
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
81+
* @param minConsecutiveDroppedFramesToNotify The minimum number of consecutive frames that must
82+
* be dropped for {@link VideoRendererEventListener#onConsecutiveDroppedFrames(int, long)} to
83+
* be called.
8184
*/
8285
public Libgav1VideoRenderer(
8386
long allowedJoiningTimeMs,
8487
@Nullable Handler eventHandler,
8588
@Nullable VideoRendererEventListener eventListener,
86-
int maxDroppedFramesToNotify) {
89+
int maxDroppedFramesToNotify,
90+
int minConsecutiveDroppedFramesToNotify) {
8791
this(
8892
allowedJoiningTimeMs,
8993
eventHandler,
9094
eventListener,
9195
maxDroppedFramesToNotify,
96+
minConsecutiveDroppedFramesToNotify,
9297
THREAD_COUNT_AUTODETECT,
9398
DEFAULT_NUM_OF_INPUT_BUFFERS,
9499
DEFAULT_NUM_OF_OUTPUT_BUFFERS);
@@ -104,6 +109,9 @@ public Libgav1VideoRenderer(
104109
* @param eventListener A listener of events. May be null if delivery of events is not required.
105110
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
106111
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
112+
* @param minConsecutiveDroppedFramesToNotify The minimum number of consecutive frames that must
113+
* be dropped for {@link VideoRendererEventListener#onConsecutiveDroppedFrames(int, long)} to
114+
* be called.
107115
* @param threads Number of threads libgav1 will use to decode. If {@link
108116
* #THREAD_COUNT_AUTODETECT} is passed, then the number of threads to use is autodetected
109117
* based on CPU capabilities.
@@ -115,10 +123,16 @@ public Libgav1VideoRenderer(
115123
@Nullable Handler eventHandler,
116124
@Nullable VideoRendererEventListener eventListener,
117125
int maxDroppedFramesToNotify,
126+
int minConsecutiveDroppedFramesToNotify,
118127
int threads,
119128
int numInputBuffers,
120129
int numOutputBuffers) {
121-
super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
130+
super(
131+
allowedJoiningTimeMs,
132+
eventHandler,
133+
eventListener,
134+
maxDroppedFramesToNotify,
135+
minConsecutiveDroppedFramesToNotify);
122136
this.threads = threads;
123137
this.numInputBuffers = numInputBuffers;
124138
this.numOutputBuffers = numOutputBuffers;

libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/ExperimentalFfmpegVideoRenderer.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,22 @@ public final class ExperimentalFfmpegVideoRenderer extends DecoderVideoRenderer
5757
* @param eventListener A listener of events. May be null if delivery of events is not required.
5858
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
5959
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
60+
* @param minConsecutiveDroppedFramesToNotify The minimum number of consecutive frames that must
61+
* be dropped for {@link VideoRendererEventListener#onConsecutiveDroppedFrames(int, long)} to
62+
* be called.
6063
*/
6164
public ExperimentalFfmpegVideoRenderer(
6265
long allowedJoiningTimeMs,
6366
@Nullable Handler eventHandler,
6467
@Nullable VideoRendererEventListener eventListener,
65-
int maxDroppedFramesToNotify) {
66-
super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
68+
int maxDroppedFramesToNotify,
69+
int minConsecutiveDroppedFramesToNotify) {
70+
super(
71+
allowedJoiningTimeMs,
72+
eventHandler,
73+
eventListener,
74+
maxDroppedFramesToNotify,
75+
minConsecutiveDroppedFramesToNotify);
6776
// TODO: Implement.
6877
}
6978

libraries/decoder_vp9/src/main/java/androidx/media3/decoder/vp9/LibvpxVideoRenderer.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class LibvpxVideoRenderer extends DecoderVideoRenderer {
6565
* can attempt to seamlessly join an ongoing playback.
6666
*/
6767
public LibvpxVideoRenderer(long allowedJoiningTimeMs) {
68-
this(allowedJoiningTimeMs, null, null, 0);
68+
this(allowedJoiningTimeMs, null, null, 0, 0);
6969
}
7070

7171
/**
@@ -78,17 +78,22 @@ public LibvpxVideoRenderer(long allowedJoiningTimeMs) {
7878
* @param eventListener A listener of events. May be null if delivery of events is not required.
7979
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
8080
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
81+
* @param minConsecutiveDroppedFramesToNotify The minimum number of consecutive frames that must
82+
* be dropped for {@link VideoRendererEventListener#onConsecutiveDroppedFrames(int, long)} to
83+
* be called.
8184
*/
8285
public LibvpxVideoRenderer(
8386
long allowedJoiningTimeMs,
8487
@Nullable Handler eventHandler,
8588
@Nullable VideoRendererEventListener eventListener,
86-
int maxDroppedFramesToNotify) {
89+
int maxDroppedFramesToNotify,
90+
int minConsecutiveDroppedFramesToNotify) {
8791
this(
8892
allowedJoiningTimeMs,
8993
eventHandler,
9094
eventListener,
9195
maxDroppedFramesToNotify,
96+
minConsecutiveDroppedFramesToNotify,
9297
getRuntime().availableProcessors(),
9398
/* numInputBuffers= */ 4,
9499
/* numOutputBuffers= */ 4);
@@ -104,6 +109,9 @@ public LibvpxVideoRenderer(
104109
* @param eventListener A listener of events. May be null if delivery of events is not required.
105110
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
106111
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
112+
* @param minConsecutiveDroppedFramesToNotify The minimum number of consecutive frames that must
113+
* be dropped for {@link VideoRendererEventListener#onConsecutiveDroppedFrames(int, long)} to
114+
* be called.
107115
* @param threads Number of threads libvpx will use to decode.
108116
* @param numInputBuffers Number of input buffers.
109117
* @param numOutputBuffers Number of output buffers.
@@ -113,10 +121,16 @@ public LibvpxVideoRenderer(
113121
@Nullable Handler eventHandler,
114122
@Nullable VideoRendererEventListener eventListener,
115123
int maxDroppedFramesToNotify,
124+
int minConsecutiveDroppedFramesToNotify,
116125
int threads,
117126
int numInputBuffers,
118127
int numOutputBuffers) {
119-
super(allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify);
128+
super(
129+
allowedJoiningTimeMs,
130+
eventHandler,
131+
eventListener,
132+
maxDroppedFramesToNotify,
133+
minConsecutiveDroppedFramesToNotify);
120134
this.threads = threads;
121135
this.numInputBuffers = numInputBuffers;
122136
this.numOutputBuffers = numOutputBuffers;

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java

+17-4
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ public class DefaultRenderersFactory implements RenderersFactory {
9696
*/
9797
public static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
9898

99+
/**
100+
* The minimum number of consecutive video frames that would be dropped to
101+
* consider invoking {@link VideoRendererEventListener#onConsecutiveDroppedFrames(int, long)}.
102+
*/
103+
public static final int MIN_CONSECUTIVE_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 5;
104+
99105
private static final String TAG = "DefaultRenderersFactory";
100106

101107
private final Context context;
@@ -347,7 +353,8 @@ protected void buildVideoRenderers(
347353
enableDecoderFallback,
348354
eventHandler,
349355
eventListener,
350-
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
356+
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY,
357+
MIN_CONSECUTIVE_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
351358
out.add(videoRenderer);
352359

353360
if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
@@ -366,14 +373,16 @@ protected void buildVideoRenderers(
366373
long.class,
367374
android.os.Handler.class,
368375
androidx.media3.exoplayer.video.VideoRendererEventListener.class,
376+
int.class,
369377
int.class);
370378
Renderer renderer =
371379
(Renderer)
372380
constructor.newInstance(
373381
allowedVideoJoiningTimeMs,
374382
eventHandler,
375383
eventListener,
376-
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
384+
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY,
385+
MIN_CONSECUTIVE_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
377386
out.add(extensionRendererIndex++, renderer);
378387
Log.i(TAG, "Loaded LibvpxVideoRenderer.");
379388
} catch (ClassNotFoundException e) {
@@ -391,14 +400,16 @@ protected void buildVideoRenderers(
391400
long.class,
392401
android.os.Handler.class,
393402
androidx.media3.exoplayer.video.VideoRendererEventListener.class,
403+
int.class,
394404
int.class);
395405
Renderer renderer =
396406
(Renderer)
397407
constructor.newInstance(
398408
allowedVideoJoiningTimeMs,
399409
eventHandler,
400410
eventListener,
401-
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
411+
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY,
412+
MIN_CONSECUTIVE_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
402413
out.add(extensionRendererIndex++, renderer);
403414
Log.i(TAG, "Loaded Libgav1VideoRenderer.");
404415
} catch (ClassNotFoundException e) {
@@ -417,14 +428,16 @@ protected void buildVideoRenderers(
417428
long.class,
418429
android.os.Handler.class,
419430
androidx.media3.exoplayer.video.VideoRendererEventListener.class,
431+
int.class,
420432
int.class);
421433
Renderer renderer =
422434
(Renderer)
423435
constructor.newInstance(
424436
allowedVideoJoiningTimeMs,
425437
eventHandler,
426438
eventListener,
427-
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
439+
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY,
440+
MIN_CONSECUTIVE_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
428441
out.add(extensionRendererIndex++, renderer);
429442
Log.i(TAG, "Loaded FfmpegVideoRenderer.");
430443
} catch (ClassNotFoundException e) {

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java

+5
Original file line numberDiff line numberDiff line change
@@ -3033,6 +3033,11 @@ public void onDroppedFrames(int count, long elapsed) {
30333033
analyticsCollector.onDroppedFrames(count, elapsed);
30343034
}
30353035

3036+
@Override
3037+
public void onConsecutiveDroppedFrames(int count, long elapsed) {
3038+
analyticsCollector.onConsecutiveDroppedFrames(count, elapsed);
3039+
}
3040+
30363041
@Override
30373042
public void onVideoSizeChanged(VideoSize newVideoSize) {
30383043
videoSize = newVideoSize;

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/analytics/AnalyticsCollector.java

+12
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,18 @@ void onVideoInputFormatChanged(
250250
*/
251251
void onDroppedFrames(int count, long elapsedMs);
252252

253+
/**
254+
* Called to report the number of consecutive frames dropped by the video renderer. Consecutive
255+
* dropped frames are reported once a frame is renderered after a period in which more frames were
256+
* dropped consecutively than the specified threshold.
257+
*
258+
* @param count The number of consecutive dropped frames.
259+
* @param elapsedMs The duration in milliseconds over which the consecutive frames were dropped.
260+
* This duration is timed from when the first frame was dropped, until the time the renderer
261+
* succesfully rendered a frame or the rendered was interrupted (stopped, seeked, disabled).
262+
*/
263+
void onConsecutiveDroppedFrames(int count, long elapsedMs);
264+
253265
/**
254266
* Called when a video decoder is released.
255267
*

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/analytics/AnalyticsListener.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,8 @@ public int size() {
235235
EVENT_VIDEO_CODEC_ERROR,
236236
EVENT_AUDIO_TRACK_INITIALIZED,
237237
EVENT_AUDIO_TRACK_RELEASED,
238-
EVENT_RENDERER_READY_CHANGED
238+
EVENT_RENDERER_READY_CHANGED,
239+
EVENT_CONSECUTIVE_DROPPED_VIDEO_FRAMES,
239240
})
240241
@interface EventFlags {}
241242

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

452+
/** Consecutive video frames have been dropped. */
453+
@UnstableApi int EVENT_CONSECUTIVE_DROPPED_VIDEO_FRAMES = 1034;
454+
451455
/** Time information of an event. */
452456
@UnstableApi
453457
final class EventTime {
@@ -1244,6 +1248,18 @@ default void onVideoInputFormatChanged(
12441248
@UnstableApi
12451249
default void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {}
12461250

1251+
/**
1252+
* Called after consecutive video frames have been dropped.
1253+
*
1254+
* @param eventTime The event time.
1255+
* @param consecutiveDroppedFrames The number of consecutive frames that have been dropped before the
1256+
* last rendered frame.
1257+
* @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration
1258+
* is timed from the first dropped framed in the sequence.
1259+
*/
1260+
@UnstableApi
1261+
default void onConsecutiveDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {}
1262+
12471263
/**
12481264
* Called when a video renderer releases a decoder.
12491265
*

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/analytics/DefaultAnalyticsCollector.java

+9
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,15 @@ public final void onDroppedFrames(int count, long elapsedMs) {
340340
listener -> listener.onDroppedVideoFrames(eventTime, count, elapsedMs));
341341
}
342342

343+
@Override
344+
public final void onConsecutiveDroppedFrames(int count, long elapsedMs) {
345+
EventTime eventTime = generatePlayingMediaPeriodEventTime();
346+
sendEvent(
347+
eventTime,
348+
AnalyticsListener.EVENT_CONSECUTIVE_DROPPED_VIDEO_FRAMES,
349+
listener -> listener.onConsecutiveDroppedVideoFrames(eventTime, count, elapsedMs));
350+
}
351+
343352
@Override
344353
public final void onVideoDecoderReleased(String decoderName) {
345354
EventTime eventTime = generateReadingMediaPeriodEventTime();

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/util/EventLogger.java

+7
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,13 @@ public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long el
451451
logd(eventTime, "droppedFrames", Integer.toString(droppedFrames));
452452
}
453453

454+
@UnstableApi
455+
@Override
456+
public void onConsecutiveDroppedVideoFrames(
457+
EventTime eventTime, int consecutiveDroppedFrames, long elapsedMs) {
458+
logd(eventTime, "consecutiveDroppedFrames", Integer.toString(consecutiveDroppedFrames));
459+
}
460+
454461
@UnstableApi
455462
@Override
456463
public void onVideoDecoderReleased(EventTime eventTime, String decoderName) {

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/video/DecoderVideoRenderer.java

+22-3
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
113113

114114
private final long allowedJoiningTimeMs;
115115
private final int maxDroppedFramesToNotify;
116+
private final int minConsecutiveDroppedFramesToNotify;
116117
private final EventDispatcher eventDispatcher;
117118
private final TimedValueQueue<Format> formatQueue;
118119
private final DecoderInputBuffer flagsOnlyBuffer;
@@ -150,6 +151,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
150151

151152
private long droppedFrameAccumulationStartTimeMs;
152153
private int droppedFrames;
154+
private long consecutiveDroppedFrameAccumulationStartTimeMs;
153155
private int consecutiveDroppedFrameCount;
154156
private int buffersInCodecCount;
155157
private long lastRenderTimeUs;
@@ -165,15 +167,20 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
165167
* @param eventListener A listener of events. May be null if delivery of events is not required.
166168
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
167169
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
170+
* @param minConsecutiveDroppedFramesToNotify The minimum number of consecutive frames that must
171+
* be dropped for {@link VideoRendererEventListener#onConsecutiveDroppedFrames(int, long)} to
172+
* be called.
168173
*/
169174
protected DecoderVideoRenderer(
170175
long allowedJoiningTimeMs,
171176
@Nullable Handler eventHandler,
172177
@Nullable VideoRendererEventListener eventListener,
173-
int maxDroppedFramesToNotify) {
178+
int maxDroppedFramesToNotify,
179+
int minConsecutiveDroppedFramesToNotify) {
174180
super(C.TRACK_TYPE_VIDEO);
175181
this.allowedJoiningTimeMs = allowedJoiningTimeMs;
176182
this.maxDroppedFramesToNotify = maxDroppedFramesToNotify;
183+
this.minConsecutiveDroppedFramesToNotify = minConsecutiveDroppedFramesToNotify;
177184
joiningDeadlineMs = C.TIME_UNSET;
178185
formatQueue = new TimedValueQueue<>();
179186
flagsOnlyBuffer = DecoderInputBuffer.newNoDataInstance();
@@ -296,7 +303,7 @@ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlayb
296303
outputStreamEnded = false;
297304
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED);
298305
initialPositionUs = C.TIME_UNSET;
299-
consecutiveDroppedFrameCount = 0;
306+
maybeNotifyConsecutiveDroppedFrames();
300307
if (decoder != null) {
301308
flushDecoder();
302309
}
@@ -319,6 +326,7 @@ protected void onStarted() {
319326
protected void onStopped() {
320327
joiningDeadlineMs = C.TIME_UNSET;
321328
maybeNotifyDroppedFrames();
329+
maybeNotifyConsecutiveDroppedFrames();
322330
}
323331

324332
@Override
@@ -603,8 +611,8 @@ protected void renderOutputBuffer(
603611
} else {
604612
renderOutputBufferToSurface(outputBuffer, checkNotNull(outputSurface));
605613
}
606-
consecutiveDroppedFrameCount = 0;
607614
decoderCounters.renderedOutputBufferCount++;
615+
maybeNotifyConsecutiveDroppedFrames();
608616
maybeNotifyRenderedFirstFrame();
609617
}
610618
}
@@ -996,6 +1004,17 @@ private void maybeNotifyDroppedFrames() {
9961004
}
9971005
}
9981006

1007+
private void maybeNotifyConsecutiveDroppedFrames() {
1008+
if (consecutiveDroppedFrameCount > 0
1009+
&& consecutiveDroppedFrameCount >= minConsecutiveDroppedFramesToNotify) {
1010+
long elapsedMs = SystemClock.elapsedRealtime() - consecutiveDroppedFrameAccumulationStartTimeMs;
1011+
eventDispatcher.consecutiveDroppedFrames(consecutiveDroppedFrameCount, elapsedMs);
1012+
}
1013+
// Always reset the counter to 0, even if the threshold is not reached.
1014+
consecutiveDroppedFrameCount = 0;
1015+
consecutiveDroppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
1016+
}
1017+
9991018
private static boolean isBufferLate(long earlyUs) {
10001019
// Class a buffer as late if it should have been presented more than 30 ms ago.
10011020
return earlyUs < -30000;

0 commit comments

Comments
 (0)