diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc index 6ffe561a75..3578095ce3 100644 --- a/pc/peerconnection.cc +++ b/pc/peerconnection.cc @@ -1259,7 +1259,8 @@ RTCErrorOr> PeerConnection::AddTransceiver( cricket::MediaType media_type, rtc::scoped_refptr track, - const RtpTransceiverInit& init) { + const RtpTransceiverInit& init, + bool fire_callback) { RTC_DCHECK((media_type == cricket::MEDIA_TYPE_AUDIO || media_type == cricket::MEDIA_TYPE_VIDEO)); if (track) { @@ -1285,7 +1286,9 @@ PeerConnection::AddTransceiver( auto transceiver = CreateAndAddTransceiver(sender, receiver); transceiver->internal()->set_direction(init.direction); - observer_->OnRenegotiationNeeded(); + if (fire_callback) { + observer_->OnRenegotiationNeeded(); + } return rtc::scoped_refptr(transceiver); } @@ -1565,11 +1568,80 @@ void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, return; } + // Legacy handling for offer_to_receive_audio and offer_to_receive_video. + // Specified in WebRTC section 4.4.3.2 "Legacy configuration extensions". + if (IsUnifiedPlan()) { + RTCError error = HandleLegacyOfferOptions(options); + if (!error.ok()) { + PostCreateSessionDescriptionFailure(observer, error.message()); + return; + } + } + cricket::MediaSessionOptions session_options; GetOptionsForOffer(options, &session_options); webrtc_session_desc_factory_->CreateOffer(observer, options, session_options); } +RTCError PeerConnection::HandleLegacyOfferOptions( + const RTCOfferAnswerOptions& options) { + RTC_DCHECK(IsUnifiedPlan()); + + if (options.offer_to_receive_audio == 0) { + RemoveRecvDirectionFromReceivingTransceiversOfType( + cricket::MEDIA_TYPE_AUDIO); + } else if (options.offer_to_receive_audio == 1) { + AddUpToOneReceivingTransceiverOfType(cricket::MEDIA_TYPE_AUDIO); + } else if (options.offer_to_receive_audio > 1) { + LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_PARAMETER, + "offer_to_receive_audio > 1 is not supported."); + } + + if (options.offer_to_receive_video == 0) { + RemoveRecvDirectionFromReceivingTransceiversOfType( + cricket::MEDIA_TYPE_VIDEO); + } else if (options.offer_to_receive_video == 1) { + AddUpToOneReceivingTransceiverOfType(cricket::MEDIA_TYPE_VIDEO); + } else if (options.offer_to_receive_video > 1) { + LOG_AND_RETURN_ERROR(RTCErrorType::UNSUPPORTED_PARAMETER, + "offer_to_receive_video > 1 is not supported."); + } + + return RTCError::OK(); +} + +void PeerConnection::RemoveRecvDirectionFromReceivingTransceiversOfType( + cricket::MediaType media_type) { + for (auto transceiver : GetReceivingTransceiversOfType(media_type)) { + transceiver->internal()->set_direction( + RtpTransceiverDirectionWithRecvSet(transceiver->direction(), false)); + } +} + +void PeerConnection::AddUpToOneReceivingTransceiverOfType( + cricket::MediaType media_type) { + if (GetReceivingTransceiversOfType(media_type).empty()) { + RtpTransceiverInit init; + init.direction = RtpTransceiverDirection::kRecvOnly; + AddTransceiver(media_type, nullptr, init, /*fire_callback=*/false); + } +} + +std::vector>> +PeerConnection::GetReceivingTransceiversOfType(cricket::MediaType media_type) { + std::vector< + rtc::scoped_refptr>> + receiving_transceivers; + for (auto transceiver : transceivers_) { + if (!transceiver->stopped() && + transceiver->internal()->media_type() == media_type && + RtpTransceiverDirectionHasRecv(transceiver->direction())) { + receiving_transceivers.push_back(transceiver); + } + } + return receiving_transceivers; +} + void PeerConnection::CreateAnswer( CreateSessionDescriptionObserver* observer, const MediaConstraintsInterface* constraints) { @@ -1615,6 +1687,19 @@ void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer, return; } + if (IsUnifiedPlan()) { + if (options.offer_to_receive_audio != RTCOfferAnswerOptions::kUndefined) { + RTC_LOG(LS_WARNING) << "CreateAnswer: offer_to_receive_audio is not " + "supported with Unified Plan semantics. Use the " + "RtpTransceiver API instead."; + } + if (options.offer_to_receive_video != RTCOfferAnswerOptions::kUndefined) { + RTC_LOG(LS_WARNING) << "CreateAnswer: offer_to_receive_video is not " + "supported with Unified Plan semantics. Use the " + "RtpTransceiver API instead."; + } + } + cricket::MediaSessionOptions session_options; GetOptionsForAnswer(options, &session_options); diff --git a/pc/peerconnection.h b/pc/peerconnection.h index 3ebe8fda9b..e88fab82a7 100644 --- a/pc/peerconnection.h +++ b/pc/peerconnection.h @@ -354,10 +354,13 @@ class PeerConnection : public PeerConnectionInternal, rtc::scoped_refptr> FindTransceiverBySender(rtc::scoped_refptr sender); + // Internal implementation for AddTransceiver family of methods. If + // |fire_callback| is set, fires OnRenegotiationNeeded callback if successful. RTCErrorOr> AddTransceiver( cricket::MediaType media_type, rtc::scoped_refptr track, - const RtpTransceiverInit& init); + const RtpTransceiverInit& init, + bool fire_callback = true); rtc::scoped_refptr> CreateSender(cricket::MediaType media_type, @@ -482,6 +485,14 @@ class PeerConnection : public PeerConnectionInternal, offer_answer_options, cricket::MediaSessionOptions* session_options); + RTCError HandleLegacyOfferOptions(const RTCOfferAnswerOptions& options); + void RemoveRecvDirectionFromReceivingTransceiversOfType( + cricket::MediaType media_type); + void AddUpToOneReceivingTransceiverOfType(cricket::MediaType media_type); + std::vector< + rtc::scoped_refptr>> + GetReceivingTransceiversOfType(cricket::MediaType media_type); + // Returns a MediaSessionOptions struct with options decided by // |constraints|, the local MediaStreams and DataChannels. void GetOptionsForAnswer(const RTCOfferAnswerOptions& offer_answer_options, diff --git a/pc/peerconnection_media_unittest.cc b/pc/peerconnection_media_unittest.cc index 40fc1ddad3..cfd480328f 100644 --- a/pc/peerconnection_media_unittest.cc +++ b/pc/peerconnection_media_unittest.cc @@ -361,39 +361,38 @@ TEST_P(PeerConnectionMediaTest, NewStreamInRemoteOfferAddsRecvStreams) { // Test that a new stream in a subsequent answer causes a new send stream to be // created on the callee when added locally. TEST_P(PeerConnectionMediaTest, NewStreamInLocalAnswerAddsSendStreams) { - // TODO(bugs.webrtc.org/8765): Enable this test under Unified Plan once - // offer_to_receive_audio is implemented. - if (IsUnifiedPlan()) { - return; - } - auto caller = CreatePeerConnection(); auto callee = CreatePeerConnectionWithAudioVideo(); - RTCOfferAnswerOptions options; - options.offer_to_receive_audio = + RTCOfferAnswerOptions offer_options; + offer_options.offer_to_receive_audio = RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; - options.offer_to_receive_video = + offer_options.offer_to_receive_video = RTCOfferAnswerOptions::kOfferToReceiveMediaTrue; + RTCOfferAnswerOptions answer_options; - ASSERT_TRUE( - callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(options))); - ASSERT_TRUE( - caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get(), offer_options, + answer_options)); // Add second set of tracks to the callee. callee->AddAudioTrack("a2"); callee->AddVideoTrack("v2"); - ASSERT_TRUE( - callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(options))); - ASSERT_TRUE( - caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal())); + ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get(), offer_options, + answer_options)); auto callee_voice = callee->media_engine()->GetVoiceChannel(0); - EXPECT_EQ(2u, callee_voice->send_streams().size()); + ASSERT_TRUE(callee_voice); auto callee_video = callee->media_engine()->GetVideoChannel(0); - EXPECT_EQ(2u, callee_video->send_streams().size()); + ASSERT_TRUE(callee_video); + + if (IsUnifiedPlan()) { + EXPECT_EQ(1u, callee_voice->send_streams().size()); + EXPECT_EQ(1u, callee_video->send_streams().size()); + } else { + EXPECT_EQ(2u, callee_voice->send_streams().size()); + EXPECT_EQ(2u, callee_video->send_streams().size()); + } } // A PeerConnection with no local streams and no explicit answer constraints @@ -438,11 +437,6 @@ class PeerConnectionMediaOfferDirectionTest // Tests that the correct direction is set on the media description according // to the presence of a local media track and the offer_to_receive setting. TEST_P(PeerConnectionMediaOfferDirectionTest, VerifyDirection) { - // TODO(bugs.webrtc.org/8765): Enable this test under Unified Plan once - // offer_to_receive_audio is implemented. - if (IsUnifiedPlan()) { - return; - } auto caller = CreatePeerConnection(); if (send_media_) { caller->AddAudioTrack("a"); @@ -496,11 +490,13 @@ class PeerConnectionMediaAnswerDirectionTest // in the offer, the presence of a local media track on the receive side and the // offer_to_receive setting. TEST_P(PeerConnectionMediaAnswerDirectionTest, VerifyDirection) { - // TODO(bugs.webrtc.org/8765): Enable this test under Unified Plan once - // offer_to_receive_audio is implemented. - if (IsUnifiedPlan()) { + if (IsUnifiedPlan() && + offer_to_receive_ != RTCOfferAnswerOptions::kUndefined) { + // offer_to_receive_ is not implemented when creating answers with Unified + // Plan semantics specified. return; } + auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); @@ -544,11 +540,13 @@ TEST_P(PeerConnectionMediaAnswerDirectionTest, VerifyDirection) { // local media track and has set offer_to_receive to 0, no matter which // direction the caller indicated in the offer. TEST_P(PeerConnectionMediaAnswerDirectionTest, VerifyRejected) { - // TODO(bugs.webrtc.org/8765): Enable this test under Unified Plan once - // offer_to_receive_audio is implemented. - if (IsUnifiedPlan()) { + if (IsUnifiedPlan() && + offer_to_receive_ != RTCOfferAnswerOptions::kUndefined) { + // offer_to_receive_ is not implemented when creating answers with Unified + // Plan semantics specified. return; } + auto caller = CreatePeerConnection(); caller->AddAudioTrack("a"); @@ -587,12 +585,6 @@ INSTANTIATE_TEST_CASE_P(PeerConnectionMediaTest, Values(-1, 0, 1))); TEST_P(PeerConnectionMediaTest, OfferHasDifferentDirectionForAudioVideo) { - // TODO(bugs.webrtc.org/8765): Enable this test under Unified Plan once - // offer_to_receive_audio is implemented. - if (IsUnifiedPlan()) { - return; - } - auto caller = CreatePeerConnection(); caller->AddVideoTrack("v"); @@ -608,9 +600,9 @@ TEST_P(PeerConnectionMediaTest, OfferHasDifferentDirectionForAudioVideo) { } TEST_P(PeerConnectionMediaTest, AnswerHasDifferentDirectionsForAudioVideo) { - // TODO(bugs.webrtc.org/8765): Enable this test under Unified Plan once - // offer_to_receive_audio is implemented. if (IsUnifiedPlan()) { + // offer_to_receive_ is not implemented when creating answers with Unified + // Plan semantics specified. return; } @@ -780,9 +772,9 @@ INSTANTIATE_TEST_CASE_P( // a series of offer/answers where audio/video are both sent, then audio is // rejected, then both audio/video sent again. TEST_P(PeerConnectionMediaTest, TestAVOfferWithAudioOnlyAnswer) { - // TODO(bugs.webrtc.org/8765): Enable this test under Unified Plan once - // offer_to_receive_audio is implemented. if (IsUnifiedPlan()) { + // offer_to_receive_ is not implemented when creating answers with Unified + // Plan semantics specified. return; } @@ -844,9 +836,9 @@ TEST_P(PeerConnectionMediaTest, TestAVOfferWithAudioOnlyAnswer) { // a series of offer/answers where audio/video are both sent, then video is // rejected, then both audio/video sent again. TEST_P(PeerConnectionMediaTest, TestAVOfferWithVideoOnlyAnswer) { - // TODO(bugs.webrtc.org/8765): Enable this test under Unified Plan once - // offer_to_receive_audio is implemented. if (IsUnifiedPlan()) { + // offer_to_receive_ is not implemented when creating answers with Unified + // Plan semantics specified. return; } diff --git a/pc/peerconnectionwrapper.cc b/pc/peerconnectionwrapper.cc index 21d154289f..c09258d13b 100644 --- a/pc/peerconnectionwrapper.cc +++ b/pc/peerconnectionwrapper.cc @@ -24,6 +24,8 @@ namespace webrtc { +using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions; + namespace { const uint32_t kDefaultTimeout = 10000U; } @@ -57,7 +59,7 @@ MockPeerConnectionObserver* PeerConnectionWrapper::observer() { std::unique_ptr PeerConnectionWrapper::CreateOffer() { - return CreateOffer(PeerConnectionInterface::RTCOfferAnswerOptions()); + return CreateOffer(RTCOfferAnswerOptions()); } std::unique_ptr PeerConnectionWrapper::CreateOffer( @@ -72,8 +74,7 @@ std::unique_ptr PeerConnectionWrapper::CreateOffer( std::unique_ptr PeerConnectionWrapper::CreateOfferAndSetAsLocal() { - return CreateOfferAndSetAsLocal( - PeerConnectionInterface::RTCOfferAnswerOptions()); + return CreateOfferAndSetAsLocal(RTCOfferAnswerOptions()); } std::unique_ptr @@ -89,7 +90,7 @@ PeerConnectionWrapper::CreateOfferAndSetAsLocal( std::unique_ptr PeerConnectionWrapper::CreateAnswer() { - return CreateAnswer(PeerConnectionInterface::RTCOfferAnswerOptions()); + return CreateAnswer(RTCOfferAnswerOptions()); } std::unique_ptr @@ -105,8 +106,7 @@ PeerConnectionWrapper::CreateAnswer( std::unique_ptr PeerConnectionWrapper::CreateAnswerAndSetAsLocal() { - return CreateAnswerAndSetAsLocal( - PeerConnectionInterface::RTCOfferAnswerOptions()); + return CreateAnswerAndSetAsLocal(RTCOfferAnswerOptions()); } std::unique_ptr @@ -181,12 +181,20 @@ bool PeerConnectionWrapper::SetSdp( bool PeerConnectionWrapper::ExchangeOfferAnswerWith( PeerConnectionWrapper* answerer) { + return ExchangeOfferAnswerWith(answerer, RTCOfferAnswerOptions(), + RTCOfferAnswerOptions()); +} + +bool PeerConnectionWrapper::ExchangeOfferAnswerWith( + PeerConnectionWrapper* answerer, + const PeerConnectionInterface::RTCOfferAnswerOptions& offer_options, + const PeerConnectionInterface::RTCOfferAnswerOptions& answer_options) { RTC_DCHECK(answerer); if (answerer == this) { RTC_LOG(LS_ERROR) << "Cannot exchange offer/answer with ourself!"; return false; } - auto offer = CreateOffer(); + auto offer = CreateOffer(offer_options); EXPECT_TRUE(offer); if (!offer) { return false; @@ -202,7 +210,7 @@ bool PeerConnectionWrapper::ExchangeOfferAnswerWith( if (!set_remote_offer) { return false; } - auto answer = answerer->CreateAnswer(); + auto answer = answerer->CreateAnswer(answer_options); EXPECT_TRUE(answer); if (!answer) { return false; diff --git a/pc/peerconnectionwrapper.h b/pc/peerconnectionwrapper.h index b5aa16342b..b775e851e2 100644 --- a/pc/peerconnectionwrapper.h +++ b/pc/peerconnectionwrapper.h @@ -97,16 +97,20 @@ class PeerConnectionWrapper { // generating the offer and the given PeerConnectionWrapper generating the // answer. // Equivalent to: - // 1. this->CreateOffer() + // 1. this->CreateOffer(offer_options) // 2. this->SetLocalDescription(offer) // 3. answerer->SetRemoteDescription(offer) - // 4. answerer->CreateAnswer() + // 4. answerer->CreateAnswer(answer_options) // 5. answerer->SetLocalDescription(answer) // 6. this->SetRemoteDescription(answer) // Returns true if all steps succeed, false otherwise. // Suggested usage: // ASSERT_TRUE(caller->ExchangeOfferAnswerWith(callee.get())); bool ExchangeOfferAnswerWith(PeerConnectionWrapper* answerer); + bool ExchangeOfferAnswerWith( + PeerConnectionWrapper* answerer, + const PeerConnectionInterface::RTCOfferAnswerOptions& offer_options, + const PeerConnectionInterface::RTCOfferAnswerOptions& answer_options); // The following are wrappers for the underlying PeerConnection's // AddTransceiver method. They return the result of calling AddTransceiver diff --git a/pc/rtpmediautils.cc b/pc/rtpmediautils.cc index f88861c320..337aa2d090 100644 --- a/pc/rtpmediautils.cc +++ b/pc/rtpmediautils.cc @@ -50,6 +50,20 @@ RtpTransceiverDirection RtpTransceiverDirectionReversed( return direction; } +RtpTransceiverDirection RtpTransceiverDirectionWithSendSet( + RtpTransceiverDirection direction, + bool send) { + return RtpTransceiverDirectionFromSendRecv( + send, RtpTransceiverDirectionHasRecv(direction)); +} + +RtpTransceiverDirection RtpTransceiverDirectionWithRecvSet( + RtpTransceiverDirection direction, + bool recv) { + return RtpTransceiverDirectionFromSendRecv( + RtpTransceiverDirectionHasSend(direction), recv); +} + const char* RtpTransceiverDirectionToString(RtpTransceiverDirection direction) { switch (direction) { case RtpTransceiverDirection::kSendRecv: diff --git a/pc/rtpmediautils.h b/pc/rtpmediautils.h index 6f547bd04a..6de6f8f0af 100644 --- a/pc/rtpmediautils.h +++ b/pc/rtpmediautils.h @@ -31,6 +31,16 @@ bool RtpTransceiverDirectionHasRecv(RtpTransceiverDirection direction); RtpTransceiverDirection RtpTransceiverDirectionReversed( RtpTransceiverDirection direction); +// Returns the RtpTransceiverDirection with its send component set to |send|. +RtpTransceiverDirection RtpTransceiverDirectionWithSendSet( + RtpTransceiverDirection direction, + bool send = true); + +// Returns the RtpTransceiverDirection with its recv component set to |recv|. +RtpTransceiverDirection RtpTransceiverDirectionWithRecvSet( + RtpTransceiverDirection direction, + bool recv = true); + // Returns an unspecified string representation of the given direction. const char* RtpTransceiverDirectionToString(RtpTransceiverDirection direction); diff --git a/pc/rtpmediautils_unittest.cc b/pc/rtpmediautils_unittest.cc index d72f35be07..af7e8f671b 100644 --- a/pc/rtpmediautils_unittest.cc +++ b/pc/rtpmediautils_unittest.cc @@ -8,16 +8,24 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include + #include "pc/rtpmediautils.h" #include "test/gtest.h" namespace webrtc { +using ::testing::Bool; +using ::testing::Combine; using ::testing::Values; +using ::testing::ValuesIn; + +RtpTransceiverDirection kAllDirections[] = { + RtpTransceiverDirection::kSendRecv, RtpTransceiverDirection::kSendOnly, + RtpTransceiverDirection::kRecvOnly, RtpTransceiverDirection::kInactive}; class EnumerateAllDirectionsTest - : public ::testing::Test, - public ::testing::WithParamInterface {}; + : public ::testing::TestWithParam {}; // Test that converting the direction to send/recv and back again results in the // same direction. @@ -51,9 +59,38 @@ TEST_P(EnumerateAllDirectionsTest, TestReversedIdentity) { INSTANTIATE_TEST_CASE_P(RtpTransceiverDirectionTest, EnumerateAllDirectionsTest, - Values(RtpTransceiverDirection::kSendRecv, - RtpTransceiverDirection::kSendOnly, - RtpTransceiverDirection::kRecvOnly, - RtpTransceiverDirection::kInactive)); + ValuesIn(kAllDirections)); + +class EnumerateAllDirectionsAndBool + : public ::testing::TestWithParam< + std::tuple> {}; + +TEST_P(EnumerateAllDirectionsAndBool, TestWithSendSet) { + RtpTransceiverDirection direction = std::get<0>(GetParam()); + bool send = std::get<1>(GetParam()); + + RtpTransceiverDirection result = + RtpTransceiverDirectionWithSendSet(direction, send); + + EXPECT_EQ(send, RtpTransceiverDirectionHasSend(result)); + EXPECT_EQ(RtpTransceiverDirectionHasRecv(direction), + RtpTransceiverDirectionHasRecv(result)); +} + +TEST_P(EnumerateAllDirectionsAndBool, TestWithRecvSet) { + RtpTransceiverDirection direction = std::get<0>(GetParam()); + bool recv = std::get<1>(GetParam()); + + RtpTransceiverDirection result = + RtpTransceiverDirectionWithRecvSet(direction, recv); + + EXPECT_EQ(RtpTransceiverDirectionHasSend(direction), + RtpTransceiverDirectionHasSend(result)); + EXPECT_EQ(recv, RtpTransceiverDirectionHasRecv(result)); +} + +INSTANTIATE_TEST_CASE_P(RtpTransceiverDirectionTest, + EnumerateAllDirectionsAndBool, + Combine(ValuesIn(kAllDirections), Bool())); } // namespace webrtc