| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * libjingle | |
| 3 * Copyright 2014 Google Inc. | |
| 4 * | |
| 5 * Redistribution and use in source and binary forms, with or without | |
| 6 * modification, are permitted provided that the following conditions are met: | |
| 7 * | |
| 8 * 1. Redistributions of source code must retain the above copyright notice, | |
| 9 * this list of conditions and the following disclaimer. | |
| 10 * 2. Redistributions in binary form must reproduce the above copyright notice, | |
| 11 * this list of conditions and the following disclaimer in the documentation | |
| 12 * and/or other materials provided with the distribution. | |
| 13 * 3. The name of the author may not be used to endorse or promote products | |
| 14 * derived from this software without specific prior written permission. | |
| 15 * | |
| 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED | |
| 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
| 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | |
| 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | |
| 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | |
| 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 | |
| 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 */ | |
| 27 | |
| 28 package org.appspot.apprtc.test; | |
| 29 | |
| 30 import java.util.LinkedList; | |
| 31 import java.util.List; | |
| 32 import java.util.concurrent.CountDownLatch; | |
| 33 import java.util.concurrent.TimeUnit; | |
| 34 | |
| 35 import org.appspot.apprtc.AppRTCClient.SignalingParameters; | |
| 36 import org.appspot.apprtc.PeerConnectionClient; | |
| 37 import org.appspot.apprtc.PeerConnectionClient.PeerConnectionEvents; | |
| 38 import org.appspot.apprtc.PeerConnectionClient.PeerConnectionParameters; | |
| 39 import org.appspot.apprtc.util.LooperExecutor; | |
| 40 import org.webrtc.IceCandidate; | |
| 41 import org.webrtc.MediaConstraints; | |
| 42 import org.webrtc.PeerConnection; | |
| 43 import org.webrtc.PeerConnectionFactory; | |
| 44 import org.webrtc.SessionDescription; | |
| 45 import org.webrtc.StatsReport; | |
| 46 import org.webrtc.VideoRenderer; | |
| 47 | |
| 48 import android.test.InstrumentationTestCase; | |
| 49 import android.util.Log; | |
| 50 | |
| 51 public class PeerConnectionClientTest extends InstrumentationTestCase | |
| 52 implements PeerConnectionEvents { | |
| 53 private static final String TAG = "RTCClientTest"; | |
| 54 private static final int ICE_CONNECTION_WAIT_TIMEOUT = 10000; | |
| 55 private static final int WAIT_TIMEOUT = 7000; | |
| 56 private static final int CAMERA_SWITCH_ATTEMPTS = 3; | |
| 57 private static final int VIDEO_RESTART_ATTEMPTS = 3; | |
| 58 private static final int VIDEO_RESTART_TIMEOUT = 500; | |
| 59 private static final int EXPECTED_VIDEO_FRAMES = 10; | |
| 60 private static final String VIDEO_CODEC_VP8 = "VP8"; | |
| 61 private static final String VIDEO_CODEC_VP9 = "VP9"; | |
| 62 private static final String VIDEO_CODEC_H264 = "H264"; | |
| 63 private static final int AUDIO_RUN_TIMEOUT = 1000; | |
| 64 private static final String LOCAL_RENDERER_NAME = "Local renderer"; | |
| 65 private static final String REMOTE_RENDERER_NAME = "Remote renderer"; | |
| 66 | |
| 67 // The peer connection client is assumed to be thread safe in itself; the | |
| 68 // reference is written by the test thread and read by worker threads. | |
| 69 private volatile PeerConnectionClient pcClient; | |
| 70 private volatile boolean loopback; | |
| 71 | |
| 72 // These are protected by their respective event objects. | |
| 73 private LooperExecutor signalingExecutor; | |
| 74 private boolean isClosed; | |
| 75 private boolean isIceConnected; | |
| 76 private SessionDescription localSdp; | |
| 77 private List<IceCandidate> iceCandidates = new LinkedList<IceCandidate>(); | |
| 78 private final Object localSdpEvent = new Object(); | |
| 79 private final Object iceCandidateEvent = new Object(); | |
| 80 private final Object iceConnectedEvent = new Object(); | |
| 81 private final Object closeEvent = new Object(); | |
| 82 | |
| 83 // Mock renderer implementation. | |
| 84 private static class MockRenderer implements VideoRenderer.Callbacks { | |
| 85 // These are protected by 'this' since we gets called from worker threads. | |
| 86 private String rendererName; | |
| 87 private boolean renderFrameCalled = false; | |
| 88 | |
| 89 // Thread-safe in itself. | |
| 90 private CountDownLatch doneRendering; | |
| 91 | |
| 92 public MockRenderer(int expectedFrames, String rendererName) { | |
| 93 this.rendererName = rendererName; | |
| 94 reset(expectedFrames); | |
| 95 } | |
| 96 | |
| 97 // Resets render to wait for new amount of video frames. | |
| 98 public synchronized void reset(int expectedFrames) { | |
| 99 renderFrameCalled = false; | |
| 100 doneRendering = new CountDownLatch(expectedFrames); | |
| 101 } | |
| 102 | |
| 103 // TODO(guoweis): Remove this once chrome code base is updated. | |
| 104 @Override | |
| 105 public boolean canApplyRotation() { | |
| 106 return false; | |
| 107 } | |
| 108 | |
| 109 @Override | |
| 110 public synchronized void renderFrame(VideoRenderer.I420Frame frame) { | |
| 111 if (!renderFrameCalled) { | |
| 112 if (rendererName != null) { | |
| 113 Log.d(TAG, rendererName + " render frame: " + frame.width + " x " + fr
ame.height); | |
| 114 } else { | |
| 115 Log.d(TAG, "Render frame: " + frame.width + " x " + frame.height); | |
| 116 } | |
| 117 } | |
| 118 renderFrameCalled = true; | |
| 119 doneRendering.countDown(); | |
| 120 } | |
| 121 | |
| 122 | |
| 123 // This method shouldn't hold any locks or touch member variables since it | |
| 124 // blocks. | |
| 125 public boolean waitForFramesRendered(int timeoutMs) | |
| 126 throws InterruptedException { | |
| 127 doneRendering.await(timeoutMs, TimeUnit.MILLISECONDS); | |
| 128 return (doneRendering.getCount() <= 0); | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 // Peer connection events implementation. | |
| 133 @Override | |
| 134 public void onLocalDescription(SessionDescription sdp) { | |
| 135 Log.d(TAG, "LocalSDP type: " + sdp.type); | |
| 136 synchronized (localSdpEvent) { | |
| 137 localSdp = sdp; | |
| 138 localSdpEvent.notifyAll(); | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 @Override | |
| 143 public void onIceCandidate(final IceCandidate candidate) { | |
| 144 synchronized(iceCandidateEvent) { | |
| 145 Log.d(TAG, "IceCandidate #" + iceCandidates.size() + " : " + candidate.toS
tring()); | |
| 146 if (loopback) { | |
| 147 // Loopback local ICE candidate in a separate thread to avoid adding | |
| 148 // remote ICE candidate in a local ICE candidate callback. | |
| 149 signalingExecutor.execute(new Runnable() { | |
| 150 @Override | |
| 151 public void run() { | |
| 152 pcClient.addRemoteIceCandidate(candidate); | |
| 153 } | |
| 154 }); | |
| 155 } | |
| 156 iceCandidates.add(candidate); | |
| 157 iceCandidateEvent.notifyAll(); | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 @Override | |
| 162 public void onIceConnected() { | |
| 163 Log.d(TAG, "ICE Connected"); | |
| 164 synchronized(iceConnectedEvent) { | |
| 165 isIceConnected = true; | |
| 166 iceConnectedEvent.notifyAll(); | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 @Override | |
| 171 public void onIceDisconnected() { | |
| 172 Log.d(TAG, "ICE Disconnected"); | |
| 173 synchronized(iceConnectedEvent) { | |
| 174 isIceConnected = false; | |
| 175 iceConnectedEvent.notifyAll(); | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 @Override | |
| 180 public void onPeerConnectionClosed() { | |
| 181 Log.d(TAG, "PeerConnection closed"); | |
| 182 synchronized(closeEvent) { | |
| 183 isClosed = true; | |
| 184 closeEvent.notifyAll(); | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 @Override | |
| 189 public void onPeerConnectionError(String description) { | |
| 190 fail("PC Error: " + description); | |
| 191 } | |
| 192 | |
| 193 @Override | |
| 194 public void onPeerConnectionStatsReady(StatsReport[] reports) { | |
| 195 } | |
| 196 | |
| 197 // Helper wait functions. | |
| 198 private boolean waitForLocalSDP(int timeoutMs) | |
| 199 throws InterruptedException { | |
| 200 synchronized(localSdpEvent) { | |
| 201 if (localSdp == null) { | |
| 202 localSdpEvent.wait(timeoutMs); | |
| 203 } | |
| 204 return (localSdp != null); | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 private boolean waitForIceCandidates(int timeoutMs) | |
| 209 throws InterruptedException { | |
| 210 synchronized(iceCandidateEvent) { | |
| 211 if (iceCandidates.size() == 0) { | |
| 212 iceCandidateEvent.wait(timeoutMs); | |
| 213 } | |
| 214 return (iceCandidates.size() > 0); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 private boolean waitForIceConnected(int timeoutMs) | |
| 219 throws InterruptedException { | |
| 220 synchronized(iceConnectedEvent) { | |
| 221 if (!isIceConnected) { | |
| 222 iceConnectedEvent.wait(timeoutMs); | |
| 223 } | |
| 224 if (!isIceConnected) { | |
| 225 Log.e(TAG, "ICE connection failure"); | |
| 226 } | |
| 227 | |
| 228 return isIceConnected; | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 private boolean waitForPeerConnectionClosed(int timeoutMs) | |
| 233 throws InterruptedException { | |
| 234 synchronized(closeEvent) { | |
| 235 if (!isClosed) { | |
| 236 closeEvent.wait(timeoutMs); | |
| 237 } | |
| 238 return isClosed; | |
| 239 } | |
| 240 } | |
| 241 | |
| 242 PeerConnectionClient createPeerConnectionClient( | |
| 243 MockRenderer localRenderer, MockRenderer remoteRenderer, | |
| 244 boolean enableVideo, String videoCodec) { | |
| 245 List<PeerConnection.IceServer> iceServers = | |
| 246 new LinkedList<PeerConnection.IceServer>(); | |
| 247 SignalingParameters signalingParameters = new SignalingParameters( | |
| 248 iceServers, true, // iceServers, initiator. | |
| 249 null, null, null, // clientId, wssUrl, wssPostUrl. | |
| 250 null, null); // offerSdp, iceCandidates. | |
| 251 PeerConnectionParameters peerConnectionParameters = | |
| 252 new PeerConnectionParameters( | |
| 253 enableVideo, true, // videoCallEnabled, loopback. | |
| 254 0, 0, 0, 0, videoCodec, true, // video codec parameters. | |
| 255 0, "OPUS", false, true); // audio codec parameters. | |
| 256 | |
| 257 PeerConnectionClient client = PeerConnectionClient.getInstance(); | |
| 258 PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); | |
| 259 options.networkIgnoreMask = 0; | |
| 260 client.setPeerConnectionFactoryOptions(options); | |
| 261 client.createPeerConnectionFactory( | |
| 262 getInstrumentation().getContext(), null, | |
| 263 peerConnectionParameters, this); | |
| 264 client.createPeerConnection( | |
| 265 localRenderer, remoteRenderer, signalingParameters); | |
| 266 client.createOffer(); | |
| 267 return client; | |
| 268 } | |
| 269 | |
| 270 @Override | |
| 271 public void setUp() { | |
| 272 signalingExecutor = new LooperExecutor(); | |
| 273 signalingExecutor.requestStart(); | |
| 274 } | |
| 275 | |
| 276 @Override | |
| 277 public void tearDown() { | |
| 278 signalingExecutor.requestStop(); | |
| 279 } | |
| 280 | |
| 281 public void testSetLocalOfferMakesVideoFlowLocally() | |
| 282 throws InterruptedException { | |
| 283 Log.d(TAG, "testSetLocalOfferMakesVideoFlowLocally"); | |
| 284 MockRenderer localRenderer = new MockRenderer(EXPECTED_VIDEO_FRAMES, LOCAL_R
ENDERER_NAME); | |
| 285 pcClient = createPeerConnectionClient( | |
| 286 localRenderer, new MockRenderer(0, null), true, VIDEO_CODEC_VP8); | |
| 287 | |
| 288 // Wait for local SDP and ice candidates set events. | |
| 289 assertTrue("Local SDP was not set.", waitForLocalSDP(WAIT_TIMEOUT)); | |
| 290 assertTrue("ICE candidates were not generated.", | |
| 291 waitForIceCandidates(WAIT_TIMEOUT)); | |
| 292 | |
| 293 // Check that local video frames were rendered. | |
| 294 assertTrue("Local video frames were not rendered.", | |
| 295 localRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 296 | |
| 297 pcClient.close(); | |
| 298 assertTrue("PeerConnection close event was not received.", | |
| 299 waitForPeerConnectionClosed(WAIT_TIMEOUT)); | |
| 300 Log.d(TAG, "testSetLocalOfferMakesVideoFlowLocally Done."); | |
| 301 } | |
| 302 | |
| 303 private void doLoopbackTest(boolean enableVideo, String videoCodec) | |
| 304 throws InterruptedException { | |
| 305 loopback = true; | |
| 306 MockRenderer localRenderer = null; | |
| 307 MockRenderer remoteRenderer = null; | |
| 308 if (enableVideo) { | |
| 309 Log.d(TAG, "testLoopback for video " + videoCodec); | |
| 310 localRenderer = new MockRenderer(EXPECTED_VIDEO_FRAMES, LOCAL_RENDERER_NAM
E); | |
| 311 remoteRenderer = new MockRenderer(EXPECTED_VIDEO_FRAMES, REMOTE_RENDERER_N
AME); | |
| 312 } else { | |
| 313 Log.d(TAG, "testLoopback for audio."); | |
| 314 } | |
| 315 pcClient = createPeerConnectionClient( | |
| 316 localRenderer, remoteRenderer, enableVideo, videoCodec); | |
| 317 | |
| 318 // Wait for local SDP, rename it to answer and set as remote SDP. | |
| 319 assertTrue("Local SDP was not set.", waitForLocalSDP(WAIT_TIMEOUT)); | |
| 320 SessionDescription remoteSdp = new SessionDescription( | |
| 321 SessionDescription.Type.fromCanonicalForm("answer"), | |
| 322 localSdp.description); | |
| 323 pcClient.setRemoteDescription(remoteSdp); | |
| 324 | |
| 325 // Wait for ICE connection. | |
| 326 assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAI
T_TIMEOUT)); | |
| 327 | |
| 328 if (enableVideo) { | |
| 329 // Check that local and remote video frames were rendered. | |
| 330 assertTrue("Local video frames were not rendered.", | |
| 331 localRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 332 assertTrue("Remote video frames were not rendered.", | |
| 333 remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 334 } else { | |
| 335 // For audio just sleep for 1 sec. | |
| 336 // TODO(glaznev): check how we can detect that remote audio was rendered. | |
| 337 Thread.sleep(AUDIO_RUN_TIMEOUT); | |
| 338 } | |
| 339 | |
| 340 pcClient.close(); | |
| 341 assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT)); | |
| 342 Log.d(TAG, "testLoopback done."); | |
| 343 } | |
| 344 | |
| 345 public void testLoopbackAudio() throws InterruptedException { | |
| 346 doLoopbackTest(false, VIDEO_CODEC_VP8); | |
| 347 } | |
| 348 | |
| 349 public void testLoopbackVp8() throws InterruptedException { | |
| 350 doLoopbackTest(true, VIDEO_CODEC_VP8); | |
| 351 } | |
| 352 | |
| 353 public void DISABLED_testLoopbackVp9() throws InterruptedException { | |
| 354 doLoopbackTest(true, VIDEO_CODEC_VP9); | |
| 355 } | |
| 356 | |
| 357 public void testLoopbackH264() throws InterruptedException { | |
| 358 doLoopbackTest(true, VIDEO_CODEC_H264); | |
| 359 } | |
| 360 | |
| 361 // Checks if default front camera can be switched to back camera and then | |
| 362 // again to front camera. | |
| 363 public void testCameraSwitch() throws InterruptedException { | |
| 364 Log.d(TAG, "testCameraSwitch"); | |
| 365 loopback = true; | |
| 366 | |
| 367 MockRenderer localRenderer = new MockRenderer(EXPECTED_VIDEO_FRAMES, LOCAL_R
ENDERER_NAME); | |
| 368 MockRenderer remoteRenderer = new MockRenderer(EXPECTED_VIDEO_FRAMES, REMOTE
_RENDERER_NAME); | |
| 369 | |
| 370 pcClient = createPeerConnectionClient( | |
| 371 localRenderer, remoteRenderer, true, VIDEO_CODEC_VP8); | |
| 372 | |
| 373 // Wait for local SDP, rename it to answer and set as remote SDP. | |
| 374 assertTrue("Local SDP was not set.", waitForLocalSDP(WAIT_TIMEOUT)); | |
| 375 SessionDescription remoteSdp = new SessionDescription( | |
| 376 SessionDescription.Type.fromCanonicalForm("answer"), | |
| 377 localSdp.description); | |
| 378 pcClient.setRemoteDescription(remoteSdp); | |
| 379 | |
| 380 // Wait for ICE connection. | |
| 381 assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAI
T_TIMEOUT)); | |
| 382 | |
| 383 // Check that local and remote video frames were rendered. | |
| 384 assertTrue("Local video frames were not rendered before camera switch.", | |
| 385 localRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 386 assertTrue("Remote video frames were not rendered before camera switch.", | |
| 387 remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 388 | |
| 389 for (int i = 0; i < CAMERA_SWITCH_ATTEMPTS; i++) { | |
| 390 // Try to switch camera | |
| 391 pcClient.switchCamera(); | |
| 392 | |
| 393 // Reset video renders and check that local and remote video frames | |
| 394 // were rendered after camera switch. | |
| 395 localRenderer.reset(EXPECTED_VIDEO_FRAMES); | |
| 396 remoteRenderer.reset(EXPECTED_VIDEO_FRAMES); | |
| 397 assertTrue("Local video frames were not rendered after camera switch.", | |
| 398 localRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 399 assertTrue("Remote video frames were not rendered after camera switch.", | |
| 400 remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 401 } | |
| 402 pcClient.close(); | |
| 403 assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT)); | |
| 404 Log.d(TAG, "testCameraSwitch done."); | |
| 405 } | |
| 406 | |
| 407 // Checks if video source can be restarted - simulate app goes to | |
| 408 // background and back to foreground. | |
| 409 public void testVideoSourceRestart() throws InterruptedException { | |
| 410 Log.d(TAG, "testVideoSourceRestart"); | |
| 411 loopback = true; | |
| 412 | |
| 413 MockRenderer localRenderer = new MockRenderer(EXPECTED_VIDEO_FRAMES, LOCAL_R
ENDERER_NAME); | |
| 414 MockRenderer remoteRenderer = new MockRenderer(EXPECTED_VIDEO_FRAMES, REMOTE
_RENDERER_NAME); | |
| 415 | |
| 416 pcClient = createPeerConnectionClient( | |
| 417 localRenderer, remoteRenderer, true, VIDEO_CODEC_VP8); | |
| 418 | |
| 419 // Wait for local SDP, rename it to answer and set as remote SDP. | |
| 420 assertTrue("Local SDP was not set.", waitForLocalSDP(WAIT_TIMEOUT)); | |
| 421 SessionDescription remoteSdp = new SessionDescription( | |
| 422 SessionDescription.Type.fromCanonicalForm("answer"), | |
| 423 localSdp.description); | |
| 424 pcClient.setRemoteDescription(remoteSdp); | |
| 425 | |
| 426 // Wait for ICE connection. | |
| 427 assertTrue("ICE connection failure.", waitForIceConnected(ICE_CONNECTION_WAI
T_TIMEOUT)); | |
| 428 | |
| 429 // Check that local and remote video frames were rendered. | |
| 430 assertTrue("Local video frames were not rendered before video restart.", | |
| 431 localRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 432 assertTrue("Remote video frames were not rendered before video restart.", | |
| 433 remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 434 | |
| 435 // Stop and then start video source a few times. | |
| 436 for (int i = 0; i < VIDEO_RESTART_ATTEMPTS; i++) { | |
| 437 pcClient.stopVideoSource(); | |
| 438 Thread.sleep(VIDEO_RESTART_TIMEOUT); | |
| 439 pcClient.startVideoSource(); | |
| 440 | |
| 441 // Reset video renders and check that local and remote video frames | |
| 442 // were rendered after video restart. | |
| 443 localRenderer.reset(EXPECTED_VIDEO_FRAMES); | |
| 444 remoteRenderer.reset(EXPECTED_VIDEO_FRAMES); | |
| 445 assertTrue("Local video frames were not rendered after video restart.", | |
| 446 localRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 447 assertTrue("Remote video frames were not rendered after video restart.", | |
| 448 remoteRenderer.waitForFramesRendered(WAIT_TIMEOUT)); | |
| 449 } | |
| 450 pcClient.close(); | |
| 451 assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT)); | |
| 452 Log.d(TAG, "testVideoSourceRestart done."); | |
| 453 } | |
| 454 | |
| 455 } | |
| OLD | NEW |