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 |