| 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) {
|
|
|