Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2)

Side by Side Diff: talk/app/webrtc/java/android/org/webrtc/SurfaceViewRenderer.java

Issue 1308223002: Android: Add new renderer SurfaceViewRenderer (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: add TODO to investigate how to release() safely Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | talk/libjingle.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * libjingle
3 * Copyright 2015 Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 package org.webrtc;
29
30 import java.nio.ByteBuffer;
31
32 import android.content.Context;
33 import android.graphics.Point;
34 import android.graphics.SurfaceTexture;
35 import android.opengl.EGLContext;
36 import android.opengl.GLES20;
37 import android.opengl.Matrix;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.util.AttributeSet;
41 import android.util.Log;
42 import android.view.SurfaceHolder;
43 import android.view.SurfaceView;
44
45 /**
46 * Implements org.webrtc.VideoRenderer.Callbacks by displaying the video stream on a SurfaceView.
47 * renderFrame() is asynchronous to avoid blocking the calling thread.
48 * This class is thread safe and handles access from potentially four different threads:
49 * Interaction from the main app in init, release, setMirror, and setScalingtype .
50 * Interaction from C++ webrtc::VideoRendererInterface in renderFrame and canApp lyRotation.
51 * Interaction from the Activity lifecycle in surfaceCreated, surfaceChanged, an d surfaceDestroyed.
52 * Interaction with the layout framework in onMeasure and onSizeChanged.
53 */
54 public class SurfaceViewRenderer extends SurfaceView
55 implements SurfaceHolder.Callback, VideoRenderer.Callbacks {
56 private static final String TAG = "SurfaceViewRenderer";
57
58 // These variables are synchronized on |threadLock|.
59 private final Object threadLock = new Object();
60 // Dedicated render thread.
61 private HandlerThread renderThread;
62 // Handler for inter-thread communication.
63 private Handler renderThreadHandler;
64
65 // EGL and GL resources for drawing YUV/OES textures. After initilization, the se are only accessed
66 // from the render thread.
67 private EglBase eglBase;
68 private GlRectDrawer drawer;
69 // Texture ids for YUV frames. Allocated on first arrival of a YUV frame.
70 private int[] yuvTextures = null;
71
72 // Pending frame to render. Serves as a queue with size 1. Synchronized on |fr ameLock|.
73 private final Object frameLock = new Object();
74 private VideoRenderer.I420Frame pendingFrame;
75
76 // These variables are synchronized on |layoutLock|.
77 private final Object layoutLock = new Object();
78 // These three different dimension values are used to keep track of the state in these functions:
79 // requestLayout() -> onMeasure() -> onLayout() -> surfaceChanged().
80 // requestLayout() is triggered internally by frame size changes, but can also be triggered
81 // externally by layout update requests.
82 // Most recent measurement specification from onMeasure().
83 private int widthSpec;
84 private int heightSpec;
85 // Current size on screen in pixels. Updated in onLayout(), and should be cons istent with
86 // |widthSpec|/|heightSpec| after that.
87 private int layoutWidth;
88 private int layoutHeight;
89 // Current surface size of the underlying Surface. Updated in surfaceChanged() , and should be
90 // consistent with |layoutWidth|/|layoutHeight| after that.
91 // TODO(magjed): Enable hardware scaler with SurfaceHolder.setFixedSize(). Thi s will decouple
92 // layout and surface size.
93 private int surfaceWidth;
94 private int surfaceHeight;
95 // Last rendered frame dimensions, or 0 if no frame has been rendered yet.
96 private int frameWidth;
97 private int frameHeight;
98 private int frameRotation;
99 // |scalingType| determines how the video will fill the allowed layout area in onMeasure().
100 private RendererCommon.ScalingType scalingType = RendererCommon.ScalingType.SC ALE_ASPECT_BALANCED;
101 // If true, mirrors the video stream horizontally.
102 private boolean mirror;
103 // Callback for reporting renderer events.
104 private RendererCommon.RendererEvents rendererEvents;
105
106 // These variables are synchronized on |statisticsLock|.
107 private final Object statisticsLock = new Object();
108 // Total number of video frames received in renderFrame() call.
109 private int framesReceived;
110 // Number of video frames dropped by renderFrame() because previous frame has not been rendered
111 // yet.
112 private int framesDropped;
113 // Number of rendered video frames.
114 private int framesRendered;
115 // Time in ns when the first video frame was rendered.
116 private long firstFrameTimeNs;
117 // Time in ns spent in renderFrameOnRenderThread() function.
118 private long renderTimeNs;
119
120 // Runnable for posting frames to render thread..
121 private final Runnable renderFrameRunnable = new Runnable() {
122 @Override public void run() {
123 renderFrameOnRenderThread();
124 }
125 };
126
127 /**
128 * Standard View constructor. In order to render something, you must first cal l init().
129 */
130 public SurfaceViewRenderer(Context context) {
131 super(context);
132 }
133
134 /**
135 * Standard View constructor. In order to render something, you must first cal l init().
136 */
137 public SurfaceViewRenderer(Context context, AttributeSet attrs) {
138 super(context, attrs);
139 }
140
141 /**
142 * Initialize this class, sharing resources with |sharedContext|.
143 */
144 public void init(
145 EGLContext sharedContext, RendererCommon.RendererEvents rendererEvents) {
146 if (renderThreadHandler != null) {
147 throw new IllegalStateException("Already initialized");
148 }
149 Log.d(TAG, "Initializing");
150 this.rendererEvents = rendererEvents;
151 renderThread = new HandlerThread(TAG);
152 renderThread.start();
153 renderThreadHandler = new Handler(renderThread.getLooper());
154 eglBase = new EglBase(sharedContext, EglBase.ConfigType.PLAIN);
155 drawer = new GlRectDrawer();
156 getHolder().addCallback(this);
157 }
158
159 /**
160 * Release all resources. This needs to be done manually, otherwise the resour ces are leaked. You
161 * should call this before the Activity is destroyed, while the EGLContext is still valid.
162 */
163 public void release() {
164 synchronized (threadLock) {
165 if (renderThreadHandler == null) {
166 Log.d(TAG, "Already released");
167 return;
168 }
169 // Release EGL and GL resources on render thread.
170 // TODO(magjed): This might not be necessary - all OpenGL resources are au tomatically deleted
171 // when the EGL context is lost. It might be dangerous to delete them manu ally in
172 // Activity.onDestroy().
173 renderThreadHandler.post(new Runnable() {
174 @Override public void run() {
175 drawer.release();
176 drawer = null;
177 if (yuvTextures != null) {
178 GLES20.glDeleteTextures(3, yuvTextures, 0);
179 yuvTextures = null;
180 }
181 eglBase.release();
182 eglBase = null;
183 }
184 });
185 // Don't accept any more messages to the render thread.
186 renderThreadHandler = null;
187 // Quit safely to make sure the EGL/GL cleanup posted above is executed.
188 renderThread.quitSafely();
189 renderThread = null;
190 }
191 getHolder().removeCallback(this);
192 synchronized (frameLock) {
193 if (pendingFrame != null) {
194 VideoRenderer.renderFrameDone(pendingFrame);
195 pendingFrame = null;
196 }
197 }
198 }
199
200 /**
201 * Set if the video stream should be mirrored or not.
202 */
203 public void setMirror(final boolean mirror) {
204 synchronized (layoutLock) {
205 this.mirror = mirror;
206 }
207 }
208
209 /**
210 * Set how the video will fill the allowed layout area.
211 */
212 public void setScalingType(RendererCommon.ScalingType scalingType) {
213 synchronized (layoutLock) {
214 this.scalingType = scalingType;
215 }
216 }
217
218 // VideoRenderer.Callbacks interface.
219 @Override
220 public void renderFrame(VideoRenderer.I420Frame frame) {
221 synchronized (statisticsLock) {
222 ++framesReceived;
223 }
224 synchronized (threadLock) {
225 if (renderThreadHandler == null) {
226 Log.d(TAG, "Dropping frame - SurfaceViewRenderer not initialized or alre ady released.");
227 } else {
228 synchronized (frameLock) {
229 if (pendingFrame == null) {
230 updateFrameDimensionsAndReportEvents(frame);
231 pendingFrame = frame;
232 renderThreadHandler.post(renderFrameRunnable);
233 return;
234 }
235 }
236 }
237 }
238 // Drop frame.
239 synchronized (statisticsLock) {
240 ++framesDropped;
241 }
242 VideoRenderer.renderFrameDone(frame);
243 }
244
245 // Returns desired layout size given current measure specification and video a spect ratio.
246 private Point getDesiredLayoutSize() {
247 synchronized (layoutLock) {
248 final int maxWidth = getDefaultSize(Integer.MAX_VALUE, widthSpec);
249 final int maxHeight = getDefaultSize(Integer.MAX_VALUE, heightSpec);
250 final Point size =
251 RendererCommon.getDisplaySize(scalingType, frameAspectRatio(), maxWidt h, maxHeight);
252 if (MeasureSpec.getMode(widthSpec) == MeasureSpec.EXACTLY) {
253 size.x = maxWidth;
254 }
255 if (MeasureSpec.getMode(heightSpec) == MeasureSpec.EXACTLY) {
256 size.y = maxHeight;
257 }
258 return size;
259 }
260 }
261
262 // View layout interface.
263 @Override
264 protected void onMeasure(int widthSpec, int heightSpec) {
265 synchronized (layoutLock) {
266 this.widthSpec = widthSpec;
267 this.heightSpec = heightSpec;
268 }
269 final Point size = getDesiredLayoutSize();
270 setMeasuredDimension(size.x, size.y);
271 }
272
273 @Override
274 protected void onLayout(boolean changed, int left, int top, int right, int bot tom) {
275 synchronized (layoutLock) {
276 layoutWidth = right - left;
277 layoutHeight = bottom - top;
278 }
279 // Might have a pending frame waiting for a layout of correct size.
280 runOnRenderThread(renderFrameRunnable);
281 }
282
283 // SurfaceHolder.Callback interface.
284 @Override
285 public void surfaceCreated(final SurfaceHolder holder) {
286 Log.d(TAG, "Surface created");
287 runOnRenderThread(new Runnable() {
288 @Override public void run() {
289 eglBase.createSurface(holder.getSurface());
290 eglBase.makeCurrent();
291 // Necessary for YUV frames with odd width.
292 GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
293 }
294 });
295 }
296
297 @Override
298 public void surfaceDestroyed(SurfaceHolder holder) {
299 Log.d(TAG, "Surface destroyed");
300 synchronized (layoutLock) {
301 surfaceWidth = 0;
302 surfaceHeight = 0;
303 }
304 runOnRenderThread(new Runnable() {
305 @Override public void run() {
306 eglBase.releaseSurface();
307 }
308 });
309 }
310
311 @Override
312 public void surfaceChanged(SurfaceHolder holder, int format, int width, int he ight) {
313 Log.d(TAG, "Surface changed: " + width + "x" + height);
314 synchronized (layoutLock) {
315 surfaceWidth = width;
316 surfaceHeight = height;
317 }
318 // Might have a pending frame waiting for a surface of correct size.
319 runOnRenderThread(renderFrameRunnable);
320 }
321
322 /**
323 * Private helper function to post tasks safely.
324 */
325 private void runOnRenderThread(Runnable runnable) {
326 synchronized (threadLock) {
327 if (renderThreadHandler != null) {
328 renderThreadHandler.post(runnable);
329 }
330 }
331 }
332
333 /**
334 * Requests new layout if necessary. Returns true if layout and surface size a re consistent.
335 */
336 private boolean checkConsistentLayout() {
337 synchronized (layoutLock) {
338 final Point desiredLayoutSize = getDesiredLayoutSize();
339 if (desiredLayoutSize.x != layoutWidth || desiredLayoutSize.y != layoutHei ght) {
340 Log.d(TAG, "Requesting new layout with size: "
341 + desiredLayoutSize.x + "x" + desiredLayoutSize.y);
342 // Request layout update on UI thread.
343 post(new Runnable() {
344 @Override public void run() {
345 requestLayout();
346 }
347 });
348 return false;
349 }
350 // Wait for requestLayout() to propagate through this sequence before retu rning true:
351 // requestLayout() -> onMeasure() -> onLayout() -> surfaceChanged().
352 return surfaceWidth == layoutWidth && surfaceHeight == layoutHeight;
353 }
354 }
355
356 /**
357 * Renders and releases |pendingFrame|.
358 */
359 private void renderFrameOnRenderThread() {
360 if (eglBase == null || !eglBase.hasSurface()) {
361 Log.d(TAG, "No surface to draw on");
362 return;
363 }
364 if (!checkConsistentLayout()) {
365 // Output intermediate black frames while the layout is updated.
366 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
367 eglBase.swapBuffers();
368 return;
369 }
370 // After a surface size change, the EGLSurface might still have a buffer of the old size in the
371 // pipeline. Querying the EGLSurface will show if the underlying buffer dime nsions haven't yet
372 // changed. Such a buffer will be rendered incorrectly, so flush it with a b lack frame.
373 synchronized (layoutLock) {
374 if (eglBase.surfaceWidth() != surfaceWidth || eglBase.surfaceHeight() != s urfaceHeight) {
375 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
376 eglBase.swapBuffers();
377 }
378 }
379 // Fetch and render |pendingFrame|.
380 final VideoRenderer.I420Frame frame;
381 synchronized (frameLock) {
382 if (pendingFrame == null) {
383 return;
384 }
385 frame = pendingFrame;
386 pendingFrame = null;
387 }
388
389 final long startTimeNs = System.nanoTime();
390 if (!frame.yuvFrame) {
391 // TODO(magjed): Move updateTexImage() to the video source instead.
392 SurfaceTexture surfaceTexture = (SurfaceTexture) frame.textureObject;
393 surfaceTexture.updateTexImage();
394 }
395
396 final float[] texMatrix = new float[16];
397 synchronized (layoutLock) {
398 final float[] samplingMatrix = RendererCommon.getSamplingMatrix(
399 (SurfaceTexture) frame.textureObject, frame.rotationDegree);
400 final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
401 mirror, frameAspectRatio(), (float) layoutWidth / layoutHeight);
402 Matrix.multiplyMM(texMatrix, 0, samplingMatrix, 0, layoutMatrix, 0);
403 }
404
405 GLES20.glViewport(0, 0, surfaceWidth, surfaceHeight);
406 if (frame.yuvFrame) {
407 // Make sure YUV textures are allocated.
408 if (yuvTextures == null) {
409 yuvTextures = new int[3];
410 for (int i = 0; i < 3; i++) {
411 yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
412 }
413 }
414 drawer.uploadYuvData(
415 yuvTextures, frame.width, frame.height, frame.yuvStrides, frame.yuvPla nes);
416 drawer.drawYuv(yuvTextures, texMatrix);
417 } else {
418 drawer.drawOes(frame.textureId, texMatrix);
419 }
420
421 eglBase.swapBuffers();
422 VideoRenderer.renderFrameDone(frame);
423 synchronized (statisticsLock) {
424 if (framesRendered == 0) {
425 firstFrameTimeNs = startTimeNs;
426 }
427 ++framesRendered;
428 renderTimeNs += (System.nanoTime() - startTimeNs);
429 if (framesRendered % 300 == 0) {
430 logStatistics();
431 }
432 }
433 }
434
435 // Return current frame aspect ratio, taking rotation into account.
436 private float frameAspectRatio() {
437 synchronized (layoutLock) {
438 if (frameWidth == 0 || frameHeight == 0) {
439 return 0.0f;
440 }
441 return (frameRotation % 180 == 0) ? (float) frameWidth / frameHeight
442 : (float) frameHeight / frameWidth;
443 }
444 }
445
446 // Update frame dimensions and report any changes to |rendererEvents|.
447 private void updateFrameDimensionsAndReportEvents(VideoRenderer.I420Frame fram e) {
448 synchronized (layoutLock) {
449 if (frameWidth != frame.width || frameHeight != frame.height
450 || frameRotation != frame.rotationDegree) {
451 if (rendererEvents != null) {
452 final String id = getResources().getResourceEntryName(getId());
453 if (frameWidth == 0 || frameHeight == 0) {
454 Log.d(TAG, "ID: " + id + ". Reporting first rendered frame.");
455 rendererEvents.onFirstFrameRendered();
456 }
457 Log.d(TAG, "ID: " + id + ". Reporting frame resolution changed to "
458 + frame.width + "x" + frame.height + " with rotation " + frame.rot ationDegree);
459 rendererEvents.onFrameResolutionChanged(frame.width, frame.height, fra me.rotationDegree);
460 }
461 frameWidth = frame.width;
462 frameHeight = frame.height;
463 frameRotation = frame.rotationDegree;
464 }
465 }
466 }
467
468 private void logStatistics() {
469 synchronized (statisticsLock) {
470 Log.d(TAG, "ID: " + getResources().getResourceEntryName(getId()) + ". Fram es received: "
471 + framesReceived + ". Dropped: " + framesDropped + ". Rendered: " + fr amesRendered);
472 if (framesReceived > 0 && framesRendered > 0) {
473 final long timeSinceFirstFrameNs = System.nanoTime() - firstFrameTimeNs;
474 Log.d(TAG, "Duration: " + (int) (timeSinceFirstFrameNs / 1e6) +
475 " ms. FPS: " + (float) framesRendered * 1e9 / timeSinceFirstFrameNs) ;
476 Log.d(TAG, "Average render time: "
477 + (int) (renderTimeNs / (1000 * framesRendered)) + " us.");
478 }
479 }
480 }
481 }
OLDNEW
« no previous file with comments | « no previous file | talk/libjingle.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698