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