| Index: talk/app/webrtc/java/android/org/webrtc/SurfaceViewRenderer.java
|
| diff --git a/talk/app/webrtc/java/android/org/webrtc/SurfaceViewRenderer.java b/talk/app/webrtc/java/android/org/webrtc/SurfaceViewRenderer.java
|
| index 1d071129f0b6d9ef631c0444ebeb9894af5fc44d..170827efc11acd34726b9fe12ca688d6aa6162a1 100644
|
| --- a/talk/app/webrtc/java/android/org/webrtc/SurfaceViewRenderer.java
|
| +++ b/talk/app/webrtc/java/android/org/webrtc/SurfaceViewRenderer.java
|
| @@ -76,23 +76,22 @@ public class SurfaceViewRenderer extends SurfaceView
|
|
|
| // These variables are synchronized on |layoutLock|.
|
| private final Object layoutLock = new Object();
|
| - // These three different dimension values are used to keep track of the state in these functions:
|
| - // requestLayout() -> onMeasure() -> onLayout() -> surfaceChanged().
|
| - // requestLayout() is triggered internally by frame size changes, but can also be triggered
|
| - // externally by layout update requests.
|
| - // Most recent measurement specification from onMeasure().
|
| - private int widthSpec;
|
| - private int heightSpec;
|
| - // Current size on screen in pixels. Updated in onLayout(), and should be consistent with
|
| - // |widthSpec|/|heightSpec| after that.
|
| - private int layoutWidth;
|
| - private int layoutHeight;
|
| - // Current surface size of the underlying Surface. Updated in surfaceChanged(), and should be
|
| - // consistent with |layoutWidth|/|layoutHeight| after that.
|
| + // These dimension values are used to keep track of the state in these functions: onMeasure(),
|
| + // onLayout(), and surfaceChanged(). A new layout is triggered with requestLayout(). This happens
|
| + // internally when the incoming frame size changes. requestLayout() can also be triggered
|
| + // externally. The layout change is a two pass process: first onMeasure() is called in a top-down
|
| + // traversal of the View tree, followed by an onLayout() pass that is also top-down. During the
|
| + // onLayout() pass, each parent is responsible for positioning its children using the sizes
|
| + // computed in the measure pass.
|
| + // |desiredLayoutsize| is the layout size we have requested in onMeasure() and are waiting for to
|
| + // take effect.
|
| + private Point desiredLayoutSize = new Point();
|
| + // |layoutSize|/|surfaceSize| is the actual current layout/surface size. They are updated in
|
| + // onLayout() and surfaceChanged() respectively.
|
| + private final Point layoutSize = new Point();
|
| // TODO(magjed): Enable hardware scaler with SurfaceHolder.setFixedSize(). This will decouple
|
| // layout and surface size.
|
| - private int surfaceWidth;
|
| - private int surfaceHeight;
|
| + private final Point surfaceSize = new Point();
|
| // |isSurfaceCreated| keeps track of the current status in surfaceCreated()/surfaceDestroyed().
|
| private boolean isSurfaceCreated;
|
| // Last rendered frame dimensions, or 0 if no frame has been rendered yet.
|
| @@ -120,12 +119,18 @@ public class SurfaceViewRenderer extends SurfaceView
|
| // Time in ns spent in renderFrameOnRenderThread() function.
|
| private long renderTimeNs;
|
|
|
| - // Runnable for posting frames to render thread..
|
| + // Runnable for posting frames to render thread.
|
| private final Runnable renderFrameRunnable = new Runnable() {
|
| @Override public void run() {
|
| renderFrameOnRenderThread();
|
| }
|
| };
|
| + // Runnable for clearing Surface to black.
|
| + private final Runnable makeBlackRunnable = new Runnable() {
|
| + @Override public void run() {
|
| + makeBlack();
|
| + }
|
| + };
|
|
|
| /**
|
| * Standard View constructor. In order to render something, you must first call init().
|
| @@ -209,10 +214,8 @@ public class SurfaceViewRenderer extends SurfaceView
|
| GLES20.glDeleteTextures(3, yuvTextures, 0);
|
| yuvTextures = null;
|
| }
|
| - if (eglBase.hasSurface()) {
|
| - // Clear last rendered image to black.
|
| - makeBlack();
|
| - }
|
| + // Clear last rendered image to black.
|
| + makeBlack();
|
| eglBase.release();
|
| eglBase = null;
|
| eglCleanupBarrier.countDown();
|
| @@ -296,7 +299,7 @@ public class SurfaceViewRenderer extends SurfaceView
|
| }
|
|
|
| // Returns desired layout size given current measure specification and video aspect ratio.
|
| - private Point getDesiredLayoutSize() {
|
| + private Point getDesiredLayoutSize(int widthSpec, int heightSpec) {
|
| synchronized (layoutLock) {
|
| final int maxWidth = getDefaultSize(Integer.MAX_VALUE, widthSpec);
|
| final int maxHeight = getDefaultSize(Integer.MAX_VALUE, heightSpec);
|
| @@ -316,18 +319,30 @@ public class SurfaceViewRenderer extends SurfaceView
|
| @Override
|
| protected void onMeasure(int widthSpec, int heightSpec) {
|
| synchronized (layoutLock) {
|
| - this.widthSpec = widthSpec;
|
| - this.heightSpec = heightSpec;
|
| - final Point size = getDesiredLayoutSize();
|
| - setMeasuredDimension(size.x, size.y);
|
| + if (frameWidth == 0 || frameHeight == 0) {
|
| + super.onMeasure(widthSpec, heightSpec);
|
| + return;
|
| + }
|
| + desiredLayoutSize = getDesiredLayoutSize(widthSpec, heightSpec);
|
| + if (desiredLayoutSize.x != getMeasuredWidth() || desiredLayoutSize.y != getMeasuredHeight()) {
|
| + // Clear the surface asap before the layout change to avoid stretched video and other
|
| + // render artifacs. Don't wait for it to finish because the IO thread should never be
|
| + // blocked, so it's a best-effort attempt.
|
| + synchronized (handlerLock) {
|
| + if (renderThreadHandler != null) {
|
| + renderThreadHandler.postAtFrontOfQueue(makeBlackRunnable);
|
| + }
|
| + }
|
| + }
|
| + setMeasuredDimension(desiredLayoutSize.x, desiredLayoutSize.y);
|
| }
|
| }
|
|
|
| @Override
|
| protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
| synchronized (layoutLock) {
|
| - layoutWidth = right - left;
|
| - layoutHeight = bottom - top;
|
| + layoutSize.x = right - left;
|
| + layoutSize.y = bottom - top;
|
| }
|
| // Might have a pending frame waiting for a layout of correct size.
|
| runOnRenderThread(renderFrameRunnable);
|
| @@ -348,8 +363,8 @@ public class SurfaceViewRenderer extends SurfaceView
|
| Logging.d(TAG, getResourceName() + "Surface destroyed.");
|
| synchronized (layoutLock) {
|
| isSurfaceCreated = false;
|
| - surfaceWidth = 0;
|
| - surfaceHeight = 0;
|
| + surfaceSize.x = 0;
|
| + surfaceSize.y = 0;
|
| }
|
| runOnRenderThread(new Runnable() {
|
| @Override public void run() {
|
| @@ -362,8 +377,8 @@ public class SurfaceViewRenderer extends SurfaceView
|
| public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
| Logging.d(TAG, getResourceName() + "Surface changed: " + width + "x" + height);
|
| synchronized (layoutLock) {
|
| - surfaceWidth = width;
|
| - surfaceHeight = height;
|
| + surfaceSize.x = width;
|
| + surfaceSize.y = height;
|
| }
|
| // Might have a pending frame waiting for a surface of correct size.
|
| runOnRenderThread(renderFrameRunnable);
|
| @@ -392,31 +407,23 @@ public class SurfaceViewRenderer extends SurfaceView
|
| if (Thread.currentThread() != renderThread) {
|
| throw new IllegalStateException(getResourceName() + "Wrong thread.");
|
| }
|
| - GLES20.glClearColor(0, 0, 0, 0);
|
| - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
| - eglBase.swapBuffers();
|
| + if (eglBase != null && eglBase.hasSurface()) {
|
| + GLES20.glClearColor(0, 0, 0, 0);
|
| + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
| + eglBase.swapBuffers();
|
| + }
|
| }
|
|
|
| /**
|
| * Requests new layout if necessary. Returns true if layout and surface size are consistent.
|
| */
|
| private boolean checkConsistentLayout() {
|
| + if (Thread.currentThread() != renderThread) {
|
| + throw new IllegalStateException(getResourceName() + "Wrong thread.");
|
| + }
|
| synchronized (layoutLock) {
|
| - final Point desiredLayoutSize = getDesiredLayoutSize();
|
| - if (desiredLayoutSize.x != layoutWidth || desiredLayoutSize.y != layoutHeight) {
|
| - Logging.d(TAG, getResourceName() + "Requesting new layout with size: "
|
| - + desiredLayoutSize.x + "x" + desiredLayoutSize.y);
|
| - // Request layout update on UI thread.
|
| - post(new Runnable() {
|
| - @Override public void run() {
|
| - requestLayout();
|
| - }
|
| - });
|
| - return false;
|
| - }
|
| - // Wait for requestLayout() to propagate through this sequence before returning true:
|
| - // requestLayout() -> onMeasure() -> onLayout() -> surfaceChanged().
|
| - return surfaceWidth == layoutWidth && surfaceHeight == layoutHeight;
|
| + // Return false while we are in the middle of a layout change.
|
| + return layoutSize.equals(desiredLayoutSize) && surfaceSize.equals(layoutSize);
|
| }
|
| }
|
|
|
| @@ -451,7 +458,7 @@ public class SurfaceViewRenderer extends SurfaceView
|
| // pipeline. Querying the EGLSurface will show if the underlying buffer dimensions haven't yet
|
| // changed. Such a buffer will be rendered incorrectly, so flush it with a black frame.
|
| synchronized (layoutLock) {
|
| - if (eglBase.surfaceWidth() != surfaceWidth || eglBase.surfaceHeight() != surfaceHeight) {
|
| + if (eglBase.surfaceWidth() != surfaceSize.x || eglBase.surfaceHeight() != surfaceSize.y) {
|
| makeBlack();
|
| }
|
| }
|
| @@ -462,11 +469,11 @@ public class SurfaceViewRenderer extends SurfaceView
|
| final float[] rotatedSamplingMatrix =
|
| RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
|
| final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
|
| - mirror, frameAspectRatio(), (float) layoutWidth / layoutHeight);
|
| + mirror, frameAspectRatio(), (float) layoutSize.x / layoutSize.y);
|
| texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);
|
| }
|
|
|
| - GLES20.glViewport(0, 0, surfaceWidth, surfaceHeight);
|
| + GLES20.glViewport(0, 0, surfaceSize.x, surfaceSize.y);
|
| // TODO(magjed): glClear() shouldn't be necessary since every pixel is covered anyway, but it's
|
| // a workaround for bug 5147. Performance will be slightly worse.
|
| GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
| @@ -490,6 +497,12 @@ public class SurfaceViewRenderer extends SurfaceView
|
| synchronized (statisticsLock) {
|
| if (framesRendered == 0) {
|
| firstFrameTimeNs = startTimeNs;
|
| + synchronized (layoutLock) {
|
| + Logging.d(TAG, getResourceName() + "Reporting first rendered frame.");
|
| + if (rendererEvents != null) {
|
| + rendererEvents.onFirstFrameRendered();
|
| + }
|
| + }
|
| }
|
| ++framesRendered;
|
| renderTimeNs += (System.nanoTime() - startTimeNs);
|
| @@ -515,18 +528,19 @@ public class SurfaceViewRenderer extends SurfaceView
|
| synchronized (layoutLock) {
|
| if (frameWidth != frame.width || frameHeight != frame.height
|
| || frameRotation != frame.rotationDegree) {
|
| + Logging.d(TAG, getResourceName() + "Reporting frame resolution changed to "
|
| + + frame.width + "x" + frame.height + " with rotation " + frame.rotationDegree);
|
| if (rendererEvents != null) {
|
| - if (frameWidth == 0 || frameHeight == 0) {
|
| - Logging.d(TAG, getResourceName() + "Reporting first rendered frame.");
|
| - rendererEvents.onFirstFrameRendered();
|
| - }
|
| - Logging.d(TAG, getResourceName() + "Reporting frame resolution changed to "
|
| - + frame.width + "x" + frame.height + " with rotation " + frame.rotationDegree);
|
| rendererEvents.onFrameResolutionChanged(frame.width, frame.height, frame.rotationDegree);
|
| }
|
| frameWidth = frame.width;
|
| frameHeight = frame.height;
|
| frameRotation = frame.rotationDegree;
|
| + post(new Runnable() {
|
| + @Override public void run() {
|
| + requestLayout();
|
| + }
|
| + });
|
| }
|
| }
|
| }
|
|
|