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
28package org.webrtc;
29
30import java.util.ArrayList;
31import java.util.concurrent.CountDownLatch;
32
33import javax.microedition.khronos.egl.EGLConfig;
34import javax.microedition.khronos.egl.EGL10;
35import javax.microedition.khronos.egl.EGLContext;
36import javax.microedition.khronos.opengles.GL10;
37
38import android.annotation.SuppressLint;
39import android.graphics.Point;
40import android.graphics.Rect;
41import android.opengl.EGL14;
42import android.opengl.GLES20;
43import android.opengl.GLSurfaceView;
44
45import org.webrtc.Logging;
46import 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 */
55public 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
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
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
172    public synchronized void reset() {
173      seenFrame = false;
174    }
175
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
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
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
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
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
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
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
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
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. */
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
421  public static synchronized EglBase.Context getEglBaseContext() {
422    return eglContext;
423  }
424
425  /** Releases GLSurfaceView video renderer. */
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   */
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
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   */
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   */
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
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
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
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
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
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
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
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
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
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