blob: 325ac3944eeac12db2cc3584079bff36f7aec50a [file] [log] [blame]
Artem Titova6a273d2019-02-07 16:43:51 +01001/*
2 * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10#include "test/pc/e2e/peer_connection_quality_test.h"
11
Mirko Bonadei71318802019-02-18 18:52:43 +010012#include <algorithm>
Artem Titova6a273d2019-02-07 16:43:51 +010013#include <set>
14#include <utility>
15
16#include "absl/memory/memory.h"
Artem Titovbf9e01a2019-02-14 10:51:27 +010017#include "api/media_stream_interface.h"
Artem Titova6a273d2019-02-07 16:43:51 +010018#include "api/peer_connection_interface.h"
Niels Möllerd8b9ed72019-05-08 13:53:51 +020019#include "api/rtc_event_log_output_file.h"
Artem Titova6a273d2019-02-07 16:43:51 +010020#include "api/scoped_refptr.h"
Danil Chapovalovd0e0ed82019-04-18 14:48:24 +020021#include "api/task_queue/default_task_queue_factory.h"
Artem Titovd57628f2019-03-22 12:34:25 +010022#include "api/test/video_quality_analyzer_interface.h"
Artem Titova6a273d2019-02-07 16:43:51 +010023#include "api/units/time_delta.h"
Mirko Bonadeice7a4fb2019-02-25 11:45:07 +010024#include "logging/rtc_event_log/rtc_event_log.h"
Artem Titovf65a89b2019-05-07 11:56:44 +020025#include "pc/sdp_utils.h"
Artem Titovbf9e01a2019-02-14 10:51:27 +010026#include "pc/test/mock_peer_connection_observers.h"
Artem Titova6a273d2019-02-07 16:43:51 +010027#include "rtc_base/bind.h"
28#include "rtc_base/gunit.h"
Mirko Bonadei71318802019-02-18 18:52:43 +010029#include "rtc_base/numerics/safe_conversions.h"
Artem Titov9f97c9a2019-02-08 00:35:13 +010030#include "system_wrappers/include/cpu_info.h"
Mirko Bonadei3830d9b2019-02-28 14:07:56 +010031#include "test/pc/e2e/analyzer/audio/default_audio_quality_analyzer.h"
Artem Titov859abef2019-03-01 11:11:09 +010032#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer.h"
Artem Titovf65a89b2019-05-07 11:56:44 +020033#include "test/pc/e2e/sdp/sdp_changer.h"
Mirko Bonadei12ae4f42019-02-26 15:19:07 +010034#include "test/pc/e2e/stats_poller.h"
Artem Titova6a273d2019-02-07 16:43:51 +010035#include "test/testsupport/file_utils.h"
36
37namespace webrtc {
Artem Titov0b443142019-03-20 11:11:08 +010038namespace webrtc_pc_e2e {
Artem Titova6a273d2019-02-07 16:43:51 +010039namespace {
40
41using VideoConfig = PeerConnectionE2EQualityTestFixture::VideoConfig;
42
43constexpr int kDefaultTimeoutMs = 10000;
44constexpr char kSignalThreadName[] = "signaling_thread";
Artem Titov9f97c9a2019-02-08 00:35:13 +010045// 1 signaling, 2 network, 2 worker and 2 extra for codecs etc.
46constexpr int kPeerConnectionUsedThreads = 7;
47// Framework has extra thread for network layer and extra thread for peer
48// connection stats polling.
49constexpr int kFrameworkUsedThreads = 2;
50constexpr int kMaxVideoAnalyzerThreads = 8;
Artem Titova6a273d2019-02-07 16:43:51 +010051
Mirko Bonadei12ae4f42019-02-26 15:19:07 +010052constexpr TimeDelta kStatsUpdateInterval = TimeDelta::Seconds<1>();
53constexpr TimeDelta kStatsPollingStopTimeout = TimeDelta::Seconds<1>();
54
Artem Titova6a273d2019-02-07 16:43:51 +010055std::string VideoConfigSourcePresenceToString(const VideoConfig& video_config) {
56 char buf[1024];
57 rtc::SimpleStringBuilder builder(buf);
58 builder << "video_config.generator=" << video_config.generator.has_value()
59 << "; video_config.input_file_name="
60 << video_config.input_file_name.has_value()
61 << "; video_config.screen_share_config="
62 << video_config.screen_share_config.has_value() << ";";
63 return builder.str();
64}
65
Artem Titovbf9e01a2019-02-14 10:51:27 +010066class FixturePeerConnectionObserver : public MockPeerConnectionObserver {
67 public:
68 // |on_track_callback| will be called when any new track will be added to peer
69 // connection.
70 // |on_connected_callback| will be called when peer connection will come to
71 // either connected or completed state. Client should notice that in the case
72 // of reconnect this callback can be called again, so it should be tolerant
73 // to such behavior.
74 FixturePeerConnectionObserver(
75 std::function<void(rtc::scoped_refptr<RtpTransceiverInterface>)>
76 on_track_callback,
77 std::function<void()> on_connected_callback)
78 : on_track_callback_(std::move(on_track_callback)),
79 on_connected_callback_(std::move(on_connected_callback)) {}
80
81 void OnTrack(
82 rtc::scoped_refptr<RtpTransceiverInterface> transceiver) override {
83 MockPeerConnectionObserver::OnTrack(transceiver);
84 on_track_callback_(transceiver);
85 }
86
87 void OnIceConnectionChange(
88 PeerConnectionInterface::IceConnectionState new_state) override {
89 MockPeerConnectionObserver::OnIceConnectionChange(new_state);
90 if (ice_connected_) {
91 on_connected_callback_();
92 }
93 }
94
95 private:
96 std::function<void(rtc::scoped_refptr<RtpTransceiverInterface>)>
97 on_track_callback_;
98 std::function<void()> on_connected_callback_;
99};
100
Artem Titova6a273d2019-02-07 16:43:51 +0100101} // namespace
102
103PeerConnectionE2EQualityTest::PeerConnectionE2EQualityTest(
Artem Titov59835852019-02-27 17:44:13 +0100104 std::string test_case_name,
Mirko Bonadeif5d88082019-02-20 16:16:54 +0100105 std::unique_ptr<AudioQualityAnalyzerInterface> audio_quality_analyzer,
106 std::unique_ptr<VideoQualityAnalyzerInterface> video_quality_analyzer)
Artem Titov59835852019-02-27 17:44:13 +0100107 : clock_(Clock::GetRealTimeClock()),
Danil Chapovalovd0e0ed82019-04-18 14:48:24 +0200108 task_queue_factory_(CreateDefaultTaskQueueFactory()),
Artem Titovba82e002019-03-15 15:57:53 +0100109 test_case_name_(std::move(test_case_name)) {
Artem Titova6a273d2019-02-07 16:43:51 +0100110 // Create default video quality analyzer. We will always create an analyzer,
111 // even if there are no video streams, because it will be installed into video
112 // encoder/decoder factories.
Mirko Bonadeif5d88082019-02-20 16:16:54 +0100113 if (video_quality_analyzer == nullptr) {
Artem Titov859abef2019-03-01 11:11:09 +0100114 video_quality_analyzer = absl::make_unique<DefaultVideoQualityAnalyzer>();
Artem Titova6a273d2019-02-07 16:43:51 +0100115 }
116 encoded_image_id_controller_ =
Artem Titov32232e92019-02-20 21:13:14 +0100117 absl::make_unique<SingleProcessEncodedImageDataInjector>();
Artem Titova6a273d2019-02-07 16:43:51 +0100118 video_quality_analyzer_injection_helper_ =
119 absl::make_unique<VideoQualityAnalyzerInjectionHelper>(
Mirko Bonadeif5d88082019-02-20 16:16:54 +0100120 std::move(video_quality_analyzer), encoded_image_id_controller_.get(),
Artem Titova6a273d2019-02-07 16:43:51 +0100121 encoded_image_id_controller_.get());
Mirko Bonadei12ae4f42019-02-26 15:19:07 +0100122
123 if (audio_quality_analyzer == nullptr) {
124 audio_quality_analyzer = absl::make_unique<DefaultAudioQualityAnalyzer>();
125 }
126 audio_quality_analyzer_.swap(audio_quality_analyzer);
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100127}
128
Artem Titovba82e002019-03-15 15:57:53 +0100129void PeerConnectionE2EQualityTest::ExecuteAt(
130 TimeDelta target_time_since_start,
131 std::function<void(TimeDelta)> func) {
132 ExecuteTask(target_time_since_start, absl::nullopt, func);
133}
134
135void PeerConnectionE2EQualityTest::ExecuteEvery(
136 TimeDelta initial_delay_since_start,
137 TimeDelta interval,
138 std::function<void(TimeDelta)> func) {
139 ExecuteTask(initial_delay_since_start, interval, func);
140}
141
142void PeerConnectionE2EQualityTest::ExecuteTask(
143 TimeDelta initial_delay_since_start,
144 absl::optional<TimeDelta> interval,
145 std::function<void(TimeDelta)> func) {
146 RTC_CHECK(initial_delay_since_start.IsFinite() &&
147 initial_delay_since_start >= TimeDelta::Zero());
148 RTC_CHECK(!interval ||
149 (interval->IsFinite() && *interval > TimeDelta::Zero()));
150 rtc::CritScope crit(&lock_);
151 ScheduledActivity activity(initial_delay_since_start, interval, func);
152 if (start_time_.IsInfinite()) {
153 scheduled_activities_.push(std::move(activity));
154 } else {
155 PostTask(std::move(activity));
156 }
157}
158
159void PeerConnectionE2EQualityTest::PostTask(ScheduledActivity activity) {
160 // Because start_time_ will never change at this point copy it to local
161 // variable to capture in in lambda without requirement to hold a lock.
162 Timestamp start_time = start_time_;
163
164 TimeDelta remaining_delay =
165 activity.initial_delay_since_start == TimeDelta::Zero()
166 ? TimeDelta::Zero()
167 : activity.initial_delay_since_start - (Now() - start_time_);
168 if (remaining_delay < TimeDelta::Zero()) {
169 RTC_LOG(WARNING) << "Executing late task immediately, late by="
170 << ToString(remaining_delay.Abs());
171 remaining_delay = TimeDelta::Zero();
172 }
173
174 if (activity.interval) {
175 if (remaining_delay == TimeDelta::Zero()) {
176 repeating_task_handles_.push_back(RepeatingTaskHandle::Start(
177 task_queue_->Get(), [activity, start_time, this]() {
178 activity.func(Now() - start_time);
179 return *activity.interval;
180 }));
181 return;
182 }
183 repeating_task_handles_.push_back(RepeatingTaskHandle::DelayedStart(
184 task_queue_->Get(), remaining_delay, [activity, start_time, this]() {
185 activity.func(Now() - start_time);
186 return *activity.interval;
187 }));
188 return;
189 }
190
191 if (remaining_delay == TimeDelta::Zero()) {
192 task_queue_->PostTask(
193 [activity, start_time, this]() { activity.func(Now() - start_time); });
194 return;
195 }
196
197 task_queue_->PostDelayedTask(
198 [activity, start_time, this]() { activity.func(Now() - start_time); },
199 remaining_delay.ms());
200}
201
Artem Titov18459222019-04-24 11:09:35 +0200202void PeerConnectionE2EQualityTest::AddQualityMetricsReporter(
203 std::unique_ptr<QualityMetricsReporter> quality_metrics_reporter) {
204 quality_metrics_reporters_.push_back(std::move(quality_metrics_reporter));
205}
206
Artem Titovd09bc552019-03-20 11:18:58 +0100207void PeerConnectionE2EQualityTest::AddPeer(
208 rtc::Thread* network_thread,
209 rtc::NetworkManager* network_manager,
210 rtc::FunctionView<void(PeerConfigurer*)> configurer) {
211 peer_configurations_.push_back(
212 absl::make_unique<PeerConfigurerImpl>(network_thread, network_manager));
213 configurer(peer_configurations_.back().get());
214}
215
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100216void PeerConnectionE2EQualityTest::Run(
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100217 RunParams run_params) {
Artem Titovd09bc552019-03-20 11:18:58 +0100218 RTC_CHECK_EQ(peer_configurations_.size(), 2)
219 << "Only peer to peer calls are allowed, please add 2 peers";
220
221 std::unique_ptr<Params> alice_params =
222 peer_configurations_[0]->ReleaseParams();
223 std::unique_ptr<InjectableComponents> alice_components =
224 peer_configurations_[0]->ReleaseComponents();
225 std::unique_ptr<Params> bob_params = peer_configurations_[1]->ReleaseParams();
226 std::unique_ptr<InjectableComponents> bob_components =
227 peer_configurations_[1]->ReleaseComponents();
228 peer_configurations_.clear();
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100229
Artem Titov9a7e7212019-02-28 16:34:17 +0100230 SetDefaultValuesForMissingParams({alice_params.get(), bob_params.get()});
Artem Titovade945d2019-04-02 18:31:48 +0200231 ValidateParams(run_params, {alice_params.get(), bob_params.get()});
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100232
233 // Print test summary
234 RTC_LOG(INFO)
235 << "Media quality test: Alice will make a call to Bob with media video="
236 << !alice_params->video_configs.empty()
237 << "; audio=" << alice_params->audio_config.has_value()
238 << ". Bob will respond with media video="
239 << !bob_params->video_configs.empty()
240 << "; audio=" << bob_params->audio_config.has_value();
241
242 const std::unique_ptr<rtc::Thread> signaling_thread = rtc::Thread::Create();
243 signaling_thread->SetName(kSignalThreadName, nullptr);
244 signaling_thread->Start();
Artem Titova6a273d2019-02-07 16:43:51 +0100245
Artem Titov70f80e52019-04-12 13:13:39 +0200246 // Create a |task_queue_|.
247 task_queue_ = absl::make_unique<TaskQueueForTest>("pc_e2e_quality_test");
248
Artem Titova6a273d2019-02-07 16:43:51 +0100249 // Create call participants: Alice and Bob.
250 // Audio streams are intercepted in AudioDeviceModule, so if it is required to
251 // catch output of Alice's stream, Alice's output_dump_file_name should be
252 // passed to Bob's TestPeer setup as audio output file name.
253 absl::optional<std::string> alice_audio_output_dump_file_name =
254 bob_params->audio_config ? bob_params->audio_config->output_dump_file_name
255 : absl::nullopt;
256 absl::optional<std::string> bob_audio_output_dump_file_name =
257 alice_params->audio_config
258 ? alice_params->audio_config->output_dump_file_name
259 : absl::nullopt;
Artem Titovbf9e01a2019-02-14 10:51:27 +0100260 // Copy Alice and Bob video configs to correctly pass them into lambdas.
261 std::vector<VideoConfig> alice_video_configs = alice_params->video_configs;
262 std::vector<VideoConfig> bob_video_configs = bob_params->video_configs;
263
Artem Titova6a273d2019-02-07 16:43:51 +0100264 alice_ = TestPeer::CreateTestPeer(
265 std::move(alice_components), std::move(alice_params),
Artem Titovbf9e01a2019-02-14 10:51:27 +0100266 absl::make_unique<FixturePeerConnectionObserver>(
267 [this, bob_video_configs](
268 rtc::scoped_refptr<RtpTransceiverInterface> transceiver) {
Mirko Bonadeif948eb62019-04-05 15:13:23 +0200269 OnTrackCallback(transceiver, bob_video_configs);
Artem Titovbf9e01a2019-02-14 10:51:27 +0100270 },
271 [this]() { StartVideo(alice_video_sources_); }),
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100272 video_quality_analyzer_injection_helper_.get(), signaling_thread.get(),
Artem Titovade945d2019-04-02 18:31:48 +0200273 alice_audio_output_dump_file_name,
Artem Titov70f80e52019-04-12 13:13:39 +0200274 run_params.video_encoder_bitrate_multiplier, task_queue_.get());
Artem Titova6a273d2019-02-07 16:43:51 +0100275 bob_ = TestPeer::CreateTestPeer(
276 std::move(bob_components), std::move(bob_params),
Artem Titovbf9e01a2019-02-14 10:51:27 +0100277 absl::make_unique<FixturePeerConnectionObserver>(
278 [this, alice_video_configs](
279 rtc::scoped_refptr<RtpTransceiverInterface> transceiver) {
Mirko Bonadeif948eb62019-04-05 15:13:23 +0200280 OnTrackCallback(transceiver, alice_video_configs);
Artem Titovbf9e01a2019-02-14 10:51:27 +0100281 },
282 [this]() { StartVideo(bob_video_sources_); }),
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100283 video_quality_analyzer_injection_helper_.get(), signaling_thread.get(),
Artem Titovade945d2019-04-02 18:31:48 +0200284 bob_audio_output_dump_file_name,
Artem Titov70f80e52019-04-12 13:13:39 +0200285 run_params.video_encoder_bitrate_multiplier, task_queue_.get());
Artem Titov9f97c9a2019-02-08 00:35:13 +0100286
287 int num_cores = CpuInfo::DetectNumberOfCores();
288 RTC_DCHECK_GE(num_cores, 1);
289
290 int video_analyzer_threads =
291 num_cores - kPeerConnectionUsedThreads - kFrameworkUsedThreads;
292 if (video_analyzer_threads <= 0) {
293 video_analyzer_threads = 1;
294 }
295 video_analyzer_threads =
296 std::min(video_analyzer_threads, kMaxVideoAnalyzerThreads);
297 RTC_LOG(INFO) << "video_analyzer_threads=" << video_analyzer_threads;
298
Artem Titov59835852019-02-27 17:44:13 +0100299 video_quality_analyzer_injection_helper_->Start(test_case_name_,
300 video_analyzer_threads);
Mirko Bonadeif948eb62019-04-05 15:13:23 +0200301 audio_quality_analyzer_->Start(test_case_name_, &analyzer_helper_);
Artem Titov18459222019-04-24 11:09:35 +0200302 for (auto& reporter : quality_metrics_reporters_) {
303 reporter->Start(test_case_name_);
304 }
Mirko Bonadeice7a4fb2019-02-25 11:45:07 +0100305
306 // Start RTCEventLog recording if requested.
307 if (alice_->params()->rtc_event_log_path) {
308 auto alice_rtc_event_log = absl::make_unique<webrtc::RtcEventLogOutputFile>(
309 alice_->params()->rtc_event_log_path.value());
310 alice_->pc()->StartRtcEventLog(std::move(alice_rtc_event_log),
311 webrtc::RtcEventLog::kImmediateOutput);
312 }
313 if (bob_->params()->rtc_event_log_path) {
314 auto bob_rtc_event_log = absl::make_unique<webrtc::RtcEventLogOutputFile>(
315 bob_->params()->rtc_event_log_path.value());
316 bob_->pc()->StartRtcEventLog(std::move(bob_rtc_event_log),
317 webrtc::RtcEventLog::kImmediateOutput);
318 }
319
Artem Titovba82e002019-03-15 15:57:53 +0100320 // Setup call.
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100321 signaling_thread->Invoke<void>(
Artem Titova6a273d2019-02-07 16:43:51 +0100322 RTC_FROM_HERE,
Artem Titovf65a89b2019-05-07 11:56:44 +0200323 rtc::Bind(&PeerConnectionE2EQualityTest::SetupCallOnSignalingThread, this,
324 run_params));
Artem Titovba82e002019-03-15 15:57:53 +0100325 {
326 rtc::CritScope crit(&lock_);
327 start_time_ = Now();
328 while (!scheduled_activities_.empty()) {
329 PostTask(std::move(scheduled_activities_.front()));
330 scheduled_activities_.pop();
331 }
332 }
Mirko Bonadei71318802019-02-18 18:52:43 +0100333
Mirko Bonadei12ae4f42019-02-26 15:19:07 +0100334 StatsPoller stats_poller({audio_quality_analyzer_.get(),
335 video_quality_analyzer_injection_helper_.get()},
336 {{"alice", alice_.get()}, {"bob", bob_.get()}});
337
Artem Titovba82e002019-03-15 15:57:53 +0100338 task_queue_->PostTask([&stats_poller, this]() {
339 RTC_DCHECK_RUN_ON(task_queue_.get());
Sebastian Janssoncda86dd2019-03-11 17:26:36 +0100340 stats_polling_task_ =
Artem Titovba82e002019-03-15 15:57:53 +0100341 RepeatingTaskHandle::Start(task_queue_->Get(), [this, &stats_poller]() {
342 RTC_DCHECK_RUN_ON(task_queue_.get());
Sebastian Janssoncda86dd2019-03-11 17:26:36 +0100343 stats_poller.PollStatsAndNotifyObservers();
344 return kStatsUpdateInterval;
345 });
Mirko Bonadei12ae4f42019-02-26 15:19:07 +0100346 });
347
Mirko Bonadei71318802019-02-18 18:52:43 +0100348 rtc::Event done;
Mirko Bonadei12ae4f42019-02-26 15:19:07 +0100349 done.Wait(run_params.run_duration.ms());
350
351 rtc::Event stats_polling_stopped;
Artem Titovba82e002019-03-15 15:57:53 +0100352 task_queue_->PostTask([&stats_polling_stopped, this]() {
353 RTC_DCHECK_RUN_ON(task_queue_.get());
Mirko Bonadei12ae4f42019-02-26 15:19:07 +0100354 stats_polling_task_.Stop();
355 stats_polling_stopped.Set();
356 });
357 bool no_timeout = stats_polling_stopped.Wait(kStatsPollingStopTimeout.ms());
358 RTC_CHECK(no_timeout) << "Failed to stop Stats polling after "
359 << kStatsPollingStopTimeout.seconds() << " seconds.";
Mirko Bonadei71318802019-02-18 18:52:43 +0100360
Artem Titov70f80e52019-04-12 13:13:39 +0200361 // We need to detach AEC dumping from peers, because dump uses |task_queue_|
362 // inside.
363 alice_->DetachAecDump();
364 bob_->DetachAecDump();
Artem Titovba82e002019-03-15 15:57:53 +0100365 // Destroy |task_queue_|. It is done to stop all running tasks and prevent
366 // their access to any call related objects after these objects will be
367 // destroyed during call tear down.
368 task_queue_.reset();
369 // Tear down the call.
Mirko Bonadei71318802019-02-18 18:52:43 +0100370 signaling_thread->Invoke<void>(
371 RTC_FROM_HERE,
372 rtc::Bind(&PeerConnectionE2EQualityTest::TearDownCallOnSignalingThread,
373 this));
Artem Titovb93c4e62019-05-02 10:52:07 +0200374 Timestamp end_time = Now();
375 {
376 rtc::CritScope crit(&lock_);
377 real_test_duration_ = end_time - start_time_;
378 }
Mirko Bonadei71318802019-02-18 18:52:43 +0100379
Mirko Bonadeif948eb62019-04-05 15:13:23 +0200380 audio_quality_analyzer_->Stop();
Artem Titov9f97c9a2019-02-08 00:35:13 +0100381 video_quality_analyzer_injection_helper_->Stop();
Artem Titov18459222019-04-24 11:09:35 +0200382 for (auto& reporter : quality_metrics_reporters_) {
383 reporter->StopAndReportResults();
384 }
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100385
386 // Ensuring that TestPeers have been destroyed in order to correctly close
387 // Audio dumps.
388 RTC_CHECK(!alice_);
389 RTC_CHECK(!bob_);
390 // Ensuring that FrameGeneratorCapturerVideoTrackSource and VideoFrameWriter
391 // are destroyed on the right thread.
Artem Titovbf9e01a2019-02-14 10:51:27 +0100392 RTC_CHECK(alice_video_sources_.empty());
393 RTC_CHECK(bob_video_sources_.empty());
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100394 RTC_CHECK(video_writers_.empty());
Artem Titova6a273d2019-02-07 16:43:51 +0100395}
396
Artem Titov9a7e7212019-02-28 16:34:17 +0100397void PeerConnectionE2EQualityTest::SetDefaultValuesForMissingParams(
Artem Titova6a273d2019-02-07 16:43:51 +0100398 std::vector<Params*> params) {
Artem Titov3481db22019-02-28 13:13:15 +0100399 int video_counter = 0;
400 int audio_counter = 0;
Artem Titova6a273d2019-02-07 16:43:51 +0100401 std::set<std::string> video_labels;
Artem Titov3481db22019-02-28 13:13:15 +0100402 std::set<std::string> audio_labels;
Artem Titova6a273d2019-02-07 16:43:51 +0100403 for (auto* p : params) {
404 for (auto& video_config : p->video_configs) {
Artem Titov9a7e7212019-02-28 16:34:17 +0100405 if (!video_config.generator && !video_config.input_file_name &&
406 !video_config.screen_share_config) {
407 video_config.generator = VideoGeneratorType::kDefault;
408 }
Artem Titova6a273d2019-02-07 16:43:51 +0100409 if (!video_config.stream_label) {
410 std::string label;
411 do {
Artem Titov3481db22019-02-28 13:13:15 +0100412 label = "_auto_video_stream_label_" + std::to_string(video_counter);
413 ++video_counter;
Artem Titova6a273d2019-02-07 16:43:51 +0100414 } while (!video_labels.insert(label).second);
415 video_config.stream_label = label;
416 }
417 }
Artem Titov3481db22019-02-28 13:13:15 +0100418 if (p->audio_config) {
419 if (!p->audio_config->stream_label) {
420 std::string label;
421 do {
422 label = "_auto_audio_stream_label_" + std::to_string(audio_counter);
423 ++audio_counter;
424 } while (!audio_labels.insert(label).second);
425 p->audio_config->stream_label = label;
426 }
427 }
Artem Titova6a273d2019-02-07 16:43:51 +0100428 }
429}
430
Artem Titovade945d2019-04-02 18:31:48 +0200431void PeerConnectionE2EQualityTest::ValidateParams(const RunParams& run_params,
432 std::vector<Params*> params) {
433 RTC_CHECK_GT(run_params.video_encoder_bitrate_multiplier, 0.0);
434
Artem Titova6a273d2019-02-07 16:43:51 +0100435 std::set<std::string> video_labels;
Artem Titov3481db22019-02-28 13:13:15 +0100436 std::set<std::string> audio_labels;
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100437 int media_streams_count = 0;
438
Artem Titova6a273d2019-02-07 16:43:51 +0100439 for (Params* p : params) {
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100440 if (p->audio_config) {
441 media_streams_count++;
442 }
443 media_streams_count += p->video_configs.size();
444
Artem Titova6a273d2019-02-07 16:43:51 +0100445 // Validate that each video config has exactly one of |generator|,
446 // |input_file_name| or |screen_share_config| set. Also validate that all
447 // video stream labels are unique.
448 for (auto& video_config : p->video_configs) {
449 RTC_CHECK(video_config.stream_label);
450 bool inserted =
451 video_labels.insert(video_config.stream_label.value()).second;
452 RTC_CHECK(inserted) << "Duplicate video_config.stream_label="
453 << video_config.stream_label.value();
454 RTC_CHECK(video_config.generator || video_config.input_file_name ||
455 video_config.screen_share_config)
456 << VideoConfigSourcePresenceToString(video_config);
457 RTC_CHECK(!(video_config.input_file_name && video_config.generator))
458 << VideoConfigSourcePresenceToString(video_config);
459 RTC_CHECK(
460 !(video_config.input_file_name && video_config.screen_share_config))
461 << VideoConfigSourcePresenceToString(video_config);
462 RTC_CHECK(!(video_config.screen_share_config && video_config.generator))
463 << VideoConfigSourcePresenceToString(video_config);
464 }
465 if (p->audio_config) {
Artem Titov3481db22019-02-28 13:13:15 +0100466 bool inserted =
467 audio_labels.insert(p->audio_config->stream_label.value()).second;
468 RTC_CHECK(inserted) << "Duplicate audio_config.stream_label="
469 << p->audio_config->stream_label.value();
Artem Titova6a273d2019-02-07 16:43:51 +0100470 // Check that if mode input file name specified only if mode is kFile.
471 if (p->audio_config.value().mode == AudioConfig::Mode::kGenerated) {
472 RTC_CHECK(!p->audio_config.value().input_file_name);
473 }
474 if (p->audio_config.value().mode == AudioConfig::Mode::kFile) {
475 RTC_CHECK(p->audio_config.value().input_file_name);
Artem Titov0b443142019-03-20 11:11:08 +0100476 RTC_CHECK(
Artem Titov70f80e52019-04-12 13:13:39 +0200477 test::FileExists(p->audio_config.value().input_file_name.value()))
478 << p->audio_config.value().input_file_name.value()
479 << " doesn't exist";
Artem Titova6a273d2019-02-07 16:43:51 +0100480 }
481 }
482 }
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100483
484 RTC_CHECK_GT(media_streams_count, 0) << "No media in the call.";
Artem Titova6a273d2019-02-07 16:43:51 +0100485}
486
Mirko Bonadeif948eb62019-04-05 15:13:23 +0200487void PeerConnectionE2EQualityTest::OnTrackCallback(
Artem Titovbf9e01a2019-02-14 10:51:27 +0100488 rtc::scoped_refptr<RtpTransceiverInterface> transceiver,
489 std::vector<VideoConfig> remote_video_configs) {
490 const rtc::scoped_refptr<MediaStreamTrackInterface>& track =
491 transceiver->receiver()->track();
Mirko Bonadeif948eb62019-04-05 15:13:23 +0200492 RTC_CHECK_EQ(transceiver->receiver()->stream_ids().size(), 1);
493 std::string stream_label = transceiver->receiver()->stream_ids().front();
494 analyzer_helper_.AddTrackToStreamMapping(track->id(), stream_label);
Artem Titovbf9e01a2019-02-14 10:51:27 +0100495 if (track->kind() != MediaStreamTrackInterface::kVideoKind) {
496 return;
497 }
498
499 VideoConfig* video_config = nullptr;
500 for (auto& config : remote_video_configs) {
Artem Titov7c554152019-02-28 10:25:52 +0100501 if (config.stream_label == stream_label) {
Artem Titovbf9e01a2019-02-14 10:51:27 +0100502 video_config = &config;
503 break;
504 }
505 }
506 RTC_CHECK(video_config);
Artem Titov0b443142019-03-20 11:11:08 +0100507 test::VideoFrameWriter* writer = MaybeCreateVideoWriter(
Artem Titovbf9e01a2019-02-14 10:51:27 +0100508 video_config->output_dump_file_name, *video_config);
509 // It is safe to cast here, because it is checked above that
510 // track->kind() is kVideoKind.
511 auto* video_track = static_cast<VideoTrackInterface*>(track.get());
512 std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> video_sink =
513 video_quality_analyzer_injection_helper_->CreateVideoSink(writer);
514 video_track->AddOrUpdateSink(video_sink.get(), rtc::VideoSinkWants());
515 output_video_sinks_.push_back(std::move(video_sink));
516}
517
Artem Titovf65a89b2019-05-07 11:56:44 +0200518void PeerConnectionE2EQualityTest::SetupCallOnSignalingThread(
519 const RunParams& run_params) {
Artem Titov7c554152019-02-28 10:25:52 +0100520 // We need receive-only transceivers for Bob's media stream, so there will
521 // be media section in SDP for that streams in Alice's offer, because it is
522 // forbidden to add new media sections in answer in Unified Plan.
523 RtpTransceiverInit receive_only_transceiver_init;
524 receive_only_transceiver_init.direction = RtpTransceiverDirection::kRecvOnly;
525 if (bob_->params()->audio_config) {
526 // Setup receive audio transceiver if Bob has audio to send. If we'll need
527 // multiple audio streams, then we need transceiver for each Bob's audio
528 // stream.
529 alice_->AddTransceiver(cricket::MediaType::MEDIA_TYPE_AUDIO,
530 receive_only_transceiver_init);
531 }
532 for (size_t i = 0; i < bob_->params()->video_configs.size(); ++i) {
533 alice_->AddTransceiver(cricket::MediaType::MEDIA_TYPE_VIDEO,
534 receive_only_transceiver_init);
535 }
536 // Then add media for Alice and Bob
537 alice_video_sources_ = MaybeAddMedia(alice_.get());
538 bob_video_sources_ = MaybeAddMedia(bob_.get());
Artem Titova6a273d2019-02-07 16:43:51 +0100539
Artem Titovf65a89b2019-05-07 11:56:44 +0200540 SetPeerCodecPreferences(alice_.get(), run_params);
541 SetPeerCodecPreferences(bob_.get(), run_params);
542
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100543 SetupCall();
Mirko Bonadei71318802019-02-18 18:52:43 +0100544}
Artem Titova6a273d2019-02-07 16:43:51 +0100545
Mirko Bonadei71318802019-02-18 18:52:43 +0100546void PeerConnectionE2EQualityTest::TearDownCallOnSignalingThread() {
Artem Titova6a273d2019-02-07 16:43:51 +0100547 TearDownCall();
Artem Titova6a273d2019-02-07 16:43:51 +0100548}
549
Artem Titovbf9e01a2019-02-14 10:51:27 +0100550std::vector<rtc::scoped_refptr<FrameGeneratorCapturerVideoTrackSource>>
Artem Titov7c554152019-02-28 10:25:52 +0100551PeerConnectionE2EQualityTest::MaybeAddMedia(TestPeer* peer) {
552 MaybeAddAudio(peer);
553 return MaybeAddVideo(peer);
Artem Titova6a273d2019-02-07 16:43:51 +0100554}
555
Artem Titovbf9e01a2019-02-14 10:51:27 +0100556std::vector<rtc::scoped_refptr<FrameGeneratorCapturerVideoTrackSource>>
Artem Titov7c554152019-02-28 10:25:52 +0100557PeerConnectionE2EQualityTest::MaybeAddVideo(TestPeer* peer) {
Artem Titova6a273d2019-02-07 16:43:51 +0100558 // Params here valid because of pre-run validation.
559 Params* params = peer->params();
Artem Titovbf9e01a2019-02-14 10:51:27 +0100560 std::vector<rtc::scoped_refptr<FrameGeneratorCapturerVideoTrackSource>> out;
Artem Titova6a273d2019-02-07 16:43:51 +0100561 for (auto video_config : params->video_configs) {
562 // Create video generator.
Artem Titov0b443142019-03-20 11:11:08 +0100563 std::unique_ptr<test::FrameGenerator> frame_generator =
Artem Titova6a273d2019-02-07 16:43:51 +0100564 CreateFrameGenerator(video_config);
565
566 // Wrap it to inject video quality analyzer and enable dump of input video
567 // if required.
Artem Titov0b443142019-03-20 11:11:08 +0100568 test::VideoFrameWriter* writer =
Artem Titova6a273d2019-02-07 16:43:51 +0100569 MaybeCreateVideoWriter(video_config.input_dump_file_name, video_config);
570 frame_generator =
571 video_quality_analyzer_injection_helper_->WrapFrameGenerator(
572 video_config.stream_label.value(), std::move(frame_generator),
573 writer);
574
575 // Setup FrameGenerator into peer connection.
Danil Chapovalovd0e0ed82019-04-18 14:48:24 +0200576 auto capturer = absl::make_unique<test::FrameGeneratorCapturer>(
577 clock_, std::move(frame_generator), video_config.fps,
578 *task_queue_factory_);
579 capturer->Init();
Artem Titova6a273d2019-02-07 16:43:51 +0100580 rtc::scoped_refptr<FrameGeneratorCapturerVideoTrackSource> source =
581 new rtc::RefCountedObject<FrameGeneratorCapturerVideoTrackSource>(
Danil Chapovalovd0e0ed82019-04-18 14:48:24 +0200582 std::move(capturer));
Artem Titovbf9e01a2019-02-14 10:51:27 +0100583 out.push_back(source);
Artem Titova6a273d2019-02-07 16:43:51 +0100584 RTC_LOG(INFO) << "Adding video with video_config.stream_label="
585 << video_config.stream_label.value();
586 rtc::scoped_refptr<VideoTrackInterface> track =
587 peer->pc_factory()->CreateVideoTrack(video_config.stream_label.value(),
588 source);
Artem Titov7c554152019-02-28 10:25:52 +0100589 peer->AddTrack(track, {video_config.stream_label.value()});
Artem Titova6a273d2019-02-07 16:43:51 +0100590 }
Artem Titovbf9e01a2019-02-14 10:51:27 +0100591 return out;
Artem Titova6a273d2019-02-07 16:43:51 +0100592}
593
Artem Titov0b443142019-03-20 11:11:08 +0100594std::unique_ptr<test::FrameGenerator>
Artem Titova6a273d2019-02-07 16:43:51 +0100595PeerConnectionE2EQualityTest::CreateFrameGenerator(
596 const VideoConfig& video_config) {
597 if (video_config.generator) {
Artem Titov0b443142019-03-20 11:11:08 +0100598 absl::optional<test::FrameGenerator::OutputType> frame_generator_type =
Artem Titova6a273d2019-02-07 16:43:51 +0100599 absl::nullopt;
600 if (video_config.generator == VideoGeneratorType::kDefault) {
Artem Titov0b443142019-03-20 11:11:08 +0100601 frame_generator_type = test::FrameGenerator::OutputType::I420;
Artem Titova6a273d2019-02-07 16:43:51 +0100602 } else if (video_config.generator == VideoGeneratorType::kI420A) {
Artem Titov0b443142019-03-20 11:11:08 +0100603 frame_generator_type = test::FrameGenerator::OutputType::I420A;
Artem Titova6a273d2019-02-07 16:43:51 +0100604 } else if (video_config.generator == VideoGeneratorType::kI010) {
Artem Titov0b443142019-03-20 11:11:08 +0100605 frame_generator_type = test::FrameGenerator::OutputType::I010;
Artem Titova6a273d2019-02-07 16:43:51 +0100606 }
Artem Titov0b443142019-03-20 11:11:08 +0100607 return test::FrameGenerator::CreateSquareGenerator(
Artem Titova6a273d2019-02-07 16:43:51 +0100608 static_cast<int>(video_config.width),
609 static_cast<int>(video_config.height), frame_generator_type,
610 absl::nullopt);
611 }
612 if (video_config.input_file_name) {
Artem Titov0b443142019-03-20 11:11:08 +0100613 return test::FrameGenerator::CreateFromYuvFile(
Artem Titova6a273d2019-02-07 16:43:51 +0100614 std::vector<std::string>(/*count=*/1,
615 video_config.input_file_name.value()),
616 video_config.width, video_config.height, /*frame_repeat_count=*/1);
617 }
618 if (video_config.screen_share_config) {
619 // TODO(titovartem) implement screen share support
620 // (http://bugs.webrtc.org/10138)
621 RTC_NOTREACHED() << "Screen share is not implemented";
622 return nullptr;
623 }
624 RTC_NOTREACHED() << "Unsupported video_config input source";
625 return nullptr;
626}
627
Artem Titov7c554152019-02-28 10:25:52 +0100628void PeerConnectionE2EQualityTest::MaybeAddAudio(TestPeer* peer) {
629 if (!peer->params()->audio_config) {
630 return;
631 }
Artem Titov3481db22019-02-28 13:13:15 +0100632 const AudioConfig& audio_config = peer->params()->audio_config.value();
Artem Titova6a273d2019-02-07 16:43:51 +0100633 rtc::scoped_refptr<webrtc::AudioSourceInterface> source =
Artem Titov3481db22019-02-28 13:13:15 +0100634 peer->pc_factory()->CreateAudioSource(audio_config.audio_options);
Artem Titova6a273d2019-02-07 16:43:51 +0100635 rtc::scoped_refptr<AudioTrackInterface> track =
Artem Titov3481db22019-02-28 13:13:15 +0100636 peer->pc_factory()->CreateAudioTrack(*audio_config.stream_label, source);
637 peer->AddTrack(track, {*audio_config.stream_label});
Artem Titova6a273d2019-02-07 16:43:51 +0100638}
639
Artem Titovf65a89b2019-05-07 11:56:44 +0200640void PeerConnectionE2EQualityTest::SetPeerCodecPreferences(
641 TestPeer* peer,
642 const RunParams& run_params) {
643 std::vector<RtpCodecCapability> video_capabilities = FilterCodecCapabilities(
644 run_params.video_codec_name, run_params.video_codec_required_params,
645 run_params.use_ulp_fec, run_params.use_flex_fec,
646 peer->pc_factory()
647 ->GetRtpSenderCapabilities(cricket::MediaType::MEDIA_TYPE_VIDEO)
648 .codecs);
649
650 // Set codecs for transceivers
651 for (auto transceiver : peer->pc()->GetTransceivers()) {
652 if (transceiver->media_type() == cricket::MediaType::MEDIA_TYPE_VIDEO) {
653 transceiver->SetCodecPreferences(video_capabilities);
654 }
655 }
656}
657
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100658void PeerConnectionE2EQualityTest::SetupCall() {
Artem Titova6a273d2019-02-07 16:43:51 +0100659 // Connect peers.
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100660 ASSERT_TRUE(alice_->ExchangeOfferAnswerWith(bob_.get()));
Artem Titova6a273d2019-02-07 16:43:51 +0100661 // Do the SDP negotiation, and also exchange ice candidates.
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100662 ASSERT_EQ_WAIT(alice_->signaling_state(), PeerConnectionInterface::kStable,
Artem Titova6a273d2019-02-07 16:43:51 +0100663 kDefaultTimeoutMs);
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100664 ASSERT_TRUE_WAIT(alice_->IsIceGatheringDone(), kDefaultTimeoutMs);
665 ASSERT_TRUE_WAIT(bob_->IsIceGatheringDone(), kDefaultTimeoutMs);
Artem Titova6a273d2019-02-07 16:43:51 +0100666
667 // Connect an ICE candidate pairs.
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100668 ASSERT_TRUE(bob_->AddIceCandidates(alice_->observer()->GetAllCandidates()));
669 ASSERT_TRUE(alice_->AddIceCandidates(bob_->observer()->GetAllCandidates()));
Artem Titova6a273d2019-02-07 16:43:51 +0100670 // This means that ICE and DTLS are connected.
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100671 ASSERT_TRUE_WAIT(bob_->IsIceConnected(), kDefaultTimeoutMs);
672 ASSERT_TRUE_WAIT(alice_->IsIceConnected(), kDefaultTimeoutMs);
Artem Titova6a273d2019-02-07 16:43:51 +0100673}
674
Artem Titovbf9e01a2019-02-14 10:51:27 +0100675void PeerConnectionE2EQualityTest::StartVideo(
676 const std::vector<
677 rtc::scoped_refptr<FrameGeneratorCapturerVideoTrackSource>>& sources) {
678 for (auto& source : sources) {
679 if (source->state() != MediaSourceInterface::SourceState::kLive) {
680 source->Start();
Artem Titova6a273d2019-02-07 16:43:51 +0100681 }
Artem Titova6a273d2019-02-07 16:43:51 +0100682 }
683}
684
685void PeerConnectionE2EQualityTest::TearDownCall() {
Artem Titovbf9e01a2019-02-14 10:51:27 +0100686 for (const auto& video_source : alice_video_sources_) {
687 video_source->Stop();
688 }
689 for (const auto& video_source : bob_video_sources_) {
Artem Titova6a273d2019-02-07 16:43:51 +0100690 video_source->Stop();
691 }
692
693 alice_->pc()->Close();
694 bob_->pc()->Close();
695
696 for (const auto& video_writer : video_writers_) {
697 video_writer->Close();
698 }
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100699
Artem Titovbf9e01a2019-02-14 10:51:27 +0100700 alice_video_sources_.clear();
701 bob_video_sources_.clear();
Mirko Bonadei2bd54a12019-02-13 09:07:55 +0100702 video_writers_.clear();
703 alice_.reset();
704 bob_.reset();
Artem Titova6a273d2019-02-07 16:43:51 +0100705}
706
Artem Titov0b443142019-03-20 11:11:08 +0100707test::VideoFrameWriter* PeerConnectionE2EQualityTest::MaybeCreateVideoWriter(
Artem Titova6a273d2019-02-07 16:43:51 +0100708 absl::optional<std::string> file_name,
709 const VideoConfig& config) {
710 if (!file_name) {
711 return nullptr;
712 }
Artem Titov0b443142019-03-20 11:11:08 +0100713 auto video_writer = absl::make_unique<test::VideoFrameWriter>(
Artem Titova6a273d2019-02-07 16:43:51 +0100714 file_name.value(), config.width, config.height, config.fps);
Artem Titov0b443142019-03-20 11:11:08 +0100715 test::VideoFrameWriter* out = video_writer.get();
Artem Titova6a273d2019-02-07 16:43:51 +0100716 video_writers_.push_back(std::move(video_writer));
717 return out;
718}
719
Artem Titovba82e002019-03-15 15:57:53 +0100720Timestamp PeerConnectionE2EQualityTest::Now() const {
721 return Timestamp::us(clock_->TimeInMicroseconds());
722}
723
724PeerConnectionE2EQualityTest::ScheduledActivity::ScheduledActivity(
725 TimeDelta initial_delay_since_start,
726 absl::optional<TimeDelta> interval,
727 std::function<void(TimeDelta)> func)
728 : initial_delay_since_start(initial_delay_since_start),
729 interval(std::move(interval)),
730 func(std::move(func)) {}
731
Artem Titov0b443142019-03-20 11:11:08 +0100732} // namespace webrtc_pc_e2e
Artem Titova6a273d2019-02-07 16:43:51 +0100733} // namespace webrtc