| 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 415e1278541b0c7fe0fd59e9f0d01085e2e59399..d026baba2e507c5b3d9c7f07e3f2b666d12b1b7f 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.
|
| @@ -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;
|
| private RendererCommon.GlDrawer drawer;
|
| // Texture ids for YUV frames. Allocated on first arrival of a YUV frame.
|
| private int[] yuvTextures = null;
|
| @@ -216,6 +236,10 @@ 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;
|
| @@ -333,6 +357,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 null 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) {
|
| @@ -466,6 +520,8 @@ public class EglRenderer implements VideoRenderer.Callbacks {
|
| return;
|
| }
|
|
|
| + notifyFrameListeners(frame);
|
| +
|
| final long startTimeNs = System.nanoTime();
|
| float[] texMatrix =
|
| RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
|
| @@ -527,6 +583,78 @@ public class EglRenderer implements VideoRenderer.Callbacks {
|
| }
|
| }
|
|
|
| + private int roundUp(float value, int multiple) {
|
| + return ((int) (value + multiple - 1) / multiple) * multiple;
|
| + }
|
| +
|
| + private void notifyFrameListeners(VideoRenderer.I420Frame frame) {
|
| + synchronized (frameListenerLock) {
|
| + if (frameListeners.isEmpty()) {
|
| + return;
|
| + }
|
| + // Notify callbacks. Make temporary copy of callback list to avoid
|
| + // ConcurrentModificationException, in case callbacks call addFramelistener or
|
| + // removeFrameListener.
|
| + final ArrayList<ScaleAndFrameListener> tmpList = new ArrayList<>(frameListeners);
|
| + frameListeners.clear();
|
| +
|
| + final float[] transformMatrix =
|
| + mirror ? RendererCommon.horizontalFlipMatrix() : RendererCommon.identityMatrix();
|
| +
|
| + for (ScaleAndFrameListener scaleAndListener : tmpList) {
|
| + if (scaleAndListener.scale == 0) {
|
| + scaleAndListener.listener.onFrame(null /* frame */);
|
| + } else {
|
| + final ByteBuffer yPlane;
|
| + final ByteBuffer uPlane;
|
| + final ByteBuffer vPlane;
|
| + final int yStride;
|
| + final int uStride;
|
| + final int vStride;
|
| + final int scaledWidth;
|
| + final int scaledHeight;
|
| +
|
| + if (frame.yuvFrame) {
|
| + scaledWidth = (int) (scaleAndListener.scale * frame.rotatedWidth());
|
| + scaledHeight = (int) (scaleAndListener.scale * frame.rotatedHeight());
|
| + yPlane = frame.yuvPlanes[0];
|
| + uPlane = frame.yuvPlanes[1];
|
| + vPlane = frame.yuvPlanes[2];
|
| + yStride = frame.yuvStrides[0];
|
| + uStride = frame.yuvStrides[1];
|
| + vStride = frame.yuvStrides[2];
|
| + } else {
|
| + if (yuvConverter == null) {
|
| + yuvConverter = new YuvConverter();
|
| + }
|
| + scaled_width = roundUp(scaleAndListener.scale * frame.rotatedWidth(), 8);
|
| + scaled_height = roundUp(scaleAndListener.scale * frame.rotatedHeight(), 2);
|
| + final int size = scaled_width * scaled_height * 3 / 2;
|
| +
|
| + final ByteBuffer buf = ByteBuffer.allocateDirect(size);
|
| + yuvConverter.convert(
|
| + buf, scaled_width, scaled_height, scaled_width, frame.textureId, transformMatrix);
|
| + yPlane = buf;
|
| + buf.position(scaled_width * scaled_height);
|
| + uPlane = buf.slice();
|
| + buf.position(scaled_width * scaled_height + scaled_width / 2);
|
| + vPlane = buf.slice();
|
| + yStride = scaled_width;
|
| + uStride = scaled_width;
|
| + vStride = scaled_width;
|
| + }
|
| +
|
| + final ByteBuffer argbBuffer = ByteBuffer.allocateDirect(scaled_width * scaled_height * 4);
|
| + // TODO. Call libyuv::I420ToARGB through JNI.
|
| + // nativeI420ToArgb(yPlane, yStride, uPlane, uStride, vPlane, vStride, argbBuffer);
|
| + final Bitmap bitmap = Bitmap.createBitmap(
|
| + argbBuffer.array(), scaled_width, scaled_height, Bitmap.Config.ARGB_8888);
|
| + scaleAndListener.listener.onFrame(bitmap);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| private String averageTimeAsString(long sumTimeNs, int count) {
|
| return (count <= 0) ? "NA" : TimeUnit.NANOSECONDS.toMicros(sumTimeNs / count) + " μs";
|
| }
|
|
|