Add support for data channels with Unified Plan
Bug: webrtc:7600
Change-Id: Idca1219fa692b24ced104aff7e89cde8a1bfe301
Reviewed-on: https://webrtc-review.googlesource.com/36240
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Peter Thatcher <pthatcher@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21478}
diff --git a/pc/mediasession.cc b/pc/mediasession.cc
index 692bcc2..f53785d 100644
--- a/pc/mediasession.cc
+++ b/pc/mediasession.cc
@@ -579,7 +579,7 @@
target_cryptos->end());
}
-static bool IsRtpProtocol(const std::string& protocol) {
+bool IsRtpProtocol(const std::string& protocol) {
return protocol.empty() ||
(protocol.find(cricket::kMediaProtocolRtpPrefix) != std::string::npos);
}
diff --git a/pc/mediasession.h b/pc/mediasession.h
index d3a9b3c..7c531aa 100644
--- a/pc/mediasession.h
+++ b/pc/mediasession.h
@@ -329,6 +329,9 @@
const rtc::CryptoOptions& crypto_options,
std::vector<std::string>* crypto_suite_names);
+// Returns true if the given media section protocol indicates use of RTP.
+bool IsRtpProtocol(const std::string& protocol);
+
} // namespace cricket
#endif // PC_MEDIASESSION_H_
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 94f2db1..ee1cffe 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -2099,8 +2099,14 @@
return error;
}
} else if (media_type == cricket::MEDIA_TYPE_DATA) {
- // TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
- // Plan.
+ if (GetDataMid() && new_content.name != *GetDataMid()) {
+ // Ignore all but the first data section.
+ continue;
+ }
+ RTCError error = UpdateDataChannel(source, new_content, bundle_group);
+ if (!error.ok()) {
+ return error;
+ }
} else {
LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
"Unknown section type.");
@@ -2147,6 +2153,34 @@
return RTCError::OK();
}
+RTCError PeerConnection::UpdateDataChannel(
+ cricket::ContentSource source,
+ const cricket::ContentInfo& content,
+ const cricket::ContentGroup* bundle_group) {
+ if (data_channel_type_ == cricket::DCT_NONE) {
+ LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
+ "Data channels are not supported.");
+ }
+ if (content.rejected) {
+ DestroyDataChannel();
+ } else {
+ if (!rtp_data_channel_ && !sctp_transport_) {
+ if (!CreateDataChannel(content.name, GetTransportNameForMediaSection(
+ content.name, bundle_group))) {
+ LOG_AND_RETURN_ERROR(RTCErrorType::INTERNAL_ERROR,
+ "Failed to create data channel.");
+ }
+ }
+ if (source == cricket::CS_REMOTE) {
+ const MediaContentDescription* data_desc = content.media_description();
+ if (data_desc && cricket::IsRtpProtocol(data_desc->protocol())) {
+ UpdateRemoteRtpDataChannels(GetActiveStreams(data_desc));
+ }
+ }
+ }
+ return RTCError::OK();
+}
+
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
PeerConnection::AssociateTransceiver(cricket::ContentSource source,
size_t mline_index,
@@ -2946,6 +2980,15 @@
GetOptionsForPlanBOffer(offer_answer_options, session_options);
}
+ // Intentionally unset the data channel type for RTP data channel with the
+ // second condition. Otherwise the RTP data channels would be successfully
+ // negotiated by default and the unit tests in WebRtcDataBrowserTest will fail
+ // when building with chromium. We want to leave RTP data channels broken, so
+ // people won't try to use them.
+ if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
+ session_options->data_channel_type = data_channel_type();
+ }
+
// Apply ICE restart flag and renomination flag.
for (auto& options : session_options->media_description_options) {
options.transport_options.ice_restart = offer_answer_options.ice_restart;
@@ -3022,9 +3065,7 @@
}
if (!data_index && offer_new_data_description) {
session_options->media_description_options.push_back(
- cricket::MediaDescriptionOptions(
- cricket::MEDIA_TYPE_DATA, cricket::CN_DATA,
- RtpTransceiverDirection::kSendRecv, false));
+ GetMediaDescriptionOptionsForActiveData(cricket::CN_DATA));
data_index = session_options->media_description_options.size() - 1;
}
@@ -3034,22 +3075,9 @@
cricket::MediaDescriptionOptions* video_media_description_options =
!video_index ? nullptr
: &session_options->media_description_options[*video_index];
- cricket::MediaDescriptionOptions* data_media_description_options =
- !data_index ? nullptr
- : &session_options->media_description_options[*data_index];
AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
video_media_description_options);
- AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options);
-
- // Intentionally unset the data channel type for RTP data channel with the
- // second condition. Otherwise the RTP data channels would be successfully
- // negotiated by default and the unit tests in WebRtcDataBrowserTest will fail
- // when building with chromium. We want to leave RTP data channels broken, so
- // people won't try to use them.
- if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
- session_options->data_channel_type = data_channel_type();
- }
}
// Find a new MID that is not already in |used_mids|, then add it to |used_mids|
@@ -3150,8 +3178,14 @@
}
} else {
RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
- // TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
- // Plan.
+ RTC_CHECK(GetDataMid());
+ if (had_been_rejected || mid != *GetDataMid()) {
+ session_options->media_description_options.push_back(
+ GetMediaDescriptionOptionsForRejectedData(mid));
+ } else {
+ session_options->media_description_options.push_back(
+ GetMediaDescriptionOptionsForActiveData(mid));
+ }
}
}
// Next, look for transceivers that are newly added (that is, are not stopped
@@ -3178,8 +3212,12 @@
// See comment above for why CreateOffer changes the transceiver's state.
transceiver->internal()->set_mline_index(mline_index);
}
- // TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
- // Plan.
+ // Lastly, add a m-section if we have local data channels and an m section
+ // does not already exist.
+ if (!GetDataMid() && HasDataChannels()) {
+ session_options->media_description_options.push_back(
+ GetMediaDescriptionOptionsForActiveData(AllocateMid(&used_mids)));
+ }
}
void PeerConnection::GetOptionsForAnswer(
@@ -3193,6 +3231,14 @@
GetOptionsForPlanBAnswer(offer_answer_options, session_options);
}
+ // Intentionally unset the data channel type for RTP data channel. Otherwise
+ // the RTP data channels would be successfully negotiated by default and the
+ // unit tests in WebRtcDataBrowserTest will fail when building with chromium.
+ // We want to leave RTP data channels broken, so people won't try to use them.
+ if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
+ session_options->data_channel_type = data_channel_type();
+ }
+
// Apply ICE renomination flag.
for (auto& options : session_options->media_description_options) {
options.transport_options.enable_ice_renomination =
@@ -3247,21 +3293,9 @@
cricket::MediaDescriptionOptions* video_media_description_options =
!video_index ? nullptr
: &session_options->media_description_options[*video_index];
- cricket::MediaDescriptionOptions* data_media_description_options =
- !data_index ? nullptr
- : &session_options->media_description_options[*data_index];
AddRtpSenderOptions(GetSendersInternal(), audio_media_description_options,
video_media_description_options);
- AddRtpDataChannelOptions(rtp_data_channels_, data_media_description_options);
-
- // Intentionally unset the data channel type for RTP data channel. Otherwise
- // the RTP data channels would be successfully negotiated by default and the
- // unit tests in WebRtcDataBrowserTest will fail when building with chromium.
- // We want to leave RTP data channels broken, so people won't try to use them.
- if (!rtp_data_channels_.empty() || data_channel_type() != cricket::DCT_RTP) {
- session_options->data_channel_type = data_channel_type();
- }
}
void PeerConnection::GetOptionsForUnifiedPlanAnswer(
@@ -3282,8 +3316,16 @@
GetMediaDescriptionOptionsForTransceiver(transceiver, content.name));
} else {
RTC_CHECK_EQ(cricket::MEDIA_TYPE_DATA, media_type);
- // TODO(bugs.webrtc.org/7600): Add support for data channels with Unified
- // Plan.
+ RTC_CHECK(GetDataMid());
+ // Always reject a data section if it has already been rejected.
+ // Additionally, reject all data sections except for the first one.
+ if (content.rejected || content.name != *GetDataMid()) {
+ session_options->media_description_options.push_back(
+ GetMediaDescriptionOptionsForRejectedData(content.name));
+ } else {
+ session_options->media_description_options.push_back(
+ GetMediaDescriptionOptionsForActiveData(content.name));
+ }
}
}
}
@@ -3331,22 +3373,52 @@
// If we already have an data m= section, reject this extra one.
if (*data_index) {
session_options->media_description_options.push_back(
- cricket::MediaDescriptionOptions(
- cricket::MEDIA_TYPE_DATA, content.name,
- RtpTransceiverDirection::kInactive, true));
+ GetMediaDescriptionOptionsForRejectedData(content.name));
} else {
session_options->media_description_options.push_back(
- cricket::MediaDescriptionOptions(
- cricket::MEDIA_TYPE_DATA, content.name,
- // Direction for data sections is meaningless, but legacy
- // endpoints might expect sendrecv.
- RtpTransceiverDirection::kSendRecv, false));
+ GetMediaDescriptionOptionsForActiveData(content.name));
*data_index = session_options->media_description_options.size() - 1;
}
}
}
}
+cricket::MediaDescriptionOptions
+PeerConnection::GetMediaDescriptionOptionsForActiveData(
+ const std::string& mid) const {
+ // Direction for data sections is meaningless, but legacy endpoints might
+ // expect sendrecv.
+ cricket::MediaDescriptionOptions options(cricket::MEDIA_TYPE_DATA, mid,
+ RtpTransceiverDirection::kSendRecv,
+ /*stopped=*/false);
+ AddRtpDataChannelOptions(rtp_data_channels_, &options);
+ return options;
+}
+
+cricket::MediaDescriptionOptions
+PeerConnection::GetMediaDescriptionOptionsForRejectedData(
+ const std::string& mid) const {
+ cricket::MediaDescriptionOptions options(cricket::MEDIA_TYPE_DATA, mid,
+ RtpTransceiverDirection::kInactive,
+ /*stopped=*/true);
+ AddRtpDataChannelOptions(rtp_data_channels_, &options);
+ return options;
+}
+
+rtc::Optional<std::string> PeerConnection::GetDataMid() const {
+ switch (data_channel_type_) {
+ case cricket::DCT_RTP:
+ if (!rtp_data_channel_) {
+ return rtc::nullopt;
+ }
+ return rtp_data_channel_->content_name();
+ case cricket::DCT_SCTP:
+ return sctp_content_name_;
+ default:
+ return rtc::nullopt;
+ }
+}
+
void PeerConnection::RemoveSenders(cricket::MediaType media_type) {
UpdateLocalSenders(std::vector<cricket::StreamParams>(), media_type);
UpdateRemoteSendersList(std::vector<cricket::StreamParams>(), false,
diff --git a/pc/peerconnection.h b/pc/peerconnection.h
index 66061f1..a3797f4 100644
--- a/pc/peerconnection.h
+++ b/pc/peerconnection.h
@@ -428,6 +428,12 @@
const cricket::ContentInfo& content,
const cricket::ContentGroup* bundle_group);
+ // Either creates or destroys the local data channel according to the given
+ // media section.
+ RTCError UpdateDataChannel(cricket::ContentSource source,
+ const cricket::ContentInfo& content,
+ const cricket::ContentGroup* bundle_group);
+
// Associate the given transceiver according to the JSEP rules.
RTCErrorOr<
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
@@ -501,6 +507,21 @@
rtc::Optional<size_t>* data_index,
cricket::MediaSessionOptions* session_options);
+ // Generates the active MediaDescriptionOptions for the local data channel
+ // given the specified MID.
+ cricket::MediaDescriptionOptions GetMediaDescriptionOptionsForActiveData(
+ const std::string& mid) const;
+
+ // Generates the rejected MediaDescriptionOptions for the local data channel
+ // given the specified MID.
+ cricket::MediaDescriptionOptions GetMediaDescriptionOptionsForRejectedData(
+ const std::string& mid) const;
+
+ // Returns the MID for the data section associated with either the
+ // RtpDataChannel or SCTP data channel, if it has been set. If no data
+ // channels are configured this will return nullopt.
+ rtc::Optional<std::string> GetDataMid() const;
+
// Remove all local and remote senders of type |media_type|.
// Called when a media type is rejected (m-line set to port 0).
void RemoveSenders(cricket::MediaType media_type);
diff --git a/pc/peerconnection_jsep_unittest.cc b/pc/peerconnection_jsep_unittest.cc
index 119b135..5edd1a3 100644
--- a/pc/peerconnection_jsep_unittest.cc
+++ b/pc/peerconnection_jsep_unittest.cc
@@ -10,13 +10,17 @@
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "media/engine/webrtcmediaengine.h"
+#include "modules/audio_processing/include/audio_processing.h"
#include "pc/mediasession.h"
+#include "pc/peerconnectionfactory.h"
#include "pc/peerconnectionwrapper.h"
#include "pc/sdputils.h"
#ifdef WEBRTC_ANDROID
#include "pc/test/androidtestinitializer.h"
#endif
#include "pc/test/fakeaudiocapturemodule.h"
+#include "pc/test/fakesctptransport.h"
#include "rtc_base/gunit.h"
#include "rtc_base/ptr_util.h"
#include "rtc_base/virtualsocketserver.h"
@@ -36,6 +40,30 @@
using ::testing::Combine;
using ::testing::ElementsAre;
+class PeerConnectionFactoryForJsepTest : public PeerConnectionFactory {
+ public:
+ PeerConnectionFactoryForJsepTest()
+ : PeerConnectionFactory(
+ rtc::Thread::Current(),
+ rtc::Thread::Current(),
+ rtc::Thread::Current(),
+ rtc::WrapUnique(cricket::WebRtcMediaEngineFactory::Create(
+ FakeAudioCaptureModule::Create(),
+ CreateBuiltinAudioEncoderFactory(),
+ CreateBuiltinAudioDecoderFactory(),
+ nullptr,
+ nullptr,
+ nullptr,
+ AudioProcessing::Create())),
+ CreateCallFactory(),
+ nullptr) {}
+
+ std::unique_ptr<cricket::SctpTransportInternalFactory>
+ CreateSctpTransportInternalFactory() {
+ return rtc::MakeUnique<FakeSctpTransportFactory>();
+ }
+};
+
class PeerConnectionJsepTest : public ::testing::Test {
protected:
typedef std::unique_ptr<PeerConnectionWrapper> WrapperPtr;
@@ -45,10 +73,6 @@
#ifdef WEBRTC_ANDROID
InitializeAndroidObjects();
#endif
- pc_factory_ = CreatePeerConnectionFactory(
- rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
- FakeAudioCaptureModule::Create(), CreateBuiltinAudioEncoderFactory(),
- CreateBuiltinAudioDecoderFactory(), nullptr, nullptr);
}
WrapperPtr CreatePeerConnection() {
@@ -58,20 +82,22 @@
}
WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
+ rtc::scoped_refptr<PeerConnectionFactory> pc_factory(
+ new rtc::RefCountedObject<PeerConnectionFactoryForJsepTest>());
+ RTC_CHECK(pc_factory->Initialize());
auto observer = rtc::MakeUnique<MockPeerConnectionObserver>();
- auto pc = pc_factory_->CreatePeerConnection(config, nullptr, nullptr,
- observer.get());
+ auto pc = pc_factory->CreatePeerConnection(config, nullptr, nullptr,
+ observer.get());
if (!pc) {
return nullptr;
}
- return rtc::MakeUnique<PeerConnectionWrapper>(pc_factory_, pc,
+ return rtc::MakeUnique<PeerConnectionWrapper>(pc_factory, pc,
std::move(observer));
}
std::unique_ptr<rtc::VirtualSocketServer> vss_;
rtc::AutoSocketServerThread main_;
- rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
};
// Tests for JSEP initial offer generation.
@@ -80,8 +106,9 @@
// no media sections.
TEST_F(PeerConnectionJsepTest, EmptyInitialOffer) {
auto caller = CreatePeerConnection();
+
auto offer = caller->CreateOffer();
- EXPECT_EQ(0u, offer->description()->contents().size());
+ ASSERT_EQ(0u, offer->description()->contents().size());
}
// Test that an initial offer with one audio track generates one audio media
@@ -89,8 +116,8 @@
TEST_F(PeerConnectionJsepTest, AudioOnlyInitialOffer) {
auto caller = CreatePeerConnection();
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
- auto offer = caller->CreateOffer();
+ auto offer = caller->CreateOffer();
auto contents = offer->description()->contents();
ASSERT_EQ(1u, contents.size());
EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, contents[0].media_description()->type());
@@ -101,13 +128,37 @@
TEST_F(PeerConnectionJsepTest, VideoOnlyInitialOffer) {
auto caller = CreatePeerConnection();
caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
- auto offer = caller->CreateOffer();
+ auto offer = caller->CreateOffer();
auto contents = offer->description()->contents();
ASSERT_EQ(1u, contents.size());
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[0].media_description()->type());
}
+// Test that an initial offer with one data channel generates one data media
+// section.
+TEST_F(PeerConnectionJsepTest, DataOnlyInitialOffer) {
+ auto caller = CreatePeerConnection();
+ caller->CreateDataChannel("dc");
+
+ auto offer = caller->CreateOffer();
+ auto contents = offer->description()->contents();
+ ASSERT_EQ(1u, contents.size());
+ EXPECT_EQ(cricket::MEDIA_TYPE_DATA, contents[0].media_description()->type());
+}
+
+// Test that creating multiple data channels only results in one data section
+// generated in the offer.
+TEST_F(PeerConnectionJsepTest, MultipleDataChannelsCreateOnlyOneDataSection) {
+ auto caller = CreatePeerConnection();
+ caller->CreateDataChannel("first");
+ caller->CreateDataChannel("second");
+ caller->CreateDataChannel("third");
+
+ auto offer = caller->CreateOffer();
+ ASSERT_EQ(1u, offer->description()->contents().size());
+}
+
// Test that multiple media sections in the initial offer are ordered in the
// order the transceivers were added to the PeerConnection. This is required by
// JSEP section 5.2.1.
@@ -118,8 +169,8 @@
RtpTransceiverInit init;
init.direction = RtpTransceiverDirection::kSendOnly;
caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init);
- auto offer = caller->CreateOffer();
+ auto offer = caller->CreateOffer();
auto contents = offer->description()->contents();
ASSERT_EQ(3u, contents.size());
@@ -147,12 +198,8 @@
auto caller = CreatePeerConnection();
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+
auto offer = caller->CreateOffer();
-
- std::string sdp;
- offer->ToString(&sdp);
- RTC_LOG(LS_INFO) << sdp;
-
auto contents = offer->description()->contents();
ASSERT_EQ(2u, contents.size());
EXPECT_NE(contents[0].name, contents[1].name);
@@ -172,6 +219,7 @@
TEST_F(PeerConnectionJsepTest, SetLocalEmptyOfferCreatesNoTransceivers) {
auto caller = CreatePeerConnection();
+
ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
EXPECT_THAT(caller->pc()->GetTransceivers(), ElementsAre());
@@ -348,19 +396,26 @@
auto first_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
auto second_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
auto third_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
+ caller->CreateDataChannel("dc");
auto callee = CreatePeerConnection();
- ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
+ auto offer = caller->CreateOffer();
+ const auto* offer_data = cricket::GetFirstDataContent(offer->description());
+ ASSERT_TRUE(
+ caller->SetLocalDescription(CloneSessionDescription(offer.get())));
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
auto answer = callee->CreateAnswer();
auto contents = answer->description()->contents();
- ASSERT_EQ(3u, contents.size());
+ ASSERT_EQ(4u, contents.size());
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[0].media_description()->type());
- EXPECT_EQ(*first_transceiver->mid(), contents[0].name);
+ EXPECT_EQ(first_transceiver->mid(), contents[0].name);
EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, contents[1].media_description()->type());
- EXPECT_EQ(*second_transceiver->mid(), contents[1].name);
+ EXPECT_EQ(second_transceiver->mid(), contents[1].name);
EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[2].media_description()->type());
- EXPECT_EQ(*third_transceiver->mid(), contents[2].name);
+ EXPECT_EQ(third_transceiver->mid(), contents[2].name);
+ EXPECT_EQ(cricket::MEDIA_TYPE_DATA, contents[3].media_description()->type());
+ EXPECT_EQ(offer_data->name, contents[3].name);
}
// Test that an answering media section is marked as rejected if the underlying
@@ -619,6 +674,62 @@
Combine(Values(cricket::MEDIA_TYPE_AUDIO, cricket::MEDIA_TYPE_VIDEO),
Values(cricket::MEDIA_TYPE_AUDIO, cricket::MEDIA_TYPE_VIDEO)));
+// Test that a new data channel section will not reuse a recycleable audio or
+// video media section. Additionally, tests that the new section is added to the
+// end of the session description.
+TEST_F(PeerConnectionJsepTest, DataChannelDoesNotRecycleMediaSection) {
+ auto caller = CreatePeerConnection();
+ auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO);
+ auto callee = CreatePeerConnection();
+
+ ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
+
+ transceiver->Stop();
+
+ ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
+
+ caller->CreateDataChannel("dc");
+
+ auto offer = caller->CreateOffer();
+ auto offer_contents = offer->description()->contents();
+ ASSERT_EQ(2u, offer_contents.size());
+ EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO,
+ offer_contents[0].media_description()->type());
+ EXPECT_EQ(cricket::MEDIA_TYPE_DATA,
+ offer_contents[1].media_description()->type());
+
+ ASSERT_TRUE(
+ caller->SetLocalDescription(CloneSessionDescription(offer.get())));
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+
+ auto answer = callee->CreateAnswer();
+ auto answer_contents = answer->description()->contents();
+ ASSERT_EQ(2u, answer_contents.size());
+ EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO,
+ answer_contents[0].media_description()->type());
+ EXPECT_EQ(cricket::MEDIA_TYPE_DATA,
+ answer_contents[1].media_description()->type());
+}
+
+// Test that if a new track is added to an existing session that has a data,
+// the new section comes at the end of the new offer, after the existing data
+// section.
+TEST_F(PeerConnectionJsepTest, AudioTrackAddedAfterDataSectionInReoffer) {
+ auto caller = CreatePeerConnection();
+ caller->CreateDataChannel("dc");
+ auto callee = CreatePeerConnection();
+
+ ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get()));
+
+ caller->AddAudioTrack("a");
+
+ auto offer = caller->CreateOffer();
+ auto contents = offer->description()->contents();
+ ASSERT_EQ(2u, contents.size());
+ EXPECT_EQ(cricket::MEDIA_TYPE_DATA, contents[0].media_description()->type());
+ EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, contents[1].media_description()->type());
+}
+
// Tests for MID properties.
static void RenameSection(size_t mline_index,
@@ -712,6 +823,40 @@
EXPECT_NE(reoffer_contents[0].name, reoffer_contents[1].name);
}
+// Test that if an audio or video section has the default data section MID, then
+// CreateOffer will generate a unique MID for the newly added data section.
+TEST_F(PeerConnectionJsepTest,
+ CreateOfferGeneratesUniqueMidForDataSectionIfAlreadyTaken) {
+ // First, find what the default MID is for the data channel.
+ auto pc = CreatePeerConnection();
+ pc->CreateDataChannel("dc");
+ auto default_offer = pc->CreateOffer();
+ std::string default_data_mid =
+ default_offer->description()->contents()[0].name;
+
+ // Now do an offer/answer with one audio track which has a MID set to the
+ // default data MID.
+ auto caller = CreatePeerConnection();
+ caller->AddAudioTrack("a");
+ auto callee = CreatePeerConnection();
+
+ auto offer = caller->CreateOffer();
+ RenameSection(0, default_data_mid, offer.get());
+
+ ASSERT_TRUE(
+ caller->SetLocalDescription(CloneSessionDescription(offer.get())));
+ ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
+ ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
+
+ // Add a data channel and ensure that the MID is different.
+ caller->CreateDataChannel("dc");
+
+ auto reoffer = caller->CreateOffer();
+ auto reoffer_contents = reoffer->description()->contents();
+ EXPECT_EQ(default_data_mid, reoffer_contents[0].name);
+ EXPECT_NE(reoffer_contents[0].name, reoffer_contents[1].name);
+}
+
// Test that a reoffer initiated by the callee adds a new track to the caller.
TEST_F(PeerConnectionJsepTest, CalleeDoesReoffer) {
auto caller = CreatePeerConnection();
diff --git a/pc/peerconnectionwrapper.cc b/pc/peerconnectionwrapper.cc
index d297054..5d804a8 100644
--- a/pc/peerconnectionwrapper.cc
+++ b/pc/peerconnectionwrapper.cc
@@ -278,6 +278,11 @@
return pc()->AddTrack(CreateVideoTrack(track_label), std::move(streams));
}
+rtc::scoped_refptr<DataChannelInterface>
+PeerConnectionWrapper::CreateDataChannel(const std::string& label) {
+ return pc()->CreateDataChannel(label, nullptr);
+}
+
PeerConnectionInterface::SignalingState
PeerConnectionWrapper::signaling_state() {
return pc()->signaling_state();
diff --git a/pc/peerconnectionwrapper.h b/pc/peerconnectionwrapper.h
index 9208207..0830b55 100644
--- a/pc/peerconnectionwrapper.h
+++ b/pc/peerconnectionwrapper.h
@@ -142,6 +142,11 @@
const std::string& track_label,
std::vector<MediaStreamInterface*> streams = {});
+ // Calls the underlying PeerConnection's CreateDataChannel method with default
+ // initialization parameters.
+ rtc::scoped_refptr<DataChannelInterface> CreateDataChannel(
+ const std::string& label);
+
// Returns the signaling state of the underlying PeerConnection.
PeerConnectionInterface::SignalingState signaling_state();