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.nio.ByteBuffer;
     31 import java.nio.ByteOrder;
     32 import java.nio.FloatBuffer;
     33 import java.util.ArrayList;
     34 import java.util.concurrent.CountDownLatch;
     35 import java.util.concurrent.LinkedBlockingQueue;
     36 
     37 import javax.microedition.khronos.egl.EGLConfig;
     38 import javax.microedition.khronos.opengles.GL10;
     39 
     40 import android.graphics.SurfaceTexture;
     41 import android.opengl.EGL14;
     42 import android.opengl.EGLContext;
     43 import android.opengl.GLES11Ext;
     44 import android.opengl.GLES20;
     45 import android.opengl.GLSurfaceView;
     46 import android.util.Log;
     47 
     48 import org.webrtc.VideoRenderer.I420Frame;
     49 
     50 /**
     51  * Efficiently renders YUV frames using the GPU for CSC.
     52  * Clients will want first to call setView() to pass GLSurfaceView
     53  * and then for each video stream either create instance of VideoRenderer using
     54  * createGui() call or VideoRenderer.Callbacks interface using create() call.
     55  * Only one instance of the class can be created.
     56  */
     57 public class VideoRendererGui implements GLSurfaceView.Renderer {
     58   private static VideoRendererGui instance = null;
     59   private static final String TAG = "VideoRendererGui";
     60   private GLSurfaceView surface;
     61   private static EGLContext eglContext = null;
     62   // Indicates if SurfaceView.Renderer.onSurfaceCreated was called.
     63   // If true then for every newly created yuv image renderer createTexture()
     64   // should be called. The variable is accessed on multiple threads and
     65   // all accesses are synchronized on yuvImageRenderers' object lock.
     66   private boolean onSurfaceCreatedCalled;
     67   private int screenWidth;
     68   private int screenHeight;
     69   // List of yuv renderers.
     70   private ArrayList<YuvImageRenderer> yuvImageRenderers;
     71   private int yuvProgram;
     72   private int oesProgram;
     73   // Types of video scaling:
     74   // SCALE_ASPECT_FIT - video frame is scaled to fit the size of the view by
     75   //    maintaining the aspect ratio (black borders may be displayed).
     76   // SCALE_ASPECT_FILL - video frame is scaled to fill the size of the view by
     77   //    maintaining the aspect ratio. Some portion of the video frame may be
     78   //    clipped.
     79   // SCALE_FILL - video frame is scaled to to fill the size of the view. Video
     80   //    aspect ratio is changed if necessary.
     81   private static enum ScalingType
     82       { SCALE_ASPECT_FIT, SCALE_ASPECT_FILL, SCALE_FILL };
     83 
     84   private final String VERTEX_SHADER_STRING =
     85       "varying vec2 interp_tc;\n" +
     86       "attribute vec4 in_pos;\n" +
     87       "attribute vec2 in_tc;\n" +
     88       "\n" +
     89       "void main() {\n" +
     90       "  gl_Position = in_pos;\n" +
     91       "  interp_tc = in_tc;\n" +
     92       "}\n";
     93 
     94   private final String YUV_FRAGMENT_SHADER_STRING =
     95       "precision mediump float;\n" +
     96       "varying vec2 interp_tc;\n" +
     97       "\n" +
     98       "uniform sampler2D y_tex;\n" +
     99       "uniform sampler2D u_tex;\n" +
    100       "uniform sampler2D v_tex;\n" +
    101       "\n" +
    102       "void main() {\n" +
    103       // CSC according to http://www.fourcc.org/fccyvrgb.php
    104       "  float y = texture2D(y_tex, interp_tc).r;\n" +
    105       "  float u = texture2D(u_tex, interp_tc).r - 0.5;\n" +
    106       "  float v = texture2D(v_tex, interp_tc).r - 0.5;\n" +
    107       "  gl_FragColor = vec4(y + 1.403 * v, " +
    108       "                      y - 0.344 * u - 0.714 * v, " +
    109       "                      y + 1.77 * u, 1);\n" +
    110       "}\n";
    111 
    112 
    113   private static final String OES_FRAGMENT_SHADER_STRING =
    114       "#extension GL_OES_EGL_image_external : require\n" +
    115       "precision mediump float;\n" +
    116       "varying vec2 interp_tc;\n" +
    117       "\n" +
    118       "uniform samplerExternalOES oes_tex;\n" +
    119       "\n" +
    120       "void main() {\n" +
    121       "  gl_FragColor = texture2D(oes_tex, interp_tc);\n" +
    122       "}\n";
    123 
    124 
    125   private VideoRendererGui(GLSurfaceView surface) {
    126     this.surface = surface;
    127     // Create an OpenGL ES 2.0 context.
    128     surface.setPreserveEGLContextOnPause(true);
    129     surface.setEGLContextClientVersion(2);
    130     surface.setRenderer(this);
    131     surface.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    132 
    133     yuvImageRenderers = new ArrayList<YuvImageRenderer>();
    134   }
    135 
    136   // Poor-man's assert(): die with |msg| unless |condition| is true.
    137   private static void abortUnless(boolean condition, String msg) {
    138     if (!condition) {
    139       throw new RuntimeException(msg);
    140     }
    141   }
    142 
    143   // Assert that no OpenGL ES 2.0 error has been raised.
    144   private static void checkNoGLES2Error() {
    145     int error = GLES20.glGetError();
    146     abortUnless(error == GLES20.GL_NO_ERROR, "GLES20 error: " + error);
    147   }
    148 
    149   // Wrap a float[] in a direct FloatBuffer using native byte order.
    150   private static FloatBuffer directNativeFloatBuffer(float[] array) {
    151     FloatBuffer buffer = ByteBuffer.allocateDirect(array.length * 4).order(
    152         ByteOrder.nativeOrder()).asFloatBuffer();
    153     buffer.put(array);
    154     buffer.flip();
    155     return buffer;
    156   }
    157 
    158   private int loadShader(int shaderType, String source) {
    159     int[] result = new int[] {
    160         GLES20.GL_FALSE
    161     };
    162     int shader = GLES20.glCreateShader(shaderType);
    163     GLES20.glShaderSource(shader, source);
    164     GLES20.glCompileShader(shader);
    165     GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0);
    166     if (result[0] != GLES20.GL_TRUE) {
    167       Log.e(TAG, "Could not compile shader " + shaderType + ":" +
    168           GLES20.glGetShaderInfoLog(shader));
    169       throw new RuntimeException(GLES20.glGetShaderInfoLog(shader));
    170     }
    171     checkNoGLES2Error();
    172     return shader;
    173 }
    174 
    175 
    176   private int createProgram(String vertexSource, String fragmentSource) {
    177     int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
    178     int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
    179     int program = GLES20.glCreateProgram();
    180     if (program == 0) {
    181       throw new RuntimeException("Could not create program");
    182     }
    183     GLES20.glAttachShader(program, vertexShader);
    184     GLES20.glAttachShader(program, fragmentShader);
    185     GLES20.glLinkProgram(program);
    186     int[] linkStatus = new int[] {
    187         GLES20.GL_FALSE
    188     };
    189     GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
    190     if (linkStatus[0] != GLES20.GL_TRUE) {
    191       Log.e(TAG, "Could not link program: " +
    192           GLES20.glGetProgramInfoLog(program));
    193       throw new RuntimeException(GLES20.glGetProgramInfoLog(program));
    194     }
    195     checkNoGLES2Error();
    196     return program;
    197 }
    198 
    199   /**
    200    * Class used to display stream of YUV420 frames at particular location
    201    * on a screen. New video frames are sent to display using renderFrame()
    202    * call.
    203    */
    204   private static class YuvImageRenderer implements VideoRenderer.Callbacks {
    205     private GLSurfaceView surface;
    206     private int id;
    207     private int yuvProgram;
    208     private int oesProgram;
    209     private int[] yuvTextures = { -1, -1, -1 };
    210     private int oesTexture = -1;
    211     private float[] stMatrix = new float[16];
    212 
    213     // Render frame queue - accessed by two threads. renderFrame() call does
    214     // an offer (writing I420Frame to render) and early-returns (recording
    215     // a dropped frame) if that queue is full. draw() call does a peek(),
    216     // copies frame to texture and then removes it from a queue using poll().
    217     LinkedBlockingQueue<I420Frame> frameToRenderQueue;
    218     // Local copy of incoming video frame.
    219     private I420Frame yuvFrameToRender;
    220     private I420Frame textureFrameToRender;
    221     // Type of video frame used for recent frame rendering.
    222     private static enum RendererType { RENDERER_YUV, RENDERER_TEXTURE };
    223     private RendererType rendererType;
    224     private ScalingType scalingType;
    225     // Flag if renderFrame() was ever called.
    226     boolean seenFrame;
    227     // Total number of video frames received in renderFrame() call.
    228     private int framesReceived;
    229     // Number of video frames dropped by renderFrame() because previous
    230     // frame has not been rendered yet.
    231     private int framesDropped;
    232     // Number of rendered video frames.
    233     private int framesRendered;
    234     // Time in ns when the first video frame was rendered.
    235     private long startTimeNs = -1;
    236     // Time in ns spent in draw() function.
    237     private long drawTimeNs;
    238     // Time in ns spent in renderFrame() function - including copying frame
    239     // data to rendering planes.
    240     private long copyTimeNs;
    241     // Texture vertices.
    242     private float texLeft;
    243     private float texRight;
    244     private float texTop;
    245     private float texBottom;
    246     private FloatBuffer textureVertices;
    247     // Texture UV coordinates offsets.
    248     private float texOffsetU;
    249     private float texOffsetV;
    250     private FloatBuffer textureCoords;
    251     // Flag if texture vertices or coordinates update is needed.
    252     private boolean updateTextureProperties;
    253     // Viewport dimensions.
    254     private int screenWidth;
    255     private int screenHeight;
    256     // Video dimension.
    257     private int videoWidth;
    258     private int videoHeight;
    259 
    260     private YuvImageRenderer(
    261         GLSurfaceView surface, int id,
    262         int x, int y, int width, int height,
    263         ScalingType scalingType) {
    264       Log.d(TAG, "YuvImageRenderer.Create id: " + id);
    265       this.surface = surface;
    266       this.id = id;
    267       this.scalingType = scalingType;
    268       frameToRenderQueue = new LinkedBlockingQueue<I420Frame>(1);
    269       // Create texture vertices.
    270       texLeft = (x - 50) / 50.0f;
    271       texTop = (50 - y) / 50.0f;
    272       texRight = Math.min(1.0f, (x + width - 50) / 50.0f);
    273       texBottom = Math.max(-1.0f, (50 - y - height) / 50.0f);
    274       float textureVeticesFloat[] = new float[] {
    275           texLeft, texTop,
    276           texLeft, texBottom,
    277           texRight, texTop,
    278           texRight, texBottom
    279       };
    280       textureVertices = directNativeFloatBuffer(textureVeticesFloat);
    281       // Create texture UV coordinates.
    282       texOffsetU = 0;
    283       texOffsetV = 0;
    284       float textureCoordinatesFloat[] = new float[] {
    285           texOffsetU, texOffsetV,               // left top
    286           texOffsetU, 1.0f - texOffsetV,        // left bottom
    287           1.0f - texOffsetU, texOffsetV,        // right top
    288           1.0f - texOffsetU, 1.0f - texOffsetV  // right bottom
    289       };
    290       textureCoords = directNativeFloatBuffer(textureCoordinatesFloat);
    291       updateTextureProperties = false;
    292     }
    293 
    294     private void createTextures(int yuvProgram, int oesProgram) {
    295       Log.d(TAG, "  YuvImageRenderer.createTextures " + id + " on GL thread:" +
    296           Thread.currentThread().getId());
    297       this.yuvProgram = yuvProgram;
    298       this.oesProgram = oesProgram;
    299 
    300       // Generate 3 texture ids for Y/U/V and place them into |yuvTextures|.
    301       GLES20.glGenTextures(3, yuvTextures, 0);
    302       for (int i = 0; i < 3; i++)  {
    303         GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
    304         GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
    305         GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
    306             128, 128, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, null);
    307         GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
    308             GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
    309         GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
    310             GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    311         GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
    312             GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    313         GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
    314             GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    315       }
    316       checkNoGLES2Error();
    317     }
    318 
    319     private void checkAdjustTextureCoords() {
    320       if (!updateTextureProperties ||
    321           scalingType == ScalingType.SCALE_FILL) {
    322         return;
    323       }
    324       // Re - calculate texture vertices to preserve video aspect ratio.
    325       float texRight = this.texRight;
    326       float texLeft = this.texLeft;
    327       float texTop = this.texTop;
    328       float texBottom = this.texBottom;
    329       float displayWidth = (texRight - texLeft) * screenWidth / 2;
    330       float displayHeight = (texTop - texBottom) * screenHeight / 2;
    331       if (displayWidth > 1 && displayHeight > 1 &&
    332           videoWidth > 1 && videoHeight > 1) {
    333         float displayAspectRatio = displayWidth / displayHeight;
    334         float videoAspectRatio = (float)videoWidth / videoHeight;
    335         if (scalingType == ScalingType.SCALE_ASPECT_FIT) {
    336           // Need to re-adjust vertices width or height to match video AR.
    337           if (displayAspectRatio > videoAspectRatio) {
    338             float deltaX = (displayWidth - videoAspectRatio * displayHeight) /
    339                     instance.screenWidth;
    340             texRight -= deltaX;
    341             texLeft += deltaX;
    342           } else {
    343             float deltaY = (displayHeight - displayWidth / videoAspectRatio) /
    344                     instance.screenHeight;
    345             texTop -= deltaY;
    346             texBottom += deltaY;
    347           }
    348           // Re-allocate vertices buffer to adjust to video aspect ratio.
    349           float textureVeticesFloat[] = new float[] {
    350               texLeft, texTop,
    351               texLeft, texBottom,
    352               texRight, texTop,
    353               texRight, texBottom
    354           };
    355           textureVertices = directNativeFloatBuffer(textureVeticesFloat);
    356         }
    357         if (scalingType == ScalingType.SCALE_ASPECT_FILL) {
    358           // Need to re-adjust UV coordinates to match display AR.
    359           if (displayAspectRatio > videoAspectRatio) {
    360             texOffsetV = (1.0f - videoAspectRatio / displayAspectRatio) / 2.0f;
    361           } else {
    362             texOffsetU = (1.0f - displayAspectRatio / videoAspectRatio) / 2.0f;
    363           }
    364           // Re-allocate coordinates buffer to adjust to display aspect ratio.
    365           float textureCoordinatesFloat[] = new float[] {
    366               texOffsetU, texOffsetV,               // left top
    367               texOffsetU, 1.0f - texOffsetV,        // left bottom
    368               1.0f - texOffsetU, texOffsetV,        // right top
    369               1.0f - texOffsetU, 1.0f - texOffsetV  // right bottom
    370           };
    371           textureCoords = directNativeFloatBuffer(textureCoordinatesFloat);
    372         }
    373       }
    374       updateTextureProperties = false;
    375     }
    376 
    377     private void draw() {
    378       if (!seenFrame) {
    379         // No frame received yet - nothing to render.
    380         return;
    381       }
    382       // Check if texture vertices/coordinates adjustment is required when
    383       // screen orientation changes or video frame size changes.
    384       checkAdjustTextureCoords();
    385 
    386       long now = System.nanoTime();
    387 
    388       I420Frame frameFromQueue;
    389       synchronized (frameToRenderQueue) {
    390         frameFromQueue = frameToRenderQueue.peek();
    391         if (frameFromQueue != null && startTimeNs == -1) {
    392           startTimeNs = now;
    393         }
    394 
    395         if (rendererType == RendererType.RENDERER_YUV) {
    396           // YUV textures rendering.
    397           GLES20.glUseProgram(yuvProgram);
    398 
    399           for (int i = 0; i < 3; ++i) {
    400             GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
    401             GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
    402             if (frameFromQueue != null) {
    403               int w = (i == 0) ?
    404                   frameFromQueue.width : frameFromQueue.width / 2;
    405               int h = (i == 0) ?
    406                   frameFromQueue.height : frameFromQueue.height / 2;
    407               GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
    408                   w, h, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
    409                   frameFromQueue.yuvPlanes[i]);
    410             }
    411           }
    412         } else {
    413           // External texture rendering.
    414           GLES20.glUseProgram(oesProgram);
    415 
    416           if (frameFromQueue != null) {
    417             oesTexture = frameFromQueue.textureId;
    418             if (frameFromQueue.textureObject instanceof SurfaceTexture) {
    419               SurfaceTexture surfaceTexture =
    420                   (SurfaceTexture) frameFromQueue.textureObject;
    421               surfaceTexture.updateTexImage();
    422               surfaceTexture.getTransformMatrix(stMatrix);
    423             }
    424           }
    425           GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    426           GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTexture);
    427         }
    428 
    429         if (frameFromQueue != null) {
    430           frameToRenderQueue.poll();
    431         }
    432       }
    433 
    434       if (rendererType == RendererType.RENDERER_YUV) {
    435         GLES20.glUniform1i(GLES20.glGetUniformLocation(yuvProgram, "y_tex"), 0);
    436         GLES20.glUniform1i(GLES20.glGetUniformLocation(yuvProgram, "u_tex"), 1);
    437         GLES20.glUniform1i(GLES20.glGetUniformLocation(yuvProgram, "v_tex"), 2);
    438       }
    439 
    440       int posLocation = GLES20.glGetAttribLocation(yuvProgram, "in_pos");
    441       if (posLocation == -1) {
    442         throw new RuntimeException("Could not get attrib location for in_pos");
    443       }
    444       GLES20.glEnableVertexAttribArray(posLocation);
    445       GLES20.glVertexAttribPointer(
    446           posLocation, 2, GLES20.GL_FLOAT, false, 0, textureVertices);
    447 
    448       int texLocation = GLES20.glGetAttribLocation(yuvProgram, "in_tc");
    449       if (texLocation == -1) {
    450         throw new RuntimeException("Could not get attrib location for in_tc");
    451       }
    452       GLES20.glEnableVertexAttribArray(texLocation);
    453       GLES20.glVertexAttribPointer(
    454           texLocation, 2, GLES20.GL_FLOAT, false, 0, textureCoords);
    455 
    456       GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    457 
    458       GLES20.glDisableVertexAttribArray(posLocation);
    459       GLES20.glDisableVertexAttribArray(texLocation);
    460 
    461       checkNoGLES2Error();
    462 
    463       if (frameFromQueue != null) {
    464         framesRendered++;
    465         drawTimeNs += (System.nanoTime() - now);
    466         if ((framesRendered % 150) == 0) {
    467           logStatistics();
    468         }
    469       }
    470     }
    471 
    472     private void logStatistics() {
    473       long timeSinceFirstFrameNs = System.nanoTime() - startTimeNs;
    474       Log.d(TAG, "ID: " + id + ". Type: " + rendererType +
    475           ". Frames received: " + framesReceived +
    476           ". Dropped: " + framesDropped + ". Rendered: " + framesRendered);
    477       if (framesReceived > 0 && framesRendered > 0) {
    478         Log.d(TAG, "Duration: " + (int)(timeSinceFirstFrameNs / 1e6) +
    479             " ms. FPS: " + (float)framesRendered * 1e9 / timeSinceFirstFrameNs);
    480         Log.d(TAG, "Draw time: " +
    481             (int) (drawTimeNs / (1000 * framesRendered)) + " us. Copy time: " +
    482             (int) (copyTimeNs / (1000 * framesReceived)) + " us");
    483       }
    484     }
    485 
    486     public void setScreenSize(final int screenWidth, final int screenHeight) {
    487       this.screenWidth = screenWidth;
    488       this.screenHeight = screenHeight;
    489       updateTextureProperties = true;
    490     }
    491 
    492     @Override
    493     public void setSize(final int width, final int height) {
    494       Log.d(TAG, "ID: " + id + ". YuvImageRenderer.setSize: " +
    495           width + " x " + height);
    496       videoWidth = width;
    497       videoHeight = height;
    498       int[] strides = { width, width / 2, width / 2  };
    499       // Frame re-allocation need to be synchronized with copying
    500       // frame to textures in draw() function to avoid re-allocating
    501       // the frame while it is being copied.
    502       synchronized (frameToRenderQueue) {
    503         // Clear rendering queue.
    504         frameToRenderQueue.poll();
    505         // Re-allocate / allocate the frame.
    506         yuvFrameToRender = new I420Frame(width, height, strides, null);
    507         textureFrameToRender = new I420Frame(width, height, null, -1);
    508         updateTextureProperties = true;
    509       }
    510     }
    511 
    512     @Override
    513     public synchronized void renderFrame(I420Frame frame) {
    514       long now = System.nanoTime();
    515       framesReceived++;
    516       // Skip rendering of this frame if setSize() was not called.
    517       if (yuvFrameToRender == null || textureFrameToRender == null) {
    518         framesDropped++;
    519         return;
    520       }
    521       // Check input frame parameters.
    522       if (frame.yuvFrame) {
    523         if (!(frame.yuvStrides[0] == frame.width &&
    524             frame.yuvStrides[1] == frame.width / 2 &&
    525             frame.yuvStrides[2] == frame.width / 2)) {
    526           Log.e(TAG, "Incorrect strides " + frame.yuvStrides[0] + ", " +
    527               frame.yuvStrides[1] + ", " + frame.yuvStrides[2]);
    528           return;
    529         }
    530         // Check incoming frame dimensions.
    531         if (frame.width != yuvFrameToRender.width ||
    532             frame.height != yuvFrameToRender.height) {
    533           throw new RuntimeException("Wrong frame size " +
    534               frame.width + " x " + frame.height);
    535         }
    536       }
    537 
    538       if (frameToRenderQueue.size() > 0) {
    539         // Skip rendering of this frame if previous frame was not rendered yet.
    540         framesDropped++;
    541         return;
    542       }
    543 
    544       // Create a local copy of the frame.
    545       if (frame.yuvFrame) {
    546         yuvFrameToRender.copyFrom(frame);
    547         rendererType = RendererType.RENDERER_YUV;
    548         frameToRenderQueue.offer(yuvFrameToRender);
    549       } else {
    550         textureFrameToRender.copyFrom(frame);
    551         rendererType = RendererType.RENDERER_TEXTURE;
    552         frameToRenderQueue.offer(textureFrameToRender);
    553       }
    554       copyTimeNs += (System.nanoTime() - now);
    555       seenFrame = true;
    556 
    557       // Request rendering.
    558       surface.requestRender();
    559     }
    560 
    561   }
    562 
    563   /** Passes GLSurfaceView to video renderer. */
    564   public static void setView(GLSurfaceView surface) {
    565     Log.d(TAG, "VideoRendererGui.setView");
    566     instance = new VideoRendererGui(surface);
    567   }
    568 
    569   public static EGLContext getEGLContext() {
    570     return eglContext;
    571   }
    572 
    573   /**
    574    * Creates VideoRenderer with top left corner at (x, y) and resolution
    575    * (width, height). All parameters are in percentage of screen resolution.
    576    */
    577   public static VideoRenderer createGui(
    578       int x, int y, int width, int height) throws Exception {
    579     YuvImageRenderer javaGuiRenderer = create(x, y, width, height);
    580     return new VideoRenderer(javaGuiRenderer);
    581   }
    582 
    583   public static VideoRenderer.Callbacks createGuiRenderer(
    584       int x, int y, int width, int height) {
    585     return create(x, y, width, height);
    586   }
    587 
    588   /**
    589    * Creates VideoRenderer.Callbacks with top left corner at (x, y) and
    590    * resolution (width, height). All parameters are in percentage of
    591    * screen resolution.
    592    */
    593   public static YuvImageRenderer create(
    594       int x, int y, int width, int height) {
    595     // Check display region parameters.
    596     if (x < 0 || x > 100 || y < 0 || y > 100 ||
    597         width < 0 || width > 100 || height < 0 || height > 100 ||
    598         x + width > 100 || y + height > 100) {
    599       throw new RuntimeException("Incorrect window parameters.");
    600     }
    601 
    602     if (instance == null) {
    603       throw new RuntimeException(
    604           "Attempt to create yuv renderer before setting GLSurfaceView");
    605     }
    606     final YuvImageRenderer yuvImageRenderer = new YuvImageRenderer(
    607         instance.surface, instance.yuvImageRenderers.size(),
    608         x, y, width, height, ScalingType.SCALE_ASPECT_FIT);
    609     synchronized (instance.yuvImageRenderers) {
    610       if (instance.onSurfaceCreatedCalled) {
    611         // onSurfaceCreated has already been called for VideoRendererGui -
    612         // need to create texture for new image and add image to the
    613         // rendering list.
    614         final CountDownLatch countDownLatch = new CountDownLatch(1);
    615         instance.surface.queueEvent(new Runnable() {
    616           public void run() {
    617             yuvImageRenderer.createTextures(
    618                 instance.yuvProgram, instance.oesProgram);
    619             yuvImageRenderer.setScreenSize(
    620                 instance.screenWidth, instance.screenHeight);
    621             countDownLatch.countDown();
    622           }
    623         });
    624         // Wait for task completion.
    625         try {
    626           countDownLatch.await();
    627         } catch (InterruptedException e) {
    628           throw new RuntimeException(e);
    629         }
    630       }
    631       // Add yuv renderer to rendering list.
    632       instance.yuvImageRenderers.add(yuvImageRenderer);
    633     }
    634     return yuvImageRenderer;
    635   }
    636 
    637   @Override
    638   public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    639     Log.d(TAG, "VideoRendererGui.onSurfaceCreated");
    640     // Store render EGL context
    641     eglContext = EGL14.eglGetCurrentContext();
    642     Log.d(TAG, "VideoRendererGui EGL Context: " + eglContext);
    643 
    644     // Create YUV and OES programs.
    645     yuvProgram = createProgram(VERTEX_SHADER_STRING,
    646         YUV_FRAGMENT_SHADER_STRING);
    647     oesProgram = createProgram(VERTEX_SHADER_STRING,
    648         OES_FRAGMENT_SHADER_STRING);
    649 
    650     synchronized (yuvImageRenderers) {
    651       // Create textures for all images.
    652       for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) {
    653         yuvImageRenderer.createTextures(yuvProgram, oesProgram);
    654       }
    655       onSurfaceCreatedCalled = true;
    656     }
    657     checkNoGLES2Error();
    658     GLES20.glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
    659   }
    660 
    661   @Override
    662   public void onSurfaceChanged(GL10 unused, int width, int height) {
    663     Log.d(TAG, "VideoRendererGui.onSurfaceChanged: " +
    664         width + " x " + height + "  ");
    665     screenWidth = width;
    666     screenHeight = height;
    667     GLES20.glViewport(0, 0, width, height);
    668     synchronized (yuvImageRenderers) {
    669       for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) {
    670         yuvImageRenderer.setScreenSize(screenWidth, screenHeight);
    671       }
    672     }
    673   }
    674 
    675   @Override
    676   public void onDrawFrame(GL10 unused) {
    677     GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    678     synchronized (yuvImageRenderers) {
    679       for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) {
    680         yuvImageRenderer.draw();
    681       }
    682     }
    683   }
    684 
    685 }
    686