Chromium Code Reviews

Unified Diff: webrtc/api/android/java/src/org/webrtc/EglRenderer.java

Issue 2456873002: Android EglRenderer: Add Bitmap frame listener functionality (Closed)
Patch Set: Move notifyCallbacks outside rending time measurement period. Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
« no previous file with comments | « webrtc/api/BUILD.gn ('k') | webrtc/api/android/java/src/org/webrtc/GlRectDrawer.java » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: webrtc/api/android/java/src/org/webrtc/EglRenderer.java
diff --git a/webrtc/api/android/java/src/org/webrtc/EglRenderer.java b/webrtc/api/android/java/src/org/webrtc/EglRenderer.java
index d5c29e77404ba26ef4318d2e63eba382507a018e..9fb035b34b9edaf37997e87276c6900e8b374dc1 100644
--- a/webrtc/api/android/java/src/org/webrtc/EglRenderer.java
+++ b/webrtc/api/android/java/src/org/webrtc/EglRenderer.java
@@ -10,12 +10,16 @@
package org.webrtc;
+import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.opengl.GLES20;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.view.Surface;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -29,6 +33,18 @@ public class EglRenderer implements VideoRenderer.Callbacks {
private static final long LOG_INTERVAL_SEC = 4;
private static final int MAX_SURFACE_CLEAR_COUNT = 3;
+ public interface FrameListener { void onFrame(Bitmap frame); }
+
+ private static class ScaleAndFrameListener {
+ public final float scale;
+ public final FrameListener listener;
+
+ public ScaleAndFrameListener(float scale, FrameListener listener) {
+ this.scale = scale;
+ this.listener = listener;
+ }
+ }
+
private class EglSurfaceCreation implements Runnable {
private Object surface;
@@ -60,6 +76,9 @@ public class EglRenderer implements VideoRenderer.Callbacks {
private final Object handlerLock = new Object();
private Handler renderThreadHandler;
+ private final Object frameListenerLock = new Object();
+ private final ArrayList<ScaleAndFrameListener> frameListeners = new ArrayList<>();
+
// Variables for fps reduction.
private final Object fpsReductionLock = new Object();
// Time for when next frame should be rendered.
@@ -104,6 +123,9 @@ public class EglRenderer implements VideoRenderer.Callbacks {
// Time in ns spent by the render thread in the swapBuffers() function.
private long renderSwapBufferTimeNs;
+ // Used for bitmap capturing.
+ private GlTextureFrameBuffer bitmapTextureFramebuffer;
+
// Runnable for posting frames to render thread.
private final Runnable renderFrameRunnable = new Runnable() {
@Override
@@ -220,6 +242,10 @@ public class EglRenderer implements VideoRenderer.Callbacks {
GLES20.glDeleteTextures(3, yuvTextures, 0);
yuvTextures = null;
}
+ if (bitmapTextureFramebuffer != null) {
+ bitmapTextureFramebuffer.release();
+ bitmapTextureFramebuffer = null;
+ }
if (eglBase != null) {
logD("eglBase detach and release.");
eglBase.detachCurrent();
@@ -333,6 +359,36 @@ public class EglRenderer implements VideoRenderer.Callbacks {
setFpsReduction(0 /* fps */);
}
+ /**
+ * Register a callback to be invoked when a new video frame has been received.
+ *
+ * @param listener The callback to be invoked.
+ * @param scale The scale of the Bitmap passed to the callback, or 0 if no Bitmap is
+ * required.
+ */
+ public void addFrameListener(FrameListener listener, float scale) {
+ synchronized (frameListenerLock) {
+ frameListeners.add(new ScaleAndFrameListener(scale, listener));
+ }
+ }
+
+ /**
+ * Remove any pending callback that was added with addFrameListener. If the callback is not in
+ * the queue, nothing happens.
+ *
+ * @param runnable The callback to remove.
+ */
+ public void removeFrameListener(FrameListener listener) {
+ synchronized (frameListenerLock) {
+ final Iterator<ScaleAndFrameListener> iter = frameListeners.iterator();
+ while (iter.hasNext()) {
+ if (iter.next().listener == listener) {
+ iter.remove();
+ }
+ }
+ }
+ }
+
// VideoRenderer.Callbacks interface.
@Override
public void renderFrame(VideoRenderer.I420Frame frame) {
@@ -472,8 +528,9 @@ public class EglRenderer implements VideoRenderer.Callbacks {
}
final long startTimeNs = System.nanoTime();
- float[] texMatrix =
+ final float[] texMatrix =
RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
+ final float[] drawMatrix;
// After a surface size change, the EGLSurface might still have a buffer of the old size in the
// pipeline. Querying the EGLSurface will show if the underlying buffer dimensions haven't yet
@@ -487,7 +544,8 @@ public class EglRenderer implements VideoRenderer.Callbacks {
VideoRenderer.renderFrameDone(frame);
return;
}
- logD("Surface size mismatch - clearing surface.");
+ logD("Surface size mismatch - clearing surface. Size: " + eglBase.surfaceWidth() + "x"
+ + eglBase.surfaceHeight() + " Expected: " + surfaceWidth + "x" + surfaceHeight);
clearSurfaceOnRenderThread();
}
final float[] layoutMatrix;
@@ -498,7 +556,7 @@ public class EglRenderer implements VideoRenderer.Callbacks {
layoutMatrix =
mirror ? RendererCommon.horizontalFlipMatrix() : RendererCommon.identityMatrix();
}
- texMatrix = RendererCommon.multiplyMatrices(texMatrix, layoutMatrix);
+ drawMatrix = RendererCommon.multiplyMatrices(texMatrix, layoutMatrix);
}
GLES20.glClearColor(0 /* red */, 0 /* green */, 0 /* blue */, 0 /* alpha */);
@@ -513,16 +571,15 @@ public class EglRenderer implements VideoRenderer.Callbacks {
}
yuvUploader.uploadYuvData(
yuvTextures, frame.width, frame.height, frame.yuvStrides, frame.yuvPlanes);
- drawer.drawYuv(yuvTextures, texMatrix, frame.rotatedWidth(), frame.rotatedHeight(), 0, 0,
+ drawer.drawYuv(yuvTextures, drawMatrix, frame.rotatedWidth(), frame.rotatedHeight(), 0, 0,
surfaceWidth, surfaceHeight);
} else {
- drawer.drawOes(frame.textureId, texMatrix, frame.rotatedWidth(), frame.rotatedHeight(), 0, 0,
+ drawer.drawOes(frame.textureId, drawMatrix, frame.rotatedWidth(), frame.rotatedHeight(), 0, 0,
surfaceWidth, surfaceHeight);
}
final long swapBuffersStartTimeNs = System.nanoTime();
eglBase.swapBuffers();
- VideoRenderer.renderFrameDone(frame);
final long currentTimeNs = System.nanoTime();
synchronized (statisticsLock) {
@@ -530,6 +587,65 @@ public class EglRenderer implements VideoRenderer.Callbacks {
renderTimeNs += (currentTimeNs - startTimeNs);
renderSwapBufferTimeNs += (currentTimeNs - swapBuffersStartTimeNs);
}
+
+ notifyCallbacks(frame, 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<ScaleAndFrameListener> tmpList;
+ synchronized (frameListenerLock) {
+ 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 (ScaleAndFrameListener scaleAndListener : tmpList) {
+ final int scaledWidth = (int) (scaleAndListener.scale * frame.rotatedWidth());
+ final int scaledHeight = (int) (scaleAndListener.scale * frame.rotatedHeight());
+
+ if (scaledWidth == 0 || scaledHeight == 0) {
+ scaleAndListener.listener.onFrame(null);
+ continue;
+ }
+
+ if (bitmapTextureFramebuffer == null) {
+ bitmapTextureFramebuffer = new GlTextureFrameBuffer(GLES20.GL_RGBA);
+ }
+ bitmapTextureFramebuffer.setSize(scaledWidth, scaledHeight);
+
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, bitmapTextureFramebuffer.getFrameBufferId());
+ GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
+ GLES20.GL_TEXTURE_2D, bitmapTextureFramebuffer.getTextureId(), 0);
+
+ if (frame.yuvFrame) {
+ drawer.drawYuv(yuvTextures, bitmapMatrix, frame.rotatedWidth(), frame.rotatedHeight(),
+ 0 /* viewportX */, 0 /* viewportY */, scaledWidth, scaledHeight);
+ } else {
+ drawer.drawOes(frame.textureId, bitmapMatrix, frame.rotatedWidth(), frame.rotatedHeight(),
+ 0 /* viewportX */, 0 /* viewportY */, scaledWidth, scaledHeight);
+ }
+
+ final ByteBuffer bitmapBuffer = ByteBuffer.allocateDirect(scaledWidth * scaledHeight * 4);
+ GLES20.glViewport(0, 0, scaledWidth, scaledHeight);
+ GLES20.glReadPixels(
+ 0, 0, scaledWidth, scaledHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, bitmapBuffer);
+
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+ GlUtil.checkNoGLES2Error("EglRenderer.notifyCallbacks");
+
+ final Bitmap bitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
+ bitmap.copyPixelsFromBuffer(bitmapBuffer);
+ scaleAndListener.listener.onFrame(bitmap);
+ }
}
private String averageTimeAsString(long sumTimeNs, int count) {
« no previous file with comments | « webrtc/api/BUILD.gn ('k') | webrtc/api/android/java/src/org/webrtc/GlRectDrawer.java » ('j') | no next file with comments »

Powered by Google App Engine