diff --git a/webrtc/sdk/android/api/org/webrtc/EglRenderer.java b/webrtc/sdk/android/api/org/webrtc/EglRenderer.java index 3d65b72b90..d0458ed6eb 100644 --- a/webrtc/sdk/android/api/org/webrtc/EglRenderer.java +++ b/webrtc/sdk/android/api/org/webrtc/EglRenderer.java @@ -40,12 +40,14 @@ public class EglRenderer implements VideoRenderer.Callbacks { public final FrameListener listener; public final float scale; public final RendererCommon.GlDrawer drawer; + public final boolean applyFpsReduction; - public FrameListenerAndParams( - FrameListener listener, float scale, RendererCommon.GlDrawer drawer) { + public FrameListenerAndParams(FrameListener listener, float scale, + RendererCommon.GlDrawer drawer, boolean applyFpsReduction) { this.listener = listener; this.scale = scale; this.drawer = drawer; + this.applyFpsReduction = applyFpsReduction; } } @@ -365,7 +367,7 @@ public class EglRenderer implements VideoRenderer.Callbacks { * required. */ public void addFrameListener(final FrameListener listener, final float scale) { - addFrameListener(listener, scale, null); + addFrameListener(listener, scale, null, false /* applyFpsReduction */); } /** @@ -379,11 +381,28 @@ public class EglRenderer implements VideoRenderer.Callbacks { */ public void addFrameListener( final FrameListener listener, final float scale, final RendererCommon.GlDrawer drawerParam) { + addFrameListener(listener, scale, drawerParam, false /* applyFpsReduction */); + } + + /** + * Register a callback to be invoked when a new video frame has been received. + * + * @param listener The callback to be invoked. The callback will be invoked on the render thread. + * It should be lightweight and must not call removeFrameListener. + * @param scale The scale of the Bitmap passed to the callback, or 0 if no Bitmap is + * required. + * @param drawer Custom drawer to use for this frame listener or null to use the default one. + * @param applyFpsReduction This callback will not be called for frames that have been dropped by + * FPS reduction. + */ + public void addFrameListener(final FrameListener listener, final float scale, + final RendererCommon.GlDrawer drawerParam, final boolean applyFpsReduction) { postToRenderThread(new Runnable() { @Override public void run() { final RendererCommon.GlDrawer listenerDrawer = drawerParam == null ? drawer : drawerParam; - frameListeners.add(new FrameListenerAndParams(listener, scale, listenerDrawer)); + frameListeners.add( + new FrameListenerAndParams(listener, scale, listenerDrawer, applyFpsReduction)); } }); } @@ -593,7 +612,8 @@ public class EglRenderer implements VideoRenderer.Callbacks { // frame was rendered. This is the case when there are frameListeners with scale != 0f. if (!shouldUploadYuvTextures) { for (FrameListenerAndParams listenerAndParams : frameListeners) { - if (listenerAndParams.scale != 0f) { + if (listenerAndParams.scale != 0f + && (shouldRenderFrame || !listenerAndParams.applyFpsReduction)) { shouldUploadYuvTextures = true; break; } @@ -626,12 +646,12 @@ public class EglRenderer implements VideoRenderer.Callbacks { } } - notifyCallbacks(frame, yuvTextures, texMatrix); + notifyCallbacks(frame, yuvTextures, texMatrix, shouldRenderFrame); VideoRenderer.renderFrameDone(frame); } private void notifyCallbacks( - VideoRenderer.I420Frame frame, int[] yuvTextures, float[] texMatrix) { + VideoRenderer.I420Frame frame, int[] yuvTextures, float[] texMatrix, boolean wasRendered) { if (frameListeners.isEmpty()) return; @@ -640,7 +660,14 @@ public class EglRenderer implements VideoRenderer.Callbacks { mirror ? RendererCommon.horizontalFlipMatrix() : RendererCommon.identityMatrix()), RendererCommon.verticalFlipMatrix()); - for (FrameListenerAndParams listenerAndParams : frameListeners) { + Iterator it = frameListeners.iterator(); + while (it.hasNext()) { + FrameListenerAndParams listenerAndParams = it.next(); + if (!wasRendered && listenerAndParams.applyFpsReduction) { + continue; + } + it.remove(); + final int scaledWidth = (int) (listenerAndParams.scale * frame.rotatedWidth()); final int scaledHeight = (int) (listenerAndParams.scale * frame.rotatedHeight()); @@ -680,7 +707,6 @@ public class EglRenderer implements VideoRenderer.Callbacks { bitmap.copyPixelsFromBuffer(bitmapBuffer); listenerAndParams.listener.onFrame(bitmap); } - frameListeners.clear(); } private String averageTimeAsString(long sumTimeNs, int count) { diff --git a/webrtc/sdk/android/instrumentationtests/src/org/webrtc/EglRendererTest.java b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/EglRendererTest.java index e74ec93c72..49c7dd33ce 100644 --- a/webrtc/sdk/android/instrumentationtests/src/org/webrtc/EglRendererTest.java +++ b/webrtc/sdk/android/instrumentationtests/src/org/webrtc/EglRendererTest.java @@ -310,12 +310,29 @@ public class EglRendererTest { @Test @SmallTest - public void testFrameListenersWhilePaused() throws Exception { - // Test that frame listeners receive frames while renderer is paused. + public void testFrameListenersFpsReduction() throws Exception { + // Test that normal frame listeners receive frames while the renderer is paused. eglRenderer.pauseVideo(); eglRenderer.addFrameListener(testFrameListener, 1f /* scaleFactor */); feedFrame(0); assertTrue(testFrameListener.waitForBitmap(RENDER_WAIT_MS)); checkBitmapContent(testFrameListener.resetAndGetBitmap(), 0); + + // Test that frame listeners with FPS reduction applied receive frames while the renderer is not + // paused. + eglRenderer.disableFpsReduction(); + eglRenderer.addFrameListener( + testFrameListener, 1f /* scaleFactor */, null, true /* applyFpsReduction */); + feedFrame(1); + assertTrue(testFrameListener.waitForBitmap(RENDER_WAIT_MS)); + checkBitmapContent(testFrameListener.resetAndGetBitmap(), 1); + + // Test that frame listeners with FPS reduction applied will not receive frames while the + // renderer is paused. + eglRenderer.pauseVideo(); + eglRenderer.addFrameListener( + testFrameListener, 1f /* scaleFactor */, null, true /* applyFpsReduction */); + feedFrame(1); + assertFalse(testFrameListener.waitForBitmap(RENDER_WAIT_MS)); } }