blob: b31861dbab5dd37ff06df30d338915926e1b73d2 [file] [log] [blame]
Steve Anton6f25b092017-10-23 09:39:20 -07001/*
2 * Copyright 2017 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
Karl Wiberg32df86e2017-11-03 10:24:27 +010011#include "api/audio_codecs/builtin_audio_decoder_factory.h"
12#include "api/audio_codecs/builtin_audio_encoder_factory.h"
Steve Anton6f25b092017-10-23 09:39:20 -070013#include "api/peerconnectionproxy.h"
Anders Carlsson67537952018-05-03 11:28:29 +020014#include "api/video_codecs/builtin_video_decoder_factory.h"
15#include "api/video_codecs/builtin_video_encoder_factory.h"
Steve Anton6f25b092017-10-23 09:39:20 -070016#include "p2p/base/fakeportallocator.h"
17#include "p2p/base/teststunserver.h"
18#include "p2p/client/basicportallocator.h"
19#include "pc/mediasession.h"
20#include "pc/peerconnection.h"
21#include "pc/peerconnectionwrapper.h"
22#include "pc/sdputils.h"
23#ifdef WEBRTC_ANDROID
24#include "pc/test/androidtestinitializer.h"
25#endif
26#include "pc/test/fakeaudiocapturemodule.h"
27#include "rtc_base/fakenetwork.h"
28#include "rtc_base/gunit.h"
29#include "rtc_base/ptr_util.h"
30#include "rtc_base/virtualsocketserver.h"
31#include "test/gmock.h"
32
33namespace webrtc {
34
35using BundlePolicy = PeerConnectionInterface::BundlePolicy;
36using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
37using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
38using RtcpMuxPolicy = PeerConnectionInterface::RtcpMuxPolicy;
39using rtc::SocketAddress;
Steve Anton7464fca2018-01-19 11:10:37 -080040using ::testing::Combine;
Steve Anton6f25b092017-10-23 09:39:20 -070041using ::testing::ElementsAre;
42using ::testing::UnorderedElementsAre;
43using ::testing::Values;
44
45constexpr int kDefaultTimeout = 10000;
46
47// TODO(steveanton): These tests should be rewritten to use the standard
48// RtpSenderInterface/DtlsTransportInterface objects once they're available in
49// the API. The RtpSender can be used to determine which transport a given media
50// will use: https://www.w3.org/TR/webrtc/#dom-rtcrtpsender-transport
Steve Anton7464fca2018-01-19 11:10:37 -080051// Should also be able to remove GetTransceiversForTesting at that point.
Steve Anton6f25b092017-10-23 09:39:20 -070052
53class PeerConnectionWrapperForBundleTest : public PeerConnectionWrapper {
54 public:
55 using PeerConnectionWrapper::PeerConnectionWrapper;
56
57 bool AddIceCandidateToMedia(cricket::Candidate* candidate,
58 cricket::MediaType media_type) {
59 auto* desc = pc()->remote_description()->description();
60 for (size_t i = 0; i < desc->contents().size(); i++) {
61 const auto& content = desc->contents()[i];
Steve Antonb1c1de12017-12-21 15:14:30 -080062 if (content.media_description()->type() == media_type) {
Steve Anton6f25b092017-10-23 09:39:20 -070063 candidate->set_transport_name(content.name);
64 JsepIceCandidate jsep_candidate(content.name, i, *candidate);
65 return pc()->AddIceCandidate(&jsep_candidate);
66 }
67 }
68 RTC_NOTREACHED();
69 return false;
70 }
71
Zhi Huange830e682018-03-30 10:48:35 -070072 rtc::PacketTransportInternal* voice_rtp_transport() {
73 return (voice_channel() ? voice_channel()->rtp_packet_transport()
74 : nullptr);
Steve Anton6f25b092017-10-23 09:39:20 -070075 }
76
Zhi Huange830e682018-03-30 10:48:35 -070077 rtc::PacketTransportInternal* voice_rtcp_transport() {
78 return (voice_channel() ? voice_channel()->rtcp_packet_transport()
79 : nullptr);
Steve Anton6f25b092017-10-23 09:39:20 -070080 }
81
82 cricket::VoiceChannel* voice_channel() {
Steve Antonb8867112018-02-13 10:07:54 -080083 auto transceivers = GetInternalPeerConnection()->GetTransceiversInternal();
Steve Anton7464fca2018-01-19 11:10:37 -080084 for (auto transceiver : transceivers) {
Steve Anton69470252018-02-09 11:43:08 -080085 if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
Steve Anton7464fca2018-01-19 11:10:37 -080086 return static_cast<cricket::VoiceChannel*>(
87 transceiver->internal()->channel());
88 }
89 }
90 return nullptr;
Steve Anton6f25b092017-10-23 09:39:20 -070091 }
92
Zhi Huange830e682018-03-30 10:48:35 -070093 rtc::PacketTransportInternal* video_rtp_transport() {
94 return (video_channel() ? video_channel()->rtp_packet_transport()
95 : nullptr);
Steve Anton6f25b092017-10-23 09:39:20 -070096 }
97
Zhi Huange830e682018-03-30 10:48:35 -070098 rtc::PacketTransportInternal* video_rtcp_transport() {
99 return (video_channel() ? video_channel()->rtcp_packet_transport()
100 : nullptr);
Steve Anton6f25b092017-10-23 09:39:20 -0700101 }
102
103 cricket::VideoChannel* video_channel() {
Steve Antonb8867112018-02-13 10:07:54 -0800104 auto transceivers = GetInternalPeerConnection()->GetTransceiversInternal();
Steve Anton7464fca2018-01-19 11:10:37 -0800105 for (auto transceiver : transceivers) {
Steve Anton69470252018-02-09 11:43:08 -0800106 if (transceiver->media_type() == cricket::MEDIA_TYPE_VIDEO) {
Steve Anton7464fca2018-01-19 11:10:37 -0800107 return static_cast<cricket::VideoChannel*>(
108 transceiver->internal()->channel());
109 }
110 }
111 return nullptr;
Steve Anton6f25b092017-10-23 09:39:20 -0700112 }
113
114 PeerConnection* GetInternalPeerConnection() {
Mirko Bonadeie97de912017-12-13 11:29:34 +0100115 auto* pci =
116 static_cast<PeerConnectionProxyWithInternal<PeerConnectionInterface>*>(
117 pc());
118 return static_cast<PeerConnection*>(pci->internal());
Steve Anton6f25b092017-10-23 09:39:20 -0700119 }
120
121 // Returns true if the stats indicate that an ICE connection is either in
122 // progress or established with the given remote address.
123 bool HasConnectionWithRemoteAddress(const SocketAddress& address) {
124 auto report = GetStats();
125 if (!report) {
126 return false;
127 }
128 std::string matching_candidate_id;
129 for (auto* ice_candidate_stats :
130 report->GetStatsOfType<RTCRemoteIceCandidateStats>()) {
131 if (*ice_candidate_stats->ip == address.HostAsURIString() &&
132 *ice_candidate_stats->port == address.port()) {
133 matching_candidate_id = ice_candidate_stats->id();
134 break;
135 }
136 }
137 if (matching_candidate_id.empty()) {
138 return false;
139 }
140 for (auto* pair_stats :
141 report->GetStatsOfType<RTCIceCandidatePairStats>()) {
142 if (*pair_stats->remote_candidate_id == matching_candidate_id) {
143 if (*pair_stats->state == RTCStatsIceCandidatePairState::kInProgress ||
144 *pair_stats->state == RTCStatsIceCandidatePairState::kSucceeded) {
145 return true;
146 }
147 }
148 }
149 return false;
150 }
151
152 rtc::FakeNetworkManager* network() { return network_; }
153
154 void set_network(rtc::FakeNetworkManager* network) { network_ = network; }
155
156 private:
157 rtc::FakeNetworkManager* network_;
158};
159
Steve Anton7464fca2018-01-19 11:10:37 -0800160class PeerConnectionBundleBaseTest : public ::testing::Test {
Steve Anton6f25b092017-10-23 09:39:20 -0700161 protected:
162 typedef std::unique_ptr<PeerConnectionWrapperForBundleTest> WrapperPtr;
163
Steve Anton7464fca2018-01-19 11:10:37 -0800164 explicit PeerConnectionBundleBaseTest(SdpSemantics sdp_semantics)
165 : vss_(new rtc::VirtualSocketServer()),
166 main_(vss_.get()),
167 sdp_semantics_(sdp_semantics) {
Steve Anton6f25b092017-10-23 09:39:20 -0700168#ifdef WEBRTC_ANDROID
169 InitializeAndroidObjects();
170#endif
171 pc_factory_ = CreatePeerConnectionFactory(
172 rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
Anders Carlsson67537952018-05-03 11:28:29 +0200173 rtc::scoped_refptr<AudioDeviceModule>(FakeAudioCaptureModule::Create()),
174 CreateBuiltinAudioEncoderFactory(), CreateBuiltinAudioDecoderFactory(),
175 CreateBuiltinVideoEncoderFactory(), CreateBuiltinVideoDecoderFactory(),
176 nullptr /* audio_mixer */, nullptr /* audio_processing */);
Steve Anton6f25b092017-10-23 09:39:20 -0700177 }
178
179 WrapperPtr CreatePeerConnection() {
180 return CreatePeerConnection(RTCConfiguration());
181 }
182
183 WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
184 auto* fake_network = NewFakeNetwork();
185 auto port_allocator =
186 rtc::MakeUnique<cricket::BasicPortAllocator>(fake_network);
187 port_allocator->set_flags(cricket::PORTALLOCATOR_DISABLE_TCP |
188 cricket::PORTALLOCATOR_DISABLE_RELAY);
189 port_allocator->set_step_delay(cricket::kMinimumStepDelay);
190 auto observer = rtc::MakeUnique<MockPeerConnectionObserver>();
Steve Anton7464fca2018-01-19 11:10:37 -0800191 RTCConfiguration modified_config = config;
192 modified_config.sdp_semantics = sdp_semantics_;
Steve Anton6f25b092017-10-23 09:39:20 -0700193 auto pc = pc_factory_->CreatePeerConnection(
Steve Anton7464fca2018-01-19 11:10:37 -0800194 modified_config, std::move(port_allocator), nullptr, observer.get());
Steve Anton6f25b092017-10-23 09:39:20 -0700195 if (!pc) {
196 return nullptr;
197 }
198
199 auto wrapper = rtc::MakeUnique<PeerConnectionWrapperForBundleTest>(
200 pc_factory_, pc, std::move(observer));
201 wrapper->set_network(fake_network);
202 return wrapper;
203 }
204
205 // Accepts the same arguments as CreatePeerConnection and adds default audio
206 // and video tracks.
207 template <typename... Args>
208 WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) {
209 auto wrapper = CreatePeerConnection(std::forward<Args>(args)...);
210 if (!wrapper) {
211 return nullptr;
212 }
213 wrapper->AddAudioTrack("a");
214 wrapper->AddVideoTrack("v");
215 return wrapper;
216 }
217
218 cricket::Candidate CreateLocalUdpCandidate(
219 const rtc::SocketAddress& address) {
220 cricket::Candidate candidate;
221 candidate.set_component(cricket::ICE_CANDIDATE_COMPONENT_DEFAULT);
222 candidate.set_protocol(cricket::UDP_PROTOCOL_NAME);
223 candidate.set_address(address);
224 candidate.set_type(cricket::LOCAL_PORT_TYPE);
225 return candidate;
226 }
227
228 rtc::FakeNetworkManager* NewFakeNetwork() {
229 // The PeerConnection's port allocator is tied to the PeerConnection's
230 // lifetime and expects the underlying NetworkManager to outlive it. If
231 // PeerConnectionWrapper owned the NetworkManager, it would be destroyed
232 // before the PeerConnection (since subclass members are destroyed before
233 // base class members). Therefore, the test fixture will own all the fake
234 // networks even though tests should access the fake network through the
235 // PeerConnectionWrapper.
236 auto* fake_network = new rtc::FakeNetworkManager();
237 fake_networks_.emplace_back(fake_network);
238 return fake_network;
239 }
240
241 std::unique_ptr<rtc::VirtualSocketServer> vss_;
242 rtc::AutoSocketServerThread main_;
243 rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
244 std::vector<std::unique_ptr<rtc::FakeNetworkManager>> fake_networks_;
Steve Anton7464fca2018-01-19 11:10:37 -0800245 const SdpSemantics sdp_semantics_;
246};
247
248class PeerConnectionBundleTest
249 : public PeerConnectionBundleBaseTest,
250 public ::testing::WithParamInterface<SdpSemantics> {
251 protected:
252 PeerConnectionBundleTest() : PeerConnectionBundleBaseTest(GetParam()) {}
Steve Anton6f25b092017-10-23 09:39:20 -0700253};
254
Taylor Brandstetter0ab56512018-04-12 10:30:48 -0700255class PeerConnectionBundleTestUnifiedPlan
256 : public PeerConnectionBundleBaseTest {
257 protected:
258 PeerConnectionBundleTestUnifiedPlan()
259 : PeerConnectionBundleBaseTest(SdpSemantics::kUnifiedPlan) {}
260};
261
Steve Anton6f25b092017-10-23 09:39:20 -0700262SdpContentMutator RemoveRtcpMux() {
263 return [](cricket::ContentInfo* content, cricket::TransportInfo* transport) {
Steve Antonb1c1de12017-12-21 15:14:30 -0800264 content->media_description()->set_rtcp_mux(false);
Steve Anton6f25b092017-10-23 09:39:20 -0700265 };
266}
267
268std::vector<int> GetCandidateComponents(
269 const std::vector<IceCandidateInterface*> candidates) {
270 std::vector<int> components;
271 for (auto* candidate : candidates) {
272 components.push_back(candidate->candidate().component());
273 }
274 return components;
275}
276
277// Test that there are 2 local UDP candidates (1 RTP and 1 RTCP candidate) for
278// each media section when disabling bundling and disabling RTCP multiplexing.
Steve Anton7464fca2018-01-19 11:10:37 -0800279TEST_P(PeerConnectionBundleTest,
Steve Anton6f25b092017-10-23 09:39:20 -0700280 TwoCandidatesForEachTransportWhenNoBundleNoRtcpMux) {
281 const SocketAddress kCallerAddress("1.1.1.1", 0);
282 const SocketAddress kCalleeAddress("2.2.2.2", 0);
283
284 RTCConfiguration config;
285 config.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyNegotiate;
286 auto caller = CreatePeerConnectionWithAudioVideo(config);
287 caller->network()->AddInterface(kCallerAddress);
288 auto callee = CreatePeerConnectionWithAudioVideo(config);
289 callee->network()->AddInterface(kCalleeAddress);
290
291 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
292 RTCOfferAnswerOptions options_no_bundle;
293 options_no_bundle.use_rtp_mux = false;
294 auto answer = callee->CreateAnswer(options_no_bundle);
295 SdpContentsForEach(RemoveRtcpMux(), answer->description());
296 ASSERT_TRUE(
297 callee->SetLocalDescription(CloneSessionDescription(answer.get())));
298 ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
299
300 // Check that caller has separate RTP and RTCP candidates for each media.
301 EXPECT_TRUE_WAIT(caller->IsIceGatheringDone(), kDefaultTimeout);
302 EXPECT_THAT(
303 GetCandidateComponents(caller->observer()->GetCandidatesByMline(0)),
304 UnorderedElementsAre(cricket::ICE_CANDIDATE_COMPONENT_RTP,
305 cricket::ICE_CANDIDATE_COMPONENT_RTCP));
306 EXPECT_THAT(
307 GetCandidateComponents(caller->observer()->GetCandidatesByMline(1)),
308 UnorderedElementsAre(cricket::ICE_CANDIDATE_COMPONENT_RTP,
309 cricket::ICE_CANDIDATE_COMPONENT_RTCP));
310
311 // Check that callee has separate RTP and RTCP candidates for each media.
312 EXPECT_TRUE_WAIT(callee->IsIceGatheringDone(), kDefaultTimeout);
313 EXPECT_THAT(
314 GetCandidateComponents(callee->observer()->GetCandidatesByMline(0)),
315 UnorderedElementsAre(cricket::ICE_CANDIDATE_COMPONENT_RTP,
316 cricket::ICE_CANDIDATE_COMPONENT_RTCP));
317 EXPECT_THAT(
318 GetCandidateComponents(callee->observer()->GetCandidatesByMline(1)),
319 UnorderedElementsAre(cricket::ICE_CANDIDATE_COMPONENT_RTP,
320 cricket::ICE_CANDIDATE_COMPONENT_RTCP));
321}
322
323// Test that there is 1 local UDP candidate for both RTP and RTCP for each media
324// section when disabling bundle but enabling RTCP multiplexing.
Steve Anton7464fca2018-01-19 11:10:37 -0800325TEST_P(PeerConnectionBundleTest,
Steve Anton6f25b092017-10-23 09:39:20 -0700326 OneCandidateForEachTransportWhenNoBundleButRtcpMux) {
327 const SocketAddress kCallerAddress("1.1.1.1", 0);
328
329 auto caller = CreatePeerConnectionWithAudioVideo();
330 caller->network()->AddInterface(kCallerAddress);
331 auto callee = CreatePeerConnectionWithAudioVideo();
332
333 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
334 RTCOfferAnswerOptions options_no_bundle;
335 options_no_bundle.use_rtp_mux = false;
336 ASSERT_TRUE(
337 caller->SetRemoteDescription(callee->CreateAnswer(options_no_bundle)));
338
339 EXPECT_TRUE_WAIT(caller->IsIceGatheringDone(), kDefaultTimeout);
340
341 EXPECT_EQ(1u, caller->observer()->GetCandidatesByMline(0).size());
342 EXPECT_EQ(1u, caller->observer()->GetCandidatesByMline(1).size());
343}
344
345// Test that there is 1 local UDP candidate in only the first media section when
346// bundling and enabling RTCP multiplexing.
Steve Anton7464fca2018-01-19 11:10:37 -0800347TEST_P(PeerConnectionBundleTest,
Steve Anton6f25b092017-10-23 09:39:20 -0700348 OneCandidateOnlyOnFirstTransportWhenBundleAndRtcpMux) {
349 const SocketAddress kCallerAddress("1.1.1.1", 0);
350
351 RTCConfiguration config;
352 config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
353 auto caller = CreatePeerConnectionWithAudioVideo(config);
354 caller->network()->AddInterface(kCallerAddress);
355 auto callee = CreatePeerConnectionWithAudioVideo(config);
356
357 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
358 ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateAnswer()));
359
360 EXPECT_TRUE_WAIT(caller->IsIceGatheringDone(), kDefaultTimeout);
361
362 EXPECT_EQ(1u, caller->observer()->GetCandidatesByMline(0).size());
363 EXPECT_EQ(0u, caller->observer()->GetCandidatesByMline(1).size());
364}
365
Zhi Huange830e682018-03-30 10:48:35 -0700366// It will fail if the offerer uses the mux-BUNDLE policy but the answerer
367// doesn't support BUNDLE.
368TEST_P(PeerConnectionBundleTest, MaxBundleNotSupportedInAnswer) {
369 RTCConfiguration config;
370 config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
371 auto caller = CreatePeerConnectionWithAudioVideo(config);
372 auto callee = CreatePeerConnectionWithAudioVideo();
373
374 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
375 bool equal_before =
376 (caller->voice_rtp_transport() == caller->video_rtp_transport());
377 EXPECT_EQ(true, equal_before);
378 RTCOfferAnswerOptions options;
379 options.use_rtp_mux = false;
380 EXPECT_FALSE(
381 caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(options)));
382}
383
Steve Anton6f25b092017-10-23 09:39:20 -0700384// The following parameterized test verifies that an offer/answer with varying
385// bundle policies and either bundle in the answer or not will produce the
386// expected RTP transports for audio and video. In particular, for bundling we
387// care about whether they are separate transports or the same.
388
389enum class BundleIncluded { kBundleInAnswer, kBundleNotInAnswer };
390std::ostream& operator<<(std::ostream& out, BundleIncluded value) {
391 switch (value) {
392 case BundleIncluded::kBundleInAnswer:
393 return out << "bundle in answer";
394 case BundleIncluded::kBundleNotInAnswer:
395 return out << "bundle not in answer";
396 }
397 return out << "unknown";
398}
399
400class PeerConnectionBundleMatrixTest
Steve Anton7464fca2018-01-19 11:10:37 -0800401 : public PeerConnectionBundleBaseTest,
Steve Anton6f25b092017-10-23 09:39:20 -0700402 public ::testing::WithParamInterface<
Steve Anton7464fca2018-01-19 11:10:37 -0800403 std::tuple<SdpSemantics,
404 std::tuple<BundlePolicy, BundleIncluded, bool, bool>>> {
Steve Anton6f25b092017-10-23 09:39:20 -0700405 protected:
Steve Anton7464fca2018-01-19 11:10:37 -0800406 PeerConnectionBundleMatrixTest()
407 : PeerConnectionBundleBaseTest(std::get<0>(GetParam())) {
408 auto param = std::get<1>(GetParam());
409 bundle_policy_ = std::get<0>(param);
410 bundle_included_ = std::get<1>(param);
411 expected_same_before_ = std::get<2>(param);
412 expected_same_after_ = std::get<3>(param);
Steve Anton6f25b092017-10-23 09:39:20 -0700413 }
414
415 PeerConnectionInterface::BundlePolicy bundle_policy_;
416 BundleIncluded bundle_included_;
417 bool expected_same_before_;
418 bool expected_same_after_;
419};
420
421TEST_P(PeerConnectionBundleMatrixTest,
422 VerifyTransportsBeforeAndAfterSettingRemoteAnswer) {
423 RTCConfiguration config;
424 config.bundle_policy = bundle_policy_;
425 auto caller = CreatePeerConnectionWithAudioVideo(config);
426 auto callee = CreatePeerConnectionWithAudioVideo();
427
428 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
Zhi Huange830e682018-03-30 10:48:35 -0700429 bool equal_before =
430 (caller->voice_rtp_transport() == caller->video_rtp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700431 EXPECT_EQ(expected_same_before_, equal_before);
432
433 RTCOfferAnswerOptions options;
434 options.use_rtp_mux = (bundle_included_ == BundleIncluded::kBundleInAnswer);
435 ASSERT_TRUE(
436 caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(options)));
Zhi Huange830e682018-03-30 10:48:35 -0700437 bool equal_after =
438 (caller->voice_rtp_transport() == caller->video_rtp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700439 EXPECT_EQ(expected_same_after_, equal_after);
440}
441
442// The max-bundle policy means we should anticipate bundling being negotiated,
443// and multiplex audio/video from the start.
444// For all other policies, bundling should only be enabled if negotiated by the
445// answer.
446INSTANTIATE_TEST_CASE_P(
447 PeerConnectionBundleTest,
448 PeerConnectionBundleMatrixTest,
Steve Anton7464fca2018-01-19 11:10:37 -0800449 Combine(Values(SdpSemantics::kPlanB, SdpSemantics::kUnifiedPlan),
450 Values(std::make_tuple(BundlePolicy::kBundlePolicyBalanced,
451 BundleIncluded::kBundleInAnswer,
452 false,
453 true),
454 std::make_tuple(BundlePolicy::kBundlePolicyBalanced,
455 BundleIncluded::kBundleNotInAnswer,
456 false,
457 false),
458 std::make_tuple(BundlePolicy::kBundlePolicyMaxBundle,
459 BundleIncluded::kBundleInAnswer,
460 true,
461 true),
Steve Anton7464fca2018-01-19 11:10:37 -0800462 std::make_tuple(BundlePolicy::kBundlePolicyMaxCompat,
463 BundleIncluded::kBundleInAnswer,
464 false,
465 true),
466 std::make_tuple(BundlePolicy::kBundlePolicyMaxCompat,
467 BundleIncluded::kBundleNotInAnswer,
468 false,
469 false))));
Steve Anton6f25b092017-10-23 09:39:20 -0700470
471// Test that the audio/video transports on the callee side are the same before
472// and after setting a local answer when max BUNDLE is enabled and an offer with
473// BUNDLE is received.
Steve Anton7464fca2018-01-19 11:10:37 -0800474TEST_P(PeerConnectionBundleTest,
Steve Anton6f25b092017-10-23 09:39:20 -0700475 TransportsSameForMaxBundleWithBundleInRemoteOffer) {
476 auto caller = CreatePeerConnectionWithAudioVideo();
477 RTCConfiguration config;
478 config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
479 auto callee = CreatePeerConnectionWithAudioVideo(config);
480
481 RTCOfferAnswerOptions options_with_bundle;
482 options_with_bundle.use_rtp_mux = true;
483 ASSERT_TRUE(callee->SetRemoteDescription(
484 caller->CreateOfferAndSetAsLocal(options_with_bundle)));
485
Zhi Huange830e682018-03-30 10:48:35 -0700486 EXPECT_EQ(callee->voice_rtp_transport(), callee->video_rtp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700487
488 ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer()));
489
Zhi Huange830e682018-03-30 10:48:35 -0700490 EXPECT_EQ(callee->voice_rtp_transport(), callee->video_rtp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700491}
492
Steve Anton7464fca2018-01-19 11:10:37 -0800493TEST_P(PeerConnectionBundleTest,
Steve Anton6f25b092017-10-23 09:39:20 -0700494 FailToSetRemoteOfferWithNoBundleWhenBundlePolicyMaxBundle) {
495 auto caller = CreatePeerConnectionWithAudioVideo();
496 RTCConfiguration config;
497 config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
498 auto callee = CreatePeerConnectionWithAudioVideo(config);
499
500 RTCOfferAnswerOptions options_no_bundle;
501 options_no_bundle.use_rtp_mux = false;
502 EXPECT_FALSE(callee->SetRemoteDescription(
503 caller->CreateOfferAndSetAsLocal(options_no_bundle)));
504}
505
506// Test that if the media section which has the bundled transport is rejected,
507// then the peers still connect and the bundled transport switches to the other
508// media section.
509// Note: This is currently failing because of the following bug:
510// https://bugs.chromium.org/p/webrtc/issues/detail?id=6280
Steve Anton7464fca2018-01-19 11:10:37 -0800511TEST_P(PeerConnectionBundleTest,
Steve Anton6f25b092017-10-23 09:39:20 -0700512 DISABLED_SuccessfullyNegotiateMaxBundleIfBundleTransportMediaRejected) {
513 RTCConfiguration config;
514 config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
515 auto caller = CreatePeerConnectionWithAudioVideo(config);
516 auto callee = CreatePeerConnection();
517 callee->AddVideoTrack("v");
518
519 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
520
521 RTCOfferAnswerOptions options;
522 options.offer_to_receive_audio = 0;
523 ASSERT_TRUE(
524 caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(options)));
525
Zhi Huange830e682018-03-30 10:48:35 -0700526 EXPECT_FALSE(caller->voice_rtp_transport());
527 EXPECT_TRUE(caller->video_rtp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700528}
529
530// When requiring RTCP multiplexing, the PeerConnection never makes RTCP
531// transport channels.
Steve Anton7464fca2018-01-19 11:10:37 -0800532TEST_P(PeerConnectionBundleTest, NeverCreateRtcpTransportWithRtcpMuxRequired) {
Steve Anton6f25b092017-10-23 09:39:20 -0700533 RTCConfiguration config;
534 config.rtcp_mux_policy = RtcpMuxPolicy::kRtcpMuxPolicyRequire;
535 auto caller = CreatePeerConnectionWithAudioVideo(config);
536 auto callee = CreatePeerConnectionWithAudioVideo();
537
538 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
539
Zhi Huange830e682018-03-30 10:48:35 -0700540 EXPECT_FALSE(caller->voice_rtcp_transport());
541 EXPECT_FALSE(caller->video_rtcp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700542
543 ASSERT_TRUE(
544 caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
545
Zhi Huange830e682018-03-30 10:48:35 -0700546 EXPECT_FALSE(caller->voice_rtcp_transport());
547 EXPECT_FALSE(caller->video_rtcp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700548}
549
Zhi Huange830e682018-03-30 10:48:35 -0700550// When negotiating RTCP multiplexing, the PeerConnection makes RTCP transports
551// when the offer is sent, but will destroy them once the remote answer is set.
Steve Anton7464fca2018-01-19 11:10:37 -0800552TEST_P(PeerConnectionBundleTest,
Steve Anton6f25b092017-10-23 09:39:20 -0700553 CreateRtcpTransportOnlyBeforeAnswerWithRtcpMuxNegotiate) {
554 RTCConfiguration config;
555 config.rtcp_mux_policy = RtcpMuxPolicy::kRtcpMuxPolicyNegotiate;
556 auto caller = CreatePeerConnectionWithAudioVideo(config);
557 auto callee = CreatePeerConnectionWithAudioVideo();
558
559 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
560
Zhi Huange830e682018-03-30 10:48:35 -0700561 EXPECT_TRUE(caller->voice_rtcp_transport());
562 EXPECT_TRUE(caller->video_rtcp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700563
564 ASSERT_TRUE(
565 caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
566
Zhi Huange830e682018-03-30 10:48:35 -0700567 EXPECT_FALSE(caller->voice_rtcp_transport());
568 EXPECT_FALSE(caller->video_rtcp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700569}
570
Steve Anton7464fca2018-01-19 11:10:37 -0800571TEST_P(PeerConnectionBundleTest, FailToSetDescriptionWithBundleAndNoRtcpMux) {
Steve Anton6f25b092017-10-23 09:39:20 -0700572 auto caller = CreatePeerConnectionWithAudioVideo();
573 auto callee = CreatePeerConnectionWithAudioVideo();
574
575 RTCOfferAnswerOptions options;
576 options.use_rtp_mux = true;
577
578 auto offer = caller->CreateOffer(options);
579 SdpContentsForEach(RemoveRtcpMux(), offer->description());
580
581 std::string error;
582 EXPECT_FALSE(caller->SetLocalDescription(CloneSessionDescription(offer.get()),
583 &error));
584 EXPECT_EQ(
585 "Failed to set local offer sdp: rtcp-mux must be enabled when BUNDLE is "
586 "enabled.",
587 error);
588
589 EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer), &error));
590 EXPECT_EQ(
591 "Failed to set remote offer sdp: rtcp-mux must be enabled when BUNDLE is "
592 "enabled.",
593 error);
594}
595
596// Test that candidates sent to the "video" transport do not get pushed down to
597// the "audio" transport channel when bundling.
Steve Anton7464fca2018-01-19 11:10:37 -0800598TEST_P(PeerConnectionBundleTest,
Steve Anton6f25b092017-10-23 09:39:20 -0700599 IgnoreCandidatesForUnusedTransportWhenBundling) {
600 const SocketAddress kAudioAddress1("1.1.1.1", 1111);
601 const SocketAddress kAudioAddress2("2.2.2.2", 2222);
602 const SocketAddress kVideoAddress("3.3.3.3", 3333);
603 const SocketAddress kCallerAddress("4.4.4.4", 0);
604 const SocketAddress kCalleeAddress("5.5.5.5", 0);
605
606 auto caller = CreatePeerConnectionWithAudioVideo();
607 auto callee = CreatePeerConnectionWithAudioVideo();
608
609 caller->network()->AddInterface(kCallerAddress);
610 callee->network()->AddInterface(kCalleeAddress);
611
612 RTCOfferAnswerOptions options;
613 options.use_rtp_mux = true;
614
615 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
616 ASSERT_TRUE(
Zhi Huange830e682018-03-30 10:48:35 -0700617 caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(options)));
Steve Anton6f25b092017-10-23 09:39:20 -0700618
619 // The way the *_WAIT checks work is they only wait if the condition fails,
620 // which does not help in the case where state is not changing. This is
621 // problematic in this test since we want to verify that adding a video
622 // candidate does _not_ change state. So we interleave candidates and assume
623 // that messages are executed in the order they were posted.
624
625 cricket::Candidate audio_candidate1 = CreateLocalUdpCandidate(kAudioAddress1);
626 ASSERT_TRUE(caller->AddIceCandidateToMedia(&audio_candidate1,
627 cricket::MEDIA_TYPE_AUDIO));
628
629 cricket::Candidate video_candidate = CreateLocalUdpCandidate(kVideoAddress);
630 ASSERT_TRUE(caller->AddIceCandidateToMedia(&video_candidate,
631 cricket::MEDIA_TYPE_VIDEO));
632
633 cricket::Candidate audio_candidate2 = CreateLocalUdpCandidate(kAudioAddress2);
634 ASSERT_TRUE(caller->AddIceCandidateToMedia(&audio_candidate2,
635 cricket::MEDIA_TYPE_AUDIO));
636
637 EXPECT_TRUE_WAIT(caller->HasConnectionWithRemoteAddress(kAudioAddress1),
638 kDefaultTimeout);
639 EXPECT_TRUE_WAIT(caller->HasConnectionWithRemoteAddress(kAudioAddress2),
640 kDefaultTimeout);
641 EXPECT_FALSE(caller->HasConnectionWithRemoteAddress(kVideoAddress));
642}
643
644// Test that the transport used by both audio and video is the transport
645// associated with the first MID in the answer BUNDLE group, even if it's in a
646// different order from the offer.
Steve Anton7464fca2018-01-19 11:10:37 -0800647TEST_P(PeerConnectionBundleTest, BundleOnFirstMidInAnswer) {
Steve Anton6f25b092017-10-23 09:39:20 -0700648 auto caller = CreatePeerConnectionWithAudioVideo();
649 auto callee = CreatePeerConnectionWithAudioVideo();
650
651 ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
652
Zhi Huange830e682018-03-30 10:48:35 -0700653 auto* old_video_transport = caller->video_rtp_transport();
Steve Anton6f25b092017-10-23 09:39:20 -0700654
655 auto answer = callee->CreateAnswer();
656 auto* old_bundle_group =
657 answer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
Steve Anton7464fca2018-01-19 11:10:37 -0800658 std::string first_mid = old_bundle_group->content_names()[0];
659 std::string second_mid = old_bundle_group->content_names()[1];
Steve Anton6f25b092017-10-23 09:39:20 -0700660 answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
661
662 cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
Steve Anton7464fca2018-01-19 11:10:37 -0800663 new_bundle_group.AddContentName(second_mid);
664 new_bundle_group.AddContentName(first_mid);
Steve Anton6f25b092017-10-23 09:39:20 -0700665 answer->description()->AddGroup(new_bundle_group);
666
667 ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer)));
668
Zhi Huange830e682018-03-30 10:48:35 -0700669 EXPECT_EQ(old_video_transport, caller->video_rtp_transport());
670 EXPECT_EQ(caller->voice_rtp_transport(), caller->video_rtp_transport());
Steve Anton6f25b092017-10-23 09:39:20 -0700671}
672
Zhi Huang365381f2018-04-13 16:44:34 -0700673// This tests that applying description with conflicted RTP demuxing criteria
674// will fail.
675TEST_P(PeerConnectionBundleTest,
676 ApplyDescriptionWithConflictedDemuxCriteriaFail) {
677 auto caller = CreatePeerConnectionWithAudioVideo();
678 auto callee = CreatePeerConnectionWithAudioVideo();
679
680 RTCOfferAnswerOptions options;
681 options.use_rtp_mux = false;
682 auto offer = caller->CreateOffer(options);
683 // Modified the SDP to make two m= sections have the same SSRC.
684 ASSERT_GE(offer->description()->contents().size(), 2U);
685 offer->description()
686 ->contents()[0]
687 .description->mutable_streams()[0]
688 .ssrcs[0] = 1111222;
689 offer->description()
690 ->contents()[1]
691 .description->mutable_streams()[0]
692 .ssrcs[0] = 1111222;
693 EXPECT_TRUE(
694 caller->SetLocalDescription(CloneSessionDescription(offer.get())));
695 EXPECT_TRUE(callee->SetRemoteDescription(std::move(offer)));
696 EXPECT_TRUE(callee->CreateAnswerAndSetAsLocal(options));
697
698 // Enable BUNDLE in subsequent offer/answer exchange and two m= sections are
699 // expectd to use one RtpTransport underneath.
700 options.use_rtp_mux = true;
701 EXPECT_TRUE(
702 callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(options)));
703 auto answer = callee->CreateAnswer(options);
704 // When BUNDLE is enabled, applying the description is expected to fail
705 // because the demuxing criteria is conflicted.
706 EXPECT_FALSE(callee->SetLocalDescription(std::move(answer)));
707}
708
Zhi Huangd2248f82018-04-10 14:41:03 -0700709// This tests that changing the pre-negotiated BUNDLE tag is not supported.
710TEST_P(PeerConnectionBundleTest, RejectDescriptionChangingBundleTag) {
711 RTCConfiguration config;
712 config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
713 auto caller = CreatePeerConnectionWithAudioVideo(config);
714 auto callee = CreatePeerConnectionWithAudioVideo(config);
715
716 RTCOfferAnswerOptions options;
717 options.use_rtp_mux = true;
718 auto offer = caller->CreateOfferAndSetAsLocal(options);
719
720 // Create a new bundle-group with different bundled_mid.
721 auto* old_bundle_group =
722 offer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
723 std::string first_mid = old_bundle_group->content_names()[0];
724 std::string second_mid = old_bundle_group->content_names()[1];
725 cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
726 new_bundle_group.AddContentName(second_mid);
727
728 auto re_offer = CloneSessionDescription(offer.get());
729 callee->SetRemoteDescription(std::move(offer));
730 auto answer = callee->CreateAnswer(options);
731 // Reject the first MID.
732 answer->description()->contents()[0].rejected = true;
733 // Remove the first MID from the bundle group.
734 answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
735 answer->description()->AddGroup(new_bundle_group);
736 // The answer is expected to be rejected.
737 EXPECT_FALSE(caller->SetRemoteDescription(std::move(answer)));
738
739 // Do the same thing for re-offer.
740 re_offer->description()->contents()[0].rejected = true;
741 re_offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
742 re_offer->description()->AddGroup(new_bundle_group);
743 // The re-offer is expected to be rejected.
744 EXPECT_FALSE(caller->SetLocalDescription(std::move(re_offer)));
745}
746
747// This tests that removing contents from BUNDLE group and reject the whole
748// BUNDLE group could work. This is a regression test for
749// (https://bugs.chromium.org/p/chromium/issues/detail?id=827917)
750TEST_P(PeerConnectionBundleTest, RemovingContentAndRejectBundleGroup) {
751 RTCConfiguration config;
752#ifndef HAVE_SCTP
753 config.enable_rtp_data_channel = true;
754#endif
755 config.bundle_policy = BundlePolicy::kBundlePolicyMaxBundle;
756 auto caller = CreatePeerConnectionWithAudioVideo(config);
757 caller->CreateDataChannel("dc");
758
759 auto offer = caller->CreateOfferAndSetAsLocal();
760 auto re_offer = CloneSessionDescription(offer.get());
761
762 // Removing the second MID from the BUNDLE group.
763 auto* old_bundle_group =
764 offer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
765 std::string first_mid = old_bundle_group->content_names()[0];
766 std::string third_mid = old_bundle_group->content_names()[2];
767 cricket::ContentGroup new_bundle_group(cricket::GROUP_TYPE_BUNDLE);
768 new_bundle_group.AddContentName(first_mid);
769 new_bundle_group.AddContentName(third_mid);
770
771 // Reject the entire new bundle group.
772 re_offer->description()->contents()[0].rejected = true;
773 re_offer->description()->contents()[2].rejected = true;
774 re_offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
775 re_offer->description()->AddGroup(new_bundle_group);
776
777 EXPECT_TRUE(caller->SetLocalDescription(std::move(re_offer)));
778}
779
780// This tests that the BUNDLE group in answer should be a subset of the offered
781// group.
782TEST_P(PeerConnectionBundleTest, AddContentToBundleGroupInAnswerNotSupported) {
783 auto caller = CreatePeerConnectionWithAudioVideo();
784 auto callee = CreatePeerConnectionWithAudioVideo();
785
786 auto offer = caller->CreateOffer();
787 std::string first_mid = offer->description()->contents()[0].name;
788 std::string second_mid = offer->description()->contents()[1].name;
789
790 cricket::ContentGroup bundle_group(cricket::GROUP_TYPE_BUNDLE);
791 bundle_group.AddContentName(first_mid);
792 offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
793 offer->description()->AddGroup(bundle_group);
794 EXPECT_TRUE(
795 caller->SetLocalDescription(CloneSessionDescription(offer.get())));
796 EXPECT_TRUE(callee->SetRemoteDescription(std::move(offer)));
797
798 auto answer = callee->CreateAnswer();
799 bundle_group.AddContentName(second_mid);
800 answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
801 answer->description()->AddGroup(bundle_group);
802
803 // The answer is expected to be rejected because second mid is not in the
804 // offered BUNDLE group.
805 EXPECT_FALSE(callee->SetLocalDescription(std::move(answer)));
806}
807
808// This tests that the BUNDLE group with non-existing MID should be rejectd.
809TEST_P(PeerConnectionBundleTest, RejectBundleGroupWithNonExistingMid) {
810 auto caller = CreatePeerConnectionWithAudioVideo();
811 auto callee = CreatePeerConnectionWithAudioVideo();
812
813 auto offer = caller->CreateOffer();
814 auto invalid_bundle_group =
815 *offer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
816 invalid_bundle_group.AddContentName("non-existing-MID");
817 offer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
818 offer->description()->AddGroup(invalid_bundle_group);
819
820 EXPECT_FALSE(
821 caller->SetLocalDescription(CloneSessionDescription(offer.get())));
822 EXPECT_FALSE(callee->SetRemoteDescription(std::move(offer)));
823}
824
825// This tests that an answer shouldn't be able to remove an m= section from an
826// established group without rejecting it.
827TEST_P(PeerConnectionBundleTest, RemoveContentFromBundleGroup) {
828 auto caller = CreatePeerConnectionWithAudioVideo();
829 auto callee = CreatePeerConnectionWithAudioVideo();
830
831 EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
832 EXPECT_TRUE(
833 caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
834
835 EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
836 auto answer = callee->CreateAnswer();
837 std::string second_mid = answer->description()->contents()[1].name;
838
839 auto invalid_bundle_group =
840 *answer->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
841 invalid_bundle_group.RemoveContentName(second_mid);
842 answer->description()->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE);
843 answer->description()->AddGroup(invalid_bundle_group);
844
845 EXPECT_FALSE(
846 callee->SetLocalDescription(CloneSessionDescription(answer.get())));
847}
848
Steve Anton7464fca2018-01-19 11:10:37 -0800849INSTANTIATE_TEST_CASE_P(PeerConnectionBundleTest,
850 PeerConnectionBundleTest,
851 Values(SdpSemantics::kPlanB,
852 SdpSemantics::kUnifiedPlan));
853
Taylor Brandstetter0ab56512018-04-12 10:30:48 -0700854// According to RFC5888, if an endpoint understands the semantics of an
855// "a=group", it MUST return an answer with that group. So, an empty BUNDLE
856// group is valid when the answerer rejects all m= sections (by stopping all
857// transceivers), meaning there's nothing to bundle.
858//
859// Only writing this test for Unified Plan mode, since there's no way to reject
860// m= sections in answers for Plan B without SDP munging.
861TEST_F(PeerConnectionBundleTestUnifiedPlan,
862 EmptyBundleGroupCreatedInAnswerWhenAppropriate) {
863 auto caller = CreatePeerConnectionWithAudioVideo();
864 auto callee = CreatePeerConnection();
865
866 EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
867
868 // Stop all transceivers, causing all m= sections to be rejected.
869 for (const auto& transceiver : callee->pc()->GetTransceivers()) {
870 transceiver->Stop();
871 }
872 EXPECT_TRUE(
873 caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
874
875 // Verify that the answer actually contained an empty bundle group.
876 const SessionDescriptionInterface* desc = callee->pc()->local_description();
877 ASSERT_NE(nullptr, desc);
878 const cricket::ContentGroup* bundle_group =
879 desc->description()->GetGroupByName(cricket::GROUP_TYPE_BUNDLE);
880 ASSERT_NE(nullptr, bundle_group);
881 EXPECT_TRUE(bundle_group->content_names().empty());
882}
883
Steve Anton6f25b092017-10-23 09:39:20 -0700884} // namespace webrtc