OLD | NEW |
1 /* | 1 /* |
2 * libjingle | 2 * libjingle |
3 * Copyright 2015 Google Inc. | 3 * Copyright 2015 Google Inc. |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions are met: | 6 * modification, are permitted provided that the following conditions are met: |
7 * | 7 * |
8 * 1. Redistributions of source code must retain the above copyright notice, | 8 * 1. Redistributions of source code must retain the above copyright notice, |
9 * this list of conditions and the following disclaimer. | 9 * this list of conditions and the following disclaimer. |
10 * 2. Redistributions in binary form must reproduce the above copyright notice, | 10 * 2. Redistributions in binary form must reproduce the above copyright notice, |
(...skipping 10 matching lines...) Expand all Loading... |
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 */ | 26 */ |
27 | 27 |
28 package org.webrtc; | 28 package org.webrtc; |
29 | 29 |
30 import android.content.Context; | 30 import android.content.Context; |
| 31 import android.content.res.Resources.NotFoundException; |
31 import android.graphics.Point; | 32 import android.graphics.Point; |
32 import android.graphics.SurfaceTexture; | 33 import android.graphics.SurfaceTexture; |
33 import android.opengl.GLES20; | 34 import android.opengl.GLES20; |
34 import android.opengl.Matrix; | 35 import android.opengl.Matrix; |
35 import android.os.Handler; | 36 import android.os.Handler; |
36 import android.os.HandlerThread; | 37 import android.os.HandlerThread; |
37 import android.util.AttributeSet; | 38 import android.util.AttributeSet; |
38 import android.view.SurfaceHolder; | 39 import android.view.SurfaceHolder; |
39 import android.view.SurfaceView; | 40 import android.view.SurfaceView; |
40 | 41 |
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
145 } | 146 } |
146 | 147 |
147 /** | 148 /** |
148 * Initialize this class, sharing resources with |sharedContext|. It is allowe
d to call init() to | 149 * Initialize this class, sharing resources with |sharedContext|. It is allowe
d to call init() to |
149 * reinitialize the renderer after a previous init()/release() cycle. | 150 * reinitialize the renderer after a previous init()/release() cycle. |
150 */ | 151 */ |
151 public void init( | 152 public void init( |
152 EGLContext sharedContext, RendererCommon.RendererEvents rendererEvents) { | 153 EGLContext sharedContext, RendererCommon.RendererEvents rendererEvents) { |
153 synchronized (handlerLock) { | 154 synchronized (handlerLock) { |
154 if (renderThreadHandler != null) { | 155 if (renderThreadHandler != null) { |
155 throw new IllegalStateException("Already initialized"); | 156 throw new IllegalStateException(getResourceName() + "Already initialized
"); |
156 } | 157 } |
157 Logging.d(TAG, "Initializing"); | 158 Logging.d(TAG, getResourceName() + "Initializing."); |
158 this.rendererEvents = rendererEvents; | 159 this.rendererEvents = rendererEvents; |
159 renderThread = new HandlerThread(TAG); | 160 renderThread = new HandlerThread(TAG); |
160 renderThread.start(); | 161 renderThread.start(); |
161 drawer = new GlRectDrawer(); | 162 drawer = new GlRectDrawer(); |
162 eglBase = new EglBase(sharedContext, EglBase.ConfigType.PLAIN); | 163 eglBase = new EglBase(sharedContext, EglBase.ConfigType.PLAIN); |
163 renderThreadHandler = new Handler(renderThread.getLooper()); | 164 renderThreadHandler = new Handler(renderThread.getLooper()); |
164 } | 165 } |
165 tryCreateEglSurface(); | 166 tryCreateEglSurface(); |
166 } | 167 } |
167 | 168 |
(...skipping 20 matching lines...) Expand all Loading... |
188 /** | 189 /** |
189 * Block until any pending frame is returned and all GL resources released, ev
en if an interrupt | 190 * Block until any pending frame is returned and all GL resources released, ev
en if an interrupt |
190 * occurs. If an interrupt occurs during release(), the interrupt flag will be
set. This function | 191 * occurs. If an interrupt occurs during release(), the interrupt flag will be
set. This function |
191 * should be called before the Activity is destroyed and the EGLContext is sti
ll valid. If you | 192 * should be called before the Activity is destroyed and the EGLContext is sti
ll valid. If you |
192 * don't call this function, the GL resources might leak. | 193 * don't call this function, the GL resources might leak. |
193 */ | 194 */ |
194 public void release() { | 195 public void release() { |
195 final CountDownLatch eglCleanupBarrier = new CountDownLatch(1); | 196 final CountDownLatch eglCleanupBarrier = new CountDownLatch(1); |
196 synchronized (handlerLock) { | 197 synchronized (handlerLock) { |
197 if (renderThreadHandler == null) { | 198 if (renderThreadHandler == null) { |
198 Logging.d(TAG, "Already released"); | 199 Logging.d(TAG, getResourceName() + "Already released"); |
199 return; | 200 return; |
200 } | 201 } |
201 // Release EGL and GL resources on render thread. | 202 // Release EGL and GL resources on render thread. |
202 // TODO(magjed): This might not be necessary - all OpenGL resources are au
tomatically deleted | 203 // TODO(magjed): This might not be necessary - all OpenGL resources are au
tomatically deleted |
203 // when the EGL context is lost. It might be dangerous to delete them manu
ally in | 204 // when the EGL context is lost. It might be dangerous to delete them manu
ally in |
204 // Activity.onDestroy(). | 205 // Activity.onDestroy(). |
205 renderThreadHandler.postAtFrontOfQueue(new Runnable() { | 206 renderThreadHandler.postAtFrontOfQueue(new Runnable() { |
206 @Override public void run() { | 207 @Override public void run() { |
207 drawer.release(); | 208 drawer.release(); |
208 drawer = null; | 209 drawer = null; |
209 if (yuvTextures != null) { | 210 if (yuvTextures != null) { |
210 GLES20.glDeleteTextures(3, yuvTextures, 0); | 211 GLES20.glDeleteTextures(3, yuvTextures, 0); |
211 yuvTextures = null; | 212 yuvTextures = null; |
212 } | 213 } |
213 if (eglBase.hasSurface()) { | 214 if (eglBase.hasSurface()) { |
214 // Clear last rendered image to black. | 215 // Clear last rendered image to black. |
215 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); | 216 makeBlack(); |
216 eglBase.swapBuffers(); | |
217 } | 217 } |
218 eglBase.release(); | 218 eglBase.release(); |
219 eglBase = null; | 219 eglBase = null; |
220 eglCleanupBarrier.countDown(); | 220 eglCleanupBarrier.countDown(); |
221 } | 221 } |
222 }); | 222 }); |
223 // Don't accept any more frames or messages to the render thread. | 223 // Don't accept any more frames or messages to the render thread. |
224 renderThreadHandler = null; | 224 renderThreadHandler = null; |
225 } | 225 } |
226 // Make sure the EGL/GL cleanup posted above is executed. | 226 // Make sure the EGL/GL cleanup posted above is executed. |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
270 } | 270 } |
271 | 271 |
272 // VideoRenderer.Callbacks interface. | 272 // VideoRenderer.Callbacks interface. |
273 @Override | 273 @Override |
274 public void renderFrame(VideoRenderer.I420Frame frame) { | 274 public void renderFrame(VideoRenderer.I420Frame frame) { |
275 synchronized (statisticsLock) { | 275 synchronized (statisticsLock) { |
276 ++framesReceived; | 276 ++framesReceived; |
277 } | 277 } |
278 synchronized (handlerLock) { | 278 synchronized (handlerLock) { |
279 if (renderThreadHandler == null) { | 279 if (renderThreadHandler == null) { |
280 Logging.d(TAG, "Dropping frame - SurfaceViewRenderer not initialized or
already released."); | 280 Logging.d(TAG, getResourceName() |
| 281 + "Dropping frame - Not initialized or already released."); |
281 VideoRenderer.renderFrameDone(frame); | 282 VideoRenderer.renderFrameDone(frame); |
282 return; | 283 return; |
283 } | 284 } |
284 synchronized (frameLock) { | 285 synchronized (frameLock) { |
285 if (pendingFrame != null) { | 286 if (pendingFrame != null) { |
286 // Drop old frame. | 287 // Drop old frame. |
287 synchronized (statisticsLock) { | 288 synchronized (statisticsLock) { |
288 ++framesDropped; | 289 ++framesDropped; |
289 } | 290 } |
290 VideoRenderer.renderFrameDone(pendingFrame); | 291 VideoRenderer.renderFrameDone(pendingFrame); |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
330 layoutWidth = right - left; | 331 layoutWidth = right - left; |
331 layoutHeight = bottom - top; | 332 layoutHeight = bottom - top; |
332 } | 333 } |
333 // Might have a pending frame waiting for a layout of correct size. | 334 // Might have a pending frame waiting for a layout of correct size. |
334 runOnRenderThread(renderFrameRunnable); | 335 runOnRenderThread(renderFrameRunnable); |
335 } | 336 } |
336 | 337 |
337 // SurfaceHolder.Callback interface. | 338 // SurfaceHolder.Callback interface. |
338 @Override | 339 @Override |
339 public void surfaceCreated(final SurfaceHolder holder) { | 340 public void surfaceCreated(final SurfaceHolder holder) { |
340 Logging.d(TAG, "Surface created"); | 341 Logging.d(TAG, getResourceName() + "Surface created."); |
341 synchronized (layoutLock) { | 342 synchronized (layoutLock) { |
342 isSurfaceCreated = true; | 343 isSurfaceCreated = true; |
343 } | 344 } |
344 tryCreateEglSurface(); | 345 tryCreateEglSurface(); |
345 } | 346 } |
346 | 347 |
347 @Override | 348 @Override |
348 public void surfaceDestroyed(SurfaceHolder holder) { | 349 public void surfaceDestroyed(SurfaceHolder holder) { |
349 Logging.d(TAG, "Surface destroyed"); | 350 Logging.d(TAG, getResourceName() + "Surface destroyed."); |
350 synchronized (layoutLock) { | 351 synchronized (layoutLock) { |
351 isSurfaceCreated = false; | 352 isSurfaceCreated = false; |
352 surfaceWidth = 0; | 353 surfaceWidth = 0; |
353 surfaceHeight = 0; | 354 surfaceHeight = 0; |
354 } | 355 } |
355 runOnRenderThread(new Runnable() { | 356 runOnRenderThread(new Runnable() { |
356 @Override public void run() { | 357 @Override public void run() { |
357 eglBase.releaseSurface(); | 358 eglBase.releaseSurface(); |
358 } | 359 } |
359 }); | 360 }); |
360 } | 361 } |
361 | 362 |
362 @Override | 363 @Override |
363 public void surfaceChanged(SurfaceHolder holder, int format, int width, int he
ight) { | 364 public void surfaceChanged(SurfaceHolder holder, int format, int width, int he
ight) { |
364 Logging.d(TAG, "Surface changed: " + width + "x" + height); | 365 Logging.d(TAG, getResourceName() + "Surface changed: " + width + "x" + heigh
t); |
365 synchronized (layoutLock) { | 366 synchronized (layoutLock) { |
366 surfaceWidth = width; | 367 surfaceWidth = width; |
367 surfaceHeight = height; | 368 surfaceHeight = height; |
368 } | 369 } |
369 // Might have a pending frame waiting for a surface of correct size. | 370 // Might have a pending frame waiting for a surface of correct size. |
370 runOnRenderThread(renderFrameRunnable); | 371 runOnRenderThread(renderFrameRunnable); |
371 } | 372 } |
372 | 373 |
373 /** | 374 /** |
374 * Private helper function to post tasks safely. | 375 * Private helper function to post tasks safely. |
375 */ | 376 */ |
376 private void runOnRenderThread(Runnable runnable) { | 377 private void runOnRenderThread(Runnable runnable) { |
377 synchronized (handlerLock) { | 378 synchronized (handlerLock) { |
378 if (renderThreadHandler != null) { | 379 if (renderThreadHandler != null) { |
379 renderThreadHandler.post(runnable); | 380 renderThreadHandler.post(runnable); |
380 } | 381 } |
381 } | 382 } |
382 } | 383 } |
383 | 384 |
| 385 private String getResourceName() { |
| 386 try { |
| 387 return getResources().getResourceEntryName(getId()) + ": "; |
| 388 } catch (NotFoundException e) { |
| 389 return ""; |
| 390 } |
| 391 } |
| 392 |
| 393 private void makeBlack() { |
| 394 if (Thread.currentThread() != renderThread) { |
| 395 throw new IllegalStateException(getResourceName() + "Wrong thread."); |
| 396 } |
| 397 GLES20.glClearColor(0, 0, 0, 0); |
| 398 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); |
| 399 eglBase.swapBuffers(); |
| 400 } |
| 401 |
384 /** | 402 /** |
385 * Requests new layout if necessary. Returns true if layout and surface size a
re consistent. | 403 * Requests new layout if necessary. Returns true if layout and surface size a
re consistent. |
386 */ | 404 */ |
387 private boolean checkConsistentLayout() { | 405 private boolean checkConsistentLayout() { |
388 synchronized (layoutLock) { | 406 synchronized (layoutLock) { |
389 final Point desiredLayoutSize = getDesiredLayoutSize(); | 407 final Point desiredLayoutSize = getDesiredLayoutSize(); |
390 if (desiredLayoutSize.x != layoutWidth || desiredLayoutSize.y != layoutHei
ght) { | 408 if (desiredLayoutSize.x != layoutWidth || desiredLayoutSize.y != layoutHei
ght) { |
391 Logging.d(TAG, "Requesting new layout with size: " | 409 Logging.d(TAG, getResourceName() + "Requesting new layout with size: " |
392 + desiredLayoutSize.x + "x" + desiredLayoutSize.y); | 410 + desiredLayoutSize.x + "x" + desiredLayoutSize.y); |
393 // Request layout update on UI thread. | 411 // Request layout update on UI thread. |
394 post(new Runnable() { | 412 post(new Runnable() { |
395 @Override public void run() { | 413 @Override public void run() { |
396 requestLayout(); | 414 requestLayout(); |
397 } | 415 } |
398 }); | 416 }); |
399 return false; | 417 return false; |
400 } | 418 } |
401 // Wait for requestLayout() to propagate through this sequence before retu
rning true: | 419 // Wait for requestLayout() to propagate through this sequence before retu
rning true: |
402 // requestLayout() -> onMeasure() -> onLayout() -> surfaceChanged(). | 420 // requestLayout() -> onMeasure() -> onLayout() -> surfaceChanged(). |
403 return surfaceWidth == layoutWidth && surfaceHeight == layoutHeight; | 421 return surfaceWidth == layoutWidth && surfaceHeight == layoutHeight; |
404 } | 422 } |
405 } | 423 } |
406 | 424 |
407 /** | 425 /** |
408 * Renders and releases |pendingFrame|. | 426 * Renders and releases |pendingFrame|. |
409 */ | 427 */ |
410 private void renderFrameOnRenderThread() { | 428 private void renderFrameOnRenderThread() { |
| 429 if (Thread.currentThread() != renderThread) { |
| 430 throw new IllegalStateException(getResourceName() + "Wrong thread."); |
| 431 } |
411 if (eglBase == null || !eglBase.hasSurface()) { | 432 if (eglBase == null || !eglBase.hasSurface()) { |
412 Logging.d(TAG, "No surface to draw on"); | 433 Logging.d(TAG, getResourceName() + "No surface to draw on"); |
413 return; | 434 return; |
414 } | 435 } |
415 if (!checkConsistentLayout()) { | 436 if (!checkConsistentLayout()) { |
416 // Output intermediate black frames while the layout is updated. | 437 // Output intermediate black frames while the layout is updated. |
417 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); | 438 makeBlack(); |
418 eglBase.swapBuffers(); | |
419 return; | 439 return; |
420 } | 440 } |
421 // After a surface size change, the EGLSurface might still have a buffer of
the old size in the | 441 // After a surface size change, the EGLSurface might still have a buffer of
the old size in the |
422 // pipeline. Querying the EGLSurface will show if the underlying buffer dime
nsions haven't yet | 442 // pipeline. Querying the EGLSurface will show if the underlying buffer dime
nsions haven't yet |
423 // changed. Such a buffer will be rendered incorrectly, so flush it with a b
lack frame. | 443 // changed. Such a buffer will be rendered incorrectly, so flush it with a b
lack frame. |
424 synchronized (layoutLock) { | 444 synchronized (layoutLock) { |
425 if (eglBase.surfaceWidth() != surfaceWidth || eglBase.surfaceHeight() != s
urfaceHeight) { | 445 if (eglBase.surfaceWidth() != surfaceWidth || eglBase.surfaceHeight() != s
urfaceHeight) { |
426 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); | 446 makeBlack(); |
427 eglBase.swapBuffers(); | |
428 } | 447 } |
429 } | 448 } |
430 // Fetch and render |pendingFrame|. | 449 // Fetch and render |pendingFrame|. |
431 final VideoRenderer.I420Frame frame; | 450 final VideoRenderer.I420Frame frame; |
432 synchronized (frameLock) { | 451 synchronized (frameLock) { |
433 if (pendingFrame == null) { | 452 if (pendingFrame == null) { |
434 return; | 453 return; |
435 } | 454 } |
436 frame = pendingFrame; | 455 frame = pendingFrame; |
437 pendingFrame = null; | 456 pendingFrame = null; |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
502 : (float) frameHeight / frameWidth; | 521 : (float) frameHeight / frameWidth; |
503 } | 522 } |
504 } | 523 } |
505 | 524 |
506 // Update frame dimensions and report any changes to |rendererEvents|. | 525 // Update frame dimensions and report any changes to |rendererEvents|. |
507 private void updateFrameDimensionsAndReportEvents(VideoRenderer.I420Frame fram
e) { | 526 private void updateFrameDimensionsAndReportEvents(VideoRenderer.I420Frame fram
e) { |
508 synchronized (layoutLock) { | 527 synchronized (layoutLock) { |
509 if (frameWidth != frame.width || frameHeight != frame.height | 528 if (frameWidth != frame.width || frameHeight != frame.height |
510 || frameRotation != frame.rotationDegree) { | 529 || frameRotation != frame.rotationDegree) { |
511 if (rendererEvents != null) { | 530 if (rendererEvents != null) { |
512 final String id = getResources().getResourceEntryName(getId()); | |
513 if (frameWidth == 0 || frameHeight == 0) { | 531 if (frameWidth == 0 || frameHeight == 0) { |
514 Logging.d(TAG, "ID: " + id + ". Reporting first rendered frame."); | 532 Logging.d(TAG, getResourceName() + "Reporting first rendered frame."
); |
515 rendererEvents.onFirstFrameRendered(); | 533 rendererEvents.onFirstFrameRendered(); |
516 } | 534 } |
517 Logging.d(TAG, "ID: " + id + ". Reporting frame resolution changed to
" | 535 Logging.d(TAG, getResourceName() + "Reporting frame resolution changed
to " |
518 + frame.width + "x" + frame.height + " with rotation " + frame.rot
ationDegree); | 536 + frame.width + "x" + frame.height + " with rotation " + frame.rot
ationDegree); |
519 rendererEvents.onFrameResolutionChanged(frame.width, frame.height, fra
me.rotationDegree); | 537 rendererEvents.onFrameResolutionChanged(frame.width, frame.height, fra
me.rotationDegree); |
520 } | 538 } |
521 frameWidth = frame.width; | 539 frameWidth = frame.width; |
522 frameHeight = frame.height; | 540 frameHeight = frame.height; |
523 frameRotation = frame.rotationDegree; | 541 frameRotation = frame.rotationDegree; |
524 } | 542 } |
525 } | 543 } |
526 } | 544 } |
527 | 545 |
528 private void logStatistics() { | 546 private void logStatistics() { |
529 synchronized (statisticsLock) { | 547 synchronized (statisticsLock) { |
530 Logging.d(TAG, "ID: " + getResources().getResourceEntryName(getId()) + ".
Frames received: " | 548 Logging.d(TAG, getResourceName() + "Frames received: " |
531 + framesReceived + ". Dropped: " + framesDropped + ". Rendered: " + fr
amesRendered); | 549 + framesReceived + ". Dropped: " + framesDropped + ". Rendered: " + fr
amesRendered); |
532 if (framesReceived > 0 && framesRendered > 0) { | 550 if (framesReceived > 0 && framesRendered > 0) { |
533 final long timeSinceFirstFrameNs = System.nanoTime() - firstFrameTimeNs; | 551 final long timeSinceFirstFrameNs = System.nanoTime() - firstFrameTimeNs; |
534 Logging.d(TAG, "Duration: " + (int) (timeSinceFirstFrameNs / 1e6) + | 552 Logging.d(TAG, getResourceName() + "Duration: " + (int) (timeSinceFirstF
rameNs / 1e6) + |
535 " ms. FPS: " + (float) framesRendered * 1e9 / timeSinceFirstFrameNs)
; | 553 " ms. FPS: " + (float) framesRendered * 1e9 / timeSinceFirstFrameNs)
; |
536 Logging.d(TAG, "Average render time: " | 554 Logging.d(TAG, getResourceName() + "Average render time: " |
537 + (int) (renderTimeNs / (1000 * framesRendered)) + " us."); | 555 + (int) (renderTimeNs / (1000 * framesRendered)) + " us."); |
538 } | 556 } |
539 } | 557 } |
540 } | 558 } |
541 } | 559 } |
OLD | NEW |