Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(342)

Side by Side Diff: talk/app/webrtc/androidtests/src/org/webrtc/VideoCapturerAndroidTest.java

Issue 1350863002: VideoCapturerAndroid: Fix threading issues (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Addressing hbos@s comments Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 11 matching lines...) Expand all
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 package org.webrtc; 27 package org.webrtc;
28 28
29 import android.hardware.Camera; 29 import android.hardware.Camera;
30 import android.test.ActivityTestCase; 30 import android.test.ActivityTestCase;
31 import android.test.suitebuilder.annotation.SmallTest; 31 import android.test.suitebuilder.annotation.SmallTest;
32 import android.test.suitebuilder.annotation.MediumTest;
32 import android.util.Size; 33 import android.util.Size;
33 34
34 import org.webrtc.CameraEnumerationAndroid.CaptureFormat; 35 import org.webrtc.CameraEnumerationAndroid.CaptureFormat;
35 import org.webrtc.VideoRenderer.I420Frame; 36 import org.webrtc.VideoRenderer.I420Frame;
36 37
37 import java.util.ArrayList; 38 import java.util.ArrayList;
38 import java.util.HashSet; 39 import java.util.HashSet;
39 import java.util.List; 40 import java.util.List;
40 import java.util.Set; 41 import java.util.Set;
42 import java.util.concurrent.CountDownLatch;
41 43
42 @SuppressWarnings("deprecation") 44 @SuppressWarnings("deprecation")
43 public class VideoCapturerAndroidTest extends ActivityTestCase { 45 public class VideoCapturerAndroidTest extends ActivityTestCase {
44 static class RendererCallbacks implements VideoRenderer.Callbacks { 46 static class RendererCallbacks implements VideoRenderer.Callbacks {
45 private int framesRendered = 0; 47 private int framesRendered = 0;
46 private Object frameLock = 0; 48 private Object frameLock = 0;
47 49
48 @Override 50 @Override
49 public void renderFrame(I420Frame frame) { 51 public void renderFrame(I420Frame frame) {
50 synchronized (frameLock) { 52 synchronized (frameLock) {
51 ++framesRendered; 53 ++framesRendered;
52 frameLock.notify(); 54 frameLock.notify();
53 } 55 }
54 VideoRenderer.renderFrameDone(frame); 56 VideoRenderer.renderFrameDone(frame);
55 } 57 }
56 58
57 public int WaitForNextFrameToRender() throws InterruptedException { 59 public int WaitForNextFrameToRender() throws InterruptedException {
58 synchronized (frameLock) { 60 synchronized (frameLock) {
59 frameLock.wait(); 61 frameLock.wait();
60 return framesRendered; 62 return framesRendered;
61 } 63 }
62 } 64 }
63 } 65 }
64 66
65 static class AsyncRenderer implements VideoRenderer.Callbacks { 67 static class FakeAsyncRenderer implements VideoRenderer.Callbacks {
66 private final List<I420Frame> pendingFrames = new ArrayList<I420Frame>(); 68 private final List<I420Frame> pendingFrames = new ArrayList<I420Frame>();
67 69
68 @Override 70 @Override
69 public void renderFrame(I420Frame frame) { 71 public void renderFrame(I420Frame frame) {
70 synchronized (pendingFrames) { 72 synchronized (pendingFrames) {
71 pendingFrames.add(frame); 73 pendingFrames.add(frame);
72 pendingFrames.notifyAll(); 74 pendingFrames.notifyAll();
73 } 75 }
74 } 76 }
75 77
76 // Wait until at least one frame have been received, before returning them. 78 // Wait until at least one frame have been received, before returning them.
77 public List<I420Frame> WaitForFrames() { 79 public List<I420Frame> waitForPendingFrames() throws InterruptedException {
78 synchronized (pendingFrames) { 80 synchronized (pendingFrames) {
79 while (pendingFrames.isEmpty()) { 81 while (pendingFrames.isEmpty()) {
80 try { 82 pendingFrames.wait();
81 pendingFrames.wait();
82 } catch (InterruptedException e) {
83 // Ignore.
84 }
85 } 83 }
86 final List<I420Frame> frames = new ArrayList<I420Frame>(pendingFrames); 84 return new ArrayList<I420Frame>(pendingFrames);
87 pendingFrames.clear();
88 return frames;
89 } 85 }
90 } 86 }
91 } 87 }
92 88
93 static class FakeCapturerObserver implements 89 static class FakeCapturerObserver implements
94 VideoCapturerAndroid.CapturerObserver { 90 VideoCapturerAndroid.CapturerObserver {
95 private int framesCaptured = 0; 91 private int framesCaptured = 0;
96 private int frameSize = 0; 92 private int frameSize = 0;
97 private Object frameLock = 0; 93 private Object frameLock = 0;
98 private Object capturerStartLock = 0; 94 private Object capturerStartLock = 0;
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
162 VideoCapturerAndroid.create(deviceName, null); 158 VideoCapturerAndroid.create(deviceName, null);
163 VideoSource source = 159 VideoSource source =
164 factory.createVideoSource(capturer, new MediaConstraints()); 160 factory.createVideoSource(capturer, new MediaConstraints());
165 VideoTrack track = factory.createVideoTrack("dummy", source); 161 VideoTrack track = factory.createVideoTrack("dummy", source);
166 RendererCallbacks callbacks = new RendererCallbacks(); 162 RendererCallbacks callbacks = new RendererCallbacks();
167 track.addRenderer(new VideoRenderer(callbacks)); 163 track.addRenderer(new VideoRenderer(callbacks));
168 assertTrue(callbacks.WaitForNextFrameToRender() > 0); 164 assertTrue(callbacks.WaitForNextFrameToRender() > 0);
169 track.dispose(); 165 track.dispose();
170 source.dispose(); 166 source.dispose();
171 factory.dispose(); 167 factory.dispose();
168 assertTrue(capturer.isReleased());
172 } 169 }
173 170
174 @Override 171 @Override
175 protected void setUp() { 172 protected void setUp() {
176 assertTrue(PeerConnectionFactory.initializeAndroidGlobals( 173 assertTrue(PeerConnectionFactory.initializeAndroidGlobals(
177 getInstrumentation().getContext(), true, true, true)); 174 getInstrumentation().getContext(), true, true, true));
178 } 175 }
179 176
180 @SmallTest 177 @SmallTest
181 // Test that enumerating formats using android.hardware.camera2 will give the same formats as 178 // Test that enumerating formats using android.hardware.camera2 will give the same formats as
(...skipping 24 matching lines...) Expand all
206 } 203 }
207 } 204 }
208 } 205 }
209 } 206 }
210 207
211 @SmallTest 208 @SmallTest
212 public void testCreateAndRelease() throws Exception { 209 public void testCreateAndRelease() throws Exception {
213 VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null); 210 VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null);
214 assertNotNull(capturer); 211 assertNotNull(capturer);
215 capturer.dispose(); 212 capturer.dispose();
213 assertTrue(capturer.isReleased());
216 } 214 }
217 215
218 @SmallTest 216 @SmallTest
219 public void testCreateNonExistingCamera() throws Exception { 217 public void testCreateNonExistingCamera() throws Exception {
220 VideoCapturerAndroid capturer = VideoCapturerAndroid.create( 218 VideoCapturerAndroid capturer = VideoCapturerAndroid.create(
221 "non-existing camera", null); 219 "non-existing camera", null);
222 assertNull(capturer); 220 assertNull(capturer);
223 } 221 }
224 222
225 @SmallTest 223 @SmallTest
(...skipping 17 matching lines...) Expand all
243 // to a Java video renderer using the back facing video capturer. 241 // to a Java video renderer using the back facing video capturer.
244 // It tests both the Java and the C++ layer. 242 // It tests both the Java and the C++ layer.
245 public void testStartBackFacingVideoCapturer() throws Exception { 243 public void testStartBackFacingVideoCapturer() throws Exception {
246 if (!HaveTwoCameras()) { 244 if (!HaveTwoCameras()) {
247 return; 245 return;
248 } 246 }
249 startCapturerAndRender(CameraEnumerationAndroid.getNameOfBackFacingDevice()) ; 247 startCapturerAndRender(CameraEnumerationAndroid.getNameOfBackFacingDevice()) ;
250 } 248 }
251 249
252 @SmallTest 250 @SmallTest
253 // This test that the default camera can be started and but the camera can 251 // This test that the default camera can be started and that the camera can
254 // later be switched to another camera. 252 // later be switched to another camera.
255 // It tests both the Java and the C++ layer. 253 // It tests both the Java and the C++ layer.
256 public void testSwitchVideoCapturer() throws Exception { 254 public void testSwitchVideoCapturer() throws Exception {
257 PeerConnectionFactory factory = new PeerConnectionFactory(); 255 PeerConnectionFactory factory = new PeerConnectionFactory();
258 VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null); 256 VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null);
259 VideoSource source = 257 VideoSource source =
260 factory.createVideoSource(capturer, new MediaConstraints()); 258 factory.createVideoSource(capturer, new MediaConstraints());
261 VideoTrack track = factory.createVideoTrack("dummy", source); 259 VideoTrack track = factory.createVideoTrack("dummy", source);
262 260
263 if (HaveTwoCameras()) 261 // Array with one element to avoid final problem in nested classes.
264 assertTrue(capturer.switchCamera(null)); 262 final boolean[] cameraSwitchSuccessful = new boolean[1];
265 else 263 final CountDownLatch barrier = new CountDownLatch(1);
266 assertFalse(capturer.switchCamera(null)); 264 capturer.switchCamera(new VideoCapturerAndroid.CameraSwitchHandler() {
265 @Override
266 public void onCameraSwitchDone(boolean isFrontCamera) {
267 cameraSwitchSuccessful[0] = true;
268 barrier.countDown();
269 }
270 @Override
271 public void onCameraSwitchError(String errorDescription) {
272 cameraSwitchSuccessful[0] = false;
273 barrier.countDown();
274 }
275 });
276 // Wait until the camera has been switched.
277 barrier.await();
267 278
268 // Wait until the camera have been switched. 279 // Check result.
269 capturer.runCameraThreadUntilIdle(); 280 if (HaveTwoCameras()) {
270 281 assertTrue(cameraSwitchSuccessful[0]);
282 } else {
283 assertFalse(cameraSwitchSuccessful[0]);
284 }
271 // Ensure that frames are received. 285 // Ensure that frames are received.
272 RendererCallbacks callbacks = new RendererCallbacks(); 286 RendererCallbacks callbacks = new RendererCallbacks();
273 track.addRenderer(new VideoRenderer(callbacks)); 287 track.addRenderer(new VideoRenderer(callbacks));
274 assertTrue(callbacks.WaitForNextFrameToRender() > 0); 288 assertTrue(callbacks.WaitForNextFrameToRender() > 0);
275 track.dispose(); 289 track.dispose();
276 source.dispose(); 290 source.dispose();
277 factory.dispose(); 291 factory.dispose();
292 assertTrue(capturer.isReleased());
278 } 293 }
279 294
280 @SmallTest 295 @SmallTest
281 // This test that the VideoSource that the VideoCapturer is connected to can 296 // This test that the VideoSource that the VideoCapturer is connected to can
282 // be stopped and restarted. It tests both the Java and the C++ layer. 297 // be stopped and restarted. It tests both the Java and the C++ layer.
283 public void testStopRestartVideoSource() throws Exception { 298 public void testStopRestartVideoSource() throws Exception {
284 PeerConnectionFactory factory = new PeerConnectionFactory(); 299 PeerConnectionFactory factory = new PeerConnectionFactory();
285 VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null); 300 VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null);
286 VideoSource source = 301 VideoSource source =
287 factory.createVideoSource(capturer, new MediaConstraints()); 302 factory.createVideoSource(capturer, new MediaConstraints());
288 VideoTrack track = factory.createVideoTrack("dummy", source); 303 VideoTrack track = factory.createVideoTrack("dummy", source);
289 RendererCallbacks callbacks = new RendererCallbacks(); 304 RendererCallbacks callbacks = new RendererCallbacks();
290 track.addRenderer(new VideoRenderer(callbacks)); 305 track.addRenderer(new VideoRenderer(callbacks));
291 assertTrue(callbacks.WaitForNextFrameToRender() > 0); 306 assertTrue(callbacks.WaitForNextFrameToRender() > 0);
292 assertEquals(MediaSource.State.LIVE, source.state()); 307 assertEquals(MediaSource.State.LIVE, source.state());
293 308
294 source.stop(); 309 source.stop();
295 assertEquals(MediaSource.State.ENDED, source.state()); 310 assertEquals(MediaSource.State.ENDED, source.state());
296 311
297 source.restart(); 312 source.restart();
298 assertTrue(callbacks.WaitForNextFrameToRender() > 0); 313 assertTrue(callbacks.WaitForNextFrameToRender() > 0);
299 assertEquals(MediaSource.State.LIVE, source.state()); 314 assertEquals(MediaSource.State.LIVE, source.state());
300 track.dispose(); 315 track.dispose();
301 source.dispose(); 316 source.dispose();
302 factory.dispose(); 317 factory.dispose();
318 assertTrue(capturer.isReleased());
303 } 319 }
304 320
305 @SmallTest 321 @SmallTest
306 // This test that the camera can be started at different resolutions. 322 // This test that the camera can be started at different resolutions.
307 // It does not test or use the C++ layer. 323 // It does not test or use the C++ layer.
308 public void testStartStopWithDifferentResolutions() throws Exception { 324 public void testStartStopWithDifferentResolutions() throws Exception {
309 FakeCapturerObserver observer = new FakeCapturerObserver(); 325 FakeCapturerObserver observer = new FakeCapturerObserver();
310 326
311 String deviceName = CameraEnumerationAndroid.getDeviceName(0); 327 String deviceName = CameraEnumerationAndroid.getDeviceName(0);
312 List<CaptureFormat> formats = CameraEnumerationAndroid.getSupportedFormats(0 ); 328 List<CaptureFormat> formats = CameraEnumerationAndroid.getSupportedFormats(0 );
313 VideoCapturerAndroid capturer = 329 VideoCapturerAndroid capturer =
314 VideoCapturerAndroid.create(deviceName, null); 330 VideoCapturerAndroid.create(deviceName, null);
315 331
316 for(int i = 0; i < 3 ; ++i) { 332 for(int i = 0; i < 3 ; ++i) {
317 CameraEnumerationAndroid.CaptureFormat format = formats.get(i); 333 CameraEnumerationAndroid.CaptureFormat format = formats.get(i);
318 capturer.startCapture(format.width, format.height, format.maxFramerate, 334 capturer.startCapture(format.width, format.height, format.maxFramerate,
319 getInstrumentation().getContext(), observer); 335 getInstrumentation().getContext(), observer);
320 assertTrue(observer.WaitForCapturerToStart()); 336 assertTrue(observer.WaitForCapturerToStart());
321 observer.WaitForNextCapturedFrame(); 337 observer.WaitForNextCapturedFrame();
322 // Check the frame size. 338 // Check the frame size.
323 assertEquals(format.frameSize(), observer.frameSize()); 339 assertEquals(format.frameSize(), observer.frameSize());
324 capturer.stopCapture(); 340 capturer.stopCapture();
341 for (long timestamp : observer.getCopyAndResetListOftimeStamps()) {
342 capturer.returnBuffer(timestamp);
343 }
325 } 344 }
326 capturer.dispose(); 345 capturer.dispose();
346 assertTrue(capturer.isReleased());
327 } 347 }
328 348
329 @SmallTest 349 @SmallTest
330 // This test what happens if buffers are returned after the capturer have 350 // This test what happens if buffers are returned after the capturer have
331 // been stopped and restarted. It does not test or use the C++ layer. 351 // been stopped and restarted. It does not test or use the C++ layer.
332 public void testReturnBufferLate() throws Exception { 352 public void testReturnBufferLate() throws Exception {
333 FakeCapturerObserver observer = new FakeCapturerObserver(); 353 FakeCapturerObserver observer = new FakeCapturerObserver();
334 354
335 String deviceName = CameraEnumerationAndroid.getDeviceName(0); 355 String deviceName = CameraEnumerationAndroid.getDeviceName(0);
336 List<CaptureFormat> formats = CameraEnumerationAndroid.getSupportedFormats(0 ); 356 List<CaptureFormat> formats = CameraEnumerationAndroid.getSupportedFormats(0 );
(...skipping 21 matching lines...) Expand all
358 } 378 }
359 379
360 observer.WaitForNextCapturedFrame(); 380 observer.WaitForNextCapturedFrame();
361 capturer.stopCapture(); 381 capturer.stopCapture();
362 382
363 listOftimestamps = observer.getCopyAndResetListOftimeStamps(); 383 listOftimestamps = observer.getCopyAndResetListOftimeStamps();
364 assertTrue(listOftimestamps.size() >= 2); 384 assertTrue(listOftimestamps.size() >= 2);
365 for (Long timeStamp : listOftimestamps) { 385 for (Long timeStamp : listOftimestamps) {
366 capturer.returnBuffer(timeStamp); 386 capturer.returnBuffer(timeStamp);
367 } 387 }
388 capturer.dispose();
389 assertTrue(capturer.isReleased());
368 } 390 }
369 391
370 @SmallTest 392 @MediumTest
371 // This test that we can capture frames, stop capturing, keep the frames for r endering, and then 393 // This test that we can capture frames, keep the frames in a local renderer, stop capturing,
372 // return the frames. It tests both the Java and the C++ layer. 394 // and then return the frames. The difference between the test testReturnBuffe rLate() is that we
373 public void testCaptureAndAsyncRender() { 395 // also test the JNI and C++ AndroidVideoCapturer parts.
396 public void testReturnBufferLateEndToEnd() throws InterruptedException {
374 final VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null); 397 final VideoCapturerAndroid capturer = VideoCapturerAndroid.create("", null);
375 // Helper class that sets everything up, captures at least one frame, and th en shuts 398 final PeerConnectionFactory factory = new PeerConnectionFactory();
376 // everything down. 399 final VideoSource source = factory.createVideoSource(capturer, new MediaCons traints());
377 class CaptureFramesRunnable implements Runnable { 400 final VideoTrack track = factory.createVideoTrack("dummy", source);
378 public List<I420Frame> frames; 401 final FakeAsyncRenderer renderer = new FakeAsyncRenderer();
402 track.addRenderer(new VideoRenderer(renderer));
403 // Wait for at least one frame that has not been returned.
404 assertFalse(renderer.waitForPendingFrames().isEmpty());
379 405
406 capturer.stopCapture();
407
408 // Dispose source and |capturer|.
409 track.dispose();
410 source.dispose();
411 // The pending frames should keep the JNI parts and |capturer| alive.
412 assertFalse(capturer.isReleased());
413
414 // Return the frame(s), on a different thread out of spite.
415 final List<I420Frame> pendingFrames = renderer.waitForPendingFrames();
416 final Thread returnThread = new Thread(new Runnable() {
380 @Override 417 @Override
381 public void run() { 418 public void run() {
382 PeerConnectionFactory factory = new PeerConnectionFactory(); 419 for (I420Frame frame : pendingFrames) {
383 VideoSource source = factory.createVideoSource(capturer, new MediaConstr aints()); 420 VideoRenderer.renderFrameDone(frame);
384 VideoTrack track = factory.createVideoTrack("dummy", source); 421 }
385 AsyncRenderer renderer = new AsyncRenderer(); 422 }
386 track.addRenderer(new VideoRenderer(renderer)); 423 });
424 returnThread.start();
425 returnThread.join();
387 426
388 // Wait until we get at least one frame. 427 // Check that frames have successfully returned. This will cause |capturer| to be released.
389 frames = renderer.WaitForFrames(); 428 assertTrue(capturer.isReleased());
390 429
391 // Stop everything. 430 factory.dispose();
392 track.dispose();
393 source.dispose();
394 factory.dispose();
395 }
396 }
397
398 // Capture frames on a separate thread.
399 CaptureFramesRunnable captureFramesRunnable = new CaptureFramesRunnable();
400 Thread captureThread = new Thread(captureFramesRunnable);
401 captureThread.start();
402
403 // Wait until frames are captured, and then kill the thread.
404 try {
405 captureThread.join();
406 } catch (InterruptedException e) {
407 fail("Capture thread was interrupted");
408 }
409 captureThread = null;
410
411 // Assert that we have frames that have not been returned.
412 assertTrue(!captureFramesRunnable.frames.isEmpty());
413 // Return the frame(s).
414 for (I420Frame frame : captureFramesRunnable.frames) {
415 VideoRenderer.renderFrameDone(frame);
416 }
417 assertEquals(capturer.pendingFramesTimeStamps(), "[]");
418 } 431 }
419 } 432 }
OLDNEW
« no previous file with comments | « no previous file | talk/app/webrtc/java/android/org/webrtc/VideoCapturerAndroid.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698