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"; |
} |