Rewrite WebRtcSession media tests as PeerConnection tests

Bug: webrtc:8222
Change-Id: I782a3227e30de70eb8f6c26a48723cb3510a84ad
Reviewed-on: https://webrtc-review.googlesource.com/6640
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20364}
This commit is contained in:
Steve Anton 2017-10-19 16:11:30 -07:00 committed by Commit Bot
parent 930e1af76f
commit 3df5dcac9b
16 changed files with 1642 additions and 1310 deletions

View file

@ -488,12 +488,11 @@ class FakeVoiceMediaChannel : public RtpHelper<VoiceMediaChannel> {
if (it != local_sinks_.end()) { if (it != local_sinks_.end()) {
RTC_CHECK(it->second->source() == source); RTC_CHECK(it->second->source() == source);
} else { } else {
local_sinks_.insert( local_sinks_.insert(std::make_pair(
std::make_pair(ssrc, new VoiceChannelAudioSink(source))); ssrc, rtc::MakeUnique<VoiceChannelAudioSink>(source)));
} }
} else { } else {
if (it != local_sinks_.end()) { if (it != local_sinks_.end()) {
delete it->second;
local_sinks_.erase(it); local_sinks_.erase(it);
} }
} }
@ -506,7 +505,7 @@ class FakeVoiceMediaChannel : public RtpHelper<VoiceMediaChannel> {
std::map<uint32_t, double> output_scalings_; std::map<uint32_t, double> output_scalings_;
std::vector<DtmfInfo> dtmf_info_queue_; std::vector<DtmfInfo> dtmf_info_queue_;
AudioOptions options_; AudioOptions options_;
std::map<uint32_t, VoiceChannelAudioSink*> local_sinks_; std::map<uint32_t, std::unique_ptr<VoiceChannelAudioSink>> local_sinks_;
std::unique_ptr<webrtc::AudioSinkInterface> sink_; std::unique_ptr<webrtc::AudioSinkInterface> sink_;
int max_bps_; int max_bps_;
}; };

View file

