• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.runner.AndroidJUnit4;
19 import android.util.Log;
20 import androidx.test.filters.SmallTest;
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.RTCStatsReport;
43 import org.webrtc.SessionDescription;
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(final RTCStatsReport report)211   public void onPeerConnectionStatsReady(final RTCStatsReport report) {}
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     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_VP8),
457         createCameraCapturer(false /* captureToTexture */), true /* decodeToTexture */);
458   }
459 
460   @Test
461   @SmallTest
testLoopbackVp9DecodeToTexture()462   public void testLoopbackVp9DecodeToTexture() throws InterruptedException {
463     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_VP9),
464         createCameraCapturer(false /* captureToTexture */), true /* decodeToTexture */);
465   }
466 
467   @Test
468   @SmallTest
testLoopbackH264DecodeToTexture()469   public void testLoopbackH264DecodeToTexture() throws InterruptedException {
470     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_H264),
471         createCameraCapturer(false /* captureToTexture */), true /* decodeToTexture */);
472   }
473 
474   @Test
475   @SmallTest
testLoopbackVp8CaptureToTexture()476   public void testLoopbackVp8CaptureToTexture() throws InterruptedException {
477     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_VP8),
478         createCameraCapturer(true /* captureToTexture */), true /* decodeToTexture */);
479   }
480 
481   @Test
482   @SmallTest
testLoopbackH264CaptureToTexture()483   public void testLoopbackH264CaptureToTexture() throws InterruptedException {
484     doLoopbackTest(createParametersForVideoCall(VIDEO_CODEC_H264),
485         createCameraCapturer(true /* captureToTexture */), true /* decodeToTexture */);
486   }
487 
488   // Checks if default front camera can be switched to back camera and then
489   // again to front camera.
490   @Test
491   @SmallTest
testCameraSwitch()492   public void testCameraSwitch() throws InterruptedException {
493     Log.d(TAG, "testCameraSwitch");
494     loopback = true;
495 
496     MockSink localRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, LOCAL_RENDERER_NAME);
497     MockSink remoteRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, REMOTE_RENDERER_NAME);
498 
499     pcClient = createPeerConnectionClient(localRenderer, remoteRenderer,
500         createParametersForVideoCall(VIDEO_CODEC_VP8),
501         createCameraCapturer(false /* captureToTexture */));
502 
503     // Wait for local description, set type to answer and set as remote description.
504     assertTrue("Local description was not set.", waitForLocalDescription(WAIT_TIMEOUT));
505     SessionDescription remoteDescription = new SessionDescription(
506         SessionDescription.Type.fromCanonicalForm("answer"), localDesc.description);
507     pcClient.setRemoteDescription(remoteDescription);
508 
509     // Wait for ICE connection.
510     assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAIT_TIMEOUT));
511 
512     // Check that local and remote video frames were rendered.
513     assertTrue("Local video frames were not rendered before camera switch.",
514         localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
515     assertTrue("Remote video frames were not rendered before camera switch.",
516         remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
517 
518     for (int i = 0; i < CAMERA_SWITCH_ATTEMPTS; i++) {
519       // Try to switch camera
520       pcClient.switchCamera();
521 
522       // Reset video renders and check that local and remote video frames
523       // were rendered after camera switch.
524       localRenderer.reset(EXPECTED_VIDEO_FRAMES);
525       remoteRenderer.reset(EXPECTED_VIDEO_FRAMES);
526       assertTrue("Local video frames were not rendered after camera switch.",
527           localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
528       assertTrue("Remote video frames were not rendered after camera switch.",
529           remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
530     }
531     pcClient.close();
532     assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT));
533     Log.d(TAG, "testCameraSwitch done.");
534   }
535 
536   // Checks if video source can be restarted - simulate app goes to
537   // background and back to foreground.
538   @Test
539   @SmallTest
testVideoSourceRestart()540   public void testVideoSourceRestart() throws InterruptedException {
541     Log.d(TAG, "testVideoSourceRestart");
542     loopback = true;
543 
544     MockSink localRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, LOCAL_RENDERER_NAME);
545     MockSink remoteRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, REMOTE_RENDERER_NAME);
546 
547     pcClient = createPeerConnectionClient(localRenderer, remoteRenderer,
548         createParametersForVideoCall(VIDEO_CODEC_VP8),
549         createCameraCapturer(false /* captureToTexture */));
550 
551     // Wait for local description, set type to answer and set as remote description.
552     assertTrue("Local description was not set.", waitForLocalDescription(WAIT_TIMEOUT));
553     SessionDescription remoteDescription = new SessionDescription(
554         SessionDescription.Type.fromCanonicalForm("answer"), localDesc.description);
555     pcClient.setRemoteDescription(remoteDescription);
556 
557     // Wait for ICE connection.
558     assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAIT_TIMEOUT));
559 
560     // Check that local and remote video frames were rendered.
561     assertTrue("Local video frames were not rendered before video restart.",
562         localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
563     assertTrue("Remote video frames were not rendered before video restart.",
564         remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
565 
566     // Stop and then start video source a few times.
567     for (int i = 0; i < VIDEO_RESTART_ATTEMPTS; i++) {
568       pcClient.stopVideoSource();
569       Thread.sleep(VIDEO_RESTART_TIMEOUT);
570       pcClient.startVideoSource();
571 
572       // Reset video renders and check that local and remote video frames
573       // were rendered after video restart.
574       localRenderer.reset(EXPECTED_VIDEO_FRAMES);
575       remoteRenderer.reset(EXPECTED_VIDEO_FRAMES);
576       assertTrue("Local video frames were not rendered after video restart.",
577           localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
578       assertTrue("Remote video frames were not rendered after video restart.",
579           remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
580     }
581     pcClient.close();
582     assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT));
583     Log.d(TAG, "testVideoSourceRestart done.");
584   }
585 
586   // Checks if capture format can be changed on fly and decoder can be reset properly.
587   @Test
588   @SmallTest
testCaptureFormatChange()589   public void testCaptureFormatChange() throws InterruptedException {
590     Log.d(TAG, "testCaptureFormatChange");
591     loopback = true;
592 
593     MockSink localRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, LOCAL_RENDERER_NAME);
594     MockSink remoteRenderer = new MockSink(EXPECTED_VIDEO_FRAMES, REMOTE_RENDERER_NAME);
595 
596     pcClient = createPeerConnectionClient(localRenderer, remoteRenderer,
597         createParametersForVideoCall(VIDEO_CODEC_VP8),
598         createCameraCapturer(false /* captureToTexture */));
599 
600     // Wait for local description, set type to answer and set as remote description.
601     assertTrue("Local description was not set.", waitForLocalDescription(WAIT_TIMEOUT));
602     SessionDescription remoteDescription = new SessionDescription(
603         SessionDescription.Type.fromCanonicalForm("answer"), localDesc.description);
604     pcClient.setRemoteDescription(remoteDescription);
605 
606     // Wait for ICE connection.
607     assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAIT_TIMEOUT));
608 
609     // Check that local and remote video frames were rendered.
610     assertTrue("Local video frames were not rendered before camera resolution change.",
611         localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
612     assertTrue("Remote video frames were not rendered before camera resolution change.",
613         remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
614 
615     // Change capture output format a few times.
616     for (int i = 0; i < 2 * CAPTURE_FORMAT_CHANGE_ATTEMPTS; i++) {
617       if (i % 2 == 0) {
618         pcClient.changeCaptureFormat(WIDTH_VGA, HEIGHT_VGA, MAX_VIDEO_FPS);
619       } else {
620         pcClient.changeCaptureFormat(WIDTH_QVGA, HEIGHT_QVGA, MAX_VIDEO_FPS);
621       }
622 
623       // Reset video renders and check that local and remote video frames
624       // were rendered after capture format change.
625       localRenderer.reset(EXPECTED_VIDEO_FRAMES);
626       remoteRenderer.reset(EXPECTED_VIDEO_FRAMES);
627       assertTrue("Local video frames were not rendered after capture format change.",
628           localRenderer.waitForFramesRendered(WAIT_TIMEOUT));
629       assertTrue("Remote video frames were not rendered after capture format change.",
630           remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT));
631     }
632 
633     pcClient.close();
634     assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT));
635     Log.d(TAG, "testCaptureFormatChange done.");
636   }
637 }
638