1 /*
2  * libjingle
3  * Copyright 2014 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.util.ArrayList;
31 import java.util.concurrent.CountDownLatch;
32 
33 import javax.microedition.khronos.egl.EGLConfig;
34 import javax.microedition.khronos.egl.EGL10;
35 import javax.microedition.khronos.egl.EGLContext;
36 import javax.microedition.khronos.opengles.GL10;
37 
38 import android.annotation.SuppressLint;
39 import android.graphics.Point;
40 import android.graphics.Rect;
41 import android.opengl.EGL14;
42 import android.opengl.GLES20;
43 import android.opengl.GLSurfaceView;
44 
45 import org.webrtc.Logging;
46 import org.webrtc.VideoRenderer.I420Frame;
47 
48 /**
49  * Efficiently renders YUV frames using the GPU for CSC.
50  * Clients will want first to call setView() to pass GLSurfaceView
51  * and then for each video stream either create instance of VideoRenderer using
52  * createGui() call or VideoRenderer.Callbacks interface using create() call.
53  * Only one instance of the class can be created.
54  */
55 public class VideoRendererGui implements GLSurfaceView.Renderer {
56   // |instance|, |instance.surface|, |eglContext|, and |eglContextReady| are synchronized on
57   // |VideoRendererGui.class|.
58   private static VideoRendererGui instance = null;
59   private static Runnable eglContextReady = null;
60   private static final String TAG = "VideoRendererGui";
61   private GLSurfaceView surface;
62   private static EglBase.Context eglContext = null;
63   // Indicates if SurfaceView.Renderer.onSurfaceCreated was called.
64   // If true then for every newly created yuv image renderer createTexture()
65   // should be called. The variable is accessed on multiple threads and
66   // all accesses are synchronized on yuvImageRenderers' object lock.
67   private boolean onSurfaceCreatedCalled;
68   private int screenWidth;
69   private int screenHeight;
70   // List of yuv renderers.
71   private final ArrayList<YuvImageRenderer> yuvImageRenderers;
72   // Render and draw threads.
73   private static Thread renderFrameThread;
74   private static Thread drawThread;
75 
VideoRendererGui(GLSurfaceView surface)76   private VideoRendererGui(GLSurfaceView surface) {
77     this.surface = surface;
78     // Create an OpenGL ES 2.0 context.
79     surface.setPreserveEGLContextOnPause(true);
80     surface.setEGLContextClientVersion(2);
81     surface.setRenderer(this);
82     surface.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
83 
84     yuvImageRenderers = new ArrayList<YuvImageRenderer>();
85   }
86 
87   /**
88    * Class used to display stream of YUV420 frames at particular location
89    * on a screen. New video frames are sent to display using renderFrame()
90    * call.
91    */
92   private static class YuvImageRenderer implements VideoRenderer.Callbacks {
93     // |surface| is synchronized on |this|.
94     private GLSurfaceView surface;
95     private int id;
96     // TODO(magjed): Delete GL resources in release(). Must be synchronized with draw(). We are
97     // currently leaking resources to avoid a rare crash in release() where the EGLContext has
98     // become invalid beforehand.
99     private int[] yuvTextures = { 0, 0, 0 };
100     private final RendererCommon.YuvUploader yuvUploader = new RendererCommon.YuvUploader();
101     private final RendererCommon.GlDrawer drawer;
102     // Resources for making a deep copy of incoming OES texture frame.
103     private GlTextureFrameBuffer textureCopy;
104 
105     // Pending frame to render. Serves as a queue with size 1. |pendingFrame| is accessed by two
106     // threads - frames are received in renderFrame() and consumed in draw(). Frames are dropped in
107     // renderFrame() if the previous frame has not been rendered yet.
108     private I420Frame pendingFrame;
109     private final Object pendingFrameLock = new Object();
110     // Type of video frame used for recent frame rendering.
111     private static enum RendererType { RENDERER_YUV, RENDERER_TEXTURE };
112     private RendererType rendererType;
113     private RendererCommon.ScalingType scalingType;
114     private boolean mirror;
115     private RendererCommon.RendererEvents rendererEvents;
116     // Flag if renderFrame() was ever called.
117     boolean seenFrame;
118     // Total number of video frames received in renderFrame() call.
119     private int framesReceived;
120     // Number of video frames dropped by renderFrame() because previous
121     // frame has not been rendered yet.
122     private int framesDropped;
123     // Number of rendered video frames.
124     private int framesRendered;
125     // Time in ns when the first video frame was rendered.
126     private long startTimeNs = -1;
127     // Time in ns spent in draw() function.
128     private long drawTimeNs;
129     // Time in ns spent in draw() copying resources from |pendingFrame| - including uploading frame
130     // data to rendering planes.
131     private long copyTimeNs;
132     // The allowed view area in percentage of screen size.
133     private final Rect layoutInPercentage;
134     // The actual view area in pixels. It is a centered subrectangle of the rectangle defined by
135     // |layoutInPercentage|.
136     private final Rect displayLayout = new Rect();
137     // Cached layout transformation matrix, calculated from current layout parameters.
138     private float[] layoutMatrix;
139     // Flag if layout transformation matrix update is needed.
140     private boolean updateLayoutProperties;
141     // Layout properties update lock. Guards |updateLayoutProperties|, |screenWidth|,
142     // |screenHeight|, |videoWidth|, |videoHeight|, |rotationDegree|, |scalingType|, and |mirror|.
143     private final Object updateLayoutLock = new Object();
144     // Texture sampling matrix.
145     private float[] rotatedSamplingMatrix;
146     // Viewport dimensions.
147     private int screenWidth;
148     private int screenHeight;
149     // Video dimension.
150     private int videoWidth;
151     private int videoHeight;
152 
153     // This is the degree that the frame should be rotated clockwisely to have
154     // it rendered up right.
155     private int rotationDegree;
156 
YuvImageRenderer( GLSurfaceView surface, int id, int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror, RendererCommon.GlDrawer drawer)157     private YuvImageRenderer(
158         GLSurfaceView surface, int id,
159         int x, int y, int width, int height,
160         RendererCommon.ScalingType scalingType, boolean mirror, RendererCommon.GlDrawer drawer) {
161       Logging.d(TAG, "YuvImageRenderer.Create id: " + id);
162       this.surface = surface;
163       this.id = id;
164       this.scalingType = scalingType;
165       this.mirror = mirror;
166       this.drawer = drawer;
167       layoutInPercentage = new Rect(x, y, Math.min(100, x + width), Math.min(100, y + height));
168       updateLayoutProperties = false;
169       rotationDegree = 0;
170     }
171 
reset()172     public synchronized void reset() {
173       seenFrame = false;
174     }
175 
release()176     private synchronized void release() {
177       surface = null;
178       drawer.release();
179       synchronized (pendingFrameLock) {
180         if (pendingFrame != null) {
181           VideoRenderer.renderFrameDone(pendingFrame);
182           pendingFrame = null;
183         }
184       }
185     }
186 
createTextures()187     private void createTextures() {
188       Logging.d(TAG, "  YuvImageRenderer.createTextures " + id + " on GL thread:" +
189           Thread.currentThread().getId());
190 
191       // Generate 3 texture ids for Y/U/V and place them into |yuvTextures|.
192       for (int i = 0; i < 3; i++)  {
193         yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D);
194       }
195       // Generate texture and framebuffer for offscreen texture copy.
196       textureCopy = new GlTextureFrameBuffer(GLES20.GL_RGB);
197     }
198 
updateLayoutMatrix()199     private void updateLayoutMatrix() {
200       synchronized(updateLayoutLock) {
201         if (!updateLayoutProperties) {
202           return;
203         }
204         // Initialize to maximum allowed area. Round to integer coordinates inwards the layout
205         // bounding box (ceil left/top and floor right/bottom) to not break constraints.
206         displayLayout.set(
207             (screenWidth * layoutInPercentage.left + 99) / 100,
208             (screenHeight * layoutInPercentage.top + 99) / 100,
209             (screenWidth * layoutInPercentage.right) / 100,
210             (screenHeight * layoutInPercentage.bottom) / 100);
211         Logging.d(TAG, "ID: "  + id + ". AdjustTextureCoords. Allowed display size: "
212             + displayLayout.width() + " x " + displayLayout.height() + ". Video: " + videoWidth
213             + " x " + videoHeight + ". Rotation: " + rotationDegree + ". Mirror: " + mirror);
214         final float videoAspectRatio = (rotationDegree % 180 == 0)
215             ? (float) videoWidth / videoHeight
216             : (float) videoHeight / videoWidth;
217         // Adjust display size based on |scalingType|.
218         final Point displaySize = RendererCommon.getDisplaySize(scalingType,
219             videoAspectRatio, displayLayout.width(), displayLayout.height());
220         displayLayout.inset((displayLayout.width() - displaySize.x) / 2,
221                             (displayLayout.height() - displaySize.y) / 2);
222         Logging.d(TAG, "  Adjusted display size: " + displayLayout.width() + " x "
223             + displayLayout.height());
224         layoutMatrix = RendererCommon.getLayoutMatrix(
225             mirror, videoAspectRatio, (float) displayLayout.width() / displayLayout.height());
226         updateLayoutProperties = false;
227         Logging.d(TAG, "  AdjustTextureCoords done");
228       }
229     }
230 
draw()231     private void draw() {
232       if (!seenFrame) {
233         // No frame received yet - nothing to render.
234         return;
235       }
236       long now = System.nanoTime();
237 
238       final boolean isNewFrame;
239       synchronized (pendingFrameLock) {
240         isNewFrame = (pendingFrame != null);
241         if (isNewFrame && startTimeNs == -1) {
242           startTimeNs = now;
243         }
244 
245         if (isNewFrame) {
246           rotatedSamplingMatrix = RendererCommon.rotateTextureMatrix(
247               pendingFrame.samplingMatrix, pendingFrame.rotationDegree);
248           if (pendingFrame.yuvFrame) {
249             rendererType = RendererType.RENDERER_YUV;
250             yuvUploader.uploadYuvData(yuvTextures, pendingFrame.width, pendingFrame.height,
251                 pendingFrame.yuvStrides, pendingFrame.yuvPlanes);
252           } else {
253             rendererType = RendererType.RENDERER_TEXTURE;
254             // External texture rendering. Make a deep copy of the external texture.
255             // Reallocate offscreen texture if necessary.
256             textureCopy.setSize(pendingFrame.rotatedWidth(), pendingFrame.rotatedHeight());
257 
258             // Bind our offscreen framebuffer.
259             GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, textureCopy.getFrameBufferId());
260             GlUtil.checkNoGLES2Error("glBindFramebuffer");
261 
262             // Copy the OES texture content. This will also normalize the sampling matrix.
263              drawer.drawOes(pendingFrame.textureId, rotatedSamplingMatrix,
264                  0, 0, textureCopy.getWidth(), textureCopy.getHeight());
265              rotatedSamplingMatrix = RendererCommon.identityMatrix();
266 
267              // Restore normal framebuffer.
268              GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
269              GLES20.glFinish();
270           }
271           copyTimeNs += (System.nanoTime() - now);
272           VideoRenderer.renderFrameDone(pendingFrame);
273           pendingFrame = null;
274         }
275       }
276 
277       updateLayoutMatrix();
278       final float[] texMatrix =
279           RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);
280       // OpenGL defaults to lower left origin - flip viewport position vertically.
281       final int viewportY = screenHeight - displayLayout.bottom;
282       if (rendererType == RendererType.RENDERER_YUV) {
283         drawer.drawYuv(yuvTextures, texMatrix,
284             displayLayout.left, viewportY, displayLayout.width(), displayLayout.height());
285       } else {
286         drawer.drawRgb(textureCopy.getTextureId(), texMatrix,
287             displayLayout.left, viewportY, displayLayout.width(), displayLayout.height());
288       }
289 
290       if (isNewFrame) {
291         framesRendered++;
292         drawTimeNs += (System.nanoTime() - now);
293         if ((framesRendered % 300) == 0) {
294           logStatistics();
295         }
296       }
297     }
298 
logStatistics()299     private void logStatistics() {
300       long timeSinceFirstFrameNs = System.nanoTime() - startTimeNs;
301       Logging.d(TAG, "ID: " + id + ". Type: " + rendererType +
302           ". Frames received: " + framesReceived +
303           ". Dropped: " + framesDropped + ". Rendered: " + framesRendered);
304       if (framesReceived > 0 && framesRendered > 0) {
305         Logging.d(TAG, "Duration: " + (int)(timeSinceFirstFrameNs / 1e6) +
306             " ms. FPS: " + framesRendered * 1e9 / timeSinceFirstFrameNs);
307         Logging.d(TAG, "Draw time: " +
308             (int) (drawTimeNs / (1000 * framesRendered)) + " us. Copy time: " +
309             (int) (copyTimeNs / (1000 * framesReceived)) + " us");
310       }
311     }
312 
setScreenSize(final int screenWidth, final int screenHeight)313     public void setScreenSize(final int screenWidth, final int screenHeight) {
314       synchronized(updateLayoutLock) {
315         if (screenWidth == this.screenWidth && screenHeight == this.screenHeight) {
316           return;
317         }
318         Logging.d(TAG, "ID: " + id + ". YuvImageRenderer.setScreenSize: " +
319             screenWidth + " x " + screenHeight);
320         this.screenWidth = screenWidth;
321         this.screenHeight = screenHeight;
322         updateLayoutProperties = true;
323       }
324     }
325 
setPosition(int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)326     public void setPosition(int x, int y, int width, int height,
327         RendererCommon.ScalingType scalingType, boolean mirror) {
328       final Rect layoutInPercentage =
329           new Rect(x, y, Math.min(100, x + width), Math.min(100, y + height));
330       synchronized(updateLayoutLock) {
331         if (layoutInPercentage.equals(this.layoutInPercentage) && scalingType == this.scalingType
332             && mirror == this.mirror) {
333           return;
334         }
335         Logging.d(TAG, "ID: " + id + ". YuvImageRenderer.setPosition: (" + x + ", " + y +
336             ") " +  width + " x " + height + ". Scaling: " + scalingType +
337             ". Mirror: " + mirror);
338         this.layoutInPercentage.set(layoutInPercentage);
339         this.scalingType = scalingType;
340         this.mirror = mirror;
341         updateLayoutProperties = true;
342       }
343     }
344 
setSize(final int videoWidth, final int videoHeight, final int rotation)345     private void setSize(final int videoWidth, final int videoHeight, final int rotation) {
346       if (videoWidth == this.videoWidth && videoHeight == this.videoHeight
347           && rotation == rotationDegree) {
348         return;
349       }
350       if (rendererEvents != null) {
351         Logging.d(TAG, "ID: " + id +
352             ". Reporting frame resolution changed to " + videoWidth + " x " + videoHeight);
353         rendererEvents.onFrameResolutionChanged(videoWidth, videoHeight, rotation);
354       }
355 
356       synchronized (updateLayoutLock) {
357         Logging.d(TAG, "ID: " + id + ". YuvImageRenderer.setSize: " +
358             videoWidth + " x " + videoHeight + " rotation " + rotation);
359 
360         this.videoWidth = videoWidth;
361         this.videoHeight = videoHeight;
362         rotationDegree = rotation;
363         updateLayoutProperties = true;
364         Logging.d(TAG, "  YuvImageRenderer.setSize done.");
365       }
366     }
367 
368     @Override
renderFrame(I420Frame frame)369     public synchronized void renderFrame(I420Frame frame) {
370       if (surface == null) {
371         // This object has been released.
372         VideoRenderer.renderFrameDone(frame);
373         return;
374       }
375       if (renderFrameThread == null) {
376         renderFrameThread = Thread.currentThread();
377       }
378       if (!seenFrame && rendererEvents != null) {
379         Logging.d(TAG, "ID: " + id + ". Reporting first rendered frame.");
380         rendererEvents.onFirstFrameRendered();
381       }
382       framesReceived++;
383       synchronized (pendingFrameLock) {
384         // Check input frame parameters.
385         if (frame.yuvFrame) {
386           if (frame.yuvStrides[0] < frame.width ||
387               frame.yuvStrides[1] < frame.width / 2 ||
388               frame.yuvStrides[2] < frame.width / 2) {
389             Logging.e(TAG, "Incorrect strides " + frame.yuvStrides[0] + ", " +
390                 frame.yuvStrides[1] + ", " + frame.yuvStrides[2]);
391             VideoRenderer.renderFrameDone(frame);
392             return;
393           }
394         }
395 
396         if (pendingFrame != null) {
397           // Skip rendering of this frame if previous frame was not rendered yet.
398           framesDropped++;
399           VideoRenderer.renderFrameDone(frame);
400           seenFrame = true;
401           return;
402         }
403         pendingFrame = frame;
404       }
405       setSize(frame.width, frame.height, frame.rotationDegree);
406       seenFrame = true;
407 
408       // Request rendering.
409       surface.requestRender();
410     }
411   }
412 
413   /** Passes GLSurfaceView to video renderer. */
setView(GLSurfaceView surface, Runnable eglContextReadyCallback)414   public static synchronized void setView(GLSurfaceView surface,
415       Runnable eglContextReadyCallback) {
416     Logging.d(TAG, "VideoRendererGui.setView");
417     instance = new VideoRendererGui(surface);
418     eglContextReady = eglContextReadyCallback;
419   }
420 
getEglBaseContext()421   public static synchronized EglBase.Context getEglBaseContext() {
422     return eglContext;
423   }
424 
425   /** Releases GLSurfaceView video renderer. */
dispose()426   public static synchronized void dispose() {
427     if (instance == null){
428       return;
429     }
430     Logging.d(TAG, "VideoRendererGui.dispose");
431     synchronized (instance.yuvImageRenderers) {
432       for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) {
433         yuvImageRenderer.release();
434       }
435       instance.yuvImageRenderers.clear();
436     }
437     renderFrameThread = null;
438     drawThread = null;
439     instance.surface = null;
440     eglContext = null;
441     eglContextReady = null;
442     instance = null;
443   }
444 
445   /**
446    * Creates VideoRenderer with top left corner at (x, y) and resolution
447    * (width, height). All parameters are in percentage of screen resolution.
448    */
createGui(int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)449   public static VideoRenderer createGui(int x, int y, int width, int height,
450       RendererCommon.ScalingType scalingType, boolean mirror) throws Exception {
451     YuvImageRenderer javaGuiRenderer = create(
452         x, y, width, height, scalingType, mirror);
453     return new VideoRenderer(javaGuiRenderer);
454   }
455 
createGuiRenderer( int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)456   public static VideoRenderer.Callbacks createGuiRenderer(
457       int x, int y, int width, int height,
458       RendererCommon.ScalingType scalingType, boolean mirror) {
459     return create(x, y, width, height, scalingType, mirror);
460   }
461 
462   /**
463    * Creates VideoRenderer.Callbacks with top left corner at (x, y) and
464    * resolution (width, height). All parameters are in percentage of
465    * screen resolution.
466    */
create(int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)467   public static synchronized YuvImageRenderer create(int x, int y, int width, int height,
468       RendererCommon.ScalingType scalingType, boolean mirror) {
469     return create(x, y, width, height, scalingType, mirror, new GlRectDrawer());
470   }
471 
472   /**
473    * Creates VideoRenderer.Callbacks with top left corner at (x, y) and resolution (width, height).
474    * All parameters are in percentage of screen resolution. The custom |drawer| will be used for
475    * drawing frames on the EGLSurface. This class is responsible for calling release() on |drawer|.
476    */
create(int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror, RendererCommon.GlDrawer drawer)477   public static synchronized YuvImageRenderer create(int x, int y, int width, int height,
478       RendererCommon.ScalingType scalingType, boolean mirror, RendererCommon.GlDrawer drawer) {
479     // Check display region parameters.
480     if (x < 0 || x > 100 || y < 0 || y > 100 ||
481         width < 0 || width > 100 || height < 0 || height > 100 ||
482         x + width > 100 || y + height > 100) {
483       throw new RuntimeException("Incorrect window parameters.");
484     }
485 
486     if (instance == null) {
487       throw new RuntimeException(
488           "Attempt to create yuv renderer before setting GLSurfaceView");
489     }
490     final YuvImageRenderer yuvImageRenderer = new YuvImageRenderer(
491         instance.surface, instance.yuvImageRenderers.size(),
492         x, y, width, height, scalingType, mirror, drawer);
493     synchronized (instance.yuvImageRenderers) {
494       if (instance.onSurfaceCreatedCalled) {
495         // onSurfaceCreated has already been called for VideoRendererGui -
496         // need to create texture for new image and add image to the
497         // rendering list.
498         final CountDownLatch countDownLatch = new CountDownLatch(1);
499         instance.surface.queueEvent(new Runnable() {
500           @Override
501           public void run() {
502             yuvImageRenderer.createTextures();
503             yuvImageRenderer.setScreenSize(
504                 instance.screenWidth, instance.screenHeight);
505             countDownLatch.countDown();
506           }
507         });
508         // Wait for task completion.
509         try {
510           countDownLatch.await();
511         } catch (InterruptedException e) {
512           throw new RuntimeException(e);
513         }
514       }
515       // Add yuv renderer to rendering list.
516       instance.yuvImageRenderers.add(yuvImageRenderer);
517     }
518     return yuvImageRenderer;
519   }
520 
update( VideoRenderer.Callbacks renderer, int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)521   public static synchronized void update(
522       VideoRenderer.Callbacks renderer, int x, int y, int width, int height,
523       RendererCommon.ScalingType scalingType, boolean mirror) {
524     Logging.d(TAG, "VideoRendererGui.update");
525     if (instance == null) {
526       throw new RuntimeException(
527           "Attempt to update yuv renderer before setting GLSurfaceView");
528     }
529     synchronized (instance.yuvImageRenderers) {
530       for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) {
531         if (yuvImageRenderer == renderer) {
532           yuvImageRenderer.setPosition(x, y, width, height, scalingType, mirror);
533         }
534       }
535     }
536   }
537 
setRendererEvents( VideoRenderer.Callbacks renderer, RendererCommon.RendererEvents rendererEvents)538   public static synchronized void setRendererEvents(
539       VideoRenderer.Callbacks renderer, RendererCommon.RendererEvents rendererEvents) {
540     Logging.d(TAG, "VideoRendererGui.setRendererEvents");
541     if (instance == null) {
542       throw new RuntimeException(
543           "Attempt to set renderer events before setting GLSurfaceView");
544     }
545     synchronized (instance.yuvImageRenderers) {
546       for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) {
547         if (yuvImageRenderer == renderer) {
548           yuvImageRenderer.rendererEvents = rendererEvents;
549         }
550       }
551     }
552   }
553 
remove(VideoRenderer.Callbacks renderer)554   public static synchronized void remove(VideoRenderer.Callbacks renderer) {
555     Logging.d(TAG, "VideoRendererGui.remove");
556     if (instance == null) {
557       throw new RuntimeException(
558           "Attempt to remove renderer before setting GLSurfaceView");
559     }
560     synchronized (instance.yuvImageRenderers) {
561       final int index = instance.yuvImageRenderers.indexOf(renderer);
562       if (index == -1) {
563         Logging.w(TAG, "Couldn't remove renderer (not present in current list)");
564       } else {
565         instance.yuvImageRenderers.remove(index).release();
566       }
567     }
568   }
569 
reset(VideoRenderer.Callbacks renderer)570   public static synchronized void reset(VideoRenderer.Callbacks renderer) {
571     Logging.d(TAG, "VideoRendererGui.reset");
572     if (instance == null) {
573       throw new RuntimeException(
574           "Attempt to reset renderer before setting GLSurfaceView");
575     }
576     synchronized (instance.yuvImageRenderers) {
577       for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) {
578         if (yuvImageRenderer == renderer) {
579           yuvImageRenderer.reset();
580         }
581       }
582     }
583   }
584 
printStackTrace(Thread thread, String threadName)585   private static void printStackTrace(Thread thread, String threadName) {
586     if (thread != null) {
587       StackTraceElement[] stackTraces = thread.getStackTrace();
588       if (stackTraces.length > 0) {
589         Logging.d(TAG, threadName + " stacks trace:");
590         for (StackTraceElement stackTrace : stackTraces) {
591           Logging.d(TAG, stackTrace.toString());
592         }
593       }
594     }
595   }
596 
printStackTraces()597   public static synchronized void printStackTraces() {
598     if (instance == null) {
599       return;
600     }
601     printStackTrace(renderFrameThread, "Render frame thread");
602     printStackTrace(drawThread, "Draw thread");
603   }
604 
605   @SuppressLint("NewApi")
606   @Override
onSurfaceCreated(GL10 unused, EGLConfig config)607   public void onSurfaceCreated(GL10 unused, EGLConfig config) {
608     Logging.d(TAG, "VideoRendererGui.onSurfaceCreated");
609     // Store render EGL context.
610     synchronized (VideoRendererGui.class) {
611       if (EglBase14.isEGL14Supported()) {
612         eglContext = new EglBase14.Context(EGL14.eglGetCurrentContext());
613       } else {
614         eglContext = new EglBase10.Context(((EGL10) EGLContext.getEGL()).eglGetCurrentContext());
615       }
616 
617       Logging.d(TAG, "VideoRendererGui EGL Context: " + eglContext);
618     }
619 
620     synchronized (yuvImageRenderers) {
621       // Create textures for all images.
622       for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) {
623         yuvImageRenderer.createTextures();
624       }
625       onSurfaceCreatedCalled = true;
626     }
627     GlUtil.checkNoGLES2Error("onSurfaceCreated done");
628     GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
629     GLES20.glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
630 
631     // Fire EGL context ready event.
632     synchronized (VideoRendererGui.class) {
633       if (eglContextReady != null) {
634         eglContextReady.run();
635       }
636     }
637   }
638 
639   @Override
onSurfaceChanged(GL10 unused, int width, int height)640   public void onSurfaceChanged(GL10 unused, int width, int height) {
641     Logging.d(TAG, "VideoRendererGui.onSurfaceChanged: " +
642         width + " x " + height + "  ");
643     screenWidth = width;
644     screenHeight = height;
645     synchronized (yuvImageRenderers) {
646       for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) {
647         yuvImageRenderer.setScreenSize(screenWidth, screenHeight);
648       }
649     }
650   }
651 
652   @Override
onDrawFrame(GL10 unused)653   public void onDrawFrame(GL10 unused) {
654     if (drawThread == null) {
655       drawThread = Thread.currentThread();
656     }
657     GLES20.glViewport(0, 0, screenWidth, screenHeight);
658     GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
659     synchronized (yuvImageRenderers) {
660       for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) {
661         yuvImageRenderer.draw();
662       }
663     }
664   }
665 
666 }
667