Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / libjingle / source / talk / app / webrtc / java / android / org / webrtc / VideoRendererGui.java
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 }