Move rendering code in SurfaceViewRenderer to a separate class.
The new SurfaceEglRenderer helper class extends EglRenderer and
implements rendering on a SurfaceView.
Bug: webrtc:8242
Change-Id: Ic532fe487755d3b54c6bd03f239d714e1ecb10ad
Reviewed-on: https://webrtc-review.googlesource.com/2940
Commit-Queue: Sami Kalliomäki <sakal@webrtc.org>
Reviewed-by: Sami Kalliomäki <sakal@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20562}
diff --git a/sdk/android/api/org/webrtc/SurfaceEglRenderer.java b/sdk/android/api/org/webrtc/SurfaceEglRenderer.java
new file mode 100644
index 0000000..338871c
--- /dev/null
+++ b/sdk/android/api/org/webrtc/SurfaceEglRenderer.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc;
+
+import android.view.SurfaceHolder;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Display the video stream on a Surface.
+ * renderFrame() is asynchronous to avoid blocking the calling thread.
+ * This class is thread safe and handles access from potentially three different threads:
+ * Interaction from the main app in init, release and setMirror.
+ * Interaction from C++ rtc::VideoSinkInterface in renderFrame.
+ * Interaction from SurfaceHolder lifecycle in surfaceCreated, surfaceChanged, and surfaceDestroyed.
+ */
+public class SurfaceEglRenderer extends EglRenderer implements SurfaceHolder.Callback {
+ private static final String TAG = "SurfaceEglRenderer";
+
+ // Callback for reporting renderer events. Read-only after initilization so no lock required.
+ private RendererCommon.RendererEvents rendererEvents;
+
+ private final Object layoutLock = new Object();
+ private boolean isRenderingPaused = false;
+ private boolean isFirstFrameRendered;
+ private int rotatedFrameWidth;
+ private int rotatedFrameHeight;
+ private int frameRotation;
+
+ /**
+ * In order to render something, you must first call init().
+ */
+ public SurfaceEglRenderer(String name) {
+ super(name);
+ }
+
+ /**
+ * Initialize this class, sharing resources with |sharedContext|. The custom |drawer| will be used
+ * for drawing frames on the EGLSurface. This class is responsible for calling release() on
+ * |drawer|. It is allowed to call init() to reinitialize the renderer after a previous
+ * init()/release() cycle.
+ */
+ public void init(final EglBase.Context sharedContext,
+ RendererCommon.RendererEvents rendererEvents, final int[] configAttributes,
+ RendererCommon.GlDrawer drawer) {
+ ThreadUtils.checkIsOnMainThread();
+ this.rendererEvents = rendererEvents;
+ synchronized (layoutLock) {
+ isFirstFrameRendered = false;
+ rotatedFrameWidth = 0;
+ rotatedFrameHeight = 0;
+ frameRotation = 0;
+ }
+ super.init(sharedContext, configAttributes, drawer);
+ }
+
+ @Override
+ public void init(final EglBase.Context sharedContext, final int[] configAttributes,
+ RendererCommon.GlDrawer drawer) {
+ init(sharedContext, null /* rendererEvents */, configAttributes, drawer);
+ }
+
+ /**
+ * Limit render framerate.
+ *
+ * @param fps Limit render framerate to this value, or use Float.POSITIVE_INFINITY to disable fps
+ * reduction.
+ */
+ @Override
+ public void setFpsReduction(float fps) {
+ synchronized (layoutLock) {
+ isRenderingPaused = fps == 0f;
+ }
+ super.setFpsReduction(fps);
+ }
+
+ @Override
+ public void disableFpsReduction() {
+ synchronized (layoutLock) {
+ isRenderingPaused = false;
+ }
+ super.disableFpsReduction();
+ }
+
+ @Override
+ public void pauseVideo() {
+ synchronized (layoutLock) {
+ isRenderingPaused = true;
+ }
+ super.pauseVideo();
+ }
+
+ // VideoRenderer.Callbacks interface.
+ @Override
+ public void renderFrame(VideoRenderer.I420Frame frame) {
+ updateFrameDimensionsAndReportEvents(frame);
+ super.renderFrame(frame);
+ }
+
+ // VideoSink interface.
+ @Override
+ public void onFrame(VideoFrame frame) {
+ updateFrameDimensionsAndReportEvents(frame);
+ super.onFrame(frame);
+ }
+
+ // SurfaceHolder.Callback interface.
+ @Override
+ public void surfaceCreated(final SurfaceHolder holder) {
+ ThreadUtils.checkIsOnMainThread();
+ createEglSurface(holder.getSurface());
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ ThreadUtils.checkIsOnMainThread();
+ final CountDownLatch completionLatch = new CountDownLatch(1);
+ releaseEglSurface(completionLatch::countDown);
+ ThreadUtils.awaitUninterruptibly(completionLatch);
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ ThreadUtils.checkIsOnMainThread();
+ logD("surfaceChanged: format: " + format + " size: " + width + "x" + height);
+ }
+
+ // Update frame dimensions and report any changes to |rendererEvents|.
+ private void updateFrameDimensionsAndReportEvents(VideoRenderer.I420Frame frame) {
+ synchronized (layoutLock) {
+ if (isRenderingPaused) {
+ return;
+ }
+ if (!isFirstFrameRendered) {
+ isFirstFrameRendered = true;
+ logD("Reporting first rendered frame.");
+ if (rendererEvents != null) {
+ rendererEvents.onFirstFrameRendered();
+ }
+ }
+ if (rotatedFrameWidth != frame.rotatedWidth() || rotatedFrameHeight != frame.rotatedHeight()
+ || frameRotation != frame.rotationDegree) {
+ logD("Reporting frame resolution changed to " + frame.width + "x" + frame.height
+ + " with rotation " + frame.rotationDegree);
+ if (rendererEvents != null) {
+ rendererEvents.onFrameResolutionChanged(frame.width, frame.height, frame.rotationDegree);
+ }
+ rotatedFrameWidth = frame.rotatedWidth();
+ rotatedFrameHeight = frame.rotatedHeight();
+ frameRotation = frame.rotationDegree;
+ }
+ }
+ }
+
+ // Update frame dimensions and report any changes to |rendererEvents|.
+ private void updateFrameDimensionsAndReportEvents(VideoFrame frame) {
+ synchronized (layoutLock) {
+ if (isRenderingPaused) {
+ return;
+ }
+ if (!isFirstFrameRendered) {
+ isFirstFrameRendered = true;
+ logD("Reporting first rendered frame.");
+ if (rendererEvents != null) {
+ rendererEvents.onFirstFrameRendered();
+ }
+ }
+ if (rotatedFrameWidth != frame.getRotatedWidth()
+ || rotatedFrameHeight != frame.getRotatedHeight()
+ || frameRotation != frame.getRotation()) {
+ logD("Reporting frame resolution changed to " + frame.getBuffer().getWidth() + "x"
+ + frame.getBuffer().getHeight() + " with rotation " + frame.getRotation());
+ if (rendererEvents != null) {
+ rendererEvents.onFrameResolutionChanged(
+ frame.getBuffer().getWidth(), frame.getBuffer().getHeight(), frame.getRotation());
+ }
+ rotatedFrameWidth = frame.getRotatedWidth();
+ rotatedFrameHeight = frame.getRotatedHeight();
+ frameRotation = frame.getRotation();
+ }
+ }
+ }
+
+ private void logD(String string) {
+ Logging.d(TAG, name + ": " + string);
+ }
+}