| 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 18 matching lines...) Expand all Loading... |
| 29 | 29 |
| 30 import android.content.Context; | 30 import android.content.Context; |
| 31 import android.graphics.Point; | 31 import android.graphics.Point; |
| 32 import android.graphics.SurfaceTexture; | 32 import android.graphics.SurfaceTexture; |
| 33 import android.opengl.EGLContext; | 33 import android.opengl.EGLContext; |
| 34 import android.opengl.GLES20; | 34 import android.opengl.GLES20; |
| 35 import android.opengl.Matrix; | 35 import android.opengl.Matrix; |
| 36 import android.os.Handler; | 36 import android.os.Handler; |
| 37 import android.os.HandlerThread; | 37 import android.os.HandlerThread; |
| 38 import android.util.AttributeSet; | 38 import android.util.AttributeSet; |
| 39 import android.util.Log; | |
| 40 import android.view.SurfaceHolder; | 39 import android.view.SurfaceHolder; |
| 41 import android.view.SurfaceView; | 40 import android.view.SurfaceView; |
| 42 | 41 |
| 42 import org.webrtc.Logging; |
| 43 |
| 43 /** | 44 /** |
| 44 * Implements org.webrtc.VideoRenderer.Callbacks by displaying the video stream
on a SurfaceView. | 45 * Implements org.webrtc.VideoRenderer.Callbacks by displaying the video stream
on a SurfaceView. |
| 45 * renderFrame() is asynchronous to avoid blocking the calling thread. | 46 * renderFrame() is asynchronous to avoid blocking the calling thread. |
| 46 * This class is thread safe and handles access from potentially four different
threads: | 47 * This class is thread safe and handles access from potentially four different
threads: |
| 47 * Interaction from the main app in init, release, setMirror, and setScalingtype
. | 48 * Interaction from the main app in init, release, setMirror, and setScalingtype
. |
| 48 * Interaction from C++ webrtc::VideoRendererInterface in renderFrame and canApp
lyRotation. | 49 * Interaction from C++ webrtc::VideoRendererInterface in renderFrame and canApp
lyRotation. |
| 49 * Interaction from the Activity lifecycle in surfaceCreated, surfaceChanged, an
d surfaceDestroyed. | 50 * Interaction from the Activity lifecycle in surfaceCreated, surfaceChanged, an
d surfaceDestroyed. |
| 50 * Interaction with the layout framework in onMeasure and onSizeChanged. | 51 * Interaction with the layout framework in onMeasure and onSizeChanged. |
| 51 */ | 52 */ |
| 52 public class SurfaceViewRenderer extends SurfaceView | 53 public class SurfaceViewRenderer extends SurfaceView |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 137 } | 138 } |
| 138 | 139 |
| 139 /** | 140 /** |
| 140 * Initialize this class, sharing resources with |sharedContext|. | 141 * Initialize this class, sharing resources with |sharedContext|. |
| 141 */ | 142 */ |
| 142 public void init( | 143 public void init( |
| 143 EGLContext sharedContext, RendererCommon.RendererEvents rendererEvents) { | 144 EGLContext sharedContext, RendererCommon.RendererEvents rendererEvents) { |
| 144 if (renderThreadHandler != null) { | 145 if (renderThreadHandler != null) { |
| 145 throw new IllegalStateException("Already initialized"); | 146 throw new IllegalStateException("Already initialized"); |
| 146 } | 147 } |
| 147 Log.d(TAG, "Initializing"); | 148 Logging.d(TAG, "Initializing"); |
| 148 this.rendererEvents = rendererEvents; | 149 this.rendererEvents = rendererEvents; |
| 149 renderThread = new HandlerThread(TAG); | 150 renderThread = new HandlerThread(TAG); |
| 150 renderThread.start(); | 151 renderThread.start(); |
| 151 renderThreadHandler = new Handler(renderThread.getLooper()); | 152 renderThreadHandler = new Handler(renderThread.getLooper()); |
| 152 eglBase = new EglBase(sharedContext, EglBase.ConfigType.PLAIN); | 153 eglBase = new EglBase(sharedContext, EglBase.ConfigType.PLAIN); |
| 153 drawer = new GlRectDrawer(); | 154 drawer = new GlRectDrawer(); |
| 154 getHolder().addCallback(this); | 155 getHolder().addCallback(this); |
| 155 } | 156 } |
| 156 | 157 |
| 157 /** | 158 /** |
| 158 * Release all resources. This needs to be done manually, otherwise the resour
ces are leaked. You | 159 * Release all resources. This needs to be done manually, otherwise the resour
ces are leaked. You |
| 159 * should call this before the Activity is destroyed, while the EGLContext is
still valid. | 160 * should call this before the Activity is destroyed, while the EGLContext is
still valid. |
| 160 */ | 161 */ |
| 161 public void release() { | 162 public void release() { |
| 162 synchronized (threadLock) { | 163 synchronized (threadLock) { |
| 163 if (renderThreadHandler == null) { | 164 if (renderThreadHandler == null) { |
| 164 Log.d(TAG, "Already released"); | 165 Logging.d(TAG, "Already released"); |
| 165 return; | 166 return; |
| 166 } | 167 } |
| 167 // Release EGL and GL resources on render thread. | 168 // Release EGL and GL resources on render thread. |
| 168 // TODO(magjed): This might not be necessary - all OpenGL resources are au
tomatically deleted | 169 // TODO(magjed): This might not be necessary - all OpenGL resources are au
tomatically deleted |
| 169 // when the EGL context is lost. It might be dangerous to delete them manu
ally in | 170 // when the EGL context is lost. It might be dangerous to delete them manu
ally in |
| 170 // Activity.onDestroy(). | 171 // Activity.onDestroy(). |
| 171 renderThreadHandler.post(new Runnable() { | 172 renderThreadHandler.post(new Runnable() { |
| 172 @Override public void run() { | 173 @Override public void run() { |
| 173 drawer.release(); | 174 drawer.release(); |
| 174 drawer = null; | 175 drawer = null; |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 214 } | 215 } |
| 215 | 216 |
| 216 // VideoRenderer.Callbacks interface. | 217 // VideoRenderer.Callbacks interface. |
| 217 @Override | 218 @Override |
| 218 public void renderFrame(VideoRenderer.I420Frame frame) { | 219 public void renderFrame(VideoRenderer.I420Frame frame) { |
| 219 synchronized (statisticsLock) { | 220 synchronized (statisticsLock) { |
| 220 ++framesReceived; | 221 ++framesReceived; |
| 221 } | 222 } |
| 222 synchronized (threadLock) { | 223 synchronized (threadLock) { |
| 223 if (renderThreadHandler == null) { | 224 if (renderThreadHandler == null) { |
| 224 Log.d(TAG, "Dropping frame - SurfaceViewRenderer not initialized or alre
ady released."); | 225 Logging.d(TAG, "Dropping frame - SurfaceViewRenderer not initialized or
already released."); |
| 225 } else { | 226 } else { |
| 226 synchronized (frameLock) { | 227 synchronized (frameLock) { |
| 227 if (pendingFrame == null) { | 228 if (pendingFrame == null) { |
| 228 updateFrameDimensionsAndReportEvents(frame); | 229 updateFrameDimensionsAndReportEvents(frame); |
| 229 pendingFrame = frame; | 230 pendingFrame = frame; |
| 230 renderThreadHandler.post(renderFrameRunnable); | 231 renderThreadHandler.post(renderFrameRunnable); |
| 231 return; | 232 return; |
| 232 } | 233 } |
| 233 } | 234 } |
| 234 } | 235 } |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 274 layoutWidth = right - left; | 275 layoutWidth = right - left; |
| 275 layoutHeight = bottom - top; | 276 layoutHeight = bottom - top; |
| 276 } | 277 } |
| 277 // Might have a pending frame waiting for a layout of correct size. | 278 // Might have a pending frame waiting for a layout of correct size. |
| 278 runOnRenderThread(renderFrameRunnable); | 279 runOnRenderThread(renderFrameRunnable); |
| 279 } | 280 } |
| 280 | 281 |
| 281 // SurfaceHolder.Callback interface. | 282 // SurfaceHolder.Callback interface. |
| 282 @Override | 283 @Override |
| 283 public void surfaceCreated(final SurfaceHolder holder) { | 284 public void surfaceCreated(final SurfaceHolder holder) { |
| 284 Log.d(TAG, "Surface created"); | 285 Logging.d(TAG, "Surface created"); |
| 285 runOnRenderThread(new Runnable() { | 286 runOnRenderThread(new Runnable() { |
| 286 @Override public void run() { | 287 @Override public void run() { |
| 287 eglBase.createSurface(holder.getSurface()); | 288 eglBase.createSurface(holder.getSurface()); |
| 288 eglBase.makeCurrent(); | 289 eglBase.makeCurrent(); |
| 289 // Necessary for YUV frames with odd width. | 290 // Necessary for YUV frames with odd width. |
| 290 GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); | 291 GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1); |
| 291 } | 292 } |
| 292 }); | 293 }); |
| 293 } | 294 } |
| 294 | 295 |
| 295 @Override | 296 @Override |
| 296 public void surfaceDestroyed(SurfaceHolder holder) { | 297 public void surfaceDestroyed(SurfaceHolder holder) { |
| 297 Log.d(TAG, "Surface destroyed"); | 298 Logging.d(TAG, "Surface destroyed"); |
| 298 synchronized (layoutLock) { | 299 synchronized (layoutLock) { |
| 299 surfaceWidth = 0; | 300 surfaceWidth = 0; |
| 300 surfaceHeight = 0; | 301 surfaceHeight = 0; |
| 301 } | 302 } |
| 302 runOnRenderThread(new Runnable() { | 303 runOnRenderThread(new Runnable() { |
| 303 @Override public void run() { | 304 @Override public void run() { |
| 304 eglBase.releaseSurface(); | 305 eglBase.releaseSurface(); |
| 305 } | 306 } |
| 306 }); | 307 }); |
| 307 } | 308 } |
| 308 | 309 |
| 309 @Override | 310 @Override |
| 310 public void surfaceChanged(SurfaceHolder holder, int format, int width, int he
ight) { | 311 public void surfaceChanged(SurfaceHolder holder, int format, int width, int he
ight) { |
| 311 Log.d(TAG, "Surface changed: " + width + "x" + height); | 312 Logging.d(TAG, "Surface changed: " + width + "x" + height); |
| 312 synchronized (layoutLock) { | 313 synchronized (layoutLock) { |
| 313 surfaceWidth = width; | 314 surfaceWidth = width; |
| 314 surfaceHeight = height; | 315 surfaceHeight = height; |
| 315 } | 316 } |
| 316 // Might have a pending frame waiting for a surface of correct size. | 317 // Might have a pending frame waiting for a surface of correct size. |
| 317 runOnRenderThread(renderFrameRunnable); | 318 runOnRenderThread(renderFrameRunnable); |
| 318 } | 319 } |
| 319 | 320 |
| 320 /** | 321 /** |
| 321 * Private helper function to post tasks safely. | 322 * Private helper function to post tasks safely. |
| 322 */ | 323 */ |
| 323 private void runOnRenderThread(Runnable runnable) { | 324 private void runOnRenderThread(Runnable runnable) { |
| 324 synchronized (threadLock) { | 325 synchronized (threadLock) { |
| 325 if (renderThreadHandler != null) { | 326 if (renderThreadHandler != null) { |
| 326 renderThreadHandler.post(runnable); | 327 renderThreadHandler.post(runnable); |
| 327 } | 328 } |
| 328 } | 329 } |
| 329 } | 330 } |
| 330 | 331 |
| 331 /** | 332 /** |
| 332 * Requests new layout if necessary. Returns true if layout and surface size a
re consistent. | 333 * Requests new layout if necessary. Returns true if layout and surface size a
re consistent. |
| 333 */ | 334 */ |
| 334 private boolean checkConsistentLayout() { | 335 private boolean checkConsistentLayout() { |
| 335 synchronized (layoutLock) { | 336 synchronized (layoutLock) { |
| 336 final Point desiredLayoutSize = getDesiredLayoutSize(); | 337 final Point desiredLayoutSize = getDesiredLayoutSize(); |
| 337 if (desiredLayoutSize.x != layoutWidth || desiredLayoutSize.y != layoutHei
ght) { | 338 if (desiredLayoutSize.x != layoutWidth || desiredLayoutSize.y != layoutHei
ght) { |
| 338 Log.d(TAG, "Requesting new layout with size: " | 339 Logging.d(TAG, "Requesting new layout with size: " |
| 339 + desiredLayoutSize.x + "x" + desiredLayoutSize.y); | 340 + desiredLayoutSize.x + "x" + desiredLayoutSize.y); |
| 340 // Request layout update on UI thread. | 341 // Request layout update on UI thread. |
| 341 post(new Runnable() { | 342 post(new Runnable() { |
| 342 @Override public void run() { | 343 @Override public void run() { |
| 343 requestLayout(); | 344 requestLayout(); |
| 344 } | 345 } |
| 345 }); | 346 }); |
| 346 return false; | 347 return false; |
| 347 } | 348 } |
| 348 // Wait for requestLayout() to propagate through this sequence before retu
rning true: | 349 // Wait for requestLayout() to propagate through this sequence before retu
rning true: |
| 349 // requestLayout() -> onMeasure() -> onLayout() -> surfaceChanged(). | 350 // requestLayout() -> onMeasure() -> onLayout() -> surfaceChanged(). |
| 350 return surfaceWidth == layoutWidth && surfaceHeight == layoutHeight; | 351 return surfaceWidth == layoutWidth && surfaceHeight == layoutHeight; |
| 351 } | 352 } |
| 352 } | 353 } |
| 353 | 354 |
| 354 /** | 355 /** |
| 355 * Renders and releases |pendingFrame|. | 356 * Renders and releases |pendingFrame|. |
| 356 */ | 357 */ |
| 357 private void renderFrameOnRenderThread() { | 358 private void renderFrameOnRenderThread() { |
| 358 if (eglBase == null || !eglBase.hasSurface()) { | 359 if (eglBase == null || !eglBase.hasSurface()) { |
| 359 Log.d(TAG, "No surface to draw on"); | 360 Logging.d(TAG, "No surface to draw on"); |
| 360 return; | 361 return; |
| 361 } | 362 } |
| 362 if (!checkConsistentLayout()) { | 363 if (!checkConsistentLayout()) { |
| 363 // Output intermediate black frames while the layout is updated. | 364 // Output intermediate black frames while the layout is updated. |
| 364 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); | 365 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); |
| 365 eglBase.swapBuffers(); | 366 eglBase.swapBuffers(); |
| 366 return; | 367 return; |
| 367 } | 368 } |
| 368 // After a surface size change, the EGLSurface might still have a buffer of
the old size in the | 369 // After a surface size change, the EGLSurface might still have a buffer of
the old size in the |
| 369 // pipeline. Querying the EGLSurface will show if the underlying buffer dime
nsions haven't yet | 370 // pipeline. Querying the EGLSurface will show if the underlying buffer dime
nsions haven't yet |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 442 } | 443 } |
| 443 | 444 |
| 444 // Update frame dimensions and report any changes to |rendererEvents|. | 445 // Update frame dimensions and report any changes to |rendererEvents|. |
| 445 private void updateFrameDimensionsAndReportEvents(VideoRenderer.I420Frame fram
e) { | 446 private void updateFrameDimensionsAndReportEvents(VideoRenderer.I420Frame fram
e) { |
| 446 synchronized (layoutLock) { | 447 synchronized (layoutLock) { |
| 447 if (frameWidth != frame.width || frameHeight != frame.height | 448 if (frameWidth != frame.width || frameHeight != frame.height |
| 448 || frameRotation != frame.rotationDegree) { | 449 || frameRotation != frame.rotationDegree) { |
| 449 if (rendererEvents != null) { | 450 if (rendererEvents != null) { |
| 450 final String id = getResources().getResourceEntryName(getId()); | 451 final String id = getResources().getResourceEntryName(getId()); |
| 451 if (frameWidth == 0 || frameHeight == 0) { | 452 if (frameWidth == 0 || frameHeight == 0) { |
| 452 Log.d(TAG, "ID: " + id + ". Reporting first rendered frame."); | 453 Logging.d(TAG, "ID: " + id + ". Reporting first rendered frame."); |
| 453 rendererEvents.onFirstFrameRendered(); | 454 rendererEvents.onFirstFrameRendered(); |
| 454 } | 455 } |
| 455 Log.d(TAG, "ID: " + id + ". Reporting frame resolution changed to " | 456 Logging.d(TAG, "ID: " + id + ". Reporting frame resolution changed to
" |
| 456 + frame.width + "x" + frame.height + " with rotation " + frame.rot
ationDegree); | 457 + frame.width + "x" + frame.height + " with rotation " + frame.rot
ationDegree); |
| 457 rendererEvents.onFrameResolutionChanged(frame.width, frame.height, fra
me.rotationDegree); | 458 rendererEvents.onFrameResolutionChanged(frame.width, frame.height, fra
me.rotationDegree); |
| 458 } | 459 } |
| 459 frameWidth = frame.width; | 460 frameWidth = frame.width; |
| 460 frameHeight = frame.height; | 461 frameHeight = frame.height; |
| 461 frameRotation = frame.rotationDegree; | 462 frameRotation = frame.rotationDegree; |
| 462 } | 463 } |
| 463 } | 464 } |
| 464 } | 465 } |
| 465 | 466 |
| 466 private void logStatistics() { | 467 private void logStatistics() { |
| 467 synchronized (statisticsLock) { | 468 synchronized (statisticsLock) { |
| 468 Log.d(TAG, "ID: " + getResources().getResourceEntryName(getId()) + ". Fram
es received: " | 469 Logging.d(TAG, "ID: " + getResources().getResourceEntryName(getId()) + ".
Frames received: " |
| 469 + framesReceived + ". Dropped: " + framesDropped + ". Rendered: " + fr
amesRendered); | 470 + framesReceived + ". Dropped: " + framesDropped + ". Rendered: " + fr
amesRendered); |
| 470 if (framesReceived > 0 && framesRendered > 0) { | 471 if (framesReceived > 0 && framesRendered > 0) { |
| 471 final long timeSinceFirstFrameNs = System.nanoTime() - firstFrameTimeNs; | 472 final long timeSinceFirstFrameNs = System.nanoTime() - firstFrameTimeNs; |
| 472 Log.d(TAG, "Duration: " + (int) (timeSinceFirstFrameNs / 1e6) + | 473 Logging.d(TAG, "Duration: " + (int) (timeSinceFirstFrameNs / 1e6) + |
| 473 " ms. FPS: " + (float) framesRendered * 1e9 / timeSinceFirstFrameNs)
; | 474 " ms. FPS: " + (float) framesRendered * 1e9 / timeSinceFirstFrameNs)
; |
| 474 Log.d(TAG, "Average render time: " | 475 Logging.d(TAG, "Average render time: " |
| 475 + (int) (renderTimeNs / (1000 * framesRendered)) + " us."); | 476 + (int) (renderTimeNs / (1000 * framesRendered)) + " us."); |
| 476 } | 477 } |
| 477 } | 478 } |
| 478 } | 479 } |
| 479 } | 480 } |
| OLD | NEW |