| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright 2014 The WebRTC project authors. All Rights Reserved. | |
| 3 * | |
| 4 * Use of this source code is governed by a BSD-style license | |
| 5 * that can be found in the LICENSE file in the root of the source | |
| 6 * tree. An additional intellectual property rights grant can be found | |
| 7 * in the file PATENTS. All contributing project authors may | |
| 8 * be found in the AUTHORS file in the root of the source tree. | |
| 9 */ | |
| 10 | |
| 11 package org.webrtc; | |
| 12 | |
| 13 import java.util.ArrayList; | |
| 14 import java.util.concurrent.CountDownLatch; | |
| 15 | |
| 16 import javax.microedition.khronos.egl.EGLConfig; | |
| 17 import javax.microedition.khronos.egl.EGL10; | |
| 18 import javax.microedition.khronos.egl.EGLContext; | |
| 19 import javax.microedition.khronos.opengles.GL10; | |
| 20 | |
| 21 import android.annotation.SuppressLint; | |
| 22 import android.graphics.Point; | |
| 23 import android.graphics.Rect; | |
| 24 import android.opengl.EGL14; | |
| 25 import android.opengl.GLES20; | |
| 26 import android.opengl.GLSurfaceView; | |
| 27 | |
| 28 import org.webrtc.Logging; | |
| 29 import org.webrtc.VideoRenderer.I420Frame; | |
| 30 | |
| 31 /** | |
| 32 * Efficiently renders YUV frames using the GPU for CSC. | |
| 33 * Clients will want first to call setView() to pass GLSurfaceView | |
| 34 * and then for each video stream either create instance of VideoRenderer using | |
| 35 * createGui() call or VideoRenderer.Callbacks interface using create() call. | |
| 36 * Only one instance of the class can be created. | |
| 37 */ | |
| 38 public class VideoRendererGui implements GLSurfaceView.Renderer { | |
| 39 // |instance|, |instance.surface|, |eglContext|, and |eglContextReady| are syn
chronized on | |
| 40 // |VideoRendererGui.class|. | |
| 41 private static VideoRendererGui instance = null; | |
| 42 private static Runnable eglContextReady = null; | |
| 43 private static final String TAG = "VideoRendererGui"; | |
| 44 private GLSurfaceView surface; | |
| 45 private static EglBase.Context eglContext = null; | |
| 46 // Indicates if SurfaceView.Renderer.onSurfaceCreated was called. | |
| 47 // If true then for every newly created yuv image renderer createTexture() | |
| 48 // should be called. The variable is accessed on multiple threads and | |
| 49 // all accesses are synchronized on yuvImageRenderers' object lock. | |
| 50 private boolean onSurfaceCreatedCalled; | |
| 51 private int screenWidth; | |
| 52 private int screenHeight; | |
| 53 // List of yuv renderers. | |
| 54 private final ArrayList<YuvImageRenderer> yuvImageRenderers; | |
| 55 // Render and draw threads. | |
| 56 private static Thread renderFrameThread; | |
| 57 private static Thread drawThread; | |
| 58 | |
| 59 private VideoRendererGui(GLSurfaceView surface) { | |
| 60 this.surface = surface; | |
| 61 // Create an OpenGL ES 2.0 context. | |
| 62 surface.setPreserveEGLContextOnPause(true); | |
| 63 surface.setEGLContextClientVersion(2); | |
| 64 surface.setRenderer(this); | |
| 65 surface.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); | |
| 66 | |
| 67 yuvImageRenderers = new ArrayList<YuvImageRenderer>(); | |
| 68 } | |
| 69 | |
| 70 /** | |
| 71 * Class used to display stream of YUV420 frames at particular location | |
| 72 * on a screen. New video frames are sent to display using renderFrame() | |
| 73 * call. | |
| 74 */ | |
| 75 private static class YuvImageRenderer implements VideoRenderer.Callbacks { | |
| 76 // |surface| is synchronized on |this|. | |
| 77 private GLSurfaceView surface; | |
| 78 private int id; | |
| 79 // TODO(magjed): Delete GL resources in release(). Must be synchronized with
draw(). We are | |
| 80 // currently leaking resources to avoid a rare crash in release() where the
EGLContext has | |
| 81 // become invalid beforehand. | |
| 82 private int[] yuvTextures = { 0, 0, 0 }; | |
| 83 private final RendererCommon.YuvUploader yuvUploader = new RendererCommon.Yu
vUploader(); | |
| 84 private final RendererCommon.GlDrawer drawer; | |
| 85 // Resources for making a deep copy of incoming OES texture frame. | |
| 86 private GlTextureFrameBuffer textureCopy; | |
| 87 | |
| 88 // Pending frame to render. Serves as a queue with size 1. |pendingFrame| is
accessed by two | |
| 89 // threads - frames are received in renderFrame() and consumed in draw(). Fr
ames are dropped in | |
| 90 // renderFrame() if the previous frame has not been rendered yet. | |
| 91 private I420Frame pendingFrame; | |
| 92 private final Object pendingFrameLock = new Object(); | |
| 93 // Type of video frame used for recent frame rendering. | |
| 94 private static enum RendererType { RENDERER_YUV, RENDERER_TEXTURE }; | |
| 95 private RendererType rendererType; | |
| 96 private RendererCommon.ScalingType scalingType; | |
| 97 private boolean mirror; | |
| 98 private RendererCommon.RendererEvents rendererEvents; | |
| 99 // Flag if renderFrame() was ever called. | |
| 100 boolean seenFrame; | |
| 101 // Total number of video frames received in renderFrame() call. | |
| 102 private int framesReceived; | |
| 103 // Number of video frames dropped by renderFrame() because previous | |
| 104 // frame has not been rendered yet. | |
| 105 private int framesDropped; | |
| 106 // Number of rendered video frames. | |
| 107 private int framesRendered; | |
| 108 // Time in ns when the first video frame was rendered. | |
| 109 private long startTimeNs = -1; | |
| 110 // Time in ns spent in draw() function. | |
| 111 private long drawTimeNs; | |
| 112 // Time in ns spent in draw() copying resources from |pendingFrame| - includ
ing uploading frame | |
| 113 // data to rendering planes. | |
| 114 private long copyTimeNs; | |
| 115 // The allowed view area in percentage of screen size. | |
| 116 private final Rect layoutInPercentage; | |
| 117 // The actual view area in pixels. It is a centered subrectangle of the rect
angle defined by | |
| 118 // |layoutInPercentage|. | |
| 119 private final Rect displayLayout = new Rect(); | |
| 120 // Cached layout transformation matrix, calculated from current layout param
eters. | |
| 121 private float[] layoutMatrix; | |
| 122 // Flag if layout transformation matrix update is needed. | |
| 123 private boolean updateLayoutProperties; | |
| 124 // Layout properties update lock. Guards |updateLayoutProperties|, |screenWi
dth|, | |
| 125 // |screenHeight|, |videoWidth|, |videoHeight|, |rotationDegree|, |scalingTy
pe|, and |mirror|. | |
| 126 private final Object updateLayoutLock = new Object(); | |
| 127 // Texture sampling matrix. | |
| 128 private float[] rotatedSamplingMatrix; | |
| 129 // Viewport dimensions. | |
| 130 private int screenWidth; | |
| 131 private int screenHeight; | |
| 132 // Video dimension. | |
| 133 private int videoWidth; | |
| 134 private int videoHeight; | |
| 135 | |
| 136 // This is the degree that the frame should be rotated clockwisely to have | |
| 137 // it rendered up right. | |
| 138 private int rotationDegree; | |
| 139 | |
| 140 private YuvImageRenderer( | |
| 141 GLSurfaceView surface, int id, | |
| 142 int x, int y, int width, int height, | |
| 143 RendererCommon.ScalingType scalingType, boolean mirror, RendererCommon.G
lDrawer drawer) { | |
| 144 Logging.d(TAG, "YuvImageRenderer.Create id: " + id); | |
| 145 this.surface = surface; | |
| 146 this.id = id; | |
| 147 this.scalingType = scalingType; | |
| 148 this.mirror = mirror; | |
| 149 this.drawer = drawer; | |
| 150 layoutInPercentage = new Rect(x, y, Math.min(100, x + width), Math.min(100
, y + height)); | |
| 151 updateLayoutProperties = false; | |
| 152 rotationDegree = 0; | |
| 153 } | |
| 154 | |
| 155 public synchronized void reset() { | |
| 156 seenFrame = false; | |
| 157 } | |
| 158 | |
| 159 private synchronized void release() { | |
| 160 surface = null; | |
| 161 drawer.release(); | |
| 162 synchronized (pendingFrameLock) { | |
| 163 if (pendingFrame != null) { | |
| 164 VideoRenderer.renderFrameDone(pendingFrame); | |
| 165 pendingFrame = null; | |
| 166 } | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 private void createTextures() { | |
| 171 Logging.d(TAG, " YuvImageRenderer.createTextures " + id + " on GL thread:
" + | |
| 172 Thread.currentThread().getId()); | |
| 173 | |
| 174 // Generate 3 texture ids for Y/U/V and place them into |yuvTextures|. | |
| 175 for (int i = 0; i < 3; i++) { | |
| 176 yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); | |
| 177 } | |
| 178 // Generate texture and framebuffer for offscreen texture copy. | |
| 179 textureCopy = new GlTextureFrameBuffer(GLES20.GL_RGB); | |
| 180 } | |
| 181 | |
| 182 private void updateLayoutMatrix() { | |
| 183 synchronized(updateLayoutLock) { | |
| 184 if (!updateLayoutProperties) { | |
| 185 return; | |
| 186 } | |
| 187 // Initialize to maximum allowed area. Round to integer coordinates inwa
rds the layout | |
| 188 // bounding box (ceil left/top and floor right/bottom) to not break cons
traints. | |
| 189 displayLayout.set( | |
| 190 (screenWidth * layoutInPercentage.left + 99) / 100, | |
| 191 (screenHeight * layoutInPercentage.top + 99) / 100, | |
| 192 (screenWidth * layoutInPercentage.right) / 100, | |
| 193 (screenHeight * layoutInPercentage.bottom) / 100); | |
| 194 Logging.d(TAG, "ID: " + id + ". AdjustTextureCoords. Allowed display si
ze: " | |
| 195 + displayLayout.width() + " x " + displayLayout.height() + ". Video:
" + videoWidth | |
| 196 + " x " + videoHeight + ". Rotation: " + rotationDegree + ". Mirror:
" + mirror); | |
| 197 final float videoAspectRatio = (rotationDegree % 180 == 0) | |
| 198 ? (float) videoWidth / videoHeight | |
| 199 : (float) videoHeight / videoWidth; | |
| 200 // Adjust display size based on |scalingType|. | |
| 201 final Point displaySize = RendererCommon.getDisplaySize(scalingType, | |
| 202 videoAspectRatio, displayLayout.width(), displayLayout.height()); | |
| 203 displayLayout.inset((displayLayout.width() - displaySize.x) / 2, | |
| 204 (displayLayout.height() - displaySize.y) / 2); | |
| 205 Logging.d(TAG, " Adjusted display size: " + displayLayout.width() + " x
" | |
| 206 + displayLayout.height()); | |
| 207 layoutMatrix = RendererCommon.getLayoutMatrix( | |
| 208 mirror, videoAspectRatio, (float) displayLayout.width() / displayLay
out.height()); | |
| 209 updateLayoutProperties = false; | |
| 210 Logging.d(TAG, " AdjustTextureCoords done"); | |
| 211 } | |
| 212 } | |
| 213 | |
| 214 private void draw() { | |
| 215 if (!seenFrame) { | |
| 216 // No frame received yet - nothing to render. | |
| 217 return; | |
| 218 } | |
| 219 long now = System.nanoTime(); | |
| 220 | |
| 221 final boolean isNewFrame; | |
| 222 synchronized (pendingFrameLock) { | |
| 223 isNewFrame = (pendingFrame != null); | |
| 224 if (isNewFrame && startTimeNs == -1) { | |
| 225 startTimeNs = now; | |
| 226 } | |
| 227 | |
| 228 if (isNewFrame) { | |
| 229 rotatedSamplingMatrix = RendererCommon.rotateTextureMatrix( | |
| 230 pendingFrame.samplingMatrix, pendingFrame.rotationDegree); | |
| 231 if (pendingFrame.yuvFrame) { | |
| 232 rendererType = RendererType.RENDERER_YUV; | |
| 233 yuvUploader.uploadYuvData(yuvTextures, pendingFrame.width, pendingFr
ame.height, | |
| 234 pendingFrame.yuvStrides, pendingFrame.yuvPlanes); | |
| 235 } else { | |
| 236 rendererType = RendererType.RENDERER_TEXTURE; | |
| 237 // External texture rendering. Make a deep copy of the external text
ure. | |
| 238 // Reallocate offscreen texture if necessary. | |
| 239 textureCopy.setSize(pendingFrame.rotatedWidth(), pendingFrame.rotate
dHeight()); | |
| 240 | |
| 241 // Bind our offscreen framebuffer. | |
| 242 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, textureCopy.getFrame
BufferId()); | |
| 243 GlUtil.checkNoGLES2Error("glBindFramebuffer"); | |
| 244 | |
| 245 // Copy the OES texture content. This will also normalize the sampli
ng matrix. | |
| 246 drawer.drawOes(pendingFrame.textureId, rotatedSamplingMatrix, | |
| 247 textureCopy.getWidth(), textureCopy.getHeight(), | |
| 248 0, 0, textureCopy.getWidth(), textureCopy.getHeight()); | |
| 249 rotatedSamplingMatrix = RendererCommon.identityMatrix(); | |
| 250 | |
| 251 // Restore normal framebuffer. | |
| 252 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); | |
| 253 GLES20.glFinish(); | |
| 254 } | |
| 255 copyTimeNs += (System.nanoTime() - now); | |
| 256 VideoRenderer.renderFrameDone(pendingFrame); | |
| 257 pendingFrame = null; | |
| 258 } | |
| 259 } | |
| 260 | |
| 261 updateLayoutMatrix(); | |
| 262 final float[] texMatrix = | |
| 263 RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix); | |
| 264 // OpenGL defaults to lower left origin - flip viewport position verticall
y. | |
| 265 final int viewportY = screenHeight - displayLayout.bottom; | |
| 266 if (rendererType == RendererType.RENDERER_YUV) { | |
| 267 drawer.drawYuv(yuvTextures, texMatrix, videoWidth, videoHeight, | |
| 268 displayLayout.left, viewportY, displayLayout.width(), displayLayout.
height()); | |
| 269 } else { | |
| 270 drawer.drawRgb(textureCopy.getTextureId(), texMatrix, videoWidth, videoH
eight, | |
| 271 displayLayout.left, viewportY, displayLayout.width(), displayLayout.
height()); | |
| 272 } | |
| 273 | |
| 274 if (isNewFrame) { | |
| 275 framesRendered++; | |
| 276 drawTimeNs += (System.nanoTime() - now); | |
| 277 if ((framesRendered % 300) == 0) { | |
| 278 logStatistics(); | |
| 279 } | |
| 280 } | |
| 281 } | |
| 282 | |
| 283 private void logStatistics() { | |
| 284 long timeSinceFirstFrameNs = System.nanoTime() - startTimeNs; | |
| 285 Logging.d(TAG, "ID: " + id + ". Type: " + rendererType + | |
| 286 ". Frames received: " + framesReceived + | |
| 287 ". Dropped: " + framesDropped + ". Rendered: " + framesRendered); | |
| 288 if (framesReceived > 0 && framesRendered > 0) { | |
| 289 Logging.d(TAG, "Duration: " + (int)(timeSinceFirstFrameNs / 1e6) + | |
| 290 " ms. FPS: " + framesRendered * 1e9 / timeSinceFirstFrameNs); | |
| 291 Logging.d(TAG, "Draw time: " + | |
| 292 (int) (drawTimeNs / (1000 * framesRendered)) + " us. Copy time: " + | |
| 293 (int) (copyTimeNs / (1000 * framesReceived)) + " us"); | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 public void setScreenSize(final int screenWidth, final int screenHeight) { | |
| 298 synchronized(updateLayoutLock) { | |
| 299 if (screenWidth == this.screenWidth && screenHeight == this.screenHeight
) { | |
| 300 return; | |
| 301 } | |
| 302 Logging.d(TAG, "ID: " + id + ". YuvImageRenderer.setScreenSize: " + | |
| 303 screenWidth + " x " + screenHeight); | |
| 304 this.screenWidth = screenWidth; | |
| 305 this.screenHeight = screenHeight; | |
| 306 updateLayoutProperties = true; | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 public void setPosition(int x, int y, int width, int height, | |
| 311 RendererCommon.ScalingType scalingType, boolean mirror) { | |
| 312 final Rect layoutInPercentage = | |
| 313 new Rect(x, y, Math.min(100, x + width), Math.min(100, y + height)); | |
| 314 synchronized(updateLayoutLock) { | |
| 315 if (layoutInPercentage.equals(this.layoutInPercentage) && scalingType ==
this.scalingType | |
| 316 && mirror == this.mirror) { | |
| 317 return; | |
| 318 } | |
| 319 Logging.d(TAG, "ID: " + id + ". YuvImageRenderer.setPosition: (" + x + "
, " + y + | |
| 320 ") " + width + " x " + height + ". Scaling: " + scalingType + | |
| 321 ". Mirror: " + mirror); | |
| 322 this.layoutInPercentage.set(layoutInPercentage); | |
| 323 this.scalingType = scalingType; | |
| 324 this.mirror = mirror; | |
| 325 updateLayoutProperties = true; | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 private void setSize(final int videoWidth, final int videoHeight, final int
rotation) { | |
| 330 if (videoWidth == this.videoWidth && videoHeight == this.videoHeight | |
| 331 && rotation == rotationDegree) { | |
| 332 return; | |
| 333 } | |
| 334 if (rendererEvents != null) { | |
| 335 Logging.d(TAG, "ID: " + id + | |
| 336 ". Reporting frame resolution changed to " + videoWidth + " x " + vi
deoHeight); | |
| 337 rendererEvents.onFrameResolutionChanged(videoWidth, videoHeight, rotatio
n); | |
| 338 } | |
| 339 | |
| 340 synchronized (updateLayoutLock) { | |
| 341 Logging.d(TAG, "ID: " + id + ". YuvImageRenderer.setSize: " + | |
| 342 videoWidth + " x " + videoHeight + " rotation " + rotation); | |
| 343 | |
| 344 this.videoWidth = videoWidth; | |
| 345 this.videoHeight = videoHeight; | |
| 346 rotationDegree = rotation; | |
| 347 updateLayoutProperties = true; | |
| 348 Logging.d(TAG, " YuvImageRenderer.setSize done."); | |
| 349 } | |
| 350 } | |
| 351 | |
| 352 @Override | |
| 353 public synchronized void renderFrame(I420Frame frame) { | |
| 354 if (surface == null) { | |
| 355 // This object has been released. | |
| 356 VideoRenderer.renderFrameDone(frame); | |
| 357 return; | |
| 358 } | |
| 359 if (renderFrameThread == null) { | |
| 360 renderFrameThread = Thread.currentThread(); | |
| 361 } | |
| 362 if (!seenFrame && rendererEvents != null) { | |
| 363 Logging.d(TAG, "ID: " + id + ". Reporting first rendered frame."); | |
| 364 rendererEvents.onFirstFrameRendered(); | |
| 365 } | |
| 366 framesReceived++; | |
| 367 synchronized (pendingFrameLock) { | |
| 368 // Check input frame parameters. | |
| 369 if (frame.yuvFrame) { | |
| 370 if (frame.yuvStrides[0] < frame.width || | |
| 371 frame.yuvStrides[1] < frame.width / 2 || | |
| 372 frame.yuvStrides[2] < frame.width / 2) { | |
| 373 Logging.e(TAG, "Incorrect strides " + frame.yuvStrides[0] + ", " + | |
| 374 frame.yuvStrides[1] + ", " + frame.yuvStrides[2]); | |
| 375 VideoRenderer.renderFrameDone(frame); | |
| 376 return; | |
| 377 } | |
| 378 } | |
| 379 | |
| 380 if (pendingFrame != null) { | |
| 381 // Skip rendering of this frame if previous frame was not rendered yet
. | |
| 382 framesDropped++; | |
| 383 VideoRenderer.renderFrameDone(frame); | |
| 384 seenFrame = true; | |
| 385 return; | |
| 386 } | |
| 387 pendingFrame = frame; | |
| 388 } | |
| 389 setSize(frame.width, frame.height, frame.rotationDegree); | |
| 390 seenFrame = true; | |
| 391 | |
| 392 // Request rendering. | |
| 393 surface.requestRender(); | |
| 394 } | |
| 395 } | |
| 396 | |
| 397 /** Passes GLSurfaceView to video renderer. */ | |
| 398 public static synchronized void setView(GLSurfaceView surface, | |
| 399 Runnable eglContextReadyCallback) { | |
| 400 Logging.d(TAG, "VideoRendererGui.setView"); | |
| 401 instance = new VideoRendererGui(surface); | |
| 402 eglContextReady = eglContextReadyCallback; | |
| 403 } | |
| 404 | |
| 405 public static synchronized EglBase.Context getEglBaseContext() { | |
| 406 return eglContext; | |
| 407 } | |
| 408 | |
| 409 /** Releases GLSurfaceView video renderer. */ | |
| 410 public static synchronized void dispose() { | |
| 411 if (instance == null){ | |
| 412 return; | |
| 413 } | |
| 414 Logging.d(TAG, "VideoRendererGui.dispose"); | |
| 415 synchronized (instance.yuvImageRenderers) { | |
| 416 for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) { | |
| 417 yuvImageRenderer.release(); | |
| 418 } | |
| 419 instance.yuvImageRenderers.clear(); | |
| 420 } | |
| 421 renderFrameThread = null; | |
| 422 drawThread = null; | |
| 423 instance.surface = null; | |
| 424 eglContext = null; | |
| 425 eglContextReady = null; | |
| 426 instance = null; | |
| 427 } | |
| 428 | |
| 429 /** | |
| 430 * Creates VideoRenderer with top left corner at (x, y) and resolution | |
| 431 * (width, height). All parameters are in percentage of screen resolution. | |
| 432 */ | |
| 433 public static VideoRenderer createGui(int x, int y, int width, int height, | |
| 434 RendererCommon.ScalingType scalingType, boolean mirror) throws Exception { | |
| 435 YuvImageRenderer javaGuiRenderer = create( | |
| 436 x, y, width, height, scalingType, mirror); | |
| 437 return new VideoRenderer(javaGuiRenderer); | |
| 438 } | |
| 439 | |
| 440 public static VideoRenderer.Callbacks createGuiRenderer( | |
| 441 int x, int y, int width, int height, | |
| 442 RendererCommon.ScalingType scalingType, boolean mirror) { | |
| 443 return create(x, y, width, height, scalingType, mirror); | |
| 444 } | |
| 445 | |
| 446 /** | |
| 447 * Creates VideoRenderer.Callbacks with top left corner at (x, y) and | |
| 448 * resolution (width, height). All parameters are in percentage of | |
| 449 * screen resolution. | |
| 450 */ | |
| 451 public static synchronized YuvImageRenderer create(int x, int y, int width, in
t height, | |
| 452 RendererCommon.ScalingType scalingType, boolean mirror) { | |
| 453 return create(x, y, width, height, scalingType, mirror, new GlRectDrawer()); | |
| 454 } | |
| 455 | |
| 456 /** | |
| 457 * Creates VideoRenderer.Callbacks with top left corner at (x, y) and resoluti
on (width, height). | |
| 458 * All parameters are in percentage of screen resolution. The custom |drawer|
will be used for | |
| 459 * drawing frames on the EGLSurface. This class is responsible for calling rel
ease() on |drawer|. | |
| 460 */ | |
| 461 public static synchronized YuvImageRenderer create(int x, int y, int width, in
t height, | |
| 462 RendererCommon.ScalingType scalingType, boolean mirror, RendererCommon.GlD
rawer drawer) { | |
| 463 // Check display region parameters. | |
| 464 if (x < 0 || x > 100 || y < 0 || y > 100 || | |
| 465 width < 0 || width > 100 || height < 0 || height > 100 || | |
| 466 x + width > 100 || y + height > 100) { | |
| 467 throw new RuntimeException("Incorrect window parameters."); | |
| 468 } | |
| 469 | |
| 470 if (instance == null) { | |
| 471 throw new RuntimeException( | |
| 472 "Attempt to create yuv renderer before setting GLSurfaceView"); | |
| 473 } | |
| 474 final YuvImageRenderer yuvImageRenderer = new YuvImageRenderer( | |
| 475 instance.surface, instance.yuvImageRenderers.size(), | |
| 476 x, y, width, height, scalingType, mirror, drawer); | |
| 477 synchronized (instance.yuvImageRenderers) { | |
| 478 if (instance.onSurfaceCreatedCalled) { | |
| 479 // onSurfaceCreated has already been called for VideoRendererGui - | |
| 480 // need to create texture for new image and add image to the | |
| 481 // rendering list. | |
| 482 final CountDownLatch countDownLatch = new CountDownLatch(1); | |
| 483 instance.surface.queueEvent(new Runnable() { | |
| 484 @Override | |
| 485 public void run() { | |
| 486 yuvImageRenderer.createTextures(); | |
| 487 yuvImageRenderer.setScreenSize( | |
| 488 instance.screenWidth, instance.screenHeight); | |
| 489 countDownLatch.countDown(); | |
| 490 } | |
| 491 }); | |
| 492 // Wait for task completion. | |
| 493 try { | |
| 494 countDownLatch.await(); | |
| 495 } catch (InterruptedException e) { | |
| 496 throw new RuntimeException(e); | |
| 497 } | |
| 498 } | |
| 499 // Add yuv renderer to rendering list. | |
| 500 instance.yuvImageRenderers.add(yuvImageRenderer); | |
| 501 } | |
| 502 return yuvImageRenderer; | |
| 503 } | |
| 504 | |
| 505 public static synchronized void update( | |
| 506 VideoRenderer.Callbacks renderer, int x, int y, int width, int height, | |
| 507 RendererCommon.ScalingType scalingType, boolean mirror) { | |
| 508 Logging.d(TAG, "VideoRendererGui.update"); | |
| 509 if (instance == null) { | |
| 510 throw new RuntimeException( | |
| 511 "Attempt to update yuv renderer before setting GLSurfaceView"); | |
| 512 } | |
| 513 synchronized (instance.yuvImageRenderers) { | |
| 514 for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) { | |
| 515 if (yuvImageRenderer == renderer) { | |
| 516 yuvImageRenderer.setPosition(x, y, width, height, scalingType, mirror)
; | |
| 517 } | |
| 518 } | |
| 519 } | |
| 520 } | |
| 521 | |
| 522 public static synchronized void setRendererEvents( | |
| 523 VideoRenderer.Callbacks renderer, RendererCommon.RendererEvents rendererEv
ents) { | |
| 524 Logging.d(TAG, "VideoRendererGui.setRendererEvents"); | |
| 525 if (instance == null) { | |
| 526 throw new RuntimeException( | |
| 527 "Attempt to set renderer events before setting GLSurfaceView"); | |
| 528 } | |
| 529 synchronized (instance.yuvImageRenderers) { | |
| 530 for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) { | |
| 531 if (yuvImageRenderer == renderer) { | |
| 532 yuvImageRenderer.rendererEvents = rendererEvents; | |
| 533 } | |
| 534 } | |
| 535 } | |
| 536 } | |
| 537 | |
| 538 public static synchronized void remove(VideoRenderer.Callbacks renderer) { | |
| 539 Logging.d(TAG, "VideoRendererGui.remove"); | |
| 540 if (instance == null) { | |
| 541 throw new RuntimeException( | |
| 542 "Attempt to remove renderer before setting GLSurfaceView"); | |
| 543 } | |
| 544 synchronized (instance.yuvImageRenderers) { | |
| 545 final int index = instance.yuvImageRenderers.indexOf(renderer); | |
| 546 if (index == -1) { | |
| 547 Logging.w(TAG, "Couldn't remove renderer (not present in current list)")
; | |
| 548 } else { | |
| 549 instance.yuvImageRenderers.remove(index).release(); | |
| 550 } | |
| 551 } | |
| 552 } | |
| 553 | |
| 554 public static synchronized void reset(VideoRenderer.Callbacks renderer) { | |
| 555 Logging.d(TAG, "VideoRendererGui.reset"); | |
| 556 if (instance == null) { | |
| 557 throw new RuntimeException( | |
| 558 "Attempt to reset renderer before setting GLSurfaceView"); | |
| 559 } | |
| 560 synchronized (instance.yuvImageRenderers) { | |
| 561 for (YuvImageRenderer yuvImageRenderer : instance.yuvImageRenderers) { | |
| 562 if (yuvImageRenderer == renderer) { | |
| 563 yuvImageRenderer.reset(); | |
| 564 } | |
| 565 } | |
| 566 } | |
| 567 } | |
| 568 | |
| 569 private static void printStackTrace(Thread thread, String threadName) { | |
| 570 if (thread != null) { | |
| 571 StackTraceElement[] stackTraces = thread.getStackTrace(); | |
| 572 if (stackTraces.length > 0) { | |
| 573 Logging.d(TAG, threadName + " stacks trace:"); | |
| 574 for (StackTraceElement stackTrace : stackTraces) { | |
| 575 Logging.d(TAG, stackTrace.toString()); | |
| 576 } | |
| 577 } | |
| 578 } | |
| 579 } | |
| 580 | |
| 581 public static synchronized void printStackTraces() { | |
| 582 if (instance == null) { | |
| 583 return; | |
| 584 } | |
| 585 printStackTrace(renderFrameThread, "Render frame thread"); | |
| 586 printStackTrace(drawThread, "Draw thread"); | |
| 587 } | |
| 588 | |
| 589 @SuppressLint("NewApi") | |
| 590 @Override | |
| 591 public void onSurfaceCreated(GL10 unused, EGLConfig config) { | |
| 592 Logging.d(TAG, "VideoRendererGui.onSurfaceCreated"); | |
| 593 // Store render EGL context. | |
| 594 synchronized (VideoRendererGui.class) { | |
| 595 if (EglBase14.isEGL14Supported()) { | |
| 596 eglContext = new EglBase14.Context(EGL14.eglGetCurrentContext()); | |
| 597 } else { | |
| 598 eglContext = new EglBase10.Context(((EGL10) EGLContext.getEGL()).eglGetC
urrentContext()); | |
| 599 } | |
| 600 | |
| 601 Logging.d(TAG, "VideoRendererGui EGL Context: " + eglContext); | |
| 602 } | |
| 603 | |
| 604 synchronized (yuvImageRenderers) { | |
| 605 // Create textures for all images. | |
| 606 for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { | |
| 607 yuvImageRenderer.createTextures(); | |
| 608 } | |
| 609 onSurfaceCreatedCalled = true; | |
| 610 } | |
| 611 GlUtil.checkNoGLES2Error("onSurfaceCreated done"); | |
| 612 GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); | |
| 613 GLES20.glClearColor(0.15f, 0.15f, 0.15f, 1.0f); | |
| 614 | |
| 615 // Fire EGL context ready event. | |
| 616 synchronized (VideoRendererGui.class) { | |
| 617 if (eglContextReady != null) { | |
| 618 eglContextReady.run(); | |
| 619 } | |
| 620 } | |
| 621 } | |
| 622 | |
| 623 @Override | |
| 624 public void onSurfaceChanged(GL10 unused, int width, int height) { | |
| 625 Logging.d(TAG, "VideoRendererGui.onSurfaceChanged: " + | |
| 626 width + " x " + height + " "); | |
| 627 screenWidth = width; | |
| 628 screenHeight = height; | |
| 629 synchronized (yuvImageRenderers) { | |
| 630 for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { | |
| 631 yuvImageRenderer.setScreenSize(screenWidth, screenHeight); | |
| 632 } | |
| 633 } | |
| 634 } | |
| 635 | |
| 636 @Override | |
| 637 public void onDrawFrame(GL10 unused) { | |
| 638 if (drawThread == null) { | |
| 639 drawThread = Thread.currentThread(); | |
| 640 } | |
| 641 GLES20.glViewport(0, 0, screenWidth, screenHeight); | |
| 642 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); | |
| 643 synchronized (yuvImageRenderers) { | |
| 644 for (YuvImageRenderer yuvImageRenderer : yuvImageRenderers) { | |
| 645 yuvImageRenderer.draw(); | |
| 646 } | |
| 647 } | |
| 648 } | |
| 649 | |
| 650 } | |
| OLD | NEW |