@ -394,7 +394,9 @@ if (rtc_include_tests) {
"peerconnection_crypto_unittest.cc", "peerconnection_crypto_unittest.cc",
"peerconnection_ice_unittest.cc", "peerconnection_ice_unittest.cc",
"peerconnection_integrationtest.cc", "peerconnection_integrationtest.cc",
"peerconnection_media_unittest.cc",
"peerconnection_rtp_unittest.cc", "peerconnection_rtp_unittest.cc",
"peerconnection_signaling_unittest.cc",
"peerconnectionendtoend_unittest.cc", "peerconnectionendtoend_unittest.cc",
"peerconnectionfactory_unittest.cc", "peerconnectionfactory_unittest.cc",
"peerconnectioninterface_unittest.cc", "peerconnectioninterface_unittest.cc",
@ -463,7 +465,9 @@ if (rtc_include_tests) {
"../api/audio_codecs:builtin_audio_encoder_factory", "../api/audio_codecs:builtin_audio_encoder_factory",
"../api/audio_codecs/L16:audio_decoder_L16", "../api/audio_codecs/L16:audio_decoder_L16",
"../api/audio_codecs/L16:audio_encoder_L16", "../api/audio_codecs/L16:audio_encoder_L16",
"../call:call_interfaces",
"../logging:rtc_event_log_api", "../logging:rtc_event_log_api",
"../logging:rtc_event_log_impl",
"../media:rtc_audio_video", "../media:rtc_audio_video",
"../media:rtc_data", # TODO(phoglund): AFAIK only used for one sctp constant. "../media:rtc_data", # TODO(phoglund): AFAIK only used for one sctp constant.
"../media:rtc_media_base", "../media:rtc_media_base",

View file

@ -828,10 +828,7 @@ PeerConnection::CreateDataChannel(
void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints) { const MediaConstraintsInterface* constraints) {
TRACE_EVENT0("webrtc", "PeerConnection::CreateOffer"); TRACE_EVENT0("webrtc", "PeerConnection::CreateOffer");
if (!observer) {
LOG(LS_ERROR) << "CreateOffer - observer is NULL.";
return;
}
PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options; PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options;
// Always create an offer even if |ConvertConstraintsToOfferAnswerOptions| // Always create an offer even if |ConvertConstraintsToOfferAnswerOptions|
// returns false for now. Because |ConvertConstraintsToOfferAnswerOptions| // returns false for now. Because |ConvertConstraintsToOfferAnswerOptions|
@ -848,11 +845,19 @@ void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer, void PeerConnection::CreateOffer(CreateSessionDescriptionObserver* observer,
const RTCOfferAnswerOptions& options) { const RTCOfferAnswerOptions& options) {
TRACE_EVENT0("webrtc", "PeerConnection::CreateOffer"); TRACE_EVENT0("webrtc", "PeerConnection::CreateOffer");
if (!observer) { if (!observer) {
LOG(LS_ERROR) << "CreateOffer - observer is NULL."; LOG(LS_ERROR) << "CreateOffer - observer is NULL.";
return; return;
} }
if (IsClosed()) {
std::string error = "CreateOffer called when PeerConnection is closed.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailure(observer, error);
return;
}
if (!ValidateOfferAnswerOptions(options)) { if (!ValidateOfferAnswerOptions(options)) {
std::string error = "CreateOffer called with invalid options."; std::string error = "CreateOffer called with invalid options.";
LOG(LS_ERROR) << error; LOG(LS_ERROR) << error;
@ -869,20 +874,12 @@ void PeerConnection::CreateAnswer(
CreateSessionDescriptionObserver* observer, CreateSessionDescriptionObserver* observer,
const MediaConstraintsInterface* constraints) { const MediaConstraintsInterface* constraints) {
TRACE_EVENT0("webrtc", "PeerConnection::CreateAnswer"); TRACE_EVENT0("webrtc", "PeerConnection::CreateAnswer");
if (!observer) { if (!observer) {
LOG(LS_ERROR) << "CreateAnswer - observer is NULL."; LOG(LS_ERROR) << "CreateAnswer - observer is NULL.";
return; return;
} }
if (!session_->remote_description() ||
session_->remote_description()->type() !=
SessionDescriptionInterface::kOffer) {
std::string error = "CreateAnswer called without remote offer.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailure(observer, error);
return;
}
PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options; PeerConnectionInterface::RTCOfferAnswerOptions offer_answer_options;
if (!ConvertConstraintsToOfferAnswerOptions(constraints, if (!ConvertConstraintsToOfferAnswerOptions(constraints,
&offer_answer_options)) { &offer_answer_options)) {
@ -892,9 +889,7 @@ void PeerConnection::CreateAnswer(
return; return;
} }
cricket::MediaSessionOptions session_options; CreateAnswer(observer, offer_answer_options);
GetOptionsForAnswer(offer_answer_options, &session_options);
session_->CreateAnswer(observer, session_options);
} }
void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer, void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer,
@ -905,6 +900,22 @@ void PeerConnection::CreateAnswer(CreateSessionDescriptionObserver* observer,
return; return;
} }
if (IsClosed()) {
std::string error = "CreateAnswer called when PeerConnection is closed.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailure(observer, error);
return;
}
if (!session_->remote_description() ||
session_->remote_description()->type() !=
SessionDescriptionInterface::kOffer) {
std::string error = "CreateAnswer called without remote offer.";
LOG(LS_ERROR) << error;
PostCreateSessionDescriptionFailure(observer, error);
return;
}
cricket::MediaSessionOptions session_options; cricket::MediaSessionOptions session_options;
GetOptionsForAnswer(options, &session_options); GetOptionsForAnswer(options, &session_options);
@ -915,9 +926,6 @@ void PeerConnection::SetLocalDescription(
SetSessionDescriptionObserver* observer, SetSessionDescriptionObserver* observer,
SessionDescriptionInterface* desc) { SessionDescriptionInterface* desc) {
TRACE_EVENT0("webrtc", "PeerConnection::SetLocalDescription"); TRACE_EVENT0("webrtc", "PeerConnection::SetLocalDescription");
if (IsClosed()) {
return;
}
if (!observer) { if (!observer) {
LOG(LS_ERROR) << "SetLocalDescription - observer is NULL."; LOG(LS_ERROR) << "SetLocalDescription - observer is NULL.";
return; return;
@ -926,11 +934,23 @@ void PeerConnection::SetLocalDescription(
PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL."); PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL.");
return; return;
} }
// Takes the ownership of |desc| regardless of the result.
std::unique_ptr<SessionDescriptionInterface> desc_temp(desc);
if (IsClosed()) {
std::string error = "Failed to set local " + desc->type() +
" SDP: Called in wrong state: STATE_CLOSED";
LOG(LS_ERROR) << error;
PostSetSessionDescriptionFailure(observer, error);
return;
}
// Update stats here so that we have the most recent stats for tracks and // Update stats here so that we have the most recent stats for tracks and
// streams that might be removed by updating the session description. // streams that might be removed by updating the session description.
stats_->UpdateStats(kStatsOutputLevelStandard); stats_->UpdateStats(kStatsOutputLevelStandard);
std::string error; std::string error;
if (!session_->SetLocalDescription(desc, &error)) { if (!session_->SetLocalDescription(std::move(desc_temp), &error)) {
PostSetSessionDescriptionFailure(observer, error); PostSetSessionDescriptionFailure(observer, error);
return; return;
} }
@ -1011,9 +1031,6 @@ void PeerConnection::SetRemoteDescription(
SetSessionDescriptionObserver* observer, SetSessionDescriptionObserver* observer,
SessionDescriptionInterface* desc) { SessionDescriptionInterface* desc) {
TRACE_EVENT0("webrtc", "PeerConnection::SetRemoteDescription"); TRACE_EVENT0("webrtc", "PeerConnection::SetRemoteDescription");
if (IsClosed()) {
return;
}
if (!observer) { if (!observer) {
LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL."; LOG(LS_ERROR) << "SetRemoteDescription - observer is NULL.";
return; return;
@ -1022,11 +1039,23 @@ void PeerConnection::SetRemoteDescription(
PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL."); PostSetSessionDescriptionFailure(observer, "SessionDescription is NULL.");
return; return;
} }
// Takes the ownership of |desc| regardless of the result.
std::unique_ptr<SessionDescriptionInterface> desc_temp(desc);
if (IsClosed()) {
std::string error = "Failed to set remote " + desc->type() +
" SDP: Called in wrong state: STATE_CLOSED";
LOG(LS_ERROR) << error;
PostSetSessionDescriptionFailure(observer, error);
return;
}
// Update stats here so that we have the most recent stats for tracks and // Update stats here so that we have the most recent stats for tracks and
// streams that might be removed by updating the session description. // streams that might be removed by updating the session description.
stats_->UpdateStats(kStatsOutputLevelStandard); stats_->UpdateStats(kStatsOutputLevelStandard);
std::string error; std::string error;
if (!session_->SetRemoteDescription(desc, &error)) { if (!session_->SetRemoteDescription(std::move(desc_temp), &error)) {
PostSetSessionDescriptionFailure(observer, error); PostSetSessionDescriptionFailure(observer, error);
return; return;
} }
@ -1061,6 +1090,15 @@ void PeerConnection::SetRemoteDescription(
// since only at that point will new streams have all their tracks. // since only at that point will new streams have all their tracks.
rtc::scoped_refptr<StreamCollection> new_streams(StreamCollection::Create()); rtc::scoped_refptr<StreamCollection> new_streams(StreamCollection::Create());
// TODO(steveanton): When removing RTP senders/receivers in response to a
// rejected media section, there is some cleanup logic that expects the voice/
// video channel to still be set. But in this method the voice/video channel
// would have been destroyed by WebRtcSession's SetRemoteDescription method
// above, so the cleanup that relies on them fails to run. This is hard to fix
// with WebRtcSession and PeerConnection separated, but once the classes are
// merged it will be easy to call RemoveTracks right before destroying the
// voice/video channels.
// Find all audio rtp streams and create corresponding remote AudioTracks // Find all audio rtp streams and create corresponding remote AudioTracks
// and MediaStreams. // and MediaStreams.
if (audio_content) { if (audio_content) {

View file

@ -75,7 +75,8 @@ class PeerConnectionCryptoUnitTest : public ::testing::Test {
if (!wrapper) { if (!wrapper) {
return nullptr; return nullptr;
} }
wrapper->AddAudioVideoStream("s", "a", "v"); wrapper->AddAudioTrack("a");
wrapper->AddVideoTrack("v");
return wrapper; return wrapper;
} }

View file

@ -120,7 +120,8 @@ class PeerConnectionIceUnitTest : public ::testing::Test {
if (!wrapper) { if (!wrapper) {
return nullptr; return nullptr;
} }
wrapper->AddAudioVideoStream("s", "a", "v"); wrapper->AddAudioTrack("a");
wrapper->AddVideoTrack("v");
return wrapper; return wrapper;
} }

View file

@ -0,0 +1,889 @@
/*
* 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.
*/
// This file contains tests that check the interaction between the
// PeerConnection and the underlying media engine, as well as tests that check
// the media-related aspects of SDP.
#include <tuple>
#include "call/callfactoryinterface.h"
#include "logging/rtc_event_log/rtc_event_log_factory.h"
#include "media/base/fakemediaengine.h"
#include "p2p/base/fakeportallocator.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/fakertccertificategenerator.h"
#include "rtc_base/gunit.h"
#include "rtc_base/ptr_util.h"
#include "rtc_base/virtualsocketserver.h"
#include "test/gmock.h"
namespace webrtc {
using cricket::FakeMediaEngine;
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::Values;
using ::testing::ElementsAre;
class PeerConnectionWrapperForMediaTest : public PeerConnectionWrapper {
public:
using PeerConnectionWrapper::PeerConnectionWrapper;
FakeMediaEngine* media_engine() { return media_engine_; }
void set_media_engine(FakeMediaEngine* media_engine) {
media_engine_ = media_engine;
}
private:
FakeMediaEngine* media_engine_;
};
class PeerConnectionMediaTest : public ::testing::Test {
protected:
typedef std::unique_ptr<PeerConnectionWrapperForMediaTest> WrapperPtr;
PeerConnectionMediaTest()
: vss_(new rtc::VirtualSocketServer()), main_(vss_.get()) {
#ifdef WEBRTC_ANDROID
InitializeAndroidObjects();
#endif
}
WrapperPtr CreatePeerConnection() {
return CreatePeerConnection(RTCConfiguration());
}
WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
auto media_engine = rtc::MakeUnique<FakeMediaEngine>();
auto* media_engine_ptr = media_engine.get();
auto pc_factory = CreateModularPeerConnectionFactory(
rtc::Thread::Current(), rtc::Thread::Current(), rtc::Thread::Current(),
std::move(media_engine), CreateCallFactory(),
CreateRtcEventLogFactory());
auto fake_port_allocator = rtc::MakeUnique<cricket::FakePortAllocator>(
rtc::Thread::Current(), nullptr);
auto observer = rtc::MakeUnique<MockPeerConnectionObserver>();
auto pc = pc_factory->CreatePeerConnection(
config, std::move(fake_port_allocator), nullptr, observer.get());
if (!pc) {
return nullptr;
}
auto wrapper = rtc::MakeUnique<PeerConnectionWrapperForMediaTest>(
pc_factory, pc, std::move(observer));
wrapper->set_media_engine(media_engine_ptr);
return wrapper;
}
// Accepts the same arguments as CreatePeerConnection and adds default audio
// and video tracks.
template <typename... Args>
WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) {
auto wrapper = CreatePeerConnection(std::forward<Args>(args)...);
if (!wrapper) {
return nullptr;
}
wrapper->AddAudioTrack("a");
wrapper->AddVideoTrack("v");
return wrapper;
}
const cricket::MediaContentDescription* GetMediaContent(
const SessionDescriptionInterface* sdesc,
const std::string& mid) {
const auto* content_desc =
sdesc->description()->GetContentDescriptionByName(mid);
return static_cast<const cricket::MediaContentDescription*>(content_desc);
}
cricket::MediaContentDirection GetMediaContentDirection(
const SessionDescriptionInterface* sdesc,
const std::string& mid) {
auto* media_content = GetMediaContent(sdesc, mid);
RTC_DCHECK(media_content);
return media_content->direction();
}
std::unique_ptr<rtc::VirtualSocketServer> vss_;
rtc::AutoSocketServerThread main_;
};
TEST_F(PeerConnectionMediaTest,
FailToSetRemoteDescriptionIfCreateMediaChannelFails) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
callee->media_engine()->set_fail_create_channel(true);
std::string error;
ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer(), &error));
EXPECT_EQ("Failed to set remote offer SDP: Failed to create channels.",
error);
}
TEST_F(PeerConnectionMediaTest,
FailToSetLocalDescriptionIfCreateMediaChannelFails) {
auto caller = CreatePeerConnectionWithAudioVideo();
caller->media_engine()->set_fail_create_channel(true);
std::string error;
ASSERT_FALSE(caller->SetLocalDescription(caller->CreateOffer(), &error));
EXPECT_EQ("Failed to set local offer SDP: Failed to create channels.", error);
}
std::vector<std::string> GetIds(
const std::vector<cricket::StreamParams>& streams) {
std::vector<std::string> ids;
for (const auto& stream : streams) {
ids.push_back(stream.id);
}
return ids;
}
// Test that exchanging an offer and answer with each side having an audio and
// video stream creates the appropriate send/recv streams in the underlying
// media engine on both sides.
TEST_F(PeerConnectionMediaTest, AudioVideoOfferAnswerCreateSendRecvStreams) {
const std::string kCallerAudioId = "caller_a";
const std::string kCallerVideoId = "caller_v";
const std::string kCalleeAudioId = "callee_a";
const std::string kCalleeVideoId = "callee_v";
auto caller = CreatePeerConnection();
caller->AddAudioTrack(kCallerAudioId);
caller->AddVideoTrack(kCallerVideoId);
auto callee = CreatePeerConnection();
callee->AddAudioTrack(kCalleeAudioId);
callee->AddVideoTrack(kCalleeVideoId);
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
auto* caller_voice = caller->media_engine()->GetVoiceChannel(0);
EXPECT_THAT(GetIds(caller_voice->recv_streams()),
ElementsAre(kCalleeAudioId));
EXPECT_THAT(GetIds(caller_voice->send_streams()),
ElementsAre(kCallerAudioId));
auto* caller_video = caller->media_engine()->GetVideoChannel(0);
EXPECT_THAT(GetIds(caller_video->recv_streams()),
ElementsAre(kCalleeVideoId));
EXPECT_THAT(GetIds(caller_video->send_streams()),
ElementsAre(kCallerVideoId));
auto* callee_voice = callee->media_engine()->GetVoiceChannel(0);
EXPECT_THAT(GetIds(callee_voice->recv_streams()),
ElementsAre(kCallerAudioId));
EXPECT_THAT(GetIds(callee_voice->send_streams()),
ElementsAre(kCalleeAudioId));
auto* callee_video = callee->media_engine()->GetVideoChannel(0);
EXPECT_THAT(GetIds(callee_video->recv_streams()),
ElementsAre(kCallerVideoId));
EXPECT_THAT(GetIds(callee_video->send_streams()),
ElementsAre(kCalleeVideoId));
}
// Test that removing streams from a subsequent offer causes the receive streams
// on the callee to be removed.
TEST_F(PeerConnectionMediaTest, EmptyRemoteOfferRemovesRecvStreams) {
auto caller = CreatePeerConnection();
auto caller_audio_track = caller->AddAudioTrack("a");
auto caller_video_track = caller->AddVideoTrack("v");
auto callee = CreatePeerConnectionWithAudioVideo();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
// Remove both tracks from caller.
caller->pc()->RemoveTrack(caller_audio_track);
caller->pc()->RemoveTrack(caller_video_track);
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
EXPECT_EQ(1u, callee_voice->send_streams().size());
EXPECT_EQ(0u, callee_voice->recv_streams().size());
auto callee_video = callee->media_engine()->GetVideoChannel(0);
EXPECT_EQ(1u, callee_video->send_streams().size());
EXPECT_EQ(0u, callee_video->recv_streams().size());
}
// Test that removing streams from a subsequent answer causes the send streams
// on the callee to be removed when applied locally.
TEST_F(PeerConnectionMediaTest, EmptyLocalAnswerRemovesSendStreams) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection();
auto callee_audio_track = callee->AddAudioTrack("a");
auto callee_video_track = callee->AddVideoTrack("v");
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
// Remove both tracks from callee.
callee->pc()->RemoveTrack(callee_audio_track);
callee->pc()->RemoveTrack(callee_video_track);
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
EXPECT_EQ(0u, callee_voice->send_streams().size());
EXPECT_EQ(1u, callee_voice->recv_streams().size());
auto callee_video = callee->media_engine()->GetVideoChannel(0);
EXPECT_EQ(0u, callee_video->send_streams().size());
EXPECT_EQ(1u, callee_video->recv_streams().size());
}
// Test that a new stream in a subsequent offer causes a new receive stream to
// be created on the callee.
TEST_F(PeerConnectionMediaTest, NewStreamInRemoteOfferAddsRecvStreams) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
// Add second set of tracks to the caller.
caller->AddAudioTrack("a2");
caller->AddVideoTrack("v2");
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
EXPECT_EQ(2u, callee_voice->recv_streams().size());
auto callee_video = callee->media_engine()->GetVideoChannel(0);
EXPECT_EQ(2u, callee_video->recv_streams().size());
}
// Test that a new stream in a subsequent answer causes a new send stream to be
// created on the callee when added locally.
TEST_F(PeerConnectionMediaTest, NewStreamInLocalAnswerAddsSendStreams) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnectionWithAudioVideo();
RTCOfferAnswerOptions options;
options.offer_to_receive_audio =
RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
options.offer_to_receive_video =
RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
ASSERT_TRUE(
callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal(options)));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
// 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()));
auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
EXPECT_EQ(2u, callee_voice->send_streams().size());
auto callee_video = callee->media_engine()->GetVideoChannel(0);
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.
TEST_F(PeerConnectionMediaTest,
CreateAnswerWithNoStreamsAndDefaultOptionsDoesNotReject) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto answer = callee->CreateAnswer();
const auto* audio_content =
cricket::GetFirstAudioContent(answer->description());
ASSERT_TRUE(audio_content);
EXPECT_FALSE(audio_content->rejected);
const auto* video_content =
cricket::GetFirstVideoContent(answer->description());
ASSERT_TRUE(video_content);
EXPECT_FALSE(video_content->rejected);
}
class PeerConnectionMediaOfferDirectionTest
: public PeerConnectionMediaTest,
public ::testing::WithParamInterface<
std::tuple<bool, int, cricket::MediaContentDirection>> {
protected:
PeerConnectionMediaOfferDirectionTest() {
send_media_ = std::get<0>(GetParam());
offer_to_receive_ = std::get<1>(GetParam());
expected_direction_ = std::get<2>(GetParam());
}
bool send_media_;
int offer_to_receive_;
cricket::MediaContentDirection expected_direction_;
};
// 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) {
auto caller = CreatePeerConnection();
if (send_media_) {
caller->AddAudioTrack("a");
}
RTCOfferAnswerOptions options;
options.offer_to_receive_audio = offer_to_receive_;
auto offer = caller->CreateOffer(options);
auto* media_content = GetMediaContent(offer.get(), cricket::CN_AUDIO);
if (expected_direction_ == cricket::MD_INACTIVE) {
EXPECT_FALSE(media_content);
} else {
EXPECT_EQ(expected_direction_, media_content->direction());
}
}
// Note that in these tests, MD_INACTIVE indicates that no media section is
// included in the offer, not that the media direction is inactive.
INSTANTIATE_TEST_CASE_P(PeerConnectionMediaTest,
PeerConnectionMediaOfferDirectionTest,
Values(std::make_tuple(false, -1, cricket::MD_INACTIVE),
std::make_tuple(false, 0, cricket::MD_INACTIVE),
std::make_tuple(false, 1, cricket::MD_RECVONLY),
std::make_tuple(true, -1, cricket::MD_SENDRECV),
std::make_tuple(true, 0, cricket::MD_SENDONLY),
std::make_tuple(true, 1, cricket::MD_SENDRECV)));
class PeerConnectionMediaAnswerDirectionTest
: public PeerConnectionMediaTest,
public ::testing::WithParamInterface<
std::tuple<cricket::MediaContentDirection, bool, int>> {
protected:
PeerConnectionMediaAnswerDirectionTest() {
offer_direction_ = std::get<0>(GetParam());
send_media_ = std::get<1>(GetParam());
offer_to_receive_ = std::get<2>(GetParam());
}
cricket::MediaContentDirection offer_direction_;
bool send_media_;
int offer_to_receive_;
};
// Tests that the direction in an answer is correct according to direction sent
// in the offer, the presence of a local media track on the receive side and the
// offer_to_receive setting.
TEST_P(PeerConnectionMediaAnswerDirectionTest, VerifyDirection) {
auto caller = CreatePeerConnection();
caller->AddAudioTrack("a");
// Create the offer with an audio section and set its direction.
auto offer = caller->CreateOffer();
cricket::GetFirstAudioContentDescription(offer->description())
->set_direction(offer_direction_);
auto callee = CreatePeerConnection();
if (send_media_) {
callee->AddAudioTrack("a");
}
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
// Create the answer according to the test parameters.
RTCOfferAnswerOptions options;
options.offer_to_receive_audio = offer_to_receive_;
auto answer = callee->CreateAnswer(options);
// The expected direction in the answer is the intersection of each side's
// capability to send/recv media.
// For the offerer, the direction is given in the offer (offer_direction_).
// For the answerer, the direction has two components:
// 1. Send if the answerer has a local track to send.
// 2. Receive if the answerer has explicitly set the offer_to_receive to 1 or
// if it has been left as default.
auto offer_direction =
cricket::RtpTransceiverDirection::FromMediaContentDirection(
offer_direction_);
// The negotiated components determine the direction set in the answer.
bool negotiate_send = (send_media_ && offer_direction.recv);
bool negotiate_recv = ((offer_to_receive_ != 0) && offer_direction.send);
auto expected_direction =
cricket::RtpTransceiverDirection(negotiate_send, negotiate_recv)
.ToMediaContentDirection();
EXPECT_EQ(expected_direction,
GetMediaContentDirection(answer.get(), cricket::CN_AUDIO));
}
// Tests that the media section is rejected if and only if the callee has no
// 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) {
auto caller = CreatePeerConnection();
caller->AddAudioTrack("a");
// Create the offer with an audio section and set its direction.
auto offer = caller->CreateOffer();
cricket::GetFirstAudioContentDescription(offer->description())
->set_direction(offer_direction_);
auto callee = CreatePeerConnection();
if (send_media_) {
callee->AddAudioTrack("a");
}
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
// Create the answer according to the test parameters.
RTCOfferAnswerOptions options;
options.offer_to_receive_audio = offer_to_receive_;
auto answer = callee->CreateAnswer(options);
// The media section is rejected if and only if offer_to_receive is explicitly
// set to 0 and there is no media to send.
auto* audio_content = cricket::GetFirstAudioContent(answer->description());
ASSERT_TRUE(audio_content);
EXPECT_EQ((offer_to_receive_ == 0 && !send_media_), audio_content->rejected);
}
INSTANTIATE_TEST_CASE_P(PeerConnectionMediaTest,
PeerConnectionMediaAnswerDirectionTest,
Combine(Values(cricket::MD_INACTIVE,
cricket::MD_SENDONLY,
cricket::MD_RECVONLY,
cricket::MD_SENDRECV),
Bool(),
Values(-1, 0, 1)));
TEST_F(PeerConnectionMediaTest, OfferHasDifferentDirectionForAudioVideo) {
auto caller = CreatePeerConnection();
caller->AddVideoTrack("v");
RTCOfferAnswerOptions options;
options.offer_to_receive_audio = 1;
options.offer_to_receive_video = 0;
auto offer = caller->CreateOffer(options);
EXPECT_EQ(cricket::MD_RECVONLY,
GetMediaContentDirection(offer.get(), cricket::CN_AUDIO));
EXPECT_EQ(cricket::MD_SENDONLY,
GetMediaContentDirection(offer.get(), cricket::CN_VIDEO));
}
TEST_F(PeerConnectionMediaTest, AnswerHasDifferentDirectionsForAudioVideo) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnection();
callee->AddVideoTrack("v");
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
RTCOfferAnswerOptions options;
options.offer_to_receive_audio = 1;
options.offer_to_receive_video = 0;
auto answer = callee->CreateAnswer(options);
EXPECT_EQ(cricket::MD_RECVONLY,
GetMediaContentDirection(answer.get(), cricket::CN_AUDIO));
EXPECT_EQ(cricket::MD_SENDONLY,
GetMediaContentDirection(answer.get(), cricket::CN_VIDEO));
}
void AddComfortNoiseCodecsToSend(cricket::FakeMediaEngine* media_engine) {
const cricket::AudioCodec kComfortNoiseCodec8k(102, "CN", 8000, 0, 1);
const cricket::AudioCodec kComfortNoiseCodec16k(103, "CN", 16000, 0, 1);
auto codecs = media_engine->audio_send_codecs();
codecs.push_back(kComfortNoiseCodec8k);
codecs.push_back(kComfortNoiseCodec16k);
media_engine->SetAudioCodecs(codecs);
}
bool HasAnyComfortNoiseCodecs(const cricket::SessionDescription* desc) {
const auto* audio_desc = cricket::GetFirstAudioContentDescription(desc);
for (const auto& codec : audio_desc->codecs()) {
if (codec.name == "CN") {
return true;
}
}
return false;
}
TEST_F(PeerConnectionMediaTest,
CreateOfferWithNoVoiceActivityDetectionIncludesNoComfortNoiseCodecs) {
auto caller = CreatePeerConnectionWithAudioVideo();
AddComfortNoiseCodecsToSend(caller->media_engine());
RTCOfferAnswerOptions options;
options.voice_activity_detection = false;
auto offer = caller->CreateOffer(options);
EXPECT_FALSE(HasAnyComfortNoiseCodecs(offer->description()));
}
TEST_F(PeerConnectionMediaTest,
CreateAnswerWithNoVoiceActivityDetectionIncludesNoComfortNoiseCodecs) {
auto caller = CreatePeerConnectionWithAudioVideo();
AddComfortNoiseCodecsToSend(caller->media_engine());
auto callee = CreatePeerConnectionWithAudioVideo();
AddComfortNoiseCodecsToSend(callee->media_engine());
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
RTCOfferAnswerOptions options;
options.voice_activity_detection = false;
auto answer = callee->CreateAnswer(options);
EXPECT_FALSE(HasAnyComfortNoiseCodecs(answer->description()));
}
// The following test group verifies that we reject answers with invalid media
// sections as per RFC 3264.
class PeerConnectionMediaInvalidMediaTest
: public PeerConnectionMediaTest,
public ::testing::WithParamInterface<
std::tuple<std::string,
std::function<void(cricket::SessionDescription*)>,
std::string>> {
protected:
PeerConnectionMediaInvalidMediaTest() {
mutator_ = std::get<1>(GetParam());
expected_error_ = std::get<2>(GetParam());
}
std::function<void(cricket::SessionDescription*)> mutator_;
std::string expected_error_;
};
TEST_P(PeerConnectionMediaInvalidMediaTest, FailToSetRemoteAnswer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto answer = callee->CreateAnswer();
mutator_(answer->description());
std::string error;
ASSERT_FALSE(caller->SetRemoteDescription(std::move(answer), &error));
EXPECT_EQ("Failed to set remote answer SDP: " + expected_error_, error);
}
TEST_P(PeerConnectionMediaInvalidMediaTest, FailToSetLocalAnswer) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto answer = callee->CreateAnswer();
mutator_(answer->description());
std::string error;
ASSERT_FALSE(callee->SetLocalDescription(std::move(answer), &error));
EXPECT_EQ("Failed to set local answer SDP: " + expected_error_, error);
}
void RemoveVideoContent(cricket::SessionDescription* desc) {
auto content_name = cricket::GetFirstVideoContent(desc)->name;
desc->RemoveContentByName(content_name);
desc->RemoveTransportInfoByName(content_name);
}
void RenameVideoContent(cricket::SessionDescription* desc) {
auto* video_content = cricket::GetFirstVideoContent(desc);
auto* transport_info = desc->GetTransportInfoByName(video_content->name);
video_content->name = "video_renamed";
transport_info->content_name = video_content->name;
}
void ReverseMediaContent(cricket::SessionDescription* desc) {
std::reverse(desc->contents().begin(), desc->contents().end());
std::reverse(desc->transport_infos().begin(), desc->transport_infos().end());
}
void ChangeMediaTypeAudioToVideo(cricket::SessionDescription* desc) {
desc->RemoveContentByName(cricket::CN_AUDIO);
auto* video_content = desc->GetContentByName(cricket::CN_VIDEO);
desc->AddContent(cricket::CN_AUDIO, cricket::NS_JINGLE_RTP,
video_content->description->Copy());
}
constexpr char kMLinesOutOfOrder[] =
"The order of m-lines in answer doesn't match order in offer. Rejecting "
"answer.";
INSTANTIATE_TEST_CASE_P(
PeerConnectionMediaTest,
PeerConnectionMediaInvalidMediaTest,
Values(
std::make_tuple("remove video", RemoveVideoContent, kMLinesOutOfOrder),
std::make_tuple("rename video", RenameVideoContent, kMLinesOutOfOrder),
std::make_tuple("reverse media sections",
ReverseMediaContent,
kMLinesOutOfOrder),
std::make_tuple("change audio type to video type",
ChangeMediaTypeAudioToVideo,
kMLinesOutOfOrder)));
// Test that the correct media engine send/recv streams are created when doing
// a series of offer/answers where audio/video are both sent, then audio is
// rejected, then both audio/video sent again.
TEST_F(PeerConnectionMediaTest, TestAVOfferWithAudioOnlyAnswer) {
RTCOfferAnswerOptions options_reject_video;
options_reject_video.offer_to_receive_audio =
RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
options_reject_video.offer_to_receive_video = 0;
auto caller = CreatePeerConnection();
caller->AddAudioTrack("a");
caller->AddVideoTrack("v");
auto callee = CreatePeerConnection();
// Caller initially offers to send/recv audio and video.
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
// Callee accepts the audio as recv only but rejects the video.
ASSERT_TRUE(caller->SetRemoteDescription(
callee->CreateAnswerAndSetAsLocal(options_reject_video)));
auto caller_voice = caller->media_engine()->GetVoiceChannel(0);
ASSERT_TRUE(caller_voice);
EXPECT_EQ(0u, caller_voice->recv_streams().size());
EXPECT_EQ(1u, caller_voice->send_streams().size());
auto caller_video = caller->media_engine()->GetVideoChannel(0);
EXPECT_FALSE(caller_video);
// Callee adds its own audio/video stream and offers to receive audio/video
// too.
callee->AddAudioTrack("a");
auto callee_video_track = callee->AddVideoTrack("v");
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
ASSERT_TRUE(callee_voice);
EXPECT_EQ(1u, callee_voice->recv_streams().size());
EXPECT_EQ(1u, callee_voice->send_streams().size());
auto callee_video = callee->media_engine()->GetVideoChannel(0);
ASSERT_TRUE(callee_video);
EXPECT_EQ(1u, callee_video->recv_streams().size());
EXPECT_EQ(1u, callee_video->send_streams().size());
// Callee removes video but keeps audio and rejects the video once again.
callee->pc()->RemoveTrack(callee_video_track);
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
callee->SetLocalDescription(callee->CreateAnswer(options_reject_video)));
callee_voice = callee->media_engine()->GetVoiceChannel(0);
ASSERT_TRUE(callee_voice);
EXPECT_EQ(1u, callee_voice->recv_streams().size());
EXPECT_EQ(1u, callee_voice->send_streams().size());
callee_video = callee->media_engine()->GetVideoChannel(0);
EXPECT_FALSE(callee_video);
}
// Test that the correct media engine send/recv streams are created when doing
// a series of offer/answers where audio/video are both sent, then video is
// rejected, then both audio/video sent again.
TEST_F(PeerConnectionMediaTest, TestAVOfferWithVideoOnlyAnswer) {
// Disable the bundling here. If the media is bundled on audio
// transport, then we can't reject the audio because switching the bundled
// transport is not currently supported.
// (https://bugs.chromium.org/p/webrtc/issues/detail?id=6704)
RTCOfferAnswerOptions options_no_bundle;
options_no_bundle.use_rtp_mux = false;
RTCOfferAnswerOptions options_reject_audio = options_no_bundle;
options_reject_audio.offer_to_receive_audio = 0;
options_reject_audio.offer_to_receive_video =
RTCOfferAnswerOptions::kMaxOfferToReceiveMedia;
auto caller = CreatePeerConnection();
caller->AddAudioTrack("a");
caller->AddVideoTrack("v");
auto callee = CreatePeerConnection();
// Caller initially offers to send/recv audio and video.
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
// Callee accepts the video as recv only but rejects the audio.
ASSERT_TRUE(caller->SetRemoteDescription(
callee->CreateAnswerAndSetAsLocal(options_reject_audio)));
auto caller_voice = caller->media_engine()->GetVoiceChannel(0);
EXPECT_FALSE(caller_voice);
auto caller_video = caller->media_engine()->GetVideoChannel(0);
ASSERT_TRUE(caller_video);
EXPECT_EQ(0u, caller_video->recv_streams().size());
EXPECT_EQ(1u, caller_video->send_streams().size());
// Callee adds its own audio/video stream and offers to receive audio/video
// too.
auto callee_audio_track = callee->AddAudioTrack("a");
callee->AddVideoTrack("v");
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(caller->SetRemoteDescription(
callee->CreateAnswerAndSetAsLocal(options_no_bundle)));
auto callee_voice = callee->media_engine()->GetVoiceChannel(0);
ASSERT_TRUE(callee_voice);
EXPECT_EQ(1u, callee_voice->recv_streams().size());
EXPECT_EQ(1u, callee_voice->send_streams().size());
auto callee_video = callee->media_engine()->GetVideoChannel(0);
ASSERT_TRUE(callee_video);
EXPECT_EQ(1u, callee_video->recv_streams().size());
EXPECT_EQ(1u, callee_video->send_streams().size());
// Callee removes audio but keeps video and rejects the audio once again.
callee->pc()->RemoveTrack(callee_audio_track);
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
callee->SetLocalDescription(callee->CreateAnswer(options_reject_audio)));
callee_voice = callee->media_engine()->GetVoiceChannel(0);
EXPECT_FALSE(callee_voice);
callee_video = callee->media_engine()->GetVideoChannel(0);
ASSERT_TRUE(callee_video);
EXPECT_EQ(1u, callee_video->recv_streams().size());
EXPECT_EQ(1u, callee_video->send_streams().size());
}
// Tests that if the underlying video encoder fails to be initialized (signaled
// by failing to set send codecs), the PeerConnection signals the error to the
// client.
TEST_F(PeerConnectionMediaTest, MediaEngineErrorPropagatedToClients) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto video_channel = caller->media_engine()->GetVideoChannel(0);
video_channel->set_fail_set_send_codecs(true);
std::string error;
ASSERT_FALSE(caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal(),
&error));
EXPECT_EQ(
"Failed to set remote answer SDP: Session error code: ERROR_CONTENT. "
"Session error description: Failed to set remote video description send "
"parameters..",
error);
}
// Tests that if the underlying video encoder fails once then subsequent
// attempts at setting the local/remote description will also fail, even if
// SetSendCodecs no longer fails.
TEST_F(PeerConnectionMediaTest,
FailToApplyDescriptionIfVideoEncoderHasEverFailed) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto video_channel = caller->media_engine()->GetVideoChannel(0);
video_channel->set_fail_set_send_codecs(true);
EXPECT_FALSE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
video_channel->set_fail_set_send_codecs(false);
EXPECT_FALSE(caller->SetRemoteDescription(callee->CreateAnswer()));
EXPECT_FALSE(caller->SetLocalDescription(caller->CreateOffer()));
}
void RenameContent(cricket::SessionDescription* desc,
const std::string& old_name,
const std::string& new_name) {
auto* content = desc->GetContentByName(old_name);
RTC_DCHECK(content);
content->name = new_name;
auto* transport = desc->GetTransportInfoByName(old_name);
RTC_DCHECK(transport);
transport->content_name = new_name;
}
// Tests that an answer responds with the same MIDs as the offer.
TEST_F(PeerConnectionMediaTest, AnswerHasSameMidsAsOffer) {
const std::string kAudioMid = "not default1";
const std::string kVideoMid = "not default2";
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
auto offer = caller->CreateOffer();
RenameContent(offer->description(), cricket::CN_AUDIO, kAudioMid);
RenameContent(offer->description(), cricket::CN_VIDEO, kVideoMid);
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
auto answer = callee->CreateAnswer();
EXPECT_EQ(kAudioMid,
cricket::GetFirstAudioContent(answer->description())->name);
EXPECT_EQ(kVideoMid,
cricket::GetFirstVideoContent(answer->description())->name);
}
// Test that if the callee creates a re-offer, the MIDs are the same as the
// original offer.
TEST_F(PeerConnectionMediaTest, ReOfferHasSameMidsAsFirstOffer) {
const std::string kAudioMid = "not default1";
const std::string kVideoMid = "not default2";
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
auto offer = caller->CreateOffer();
RenameContent(offer->description(), cricket::CN_AUDIO, kAudioMid);
RenameContent(offer->description(), cricket::CN_VIDEO, kVideoMid);
ASSERT_TRUE(callee->SetRemoteDescription(std::move(offer)));
ASSERT_TRUE(callee->SetLocalDescription(callee->CreateAnswer()));
auto reoffer = callee->CreateOffer();
EXPECT_EQ(kAudioMid,
cricket::GetFirstAudioContent(reoffer->description())->name);
EXPECT_EQ(kVideoMid,
cricket::GetFirstVideoContent(reoffer->description())->name);
}
TEST_F(PeerConnectionMediaTest,
CombinedAudioVideoBweConfigPropagatedToMediaEngine) {
RTCConfiguration config;
config.combined_audio_video_bwe.emplace(true);
auto caller = CreatePeerConnectionWithAudioVideo(config);
ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
auto caller_voice = caller->media_engine()->GetVoiceChannel(0);
ASSERT_TRUE(caller_voice);
const cricket::AudioOptions& audio_options = caller_voice->options();
EXPECT_EQ(config.combined_audio_video_bwe,
audio_options.combined_audio_video_bwe);
}
} // namespace webrtc

