/* * Copyright 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "pc/mediasession.h" #include "pc/peerconnectionwrapper.h" #include "pc/sdputils.h" #ifdef WEBRTC_ANDROID #include "pc/test/androidtestinitializer.h" #endif #include "pc/test/fakeaudiocapturemodule.h" #include "rtc_base/gunit.h" #include "rtc_base/ptr_util.h" #include "rtc_base/virtualsocketserver.h" #include "test/gmock.h" // This file contains tests that ensure the PeerConnection's implementation of // CreateOffer/CreateAnswer/SetLocalDescription/SetRemoteDescription conform // to the JavaScript Session Establishment Protocol (JSEP). // For now these semantics are only available when configuring the // PeerConnection with Unified Plan, but eventually that will be the default. namespace webrtc { using cricket::MediaContentDescription; using RTCConfiguration = PeerConnectionInterface::RTCConfiguration; using ::testing::Values; using ::testing::Combine; using ::testing::ElementsAre; class PeerConnectionJsepTest : public ::testing::Test { protected: typedef std::unique_ptr WrapperPtr; PeerConnectionJsepTest() : vss_(new rtc::VirtualSocketServer()), main_(vss_.get()) { #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() { RTCConfiguration config; config.sdp_semantics = SdpSemantics::kUnifiedPlan; return CreatePeerConnection(config); } WrapperPtr CreatePeerConnection(const RTCConfiguration& config) { auto observer = rtc::MakeUnique(); auto pc = pc_factory_->CreatePeerConnection(config, nullptr, nullptr, observer.get()); if (!pc) { return nullptr; } return rtc::MakeUnique(pc_factory_, pc, std::move(observer)); } std::unique_ptr vss_; rtc::AutoSocketServerThread main_; rtc::scoped_refptr pc_factory_; }; // Tests for JSEP initial offer generation. // Test that an offer created by a PeerConnection with no transceivers generates // no media sections. TEST_F(PeerConnectionJsepTest, EmptyInitialOffer) { auto caller = CreatePeerConnection(); auto offer = caller->CreateOffer(); EXPECT_EQ(0u, offer->description()->contents().size()); } // Test that an initial offer with one audio track generates one audio media // section. TEST_F(PeerConnectionJsepTest, AudioOnlyInitialOffer) { auto caller = CreatePeerConnection(); caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); 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()); } // Test than an initial offer with one video track generates one video media // section TEST_F(PeerConnectionJsepTest, VideoOnlyInitialOffer) { auto caller = CreatePeerConnection(); caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); 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 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. TEST_F(PeerConnectionJsepTest, MediaSectionsInInitialOfferOrderedCorrectly) { auto caller = CreatePeerConnection(); caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init); auto offer = caller->CreateOffer(); auto contents = offer->description()->contents(); ASSERT_EQ(3u, contents.size()); const MediaContentDescription* media_description1 = contents[0].media_description(); EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, media_description1->type()); EXPECT_EQ(RtpTransceiverDirection::kSendRecv, media_description1->direction()); const MediaContentDescription* media_description2 = contents[1].media_description(); EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, media_description2->type()); EXPECT_EQ(RtpTransceiverDirection::kSendRecv, media_description2->direction()); const MediaContentDescription* media_description3 = contents[2].media_description(); EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, media_description3->type()); EXPECT_EQ(RtpTransceiverDirection::kSendOnly, media_description3->direction()); } // Test that media sections in the initial offer have different mids. 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); } TEST_F(PeerConnectionJsepTest, StoppedTransceiverHasNoMediaSectionInInitialOffer) { auto caller = CreatePeerConnection(); auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); transceiver->Stop(); auto offer = caller->CreateOffer(); EXPECT_EQ(0u, offer->description()->contents().size()); } // Tests for JSEP SetLocalDescription with a local offer. TEST_F(PeerConnectionJsepTest, SetLocalEmptyOfferCreatesNoTransceivers) { auto caller = CreatePeerConnection(); ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer())); EXPECT_THAT(caller->pc()->GetTransceivers(), ElementsAre()); EXPECT_THAT(caller->pc()->GetSenders(), ElementsAre()); EXPECT_THAT(caller->pc()->GetReceivers(), ElementsAre()); } TEST_F(PeerConnectionJsepTest, SetLocalOfferSetsTransceiverMid) { auto caller = CreatePeerConnection(); auto audio_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); auto video_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); auto offer = caller->CreateOffer(); std::string audio_mid = offer->description()->contents()[0].name; std::string video_mid = offer->description()->contents()[1].name; ASSERT_TRUE(caller->SetLocalDescription(std::move(offer))); EXPECT_EQ(audio_mid, audio_transceiver->mid()); EXPECT_EQ(video_mid, video_transceiver->mid()); } // Tests for JSEP SetRemoteDescription with a remote offer. // Test that setting a remote offer with sendrecv audio and video creates two // transceivers, one for receiving audio and one for receiving video. TEST_F(PeerConnectionJsepTest, SetRemoteOfferCreatesTransceivers) { auto caller = CreatePeerConnection(); auto caller_audio = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); auto caller_video = caller->AddTransceiver(cricket::MEDIA_TYPE_VIDEO); auto callee = CreatePeerConnection(); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto transceivers = callee->pc()->GetTransceivers(); ASSERT_EQ(2u, transceivers.size()); EXPECT_EQ(cricket::MEDIA_TYPE_AUDIO, transceivers[0]->receiver()->media_type()); EXPECT_EQ(caller_audio->mid(), transceivers[0]->mid()); EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceivers[0]->direction()); EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, transceivers[1]->receiver()->media_type()); EXPECT_EQ(caller_video->mid(), transceivers[1]->mid()); EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceivers[1]->direction()); } // Test that setting a remote offer with an audio track will reuse the // transceiver created for a local audio track added by AddTrack. // This is specified in JSEP section 5.10 (Applying a Remote Description). The // intent is to preserve backwards compatibility with clients who only use the // AddTrack API. TEST_F(PeerConnectionJsepTest, SetRemoteOfferReusesTransceiverFromAddTrack) { auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); auto caller_audio = caller->pc()->GetTransceivers()[0]; auto callee = CreatePeerConnection(); callee->AddAudioTrack("a"); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto transceivers = callee->pc()->GetTransceivers(); ASSERT_EQ(1u, transceivers.size()); EXPECT_EQ(MediaStreamTrackInterface::kAudioKind, transceivers[0]->receiver()->track()->kind()); EXPECT_EQ(caller_audio->mid(), transceivers[0]->mid()); } // Test that setting a remote offer with an audio track marked sendonly will not // reuse a transceiver created by AddTrack. JSEP only allows the transceiver to // be reused if the offer direction is sendrecv or recvonly. TEST_F(PeerConnectionJsepTest, SetRemoteOfferDoesNotReuseTransceiverIfDirectionSendOnly) { auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); auto caller_audio = caller->pc()->GetTransceivers()[0]; caller_audio->SetDirection(RtpTransceiverDirection::kSendOnly); auto callee = CreatePeerConnection(); callee->AddAudioTrack("a"); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto transceivers = callee->pc()->GetTransceivers(); ASSERT_EQ(2u, transceivers.size()); EXPECT_EQ(rtc::nullopt, transceivers[0]->mid()); EXPECT_EQ(caller_audio->mid(), transceivers[1]->mid()); } // Test that setting a remote offer with an audio track will not reuse a // transceiver added by AddTransceiver. The logic for reusing a transceiver is // specific to those added by AddTrack and is tested above. TEST_F(PeerConnectionJsepTest, SetRemoteOfferDoesNotReuseTransceiverFromAddTransceiver) { auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); auto callee = CreatePeerConnection(); auto transceiver = callee->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto transceivers = callee->pc()->GetTransceivers(); ASSERT_EQ(2u, transceivers.size()); EXPECT_EQ(rtc::nullopt, transceivers[0]->mid()); EXPECT_EQ(caller->pc()->GetTransceivers()[0]->mid(), transceivers[1]->mid()); EXPECT_EQ(MediaStreamTrackInterface::kAudioKind, transceivers[1]->receiver()->track()->kind()); } // Test that setting a remote offer with an audio track will not reuse a // transceiver created for a local video track added by AddTrack. TEST_F(PeerConnectionJsepTest, SetRemoteOfferDoesNotReuseTransceiverOfWrongType) { auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); auto callee = CreatePeerConnection(); auto video_sender = callee->AddVideoTrack("v"); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto transceivers = callee->pc()->GetTransceivers(); ASSERT_EQ(2u, transceivers.size()); EXPECT_EQ(rtc::nullopt, transceivers[0]->mid()); EXPECT_EQ(caller->pc()->GetTransceivers()[0]->mid(), transceivers[1]->mid()); EXPECT_EQ(MediaStreamTrackInterface::kAudioKind, transceivers[1]->receiver()->track()->kind()); } // Test that setting a remote offer with an audio track will not reuse a // stopped transceiver. TEST_F(PeerConnectionJsepTest, SetRemoteOfferDoesNotReuseStoppedTransceiver) { auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); auto callee = CreatePeerConnection(); callee->AddAudioTrack("a"); callee->pc()->GetTransceivers()[0]->Stop(); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto transceivers = callee->pc()->GetTransceivers(); ASSERT_EQ(2u, transceivers.size()); EXPECT_EQ(rtc::nullopt, transceivers[0]->mid()); EXPECT_TRUE(transceivers[0]->stopped()); EXPECT_EQ(caller->pc()->GetTransceivers()[0]->mid(), transceivers[1]->mid()); EXPECT_FALSE(transceivers[1]->stopped()); } // Test that audio and video transceivers created on the remote side with // AddTrack will all be reused if there is the same number of audio/video tracks // in the remote offer. Additionally, this tests that transceivers are // successfully matched even if they are in a different order on the remote // side. TEST_F(PeerConnectionJsepTest, SetRemoteOfferReusesTransceiversOfBothTypes) { auto caller = CreatePeerConnection(); caller->AddVideoTrack("v"); caller->AddAudioTrack("a"); auto callee = CreatePeerConnection(); callee->AddAudioTrack("a"); callee->AddVideoTrack("v"); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto caller_transceivers = caller->pc()->GetTransceivers(); auto callee_transceivers = callee->pc()->GetTransceivers(); ASSERT_EQ(2u, callee_transceivers.size()); EXPECT_EQ(caller_transceivers[0]->mid(), callee_transceivers[1]->mid()); EXPECT_EQ(caller_transceivers[1]->mid(), callee_transceivers[0]->mid()); } // Tests for JSEP initial CreateAnswer. // Test that the answer to a remote offer creates media sections for each // offered media in the same order and with the same mids. TEST_F(PeerConnectionJsepTest, CreateAnswerHasSameMidsAsOffer) { auto caller = CreatePeerConnection(); 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); auto callee = CreatePeerConnection(); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto answer = callee->CreateAnswer(); auto contents = answer->description()->contents(); ASSERT_EQ(3u, contents.size()); EXPECT_EQ(cricket::MEDIA_TYPE_VIDEO, contents[0].media_description()->type()); 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(cricket::MEDIA_TYPE_VIDEO, contents[2].media_description()->type()); EXPECT_EQ(*third_transceiver->mid(), contents[2].name); } // Test that an answering media section is marked as rejected if the underlying // transceiver has been stopped. TEST_F(PeerConnectionJsepTest, CreateAnswerRejectsStoppedTransceiver) { auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); auto callee = CreatePeerConnection(); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); callee->pc()->GetTransceivers()[0]->Stop(); auto answer = callee->CreateAnswer(); auto contents = answer->description()->contents(); ASSERT_EQ(1u, contents.size()); EXPECT_TRUE(contents[0].rejected); } // Test that CreateAnswer will generate media sections which will only send or // receive if the offer indicates it can do the reciprocating direction. // The full matrix is tested more extensively in MediaSession. TEST_F(PeerConnectionJsepTest, CreateAnswerNegotiatesDirection) { auto caller = CreatePeerConnection(); RtpTransceiverInit init; init.direction = RtpTransceiverDirection::kSendOnly; caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, init); auto callee = CreatePeerConnection(); callee->AddAudioTrack("a"); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto answer = callee->CreateAnswer(); auto contents = answer->description()->contents(); ASSERT_EQ(1u, contents.size()); EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, contents[0].media_description()->direction()); } // Tests for JSEP SetLocalDescription with a local answer. // Note that these test only the additional behaviors not covered by // SetLocalDescription with a local offer. // Test that SetLocalDescription with an answer sets the current_direction // property of the transceivers mentioned in the session description. TEST_F(PeerConnectionJsepTest, SetLocalAnswerUpdatesCurrentDirection) { auto caller = CreatePeerConnection(); auto caller_audio = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); caller_audio->SetDirection(RtpTransceiverDirection::kRecvOnly); auto callee = CreatePeerConnection(); callee->AddAudioTrack("a"); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer())); auto transceivers = callee->pc()->GetTransceivers(); ASSERT_EQ(1u, transceivers.size()); // Since the offer was recvonly and the transceiver direction is sendrecv, // the negotiated direction will be sendonly. EXPECT_EQ(RtpTransceiverDirection::kSendOnly, transceivers[0]->current_direction()); } // Tests for JSEP SetRemoteDescription with a remote answer. // Note that these test only the additional behaviors not covered by // SetRemoteDescription with a remote offer. TEST_F(PeerConnectionJsepTest, SetRemoteAnswerUpdatesCurrentDirection) { auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); auto callee = CreatePeerConnection(); callee->AddAudioTrack("a"); auto callee_audio = callee->pc()->GetTransceivers()[0]; callee_audio->SetDirection(RtpTransceiverDirection::kSendOnly); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); ASSERT_TRUE( caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); auto transceivers = caller->pc()->GetTransceivers(); ASSERT_EQ(1u, transceivers.size()); // Since the remote transceiver was set to sendonly, the negotiated direction // in the answer would be sendonly which we apply as recvonly to the local // transceiver. EXPECT_EQ(RtpTransceiverDirection::kRecvOnly, transceivers[0]->current_direction()); } // Tests for multiple round trips. // Test that setting a transceiver with the inactive direction does not stop it // on either the caller or the callee. TEST_F(PeerConnectionJsepTest, SettingTransceiverInactiveDoesNotStopIt) { auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); auto callee = CreatePeerConnection(); callee->AddAudioTrack("a"); callee->pc()->GetTransceivers()[0]->SetDirection( RtpTransceiverDirection::kInactive); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); ASSERT_TRUE( caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); EXPECT_FALSE(caller->pc()->GetTransceivers()[0]->stopped()); EXPECT_FALSE(callee->pc()->GetTransceivers()[0]->stopped()); } // Test that if a transceiver had been associated and later stopped, then a // media section is still generated for it and the media section is marked as // rejected. TEST_F(PeerConnectionJsepTest, ReOfferMediaSectionForAssociatedStoppedTransceiverIsRejected) { auto caller = CreatePeerConnection(); auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); auto callee = CreatePeerConnection(); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); ASSERT_TRUE( caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); ASSERT_TRUE(transceiver->mid()); transceiver->Stop(); auto reoffer = caller->CreateOffer(); auto contents = reoffer->description()->contents(); ASSERT_EQ(1u, contents.size()); EXPECT_TRUE(contents[0].rejected); } // Test that stopping an associated transceiver on the caller side will stop the // corresponding transceiver on the remote side when the remote offer is // applied. TEST_F(PeerConnectionJsepTest, StoppingTransceiverInOfferStopsTransceiverOnRemoteSide) { auto caller = CreatePeerConnection(); auto transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); auto callee = CreatePeerConnection(); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); ASSERT_TRUE( caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); transceiver->Stop(); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); auto transceivers = callee->pc()->GetTransceivers(); EXPECT_TRUE(transceivers[0]->stopped()); EXPECT_TRUE(transceivers[0]->mid()); } // Test that CreateOffer will only generate a recycled media section if the // transceiver to be recycled has been seen stopped by the other side first. TEST_F(PeerConnectionJsepTest, CreateOfferDoesNotRecycleMediaSectionIfFirstStopped) { auto caller = CreatePeerConnection(); auto first_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); auto callee = CreatePeerConnection(); ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal())); ASSERT_TRUE( caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); auto second_transceiver = caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO); first_transceiver->Stop(); auto reoffer = caller->CreateOffer(); auto contents = reoffer->description()->contents(); ASSERT_EQ(2u, contents.size()); EXPECT_TRUE(contents[0].rejected); EXPECT_FALSE(contents[1].rejected); } // Test that the offer/answer and transceivers for both the caller and callee // side are generated/updated correctly when recycling an audio/video media // section as a media section of either the same or opposite type. class RecycleMediaSectionTest : public PeerConnectionJsepTest, public testing::WithParamInterface< std::tuple> { protected: RecycleMediaSectionTest() { first_type_ = std::get<0>(GetParam()); second_type_ = std::get<1>(GetParam()); } cricket::MediaType first_type_; cricket::MediaType second_type_; }; TEST_P(RecycleMediaSectionTest, VerifyOfferAnswerAndTransceivers) { auto caller = CreatePeerConnection(); auto first_transceiver = caller->AddTransceiver(first_type_); auto callee = CreatePeerConnection(); ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); std::string first_mid = *first_transceiver->mid(); first_transceiver->Stop(); ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); auto second_transceiver = caller->AddTransceiver(second_type_); // The offer should reuse the previous media section but allocate a new MID // and change the media type. auto offer = caller->CreateOffer(); auto offer_contents = offer->description()->contents(); ASSERT_EQ(1u, offer_contents.size()); EXPECT_FALSE(offer_contents[0].rejected); EXPECT_EQ(second_type_, offer_contents[0].media_description()->type()); std::string second_mid = offer_contents[0].name; EXPECT_NE(first_mid, second_mid); // Setting the local offer will dissociate the previous transceiver and set // the MID for the new transceiver. ASSERT_TRUE( caller->SetLocalDescription(CloneSessionDescription(offer.get()))); EXPECT_EQ(rtc::nullopt, first_transceiver->mid()); EXPECT_EQ(second_mid, second_transceiver->mid()); // Setting the remote offer will dissociate the previous transceiver and // create a new transceiver for the media section. ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); auto callee_transceivers = callee->pc()->GetTransceivers(); ASSERT_EQ(2u, callee_transceivers.size()); EXPECT_EQ(rtc::nullopt, callee_transceivers[0]->mid()); EXPECT_EQ(first_type_, callee_transceivers[0]->receiver()->media_type()); EXPECT_EQ(second_mid, callee_transceivers[1]->mid()); EXPECT_EQ(second_type_, callee_transceivers[1]->receiver()->media_type()); // The answer should have only one media section for the new transceiver. auto answer = callee->CreateAnswer(); auto answer_contents = answer->description()->contents(); ASSERT_EQ(1u, answer_contents.size()); EXPECT_FALSE(answer_contents[0].rejected); EXPECT_EQ(second_mid, answer_contents[0].name); EXPECT_EQ(second_type_, answer_contents[0].media_description()->type()); // Setting the local answer should succeed. ASSERT_TRUE( callee->SetLocalDescription(CloneSessionDescription(answer.get()))); // Setting the remote answer should succeed. ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer))); } // Test all combinations of audio and video as the first and second media type // for the media section. This is needed for full test coverage because // MediaSession has separate functions for processing audio and video media // sections. INSTANTIATE_TEST_CASE_P( PeerConnectionJsepTest, RecycleMediaSectionTest, Combine(Values(cricket::MEDIA_TYPE_AUDIO, cricket::MEDIA_TYPE_VIDEO), Values(cricket::MEDIA_TYPE_AUDIO, cricket::MEDIA_TYPE_VIDEO))); // Tests for MID properties. static void RenameSection(size_t mline_index, const std::string& new_mid, SessionDescriptionInterface* sdesc) { cricket::SessionDescription* desc = sdesc->description(); std::string old_mid = desc->contents()[mline_index].name; desc->contents()[mline_index].name = new_mid; desc->transport_infos()[mline_index].content_name = new_mid; const cricket::ContentGroup* bundle = desc->GetGroupByName(cricket::GROUP_TYPE_BUNDLE); if (bundle) { cricket::ContentGroup new_bundle = *bundle; if (new_bundle.RemoveContentName(old_mid)) { new_bundle.AddContentName(new_mid); } desc->RemoveGroupByName(cricket::GROUP_TYPE_BUNDLE); desc->AddGroup(new_bundle); } } // Test that two PeerConnections can have a successful offer/answer exchange if // the MIDs are changed from the defaults. TEST_F(PeerConnectionJsepTest, OfferAnswerWithChangedMids) { constexpr char kFirstMid[] = "nondefaultmid"; constexpr char kSecondMid[] = "randommid"; auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); caller->AddAudioTrack("b"); auto callee = CreatePeerConnection(); auto offer = caller->CreateOffer(); RenameSection(0, kFirstMid, offer.get()); RenameSection(1, kSecondMid, offer.get()); ASSERT_TRUE( caller->SetLocalDescription(CloneSessionDescription(offer.get()))); auto caller_transceivers = caller->pc()->GetTransceivers(); EXPECT_EQ(kFirstMid, caller_transceivers[0]->mid()); EXPECT_EQ(kSecondMid, caller_transceivers[1]->mid()); ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer))); auto callee_transceivers = callee->pc()->GetTransceivers(); EXPECT_EQ(kFirstMid, callee_transceivers[0]->mid()); EXPECT_EQ(kSecondMid, callee_transceivers[1]->mid()); auto answer = callee->CreateAnswer(); auto answer_contents = answer->description()->contents(); EXPECT_EQ(kFirstMid, answer_contents[0].name); EXPECT_EQ(kSecondMid, answer_contents[1].name); ASSERT_TRUE( callee->SetLocalDescription(CloneSessionDescription(answer.get()))); ASSERT_TRUE(caller->SetRemoteDescription(std::move(answer))); } // Test that CreateOffer will generate a MID that is not already used if the // default it would have picked is already taken. This is tested by using a // third PeerConnection to determine what the default would be for the second // media section then setting that as the first media section's MID. TEST_F(PeerConnectionJsepTest, CreateOfferGeneratesUniqueMidIfAlreadyTaken) { // First, find what the default MID is for the second media section. auto pc = CreatePeerConnection(); pc->AddAudioTrack("a"); pc->AddAudioTrack("b"); auto default_offer = pc->CreateOffer(); std::string default_second_mid = default_offer->description()->contents()[1].name; // Now, do an offer/answer with one track which has the MID set to the default // second MID. auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); auto callee = CreatePeerConnection(); auto offer = caller->CreateOffer(); RenameSection(0, default_second_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 second track and ensure that the MID is different. caller->AddAudioTrack("b"); auto reoffer = caller->CreateOffer(); auto reoffer_contents = reoffer->description()->contents(); EXPECT_EQ(default_second_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(); caller->AddAudioTrack("a"); auto callee = CreatePeerConnection(); callee->AddAudioTrack("a"); callee->AddVideoTrack("v"); ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); EXPECT_EQ(1u, caller->pc()->GetTransceivers().size()); EXPECT_EQ(2u, callee->pc()->GetTransceivers().size()); ASSERT_TRUE(callee->ExchangeOfferAnswerWith(caller.get())); EXPECT_EQ(2u, caller->pc()->GetTransceivers().size()); EXPECT_EQ(2u, callee->pc()->GetTransceivers().size()); } } // namespace webrtc