Home | History | Annotate | Download | only in webrtc
      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 
     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