1 /*
2  *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 package org.appspot.apprtc.test;
12 
13 import static org.junit.Assert.assertTrue;
14 import static org.junit.Assert.fail;
15 
16 import android.os.Build;
17 import android.support.test.InstrumentationRegistry;
18 import android.support.test.filters.SmallTest;
19 import android.support.test.runner.AndroidJUnit4;
20 import android.util.Log;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.concurrent.CountDownLatch;
24 import java.util.concurrent.ExecutorService;
25 import java.util.concurrent.Executors;
26 import java.util.concurrent.TimeUnit;
27 import org.appspot.apprtc.AppRTCClient.SignalingParameters;
28 import org.appspot.apprtc.PeerConnectionClient;
29 import org.appspot.apprtc.PeerConnectionClient.PeerConnectionEvents;
30 import org.appspot.apprtc.PeerConnectionClient.PeerConnectionParameters;
31 import org.junit.After;
32 import org.junit.Before;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.webrtc.Camera1Enumerator;
36 import org.webrtc.Camera2Enumerator;
37 import org.webrtc.CameraEnumerator;
38 import org.webrtc.EglBase;
39 import org.webrtc.IceCandidate;
40 import org.webrtc.PeerConnection;
41 import org.webrtc.PeerConnectionFactory;
42 import org.webrtc.SessionDescription;
43 import org.webrtc.StatsReport;
44 import org.webrtc.VideoCapturer;
45 import org.webrtc.VideoFrame;
46 import org.webrtc.VideoSink;
47 
48 @RunWith(AndroidJUnit4.class)
49 public class PeerConnectionClientTest implements PeerConnectionEvents {
50   private static final String TAG = "RTCClientTest";
51   private static final int ICE_CONNECTION_WAIT_TIMEOUT = 10000;
52   private static final int WAIT_TIMEOUT = 7000;
53   private static final int CAMERA_SWITCH_ATTEMPTS = 3;
54   private static final int VIDEO_RESTART_ATTEMPTS = 3;
55   private static final int CAPTURE_FORMAT_CHANGE_ATTEMPTS = 3;
56   private static final int VIDEO_RESTART_TIMEOUT = 500;
57   private static final int EXPECTED_VIDEO_FRAMES = 10;
58   private static final String VIDEO_CODEC_VP8 = "VP8";
59   private static final String VIDEO_CODEC_VP9 = "VP9";
60   private static final String VIDEO_CODEC_H264 = "H264";
61   private static final int AUDIO_RUN_TIMEOUT = 1000;
62   private static final String LOCAL_RENDERER_NAME = "Local renderer";
63   private static final String REMOTE_RENDERER_NAME = "Remote renderer";
64 
65   private static final int MAX_VIDEO_FPS = 30;
66   private static final int WIDTH_VGA = 640;
67   private static final int HEIGHT_VGA = 480;
68   private static final int WIDTH_QVGA = 320;
69   private static final int HEIGHT_QVGA = 240;
70 
71   // The peer connection client is assumed to be thread safe in itself; the
72   // reference is written by the test thread and read by worker threads.
73   private volatile PeerConnectionClient pcClient;
74   private volatile boolean loopback;
75 
76   // These are protected by their respective event objects.
77   private ExecutorService signalingExecutor;
78   private boolean isClosed;
79   private boolean isIceConnected;
80   private SessionDescription localDesc;
81   private List<IceCandidate> iceCandidates = new ArrayList<>();
82   private final Object localDescEvent = new Object();
83   private final Object iceCandidateEvent = new Object();
84   private final Object iceConnectedEvent = new Object();
85   private final Object closeEvent = new Object();
86 
87   // Mock VideoSink implementation.
88   private static class MockSink implements VideoSink {
89     // These are protected by 'this' since we gets called from worker threads.
90     private String rendererName;
91     private boolean renderFrameCalled;
92 
93     // Thread-safe in itself.
94     private CountDownLatch doneRendering;
95 
MockSink(int expectedFrames, String rendererName)96     public MockSink(int expectedFrames, String rendererName) {
97       this.rendererName = rendererName;
98       reset(expectedFrames);
99     }
100 
101     // Resets render to wait for new amount of video frames.
102     // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
103     @SuppressWarnings("NoSynchronizedMethodCheck")
reset(int expectedFrames)104     public synchronized void reset(int expectedFrames) {
105       renderFrameCalled = false;
106       doneRendering = new CountDownLatch(expectedFrames);
107     }
108 
109     @Override
110     // TODO(bugs.webrtc.org/8491): Remove NoSynchronizedMethodCheck suppression.
111     @SuppressWarnings("NoSynchronizedMethodCheck")
onFrame(VideoFrame frame)112     public synchronized void onFrame(VideoFrame frame) {
113       if (!renderFrameCalled) {
114         if (rendererName != null) {
115           Log.d(TAG,
116               rendererName + " render frame: " + frame.getRotatedWidth() + " x "
117                   + frame.getRotatedHeight());
118         } else {
119           Log.d(TAG, "Render frame: " + frame.getRotatedWidth() + " x " + frame.getRotatedHeight());
120         }
121       }
122       renderFrameCalled = true;
123       doneRendering.countDown();
124     }
125 
126     // This method shouldn't hold any locks or touch member variables since it
127     // blocks.
waitForFramesRendered(int timeoutMs)128     public boolean waitForFramesRendered(int timeoutMs) throws InterruptedException {
129       doneRendering.await(timeoutMs, TimeUnit.MILLISECONDS);
130       return (doneRendering.getCount() <= 0);
131     }
132   }
133 
134   // Peer connection events implementation.
135   @Override
onLocalDescription(SessionDescription desc)136   public void onLocalDescription(SessionDescription desc) {
137     Log.d(TAG, "Local description type: " + desc.type);
138     synchronized (localDescEvent) {
139       localDesc = desc;
140       localDescEvent.notifyAll();
141     }
142   }
143 
144   @Override
onIceCandidate(final IceCandidate candidate)145   public void onIceCandidate(final IceCandidate candidate) {
146     synchronized (iceCandidateEvent) {
147       Log.d(TAG, "IceCandidate #" + iceCandidates.size() + " : " + candidate.toString());
148       if (loopback) {
149         // Loopback local ICE candidate in a separate thread to avoid adding
150         // remote ICE candidate in a local ICE candidate callback.
151         signalingExecutor.execute(new Runnable() {
152           @Override
153           public void run() {
154             pcClient.addRemoteIceCandidate(candidate);
155           }
156         });
157       }
158       iceCandidates.add(candidate);
159       iceCandidateEvent.notifyAll();
160     }
161   }
162 
163   @Override
onIceCandidatesRemoved(final IceCandidate[] candidates)164   public void onIceCandidatesRemoved(final IceCandidate[] candidates) {
165     // TODO(honghaiz): Add this for tests.
166   }
167 
168   @Override
onIceConnected()169   public void onIceConnected() {
170     Log.d(TAG, "ICE Connected");
171     synchronized (iceConnectedEvent) {
172       isIceConnected = true;
173       iceConnectedEvent.notifyAll();
174     }
175   }
176 
177   @Override
onIceDisconnected()178   public void onIceDisconnected() {
179     Log.d(TAG, "ICE Disconnected");
180     synchronized (iceConnectedEvent) {
181       isIceConnected = false;
182       iceConnectedEvent.notifyAll();
183     }
184   }
185 
186   @Override
onConnected()187   public void onConnected() {
188     Log.d(TAG, "DTLS Connected");
189   }
190 
191   @Override
onDisconnected()192   public void onDisconnected() {
193     Log.d(TAG, "DTLS Disconnected");
194   }
195 
196   @Override
onPeerConnectionClosed()197   public void onPeerConnectionClosed() {
198     Log.d(TAG, "PeerConnection closed");
199     synchronized (closeEvent) {
200       isClosed = true;
201       closeEvent.notifyAll();
202     }
203   }
204 
205   @Override
onPeerConnectionError(String description)206   public void onPeerConnectionError(String description) {
207     fail("PC Error: " + description);
208   }
209 
210   @Override
onPeerConnectionStatsReady(StatsReport[] reports)211   public void onPeerConnectionStatsReady(StatsReport[] reports) {}
212 
213   // Helper wait functions.
waitForLocalDescription(int timeoutMs)214   private boolean waitForLocalDescription(int timeoutMs) throws InterruptedException {
215     synchronized (localDescEvent) {
216       final long endTimeMs = System.currentTimeMillis() + timeoutMs;
217       while (localDesc == null) {
218         final long waitTimeMs = endTimeMs - System.currentTimeMillis();
219         if (waitTimeMs < 0) {
220           return false;
221         }
222         localDescEvent.wait(waitTimeMs);
223       }
224       return true;
225     }
226   }
227 
waitForIceCandidates(int timeoutMs)228   private boolean waitForIceCandidates(int timeoutMs) throws InterruptedException {
229     synchronized (iceCandidateEvent) {
230       final long endTimeMs = System.currentTimeMillis() + timeoutMs;
231       while (iceCandidates.size() == 0) {
232         final long waitTimeMs = endTimeMs - System.currentTimeMillis();
233         if (waitTimeMs < 0) {
234           return false;
235         }
236         iceCandidateEvent.wait(timeoutMs);
237       }
238       return true;
239     }
240   }
241 
waitForIceConnected(int timeoutMs)242   private boolean waitForIceConnected(int timeoutMs) throws InterruptedException {
243     synchronized (iceConnectedEvent) {
244       final long endTimeMs = System.currentTimeMillis() + timeoutMs;
245       while (!isIceConnected) {
246         final long waitTimeMs = endTimeMs - System.currentTimeMillis();
247         if (waitTimeMs < 0) {
248           Log.e(TAG, "ICE connection failure");
249           return false;
250         }
251         iceConnectedEvent.wait(timeoutMs);
252       }
253       return true;
254     }
255   }
256 
waitForPeerConnectionClosed(int timeoutMs)257   private boolean waitForPeerConnectionClosed(int timeoutMs) throws InterruptedException {
258     synchronized (closeEvent) {
259       final long endTimeMs = System.currentTimeMillis() + timeoutMs;
260       while (!isClosed) {
261         final long waitTimeMs = endTimeMs - System.currentTimeMillis();
262         if (waitTimeMs < 0) {
263           return false;
264         }
265         closeEvent.wait(timeoutMs);
266       }
267       return true;
268     }
269   }
270 
createPeerConnectionClient(MockSink localRenderer, MockSink remoteRenderer, PeerConnectionParameters peerConnectionParameters, VideoCapturer videoCapturer)271   PeerConnectionClient createPeerConnectionClient(MockSink localRenderer, MockSink remoteRenderer,
272       PeerConnectionParameters peerConnectionParameters, VideoCapturer videoCapturer) {
273     List<PeerConnection.IceServer> iceServers = new ArrayList<>();
274     SignalingParameters signalingParameters =
275         new SignalingParameters(iceServers, true, // iceServers, initiator.
276             null, null, null, // clientId, wssUrl, wssPostUrl.
277             null, null); // offerSdp, iceCandidates.
278 
279     final EglBase eglBase = EglBase.create();
280     PeerConnectionClient client =
281         new PeerConnectionClient(InstrumentationRegistry.getTargetContext(), eglBase,
282             peerConnectionParameters, this /* PeerConnectionEvents */);
283     PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
284     options.networkIgnoreMask = 0;
285     options.disableNetworkMonitor = true;
286     client.createPeerConnectionFactory(options);
287     client.createPeerConnection(localRenderer, remoteRenderer, videoCapturer, signalingParameters);
288     client.createOffer();
289     return client;
290   }
291 
createParametersForAudioCall()292   private PeerConnectionParameters createParametersForAudioCall() {
293     return new PeerConnectionParameters(false, /* videoCallEnabled */
294         true, /* loopback */
295         false, /* tracing */
296         // Video codec parameters.
297         0, /* videoWidth */
298         0, /* videoHeight */
299         0, /* videoFps */
300         0, /* videoStartBitrate */
301         "", /* videoCodec */
302         true, /* videoCodecHwAcceleration */
303         false, /* videoFlexfecEnabled */
304         // Audio codec parameters.
305         0, /* audioStartBitrate */
306         "OPUS", /* audioCodec */
307         false, /* noAudioProcessing */
308         false, /* aecDump */
309         false, /* saveInputAudioToFile */
310         false /* useOpenSLES */, false /* disableBuiltInAEC */, false /* disableBuiltInAGC */,
311         false /* disableBuiltInNS */, false /* disableWebRtcAGC */, false /* enableRtcEventLog */,
312         null /* dataChannelParameters */);
313   }
314 
createCameraCapturer(boolean captureToTexture)315   private VideoCapturer createCameraCapturer(boolean captureToTexture) {
316     final boolean useCamera2 = captureToTexture
317         && Camera2Enumerator.isSupported(InstrumentationRegistry.getTargetContext());
318 
319     CameraEnumerator enumerator;
320     if (useCamera2) {
321       enumerator = new Camera2Enumerator(InstrumentationRegistry.getTargetContext());
322     } else {
323       enumerator = new Camera1Enumerator(captureToTexture);
324     }
325     String deviceName = enumerator.getDeviceNames()[0];
326     return enumerator.createCapturer(deviceName, null);
327   }
328 
createParametersForVideoCall(String videoCodec)329   private PeerConnectionParameters createParametersForVideoCall(String videoCodec) {
330     return new PeerConnectionParameters(true, /* videoCallEnabled */
331         true, /* loopback */
332         false, /* tracing */
333         // Video codec parameters.
334         0, /* videoWidth */
335         0, /* videoHeight */
336         0, /* videoFps */
337         0, /* videoStartBitrate */
338         videoCodec, /* videoCodec */
339         true, /* videoCodecHwAcceleration */
340         false, /* videoFlexfecEnabled */
341         // Audio codec parameters.
342         0, /* audioStartBitrate */
343         "OPUS", /* audioCodec */
344         false, /* noAudioProcessing */
345         false, /* aecDump */
346         false, /* saveInputAudioToFile */
347         false /* useOpenSLES */, false /* disableBuiltInAEC */, false /* disableBuiltInAGC */,
348         false /* disableBuiltInNS */, false /* disableWebRtcAGC */, false /* enableRtcEventLog */,
349         null /* dataChannelParameters */);
350   }
351 
352   @Before
setUp()353   public void setUp() {
354     signalingExecutor = Executors.newSingleThreadExecutor();
355   }
356 
357   @After
tearDown()358   public void tearDown() {
359     signalingExecutor.shutdown();
360   }
361 
362   @Test
363   @SmallTest
testSetLocalOfferMakesVideoFlowLocally()364   public void testSetLocalOfferMakesVideoFlowLocally() throws InterruptedException {
365     Log.d(TAG, "testSetLocalOfferMakesVideoFlowLocally");
366     MockSink localRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, LOCAL_RENDERER_NAME);
367     pcClient = createPeerConnectionClient(localRenderer,
368         new MockSink(/* expectedFrames= */ 0, /* rendererName= */ null),
369         createParametersForVideoCall(VIDEO_CODEC_VP8),
370         createCameraCapturer(false /* captureToTexture */));
371 
372     // Wait for local description and ice candidates set events.
373     assertTrue("Local description was not set.", waitForLocalDescription(WAIT_TIMEOUT));
374     assertTrue("ICE candidates were not generated.", waitForIceCandidates(WAIT_TIMEOUT));
375 
376     // Check that local video frames were rendered.
377     assertTrue(
378         "Local video frames were not rendered.", localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
379 
380     pcClient.close();
381     assertTrue(
382         "PeerConnection close event was not received.", waitForPeerConnectionClosed(WAIT_TIMEOUT));
383     Log.d(TAG, "testSetLocalOfferMakesVideoFlowLocally Done.");
384   }
385 
doLoopbackTest(PeerConnectionParameters parameters, VideoCapturer videoCapturer, boolean decodeToTexture)386   private void doLoopbackTest(PeerConnectionParameters parameters, VideoCapturer videoCapturer,
387       boolean decodeToTexture) throws InterruptedException {
388     loopback = true;
389     MockSink localRenderer = null;
390     MockSink remoteRenderer = null;
391     if (parameters.videoCallEnabled) {
392       Log.d(TAG, "testLoopback for video " + parameters.videoCodec);
393       localRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, LOCAL_RENDERER_NAME);
394       remoteRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, REMOTE_RENDERER_NAME);
395     } else {
396       Log.d(TAG, "testLoopback for audio.");
397     }
398     pcClient = createPeerConnectionClient(localRenderer, remoteRenderer, parameters, videoCapturer);
399 
400     // Wait for local description, change type to answer and set as remote description.
401     assertTrue("Local description was not set.", waitForLocalDescription(WAIT_TIMEOUT));
402     SessionDescription remoteDescription = new SessionDescription(
403         SessionDescription.Type.fromCanonicalForm("answer"), localDesc.description);
404     pcClient.setRemoteDescription(remoteDescription);
405 
406     // Wait for ICE connection.
407     assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAIT_TIMEOUT));
408 
409     if (parameters.videoCallEnabled) {
410       // Check that local and remote video frames were rendered.
411       assertTrue("Local video frames were not rendered.",
412           localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
413       assertTrue("Remote video frames were not rendered.",
414           remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
415     } else {
416       // For audio just sleep for 1 sec.
417       // TODO(glaznev): check how we can detect that remote audio was rendered.
418       Thread.sleep(AUDIO_RUN_TIMEOUT);
419     }
420 
421     pcClient.close();
422     assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT));
423     Log.d(TAG, "testLoopback done.");
424   }
425 
426   @Test
427   @SmallTest
testLoopbackAudio()428   public void testLoopbackAudio() throws InterruptedException {
429     doLoopbackTest(createParametersForAudioCall(), null, false /* decodeToTexture */);
430   }
431 
432   @Test
433   @SmallTest
testLoopbackVp8()434   public void testLoopbackVp8() throws InterruptedException {
435     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_VP8),
436         createCameraCapturer(false /* captureToTexture */), false /* decodeToTexture */);
437   }
438 
439   @Test
440   @SmallTest
testLoopbackVp9()441   public void testLoopbackVp9() throws InterruptedException {
442     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_VP9),
443         createCameraCapturer(false /* captureToTexture */), false /* decodeToTexture */);
444   }
445 
446   @Test
447   @SmallTest
testLoopbackH264()448   public void testLoopbackH264() throws InterruptedException {
449     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_H264),
450         createCameraCapturer(false /* captureToTexture */), false /* decodeToTexture */);
451   }
452 
453   @Test
454   @SmallTest
testLoopbackVp8DecodeToTexture()455   public void testLoopbackVp8DecodeToTexture() throws InterruptedException {
456     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
457       Log.i(TAG, "Decode to textures is not supported, requires SDK version 19.");
458       return;
459     }
460     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_VP8),
461         createCameraCapturer(false /* captureToTexture */), true /* decodeToTexture */);
462   }
463 
464   @Test
465   @SmallTest
testLoopbackVp9DecodeToTexture()466   public void testLoopbackVp9DecodeToTexture() throws InterruptedException {
467     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
468       Log.i(TAG, "Decode to textures is not supported, requires SDK version 19.");
469       return;
470     }
471     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_VP9),
472         createCameraCapturer(false /* captureToTexture */), true /* decodeToTexture */);
473   }
474 
475   @Test
476   @SmallTest
testLoopbackH264DecodeToTexture()477   public void testLoopbackH264DecodeToTexture() throws InterruptedException {
478     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
479       Log.i(TAG, "Decode to textures is not supported, requires SDK version 19.");
480       return;
481     }
482     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_H264),
483         createCameraCapturer(false /* captureToTexture */), true /* decodeToTexture */);
484   }
485 
486   @Test
487   @SmallTest
testLoopbackVp8CaptureToTexture()488   public void testLoopbackVp8CaptureToTexture() throws InterruptedException {
489     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
490       Log.i(TAG, "Encode to textures is not supported. Requires SDK version 19");
491       return;
492     }
493     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_VP8),
494         createCameraCapturer(true /* captureToTexture */), true /* decodeToTexture */);
495   }
496 
497   @Test
498   @SmallTest
testLoopbackH264CaptureToTexture()499   public void testLoopbackH264CaptureToTexture() throws InterruptedException {
500     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
501       Log.i(TAG, "Encode to textures is not supported. Requires KITKAT");
502       return;
503     }
504     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_H264),
505         createCameraCapturer(true /* captureToTexture */), true /* decodeToTexture */);
506   }
507 
508   // Checks if default front camera can be switched to back camera and then
509   // again to front camera.
510   @Test
511   @SmallTest
testCameraSwitch()512   public void testCameraSwitch() throws InterruptedException {
513     Log.d(TAG, "testCameraSwitch");
514     loopback = true;
515 
516     MockSink localRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, LOCAL_RENDERER_NAME);
517     MockSink remoteRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, REMOTE_RENDERER_NAME);
518 
519     pcClient = createPeerConnectionClient(localRenderer, remoteRenderer,
520         createParametersForVideoCall(VIDEO_CODEC_VP8),
521         createCameraCapturer(false /* captureToTexture */));
522 
523     // Wait for local description, set type to answer and set as remote description.
524     assertTrue("Local description was not set.", waitForLocalDescription(WAIT_TIMEOUT));
525     SessionDescription remoteDescription = new SessionDescription(
526         SessionDescription.Type.fromCanonicalForm("answer"), localDesc.description);
527     pcClient.setRemoteDescription(remoteDescription);
528 
529     // Wait for ICE connection.
530     assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAIT_TIMEOUT));
531 
532     // Check that local and remote video frames were rendered.
533     assertTrue("Local video frames were not rendered before camera switch.",
534         localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
535     assertTrue("Remote video frames were not rendered before camera switch.",
536         remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
537 
538     for (int i = 0; i < CAMERA_SWITCH_ATTEMPTS; i++) {
539       // Try to switch camera
540       pcClient.switchCamera();
541 
542       // Reset video renders and check that local and remote video frames
543       // were rendered after camera switch.
544       localRenderer.reset(EXPECTED_VIDEO_FRAMES);
545       remoteRenderer.reset(EXPECTED_VIDEO_FRAMES);
546       assertTrue("Local video frames were not rendered after camera switch.",
547           localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
548       assertTrue("Remote video frames were not rendered after camera switch.",
549           remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
550     }
551     pcClient.close();
552     assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT));
553     Log.d(TAG, "testCameraSwitch done.");
554   }
555 
556   // Checks if video source can be restarted - simulate app goes to
557   // background and back to foreground.
558   @Test
559   @SmallTest
testVideoSourceRestart()560   public void testVideoSourceRestart() throws InterruptedException {
561     Log.d(TAG, "testVideoSourceRestart");
562     loopback = true;
563 
564     MockSink localRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, LOCAL_RENDERER_NAME);
565     MockSink remoteRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, REMOTE_RENDERER_NAME);
566 
567     pcClient = createPeerConnectionClient(localRenderer, remoteRenderer,
568         createParametersForVideoCall(VIDEO_CODEC_VP8),
569         createCameraCapturer(false /* captureToTexture */));
570 
571     // Wait for local description, set type to answer and set as remote description.
572     assertTrue("Local description was not set.", waitForLocalDescription(WAIT_TIMEOUT));
573     SessionDescription remoteDescription = new SessionDescription(
574         SessionDescription.Type.fromCanonicalForm("answer"), localDesc.description);
575     pcClient.setRemoteDescription(remoteDescription);
576 
577     // Wait for ICE connection.
578     assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAIT_TIMEOUT));
579 
580     // Check that local and remote video frames were rendered.
581     assertTrue("Local video frames were not rendered before video restart.",
582         localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
583     assertTrue("Remote video frames were not rendered before video restart.",
584         remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
585 
586     // Stop and then start video source a few times.
587     for (int i = 0; i < VIDEO_RESTART_ATTEMPTS; i++) {
588       pcClient.stopVideoSource();
589       Thread.sleep(VIDEO_RESTART_TIMEOUT);
590       pcClient.startVideoSource();
591 
592       // Reset video renders and check that local and remote video frames
593       // were rendered after video restart.
594       localRenderer.reset(EXPECTED_VIDEO_FRAMES);
595       remoteRenderer.reset(EXPECTED_VIDEO_FRAMES);
596       assertTrue("Local video frames were not rendered after video restart.",
597           localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
598       assertTrue("Remote video frames were not rendered after video restart.",
599           remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
600     }
601     pcClient.close();
602     assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT));
603     Log.d(TAG, "testVideoSourceRestart done.");
604   }
605 
606   // Checks if capture format can be changed on fly and decoder can be reset properly.
607   @Test
608   @SmallTest
testCaptureFormatChange()609   public void testCaptureFormatChange() throws InterruptedException {
610     Log.d(TAG, "testCaptureFormatChange");
611     loopback = true;
612 
613     MockSink localRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, LOCAL_RENDERER_NAME);
614     MockSink remoteRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, REMOTE_RENDERER_NAME);
615 
616     pcClient = createPeerConnectionClient(localRenderer, remoteRenderer,
617         createParametersForVideoCall(VIDEO_CODEC_VP8),
618         createCameraCapturer(false /* captureToTexture */));
619 
620     // Wait for local description, set type to answer and set as remote description.
621     assertTrue("Local description was not set.", waitForLocalDescription(WAIT_TIMEOUT));
622     SessionDescription remoteDescription = new SessionDescription(
623         SessionDescription.Type.fromCanonicalForm("answer"), localDesc.description);
624     pcClient.setRemoteDescription(remoteDescription);
625 
626     // Wait for ICE connection.
627     assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAIT_TIMEOUT));
628 
629     // Check that local and remote video frames were rendered.
630     assertTrue("Local video frames were not rendered before camera resolution change.",
631         localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
632     assertTrue("Remote video frames were not rendered before camera resolution change.",
633         remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
634 
635     // Change capture output format a few times.
636     for (int i = 0; i < 2 * CAPTURE_FORMAT_CHANGE_ATTEMPTS; i++) {
637       if (i % 2 == 0) {
638         pcClient.changeCaptureFormat(WIDTH_VGA, HEIGHT_VGA, MAX_VIDEO_FPS);
639       } else {
640         pcClient.changeCaptureFormat(WIDTH_QVGA, HEIGHT_QVGA, MAX_VIDEO_FPS);
641       }
642 
643       // Reset video renders and check that local and remote video frames
644       // were rendered after capture format change.
645       localRenderer.reset(EXPECTED_VIDEO_FRAMES);
646       remoteRenderer.reset(EXPECTED_VIDEO_FRAMES);
647       assertTrue("Local video frames were not rendered after capture format change.",
648           localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
649       assertTrue("Remote video frames were not rendered after capture format change.",
650           remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
651     }
652 
653     pcClient.close();
654     assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT));
655     Log.d(TAG, "testCaptureFormatChange done.");
656   }
657 }
658