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