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