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 |