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..2a56fdbb9beb3530907cb698305a27ac7faa561c 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.IntBuffer; |
+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. |
@@ -72,6 +91,7 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
// from the render thread. |
private EglBase eglBase; |
private final RendererCommon.YuvUploader yuvUploader = new RendererCommon.YuvUploader(); |
+ private YuvConverter yuvConverter; |
magjed_webrtc
2016/11/01 18:06:46
Remove this now since you didn't end up using it.
sakal
2016/11/02 08:34:29
Done.
|
private RendererCommon.GlDrawer drawer; |
// Texture ids for YUV frames. Allocated on first arrival of a YUV frame. |
private int[] yuvTextures = null; |
@@ -104,6 +124,12 @@ 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 final int[] bitmapTexture = new int[1]; |
magjed_webrtc
2016/11/01 18:06:46
Keep this as an int instead of int[1]. Same for bi
sakal
2016/11/02 08:34:29
Done.
|
+ private final int[] bitmapFramebuffer = new int[1]; |
+ private int bitmapTextureWidth; |
+ private int bitmapTextureHeight; |
+ |
// Runnable for posting frames to render thread. |
private final Runnable renderFrameRunnable = new Runnable() { |
@Override |
@@ -216,10 +242,18 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
drawer.release(); |
drawer = null; |
} |
+ if (yuvConverter != null) { |
+ yuvConverter.release(); |
+ yuvConverter = null; |
+ } |
if (yuvTextures != null) { |
GLES20.glDeleteTextures(3, yuvTextures, 0); |
yuvTextures = null; |
} |
+ GLES20.glDeleteFramebuffers(1, bitmapFramebuffer, 0); |
magjed_webrtc
2016/11/01 18:06:46
Add 'if (bitmapFramebuffer[0] != 0)' check here. S
sakal
2016/11/02 08:34:29
"glDeleteFramebuffers silently ignores 0's and nam
|
+ bitmapFramebuffer[0] = 0; |
+ GLES20.glDeleteTextures(1, bitmapTexture, 0); |
+ bitmapTexture[0] = 0; |
if (eglBase != null) { |
logD("eglBase detach and release."); |
eglBase.detachCurrent(); |
@@ -333,6 +367,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 +536,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 +552,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 +564,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,13 +579,74 @@ 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); |
} |
+ // Notify callbacks. Make temporary copy of callback list to avoid |
magjed_webrtc
2016/11/01 18:06:46
Move this block of code into a helper function. Al
sakal
2016/11/02 08:34:29
Done.
|
+ // ConcurrentModificationException, in case callbacks call addFramelistener or |
+ // removeFrameListener. |
+ final ArrayList<ScaleAndFrameListener> tmpList = new ArrayList<>(frameListeners); |
+ frameListeners.clear(); |
+ for (ScaleAndFrameListener scaleAndListener : tmpList) { |
+ final int scaledWidth = (int) (scaleAndListener.scale * frame.rotatedWidth()); |
+ final int scaledHeight = (int) (scaleAndListener.scale * frame.rotatedHeight()); |
+ final float[] bitmapMatrix = RendererCommon.multiplyMatrices( |
magjed_webrtc
2016/11/01 18:06:46
Move this outside the loop.
sakal
2016/11/02 08:34:29
Done.
|
+ RendererCommon.multiplyMatrices(texMatrix, |
+ mirror ? RendererCommon.horizontalFlipMatrix() : RendererCommon.identityMatrix()), |
+ RendererCommon.verticalFlipMatrix()); |
+ |
+ if (scaledWidth == 0 || scaledHeight == 0) { |
+ scaleAndListener.listener.onFrame(null); |
+ continue; |
+ } |
+ if (bitmapTexture[0] == 0) { |
+ bitmapTexture[0] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); |
+ bitmapTextureWidth = bitmapTextureHeight = 0; |
+ } |
+ if (bitmapFramebuffer[0] == 0) { |
+ GLES20.glGenFramebuffers(1, bitmapFramebuffer, 0); |
+ } |
+ |
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexture[0]); |
+ if (scaledWidth != bitmapTextureWidth || scaledHeight != bitmapTextureHeight) { |
magjed_webrtc
2016/11/01 18:06:46
Wait a minute... Maybe you can use the helper clas
sakal
2016/11/02 08:34:29
Done.
|
+ GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, scaledWidth, scaledHeight, 0, |
+ GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null); |
+ bitmapTextureWidth = scaledWidth; |
+ bitmapTextureHeight = scaledHeight; |
+ } |
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, bitmapFramebuffer[0]); |
+ GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, |
+ GLES20.GL_TEXTURE_2D, bitmapTexture[0], 0); |
+ |
+ if (frame.yuvFrame) { |
+ drawer.drawYuv(yuvTextures, bitmapMatrix, frame.rotatedWidth(), frame.rotatedHeight(), 0, 0, |
+ scaledWidth, scaledHeight); |
+ } else { |
+ drawer.drawOes(frame.textureId, bitmapMatrix, frame.rotatedWidth(), frame.rotatedHeight(), |
+ 0, 0, scaledWidth, scaledHeight); |
+ } |
+ |
+ final IntBuffer bitmapBuffer = IntBuffer.allocate(scaledWidth * scaledHeight); |
+ 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.renderFrameOnRenderThread - bitmap capture"); |
+ |
+ // RGBA to ARGB conversion. |
+ final int[] bitmapArray = bitmapBuffer.array(); |
+ nativeABGRToARGB(bitmapArray, scaledWidth, scaledHeight); |
+ |
+ final Bitmap bitmap = Bitmap.createBitmap( |
+ bitmapBuffer.array(), scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888); |
+ scaleAndListener.listener.onFrame(bitmap); |
+ } |
+ |
final long swapBuffersStartTimeNs = System.nanoTime(); |
eglBase.swapBuffers(); |
VideoRenderer.renderFrameDone(frame); |
@@ -559,4 +686,6 @@ public class EglRenderer implements VideoRenderer.Callbacks { |
private void logD(String string) { |
Logging.d(TAG, name + string); |
} |
+ |
+ private static native void nativeABGRToARGB(int[] array, int width, int height); |
} |