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