
This is the first step in the next refactoring: 1. Make general peer params immutable and accessible only via const ref. 2. Extract params which can be changed during the call 3. Expose mutators for mutable params protecting them with proper locking. Bug: b/213863770 Change-Id: Ie3ac17918f1fed3b8ec84992f8b0afc402bce7f4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/260003 Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Commit-Queue: Artem Titov <titovartem@webrtc.org> Cr-Commit-Position: refs/heads/main@{#36647}
726 lines
30 KiB
C++
726 lines
30 KiB
C++
/*
|
|
* 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/pc/e2e/peer_connection_quality_test.h"
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <utility>
|
|
|
|
#include "absl/strings/string_view.h"
|
|
#include "api/jsep.h"
|
|
#include "api/media_stream_interface.h"
|
|
#include "api/peer_connection_interface.h"
|
|
#include "api/rtc_event_log/rtc_event_log.h"
|
|
#include "api/rtc_event_log_output_file.h"
|
|
#include "api/scoped_refptr.h"
|
|
#include "api/test/time_controller.h"
|
|
#include "api/test/video_quality_analyzer_interface.h"
|
|
#include "pc/sdp_utils.h"
|
|
#include "pc/test/mock_peer_connection_observers.h"
|
|
#include "rtc_base/gunit.h"
|
|
#include "rtc_base/numerics/safe_conversions.h"
|
|
#include "rtc_base/strings/string_builder.h"
|
|
#include "system_wrappers/include/cpu_info.h"
|
|
#include "system_wrappers/include/field_trial.h"
|
|
#include "test/field_trial.h"
|
|
#include "test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.h"
|
|
#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer.h"
|
|
#include "test/pc/e2e/analyzer/video/video_quality_metrics_reporter.h"
|
|
#include "test/pc/e2e/cross_media_metrics_reporter.h"
|
|
#include "test/pc/e2e/stats_poller.h"
|
|
#include "test/pc/e2e/test_peer_factory.h"
|
|
#include "test/testsupport/file_utils.h"
|
|
#include "test/testsupport/perf_test.h"
|
|
|
|
namespace webrtc {
|
|
namespace webrtc_pc_e2e {
|
|
namespace {
|
|
|
|
using VideoConfig = PeerConnectionE2EQualityTestFixture::VideoConfig;
|
|
using VideoCodecConfig = PeerConnectionE2EQualityTestFixture::VideoCodecConfig;
|
|
|
|
constexpr TimeDelta kDefaultTimeout = TimeDelta::Seconds(10);
|
|
constexpr char kSignalThreadName[] = "signaling_thread";
|
|
// 1 signaling, 2 network, 2 worker and 2 extra for codecs etc.
|
|
constexpr int kPeerConnectionUsedThreads = 7;
|
|
// Framework has extra thread for network layer and extra thread for peer
|
|
// connection stats polling.
|
|
constexpr int kFrameworkUsedThreads = 2;
|
|
constexpr int kMaxVideoAnalyzerThreads = 8;
|
|
|
|
constexpr TimeDelta kStatsUpdateInterval = TimeDelta::Seconds(1);
|
|
|
|
constexpr TimeDelta kAliveMessageLogInterval = TimeDelta::Seconds(30);
|
|
|
|
constexpr TimeDelta kQuickTestModeRunDuration = TimeDelta::Millis(100);
|
|
|
|
// Field trials to enable Flex FEC advertising and receiving.
|
|
constexpr char kFlexFecEnabledFieldTrials[] =
|
|
"WebRTC-FlexFEC-03-Advertised/Enabled/WebRTC-FlexFEC-03/Enabled/";
|
|
constexpr char kUseStandardsBytesStats[] =
|
|
"WebRTC-UseStandardBytesStats/Enabled/";
|
|
|
|
class FixturePeerConnectionObserver : public MockPeerConnectionObserver {
|
|
public:
|
|
// `on_track_callback` will be called when any new track will be added to peer
|
|
// connection.
|
|
// `on_connected_callback` will be called when peer connection will come to
|
|
// either connected or completed state. Client should notice that in the case
|
|
// of reconnect this callback can be called again, so it should be tolerant
|
|
// to such behavior.
|
|
FixturePeerConnectionObserver(
|
|
std::function<void(rtc::scoped_refptr<RtpTransceiverInterface>)>
|
|
on_track_callback,
|
|
std::function<void()> on_connected_callback)
|
|
: on_track_callback_(std::move(on_track_callback)),
|
|
on_connected_callback_(std::move(on_connected_callback)) {}
|
|
|
|
void OnTrack(
|
|
rtc::scoped_refptr<RtpTransceiverInterface> transceiver) override {
|
|
MockPeerConnectionObserver::OnTrack(transceiver);
|
|
on_track_callback_(transceiver);
|
|
}
|
|
|
|
void OnIceConnectionChange(
|
|
PeerConnectionInterface::IceConnectionState new_state) override {
|
|
MockPeerConnectionObserver::OnIceConnectionChange(new_state);
|
|
if (ice_connected_) {
|
|
on_connected_callback_();
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::function<void(rtc::scoped_refptr<RtpTransceiverInterface>)>
|
|
on_track_callback_;
|
|
std::function<void()> on_connected_callback_;
|
|
};
|
|
|
|
void ValidateP2PSimulcastParams(
|
|
const std::vector<std::unique_ptr<PeerConfigurerImpl>>& peers) {
|
|
for (size_t i = 0; i < peers.size(); ++i) {
|
|
Params* p = peers[i]->params();
|
|
for (const VideoConfig& video_config : p->video_configs) {
|
|
if (video_config.simulcast_config) {
|
|
// When we simulate SFU we support only one video codec.
|
|
RTC_CHECK_EQ(p->video_codecs.size(), 1)
|
|
<< "Only 1 video codec is supported when simulcast is enabled in "
|
|
<< "at least 1 video config";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
PeerConnectionE2EQualityTest::PeerConnectionE2EQualityTest(
|
|
std::string test_case_name,
|
|
TimeController& time_controller,
|
|
std::unique_ptr<AudioQualityAnalyzerInterface> audio_quality_analyzer,
|
|
std::unique_ptr<VideoQualityAnalyzerInterface> video_quality_analyzer)
|
|
: time_controller_(time_controller),
|
|
task_queue_factory_(time_controller_.CreateTaskQueueFactory()),
|
|
test_case_name_(std::move(test_case_name)),
|
|
executor_(std::make_unique<TestActivitiesExecutor>(
|
|
time_controller_.GetClock())) {
|
|
// Create default video quality analyzer. We will always create an analyzer,
|
|
// even if there are no video streams, because it will be installed into video
|
|
// encoder/decoder factories.
|
|
if (video_quality_analyzer == nullptr) {
|
|
video_quality_analyzer = std::make_unique<DefaultVideoQualityAnalyzer>(
|
|
time_controller_.GetClock());
|
|
}
|
|
encoded_image_data_propagator_ =
|
|
std::make_unique<SingleProcessEncodedImageDataInjector>();
|
|
video_quality_analyzer_injection_helper_ =
|
|
std::make_unique<VideoQualityAnalyzerInjectionHelper>(
|
|
std::move(video_quality_analyzer),
|
|
encoded_image_data_propagator_.get(),
|
|
encoded_image_data_propagator_.get());
|
|
|
|
if (audio_quality_analyzer == nullptr) {
|
|
audio_quality_analyzer = std::make_unique<DefaultAudioQualityAnalyzer>();
|
|
}
|
|
audio_quality_analyzer_.swap(audio_quality_analyzer);
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::ExecuteAt(
|
|
TimeDelta target_time_since_start,
|
|
std::function<void(TimeDelta)> func) {
|
|
executor_->ScheduleActivity(target_time_since_start, absl::nullopt, func);
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::ExecuteEvery(
|
|
TimeDelta initial_delay_since_start,
|
|
TimeDelta interval,
|
|
std::function<void(TimeDelta)> func) {
|
|
executor_->ScheduleActivity(initial_delay_since_start, interval, func);
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::AddQualityMetricsReporter(
|
|
std::unique_ptr<QualityMetricsReporter> quality_metrics_reporter) {
|
|
quality_metrics_reporters_.push_back(std::move(quality_metrics_reporter));
|
|
}
|
|
|
|
PeerConnectionE2EQualityTest::PeerHandle* PeerConnectionE2EQualityTest::AddPeer(
|
|
const PeerNetworkDependencies& network_dependencies,
|
|
rtc::FunctionView<void(PeerConfigurer*)> configurer) {
|
|
peer_configurations_.push_back(std::make_unique<PeerConfigurerImpl>(
|
|
network_dependencies.network_thread, network_dependencies.network_manager,
|
|
network_dependencies.packet_socket_factory));
|
|
configurer(peer_configurations_.back().get());
|
|
peer_handles_.push_back(PeerHandleImpl());
|
|
return &peer_handles_.back();
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::Run(RunParams run_params) {
|
|
webrtc::webrtc_pc_e2e::PeerParamsPreprocessor params_preprocessor;
|
|
for (auto& peer_configuration : peer_configurations_) {
|
|
params_preprocessor.SetDefaultValuesForMissingParams(*peer_configuration);
|
|
params_preprocessor.ValidateParams(*peer_configuration);
|
|
}
|
|
ValidateP2PSimulcastParams(peer_configurations_);
|
|
RTC_CHECK_EQ(peer_configurations_.size(), 2)
|
|
<< "Only peer to peer calls are allowed, please add 2 peers";
|
|
|
|
std::unique_ptr<PeerConfigurerImpl> alice_configurer =
|
|
std::move(peer_configurations_[0]);
|
|
std::unique_ptr<PeerConfigurerImpl> bob_configurer =
|
|
std::move(peer_configurations_[1]);
|
|
peer_configurations_.clear();
|
|
|
|
for (size_t i = 0; i < bob_configurer->params()->video_configs.size(); ++i) {
|
|
// We support simulcast only from caller.
|
|
RTC_CHECK(!bob_configurer->params()->video_configs[i].simulcast_config)
|
|
<< "Only simulcast stream from first peer is supported";
|
|
}
|
|
|
|
test::ScopedFieldTrials field_trials(GetFieldTrials(run_params));
|
|
|
|
// Print test summary
|
|
RTC_LOG(LS_INFO) << "Media quality test: "
|
|
<< *alice_configurer->params()->name
|
|
<< " will make a call to " << *bob_configurer->params()->name
|
|
<< " with media video="
|
|
<< !alice_configurer->params()->video_configs.empty()
|
|
<< "; audio="
|
|
<< alice_configurer->params()->audio_config.has_value()
|
|
<< ". " << *bob_configurer->params()->name
|
|
<< " will respond with media video="
|
|
<< !bob_configurer->params()->video_configs.empty()
|
|
<< "; audio="
|
|
<< bob_configurer->params()->audio_config.has_value();
|
|
|
|
const std::unique_ptr<rtc::Thread> signaling_thread =
|
|
time_controller_.CreateThread(kSignalThreadName);
|
|
media_helper_ = std::make_unique<MediaHelper>(
|
|
video_quality_analyzer_injection_helper_.get(), task_queue_factory_.get(),
|
|
time_controller_.GetClock());
|
|
|
|
// Create a `task_queue_`.
|
|
task_queue_ = std::make_unique<webrtc::TaskQueueForTest>(
|
|
time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
|
|
"pc_e2e_quality_test", webrtc::TaskQueueFactory::Priority::NORMAL));
|
|
|
|
// Create call participants: Alice and Bob.
|
|
// Audio streams are intercepted in AudioDeviceModule, so if it is required to
|
|
// catch output of Alice's stream, Alice's output_dump_file_name should be
|
|
// passed to Bob's TestPeer setup as audio output file name.
|
|
absl::optional<RemotePeerAudioConfig> alice_remote_audio_config =
|
|
RemotePeerAudioConfig::Create(bob_configurer->params()->audio_config);
|
|
absl::optional<RemotePeerAudioConfig> bob_remote_audio_config =
|
|
RemotePeerAudioConfig::Create(alice_configurer->params()->audio_config);
|
|
// Copy Alice and Bob video configs and names to correctly pass them into
|
|
// lambdas.
|
|
std::vector<VideoConfig> alice_video_configs =
|
|
alice_configurer->params()->video_configs;
|
|
std::string alice_name = alice_configurer->params()->name.value();
|
|
std::vector<VideoConfig> bob_video_configs =
|
|
bob_configurer->params()->video_configs;
|
|
std::string bob_name = bob_configurer->params()->name.value();
|
|
|
|
TestPeerFactory test_peer_factory(
|
|
signaling_thread.get(), time_controller_,
|
|
video_quality_analyzer_injection_helper_.get(), task_queue_.get());
|
|
alice_ = test_peer_factory.CreateTestPeer(
|
|
std::move(alice_configurer),
|
|
std::make_unique<FixturePeerConnectionObserver>(
|
|
[this, bob_video_configs, alice_name](
|
|
rtc::scoped_refptr<RtpTransceiverInterface> transceiver) {
|
|
OnTrackCallback(alice_name, transceiver, bob_video_configs);
|
|
},
|
|
[this]() { StartVideo(alice_video_sources_); }),
|
|
alice_remote_audio_config, run_params.echo_emulation_config);
|
|
bob_ = test_peer_factory.CreateTestPeer(
|
|
std::move(bob_configurer),
|
|
std::make_unique<FixturePeerConnectionObserver>(
|
|
[this, alice_video_configs,
|
|
bob_name](rtc::scoped_refptr<RtpTransceiverInterface> transceiver) {
|
|
OnTrackCallback(bob_name, transceiver, alice_video_configs);
|
|
},
|
|
[this]() { StartVideo(bob_video_sources_); }),
|
|
bob_remote_audio_config, run_params.echo_emulation_config);
|
|
|
|
int num_cores = CpuInfo::DetectNumberOfCores();
|
|
RTC_DCHECK_GE(num_cores, 1);
|
|
|
|
int video_analyzer_threads =
|
|
num_cores - kPeerConnectionUsedThreads - kFrameworkUsedThreads;
|
|
if (video_analyzer_threads <= 0) {
|
|
video_analyzer_threads = 1;
|
|
}
|
|
video_analyzer_threads =
|
|
std::min(video_analyzer_threads, kMaxVideoAnalyzerThreads);
|
|
RTC_LOG(LS_INFO) << "video_analyzer_threads=" << video_analyzer_threads;
|
|
quality_metrics_reporters_.push_back(
|
|
std::make_unique<VideoQualityMetricsReporter>(
|
|
time_controller_.GetClock()));
|
|
quality_metrics_reporters_.push_back(
|
|
std::make_unique<CrossMediaMetricsReporter>());
|
|
|
|
video_quality_analyzer_injection_helper_->Start(
|
|
test_case_name_,
|
|
std::vector<std::string>{alice_->params2().name.value(),
|
|
bob_->params2().name.value()},
|
|
video_analyzer_threads);
|
|
audio_quality_analyzer_->Start(test_case_name_, &analyzer_helper_);
|
|
for (auto& reporter : quality_metrics_reporters_) {
|
|
reporter->Start(test_case_name_, &analyzer_helper_);
|
|
}
|
|
|
|
// Start RTCEventLog recording if requested.
|
|
if (alice_->params2().rtc_event_log_path) {
|
|
auto alice_rtc_event_log = std::make_unique<webrtc::RtcEventLogOutputFile>(
|
|
alice_->params2().rtc_event_log_path.value());
|
|
alice_->pc()->StartRtcEventLog(std::move(alice_rtc_event_log),
|
|
webrtc::RtcEventLog::kImmediateOutput);
|
|
}
|
|
if (bob_->params2().rtc_event_log_path) {
|
|
auto bob_rtc_event_log = std::make_unique<webrtc::RtcEventLogOutputFile>(
|
|
bob_->params2().rtc_event_log_path.value());
|
|
bob_->pc()->StartRtcEventLog(std::move(bob_rtc_event_log),
|
|
webrtc::RtcEventLog::kImmediateOutput);
|
|
}
|
|
|
|
// Setup alive logging. It is done to prevent test infra to think that test is
|
|
// dead.
|
|
RepeatingTaskHandle::DelayedStart(task_queue_->Get(),
|
|
kAliveMessageLogInterval, []() {
|
|
std::printf("Test is still running...\n");
|
|
return kAliveMessageLogInterval;
|
|
});
|
|
|
|
RTC_LOG(LS_INFO) << "Configuration is done. Now " << *alice_->params2().name
|
|
<< " is calling to " << *bob_->params2().name << "...";
|
|
|
|
// Setup stats poller.
|
|
std::vector<StatsObserverInterface*> observers = {
|
|
audio_quality_analyzer_.get(),
|
|
video_quality_analyzer_injection_helper_.get()};
|
|
for (auto& reporter : quality_metrics_reporters_) {
|
|
observers.push_back(reporter.get());
|
|
}
|
|
StatsPoller stats_poller(observers, {{*alice_->params2().name, alice_.get()},
|
|
{*bob_->params2().name, bob_.get()}});
|
|
executor_->ScheduleActivity(TimeDelta::Zero(), kStatsUpdateInterval,
|
|
[&stats_poller](TimeDelta) {
|
|
stats_poller.PollStatsAndNotifyObservers();
|
|
});
|
|
|
|
// Setup call.
|
|
signaling_thread->Invoke<void>(RTC_FROM_HERE, [this, &run_params] {
|
|
SetupCallOnSignalingThread(run_params);
|
|
});
|
|
std::unique_ptr<SignalingInterceptor> signaling_interceptor =
|
|
CreateSignalingInterceptor(run_params);
|
|
// Connect peers.
|
|
signaling_thread->Invoke<void>(RTC_FROM_HERE, [this, &signaling_interceptor] {
|
|
ExchangeOfferAnswer(signaling_interceptor.get());
|
|
});
|
|
WaitUntilIceCandidatesGathered(signaling_thread.get());
|
|
|
|
signaling_thread->Invoke<void>(RTC_FROM_HERE, [this, &signaling_interceptor] {
|
|
ExchangeIceCandidates(signaling_interceptor.get());
|
|
});
|
|
WaitUntilPeersAreConnected(signaling_thread.get());
|
|
|
|
executor_->Start(task_queue_.get());
|
|
Timestamp start_time = Now();
|
|
|
|
bool is_quick_test_enabled = field_trial::IsEnabled("WebRTC-QuickPerfTest");
|
|
if (is_quick_test_enabled) {
|
|
time_controller_.AdvanceTime(kQuickTestModeRunDuration);
|
|
} else {
|
|
time_controller_.AdvanceTime(run_params.run_duration);
|
|
}
|
|
|
|
RTC_LOG(LS_INFO) << "Test is done, initiating disconnect sequence.";
|
|
|
|
// Stop all client started tasks to prevent their access to any call related
|
|
// objects after these objects will be destroyed during call tear down.
|
|
executor_->Stop();
|
|
// There is no guarantee, that last stats collection will happen at the end
|
|
// of the call, so we force it after executor, which is among others is doing
|
|
// stats collection, was stopped.
|
|
task_queue_->SendTask(
|
|
[&stats_poller]() {
|
|
// Get final end-of-call stats.
|
|
stats_poller.PollStatsAndNotifyObservers();
|
|
},
|
|
RTC_FROM_HERE);
|
|
// We need to detach AEC dumping from peers, because dump uses `task_queue_`
|
|
// inside.
|
|
alice_->DetachAecDump();
|
|
bob_->DetachAecDump();
|
|
// Tear down the call.
|
|
signaling_thread->Invoke<void>(RTC_FROM_HERE,
|
|
[this] { TearDownCallOnSignalingThread(); });
|
|
|
|
Timestamp end_time = Now();
|
|
RTC_LOG(LS_INFO) << "All peers are disconnected.";
|
|
{
|
|
MutexLock lock(&lock_);
|
|
real_test_duration_ = end_time - start_time;
|
|
}
|
|
|
|
ReportGeneralTestResults();
|
|
audio_quality_analyzer_->Stop();
|
|
video_quality_analyzer_injection_helper_->Stop();
|
|
for (auto& reporter : quality_metrics_reporters_) {
|
|
reporter->StopAndReportResults();
|
|
}
|
|
|
|
// Reset `task_queue_` after test to cleanup.
|
|
task_queue_.reset();
|
|
|
|
alice_ = nullptr;
|
|
bob_ = nullptr;
|
|
// Ensuring that TestVideoCapturerVideoTrackSource are destroyed on the right
|
|
// thread.
|
|
RTC_CHECK(alice_video_sources_.empty());
|
|
RTC_CHECK(bob_video_sources_.empty());
|
|
}
|
|
|
|
std::string PeerConnectionE2EQualityTest::GetFieldTrials(
|
|
const RunParams& run_params) {
|
|
std::vector<absl::string_view> default_field_trials = {
|
|
kUseStandardsBytesStats};
|
|
if (run_params.enable_flex_fec_support) {
|
|
default_field_trials.push_back(kFlexFecEnabledFieldTrials);
|
|
}
|
|
rtc::StringBuilder sb;
|
|
sb << field_trial::GetFieldTrialString();
|
|
for (const absl::string_view& field_trial : default_field_trials) {
|
|
sb << field_trial;
|
|
}
|
|
return sb.Release();
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::OnTrackCallback(
|
|
absl::string_view peer_name,
|
|
rtc::scoped_refptr<RtpTransceiverInterface> transceiver,
|
|
std::vector<VideoConfig> remote_video_configs) {
|
|
const rtc::scoped_refptr<MediaStreamTrackInterface>& track =
|
|
transceiver->receiver()->track();
|
|
RTC_CHECK_EQ(transceiver->receiver()->stream_ids().size(), 2)
|
|
<< "Expected 2 stream ids: 1st - sync group, 2nd - unique stream label";
|
|
std::string sync_group = transceiver->receiver()->stream_ids()[0];
|
|
std::string stream_label = transceiver->receiver()->stream_ids()[1];
|
|
analyzer_helper_.AddTrackToStreamMapping(track->id(), stream_label,
|
|
sync_group);
|
|
if (track->kind() != MediaStreamTrackInterface::kVideoKind) {
|
|
return;
|
|
}
|
|
|
|
// It is safe to cast here, because it is checked above that
|
|
// track->kind() is kVideoKind.
|
|
auto* video_track = static_cast<VideoTrackInterface*>(track.get());
|
|
std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> video_sink =
|
|
video_quality_analyzer_injection_helper_->CreateVideoSink(peer_name);
|
|
video_track->AddOrUpdateSink(video_sink.get(), rtc::VideoSinkWants());
|
|
output_video_sinks_.push_back(std::move(video_sink));
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::SetupCallOnSignalingThread(
|
|
const RunParams& run_params) {
|
|
// We need receive-only transceivers for Bob's media stream, so there will
|
|
// be media section in SDP for that streams in Alice's offer, because it is
|
|
// forbidden to add new media sections in answer in Unified Plan.
|
|
RtpTransceiverInit receive_only_transceiver_init;
|
|
receive_only_transceiver_init.direction = RtpTransceiverDirection::kRecvOnly;
|
|
int alice_transceivers_counter = 0;
|
|
if (bob_->params2().audio_config) {
|
|
// Setup receive audio transceiver if Bob has audio to send. If we'll need
|
|
// multiple audio streams, then we need transceiver for each Bob's audio
|
|
// stream.
|
|
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
|
|
alice_->AddTransceiver(cricket::MediaType::MEDIA_TYPE_AUDIO,
|
|
receive_only_transceiver_init);
|
|
RTC_CHECK(result.ok());
|
|
alice_transceivers_counter++;
|
|
}
|
|
|
|
size_t alice_video_transceivers_non_simulcast_counter = 0;
|
|
for (auto& video_config : alice_->params2().video_configs) {
|
|
RtpTransceiverInit transceiver_params;
|
|
if (video_config.simulcast_config) {
|
|
transceiver_params.direction = RtpTransceiverDirection::kSendOnly;
|
|
// Because simulcast enabled `alice_->params2().video_codecs` has only 1
|
|
// element.
|
|
if (alice_->params2().video_codecs[0].name == cricket::kVp8CodecName) {
|
|
// For Vp8 simulcast we need to add as many RtpEncodingParameters to the
|
|
// track as many simulcast streams requested. If they specified in
|
|
// `video_config.simulcast_config` it should be copied from there.
|
|
for (int i = 0;
|
|
i < video_config.simulcast_config->simulcast_streams_count; ++i) {
|
|
RtpEncodingParameters enc_params;
|
|
if (!video_config.simulcast_config->encoding_params.empty()) {
|
|
enc_params = video_config.simulcast_config->encoding_params[i];
|
|
}
|
|
// We need to be sure, that all rids will be unique with all mids.
|
|
enc_params.rid = std::to_string(alice_transceivers_counter) + "000" +
|
|
std::to_string(i);
|
|
transceiver_params.send_encodings.push_back(enc_params);
|
|
}
|
|
}
|
|
} else {
|
|
transceiver_params.direction = RtpTransceiverDirection::kSendRecv;
|
|
RtpEncodingParameters enc_params;
|
|
enc_params.max_bitrate_bps = video_config.max_encode_bitrate_bps;
|
|
enc_params.min_bitrate_bps = video_config.min_encode_bitrate_bps;
|
|
transceiver_params.send_encodings.push_back(enc_params);
|
|
|
|
alice_video_transceivers_non_simulcast_counter++;
|
|
}
|
|
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
|
|
alice_->AddTransceiver(cricket::MediaType::MEDIA_TYPE_VIDEO,
|
|
transceiver_params);
|
|
RTC_CHECK(result.ok());
|
|
|
|
alice_transceivers_counter++;
|
|
}
|
|
|
|
// Add receive only transceivers in case Bob has more video_configs than
|
|
// Alice.
|
|
for (size_t i = alice_video_transceivers_non_simulcast_counter;
|
|
i < bob_->params2().video_configs.size(); ++i) {
|
|
RTCErrorOr<rtc::scoped_refptr<RtpTransceiverInterface>> result =
|
|
alice_->AddTransceiver(cricket::MediaType::MEDIA_TYPE_VIDEO,
|
|
receive_only_transceiver_init);
|
|
RTC_CHECK(result.ok());
|
|
alice_transceivers_counter++;
|
|
}
|
|
|
|
// Then add media for Alice and Bob
|
|
media_helper_->MaybeAddAudio(alice_.get());
|
|
alice_video_sources_ = media_helper_->MaybeAddVideo(alice_.get());
|
|
media_helper_->MaybeAddAudio(bob_.get());
|
|
bob_video_sources_ = media_helper_->MaybeAddVideo(bob_.get());
|
|
|
|
SetPeerCodecPreferences(alice_.get());
|
|
SetPeerCodecPreferences(bob_.get());
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::TearDownCallOnSignalingThread() {
|
|
TearDownCall();
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::SetPeerCodecPreferences(TestPeer* peer) {
|
|
std::vector<RtpCodecCapability> with_rtx_video_capabilities =
|
|
FilterVideoCodecCapabilities(
|
|
peer->params2().video_codecs, true, peer->params2().use_ulp_fec,
|
|
peer->params2().use_flex_fec,
|
|
peer->pc_factory()
|
|
->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO)
|
|
.codecs);
|
|
std::vector<RtpCodecCapability> without_rtx_video_capabilities =
|
|
FilterVideoCodecCapabilities(
|
|
peer->params2().video_codecs, false, peer->params2().use_ulp_fec,
|
|
peer->params2().use_flex_fec,
|
|
peer->pc_factory()
|
|
->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO)
|
|
.codecs);
|
|
|
|
// Set codecs for transceivers
|
|
for (auto transceiver : peer->pc()->GetTransceivers()) {
|
|
if (transceiver->media_type() == cricket::MediaType::MEDIA_TYPE_VIDEO) {
|
|
if (transceiver->sender()->init_send_encodings().size() > 1) {
|
|
// If transceiver's sender has more then 1 send encodings, it means it
|
|
// has multiple simulcast streams, so we need disable RTX on it.
|
|
RTCError result =
|
|
transceiver->SetCodecPreferences(without_rtx_video_capabilities);
|
|
RTC_CHECK(result.ok());
|
|
} else {
|
|
RTCError result =
|
|
transceiver->SetCodecPreferences(with_rtx_video_capabilities);
|
|
RTC_CHECK(result.ok());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<SignalingInterceptor>
|
|
PeerConnectionE2EQualityTest::CreateSignalingInterceptor(
|
|
const RunParams& run_params) {
|
|
std::map<std::string, int> stream_label_to_simulcast_streams_count;
|
|
// We add only Alice here, because simulcast/svc is supported only from the
|
|
// first peer.
|
|
for (auto& video_config : alice_->params2().video_configs) {
|
|
if (video_config.simulcast_config) {
|
|
stream_label_to_simulcast_streams_count.insert(
|
|
{*video_config.stream_label,
|
|
video_config.simulcast_config->simulcast_streams_count});
|
|
}
|
|
}
|
|
PatchingParams patching_params(run_params.use_conference_mode,
|
|
stream_label_to_simulcast_streams_count);
|
|
return std::make_unique<SignalingInterceptor>(patching_params);
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::WaitUntilIceCandidatesGathered(
|
|
rtc::Thread* signaling_thread) {
|
|
ASSERT_TRUE(time_controller_.Wait(
|
|
[&]() {
|
|
return signaling_thread->Invoke<bool>(RTC_FROM_HERE, [&]() {
|
|
return alice_->IsIceGatheringDone() && bob_->IsIceGatheringDone();
|
|
});
|
|
},
|
|
2 * kDefaultTimeout));
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::WaitUntilPeersAreConnected(
|
|
rtc::Thread* signaling_thread) {
|
|
// This means that ICE and DTLS are connected.
|
|
alice_connected_ = time_controller_.Wait(
|
|
[&]() {
|
|
return signaling_thread->Invoke<bool>(
|
|
RTC_FROM_HERE, [&]() { return alice_->IsIceConnected(); });
|
|
},
|
|
kDefaultTimeout);
|
|
bob_connected_ = time_controller_.Wait(
|
|
[&]() {
|
|
return signaling_thread->Invoke<bool>(
|
|
RTC_FROM_HERE, [&]() { return bob_->IsIceConnected(); });
|
|
},
|
|
kDefaultTimeout);
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::ExchangeOfferAnswer(
|
|
SignalingInterceptor* signaling_interceptor) {
|
|
std::string log_output;
|
|
|
|
auto offer = alice_->CreateOffer();
|
|
RTC_CHECK(offer);
|
|
offer->ToString(&log_output);
|
|
RTC_LOG(LS_INFO) << "Original offer: " << log_output;
|
|
LocalAndRemoteSdp patch_result = signaling_interceptor->PatchOffer(
|
|
std::move(offer), alice_->params2().video_codecs[0]);
|
|
patch_result.local_sdp->ToString(&log_output);
|
|
RTC_LOG(LS_INFO) << "Offer to set as local description: " << log_output;
|
|
patch_result.remote_sdp->ToString(&log_output);
|
|
RTC_LOG(LS_INFO) << "Offer to set as remote description: " << log_output;
|
|
|
|
bool set_local_offer =
|
|
alice_->SetLocalDescription(std::move(patch_result.local_sdp));
|
|
RTC_CHECK(set_local_offer);
|
|
bool set_remote_offer =
|
|
bob_->SetRemoteDescription(std::move(patch_result.remote_sdp));
|
|
RTC_CHECK(set_remote_offer);
|
|
auto answer = bob_->CreateAnswer();
|
|
RTC_CHECK(answer);
|
|
answer->ToString(&log_output);
|
|
RTC_LOG(LS_INFO) << "Original answer: " << log_output;
|
|
patch_result = signaling_interceptor->PatchAnswer(
|
|
std::move(answer), bob_->params2().video_codecs[0]);
|
|
patch_result.local_sdp->ToString(&log_output);
|
|
RTC_LOG(LS_INFO) << "Answer to set as local description: " << log_output;
|
|
patch_result.remote_sdp->ToString(&log_output);
|
|
RTC_LOG(LS_INFO) << "Answer to set as remote description: " << log_output;
|
|
|
|
bool set_local_answer =
|
|
bob_->SetLocalDescription(std::move(patch_result.local_sdp));
|
|
RTC_CHECK(set_local_answer);
|
|
bool set_remote_answer =
|
|
alice_->SetRemoteDescription(std::move(patch_result.remote_sdp));
|
|
RTC_CHECK(set_remote_answer);
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::ExchangeIceCandidates(
|
|
SignalingInterceptor* signaling_interceptor) {
|
|
// Connect an ICE candidate pairs.
|
|
std::vector<std::unique_ptr<IceCandidateInterface>> alice_candidates =
|
|
signaling_interceptor->PatchOffererIceCandidates(
|
|
alice_->observer()->GetAllCandidates());
|
|
for (auto& candidate : alice_candidates) {
|
|
std::string candidate_str;
|
|
RTC_CHECK(candidate->ToString(&candidate_str));
|
|
RTC_LOG(LS_INFO) << *alice_->params2().name
|
|
<< " ICE candidate(mid= " << candidate->sdp_mid()
|
|
<< "): " << candidate_str;
|
|
}
|
|
ASSERT_TRUE(bob_->AddIceCandidates(std::move(alice_candidates)));
|
|
std::vector<std::unique_ptr<IceCandidateInterface>> bob_candidates =
|
|
signaling_interceptor->PatchAnswererIceCandidates(
|
|
bob_->observer()->GetAllCandidates());
|
|
for (auto& candidate : bob_candidates) {
|
|
std::string candidate_str;
|
|
RTC_CHECK(candidate->ToString(&candidate_str));
|
|
RTC_LOG(LS_INFO) << *bob_->params2().name
|
|
<< " ICE candidate(mid= " << candidate->sdp_mid()
|
|
<< "): " << candidate_str;
|
|
}
|
|
ASSERT_TRUE(alice_->AddIceCandidates(std::move(bob_candidates)));
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::StartVideo(
|
|
const std::vector<rtc::scoped_refptr<TestVideoCapturerVideoTrackSource>>&
|
|
sources) {
|
|
for (auto& source : sources) {
|
|
if (source->state() != MediaSourceInterface::SourceState::kLive) {
|
|
source->Start();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::TearDownCall() {
|
|
for (const auto& video_source : alice_video_sources_) {
|
|
video_source->Stop();
|
|
}
|
|
for (const auto& video_source : bob_video_sources_) {
|
|
video_source->Stop();
|
|
}
|
|
|
|
alice_video_sources_.clear();
|
|
bob_video_sources_.clear();
|
|
|
|
alice_->Close();
|
|
bob_->Close();
|
|
|
|
media_helper_ = nullptr;
|
|
}
|
|
|
|
void PeerConnectionE2EQualityTest::ReportGeneralTestResults() {
|
|
test::PrintResult(*alice_->params2().name + "_connected", "", test_case_name_,
|
|
alice_connected_, "unitless",
|
|
/*important=*/false,
|
|
test::ImproveDirection::kBiggerIsBetter);
|
|
test::PrintResult(*bob_->params2().name + "_connected", "", test_case_name_,
|
|
bob_connected_, "unitless",
|
|
/*important=*/false,
|
|
test::ImproveDirection::kBiggerIsBetter);
|
|
}
|
|
|
|
Timestamp PeerConnectionE2EQualityTest::Now() const {
|
|
return time_controller_.GetClock()->CurrentTime();
|
|
}
|
|
|
|
} // namespace webrtc_pc_e2e
|
|
} // namespace webrtc
|