View file

@ -0,0 +1,501 @@
/*
* 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.
*/
// This file contains tests that check the PeerConnection's signaling state
// machine, as well as tests that check basic, media-agnostic aspects of SDP.
#include <tuple>
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/peerconnectionproxy.h"
#include "pc/peerconnection.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/fakertccertificategenerator.h"
#include "rtc_base/gunit.h"
#include "rtc_base/ptr_util.h"
#include "rtc_base/stringutils.h"
#include "rtc_base/virtualsocketserver.h"
#include "test/gmock.h"
namespace webrtc {
using SignalingState = PeerConnectionInterface::SignalingState;
using RTCConfiguration = PeerConnectionInterface::RTCConfiguration;
using RTCOfferAnswerOptions = PeerConnectionInterface::RTCOfferAnswerOptions;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::Values;
class PeerConnectionWrapperForSignalingTest : public PeerConnectionWrapper {
public:
using PeerConnectionWrapper::PeerConnectionWrapper;
bool initial_offerer() {
return GetInternalPeerConnection()->initial_offerer();
}
PeerConnection* GetInternalPeerConnection() {
auto* pci = reinterpret_cast<
PeerConnectionProxyWithInternal<PeerConnectionInterface>*>(pc());
return reinterpret_cast<PeerConnection*>(pci->internal());
}
};
class PeerConnectionSignalingTest : public ::testing::Test {
protected:
typedef std::unique_ptr<PeerConnectionWrapperForSignalingTest> WrapperPtr;
PeerConnectionSignalingTest()
: 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() {
return CreatePeerConnection(RTCConfiguration());
}
WrapperPtr CreatePeerConnection(const RTCConfiguration& config) {
auto observer = rtc::MakeUnique<MockPeerConnectionObserver>();
auto pc = pc_factory_->CreatePeerConnection(config, nullptr, nullptr,
observer.get());
if (!pc) {
return nullptr;
}
return rtc::MakeUnique<PeerConnectionWrapperForSignalingTest>(
pc_factory_, pc, std::move(observer));
}
// Accepts the same arguments as CreatePeerConnection and adds default audio
// and video tracks.
template <typename... Args>
WrapperPtr CreatePeerConnectionWithAudioVideo(Args&&... args) {
auto wrapper = CreatePeerConnection(std::forward<Args>(args)...);
if (!wrapper) {
return nullptr;
}
wrapper->AddAudioTrack("a");
wrapper->AddVideoTrack("v");
return wrapper;
}
std::unique_ptr<rtc::VirtualSocketServer> vss_;
rtc::AutoSocketServerThread main_;
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
};
TEST_F(PeerConnectionSignalingTest, SetLocalOfferTwiceWorks) {
auto caller = CreatePeerConnection();
EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
}
TEST_F(PeerConnectionSignalingTest, SetRemoteOfferTwiceWorks) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOffer()));
EXPECT_TRUE(callee->SetRemoteDescription(caller->CreateOffer()));
}
TEST_F(PeerConnectionSignalingTest, FailToSetNullLocalDescription) {
auto caller = CreatePeerConnection();
std::string error;
ASSERT_FALSE(caller->SetLocalDescription(nullptr, &error));
EXPECT_EQ("SessionDescription is NULL.", error);
}
TEST_F(PeerConnectionSignalingTest, FailToSetNullRemoteDescription) {
auto caller = CreatePeerConnection();
std::string error;
ASSERT_FALSE(caller->SetRemoteDescription(nullptr, &error));
EXPECT_EQ("SessionDescription is NULL.", error);
}
// The following parameterized test verifies that calls to various signaling
// methods on PeerConnection will succeed/fail depending on what is the
// PeerConnection's signaling state. Note that the test tries many different
// forms of SignalingState::kClosed by arriving at a valid state then calling
// |Close()|. This is intended to catch cases where the PeerConnection signaling
// method ignores the closed flag but may work/not work because of the single
// state the PeerConnection was created in before it was closed.
class PeerConnectionSignalingStateTest
: public PeerConnectionSignalingTest,
public ::testing::WithParamInterface<std::tuple<SignalingState, bool>> {
protected:
RTCConfiguration GetConfig() {
RTCConfiguration config;
config.certificates.push_back(
FakeRTCCertificateGenerator::GenerateCertificate());
return config;
}
WrapperPtr CreatePeerConnectionInState(SignalingState state) {
return CreatePeerConnectionInState(std::make_tuple(state, false));
}
WrapperPtr CreatePeerConnectionInState(
std::tuple<SignalingState, bool> state_tuple) {
SignalingState state = std::get<0>(state_tuple);
bool closed = std::get<1>(state_tuple);
auto wrapper = CreatePeerConnectionWithAudioVideo(GetConfig());
switch (state) {
case SignalingState::kStable: {
break;
}
case SignalingState::kHaveLocalOffer: {
wrapper->SetLocalDescription(wrapper->CreateOffer());
break;
}
case SignalingState::kHaveLocalPrAnswer: {
auto caller = CreatePeerConnectionWithAudioVideo(GetConfig());
wrapper->SetRemoteDescription(caller->CreateOffer());
auto answer = wrapper->CreateAnswer();
wrapper->SetLocalDescription(CloneSessionDescriptionAsType(
answer.get(), SessionDescriptionInterface::kPrAnswer));
break;
}
case SignalingState::kHaveRemoteOffer: {
auto caller = CreatePeerConnectionWithAudioVideo(GetConfig());
wrapper->SetRemoteDescription(caller->CreateOffer());
break;
}
case SignalingState::kHaveRemotePrAnswer: {
auto callee = CreatePeerConnectionWithAudioVideo(GetConfig());
callee->SetRemoteDescription(wrapper->CreateOfferAndSetAsLocal());
auto answer = callee->CreateAnswer();
wrapper->SetRemoteDescription(CloneSessionDescriptionAsType(
answer.get(), SessionDescriptionInterface::kPrAnswer));
break;
}
case SignalingState::kClosed: {
RTC_NOTREACHED() << "Set the second member of the tuple to true to "
"achieve a closed state from an existing, valid "
"state.";
}
}
RTC_DCHECK_EQ(state, wrapper->pc()->signaling_state());
if (closed) {
wrapper->pc()->Close();
RTC_DCHECK_EQ(SignalingState::kClosed, wrapper->signaling_state());
}
return wrapper;
}
};
::testing::AssertionResult AssertStartsWith(const char* str_expr,
const char* prefix_expr,
const std::string& str,
const std::string& prefix) {
if (rtc::starts_with(str.c_str(), prefix.c_str())) {
return ::testing::AssertionSuccess();
} else {
return ::testing::AssertionFailure()
<< str_expr << "\nwhich is\n\"" << str << "\"\ndoes not start with\n"
<< prefix_expr << "\nwhich is\n\"" << prefix << "\"";
}
}
TEST_P(PeerConnectionSignalingStateTest, CreateOffer) {
auto wrapper = CreatePeerConnectionInState(GetParam());
if (wrapper->signaling_state() != SignalingState::kClosed) {
EXPECT_TRUE(wrapper->CreateOffer());
} else {
std::string error;
ASSERT_FALSE(wrapper->CreateOffer(RTCOfferAnswerOptions(), &error));
EXPECT_PRED_FORMAT2(AssertStartsWith, error,
"CreateOffer called when PeerConnection is closed.");
}
}
TEST_P(PeerConnectionSignalingStateTest, CreateAnswer) {
auto wrapper = CreatePeerConnectionInState(GetParam());
if (wrapper->signaling_state() == SignalingState::kHaveLocalPrAnswer ||
wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
EXPECT_TRUE(wrapper->CreateAnswer());
} else {
std::string error;
ASSERT_FALSE(wrapper->CreateAnswer(RTCOfferAnswerOptions(), &error));
if (wrapper->signaling_state() == SignalingState::kClosed) {
EXPECT_PRED_FORMAT2(AssertStartsWith, error,
"CreateAnswer called when PeerConnection is closed.");
} else {
EXPECT_PRED_FORMAT2(AssertStartsWith, error,
"CreateAnswer called without remote offer.");
}
}
}
TEST_P(PeerConnectionSignalingStateTest, SetLocalOffer) {
auto wrapper = CreatePeerConnectionInState(GetParam());
if (wrapper->signaling_state() == SignalingState::kStable ||
wrapper->signaling_state() == SignalingState::kHaveLocalOffer) {
// Need to call CreateOffer on the PeerConnection under test, otherwise when
// setting the local offer it will want to verify the DTLS fingerprint
// against the locally generated certificate, but without a call to
// CreateOffer the certificate will never be generated.
EXPECT_TRUE(wrapper->SetLocalDescription(wrapper->CreateOffer()));
} else {
auto wrapper_for_offer =
CreatePeerConnectionInState(SignalingState::kHaveLocalOffer);
auto offer =
CloneSessionDescription(wrapper_for_offer->pc()->local_description());
std::string error;
ASSERT_FALSE(wrapper->SetLocalDescription(std::move(offer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set local offer SDP: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetLocalPrAnswer) {
auto wrapper_for_pranswer =
CreatePeerConnectionInState(SignalingState::kHaveLocalPrAnswer);
auto pranswer =
CloneSessionDescription(wrapper_for_pranswer->pc()->local_description());
auto wrapper = CreatePeerConnectionInState(GetParam());
if (wrapper->signaling_state() == SignalingState::kHaveLocalPrAnswer ||
wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
EXPECT_TRUE(wrapper->SetLocalDescription(std::move(pranswer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetLocalDescription(std::move(pranswer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set local pranswer SDP: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetLocalAnswer) {
auto wrapper_for_answer =
CreatePeerConnectionInState(SignalingState::kHaveRemoteOffer);
auto answer = wrapper_for_answer->CreateAnswer();
auto wrapper = CreatePeerConnectionInState(GetParam());
if (wrapper->signaling_state() == SignalingState::kHaveLocalPrAnswer ||
wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
EXPECT_TRUE(wrapper->SetLocalDescription(std::move(answer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetLocalDescription(std::move(answer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set local answer SDP: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetRemoteOffer) {
auto wrapper_for_offer =
CreatePeerConnectionInState(SignalingState::kHaveRemoteOffer);
auto offer =
CloneSessionDescription(wrapper_for_offer->pc()->remote_description());
auto wrapper = CreatePeerConnectionInState(GetParam());
if (wrapper->signaling_state() == SignalingState::kStable ||
wrapper->signaling_state() == SignalingState::kHaveRemoteOffer) {
EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(offer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(offer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set remote offer SDP: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetRemotePrAnswer) {
auto wrapper_for_pranswer =
CreatePeerConnectionInState(SignalingState::kHaveRemotePrAnswer);
auto pranswer =
CloneSessionDescription(wrapper_for_pranswer->pc()->remote_description());
auto wrapper = CreatePeerConnectionInState(GetParam());
if (wrapper->signaling_state() == SignalingState::kHaveLocalOffer ||
wrapper->signaling_state() == SignalingState::kHaveRemotePrAnswer) {
EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(pranswer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(pranswer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set remote pranswer SDP: Called in wrong state:");
}
}
TEST_P(PeerConnectionSignalingStateTest, SetRemoteAnswer) {
auto wrapper_for_answer =
CreatePeerConnectionInState(SignalingState::kHaveRemoteOffer);
auto answer = wrapper_for_answer->CreateAnswer();
auto wrapper = CreatePeerConnectionInState(GetParam());
if (wrapper->signaling_state() == SignalingState::kHaveLocalOffer ||
wrapper->signaling_state() == SignalingState::kHaveRemotePrAnswer) {
EXPECT_TRUE(wrapper->SetRemoteDescription(std::move(answer)));
} else {
std::string error;
ASSERT_FALSE(wrapper->SetRemoteDescription(std::move(answer), &error));
EXPECT_PRED_FORMAT2(
AssertStartsWith, error,
"Failed to set remote answer SDP: Called in wrong state:");
}
}
INSTANTIATE_TEST_CASE_P(PeerConnectionSignalingTest,
PeerConnectionSignalingStateTest,
Combine(Values(SignalingState::kStable,
SignalingState::kHaveLocalOffer,
SignalingState::kHaveLocalPrAnswer,
SignalingState::kHaveRemoteOffer,
SignalingState::kHaveRemotePrAnswer),
Bool()));
TEST_F(PeerConnectionSignalingTest,
CreateAnswerSucceedsIfStableAndRemoteDescriptionIsOffer) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
ASSERT_EQ(SignalingState::kStable, callee->signaling_state());
EXPECT_TRUE(callee->CreateAnswer());
}
TEST_F(PeerConnectionSignalingTest,
CreateAnswerFailsIfStableButRemoteDescriptionIsAnswer) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
ASSERT_EQ(SignalingState::kStable, caller->signaling_state());
std::string error;
ASSERT_FALSE(caller->CreateAnswer(RTCOfferAnswerOptions(), &error));
EXPECT_EQ("CreateAnswer called without remote offer.", error);
}
// According to https://tools.ietf.org/html/rfc3264#section-8, the session id
// stays the same but the version must be incremented if a later, different
// session description is generated. These two tests verify that is the case for
// both offers and answers.
TEST_F(PeerConnectionSignalingTest,
SessionVersionIncrementedInSubsequentDifferentOffer) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
auto original_offer = caller->CreateOfferAndSetAsLocal();
const std::string original_id = original_offer->session_id();
const std::string original_version = original_offer->session_version();
ASSERT_TRUE(callee->SetRemoteDescription(std::move(original_offer)));
ASSERT_TRUE(caller->SetRemoteDescription(callee->CreateAnswer()));
// Add track to get a different offer.
caller->AddAudioTrack("a");
auto later_offer = caller->CreateOffer();
EXPECT_EQ(original_id, later_offer->session_id());
EXPECT_LT(rtc::FromString<uint64_t>(original_version),
rtc::FromString<uint64_t>(later_offer->session_version()));
}
TEST_F(PeerConnectionSignalingTest,
SessionVersionIncrementedInSubsequentDifferentAnswer) {
auto caller = CreatePeerConnection();
auto callee = CreatePeerConnection();
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
auto original_answer = callee->CreateAnswerAndSetAsLocal();
const std::string original_id = original_answer->session_id();
const std::string original_version = original_answer->session_version();
// Add track to get a different answer.
callee->AddAudioTrack("a");
auto later_answer = callee->CreateAnswer();
EXPECT_EQ(original_id, later_answer->session_id());
EXPECT_LT(rtc::FromString<uint64_t>(original_version),
rtc::FromString<uint64_t>(later_answer->session_version()));
}
TEST_F(PeerConnectionSignalingTest, InitiatorFlagSetOnCallerAndNotOnCallee) {
auto caller = CreatePeerConnectionWithAudioVideo();
auto callee = CreatePeerConnectionWithAudioVideo();
EXPECT_FALSE(caller->initial_offerer());
EXPECT_FALSE(callee->initial_offerer());
ASSERT_TRUE(callee->SetRemoteDescription(caller->CreateOfferAndSetAsLocal()));
EXPECT_TRUE(caller->initial_offerer());
EXPECT_FALSE(callee->initial_offerer());
ASSERT_TRUE(
caller->SetRemoteDescription(callee->CreateAnswerAndSetAsLocal()));
EXPECT_TRUE(caller->initial_offerer());
EXPECT_FALSE(callee->initial_offerer());
}
// Test creating a PeerConnection, request multiple offers, destroy the
// PeerConnection and make sure we get success/failure callbacks for all of the
// requests.
// Background: crbug.com/507307
TEST_F(PeerConnectionSignalingTest, CreateOffersAndShutdown) {
auto caller = CreatePeerConnection();
RTCOfferAnswerOptions options;
options.offer_to_receive_audio =
RTCOfferAnswerOptions::kOfferToReceiveMediaTrue;
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observers[100];
for (auto& observer : observers) {
observer =
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>();
caller->pc()->CreateOffer(observer, options);
}
// Destroy the PeerConnection.
caller.reset(nullptr);
for (auto& observer : observers) {
// We expect to have received a notification now even if the PeerConnection
// was terminated. The offer creation may or may not have succeeded, but we
// must have received a notification.
EXPECT_TRUE(observer->called());
}
}
} // namespace webrtc

View file

@ -2517,9 +2517,9 @@ TEST_F(PeerConnectionInterfaceTest, CloseAndTestMethods) {
EXPECT_TRUE(pc_->remote_description() != NULL); EXPECT_TRUE(pc_->remote_description() != NULL);
std::unique_ptr<SessionDescriptionInterface> offer; std::unique_ptr<SessionDescriptionInterface> offer;
EXPECT_TRUE(DoCreateOffer(&offer, nullptr)); EXPECT_FALSE(DoCreateOffer(&offer, nullptr));
std::unique_ptr<SessionDescriptionInterface> answer; std::unique_ptr<SessionDescriptionInterface> answer;
EXPECT_TRUE(DoCreateAnswer(&answer, nullptr)); EXPECT_FALSE(DoCreateAnswer(&answer, nullptr));
std::string sdp; std::string sdp;
ASSERT_TRUE(pc_->remote_description()->ToString(&sdp)); ASSERT_TRUE(pc_->remote_description()->ToString(&sdp));
@ -3558,32 +3558,6 @@ TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVideoOnlyOptions) {
EXPECT_NE(nullptr, GetFirstVideoContent(offer->description())); EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
} }
// Test that if |voice_activity_detection| is false, no CN codec is added to the
// offer.
TEST_F(PeerConnectionInterfaceTest, CreateOfferWithVADOptions) {
RTCOfferAnswerOptions rtc_options;
rtc_options.offer_to_receive_audio = 1;
rtc_options.offer_to_receive_video = 0;
std::unique_ptr<SessionDescriptionInterface> offer;
CreatePeerConnection();
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
const cricket::ContentInfo* audio_content =
offer->description()->GetContentByName(cricket::CN_AUDIO);
ASSERT_TRUE(audio_content);
// |voice_activity_detection| is true by default.
EXPECT_TRUE(HasCNCodecs(audio_content));
rtc_options.voice_activity_detection = false;
CreatePeerConnection();
offer = CreateOfferWithOptions(rtc_options);
ASSERT_TRUE(offer);
audio_content = offer->description()->GetContentByName(cricket::CN_AUDIO);
ASSERT_TRUE(audio_content);
EXPECT_FALSE(HasCNCodecs(audio_content));
}
// Test that no media content will be added to the offer if using default // Test that no media content will be added to the offer if using default
// RTCOfferAnswerOptions. // RTCOfferAnswerOptions.
TEST_F(PeerConnectionInterfaceTest, CreateOfferWithDefaultOfferAnswerOptions) { TEST_F(PeerConnectionInterfaceTest, CreateOfferWithDefaultOfferAnswerOptions) {
@ -3664,42 +3638,6 @@ TEST_F(PeerConnectionInterfaceTest, CreateOfferWithRtpMux) {
EXPECT_FALSE(offer->description()->HasGroup(cricket::GROUP_TYPE_BUNDLE)); EXPECT_FALSE(offer->description()->HasGroup(cricket::GROUP_TYPE_BUNDLE));
} }
// If SetMandatoryReceiveAudio(false) and SetMandatoryReceiveVideo(false) are
// called for the answer constraints, but an audio and a video section were
// offered, there will still be an audio and a video section in the answer.
TEST_F(PeerConnectionInterfaceTest,
RejectAudioAndVideoInAnswerWithConstraints) {
// Offer both audio and video.
RTCOfferAnswerOptions rtc_offer_options;
rtc_offer_options.offer_to_receive_audio = 1;
rtc_offer_options.offer_to_receive_video = 1;
CreatePeerConnection();
std::unique_ptr<SessionDescriptionInterface> offer;
CreateOfferWithOptionsAsRemoteDescription(&offer, rtc_offer_options);
EXPECT_NE(nullptr, GetFirstAudioContent(offer->description()));
EXPECT_NE(nullptr, GetFirstVideoContent(offer->description()));
// Since an offer has been created with both audio and video,
// Answers will contain the media types that exist in the offer regardless of
// the value of |answer_options.has_audio| and |answer_options.has_video|.
FakeConstraints answer_c;
// Reject both audio and video.
answer_c.SetMandatoryReceiveAudio(false);
answer_c.SetMandatoryReceiveVideo(false);
std::unique_ptr<SessionDescriptionInterface> answer;
ASSERT_TRUE(DoCreateAnswer(&answer, &answer_c));
const cricket::ContentInfo* audio_content =
GetFirstAudioContent(answer->description());
const cricket::ContentInfo* video_content =
GetFirstVideoContent(answer->description());
ASSERT_NE(nullptr, audio_content);
ASSERT_NE(nullptr, video_content);
EXPECT_TRUE(audio_content->rejected);
EXPECT_TRUE(video_content->rejected);
}
// This test ensures OnRenegotiationNeeded is called when we add track with // This test ensures OnRenegotiationNeeded is called when we add track with
// MediaStream -> AddTrack in the same way it is called when we add track with // MediaStream -> AddTrack in the same way it is called when we add track with
// PeerConnection -> AddTrack. // PeerConnection -> AddTrack.
@ -3734,52 +3672,6 @@ TEST_F(PeerConnectionInterfaceTest, MediaStreamAddTrackRemoveTrackRenegotiate) {
observer_.renegotiation_needed_ = false; observer_.renegotiation_needed_ = false;
} }
// Tests that creating answer would fail gracefully without being crashed if the
// remote description is unset.
TEST_F(PeerConnectionInterfaceTest, CreateAnswerWithoutRemoteDescription) {
CreatePeerConnection();
// Creating answer fails because the remote description is unset.
std::unique_ptr<SessionDescriptionInterface> answer;
EXPECT_FALSE(DoCreateAnswer(&answer, nullptr));
// Createing answer succeeds when the remote description is set.
CreateOfferAsRemoteDescription();
EXPECT_TRUE(DoCreateAnswer(&answer, nullptr));
}
// Test that an error is returned if a description is applied that doesn't
// respect the order of existing media sections.
TEST_F(PeerConnectionInterfaceTest,
MediaSectionOrderEnforcedForSubsequentOffers) {
CreatePeerConnection();
FakeConstraints constraints;
constraints.SetMandatoryReceiveAudio(true);
constraints.SetMandatoryReceiveVideo(true);
std::unique_ptr<SessionDescriptionInterface> offer;
ASSERT_TRUE(DoCreateOffer(&offer, &constraints));
EXPECT_TRUE(DoSetRemoteDescription(std::move(offer)));
std::unique_ptr<SessionDescriptionInterface> answer;
ASSERT_TRUE(DoCreateAnswer(&answer, nullptr));
EXPECT_TRUE(DoSetLocalDescription(std::move(answer)));
// A remote offer with different m=line order should be rejected.
ASSERT_TRUE(DoCreateOffer(&offer, &constraints));
std::reverse(offer->description()->contents().begin(),
offer->description()->contents().end());
std::reverse(offer->description()->transport_infos().begin(),
offer->description()->transport_infos().end());
EXPECT_FALSE(DoSetRemoteDescription(std::move(offer)));
// A subsequent local offer with different m=line order should be rejected.
ASSERT_TRUE(DoCreateOffer(&offer, &constraints));
std::reverse(offer->description()->contents().begin(),
offer->description()->contents().end());
std::reverse(offer->description()->transport_infos().begin(),
offer->description()->transport_infos().end());
EXPECT_FALSE(DoSetLocalDescription(std::move(offer)));
}
class PeerConnectionMediaConfigTest : public testing::Test { class PeerConnectionMediaConfigTest : public testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {

View file

@ -30,7 +30,7 @@ PeerConnectionWrapper::PeerConnectionWrapper(
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory, rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory,
rtc::scoped_refptr<PeerConnectionInterface> pc, rtc::scoped_refptr<PeerConnectionInterface> pc,
std::unique_ptr<MockPeerConnectionObserver> observer) std::unique_ptr<MockPeerConnectionObserver> observer)
: pc_factory_(pc_factory), pc_(pc), observer_(std::move(observer)) { : pc_factory_(pc_factory), observer_(std::move(observer)), pc_(pc) {
RTC_DCHECK(pc_factory_); RTC_DCHECK(pc_factory_);
RTC_DCHECK(pc_); RTC_DCHECK(pc_);
RTC_DCHECK(observer_); RTC_DCHECK(observer_);
@ -57,15 +57,25 @@ PeerConnectionWrapper::CreateOffer() {
} }
std::unique_ptr<SessionDescriptionInterface> PeerConnectionWrapper::CreateOffer( std::unique_ptr<SessionDescriptionInterface> PeerConnectionWrapper::CreateOffer(
const PeerConnectionInterface::RTCOfferAnswerOptions& options) { const PeerConnectionInterface::RTCOfferAnswerOptions& options,
return CreateSdp([this, options](CreateSessionDescriptionObserver* observer) { std::string* error_out) {
pc()->CreateOffer(observer, options); return CreateSdp(
}); [this, options](CreateSessionDescriptionObserver* observer) {
pc()->CreateOffer(observer, options);
},
error_out);
} }
std::unique_ptr<SessionDescriptionInterface> std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateOfferAndSetAsLocal() { PeerConnectionWrapper::CreateOfferAndSetAsLocal() {
auto offer = CreateOffer(); return CreateOfferAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateOfferAndSetAsLocal(
const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
auto offer = CreateOffer(options);
if (!offer) { if (!offer) {
return nullptr; return nullptr;
} }
@ -80,15 +90,25 @@ PeerConnectionWrapper::CreateAnswer() {
std::unique_ptr<SessionDescriptionInterface> std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswer( PeerConnectionWrapper::CreateAnswer(
const PeerConnectionInterface::RTCOfferAnswerOptions& options) { const PeerConnectionInterface::RTCOfferAnswerOptions& options,
return CreateSdp([this, options](CreateSessionDescriptionObserver* observer) { std::string* error_out) {
pc()->CreateAnswer(observer, options); return CreateSdp(
}); [this, options](CreateSessionDescriptionObserver* observer) {
pc()->CreateAnswer(observer, options);
},
error_out);
} }
std::unique_ptr<SessionDescriptionInterface> std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswerAndSetAsLocal() { PeerConnectionWrapper::CreateAnswerAndSetAsLocal() {
auto answer = CreateAnswer(); return CreateAnswerAndSetAsLocal(
PeerConnectionInterface::RTCOfferAnswerOptions());
}
std::unique_ptr<SessionDescriptionInterface>
PeerConnectionWrapper::CreateAnswerAndSetAsLocal(
const PeerConnectionInterface::RTCOfferAnswerOptions& options) {
auto answer = CreateAnswer(options);
if (!answer) { if (!answer) {
return nullptr; return nullptr;
} }
@ -97,73 +117,72 @@ PeerConnectionWrapper::CreateAnswerAndSetAsLocal() {
} }
std::unique_ptr<SessionDescriptionInterface> PeerConnectionWrapper::CreateSdp( std::unique_ptr<SessionDescriptionInterface> PeerConnectionWrapper::CreateSdp(
std::function<void(CreateSessionDescriptionObserver*)> fn) { std::function<void(CreateSessionDescriptionObserver*)> fn,
std::string* error_out) {
rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer( rtc::scoped_refptr<MockCreateSessionDescriptionObserver> observer(
new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>()); new rtc::RefCountedObject<MockCreateSessionDescriptionObserver>());
fn(observer); fn(observer);
EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout);
if (error_out && !observer->result()) {
*error_out = observer->error();
}
return observer->MoveDescription(); return observer->MoveDescription();
} }
bool PeerConnectionWrapper::SetLocalDescription( bool PeerConnectionWrapper::SetLocalDescription(
std::unique_ptr<SessionDescriptionInterface> desc) { std::unique_ptr<SessionDescriptionInterface> desc,
return SetSdp([this, &desc](SetSessionDescriptionObserver* observer) { std::string* error_out) {
pc()->SetLocalDescription(observer, desc.release()); return SetSdp(
}); [this, &desc](SetSessionDescriptionObserver* observer) {
pc()->SetLocalDescription(observer, desc.release());
},
error_out);
} }
bool PeerConnectionWrapper::SetRemoteDescription( bool PeerConnectionWrapper::SetRemoteDescription(
std::unique_ptr<SessionDescriptionInterface> desc) { std::unique_ptr<SessionDescriptionInterface> desc,
return SetSdp([this, &desc](SetSessionDescriptionObserver* observer) { std::string* error_out) {
pc()->SetRemoteDescription(observer, desc.release()); return SetSdp(
}); [this, &desc](SetSessionDescriptionObserver* observer) {
pc()->SetRemoteDescription(observer, desc.release());
},
error_out);
} }
bool PeerConnectionWrapper::SetSdp( bool PeerConnectionWrapper::SetSdp(
std::function<void(SetSessionDescriptionObserver*)> fn) { std::function<void(SetSessionDescriptionObserver*)> fn,
std::string* error_out) {
rtc::scoped_refptr<MockSetSessionDescriptionObserver> observer( rtc::scoped_refptr<MockSetSessionDescriptionObserver> observer(
new rtc::RefCountedObject<MockSetSessionDescriptionObserver>()); new rtc::RefCountedObject<MockSetSessionDescriptionObserver>());
fn(observer); fn(observer);
if (pc()->signaling_state() != PeerConnectionInterface::kClosed) { EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout);
EXPECT_EQ_WAIT(true, observer->called(), kWaitTimeout); if (error_out && !observer->result()) {
*error_out = observer->error();
} }
return observer->result(); return observer->result();
} }
void PeerConnectionWrapper::AddAudioStream(const std::string& stream_label, rtc::scoped_refptr<RtpSenderInterface> PeerConnectionWrapper::AddAudioTrack(
const std::string& track_label) { const std::string& track_label,
auto stream = pc_factory()->CreateLocalMediaStream(stream_label); std::vector<MediaStreamInterface*> streams) {
auto audio_track = pc_factory()->CreateAudioTrack(track_label, nullptr); auto media_stream_track =
EXPECT_TRUE(pc()->AddTrack(audio_track, {stream})); pc_factory()->CreateAudioTrack(track_label, nullptr);
EXPECT_TRUE_WAIT(observer()->renegotiation_needed_, kWaitTimeout); return pc()->AddTrack(media_stream_track, streams);
observer()->renegotiation_needed_ = false;
} }
void PeerConnectionWrapper::AddVideoStream(const std::string& stream_label, rtc::scoped_refptr<RtpSenderInterface> PeerConnectionWrapper::AddVideoTrack(
const std::string& track_label) { const std::string& track_label,
auto stream = pc_factory()->CreateLocalMediaStream(stream_label); std::vector<MediaStreamInterface*> streams) {
auto video_source = pc_factory()->CreateVideoSource( auto video_source = pc_factory()->CreateVideoSource(
rtc::MakeUnique<cricket::FakeVideoCapturer>()); rtc::MakeUnique<cricket::FakeVideoCapturer>());
auto video_track = pc_factory()->CreateVideoTrack(track_label, video_source); auto media_stream_track =
EXPECT_TRUE(pc()->AddTrack(video_track, {stream})); pc_factory()->CreateVideoTrack(track_label, video_source);
EXPECT_TRUE_WAIT(observer()->renegotiation_needed_, kWaitTimeout); return pc()->AddTrack(media_stream_track, streams);
observer()->renegotiation_needed_ = false;
} }
void PeerConnectionWrapper::AddAudioVideoStream( PeerConnectionInterface::SignalingState
const std::string& stream_label, PeerConnectionWrapper::signaling_state() {
const std::string& audio_track_label, return pc()->signaling_state();
const std::string& video_track_label) {
auto stream = pc_factory()->CreateLocalMediaStream(stream_label);
auto audio_track = pc_factory()->CreateAudioTrack(audio_track_label, nullptr);
EXPECT_TRUE(pc()->AddTrack(audio_track, {stream}));
auto video_source = pc_factory()->CreateVideoSource(
rtc::MakeUnique<cricket::FakeVideoCapturer>());
auto video_track =
pc_factory()->CreateVideoTrack(video_track_label, video_source);
EXPECT_TRUE(pc()->AddTrack(video_track, {stream}));
EXPECT_TRUE_WAIT(observer()->renegotiation_needed_, kWaitTimeout);
observer()->renegotiation_needed_ = false;
} }
bool PeerConnectionWrapper::IsIceGatheringDone() { bool PeerConnectionWrapper::IsIceGatheringDone() {

View file

@ -54,54 +54,69 @@ class PeerConnectionWrapper {
// resulting SessionDescription once it is available. If the method call // resulting SessionDescription once it is available. If the method call
// failed, null is returned. // failed, null is returned.
std::unique_ptr<SessionDescriptionInterface> CreateOffer( std::unique_ptr<SessionDescriptionInterface> CreateOffer(
const PeerConnectionInterface::RTCOfferAnswerOptions& options); const PeerConnectionInterface::RTCOfferAnswerOptions& options,
std::string* error_out = nullptr);
// Calls CreateOffer with default options. // Calls CreateOffer with default options.
std::unique_ptr<SessionDescriptionInterface> CreateOffer(); std::unique_ptr<SessionDescriptionInterface> CreateOffer();
// Calls CreateOffer and sets a copy of the offer as the local description. // Calls CreateOffer and sets a copy of the offer as the local description.
std::unique_ptr<SessionDescriptionInterface> CreateOfferAndSetAsLocal(
const PeerConnectionInterface::RTCOfferAnswerOptions& options);
// Calls CreateOfferAndSetAsLocal with default options.
std::unique_ptr<SessionDescriptionInterface> CreateOfferAndSetAsLocal(); std::unique_ptr<SessionDescriptionInterface> CreateOfferAndSetAsLocal();
// Calls the underlying PeerConnection's CreateAnswer method and returns the // Calls the underlying PeerConnection's CreateAnswer method and returns the
// resulting SessionDescription once it is available. If the method call // resulting SessionDescription once it is available. If the method call
// failed, null is returned. // failed, null is returned.
std::unique_ptr<SessionDescriptionInterface> CreateAnswer( std::unique_ptr<SessionDescriptionInterface> CreateAnswer(
const PeerConnectionInterface::RTCOfferAnswerOptions& options); const PeerConnectionInterface::RTCOfferAnswerOptions& options,
std::string* error_out = nullptr);
// Calls CreateAnswer with the default options. // Calls CreateAnswer with the default options.
std::unique_ptr<SessionDescriptionInterface> CreateAnswer(); std::unique_ptr<SessionDescriptionInterface> CreateAnswer();
// Calls CreateAnswer and sets a copy of the offer as the local description. // Calls CreateAnswer and sets a copy of the offer as the local description.
std::unique_ptr<SessionDescriptionInterface> CreateAnswerAndSetAsLocal(
const PeerConnectionInterface::RTCOfferAnswerOptions& options);
// Calls CreateAnswerAndSetAsLocal with default options.
std::unique_ptr<SessionDescriptionInterface> CreateAnswerAndSetAsLocal(); std::unique_ptr<SessionDescriptionInterface> CreateAnswerAndSetAsLocal();
// Calls the underlying PeerConnection's SetLocalDescription method with the // Calls the underlying PeerConnection's SetLocalDescription method with the
// given session description and waits for the success/failure response. // given session description and waits for the success/failure response.
// Returns true if the description was successfully set. // Returns true if the description was successfully set.
bool SetLocalDescription(std::unique_ptr<SessionDescriptionInterface> desc); bool SetLocalDescription(std::unique_ptr<SessionDescriptionInterface> desc,
std::string* error_out = nullptr);
// Calls the underlying PeerConnection's SetRemoteDescription method with the // Calls the underlying PeerConnection's SetRemoteDescription method with the
// given session description and waits for the success/failure response. // given session description and waits for the success/failure response.
// Returns true if the description was successfully set. // Returns true if the description was successfully set.
bool SetRemoteDescription(std::unique_ptr<SessionDescriptionInterface> desc); bool SetRemoteDescription(std::unique_ptr<SessionDescriptionInterface> desc,
std::string* error_out = nullptr);
// Adds a new stream with one audio track to the underlying PeerConnection. // Calls the underlying PeerConnection's AddTrack method with an audio media
void AddAudioStream(const std::string& stream_label, // stream track not bound to any source.
const std::string& track_label); rtc::scoped_refptr<RtpSenderInterface> AddAudioTrack(
// Adds a new stream with one video track to the underlying PeerConnection. const std::string& track_label,
void AddVideoStream(const std::string& stream_label, std::vector<MediaStreamInterface*> streams = {});
const std::string& track_label);
// Adds a new stream with one audio and one video track to the underlying // Calls the underlying PeerConnection's AddTrack method with a video media
// PeerConnection. // stream track fed by a fake video capturer.
void AddAudioVideoStream(const std::string& stream_label, rtc::scoped_refptr<RtpSenderInterface> AddVideoTrack(
const std::string& audio_track_label, const std::string& track_label,
const std::string& video_track_label); std::vector<MediaStreamInterface*> streams = {});
// Returns the signaling state of the underlying PeerConnection.
PeerConnectionInterface::SignalingState signaling_state();
// Returns true if ICE has finished gathering candidates. // Returns true if ICE has finished gathering candidates.
bool IsIceGatheringDone(); bool IsIceGatheringDone();
private: private:
std::unique_ptr<SessionDescriptionInterface> CreateSdp( std::unique_ptr<SessionDescriptionInterface> CreateSdp(
std::function<void(CreateSessionDescriptionObserver*)> fn); std::function<void(CreateSessionDescriptionObserver*)> fn,
bool SetSdp(std::function<void(SetSessionDescriptionObserver*)> fn); std::string* error_out);
bool SetSdp(std::function<void(SetSessionDescriptionObserver*)> fn,
std::string* error_out);
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_; rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
rtc::scoped_refptr<PeerConnectionInterface> pc_;
std::unique_ptr<MockPeerConnectionObserver> observer_; std::unique_ptr<MockPeerConnectionObserver> observer_;
rtc::scoped_refptr<PeerConnectionInterface> pc_;
}; };
} // namespace webrtc } // namespace webrtc

View file

@ -20,7 +20,14 @@ namespace webrtc {
std::unique_ptr<SessionDescriptionInterface> CloneSessionDescription( std::unique_ptr<SessionDescriptionInterface> CloneSessionDescription(
const SessionDescriptionInterface* sdesc) { const SessionDescriptionInterface* sdesc) {
RTC_DCHECK(sdesc); RTC_DCHECK(sdesc);
auto clone = rtc::MakeUnique<JsepSessionDescription>(sdesc->type()); return CloneSessionDescriptionAsType(sdesc, sdesc->type());
}
std::unique_ptr<SessionDescriptionInterface> CloneSessionDescriptionAsType(
const SessionDescriptionInterface* sdesc,
const std::string& type) {
RTC_DCHECK(sdesc);
auto clone = rtc::MakeUnique<JsepSessionDescription>(type);
clone->Initialize(sdesc->description()->Copy(), sdesc->session_id(), clone->Initialize(sdesc->description()->Copy(), sdesc->session_id(),
sdesc->session_version()); sdesc->session_version());
// As of writing, our version of GCC does not allow returning a unique_ptr of // As of writing, our version of GCC does not allow returning a unique_ptr of

View file

@ -23,6 +23,11 @@ namespace webrtc {
std::unique_ptr<SessionDescriptionInterface> CloneSessionDescription( std::unique_ptr<SessionDescriptionInterface> CloneSessionDescription(
const SessionDescriptionInterface* sdesc); const SessionDescriptionInterface* sdesc);
// Returns a copy of the given session description with the type changed.
std::unique_ptr<SessionDescriptionInterface> CloneSessionDescriptionAsType(
const SessionDescriptionInterface* sdesc,
const std::string& type);
// Function that takes a single session description content with its // Function that takes a single session description content with its
// corresponding transport and produces a boolean. // corresponding transport and produces a boolean.
typedef std::function<bool(const cricket::ContentInfo*, typedef std::function<bool(const cricket::ContentInfo*,

View file

@ -177,26 +177,27 @@ class MockCreateSessionDescriptionObserver
public: public:
MockCreateSessionDescriptionObserver() MockCreateSessionDescriptionObserver()
: called_(false), : called_(false),
result_(false) {} error_("MockCreateSessionDescriptionObserver not called") {}
virtual ~MockCreateSessionDescriptionObserver() {} virtual ~MockCreateSessionDescriptionObserver() {}
virtual void OnSuccess(SessionDescriptionInterface* desc) { virtual void OnSuccess(SessionDescriptionInterface* desc) {
called_ = true; called_ = true;
result_ = true; error_ = "";
desc_.reset(desc); desc_.reset(desc);
} }
virtual void OnFailure(const std::string& error) { virtual void OnFailure(const std::string& error) {
called_ = true; called_ = true;
result_ = false; error_ = error;
} }
bool called() const { return called_; } bool called() const { return called_; }
bool result() const { return result_; } bool result() const { return error_.empty(); }
const std::string& error() const { return error_; }
std::unique_ptr<SessionDescriptionInterface> MoveDescription() { std::unique_ptr<SessionDescriptionInterface> MoveDescription() {
return std::move(desc_); return std::move(desc_);
} }
private: private:
bool called_; bool called_;
bool result_; std::string error_;
std::unique_ptr<SessionDescriptionInterface> desc_; std::unique_ptr<SessionDescriptionInterface> desc_;
}; };
@ -205,22 +206,23 @@ class MockSetSessionDescriptionObserver
public: public:
MockSetSessionDescriptionObserver() MockSetSessionDescriptionObserver()
: called_(false), : called_(false),
result_(false) {} error_("MockSetSessionDescriptionObserver not called") {}
virtual ~MockSetSessionDescriptionObserver() {} virtual ~MockSetSessionDescriptionObserver() {}
virtual void OnSuccess() { virtual void OnSuccess() {
called_ = true; called_ = true;
result_ = true; error_ = "";
} }
virtual void OnFailure(const std::string& error) { virtual void OnFailure(const std::string& error) {
called_ = true; called_ = true;
result_ = false; error_ = error;
} }
bool called() const { return called_; } bool called() const { return called_; }
bool result() const { return result_; } bool result() const { return error_.empty(); }
const std::string& error() const { return error_; }
private: private:
bool called_; bool called_;
bool result_; std::string error_;
}; };
class MockDataChannelObserver : public webrtc::DataChannelObserver { class MockDataChannelObserver : public webrtc::DataChannelObserver {

View file

@ -337,7 +337,7 @@ static bool BadSdp(const std::string& source,
if (!type.empty()) { if (!type.empty()) {
desc << " " << type; desc << " " << type;
} }
desc << " sdp: " << reason; desc << " SDP: " << reason;
if (err_desc) { if (err_desc) {
*err_desc = desc.str(); *err_desc = desc.str();
@ -707,15 +707,13 @@ void WebRtcSession::CreateAnswer(
webrtc_session_desc_factory_->CreateAnswer(observer, session_options); webrtc_session_desc_factory_->CreateAnswer(observer, session_options);
} }
bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc, bool WebRtcSession::SetLocalDescription(
std::string* err_desc) { std::unique_ptr<SessionDescriptionInterface> desc,
std::string* err_desc) {
RTC_DCHECK(signaling_thread()->IsCurrent()); RTC_DCHECK(signaling_thread()->IsCurrent());
// Takes the ownership of |desc| regardless of the result.
std::unique_ptr<SessionDescriptionInterface> desc_temp(desc);
// Validate SDP. // Validate SDP.
if (!ValidateSessionDescription(desc, cricket::CS_LOCAL, err_desc)) { if (!ValidateSessionDescription(desc.get(), cricket::CS_LOCAL, err_desc)) {
return false; return false;
} }
@ -727,18 +725,19 @@ bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc,
} }
if (action == kAnswer) { if (action == kAnswer) {
current_local_description_.reset(desc_temp.release()); current_local_description_ = std::move(desc);
pending_local_description_.reset(nullptr); pending_local_description_ = nullptr;
current_remote_description_.reset(pending_remote_description_.release()); current_remote_description_ = std::move(pending_remote_description_);
} else { } else {
pending_local_description_.reset(desc_temp.release()); pending_local_description_ = std::move(desc);
} }
// Transport and Media channels will be created only when offer is set. // Transport and Media channels will be created only when offer is set.
if (action == kOffer && !CreateChannels(local_description()->description())) { if (action == kOffer && !CreateChannels(local_description()->description())) {
// TODO(mallinath) - Handle CreateChannel failure, as new local description // TODO(mallinath) - Handle CreateChannel failure, as new local description
// is applied. Restore back to old description. // is applied. Restore back to old description.
return BadLocalSdp(desc->type(), kCreateChannelFailed, err_desc); return BadLocalSdp(local_description()->type(), kCreateChannelFailed,
err_desc);
} }
// Remove unused channels if MediaContentDescription is rejected. // Remove unused channels if MediaContentDescription is rejected.
@ -754,50 +753,54 @@ bool WebRtcSession::SetLocalDescription(SessionDescriptionInterface* desc,
pending_ice_restarts_.clear(); pending_ice_restarts_.clear();
if (error() != ERROR_NONE) { if (error() != ERROR_NONE) {
return BadLocalSdp(desc->type(), GetSessionErrorMsg(), err_desc); return BadLocalSdp(local_description()->type(), GetSessionErrorMsg(),
err_desc);
} }
return true; return true;
} }
bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc, bool WebRtcSession::SetRemoteDescription(
std::string* err_desc) { std::unique_ptr<SessionDescriptionInterface> desc,
std::string* err_desc) {
RTC_DCHECK(signaling_thread()->IsCurrent()); RTC_DCHECK(signaling_thread()->IsCurrent());
// Takes the ownership of |desc| regardless of the result.
std::unique_ptr<SessionDescriptionInterface> desc_temp(desc);
// Validate SDP. // Validate SDP.
if (!ValidateSessionDescription(desc, cricket::CS_REMOTE, err_desc)) { if (!ValidateSessionDescription(desc.get(), cricket::CS_REMOTE, err_desc)) {
return false; return false;
} }
// Hold this pointer so candidates can be copied to it later in the method.
SessionDescriptionInterface* desc_ptr = desc.get();
const SessionDescriptionInterface* old_remote_description = const SessionDescriptionInterface* old_remote_description =
remote_description(); remote_description();
// Grab ownership of the description being replaced for the remainder of this // Grab ownership of the description being replaced for the remainder of this
// method, since it's used below. // method, since it's used below as |old_remote_description|.
std::unique_ptr<SessionDescriptionInterface> replaced_remote_description; std::unique_ptr<SessionDescriptionInterface> replaced_remote_description;
Action action = GetAction(desc->type()); Action action = GetAction(desc->type());
if (action == kAnswer) { if (action == kAnswer) {
replaced_remote_description.reset( replaced_remote_description = pending_remote_description_
pending_remote_description_ ? pending_remote_description_.release() ? std::move(pending_remote_description_)
: current_remote_description_.release()); : std::move(current_remote_description_);
current_remote_description_.reset(desc_temp.release()); current_remote_description_ = std::move(desc);
pending_remote_description_.reset(nullptr); pending_remote_description_ = nullptr;
current_local_description_.reset(pending_local_description_.release()); current_local_description_ = std::move(pending_local_description_);
} else { } else {
replaced_remote_description.reset(pending_remote_description_.release()); replaced_remote_description = std::move(pending_remote_description_);
pending_remote_description_.reset(desc_temp.release()); pending_remote_description_ = std::move(desc);
} }
// Transport and Media channels will be created only when offer is set. // Transport and Media channels will be created only when offer is set.
if (action == kOffer && !CreateChannels(desc->description())) { if (action == kOffer &&
!CreateChannels(remote_description()->description())) {
// TODO(mallinath) - Handle CreateChannel failure, as new local description // TODO(mallinath) - Handle CreateChannel failure, as new local description
// is applied. Restore back to old description. // is applied. Restore back to old description.
return BadRemoteSdp(desc->type(), kCreateChannelFailed, err_desc); return BadRemoteSdp(remote_description()->type(), kCreateChannelFailed,
err_desc);
} }
// Remove unused channels if MediaContentDescription is rejected. // Remove unused channels if MediaContentDescription is rejected.
RemoveUnusedChannels(desc->description()); RemoveUnusedChannels(remote_description()->description());
// NOTE: Candidates allocation will be initiated only when SetLocalDescription // NOTE: Candidates allocation will be initiated only when SetLocalDescription
// is called. // is called.
@ -805,8 +808,10 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc,
return false; return false;
} }
if (local_description() && !UseCandidatesInSessionDescription(desc)) { if (local_description() &&
return BadRemoteSdp(desc->type(), kInvalidCandidates, err_desc); !UseCandidatesInSessionDescription(remote_description())) {
return BadRemoteSdp(remote_description()->type(), kInvalidCandidates,
err_desc);
} }
if (old_remote_description) { if (old_remote_description) {
@ -817,7 +822,7 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc,
// TODO(deadbeef): When we start storing both the current and pending // TODO(deadbeef): When we start storing both the current and pending
// remote description, this should reset pending_ice_restarts and compare // remote description, this should reset pending_ice_restarts and compare
// against the current description. // against the current description.
if (CheckForRemoteIceRestart(old_remote_description, desc, if (CheckForRemoteIceRestart(old_remote_description, remote_description(),
content.name)) { content.name)) {
if (action == kOffer) { if (action == kOffer) {
pending_ice_restarts_.insert(content.name); pending_ice_restarts_.insert(content.name);
@ -831,13 +836,14 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc,
// description plus any candidates added since then. We should remove // description plus any candidates added since then. We should remove
// this once we're sure it won't break anything. // this once we're sure it won't break anything.
WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription( WebRtcSessionDescriptionFactory::CopyCandidatesFromSessionDescription(
old_remote_description, content.name, desc); old_remote_description, content.name, desc_ptr);
} }
} }
} }
if (error() != ERROR_NONE) { if (error() != ERROR_NONE) {
return BadRemoteSdp(desc->type(), GetSessionErrorMsg(), err_desc); return BadRemoteSdp(remote_description()->type(), GetSessionErrorMsg(),
err_desc);
} }
// Set the the ICE connection state to connecting since the connection may // Set the the ICE connection state to connecting since the connection may
@ -848,7 +854,7 @@ bool WebRtcSession::SetRemoteDescription(SessionDescriptionInterface* desc,
// transport and expose a new checking() member from transport that can be // transport and expose a new checking() member from transport that can be
// read to determine the current checking state. The existing SignalConnecting // read to determine the current checking state. The existing SignalConnecting
// actually means "gathering candidates", so cannot be be used here. // actually means "gathering candidates", so cannot be be used here.
if (desc->type() != SessionDescriptionInterface::kOffer && if (remote_description()->type() != SessionDescriptionInterface::kOffer &&
ice_connection_state_ == PeerConnectionInterface::kIceConnectionNew) { ice_connection_state_ == PeerConnectionInterface::kIceConnectionNew) {
SetIceConnectionState(PeerConnectionInterface::kIceConnectionChecking); SetIceConnectionState(PeerConnectionInterface::kIceConnectionChecking);
} }

View file

@ -254,11 +254,9 @@ class WebRtcSession :
const cricket::MediaSessionOptions& session_options); const cricket::MediaSessionOptions& session_options);
void CreateAnswer(CreateSessionDescriptionObserver* observer, void CreateAnswer(CreateSessionDescriptionObserver* observer,
const cricket::MediaSessionOptions& session_options); const cricket::MediaSessionOptions& session_options);
// The ownership of |desc| will be transferred after this call. bool SetLocalDescription(std::unique_ptr<SessionDescriptionInterface> desc,
bool SetLocalDescription(SessionDescriptionInterface* desc,
std::string* err_desc); std::string* err_desc);
// The ownership of |desc| will be transferred after this call. bool SetRemoteDescription(std::unique_ptr<SessionDescriptionInterface> desc,
bool SetRemoteDescription(SessionDescriptionInterface* desc,
std::string* err_desc); std::string* err_desc);
bool ProcessIceMessage(const IceCandidateInterface* ice_candidate); bool ProcessIceMessage(const IceCandidateInterface* ice_candidate);

File diff suppressed because it is too large Load diff