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);
+  }
+}