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 |