Implement legacy offer_to_receive options for Unified Plan
This implements the WebRTC specification for handling
the legacy offer options offer_to_receive_audio and
offer_to_receive_video. They are not implemented for CreateAnswer.
With Unified Plan semantics, clients should switch to the
RtpTransceiver API for ensuring the correct media sections are
offered.
Bug: webrtc:7600
Change-Id: I6ced00b86b165a352bd0ca3d64b48fadcfd12235
Reviewed-on: https://webrtc-review.googlesource.com/41341
Reviewed-by: Peter Thatcher <pthatcher@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21784}
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index 6ffe561..3578095 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -1259,7 +1259,8 @@
PeerConnection::AddTransceiver(
cricket::MediaType media_type,
rtc::scoped_refptr<MediaStreamTrackInterface> 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 @@
auto transceiver = CreateAndAddTransceiver(sender, receiver);
transceiver->internal()->set_direction(init.direction);
- observer_->OnRenegotiationNeeded();
+ if (fire_callback) {
+ observer_->OnRenegotiationNeeded();
+ }
return rtc::scoped_refptr<RtpTransceiverInterface>(transceiver);
}
@@ -1565,11 +1568,80 @@
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<rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
+PeerConnection::GetReceivingTransceiversOfType(cricket::MediaType media_type) {
+ std::vector<
+ rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
+ 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 @@
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 3ebe8fd..e88fab8 100644
--- a/pc/peerconnection.h
+++ b/pc/peerconnection.h
@@ -354,10 +354,13 @@
rtc::scoped_refptr<RtpTransceiverProxyWithInternal<RtpTransceiver>>
FindTransceiverBySender(rtc::scoped_refptr<RtpSenderInterface> sender);
+ // Internal implementation for AddTransceiver family of methods. If
+ // |fire_callback| is set, fires OnRenegotiationNeeded callback if successful.
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> AddTransceiver(
cricket::MediaType media_type,
rtc::scoped_refptr<MediaStreamTrackInterface> track,
- const RtpTransceiverInit& init);
+ const RtpTransceiverInit& init,
+ bool fire_callback = true);
rtc::scoped_refptr<RtpSenderProxyWithInternal<RtpSenderInternal>>
CreateSender(cricket::MediaType media_type,
@@ -482,6 +485,14 @@
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<RtpTransceiverProxyWithInternal<RtpTransceiver>>>
+ 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 40fc1dd..cfd4803 100644
--- a/pc/peerconnection_media_unittest.cc
+++ b/pc/peerconnection_media_unittest.cc
@@ -361,39 +361,38 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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, 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 @@
// 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 @@
// 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 21d1542..c09258d 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 @@
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateOffer() {
- return CreateOffer(PeerConnectionInterface::RTCOfferAnswerOptions());
+ return CreateOffer(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface> PeerConnectionWrapper::CreateOffer(
@@ -72,8 +74,7 @@
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateOfferAndSetAsLocal() {
- return CreateOfferAndSetAsLocal(
- PeerConnectionInterface::RTCOfferAnswerOptions());
+ return CreateOfferAndSetAsLocal(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
@@ -89,7 +90,7 @@
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswer() {
- return CreateAnswer(PeerConnectionInterface::RTCOfferAnswerOptions());
+ return CreateAnswer(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
@@ -105,8 +106,7 @@
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswerAndSetAsLocal() {
- return CreateAnswerAndSetAsLocal(
- PeerConnectionInterface::RTCOfferAnswerOptions());
+ return CreateAnswerAndSetAsLocal(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
@@ -181,12 +181,20 @@
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 @@
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 b5aa163..b775e85 100644
--- a/pc/peerconnectionwrapper.h
+++ b/pc/peerconnectionwrapper.h
@@ -97,16 +97,20 @@
// 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 f88861c..337aa2d 100644
--- a/pc/rtpmediautils.cc
+++ b/pc/rtpmediautils.cc
@@ -50,6 +50,20 @@
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 6f547bd..6de6f8f 100644
--- a/pc/rtpmediautils.h
+++ b/pc/rtpmediautils.h
@@ -31,6 +31,16 @@
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 d72f35b..af7e8f6 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 <tuple>
+
#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<RtpTransceiverDirection> {};
+ : public ::testing::TestWithParam<RtpTransceiverDirection> {};
// Test that converting the direction to send/recv and back again results in the
// same direction.
@@ -51,9 +59,38 @@
INSTANTIATE_TEST_CASE_P(RtpTransceiverDirectionTest,
EnumerateAllDirectionsTest,
- Values(RtpTransceiverDirection::kSendRecv,
- RtpTransceiverDirection::kSendOnly,
- RtpTransceiverDirection::kRecvOnly,
- RtpTransceiverDirection::kInactive));
+ ValuesIn(kAllDirections));
+
+class EnumerateAllDirectionsAndBool
+ : public ::testing::TestWithParam<
+ std::tuple<RtpTransceiverDirection, bool>> {};
+
+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