Adds PeerConnection scenario test framework.

Bug: webrtc:10839
Change-Id: If67eeb680d016d66c69d8e761a88c240e4931a5d
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/147276
Commit-Queue: Sebastian Jansson <srte@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28754}
This commit is contained in:
Sebastian Jansson
2019-08-05 11:11:58 +02:00
committed by Commit Bot
parent 139f4dc7ac
commit ad5c4accad
17 changed files with 1050 additions and 6 deletions

View File

@ -390,6 +390,7 @@ if (rtc_include_tests) {
"../rtc_base/system:file_wrapper",
"../test:single_threaded_task_queue",
"pc/e2e:e2e_unittests",
"peer_scenario/tests",
"scenario:scenario_unittests",
"time_controller",
"time_controller:time_controller_unittests",

View File

@ -14,7 +14,10 @@ rtc_source_set("emulated_network") {
":*",
]
if (rtc_include_tests) {
visibility += [ "../scenario" ]
visibility += [
"../scenario:*",
"../peer_scenario:*",
]
}
testonly = true
sources = [

View File

@ -0,0 +1,44 @@
# Copyright (c) 2019 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.
import("../../webrtc.gni")
if (rtc_include_tests) {
rtc_source_set("peer_scenario") {
testonly = true
sources = [
"peer_scenario.cc",
"peer_scenario.h",
"peer_scenario_client.cc",
"peer_scenario_client.h",
"sdp_callbacks.cc",
"sdp_callbacks.h",
"signaling_route.cc",
"signaling_route.h",
]
deps = [
"../:video_test_common",
"../../api:libjingle_peerconnection_api",
"../../api:network_emulation_manager_api",
"../../api:rtc_stats_api",
"../../api/audio_codecs:builtin_audio_decoder_factory",
"../../api/audio_codecs:builtin_audio_encoder_factory",
"../../api/rtc_event_log:rtc_event_log_factory",
"../../api/task_queue:default_task_queue_factory",
"../../api/video_codecs:builtin_video_decoder_factory",
"../../api/video_codecs:builtin_video_encoder_factory",
"../../media:rtc_audio_video",
"../../modules/audio_device:audio_device_impl",
"../../p2p:rtc_p2p",
"../../pc:pc_test_utils",
"..//network:emulated_network",
"../scenario",
"//third_party/abseil-cpp/absl/memory:memory",
]
}
}

5
test/peer_scenario/DEPS Normal file
View File

@ -0,0 +1,5 @@
include_rules = [
"+pc",
"+p2p",
]

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "test/peer_scenario/peer_scenario.h"
#include "absl/memory/memory.h"
namespace webrtc {
namespace test {
PeerScenario::PeerScenario() : signaling_thread_(rtc::Thread::Current()) {}
PeerScenarioClient* PeerScenario::CreateClient(
PeerScenarioClient::Config config) {
peer_clients_.emplace_back(net(), thread(), config);
return &peer_clients_.back();
}
SignalingRoute PeerScenario::ConnectSignaling(
PeerScenarioClient* caller,
PeerScenarioClient* callee,
std::vector<EmulatedNetworkNode*> send_link,
std::vector<EmulatedNetworkNode*> ret_link) {
return SignalingRoute(caller, callee, net_.CreateTrafficRoute(send_link),
net_.CreateTrafficRoute(ret_link));
}
void PeerScenario::SimpleConnection(
PeerScenarioClient* caller,
PeerScenarioClient* callee,
std::vector<EmulatedNetworkNode*> send_link,
std::vector<EmulatedNetworkNode*> ret_link) {
net()->CreateRoute(caller->endpoint(), send_link, callee->endpoint());
net()->CreateRoute(callee->endpoint(), ret_link, caller->endpoint());
auto signaling = ConnectSignaling(caller, callee, send_link, ret_link);
signaling.StartIceSignaling();
rtc::Event done;
signaling.NegotiateSdp(
[&](const SessionDescriptionInterface&) { done.Set(); });
RTC_CHECK(WaitAndProcess(&done));
}
void PeerScenario::AttachVideoQualityAnalyzer(VideoQualityAnalyzer* analyzer,
VideoTrackInterface* send_track,
PeerScenarioClient* receiver) {
video_quality_pairs_.emplace_back(clock(), analyzer);
auto pair = &video_quality_pairs_.back();
send_track->AddOrUpdateSink(&pair->capture_tap_, rtc::VideoSinkWants());
receiver->AddVideoReceiveSink(send_track->id(), &pair->decode_tap_);
}
bool PeerScenario::WaitAndProcess(rtc::Event* event, TimeDelta max_duration) {
constexpr int kStepMs = 5;
if (event->Wait(0))
return true;
for (int elapsed = 0; elapsed < max_duration.ms(); elapsed += kStepMs) {
thread()->ProcessMessages(kStepMs);
if (event->Wait(0))
return true;
}
return false;
}
void PeerScenario::ProcessMessages(TimeDelta duration) {
thread()->ProcessMessages(duration.ms());
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef TEST_PEER_SCENARIO_PEER_SCENARIO_H_
#define TEST_PEER_SCENARIO_PEER_SCENARIO_H_
// The peer connection scenario test framework enables writing end to end unit
// tests on the peer connection level. It's similar to the Scenario test but
// uses the full stack, including SDP and ICE negotiation. This ensures that
// features work end to end. It's also diffferent from the other tests on peer
// connection level in that it does not rely on any mocks or fakes other than
// for media input and networking. Additionally it provides direct access to the
// underlying peer connection class.
#include <list>
#include <vector>
#include "test/network/network_emulation_manager.h"
#include "test/peer_scenario/peer_scenario_client.h"
#include "test/peer_scenario/signaling_route.h"
#include "test/scenario/stats_collection.h"
#include "test/scenario/video_frame_matcher.h"
namespace webrtc {
namespace test {
// The PeerScenario class represents a PeerConnection simulation scenario. The
// main purpose is to maintain ownership and ensure safe destruction order of
// clients and network emulation. Additionally it reduces the amount of bolier
// plate requited for some actions. For example usage see the existing tests
// using this class. Note that it should be used from a single calling thread.
// This thread will also be assigned as the signaling thread for all peer
// connections that are created. This means that the process methods must be
// used when waiting to ensure that messages are processed on the signaling
// thread.
class PeerScenario {
public:
PeerScenario();
NetworkEmulationManagerImpl* net() { return &net_; }
rtc::Thread* thread() { return signaling_thread_; }
// Creates a client wrapping a peer connection conforming to the given config.
// The client will share the signaling thread with the scenario. To maintain
// control of destruction order, ownership is kept within the scenario.
PeerScenarioClient* CreateClient(PeerScenarioClient::Config config);
// Sets up a signaling route that can be used for SDP and ICE.
SignalingRoute ConnectSignaling(PeerScenarioClient* caller,
PeerScenarioClient* callee,
std::vector<EmulatedNetworkNode*> send_link,
std::vector<EmulatedNetworkNode*> ret_link);
// Connects two clients over given links. This will also start ICE signaling
// and SDP negotiation with default behavior. For customized behavior,
// ConnectSignaling should be used to allow more detailed control, for
// instance to allow different signaling and media routes.
void SimpleConnection(PeerScenarioClient* caller,
PeerScenarioClient* callee,
std::vector<EmulatedNetworkNode*> send_link,
std::vector<EmulatedNetworkNode*> ret_link);
// Starts feeding the results of comparing captured frames from |send_track|
// with decoded frames on |receiver| to |analyzer|.
// TODO(srte): Provide a way to detach to allow removal of tracks.
void AttachVideoQualityAnalyzer(VideoQualityAnalyzer* analyzer,
VideoTrackInterface* send_track,
PeerScenarioClient* receiver);
// Waits on |event| while processing messages on the signaling thread.
bool WaitAndProcess(rtc::Event* event,
TimeDelta max_duration = TimeDelta::seconds(5));
// Process messages on the signaling thread for the given duration.
void ProcessMessages(TimeDelta duration);
private:
// Helper struct to maintain ownership of the matcher and taps.
struct PeerVideoQualityPair {
public:
PeerVideoQualityPair(Clock* capture_clock, VideoQualityAnalyzer* analyzer)
: matcher_({analyzer->Handler()}),
capture_tap_(capture_clock, &matcher_),
decode_tap_(capture_clock, &matcher_, 0) {}
VideoFrameMatcher matcher_;
CapturedFrameTap capture_tap_;
DecodedFrameTap decode_tap_;
};
Clock* clock() { return Clock::GetRealTimeClock(); }
rtc::Thread* const signaling_thread_;
std::list<PeerVideoQualityPair> video_quality_pairs_;
NetworkEmulationManagerImpl net_;
std::list<PeerScenarioClient> peer_clients_;
};
} // namespace test
} // namespace webrtc
#endif // TEST_PEER_SCENARIO_PEER_SCENARIO_H_

View File

@ -0,0 +1,272 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "test/peer_scenario/peer_scenario_client.h"
#include <limits>
#include <utility>
#include "absl/memory/memory.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/rtc_event_log/rtc_event_log_factory.h"
#include "api/task_queue/default_task_queue_factory.h"
#include "api/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.h"
#include "media/engine/webrtc_media_engine.h"
#include "modules/audio_device/include/test_audio_device.h"
#include "p2p/client/basic_port_allocator.h"
#include "test/frame_generator_capturer.h"
#include "test/peer_scenario/sdp_callbacks.h"
namespace webrtc {
namespace test {
namespace {
constexpr char kCommonStreamId[] = "stream_id";
std::map<int, EmulatedEndpoint*> CreateEndpoints(
NetworkEmulationManager* net,
std::map<int, EmulatedEndpointConfig> endpoint_configs) {
std::map<int, EmulatedEndpoint*> endpoints;
for (const auto& kv : endpoint_configs)
endpoints[kv.first] = net->CreateEndpoint(kv.second);
return endpoints;
}
class LambdaPeerConnectionObserver final : public PeerConnectionObserver {
public:
explicit LambdaPeerConnectionObserver(
PeerScenarioClient::CallbackHandlers* handlers)
: handlers_(handlers) {}
void OnSignalingChange(
PeerConnectionInterface::SignalingState new_state) override {
for (const auto& handler : handlers_->on_signaling_change)
handler(new_state);
}
void OnDataChannel(
rtc::scoped_refptr<DataChannelInterface> data_channel) override {
for (const auto& handler : handlers_->on_data_channel)
handler(data_channel);
}
void OnRenegotiationNeeded() override {
for (const auto& handler : handlers_->on_renegotiation_needed)
handler();
}
void OnStandardizedIceConnectionChange(
PeerConnectionInterface::IceConnectionState new_state) override {
for (const auto& handler : handlers_->on_standardized_ice_connection_change)
handler(new_state);
}
void OnConnectionChange(
PeerConnectionInterface::PeerConnectionState new_state) override {
for (const auto& handler : handlers_->on_connection_change)
handler(new_state);
}
void OnIceGatheringChange(
PeerConnectionInterface::IceGatheringState new_state) override {
for (const auto& handler : handlers_->on_ice_gathering_change)
handler(new_state);
}
void OnIceCandidate(const IceCandidateInterface* candidate) override {
for (const auto& handler : handlers_->on_ice_candidate)
handler(candidate);
}
void OnIceCandidateError(const std::string& host_candidate,
const std::string& url,
int error_code,
const std::string& error_text) override {
for (const auto& handler : handlers_->on_ice_candidate_error)
handler(host_candidate, url, error_code, error_text);
}
void OnIceCandidatesRemoved(
const std::vector<cricket::Candidate>& candidates) override {
for (const auto& handler : handlers_->on_ice_candidates_removed)
handler(candidates);
}
void OnAddTrack(rtc::scoped_refptr<RtpReceiverInterface> receiver,
const std::vector<rtc::scoped_refptr<MediaStreamInterface> >&
streams) override {
for (const auto& handler : handlers_->on_add_track)
handler(receiver, streams);
}
void OnTrack(
rtc::scoped_refptr<RtpTransceiverInterface> transceiver) override {
for (const auto& handler : handlers_->on_track)
handler(transceiver);
}
void OnRemoveTrack(
rtc::scoped_refptr<RtpReceiverInterface> receiver) override {
for (const auto& handler : handlers_->on_remove_track)
handler(receiver);
}
private:
PeerScenarioClient::CallbackHandlers* handlers_;
};
} // namespace
PeerScenarioClient::PeerScenarioClient(NetworkEmulationManager* net,
rtc::Thread* signaling_thread,
PeerScenarioClient::Config config)
: endpoints_(CreateEndpoints(net, config.endpoints)),
signaling_thread_(signaling_thread),
worker_thread_(rtc::Thread::Create()),
handlers_(config.handlers),
observer_(new LambdaPeerConnectionObserver(&handlers_)) {
worker_thread_->SetName("worker", this);
worker_thread_->Start();
handlers_.on_track.push_back(
[this](rtc::scoped_refptr<RtpTransceiverInterface> transceiver) {
auto track = transceiver->receiver()->track().get();
if (track->kind() == MediaStreamTrackInterface::kVideoKind) {
auto* video = static_cast<VideoTrackInterface*>(track);
for (auto* sink : track_id_to_video_sinks_[track->id()]) {
video->AddOrUpdateSink(sink, rtc::VideoSinkWants());
}
}
});
std::vector<EmulatedEndpoint*> endpoints_vector;
for (const auto& kv : endpoints_)
endpoints_vector.push_back(kv.second);
auto* manager = net->CreateEmulatedNetworkManagerInterface(endpoints_vector);
PeerConnectionFactoryDependencies pcf_deps;
pcf_deps.network_thread = manager->network_thread();
pcf_deps.signaling_thread = signaling_thread_;
pcf_deps.worker_thread = worker_thread_.get();
pcf_deps.call_factory = CreateCallFactory();
pcf_deps.task_queue_factory = CreateDefaultTaskQueueFactory();
task_queue_factory_ = pcf_deps.task_queue_factory.get();
pcf_deps.event_log_factory =
absl::make_unique<RtcEventLogFactory>(task_queue_factory_);
cricket::MediaEngineDependencies media_deps;
media_deps.task_queue_factory = task_queue_factory_;
media_deps.adm = TestAudioDeviceModule::Create(
task_queue_factory_,
TestAudioDeviceModule::CreatePulsedNoiseCapturer(
config.audio.pulsed_noise->amplitude *
std::numeric_limits<int16_t>::max(),
config.audio.sample_rate, config.audio.channels),
TestAudioDeviceModule::CreateDiscardRenderer(config.audio.sample_rate));
media_deps.audio_processing = AudioProcessingBuilder().Create();
media_deps.video_encoder_factory = CreateBuiltinVideoEncoderFactory();
media_deps.video_decoder_factory = CreateBuiltinVideoDecoderFactory();
media_deps.audio_encoder_factory = CreateBuiltinAudioEncoderFactory();
media_deps.audio_decoder_factory = CreateBuiltinAudioDecoderFactory();
pcf_deps.media_engine = cricket::CreateMediaEngine(std::move(media_deps));
pcf_deps.fec_controller_factory = nullptr;
pcf_deps.network_controller_factory = nullptr;
pcf_deps.network_state_predictor_factory = nullptr;
pcf_deps.media_transport_factory = nullptr;
pc_factory_ = CreateModularPeerConnectionFactory(std::move(pcf_deps));
PeerConnectionDependencies pc_deps(observer_.get());
pc_deps.allocator = absl::make_unique<cricket::BasicPortAllocator>(
manager->network_manager());
pc_deps.allocator->set_flags(pc_deps.allocator->flags() |
cricket::PORTALLOCATOR_DISABLE_TCP);
peer_connection_ =
pc_factory_->CreatePeerConnection(config.rtc_config, std::move(pc_deps));
}
EmulatedEndpoint* PeerScenarioClient::endpoint(int index) {
RTC_CHECK_GT(endpoints_.size(), index);
return endpoints_.at(index);
}
PeerScenarioClient::AudioSendTrack PeerScenarioClient::CreateAudio(
std::string track_id,
cricket::AudioOptions options) {
AudioSendTrack res;
auto source = pc_factory_->CreateAudioSource(options);
auto track = pc_factory_->CreateAudioTrack(track_id, source);
res.track = track;
res.sender = peer_connection_->AddTrack(track, {kCommonStreamId}).value();
return res;
}
PeerScenarioClient::VideoSendTrack PeerScenarioClient::CreateVideo(
std::string track_id,
VideoSendTrackConfig config) {
VideoSendTrack res;
auto capturer = FrameGeneratorCapturer::Create(clock(), *task_queue_factory_,
config.generator);
res.capturer = capturer.get();
capturer->Init();
res.source =
new rtc::RefCountedObject<FrameGeneratorCapturerVideoTrackSource>(
std::move(capturer), config.screencast);
auto track = pc_factory_->CreateVideoTrack(track_id, res.source);
res.track = track;
res.sender = peer_connection_->AddTrack(track, {kCommonStreamId}).MoveValue();
return res;
}
void PeerScenarioClient::AddVideoReceiveSink(
std::string track_id,
rtc::VideoSinkInterface<VideoFrame>* video_sink) {
track_id_to_video_sinks_[track_id].push_back(video_sink);
}
void PeerScenarioClient::CreateAndSetSdp(
std::function<void(std::string)> offer_handler) {
peer_connection_->CreateOffer(
SdpCreateObserver([=](SessionDescriptionInterface* offer) {
std::string sdp_offer;
offer->ToString(&sdp_offer);
peer_connection_->SetLocalDescription(
SdpSetObserver([sdp_offer, offer_handler]() {
offer_handler(std::move(sdp_offer));
}),
offer);
}),
PeerConnectionInterface::RTCOfferAnswerOptions());
}
void PeerScenarioClient::SetSdpOfferAndGetAnswer(
std::string remote_offer,
std::function<void(std::string)> answer_handler) {
peer_connection_->SetRemoteDescription(
CreateSessionDescription(SdpType::kOffer, remote_offer),
SdpSetObserver([=]() {
peer_connection_->CreateAnswer(
SdpCreateObserver([=](SessionDescriptionInterface* answer) {
std::string sdp_answer;
answer->ToString(&sdp_answer);
peer_connection_->SetLocalDescription(
SdpSetObserver([answer_handler, sdp_answer]() {
answer_handler(sdp_answer);
}),
answer);
}),
PeerConnectionInterface::RTCOfferAnswerOptions());
}));
}
void PeerScenarioClient::SetSdpAnswer(
std::string remote_answer,
std::function<void(const SessionDescriptionInterface&)> done_handler) {
peer_connection_->SetRemoteDescription(
CreateSessionDescription(SdpType::kAnswer, remote_answer),
SdpSetObserver([remote_answer, done_handler] {
auto answer = CreateSessionDescription(SdpType::kAnswer, remote_answer);
done_handler(*answer);
}));
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,152 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef TEST_PEER_SCENARIO_PEER_SCENARIO_CLIENT_H_
#define TEST_PEER_SCENARIO_PEER_SCENARIO_CLIENT_H_
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "absl/memory/memory.h"
#include "api/peer_connection_interface.h"
#include "api/test/network_emulation_manager.h"
#include "pc/test/frame_generator_capturer_video_track_source.h"
namespace webrtc {
namespace test {
// Wrapper for a PeerConnection for use in PeerScenario tests. It's intended to
// be a minimal wrapper for a peer connection that's simple to use in testing.
// In particular the constructor hides a lot of the required setup for a peer
// connection.
class PeerScenarioClient {
public:
struct CallbackHandlers {
std::vector<std::function<void(PeerConnectionInterface::SignalingState)>>
on_signaling_change;
std::vector<std::function<void(rtc::scoped_refptr<DataChannelInterface>)>>
on_data_channel;
std::vector<std::function<void()>> on_renegotiation_needed;
std::vector<
std::function<void(PeerConnectionInterface::IceConnectionState)>>
on_standardized_ice_connection_change;
std::vector<
std::function<void(PeerConnectionInterface::PeerConnectionState)>>
on_connection_change;
std::vector<std::function<void(PeerConnectionInterface::IceGatheringState)>>
on_ice_gathering_change;
std::vector<std::function<void(const IceCandidateInterface*)>>
on_ice_candidate;
std::vector<std::function<
void(const std::string&, const std::string&, int, const std::string&)>>
on_ice_candidate_error;
std::vector<std::function<void(const std::vector<cricket::Candidate>&)>>
on_ice_candidates_removed;
std::vector<std::function<void(
rtc::scoped_refptr<RtpReceiverInterface>,
const std::vector<rtc::scoped_refptr<MediaStreamInterface>>&)>>
on_add_track;
std::vector<
std::function<void(rtc::scoped_refptr<RtpTransceiverInterface>)>>
on_track;
std::vector<std::function<void(rtc::scoped_refptr<RtpReceiverInterface>)>>
on_remove_track;
};
struct Config {
// WebRTC only support one audio device that is setup up on construction, so
// we provide the audio generator configuration here rather than on creation
// of the tracks. This is unlike video, where multiple capture sources can
// be used at the same time.
struct AudioSource {
int sample_rate = 48000;
int channels = 1;
struct PulsedNoise {
double amplitude = 0.1;
};
absl::optional<PulsedNoise> pulsed_noise = PulsedNoise();
} audio;
std::string client_name;
// The created endpoints can be accessed using the map key as |index| in
// PeerScenarioClient::endpoint(index).
std::map<int, EmulatedEndpointConfig> endpoints = {
{0, EmulatedEndpointConfig()}};
CallbackHandlers handlers;
PeerConnectionInterface::RTCConfiguration rtc_config;
Config() { rtc_config.sdp_semantics = SdpSemantics::kUnifiedPlan; }
};
struct VideoSendTrackConfig {
FrameGeneratorCapturerConfig generator;
bool screencast = false;
};
struct AudioSendTrack {
AudioTrackInterface* track;
RtpSenderInterface* sender;
};
struct VideoSendTrack {
FrameGeneratorCapturer* capturer;
FrameGeneratorCapturerVideoTrackSource* source;
VideoTrackInterface* track;
RtpSenderInterface* sender;
};
PeerScenarioClient(NetworkEmulationManager* net,
rtc::Thread* signaling_thread,
Config config);
PeerConnectionFactoryInterface* factory() { return pc_factory_.get(); }
PeerConnectionInterface* pc() { return peer_connection_.get(); }
rtc::Thread* thread() { return signaling_thread_; }
Clock* clock() { return Clock::GetRealTimeClock(); }
// Returns the endpoint created from the EmulatedEndpointConfig with the same
// index in PeerScenarioClient::config.
EmulatedEndpoint* endpoint(int index = 0);
AudioSendTrack CreateAudio(std::string track_id,
cricket::AudioOptions options);
VideoSendTrack CreateVideo(std::string track_id, VideoSendTrackConfig config);
void AddVideoReceiveSink(std::string track_id,
rtc::VideoSinkInterface<VideoFrame>* video_sink);
CallbackHandlers* handlers() { return &handlers_; }
// Note that there's no provision for munging SDP as that is deprecated
// behavior.
void CreateAndSetSdp(std::function<void(std::string)> offer_handler);
void SetSdpOfferAndGetAnswer(std::string remote_offer,
std::function<void(std::string)> answer_handler);
void SetSdpAnswer(
std::string remote_answer,
std::function<void(const SessionDescriptionInterface& answer)>
done_handler);
private:
const std::map<int, EmulatedEndpoint*> endpoints_;
rtc::Thread* const signaling_thread_;
const std::unique_ptr<rtc::Thread> worker_thread_;
CallbackHandlers handlers_;
std::unique_ptr<PeerConnectionObserver> observer_;
TaskQueueFactory* task_queue_factory_;
std::map<std::string, std::vector<rtc::VideoSinkInterface<VideoFrame>*>>
track_id_to_video_sinks_;
rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory_;
rtc::scoped_refptr<PeerConnectionInterface> peer_connection_;
};
} // namespace test
} // namespace webrtc
#endif // TEST_PEER_SCENARIO_PEER_SCENARIO_CLIENT_H_

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "test/peer_scenario/sdp_callbacks.h"
#include <utility>
namespace webrtc {
namespace test {
webrtc_sdp_obs_impl::SdpSetObserversInterface* SdpSetObserver(
std::function<void()> callback) {
class SdpSetObserver : public webrtc_sdp_obs_impl::SdpSetObserversInterface {
public:
explicit SdpSetObserver(std::function<void()> callback)
: callback_(std::move(callback)) {}
void OnSuccess() override { callback_(); }
void OnFailure(RTCError error) override {
RTC_NOTREACHED() << error.message();
}
void OnSetRemoteDescriptionComplete(RTCError error) override {
RTC_CHECK(error.ok()) << error.message();
callback_();
}
std::function<void()> callback_;
};
return new rtc::RefCountedObject<SdpSetObserver>(std::move(callback));
}
CreateSessionDescriptionObserver* SdpCreateObserver(
std::function<void(SessionDescriptionInterface*)> callback) {
class SdpCreateObserver : public CreateSessionDescriptionObserver {
public:
explicit SdpCreateObserver(decltype(callback) callback)
: callback_(std::move(callback)) {}
void OnSuccess(SessionDescriptionInterface* desc) override {
callback_(desc);
}
void OnFailure(RTCError error) override {
RTC_NOTREACHED() << error.message();
}
decltype(callback) callback_;
};
return new rtc::RefCountedObject<SdpCreateObserver>(std::move(callback));
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef TEST_PEER_SCENARIO_SDP_CALLBACKS_H_
#define TEST_PEER_SCENARIO_SDP_CALLBACKS_H_
#include "api/peer_connection_interface.h"
// Helpers to allow usage of std::function/lambdas to observe SDP operation in
// the peer conenction API. As they only have handlers for sucess, failures will
// cause a crash.
namespace webrtc {
namespace test {
namespace webrtc_sdp_obs_impl {
class SdpSetObserversInterface : public SetSessionDescriptionObserver,
public SetRemoteDescriptionObserverInterface {
};
} // namespace webrtc_sdp_obs_impl
// Implementation of both SetSessionDescriptionObserver and
// SetRemoteDescriptionObserverInterface for use with SDP set operations. This
// return a raw owning pointer as it's only intended to be used as input to
// PeerConnection API which will take ownership.
webrtc_sdp_obs_impl::SdpSetObserversInterface* SdpSetObserver(
std::function<void()> callback);
// Implementation of CreateSessionDescriptionObserver for use with SDP create
// operations. This return a raw owning pointer as it's only intended to be used
// as input to PeerConnection API which will take ownership.
CreateSessionDescriptionObserver* SdpCreateObserver(
std::function<void(SessionDescriptionInterface*)> callback);
} // namespace test
} // namespace webrtc
#endif // TEST_PEER_SCENARIO_SDP_CALLBACKS_H_

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "test/peer_scenario/signaling_route.h"
#include "test/network/network_emulation_manager.h"
namespace webrtc {
namespace test {
namespace {
constexpr size_t kIcePacketSize = 400;
constexpr size_t kSdpPacketSize = 1200;
struct IceMessage {
IceMessage() = default;
explicit IceMessage(const IceCandidateInterface* candidate)
: sdp_mid(candidate->sdp_mid()),
sdp_mline_index(candidate->sdp_mline_index()) {
RTC_CHECK(candidate->ToString(&sdp_line));
}
IceCandidateInterface* AsCandidate() const {
SdpParseError err;
auto* candidate =
CreateIceCandidate(sdp_mid, sdp_mline_index, sdp_line, &err);
RTC_CHECK(candidate) << "Failed to parse: \"" << err.line
<< "\". Reason: " << err.description;
return candidate;
}
std::string sdp_mid;
int sdp_mline_index;
std::string sdp_line;
};
void StartIceSignalingForRoute(PeerScenarioClient* caller,
PeerScenarioClient* callee,
TrafficRoute* send_route) {
caller->handlers()->on_ice_candidate.push_back(
[=](const IceCandidateInterface* candidate) {
IceMessage msg(candidate);
send_route->NetworkDelayedAction(kIcePacketSize, [callee, msg]() {
callee->thread()->PostTask(RTC_FROM_HERE, [callee, msg]() {
RTC_CHECK(callee->pc()->AddIceCandidate(msg.AsCandidate()));
});
});
});
}
void StartSdpNegotiation(
PeerScenarioClient* caller,
PeerScenarioClient* callee,
TrafficRoute* send_route,
TrafficRoute* ret_route,
std::function<void(SessionDescriptionInterface*)> modify_offer,
std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
caller->CreateAndSetSdp([=](std::string sdp_offer) {
if (modify_offer) {
auto offer = CreateSessionDescription(SdpType::kOffer, sdp_offer);
modify_offer(offer.get());
RTC_CHECK(offer->ToString(&sdp_offer));
}
send_route->NetworkDelayedAction(kSdpPacketSize, [=] {
callee->SetSdpOfferAndGetAnswer(sdp_offer, [=](std::string answer) {
ret_route->NetworkDelayedAction(kSdpPacketSize, [=] {
caller->SetSdpAnswer(std::move(answer), std::move(exchange_finished));
});
});
});
});
}
} // namespace
SignalingRoute::SignalingRoute(PeerScenarioClient* caller,
PeerScenarioClient* callee,
TrafficRoute* send_route,
TrafficRoute* ret_route)
: caller_(caller),
callee_(callee),
send_route_(send_route),
ret_route_(ret_route) {}
void SignalingRoute::StartIceSignaling() {
StartIceSignalingForRoute(caller_, callee_, send_route_);
StartIceSignalingForRoute(callee_, caller_, ret_route_);
}
void SignalingRoute::NegotiateSdp(
std::function<void(SessionDescriptionInterface*)> modify_offer,
std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
StartSdpNegotiation(caller_, callee_, send_route_, ret_route_, modify_offer,
exchange_finished);
}
void SignalingRoute::NegotiateSdp(
std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
NegotiateSdp({}, exchange_finished);
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef TEST_PEER_SCENARIO_SIGNALING_ROUTE_H_
#define TEST_PEER_SCENARIO_SIGNALING_ROUTE_H_
#include <string>
#include <utility>
#include "test/network/network_emulation_manager.h"
#include "test/peer_scenario/peer_scenario_client.h"
namespace webrtc {
namespace test {
// Helper class to reduce the amount of boilerplate required for ICE signalling
// ad SDP negotiation.
class SignalingRoute {
public:
SignalingRoute(PeerScenarioClient* caller,
PeerScenarioClient* callee,
TrafficRoute* send_route,
TrafficRoute* ret_route);
void StartIceSignaling();
// TODO(srte): Handle lossy links.
void NegotiateSdp(
std::function<void(SessionDescriptionInterface* offer)> modify_offer,
std::function<void(const SessionDescriptionInterface& answer)>
exchange_finished);
void NegotiateSdp(
std::function<void(const SessionDescriptionInterface& answer)>
exchange_finished);
SignalingRoute reverse() {
return SignalingRoute(callee_, caller_, ret_route_, send_route_);
}
private:
PeerScenarioClient* const caller_;
PeerScenarioClient* const callee_;
TrafficRoute* const send_route_;
TrafficRoute* const ret_route_;
};
} // namespace test
} // namespace webrtc
#endif // TEST_PEER_SCENARIO_SIGNALING_ROUTE_H_

View File

@ -0,0 +1,24 @@
# Copyright (c) 2019 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.
import("../../../webrtc.gni")
if (rtc_include_tests) {
rtc_source_set("tests") {
testonly = true
sources = [
"peer_scenario_quality_test.cc",
"remote_estimate_test.cc",
]
deps = [
"..:peer_scenario",
"../../:test_support",
"../../../pc:rtc_pc_base",
]
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "test/gtest.h"
#include "test/peer_scenario/peer_scenario.h"
namespace webrtc {
namespace test {
TEST(PeerScenarioQualityTest, PsnrIsCollected) {
VideoQualityAnalyzerConfig analyzer_config;
analyzer_config.thread = rtc::Thread::Current();
VideoQualityAnalyzer analyzer(analyzer_config);
PeerScenario s;
auto caller = s.CreateClient(PeerScenarioClient::Config());
auto callee = s.CreateClient(PeerScenarioClient::Config());
PeerScenarioClient::VideoSendTrackConfig video_conf;
video_conf.generator.squares_video->framerate = 20;
auto video = caller->CreateVideo("VIDEO", video_conf);
auto link_builder = s.net()->NodeBuilder().delay_ms(100).capacity_kbps(600);
s.AttachVideoQualityAnalyzer(&analyzer, video.track, callee);
s.SimpleConnection(caller, callee, {link_builder.Build().node},
{link_builder.Build().node});
s.ProcessMessages(TimeDelta::seconds(2));
// We expect ca 40 frames to be produced, but to avoid flakiness on slow
// machines we only test for 10.
EXPECT_GT(analyzer.stats().render.count, 10);
EXPECT_GT(analyzer.stats().psnr_with_freeze.Mean(), 20);
}
} // namespace test
} // namespace webrtc

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "pc/session_description.h"
#include "test/gtest.h"
#include "test/peer_scenario/peer_scenario.h"
namespace webrtc {
namespace test {
TEST(RemoteEstimateEndToEnd, OfferedCapabilityIsInAnswer) {
PeerScenario s;
auto* caller = s.CreateClient(PeerScenarioClient::Config());
auto* callee = s.CreateClient(PeerScenarioClient::Config());
auto send_link = {s.net()->NodeBuilder().Build().node};
auto ret_link = {s.net()->NodeBuilder().Build().node};
s.net()->CreateRoute(caller->endpoint(), send_link, callee->endpoint());
s.net()->CreateRoute(callee->endpoint(), ret_link, caller->endpoint());
auto signaling = s.ConnectSignaling(caller, callee, send_link, ret_link);
signaling.StartIceSignaling();
caller->CreateVideo("VIDEO", PeerScenarioClient::VideoSendTrackConfig());
rtc::Event offer_exchange_done;
signaling.NegotiateSdp(
[](SessionDescriptionInterface* offer) {
for (auto& cont : offer->description()->contents()) {
cont.media_description()->set_remote_estimate(true);
}
},
[&](const SessionDescriptionInterface& answer) {
for (auto& cont : answer.description()->contents()) {
EXPECT_TRUE(cont.media_description()->remote_estimate());
}
offer_exchange_done.Set();
});
EXPECT_TRUE(s.WaitAndProcess(&offer_exchange_done));
}
} // namespace test
} // namespace webrtc

View File

@ -37,11 +37,26 @@ std::function<void(const VideoFramePair&)> VideoQualityAnalyzer::Handler() {
return [this](VideoFramePair pair) { HandleFramePair(pair); };
}
void VideoQualityAnalyzer::HandleFramePair(VideoFramePair sample) {
layer_analyzers_[sample.layer_id].HandleFramePair(sample, writer_.get());
void VideoQualityAnalyzer::HandleFramePair(VideoFramePair sample, double psnr) {
layer_analyzers_[sample.layer_id].HandleFramePair(sample, psnr,
writer_.get());
cached_.reset();
}
void VideoQualityAnalyzer::HandleFramePair(VideoFramePair sample) {
double psnr = NAN;
if (sample.decoded)
psnr = I420PSNR(*sample.captured->ToI420(), *sample.decoded->ToI420());
if (config_.thread) {
config_.thread->PostTask(RTC_FROM_HERE, [this, sample, psnr] {
HandleFramePair(std::move(sample), psnr);
});
} else {
HandleFramePair(std::move(sample), psnr);
}
}
std::vector<VideoQualityStats> VideoQualityAnalyzer::layer_stats() const {
std::vector<VideoQualityStats> res;
for (auto& layer : layer_analyzers_)
@ -59,8 +74,8 @@ VideoQualityStats& VideoQualityAnalyzer::stats() {
}
void VideoLayerAnalyzer::HandleFramePair(VideoFramePair sample,
double psnr,
RtcEventLogOutput* writer) {
double psnr = NAN;
RTC_CHECK(sample.captured);
HandleCapturedFrame(sample);
if (!sample.decoded) {
@ -69,7 +84,6 @@ void VideoLayerAnalyzer::HandleFramePair(VideoFramePair sample,
++stats_.lost_count;
++skip_count_;
} else {
psnr = I420PSNR(*sample.captured->ToI420(), *sample.decoded->ToI420());
stats_.psnr_with_freeze.AddSample(psnr);
if (sample.repeated) {
++stats_.freeze_count;

View File

@ -23,13 +23,16 @@ namespace test {
struct VideoQualityAnalyzerConfig {
double psnr_coverage = 1;
rtc::Thread* thread = nullptr;
};
class VideoLayerAnalyzer {
public:
void HandleCapturedFrame(const VideoFramePair& sample);
void HandleRenderedFrame(const VideoFramePair& sample);
void HandleFramePair(VideoFramePair sample, RtcEventLogOutput* writer);
void HandleFramePair(VideoFramePair sample,
double psnr,
RtcEventLogOutput* writer);
VideoQualityStats stats_;
Timestamp last_capture_time_ = Timestamp::MinusInfinity();
Timestamp last_render_time_ = Timestamp::MinusInfinity();
@ -51,6 +54,7 @@ class VideoQualityAnalyzer {
std::function<void(const VideoFramePair&)> Handler();
private:
void HandleFramePair(VideoFramePair sample, double psnr);
const VideoQualityAnalyzerConfig config_;
std::map<int, VideoLayerAnalyzer> layer_analyzers_;
const std::unique_ptr<RtcEventLogOutput> writer_;