[Unified Plan] Support multiple BUNDLE groups.

In this CL, JsepTransportController and MediaSessionDescriptionFactory
are updated not to assume that there only exists at most a single BUNDLE
group but a list of N groups. This makes it possible to create multiple
BUNDLE groups by having multiple "a=group:BUNDLE" lines in the SDP.

This makes it possible to have some m= sections in one group and some
other m= sections in another group. For example, you could group all
audio m= sections in one group and all video m= sections in another
group. This enables "send all audio tracks on one transport and all
video tracks on another transport" in Unified Plan. This is something
that was possible in Plan B because all ssrcs in the same m= section
were implicitly bundled together forming a group of audio m= section and
video m= section (even without use of the BUNDLE tag).

PeerConnection will never create multiple BUNDLE groups by default, but
upon setting SDP with multiple BUNDLE groups the PeerConnection will
accept them if configured to accept BUNDLE. This makes it possible to
accept an SFU's BUNDLE offer without having to SDP munge the answer.

C++ unit tests are added. This fix has also been verified manually on:
https://jsfiddle.net/henbos/to89L6ce/43/

Without fix: 0+2 get bundled, 1+3 don't get bundled.
With fix: 0+2 get bundled in first group, 1+3 get bundled in second
group.

Bug: webrtc:10208
Change-Id: Iaf451fa5459c484730c8018274166ef154b19af8
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/214487
Reviewed-by: Taylor <deadbeef@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33838}
This commit is contained in:
Henrik Boström
2021-04-26 21:04:26 +02:00
committed by Commit Bot
parent 0548d18510
commit f8187e0a82
14 changed files with 1077 additions and 255 deletions

View File

