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}
This commit is contained in:
Steve Anton
2018-01-25 13:58:07 -08:00
committed by Commit Bot
parent 1204448a68
commit 22da89f502
8 changed files with 222 additions and 61 deletions

View File

@ -1259,7 +1259,8 @@ RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>>
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 @@ PeerConnection::AddTransceiver(
auto transceiver = CreateAndAddTransceiver(sender, receiver);
transceiver->internal()->set_direction(init.direction);
if (fire_callback) {
observer_->OnRenegotiationNeeded();
}
return rtc::scoped_refptr<RtpTransceiverInterface>(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<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 @@ 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);

View File

@ -354,10 +354,13 @@ class PeerConnection : public PeerConnectionInternal,
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 @@ 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<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,

View File

@ -361,40 +361,39 @@ 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);
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
// should not reject any offered media sections.
@ -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;
}

View File

@ -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<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::CreateOffer(
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateOfferAndSetAsLocal() {
return CreateOfferAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
return CreateOfferAndSetAsLocal(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
@ -89,7 +90,7 @@ PeerConnectionWrapper::CreateOfferAndSetAsLocal(
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswer() {
return CreateAnswer(PeerConnectionInterface::RTCOfferAnswerOptions());
return CreateAnswer(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
@ -105,8 +106,7 @@ PeerConnectionWrapper::CreateAnswer(
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswerAndSetAsLocal() {
return CreateAnswerAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
return CreateAnswerAndSetAsLocal(RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
@ -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;

View File

@ -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

View File

@ -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:

View File

@ -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);

View File

@ -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 @@ 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<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