Index: webrtc/sdk/android/api/org/webrtc/EglRenderer.java |
diff --git a/webrtc/sdk/android/api/org/webrtc/EglRenderer.java b/webrtc/sdk/android/api/org/webrtc/EglRenderer.java |
index f5c1fe1773ed2465a3fb3c18bb0028b5da0bbeff..0e54145063df2a746a2d2eb97b5553902fb28e75 100644 |
--- a/webrtc/sdk/android/api/org/webrtc/EglRenderer.java |
+++ b/webrtc/sdk/android/api/org/webrtc/EglRenderer.java |
@@ -95,8 +95,6 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
private EglBase eglBase; |
private final RendererCommon.YuvUploader yuvUploader = new RendererCommon.YuvUploader(); |
private RendererCommon.GlDrawer drawer; |
- // Texture ids for YUV frames. Allocated on first arrival of a YUV frame. |
- private int[] yuvTextures = null; |
// Pending frame to render. Serves as a queue with size 1. Synchronized on |frameLock|. |
private final Object frameLock = new Object(); |
@@ -239,10 +237,7 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
drawer.release(); |
drawer = null; |
} |
- if (yuvTextures != null) { |
- GLES20.glDeleteTextures(3, yuvTextures, 0); |
- yuvTextures = null; |
- } |
+ yuvUploader.release(); |
if (bitmapTextureFramebuffer != null) { |
bitmapTextureFramebuffer.release(); |
bitmapTextureFramebuffer = null; |
@@ -364,33 +359,31 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
* Register a callback to be invoked when a new video frame has been received. This version uses |
* the drawer of the EglRenderer that was passed in init. |
* |
- * @param listener The callback to be invoked. |
+ * @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. |
*/ |
public void addFrameListener(final FrameListener listener, final float scale) { |
- postToRenderThread(new Runnable() { |
- @Override |
- public void run() { |
- frameListeners.add(new FrameListenerAndParams(listener, scale, drawer)); |
- } |
- }); |
+ addFrameListener(listener, scale, null); |
} |
/** |
* Register a callback to be invoked when a new video frame has been received. |
* |
- * @param listener The callback to be invoked. |
+ * @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. |
+ * @param drawer Custom drawer to use for this frame listener or null to use the default one. |
*/ |
public void addFrameListener( |
- final FrameListener listener, final float scale, final RendererCommon.GlDrawer drawer) { |
+ final FrameListener listener, final float scale, final RendererCommon.GlDrawer drawerParam) { |
postToRenderThread(new Runnable() { |
@Override |
public void run() { |
- frameListeners.add(new FrameListenerAndParams(listener, scale, drawer)); |
+ final RendererCommon.GlDrawer listenerDrawer = drawerParam == null ? drawer : drawerParam; |
+ frameListeners.add(new FrameListenerAndParams(listener, scale, listenerDrawer)); |
} |
}); |
} |
@@ -403,6 +396,9 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
* @param runnable The callback to remove. |
*/ |
public void removeFrameListener(final FrameListener listener) { |
+ if (Thread.currentThread() == renderThreadHandler.getLooper().getThread()) { |
+ throw new RuntimeException("removeFrameListener must not be called on the render thread."); |
+ } |
final CountDownLatch latch = new CountDownLatch(1); |
postToRenderThread(new Runnable() { |
@Override |
@@ -432,20 +428,6 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
VideoRenderer.renderFrameDone(frame); |
return; |
} |
- // Check if fps reduction is active. |
- synchronized (fpsReductionLock) { |
- if (minRenderPeriodNs > 0) { |
- final long currentTimeNs = System.nanoTime(); |
- if (currentTimeNs < nextFrameTimeNs) { |
- logD("Dropping frame - fps reduction is active."); |
- VideoRenderer.renderFrameDone(frame); |
- return; |
- } |
- nextFrameTimeNs += minRenderPeriodNs; |
- // The time for the next frame should always be in the future. |
- nextFrameTimeNs = Math.max(nextFrameTimeNs, currentTimeNs); |
- } |
- } |
synchronized (frameLock) { |
dropOldFrame = (pendingFrame != null); |
if (dropOldFrame) { |
@@ -543,6 +525,28 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
VideoRenderer.renderFrameDone(frame); |
return; |
} |
+ // Check if fps reduction is active. |
+ final boolean shouldRenderFrame; |
+ synchronized (fpsReductionLock) { |
+ if (minRenderPeriodNs == Long.MAX_VALUE) { |
+ // Rendering is paused. |
+ shouldRenderFrame = false; |
+ } else if (minRenderPeriodNs <= 0) { |
+ // FPS reduction is disabled. |
+ shouldRenderFrame = true; |
+ } else { |
+ final long currentTimeNs = System.nanoTime(); |
+ if (currentTimeNs < nextFrameTimeNs) { |
+ logD("Skipping frame rendering - fps reduction is active."); |
+ shouldRenderFrame = false; |
+ } else { |
+ nextFrameTimeNs += minRenderPeriodNs; |
+ // The time for the next frame should always be in the future. |
+ nextFrameTimeNs = Math.max(nextFrameTimeNs, currentTimeNs); |
+ shouldRenderFrame = true; |
+ } |
+ } |
+ } |
final long startTimeNs = System.nanoTime(); |
final float[] texMatrix = |
@@ -575,55 +579,61 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
drawMatrix = RendererCommon.multiplyMatrices(texMatrix, layoutMatrix); |
} |
- GLES20.glClearColor(0 /* red */, 0 /* green */, 0 /* blue */, 0 /* alpha */); |
- GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); |
+ boolean shouldUploadYuvTextures = false; |
if (frame.yuvFrame) { |
- // Make sure YUV textures are allocated. |
- if (yuvTextures == null) { |
- yuvTextures = new int[3]; |
- for (int i = 0; i < 3; i++) { |
- yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); |
+ shouldUploadYuvTextures = shouldRenderFrame; |
+ // Check if there are frame listeners that we want to render a bitmap for regardless of if the |
+ // frame was rendered. This is the case when there are frameListeners with scale != 0f. |
+ if (!shouldUploadYuvTextures) { |
+ for (FrameListenerAndParams listenerAndParams : frameListeners) { |
+ if (listenerAndParams.scale != 0f) { |
+ shouldUploadYuvTextures = true; |
+ break; |
+ } |
} |
} |
- |
- yuvUploader.uploadYuvData( |
- yuvTextures, frame.width, frame.height, frame.yuvStrides, frame.yuvPlanes); |
- drawer.drawYuv(yuvTextures, drawMatrix, drawnFrameWidth, drawnFrameHeight, 0, 0, |
- eglBase.surfaceWidth(), eglBase.surfaceHeight()); |
- } else { |
- drawer.drawOes(frame.textureId, drawMatrix, drawnFrameWidth, drawnFrameHeight, 0, 0, |
- eglBase.surfaceWidth(), eglBase.surfaceHeight()); |
} |
+ final int[] yuvTextures = shouldUploadYuvTextures |
+ ? yuvUploader.uploadYuvData(frame.width, frame.height, frame.yuvStrides, frame.yuvPlanes) |
+ : null; |
- final long swapBuffersStartTimeNs = System.nanoTime(); |
- eglBase.swapBuffers(); |
+ if (shouldRenderFrame) { |
+ GLES20.glClearColor(0 /* red */, 0 /* green */, 0 /* blue */, 0 /* alpha */); |
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); |
+ if (frame.yuvFrame) { |
+ drawer.drawYuv(yuvTextures, drawMatrix, drawnFrameWidth, drawnFrameHeight, 0, 0, |
+ eglBase.surfaceWidth(), eglBase.surfaceHeight()); |
+ } else { |
+ drawer.drawOes(frame.textureId, drawMatrix, drawnFrameWidth, drawnFrameHeight, 0, 0, |
+ eglBase.surfaceWidth(), eglBase.surfaceHeight()); |
+ } |
- final long currentTimeNs = System.nanoTime(); |
- synchronized (statisticsLock) { |
- ++framesRendered; |
- renderTimeNs += (currentTimeNs - startTimeNs); |
- renderSwapBufferTimeNs += (currentTimeNs - swapBuffersStartTimeNs); |
+ final long swapBuffersStartTimeNs = System.nanoTime(); |
+ eglBase.swapBuffers(); |
+ |
+ final long currentTimeNs = System.nanoTime(); |
+ synchronized (statisticsLock) { |
+ ++framesRendered; |
+ renderTimeNs += (currentTimeNs - startTimeNs); |
+ renderSwapBufferTimeNs += (currentTimeNs - swapBuffersStartTimeNs); |
+ } |
} |
- notifyCallbacks(frame, texMatrix); |
+ notifyCallbacks(frame, yuvTextures, texMatrix); |
VideoRenderer.renderFrameDone(frame); |
} |
- private void notifyCallbacks(VideoRenderer.I420Frame frame, float[] texMatrix) { |
- // Make temporary copy of callback list to avoid ConcurrentModificationException, in case |
- // callbacks call addFramelistener or removeFrameListener. |
- final ArrayList<FrameListenerAndParams> tmpList; |
+ private void notifyCallbacks( |
+ VideoRenderer.I420Frame frame, int[] yuvTextures, float[] texMatrix) { |
if (frameListeners.isEmpty()) |
return; |
- tmpList = new ArrayList<>(frameListeners); |
- frameListeners.clear(); |
final float[] bitmapMatrix = RendererCommon.multiplyMatrices( |
RendererCommon.multiplyMatrices(texMatrix, |
mirror ? RendererCommon.horizontalFlipMatrix() : RendererCommon.identityMatrix()), |
RendererCommon.verticalFlipMatrix()); |
- for (FrameListenerAndParams listenerAndParams : tmpList) { |
+ for (FrameListenerAndParams listenerAndParams : frameListeners) { |
final int scaledWidth = (int) (listenerAndParams.scale * frame.rotatedWidth()); |
final int scaledHeight = (int) (listenerAndParams.scale * frame.rotatedHeight()); |
@@ -663,6 +673,7 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
bitmap.copyPixelsFromBuffer(bitmapBuffer); |
listenerAndParams.listener.onFrame(bitmap); |
} |
+ frameListeners.clear(); |
} |
private String averageTimeAsString(long sumTimeNs, int count) { |