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}
This commit is contained in:
Steve Anton
2017-12-28 16:38:23 -08:00
committed by Commit Bot
parent 5a25ab298d
commit fa2260d08d
7 changed files with 318 additions and 67 deletions

View File

@ -579,7 +579,7 @@ static void PruneCryptos(const CryptoParamsVec& filter,
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);
}

View File

@ -329,6 +329,9 @@ void GetSupportedDataSdesCryptoSuiteNames(
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_

View File

@ -2099,8 +2099,14 @@ RTCError PeerConnection::UpdateTransceiversAndDataChannels(
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 @@ RTCError PeerConnection::UpdateTransceiverChannel(
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 @@ void PeerConnection::GetOptionsForOffer(
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 @@ void PeerConnection::GetOptionsForPlanBOffer(
}
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 @@ void PeerConnection::GetOptionsForPlanBOffer(
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 @@ void PeerConnection::GetOptionsForUnifiedPlanOffer(
}
} 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 @@ void PeerConnection::GetOptionsForUnifiedPlanOffer(
// 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 @@ void PeerConnection::GetOptionsForAnswer(
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 @@ void PeerConnection::GetOptionsForPlanBAnswer(
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 @@ void PeerConnection::GetOptionsForUnifiedPlanAnswer(
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 @@ void PeerConnection::GenerateMediaDescriptionOptions(
// 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,

View File

@ -428,6 +428,12 @@ class PeerConnection : public PeerConnectionInterface,
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 @@ class PeerConnection : public PeerConnectionInterface,
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);

View File

@ -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::Values;
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 @@ class PeerConnectionJsepTest : public ::testing::Test {
#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 @@ class PeerConnectionJsepTest : public ::testing::Test {
}
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,
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 @@ class PeerConnectionJsepTest : public ::testing::Test {
// 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, EmptyInitialOffer) {
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, AudioOnlyInitialOffer) {
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 @@ TEST_F(PeerConnectionJsepTest, MediaSectionsInInitialOfferOrderedCorrectly) {
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 @@ TEST_F(PeerConnectionJsepTest, MediaSectionsInInitialOfferHaveDifferentMids) {
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,
TEST_F(PeerConnectionJsepTest, SetLocalEmptyOfferCreatesNoTransceivers) {
auto caller = CreatePeerConnection();
ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
EXPECT_THAT(caller->pc()->GetTransceivers(), ElementsAre());
@ -348,19 +396,26 @@ TEST_F(PeerConnectionJsepTest, CreateAnswerHasSameMidsAsOffer) {
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 @@ INSTANTIATE_TEST_CASE_P(
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 @@ TEST_F(PeerConnectionJsepTest, CreateOfferGeneratesUniqueMidIfAlreadyTaken) {
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();

View File

@ -278,6 +278,11 @@ rtc::scoped_refptr<RtpSenderInterface> PeerConnectionWrapper::AddVideoTrack(
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();

View File

@ -142,6 +142,11 @@ class PeerConnectionWrapper {
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();