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 VideoRendererGui(GLSurfaceView surface)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 YuvImageRenderer( GLSurfaceView surface, int id, int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror, RendererCommon.GlDrawer drawer)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 reset()172 public synchronized void reset() { 173 seenFrame = false; 174 } 175 release()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 createTextures()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 updateLayoutMatrix()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 draw()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 logStatistics()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 setScreenSize(final int screenWidth, final int screenHeight)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 setPosition(int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)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 setSize(final int videoWidth, final int videoHeight, final int rotation)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 renderFrame(I420Frame frame)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. */ setView(GLSurfaceView surface, Runnable eglContextReadyCallback)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 getEglBaseContext()421 public static synchronized EglBase.Context getEglBaseContext() { 422 return eglContext; 423 } 424 425 /** Releases GLSurfaceView video renderer. */ dispose()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 */ createGui(int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)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 createGuiRenderer( int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)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 */ create(int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)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 */ create(int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror, RendererCommon.GlDrawer drawer)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 update( VideoRenderer.Callbacks renderer, int x, int y, int width, int height, RendererCommon.ScalingType scalingType, boolean mirror)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 setRendererEvents( VideoRenderer.Callbacks renderer, RendererCommon.RendererEvents rendererEvents)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 remove(VideoRenderer.Callbacks renderer)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 reset(VideoRenderer.Callbacks renderer)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 printStackTrace(Thread thread, String threadName)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 printStackTraces()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 onSurfaceCreated(GL10 unused, EGLConfig config)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 onSurfaceChanged(GL10 unused, int width, int height)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 onDrawFrame(GL10 unused)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