Index: webrtc/api/android/java/src/org/webrtc/EglRenderer.java |
diff --git a/webrtc/api/android/java/src/org/webrtc/SurfaceViewRenderer.java b/webrtc/api/android/java/src/org/webrtc/EglRenderer.java |
similarity index 42% |
copy from webrtc/api/android/java/src/org/webrtc/SurfaceViewRenderer.java |
copy to webrtc/api/android/java/src/org/webrtc/EglRenderer.java |
index 5b19c431794602c1c045c74bb2df68124a4f438e..f5b9198c56030cc655ce2949d2d45f36297fceca 100644 |
--- a/webrtc/api/android/java/src/org/webrtc/SurfaceViewRenderer.java |
+++ b/webrtc/api/android/java/src/org/webrtc/EglRenderer.java |
@@ -1,5 +1,5 @@ |
/* |
- * Copyright 2015 The WebRTC project authors. All Rights Reserved. |
+ * Copyright 2016 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 |
@@ -10,35 +10,43 @@ |
package org.webrtc; |
-import android.content.Context; |
-import android.content.res.Resources.NotFoundException; |
-import android.graphics.Point; |
import android.opengl.GLES20; |
import android.os.Handler; |
import android.os.HandlerThread; |
-import android.util.AttributeSet; |
-import android.view.SurfaceHolder; |
-import android.view.SurfaceView; |
- |
+import android.os.Looper; |
+import android.view.Surface; |
import java.util.concurrent.CountDownLatch; |
- |
-import javax.microedition.khronos.egl.EGLContext; |
+import java.util.concurrent.TimeUnit; |
/** |
- * Implements org.webrtc.VideoRenderer.Callbacks by displaying the video stream on a SurfaceView. |
- * renderFrame() is asynchronous to avoid blocking the calling thread. |
- * This class is thread safe and handles access from potentially four different threads: |
- * Interaction from the main app in init, release, setMirror, and setScalingtype. |
- * Interaction from C++ rtc::VideoSinkInterface in renderFrame. |
- * Interaction from the Activity lifecycle in surfaceCreated, surfaceChanged, and surfaceDestroyed. |
- * Interaction with the layout framework in onMeasure and onSizeChanged. |
+ * Implements org.webrtc.VideoRenderer.Callbacks by displaying the video stream on an EGL Surface. |
+ * This class is intended to be used as a helper class for rendering on SurfaceViews and |
+ * TextureViews. |
*/ |
-public class SurfaceViewRenderer |
- extends SurfaceView implements SurfaceHolder.Callback, VideoRenderer.Callbacks { |
- private static final String TAG = "SurfaceViewRenderer"; |
+public class EglRenderer implements VideoRenderer.Callbacks { |
+ private static final String TAG = "EglRenderer"; |
+ private static final int MAX_SURFACE_CLEAR_COUNT = 3; |
+ |
+ private class EglSurfaceCreation implements Runnable { |
+ private Surface surface; |
+ |
+ public synchronized void setSurface(Surface surface) { |
+ this.surface = surface; |
+ } |
+ |
+ @Override |
+ public synchronized void run() { |
+ if (surface != null && eglBase != null && !eglBase.hasSurface()) { |
+ eglBase.createSurface((Surface) surface); |
+ eglBase.makeCurrent(); |
+ // Necessary for YUV frames with odd width. |
+ GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); |
+ } |
+ } |
+ } |
+ |
+ private final String name; |
- // Dedicated render thread. |
- private HandlerThread renderThread; |
// |renderThreadHandler| is a handler for communicating with |renderThread|, and is synchronized |
// on |handlerLock|. |
private final Object handlerLock = new Object(); |
@@ -58,35 +66,11 @@ public class SurfaceViewRenderer |
// These variables are synchronized on |layoutLock|. |
private final Object layoutLock = new Object(); |
- // 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 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. |
- private int rotatedFrameWidth; |
- private int rotatedFrameHeight; |
- private int frameRotation; |
- private final RendererCommon.VideoLayoutMeasure videoLayoutMeasure = |
- new RendererCommon.VideoLayoutMeasure(); |
- |
+ private int surfaceWidth; |
+ private int surfaceHeight; |
+ private float layoutAspectRatio; |
// If true, mirrors the video stream horizontally. |
private boolean mirror; |
- // Callback for reporting renderer events. |
- private RendererCommon.RendererEvents rendererEvents; |
// These variables are synchronized on |statisticsLock|. |
private final Object statisticsLock = new Object(); |
@@ -109,36 +93,15 @@ public class SurfaceViewRenderer |
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(). |
- */ |
- public SurfaceViewRenderer(Context context) { |
- super(context); |
- getHolder().addCallback(this); |
- } |
- |
- /** |
- * Standard View constructor. In order to render something, you must first call init(). |
- */ |
- public SurfaceViewRenderer(Context context, AttributeSet attrs) { |
- super(context, attrs); |
- getHolder().addCallback(this); |
- } |
+ private final EglSurfaceCreation eglSurfaceCreationRunnable = new EglSurfaceCreation(); |
/** |
- * Initialize this class, sharing resources with |sharedContext|. It is allowed to call init() to |
- * reinitialize the renderer after a previous init()/release() cycle. |
+ * Standard constructor. The name will be used for the render thread name and included when |
+ * logging. In order to render something, you must first call init() and createEglSurface. |
*/ |
- public void init(EglBase.Context sharedContext, RendererCommon.RendererEvents rendererEvents) { |
- init(sharedContext, rendererEvents, EglBase.CONFIG_PLAIN, new GlRectDrawer()); |
+ public EglRenderer(String name) { |
+ this.name = name; |
} |
/** |
@@ -147,17 +110,16 @@ public class SurfaceViewRenderer |
* |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, |
+ public void init(final EglBase.Context sharedContext, final int[] configAttributes, |
RendererCommon.GlDrawer drawer) { |
synchronized (handlerLock) { |
if (renderThreadHandler != null) { |
- throw new IllegalStateException(getResourceName() + "Already initialized"); |
+ throw new IllegalStateException(name + "Already initialized"); |
} |
- Logging.d(TAG, getResourceName() + "Initializing."); |
- this.rendererEvents = rendererEvents; |
+ logD("Initializing EglRenderer"); |
this.drawer = drawer; |
- renderThread = new HandlerThread(TAG); |
+ |
+ final HandlerThread renderThread = new HandlerThread(name + "EglRenderer"); |
renderThread.start(); |
renderThreadHandler = new Handler(renderThread.getLooper()); |
// Create EGL context on the newly created render thread. It should be possibly to create the |
@@ -170,28 +132,11 @@ public class SurfaceViewRenderer |
} |
}); |
} |
- tryCreateEglSurface(); |
} |
- /** |
- * Create and make an EGLSurface current if both init() and surfaceCreated() have been called. |
- */ |
- public void tryCreateEglSurface() { |
- // |renderThreadHandler| is only created after |eglBase| is created in init(), so the |
- // following code will only execute if eglBase != null. |
- runOnRenderThread(new Runnable() { |
- @Override |
- public void run() { |
- synchronized (layoutLock) { |
- if (eglBase != null && isSurfaceCreated && !eglBase.hasSurface()) { |
- eglBase.createSurface(getHolder().getSurface()); |
- eglBase.makeCurrent(); |
- // Necessary for YUV frames with odd width. |
- GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); |
- } |
- } |
- } |
- }); |
+ public void createEglSurface(Surface surface) { |
+ eglSurfaceCreationRunnable.setSurface(surface); |
+ runOnRenderThread(eglSurfaceCreationRunnable); |
} |
/** |
@@ -204,52 +149,52 @@ public class SurfaceViewRenderer |
final CountDownLatch eglCleanupBarrier = new CountDownLatch(1); |
synchronized (handlerLock) { |
if (renderThreadHandler == null) { |
- Logging.d(TAG, getResourceName() + "Already released"); |
+ logD("Already released"); |
return; |
} |
// Release EGL and GL resources on render thread. |
- // TODO(magjed): This might not be necessary - all OpenGL resources are automatically deleted |
- // when the EGL context is lost. It might be dangerous to delete them manually in |
- // Activity.onDestroy(). |
renderThreadHandler.postAtFrontOfQueue(new Runnable() { |
@Override |
public void run() { |
- drawer.release(); |
- drawer = null; |
+ if (drawer != null) { |
+ drawer.release(); |
+ drawer = null; |
+ } |
if (yuvTextures != null) { |
GLES20.glDeleteTextures(3, yuvTextures, 0); |
yuvTextures = null; |
} |
- // Clear last rendered image to black. |
- makeBlack(); |
- eglBase.release(); |
- eglBase = null; |
+ if (eglBase != null) { |
+ logD("eglBase detach and release."); |
+ eglBase.detachCurrent(); |
+ eglBase.release(); |
+ eglBase = null; |
+ } |
eglCleanupBarrier.countDown(); |
} |
}); |
+ final Looper renderLooper = renderThreadHandler.getLooper(); |
+ // TODO(magjed): Replace this post() with renderLooper.quitSafely() when API support >= 18. |
+ renderThreadHandler.post(new Runnable() { |
+ @Override |
+ public void run() { |
+ logD("Quitting render thread."); |
+ renderLooper.quit(); |
+ } |
+ }); |
// Don't accept any more frames or messages to the render thread. |
renderThreadHandler = null; |
} |
// Make sure the EGL/GL cleanup posted above is executed. |
ThreadUtils.awaitUninterruptibly(eglCleanupBarrier); |
- renderThread.quit(); |
synchronized (frameLock) { |
if (pendingFrame != null) { |
VideoRenderer.renderFrameDone(pendingFrame); |
pendingFrame = null; |
} |
} |
- // The |renderThread| cleanup is not safe to cancel and we need to wait until it's done. |
- ThreadUtils.joinUninterruptibly(renderThread); |
- renderThread = null; |
- // Reset statistics and event reporting. |
- synchronized (layoutLock) { |
- rotatedFrameWidth = 0; |
- rotatedFrameHeight = 0; |
- frameRotation = 0; |
- rendererEvents = null; |
- } |
resetStatistics(); |
+ logD("Releasing done."); |
} |
/** |
@@ -270,23 +215,21 @@ public class SurfaceViewRenderer |
* Set if the video stream should be mirrored or not. |
*/ |
public void setMirror(final boolean mirror) { |
+ logD("setMirror: " + mirror); |
synchronized (layoutLock) { |
this.mirror = mirror; |
} |
} |
/** |
- * Set how the video will fill the allowed layout area. |
+ * Set layout aspect ratio. This is used to crop frames when rendering to avoid stretched video. |
+ * Set this to 0 to disable cropping. |
*/ |
- public void setScalingType(RendererCommon.ScalingType scalingType) { |
- ThreadUtils.checkIsOnMainThread(); |
- videoLayoutMeasure.setScalingType(scalingType); |
- } |
- |
- public void setScalingType(RendererCommon.ScalingType scalingTypeMatchOrientation, |
- RendererCommon.ScalingType scalingTypeMismatchOrientation) { |
- ThreadUtils.checkIsOnMainThread(); |
- videoLayoutMeasure.setScalingType(scalingTypeMatchOrientation, scalingTypeMismatchOrientation); |
+ public void setLayoutAspectRatio(float layoutAspectRatio) { |
+ logD("setLayoutAspectRatio: " + layoutAspectRatio); |
+ synchronized (layoutLock) { |
+ this.layoutAspectRatio = layoutAspectRatio; |
+ } |
} |
// VideoRenderer.Callbacks interface. |
@@ -297,7 +240,7 @@ public class SurfaceViewRenderer |
} |
synchronized (handlerLock) { |
if (renderThreadHandler == null) { |
- Logging.d(TAG, getResourceName() + "Dropping frame - Not initialized or already released."); |
+ logD("Dropping frame - Not initialized or already released."); |
VideoRenderer.renderFrameDone(frame); |
return; |
} |
@@ -315,78 +258,38 @@ public class SurfaceViewRenderer |
} |
} |
- // View layout interface. |
- @Override |
- protected void onMeasure(int widthSpec, int heightSpec) { |
- ThreadUtils.checkIsOnMainThread(); |
- final boolean isNewSize; |
- synchronized (layoutLock) { |
- desiredLayoutSize = |
- videoLayoutMeasure.measure(widthSpec, heightSpec, rotatedFrameWidth, rotatedFrameHeight); |
- isNewSize = |
- (desiredLayoutSize.x != getMeasuredWidth() || desiredLayoutSize.y != getMeasuredHeight()); |
- setMeasuredDimension(desiredLayoutSize.x, desiredLayoutSize.y); |
- } |
- if (isNewSize) { |
- // 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); |
- } |
+ /** |
+ * Release EGL surface. This function will block until the EGL surface is released. |
+ */ |
+ public void releaseEglSurface() { |
+ // Ensure that the render thread is no longer touching the Surface before returning from this |
+ // function. |
+ eglSurfaceCreationRunnable.setSurface(null /* surface */); |
+ synchronized (handlerLock) { |
+ if (renderThreadHandler != null) { |
+ renderThreadHandler.removeCallbacks(eglSurfaceCreationRunnable); |
+ ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, new Runnable() { |
+ @Override |
+ public void run() { |
+ if (eglBase != null) { |
+ eglBase.detachCurrent(); |
+ eglBase.releaseSurface(); |
+ } |
+ } |
+ }); |
} |
} |
} |
- @Override |
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
- synchronized (layoutLock) { |
- layoutSize.x = right - left; |
- layoutSize.y = bottom - top; |
- } |
- // Might have a pending frame waiting for a layout of correct size. |
- runOnRenderThread(renderFrameRunnable); |
- } |
- |
- // SurfaceHolder.Callback interface. |
- @Override |
- public void surfaceCreated(final SurfaceHolder holder) { |
- Logging.d(TAG, getResourceName() + "Surface created."); |
- synchronized (layoutLock) { |
- isSurfaceCreated = true; |
- } |
- tryCreateEglSurface(); |
- } |
- |
- @Override |
- public void surfaceDestroyed(SurfaceHolder holder) { |
- Logging.d(TAG, getResourceName() + "Surface destroyed."); |
- synchronized (layoutLock) { |
- isSurfaceCreated = false; |
- surfaceSize.x = 0; |
- surfaceSize.y = 0; |
- } |
- runOnRenderThread(new Runnable() { |
- @Override |
- public void run() { |
- if (eglBase != null) { |
- eglBase.detachCurrent(); |
- eglBase.releaseSurface(); |
- } |
- } |
- }); |
- } |
- |
- @Override |
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
- Logging.d(TAG, getResourceName() + "Surface changed: " + width + "x" + height); |
+ /** |
+ * Notify that the surface size has changed. |
+ */ |
+ public void surfaceSizeChanged(int surfaceWidth, int surfaceHeight) { |
+ logD("Surface size changed: " + surfaceWidth + "x" + surfaceHeight); |
synchronized (layoutLock) { |
- surfaceSize.x = width; |
- surfaceSize.y = height; |
+ this.surfaceWidth = surfaceWidth; |
+ this.surfaceHeight = surfaceHeight; |
} |
- // Might have a pending frame waiting for a surface of correct size. |
- runOnRenderThread(renderFrameRunnable); |
} |
/** |
@@ -400,45 +303,19 @@ public class SurfaceViewRenderer |
} |
} |
- private String getResourceName() { |
- try { |
- return getResources().getResourceEntryName(getId()) + ": "; |
- } catch (NotFoundException e) { |
- return ""; |
- } |
- } |
- |
private void makeBlack() { |
- if (Thread.currentThread() != renderThread) { |
- throw new IllegalStateException(getResourceName() + "Wrong thread."); |
- } |
if (eglBase != null && eglBase.hasSurface()) { |
- GLES20.glClearColor(0, 0, 0, 0); |
+ logD("clearSurface"); |
+ GLES20.glClearColor(0 /* red */, 0 /* green */, 0 /* blue */, 0 /* alpha */); |
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) { |
- // Return false while we are in the middle of a layout change. |
- return layoutSize.equals(desiredLayoutSize) && surfaceSize.equals(layoutSize); |
- } |
- } |
- |
- /** |
* Renders and releases |pendingFrame|. |
*/ |
private void renderFrameOnRenderThread() { |
- if (Thread.currentThread() != renderThread) { |
- throw new IllegalStateException(getResourceName() + "Wrong thread."); |
- } |
// Fetch and render |pendingFrame|. |
final VideoRenderer.I420Frame frame; |
synchronized (frameLock) { |
@@ -448,40 +325,43 @@ public class SurfaceViewRenderer |
frame = pendingFrame; |
pendingFrame = null; |
} |
- updateFrameDimensionsAndReportEvents(frame); |
if (eglBase == null || !eglBase.hasSurface()) { |
- Logging.d(TAG, getResourceName() + "No surface to draw on"); |
- VideoRenderer.renderFrameDone(frame); |
- return; |
- } |
- if (!checkConsistentLayout()) { |
- // Output intermediate black frames while the layout is updated. |
- makeBlack(); |
+ logD("Dropping frame - No surface"); |
VideoRenderer.renderFrameDone(frame); |
return; |
} |
+ |
+ final long startTimeNs = System.nanoTime(); |
+ float[] texMatrix = |
+ RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree); |
+ |
// After a surface size change, the EGLSurface might still have a buffer of the old size in the |
// 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() != surfaceSize.x || eglBase.surfaceHeight() != surfaceSize.y) { |
+ int surfaceClearCount = 0; |
+ while (eglBase.surfaceWidth() != surfaceWidth || eglBase.surfaceHeight() != surfaceHeight) { |
+ ++surfaceClearCount; |
+ if (surfaceClearCount > MAX_SURFACE_CLEAR_COUNT) { |
+ logD("Failed to get surface of expected size - dropping frame."); |
+ VideoRenderer.renderFrameDone(frame); |
+ return; |
+ } |
+ logD("Surface size mismatch - clearing surface."); |
makeBlack(); |
} |
+ final float[] layoutMatrix; |
+ if (layoutAspectRatio > 0) { |
+ layoutMatrix = RendererCommon.getLayoutMatrix( |
+ mirror, frame.rotatedWidth() / (float) frame.rotatedHeight(), layoutAspectRatio); |
+ } else { |
+ layoutMatrix = |
+ mirror ? RendererCommon.horizontalFlipMatrix() : RendererCommon.identityMatrix(); |
+ } |
+ texMatrix = RendererCommon.multiplyMatrices(texMatrix, layoutMatrix); |
} |
- final long startTimeNs = System.nanoTime(); |
- final float[] texMatrix; |
- synchronized (layoutLock) { |
- final float[] rotatedSamplingMatrix = |
- RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree); |
- final float[] layoutMatrix = RendererCommon.getLayoutMatrix(mirror, |
- frame.rotatedWidth() / (float) frame.rotatedHeight(), |
- layoutSize.x / (float) layoutSize.y); |
- texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix); |
- } |
- |
- // 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.glClearColor(0 /* red */, 0 /* green */, 0 /* blue */, 0 /* alpha */); |
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); |
if (frame.yuvFrame) { |
// Make sure YUV textures are allocated. |
@@ -494,10 +374,10 @@ public class SurfaceViewRenderer |
yuvUploader.uploadYuvData( |
yuvTextures, frame.width, frame.height, frame.yuvStrides, frame.yuvPlanes); |
drawer.drawYuv(yuvTextures, texMatrix, frame.rotatedWidth(), frame.rotatedHeight(), 0, 0, |
- surfaceSize.x, surfaceSize.y); |
+ surfaceWidth, surfaceHeight); |
} else { |
drawer.drawOes(frame.textureId, texMatrix, frame.rotatedWidth(), frame.rotatedHeight(), 0, 0, |
- surfaceSize.x, surfaceSize.y); |
+ surfaceWidth, surfaceHeight); |
} |
eglBase.swapBuffers(); |
@@ -505,12 +385,6 @@ public class SurfaceViewRenderer |
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); |
@@ -520,40 +394,20 @@ public class SurfaceViewRenderer |
} |
} |
- // Update frame dimensions and report any changes to |rendererEvents|. |
- private void updateFrameDimensionsAndReportEvents(VideoRenderer.I420Frame frame) { |
- synchronized (layoutLock) { |
- if (rotatedFrameWidth != frame.rotatedWidth() || rotatedFrameHeight != frame.rotatedHeight() |
- || frameRotation != frame.rotationDegree) { |
- Logging.d(TAG, getResourceName() + "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; |
- post(new Runnable() { |
- @Override |
- public void run() { |
- requestLayout(); |
- } |
- }); |
- } |
- } |
- } |
- |
private void logStatistics() { |
synchronized (statisticsLock) { |
- Logging.d(TAG, getResourceName() + "Frames received: " + framesReceived + ". Dropped: " |
- + framesDropped + ". Rendered: " + framesRendered); |
+ logD("Frames received: " + framesReceived + ". Dropped: " + framesDropped + ". Rendered: " |
+ + framesRendered); |
if (framesReceived > 0 && framesRendered > 0) { |
final long timeSinceFirstFrameNs = System.nanoTime() - firstFrameTimeNs; |
- Logging.d(TAG, getResourceName() + "Duration: " + (int) (timeSinceFirstFrameNs / 1e6) |
- + " ms. FPS: " + framesRendered * 1e9 / timeSinceFirstFrameNs); |
- Logging.d(TAG, getResourceName() + "Average render time: " |
- + (int) (renderTimeNs / (1000 * framesRendered)) + " us."); |
+ logD("Duration: " + (int) (timeSinceFirstFrameNs / 1e6) + " ms. FPS: " |
+ + framesRendered * 1e9 / timeSinceFirstFrameNs); |
+ logD("Average render time: " + (int) (renderTimeNs / (1000 * framesRendered)) + " us."); |
} |
} |
} |
+ |
+ private void logD(String string) { |
+ Logging.d(TAG, name + string); |
+ } |
} |