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:

committed by
Commit Bot

parent
139f4dc7ac
commit
ad5c4accad
@ -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",
|
||||
|
@ -14,7 +14,10 @@ rtc_source_set("emulated_network") {
|
||||
":*",
|
||||
]
|
||||
if (rtc_include_tests) {
|
||||
visibility += [ "../scenario" ]
|
||||
visibility += [
|
||||
"../scenario:*",
|
||||
"../peer_scenario:*",
|
||||
]
|
||||
}
|
||||
testonly = true
|
||||
sources = [
|
||||
|
44
test/peer_scenario/BUILD.gn
Normal file
44
test/peer_scenario/BUILD.gn
Normal 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
5
test/peer_scenario/DEPS
Normal file
@ -0,0 +1,5 @@
|
||||
include_rules = [
|
||||
"+pc",
|
||||
"+p2p",
|
||||
]
|
||||
|
75
test/peer_scenario/peer_scenario.cc
Normal file
75
test/peer_scenario/peer_scenario.cc
Normal 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
|
104
test/peer_scenario/peer_scenario.h
Normal file
104
test/peer_scenario/peer_scenario.h
Normal 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_
|
272
test/peer_scenario/peer_scenario_client.cc
Normal file
272
test/peer_scenario/peer_scenario_client.cc
Normal 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
|
152
test/peer_scenario/peer_scenario_client.h
Normal file
152
test/peer_scenario/peer_scenario_client.h
Normal 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_
|
54
test/peer_scenario/sdp_callbacks.cc
Normal file
54
test/peer_scenario/sdp_callbacks.cc
Normal 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
|
43
test/peer_scenario/sdp_callbacks.h
Normal file
43
test/peer_scenario/sdp_callbacks.h
Normal 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_
|
104
test/peer_scenario/signaling_route.cc
Normal file
104
test/peer_scenario/signaling_route.cc
Normal 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
|
55
test/peer_scenario/signaling_route.h
Normal file
55
test/peer_scenario/signaling_route.h
Normal 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_
|
24
test/peer_scenario/tests/BUILD.gn
Normal file
24
test/peer_scenario/tests/BUILD.gn
Normal 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",
|
||||
]
|
||||
}
|
||||
}
|
39
test/peer_scenario/tests/peer_scenario_quality_test.cc
Normal file
39
test/peer_scenario/tests/peer_scenario_quality_test.cc
Normal 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
|
51
test/peer_scenario/tests/remote_estimate_test.cc
Normal file
51
test/peer_scenario/tests/remote_estimate_test.cc
Normal 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
|
@ -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;
|
||||
|
@ -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_;
|
||||
|
Reference in New Issue
Block a user