@ -33,6 +33,8 @@ static const char kIceUfrag2[] = "u0002";
static const char kIcePwd2[] = "TESTICEPWD00000000000002";
static const char kIceUfrag3[] = "u0003";
static const char kIcePwd3[] = "TESTICEPWD00000000000003";
static const char kIceUfrag4[] = "u0004";
static const char kIcePwd4[] = "TESTICEPWD00000000000004";
static const char kAudioMid1[] = "audio1";
static const char kAudioMid2[] = "audio2";
static const char kVideoMid1[] = "video1";
@ -1099,6 +1101,512 @@ TEST_F(JsepTransportControllerTest, MultipleMediaSectionsOfSameTypeWithBundle) {
ASSERT_TRUE(it2 != changed_dtls_transport_by_mid_.end());
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroups) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group1(cricket::GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(kMid1Audio);
bundle_group1.AddContentName(kMid2Video);
cricket::ContentGroup bundle_group2(cricket::GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(kMid3Audio);
bundle_group2.AddContentName(kMid4Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(bundle_group1);
local_offer->AddGroup(bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(bundle_group1);
remote_answer->AddGroup(bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Verify that (kMid1Audio,kMid2Video) and (kMid3Audio,kMid4Video) form two
// distinct bundled groups.
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Video);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
auto mid4_transport = transport_controller_->GetRtpTransport(kMid4Video);
EXPECT_EQ(mid1_transport, mid2_transport);
EXPECT_EQ(mid3_transport, mid4_transport);
EXPECT_NE(mid1_transport, mid3_transport);
auto it = changed_rtp_transport_by_mid_.find(kMid1Audio);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(it->second, mid1_transport);
it = changed_rtp_transport_by_mid_.find(kMid2Video);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(it->second, mid2_transport);
it = changed_rtp_transport_by_mid_.find(kMid3Audio);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(it->second, mid3_transport);
it = changed_rtp_transport_by_mid_.find(kMid4Video);
ASSERT_TRUE(it != changed_rtp_transport_by_mid_.end());
EXPECT_EQ(it->second, mid4_transport);
}
TEST_F(JsepTransportControllerTest,
MultipleBundleGroupsInOfferButOnlyASingleGroupInAnswer) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
cricket::ContentGroup bundle_group1(cricket::GROUP_TYPE_BUNDLE);
bundle_group1.AddContentName(kMid1Audio);
bundle_group1.AddContentName(kMid2Video);
cricket::ContentGroup bundle_group2(cricket::GROUP_TYPE_BUNDLE);
bundle_group2.AddContentName(kMid3Audio);
bundle_group2.AddContentName(kMid4Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
// The offer has both groups.
local_offer->AddGroup(bundle_group1);
local_offer->AddGroup(bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
// The answer only has a single group! This is what happens when talking to an
// endpoint that does not have support for multiple BUNDLE groups.
remote_answer->AddGroup(bundle_group1);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
// Verify that (kMid1Audio,kMid2Video) form a bundle group, but that
// kMid3Audio and kMid4Video are unbundled.
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Video);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
auto mid4_transport = transport_controller_->GetRtpTransport(kMid4Video);
EXPECT_EQ(mid1_transport, mid2_transport);
EXPECT_NE(mid3_transport, mid4_transport);
EXPECT_NE(mid1_transport, mid3_transport);
EXPECT_NE(mid1_transport, mid4_transport);
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsIllegallyChangeGroup) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid2Video) and (kMid3Audio,kMid4Video).
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid2Video);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid3Audio);
offer_bundle_group2.AddContentName(kMid4Video);
// Answer groups (kMid1Audio,kMid4Video) and (kMid3Audio,kMid2Video), i.e. the
// second group members have switched places. This should get rejected.
cricket::ContentGroup answer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group1.AddContentName(kMid1Audio);
answer_bundle_group1.AddContentName(kMid4Video);
cricket::ContentGroup answer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group2.AddContentName(kMid3Audio);
answer_bundle_group2.AddContentName(kMid2Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(offer_bundle_group1);
local_offer->AddGroup(offer_bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(answer_bundle_group1);
remote_answer->AddGroup(answer_bundle_group2);
// Accept offer.
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
// Reject answer!
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsInvalidSubsets) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid2Video) and (kMid3Audio,kMid4Video).
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid2Video);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid3Audio);
offer_bundle_group2.AddContentName(kMid4Video);
// Answer groups (kMid1Audio) and (kMid2Video), i.e. the second group was
// moved from the first group. This should get rejected.
cricket::ContentGroup answer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group1.AddContentName(kMid1Audio);
cricket::ContentGroup answer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group2.AddContentName(kMid2Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(offer_bundle_group1);
local_offer->AddGroup(offer_bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag4, kIcePwd4,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(answer_bundle_group1);
remote_answer->AddGroup(answer_bundle_group2);
// Accept offer.
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
// Reject answer!
EXPECT_FALSE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsInvalidOverlap) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Video[] = "2_video";
static const char kMid3Audio[] = "3_audio";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid3Audio) and (kMid2Video,kMid3Audio), i.e.
// kMid3Audio is in both groups - this is illegal.
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid2Video);
offer_bundle_group2.AddContentName(kMid3Audio);
auto offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(offer.get(), kMid2Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(offer.get(), kMid3Audio, kIceUfrag3, kIcePwd3,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
offer->AddGroup(offer_bundle_group1);
offer->AddGroup(offer_bundle_group2);
// Reject offer, both if set as local or remote.
EXPECT_FALSE(
transport_controller_->SetLocalDescription(SdpType::kOffer, offer.get())
.ok());
EXPECT_FALSE(
transport_controller_->SetRemoteDescription(SdpType::kOffer, offer.get())
.ok());
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsUnbundleFirstMid) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
static const char kMid5Video[] = "5_video";
static const char kMid6Video[] = "6_video";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid2Audio,kMid3Audio) and
// (kMid4Video,kMid5Video,kMid6Video).
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid2Audio);
offer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid4Video);
offer_bundle_group2.AddContentName(kMid5Video);
offer_bundle_group2.AddContentName(kMid6Video);
// Answer groups (kMid2Audio,kMid3Audio) and (kMid5Video,kMid6Video), i.e.
// we've moved the first MIDs out of the groups.
cricket::ContentGroup answer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group1.AddContentName(kMid2Audio);
answer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup answer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group2.AddContentName(kMid5Video);
answer_bundle_group2.AddContentName(kMid6Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid6Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(offer_bundle_group1);
local_offer->AddGroup(offer_bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid6Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(answer_bundle_group1);
remote_answer->AddGroup(answer_bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
auto mid4_transport = transport_controller_->GetRtpTransport(kMid4Video);
auto mid5_transport = transport_controller_->GetRtpTransport(kMid5Video);
auto mid6_transport = transport_controller_->GetRtpTransport(kMid6Video);
EXPECT_NE(mid1_transport, mid2_transport);
EXPECT_EQ(mid2_transport, mid3_transport);
EXPECT_NE(mid4_transport, mid5_transport);
EXPECT_EQ(mid5_transport, mid6_transport);
EXPECT_NE(mid1_transport, mid4_transport);
EXPECT_NE(mid2_transport, mid5_transport);
}
TEST_F(JsepTransportControllerTest, MultipleBundleGroupsChangeFirstMid) {
static const char kMid1Audio[] = "1_audio";
static const char kMid2Audio[] = "2_audio";
static const char kMid3Audio[] = "3_audio";
static const char kMid4Video[] = "4_video";
static const char kMid5Video[] = "5_video";
static const char kMid6Video[] = "6_video";
CreateJsepTransportController(JsepTransportController::Config());
// Offer groups (kMid1Audio,kMid2Audio,kMid3Audio) and
// (kMid4Video,kMid5Video,kMid6Video).
cricket::ContentGroup offer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group1.AddContentName(kMid1Audio);
offer_bundle_group1.AddContentName(kMid2Audio);
offer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup offer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
offer_bundle_group2.AddContentName(kMid4Video);
offer_bundle_group2.AddContentName(kMid5Video);
offer_bundle_group2.AddContentName(kMid6Video);
// Answer groups (kMid2Audio,kMid1Audio,kMid3Audio) and
// (kMid5Video,kMid6Video,kMid4Video), i.e. we've changed which MID is first
// but accept the whole group.
cricket::ContentGroup answer_bundle_group1(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group1.AddContentName(kMid2Audio);
answer_bundle_group1.AddContentName(kMid1Audio);
answer_bundle_group1.AddContentName(kMid3Audio);
cricket::ContentGroup answer_bundle_group2(cricket::GROUP_TYPE_BUNDLE);
answer_bundle_group2.AddContentName(kMid5Video);
answer_bundle_group2.AddContentName(kMid6Video);
answer_bundle_group2.AddContentName(kMid4Video);
auto local_offer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(local_offer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(local_offer.get(), kMid3Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(local_offer.get(), kMid6Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
local_offer->AddGroup(offer_bundle_group1);
local_offer->AddGroup(offer_bundle_group2);
auto remote_answer = std::make_unique<cricket::SessionDescription>();
AddAudioSection(remote_answer.get(), kMid1Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid2Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddAudioSection(remote_answer.get(), kMid3Audio, kIceUfrag1, kIcePwd1,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid4Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid5Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
AddVideoSection(remote_answer.get(), kMid6Video, kIceUfrag2, kIcePwd2,
cricket::ICEMODE_FULL, cricket::CONNECTIONROLE_ACTPASS,
nullptr);
remote_answer->AddGroup(answer_bundle_group1);
remote_answer->AddGroup(answer_bundle_group2);
EXPECT_TRUE(transport_controller_
->SetLocalDescription(SdpType::kOffer, local_offer.get())
.ok());
// The fact that we accept this answer is actually a bug. If we accept the
// first MID to be in the group, we should also accept that it is the tagged
// one.
// TODO(https://crbug.com/webrtc/12699): When this issue is fixed, change this
// to EXPECT_FALSE and remove the below expectations about transports.
EXPECT_TRUE(transport_controller_
->SetRemoteDescription(SdpType::kAnswer, remote_answer.get())
.ok());
auto mid1_transport = transport_controller_->GetRtpTransport(kMid1Audio);
auto mid2_transport = transport_controller_->GetRtpTransport(kMid2Audio);
auto mid3_transport = transport_controller_->GetRtpTransport(kMid3Audio);
auto mid4_transport = transport_controller_->GetRtpTransport(kMid4Video);
auto mid5_transport = transport_controller_->GetRtpTransport(kMid5Video);
auto mid6_transport = transport_controller_->GetRtpTransport(kMid6Video);
EXPECT_NE(mid1_transport, mid4_transport);
EXPECT_EQ(mid1_transport, mid2_transport);
EXPECT_EQ(mid2_transport, mid3_transport);
EXPECT_EQ(mid4_transport, mid5_transport);
EXPECT_EQ(mid5_transport, mid6_transport);
}
// Tests that only a subset of all the m= sections are bundled.
TEST_F(JsepTransportControllerTest, BundleSubsetOfMediaSections) {
CreateJsepTransportController(JsepTransportController::Config());