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 11 matching lines...) Expand all Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |