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 |