From 9a4f38ec5c4aa23d24dceb7161c41eb379198f89 Mon Sep 17 00:00:00 2001 From: Sebastian Jansson Date: Wed, 19 Dec 2018 13:14:41 +0100 Subject: [PATCH] Adds optional video quality metrics to scenario tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: webrtc:9510 Change-Id: I448e7156cc8f56930f58c4d25bd167df83a2ba85 Reviewed-on: https://webrtc-review.googlesource.com/c/114885 Commit-Queue: Sebastian Jansson Reviewed-by: Erik Språng Cr-Commit-Position: refs/heads/master@{#26065} --- test/scenario/BUILD.gn | 26 ++++ test/scenario/call_client.cc | 5 +- test/scenario/call_client.h | 6 +- test/scenario/quality_info.h | 27 ++++ test/scenario/quality_stats.cc | 183 ++++++++++++++++++++++++ test/scenario/quality_stats.h | 110 ++++++++++++++ test/scenario/quality_stats_unittest.cc | 62 ++++++++ test/scenario/scenario.cc | 11 +- test/scenario/scenario_config.h | 5 + test/scenario/video_stream.cc | 38 +++-- test/scenario/video_stream.h | 15 +- 11 files changed, 469 insertions(+), 19 deletions(-) create mode 100644 test/scenario/quality_info.h create mode 100644 test/scenario/quality_stats.cc create mode 100644 test/scenario/quality_stats.h create mode 100644 test/scenario/quality_stats_unittest.cc diff --git a/test/scenario/BUILD.gn b/test/scenario/BUILD.gn index b401947a56..9ae0ae625b 100644 --- a/test/scenario/BUILD.gn +++ b/test/scenario/BUILD.gn @@ -16,12 +16,16 @@ if (rtc_include_tests) { "audio_stream.h", "call_client.cc", "call_client.h", + "call_client.h", "column_printer.cc", "column_printer.h", "hardware_codecs.cc", "hardware_codecs.h", "network_node.cc", "network_node.h", + "quality_info.h", + "quality_stats.cc", + "quality_stats.h", "scenario.cc", "scenario.h", "scenario_config.cc", @@ -90,6 +94,7 @@ if (rtc_include_tests) { "../../system_wrappers", "../../system_wrappers:field_trial", "../../video", + "//third_party/abseil-cpp/absl/memory", "//third_party/abseil-cpp/absl/types:optional", ] if (is_android) { @@ -125,4 +130,25 @@ if (rtc_include_tests) { "//third_party/abseil-cpp/absl/memory", ] } + rtc_source_set("scenario_slow_tests") { + testonly = true + sources = [ + "quality_stats_unittest.cc", + ] + if (!build_with_chromium && is_clang) { + suppressed_configs += [ "//build/config/clang:find_bad_constructs" ] + } + deps = [ + ":scenario", + "../../logging:mocks", + "../../rtc_base:checks", + "../../rtc_base:rtc_base_approved", + "../../system_wrappers", + "../../system_wrappers:field_trial", + "../../test:field_trial", + "../../test:test_support", + "//testing/gmock", + "//third_party/abseil-cpp/absl/memory", + ] + } } diff --git a/test/scenario/call_client.cc b/test/scenario/call_client.cc index 27b8402db9..ccfcd5527c 100644 --- a/test/scenario/call_client.cc +++ b/test/scenario/call_client.cc @@ -134,17 +134,18 @@ TimeDelta LoggingNetworkControllerFactory::GetProcessInterval() const { } CallClient::CallClient(Clock* clock, + std::string name, std::string log_filename, CallClientConfig config) : clock_(clock), + name_(name), network_controller_factory_(log_filename, config.transport), fake_audio_setup_(InitAudio()), call_(CreateCall(config, &network_controller_factory_, fake_audio_setup_.audio_state)), transport_(clock_, call_.get()), - header_parser_(RtpHeaderParser::Create()) { -} // namespace test + header_parser_(RtpHeaderParser::Create()) {} CallClient::~CallClient() { delete header_parser_; diff --git a/test/scenario/call_client.h b/test/scenario/call_client.h index 69b7237116..aa5fa93c77 100644 --- a/test/scenario/call_client.h +++ b/test/scenario/call_client.h @@ -58,7 +58,10 @@ struct CallClientFakeAudio { // stream session. class CallClient : public NetworkReceiverInterface { public: - CallClient(Clock* clock, std::string log_filename, CallClientConfig config); + CallClient(Clock* clock, + std::string name, + std::string log_filename, + CallClientConfig config); RTC_DISALLOW_COPY_AND_ASSIGN(CallClient); ~CallClient(); @@ -89,6 +92,7 @@ class CallClient : public NetworkReceiverInterface { void AddExtensions(std::vector extensions); Clock* clock_; + const std::string name_; LoggingNetworkControllerFactory network_controller_factory_; CallClientFakeAudio fake_audio_setup_; std::unique_ptr call_; diff --git a/test/scenario/quality_info.h b/test/scenario/quality_info.h new file mode 100644 index 0000000000..9f17af02a2 --- /dev/null +++ b/test/scenario/quality_info.h @@ -0,0 +1,27 @@ +/* + * Copyright 2018 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_SCENARIO_QUALITY_INFO_H_ +#define TEST_SCENARIO_QUALITY_INFO_H_ + +#include "api/units/timestamp.h" + +namespace webrtc { +namespace test { +struct VideoFrameQualityInfo { + Timestamp capture_time; + Timestamp received_capture_time; + Timestamp render_time; + int width; + int height; + double psnr; +}; +} // namespace test +} // namespace webrtc +#endif // TEST_SCENARIO_QUALITY_INFO_H_ diff --git a/test/scenario/quality_stats.cc b/test/scenario/quality_stats.cc new file mode 100644 index 0000000000..2c734371fb --- /dev/null +++ b/test/scenario/quality_stats.cc @@ -0,0 +1,183 @@ +/* + * Copyright 2018 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/scenario/quality_stats.h" + +#include + +#include "common_video/libyuv/include/webrtc_libyuv.h" +#include "rtc_base/checks.h" +#include "rtc_base/event.h" + +namespace webrtc { +namespace test { + +VideoQualityAnalyzer::VideoQualityAnalyzer( + std::string filename_or_empty, + std::function frame_info_handler) + : task_queue_("VideoAnalyzer") { + if (!filename_or_empty.empty()) { + output_file_ = fopen(filename_or_empty.c_str(), "w"); + RTC_CHECK(output_file_); + PrintHeaders(); + frame_info_handlers_.push_back( + [this](const VideoFrameQualityInfo& info) { PrintFrameInfo(info); }); + } + if (frame_info_handler) + frame_info_handlers_.push_back(frame_info_handler); +} + +VideoQualityAnalyzer::~VideoQualityAnalyzer() { + rtc::Event event; + task_queue_.PostTask([&event] { event.Set(); }); + event.Wait(rtc::Event::kForever); + if (output_file_) + fclose(output_file_); +} + +void VideoQualityAnalyzer::OnCapturedFrame(const VideoFrame& frame) { + VideoFrame copy = frame; + task_queue_.PostTask([this, copy] { + if (!first_capture_ntp_time_ms_) + first_capture_ntp_time_ms_ = copy.ntp_time_ms(); + captured_frames_.push_back(std::move(copy)); + }); +} + +void VideoQualityAnalyzer::OnDecodedFrame(const VideoFrame& frame) { + VideoFrame decoded = frame; + RTC_CHECK(frame.ntp_time_ms()); + RTC_CHECK(frame.timestamp()); + task_queue_.PostTask([this, decoded] { + // If first frame never is received, this value will be wrong. However, that + // is something that is very unlikely to happen. + if (!first_decode_rtp_timestamp_) + first_decode_rtp_timestamp_ = decoded.timestamp(); + RTC_CHECK(!captured_frames_.empty()); + int64_t decoded_capture_time_ms = DecodedFrameCaptureTimeOffsetMs(decoded); + while (CapturedFrameCaptureTimeOffsetMs(captured_frames_.front()) < + decoded_capture_time_ms) { + VideoFrame lost = std::move(captured_frames_.front()); + captured_frames_.pop_front(); + VideoFrameQualityInfo lost_info = + VideoFrameQualityInfo{Timestamp::us(lost.timestamp_us()), + Timestamp::PlusInfinity(), + Timestamp::PlusInfinity(), + lost.width(), + lost.height(), + NAN}; + for (auto& handler : frame_info_handlers_) + handler(lost_info); + RTC_CHECK(!captured_frames_.empty()); + } + RTC_CHECK(!captured_frames_.empty()); + RTC_CHECK(CapturedFrameCaptureTimeOffsetMs(captured_frames_.front()) == + DecodedFrameCaptureTimeOffsetMs(decoded)); + VideoFrame captured = std::move(captured_frames_.front()); + captured_frames_.pop_front(); + + VideoFrameQualityInfo decoded_info = + VideoFrameQualityInfo{Timestamp::us(captured.timestamp_us()), + Timestamp::ms(decoded.timestamp() / 90.0), + Timestamp::ms(decoded.render_time_ms()), + decoded.width(), + decoded.height(), + I420PSNR(&captured, &decoded)}; + for (auto& handler : frame_info_handlers_) + handler(decoded_info); + }); +} + +bool VideoQualityAnalyzer::Active() const { + return !frame_info_handlers_.empty(); +} + +int64_t VideoQualityAnalyzer::DecodedFrameCaptureTimeOffsetMs( + const VideoFrame& decoded) const { + // Assumes that the underlying resolution is ms. + // Note that we intentinally allow wraparound. The code is incorrect for + // durations of more than UINT32_MAX/90 ms. + RTC_DCHECK(first_decode_rtp_timestamp_); + return (decoded.timestamp() - *first_decode_rtp_timestamp_) / 90; +} + +int64_t VideoQualityAnalyzer::CapturedFrameCaptureTimeOffsetMs( + const VideoFrame& captured) const { + RTC_DCHECK(first_capture_ntp_time_ms_); + return captured.ntp_time_ms() - *first_capture_ntp_time_ms_; +} + +void VideoQualityAnalyzer::PrintHeaders() { + fprintf(output_file_, "capt recv_capt render width height psnr\n"); +} + +void VideoQualityAnalyzer::PrintFrameInfo(const VideoFrameQualityInfo& sample) { + fprintf(output_file_, "%.3f %.3f %.3f %i %i %.3f\n", + sample.capture_time.seconds(), + sample.received_capture_time.seconds(), + sample.render_time.seconds(), sample.width, sample.height, + sample.psnr); +} + +void VideoQualityStats::HandleFrameInfo(VideoFrameQualityInfo sample) { + total++; + if (sample.render_time.IsInfinite()) { + ++lost; + } else { + ++valid; + end_to_end_seconds.AddSample( + (sample.render_time - sample.capture_time).seconds()); + psnr.AddSample(sample.psnr); + } +} + +ForwardingCapturedFrameTap::ForwardingCapturedFrameTap( + const Clock* clock, + VideoQualityAnalyzer* analyzer, + rtc::VideoSourceInterface* source) + : clock_(clock), analyzer_(analyzer), source_(source) {} + +ForwardingCapturedFrameTap::~ForwardingCapturedFrameTap() {} + +void ForwardingCapturedFrameTap::OnFrame(const VideoFrame& frame) { + RTC_CHECK(sink_); + VideoFrame copy = frame; + if (frame.ntp_time_ms() == 0) + copy.set_ntp_time_ms(clock_->CurrentNtpInMilliseconds()); + copy.set_timestamp(copy.ntp_time_ms() * 90); + analyzer_->OnCapturedFrame(copy); + sink_->OnFrame(copy); +} +void ForwardingCapturedFrameTap::OnDiscardedFrame() { + RTC_CHECK(sink_); + discarded_count_++; + sink_->OnDiscardedFrame(); +} + +void ForwardingCapturedFrameTap::AddOrUpdateSink( + VideoSinkInterface* sink, + const rtc::VideoSinkWants& wants) { + sink_ = sink; + source_->AddOrUpdateSink(this, wants); +} +void ForwardingCapturedFrameTap::RemoveSink( + VideoSinkInterface* sink) { + source_->RemoveSink(this); + sink_ = nullptr; +} + +DecodedFrameTap::DecodedFrameTap(VideoQualityAnalyzer* analyzer) + : analyzer_(analyzer) {} + +void DecodedFrameTap::OnFrame(const VideoFrame& frame) { + analyzer_->OnDecodedFrame(frame); +} + +} // namespace test +} // namespace webrtc diff --git a/test/scenario/quality_stats.h b/test/scenario/quality_stats.h new file mode 100644 index 0000000000..5e0ee48f80 --- /dev/null +++ b/test/scenario/quality_stats.h @@ -0,0 +1,110 @@ +/* + * Copyright 2018 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_SCENARIO_QUALITY_STATS_H_ +#define TEST_SCENARIO_QUALITY_STATS_H_ + +#include +#include +#include + +#include "absl/types/optional.h" +#include "api/units/timestamp.h" +#include "api/video/video_frame.h" +#include "api/video/video_sink_interface.h" +#include "api/video/video_source_interface.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/timeutils.h" +#include "system_wrappers/include/clock.h" +#include "test/scenario/quality_info.h" +#include "test/scenario/scenario_config.h" +#include "test/statistics.h" + +namespace webrtc { +namespace test { + +class VideoQualityAnalyzer { + public: + VideoQualityAnalyzer( + std::string filename_or_empty, + std::function frame_info_handler); + ~VideoQualityAnalyzer(); + void OnCapturedFrame(const VideoFrame& frame); + void OnDecodedFrame(const VideoFrame& frame); + void Synchronize(); + bool Active() const; + const Clock* clock(); + + private: + int64_t DecodedFrameCaptureTimeOffsetMs(const VideoFrame& decoded) const; + int64_t CapturedFrameCaptureTimeOffsetMs(const VideoFrame& captured) const; + void PrintHeaders(); + void PrintFrameInfo(const VideoFrameQualityInfo& sample); + std::vector> + frame_info_handlers_; + std::deque captured_frames_; + absl::optional first_capture_ntp_time_ms_; + absl::optional first_decode_rtp_timestamp_; + FILE* output_file_ = nullptr; + rtc::TaskQueue task_queue_; +}; + +struct VideoQualityStats { + int total = 0; + int valid = 0; + int lost = 0; + Statistics end_to_end_seconds; + Statistics frame_size; + Statistics psnr; + Statistics ssim; + + void HandleFrameInfo(VideoFrameQualityInfo sample); +}; + +class ForwardingCapturedFrameTap + : public rtc::VideoSinkInterface, + public rtc::VideoSourceInterface { + public: + ForwardingCapturedFrameTap(const Clock* clock, + VideoQualityAnalyzer* analyzer, + rtc::VideoSourceInterface* source); + ForwardingCapturedFrameTap(ForwardingCapturedFrameTap&) = delete; + ForwardingCapturedFrameTap& operator=(ForwardingCapturedFrameTap&) = delete; + ~ForwardingCapturedFrameTap(); + + // VideoSinkInterface interface + void OnFrame(const VideoFrame& frame) override; + void OnDiscardedFrame() override; + + // VideoSourceInterface interface + void AddOrUpdateSink(VideoSinkInterface* sink, + const rtc::VideoSinkWants& wants) override; + void RemoveSink(VideoSinkInterface* sink) override; + VideoFrame PopFrame(); + + private: + const Clock* clock_; + VideoQualityAnalyzer* const analyzer_; + rtc::VideoSourceInterface* const source_; + VideoSinkInterface* sink_; + int discarded_count_ = 0; +}; + +class DecodedFrameTap : public rtc::VideoSinkInterface { + public: + explicit DecodedFrameTap(VideoQualityAnalyzer* analyzer); + // VideoSinkInterface interface + void OnFrame(const VideoFrame& frame) override; + + private: + VideoQualityAnalyzer* const analyzer_; +}; +} // namespace test +} // namespace webrtc +#endif // TEST_SCENARIO_QUALITY_STATS_H_ diff --git a/test/scenario/quality_stats_unittest.cc b/test/scenario/quality_stats_unittest.cc new file mode 100644 index 0000000000..e3806cfedf --- /dev/null +++ b/test/scenario/quality_stats_unittest.cc @@ -0,0 +1,62 @@ +/* + * Copyright 2018 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/scenario/scenario.h" + +namespace webrtc { +namespace test { +namespace { +VideoStreamConfig AnalyzerVideoConfig(VideoQualityStats* stats) { + VideoStreamConfig config; + config.encoder.codec = VideoStreamConfig::Encoder::Codec::kVideoCodecVP8; + config.encoder.implementation = + VideoStreamConfig::Encoder::Implementation::kSoftware; + config.analyzer.frame_quality_handler = [stats](VideoFrameQualityInfo info) { + stats->HandleFrameInfo(info); + }; + return config; +} +} // namespace + +TEST(ScenarioAnalyzerTest, PsnrIsHighWhenNetworkIsGood) { + VideoQualityStats stats; + { + Scenario s; + NetworkNodeConfig good_network; + good_network.simulation.bandwidth = DataRate::kbps(1000); + auto route = s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), + {s.CreateSimulationNode(good_network)}, + s.CreateClient("callee", CallClientConfig()), + {s.CreateSimulationNode(NetworkNodeConfig())}); + s.CreateVideoStream(route->forward(), AnalyzerVideoConfig(&stats)); + s.RunFor(TimeDelta::seconds(1)); + } + EXPECT_GT(stats.psnr.Mean(), 46); +} + +TEST(ScenarioAnalyzerTest, PsnrIsLowWhenNetworkIsBad) { + VideoQualityStats stats; + { + Scenario s; + NetworkNodeConfig bad_network; + bad_network.simulation.bandwidth = DataRate::kbps(100); + bad_network.simulation.loss_rate = 0.02; + auto route = s.CreateRoutes(s.CreateClient("caller", CallClientConfig()), + {s.CreateSimulationNode(bad_network)}, + s.CreateClient("callee", CallClientConfig()), + {s.CreateSimulationNode(NetworkNodeConfig())}); + + s.CreateVideoStream(route->forward(), AnalyzerVideoConfig(&stats)); + s.RunFor(TimeDelta::seconds(2)); + } + EXPECT_LT(stats.psnr.Mean(), 40); +} +} // namespace test +} // namespace webrtc diff --git a/test/scenario/scenario.cc b/test/scenario/scenario.cc index 6b0990d29c..161a8329ee 100644 --- a/test/scenario/scenario.cc +++ b/test/scenario/scenario.cc @@ -107,7 +107,8 @@ StatesPrinter* Scenario::CreatePrinter(std::string name, CallClient* Scenario::CreateClient(std::string name, CallClientConfig config) { RTC_DCHECK(real_time_mode_); - CallClient* client = new CallClient(clock_, GetFullPathOrEmpty(name), config); + CallClient* client = + new CallClient(clock_, name, GetFullPathOrEmpty(name), config); if (config.transport.state_log_interval.IsFinite()) { Every(config.transport.state_log_interval, [this, client]() { client->network_controller_factory_.LogCongestionControllerStats(Now()); @@ -266,8 +267,12 @@ VideoStreamPair* Scenario::CreateVideoStream( VideoStreamPair* Scenario::CreateVideoStream( std::pair clients, VideoStreamConfig config) { - video_streams_.emplace_back( - new VideoStreamPair(clients.first, clients.second, config)); + std::string quality_log_file_name; + if (config.analyzer.log_to_file) + quality_log_file_name = + GetFullPathOrEmpty(clients.first->name_ + ".video_quality.txt"); + video_streams_.emplace_back(new VideoStreamPair( + clients.first, clients.second, config, quality_log_file_name)); return video_streams_.back().get(); } diff --git a/test/scenario/scenario_config.h b/test/scenario/scenario_config.h index 15db7bddba..682a1cb4a3 100644 --- a/test/scenario/scenario_config.h +++ b/test/scenario/scenario_config.h @@ -21,6 +21,7 @@ #include "api/units/time_delta.h" #include "common_types.h" // NOLINT(build/include) #include "test/frame_generator.h" +#include "test/scenario/quality_info.h" namespace webrtc { namespace test { @@ -138,6 +139,10 @@ struct VideoStreamConfig { struct Renderer { enum Type { kFake } type = kFake; }; + struct analyzer { + bool log_to_file = false; + std::function frame_quality_handler; + } analyzer; }; struct AudioStreamConfig { diff --git a/test/scenario/video_stream.cc b/test/scenario/video_stream.cc index 11c5ef1165..b68858b92a 100644 --- a/test/scenario/video_stream.cc +++ b/test/scenario/video_stream.cc @@ -12,6 +12,7 @@ #include #include +#include "absl/memory/memory.h" #include "api/test/video/function_video_encoder_factory.h" #include "api/video/builtin_video_bitrate_allocator_factory.h" #include "media/base/mediaconstants.h" @@ -175,7 +176,8 @@ VideoEncoderConfig CreateVideoEncoderConfig(VideoStreamConfig config) { SendVideoStream::SendVideoStream(CallClient* sender, VideoStreamConfig config, - Transport* send_transport) + Transport* send_transport, + VideoQualityAnalyzer* analyzer) : sender_(sender), config_(config) { for (size_t i = 0; i < config.encoder.num_simulcast_streams; ++i) { ssrcs_.push_back(sender->GetNextVideoSsrc()); @@ -244,9 +246,20 @@ SendVideoStream::SendVideoStream(CallClient* sender, send_stream_ = sender_->call_->CreateVideoSendStream( std::move(send_config), std::move(encoder_config)); + std::vector > + frame_info_handlers; + if (config.analyzer.frame_quality_handler) + frame_info_handlers.push_back(config.analyzer.frame_quality_handler); - send_stream_->SetSource(video_capturer_.get(), - config.encoder.degradation_preference); + if (analyzer->Active()) { + frame_tap_.reset(new ForwardingCapturedFrameTap(sender_->clock_, analyzer, + video_capturer_.get())); + send_stream_->SetSource(frame_tap_.get(), + config.encoder.degradation_preference); + } else { + send_stream_->SetSource(video_capturer_.get(), + config.encoder.degradation_preference); + } } SendVideoStream::~SendVideoStream() { @@ -282,7 +295,6 @@ void SendVideoStream::SetCaptureFramerate(int framerate) { RTC_CHECK(frame_generator_) << "Framerate change only implemented for generators"; frame_generator_->ChangeFramerate(framerate); - } VideoSendStream::Stats SendVideoStream::GetStats() const { @@ -311,9 +323,14 @@ ReceiveVideoStream::ReceiveVideoStream(CallClient* receiver, VideoStreamConfig config, SendVideoStream* send_stream, size_t chosen_stream, - Transport* feedback_transport) + Transport* feedback_transport, + VideoQualityAnalyzer* analyzer) : receiver_(receiver), config_(config) { - renderer_ = absl::make_unique(); + if (analyzer->Active()) { + renderer_ = absl::make_unique(analyzer); + } else { + renderer_ = absl::make_unique(); + } VideoReceiveStream::Config recv_config(feedback_transport); recv_config.rtp.remb = !config.stream.packet_feedback; recv_config.rtp.transport_cc = config.stream.packet_feedback; @@ -384,14 +401,17 @@ VideoStreamPair::~VideoStreamPair() = default; VideoStreamPair::VideoStreamPair(CallClient* sender, CallClient* receiver, - VideoStreamConfig config) + VideoStreamConfig config, + std::string quality_log_file_name) : config_(config), - send_stream_(sender, config, &sender->transport_), + analyzer_(quality_log_file_name, config.analyzer.frame_quality_handler), + send_stream_(sender, config, &sender->transport_, &analyzer_), receive_stream_(receiver, config, &send_stream_, /*chosen_stream=*/0, - &receiver->transport_) {} + &receiver->transport_, + &analyzer_) {} } // namespace test } // namespace webrtc diff --git a/test/scenario/video_stream.h b/test/scenario/video_stream.h index ebf9c951b7..6604576e32 100644 --- a/test/scenario/video_stream.h +++ b/test/scenario/video_stream.h @@ -19,6 +19,7 @@ #include "test/scenario/call_client.h" #include "test/scenario/column_printer.h" #include "test/scenario/network_node.h" +#include "test/scenario/quality_stats.h" #include "test/scenario/scenario_config.h" #include "test/test_video_capturer.h" @@ -43,7 +44,8 @@ class SendVideoStream { // Handles RTCP feedback for this stream. SendVideoStream(CallClient* sender, VideoStreamConfig config, - Transport* send_transport); + Transport* send_transport, + VideoQualityAnalyzer* analyzer); rtc::CriticalSection crit_; std::vector ssrcs_; @@ -55,6 +57,7 @@ class SendVideoStream { std::vector fake_encoders_ RTC_GUARDED_BY(crit_); std::unique_ptr bitrate_allocator_factory_; std::unique_ptr video_capturer_; + std::unique_ptr frame_tap_; FrameGeneratorCapturer* frame_generator_ = nullptr; int next_local_network_id_ = 0; int next_remote_network_id_ = 0; @@ -74,11 +77,12 @@ class ReceiveVideoStream { VideoStreamConfig config, SendVideoStream* send_stream, size_t chosen_stream, - Transport* feedback_transport); + Transport* feedback_transport, + VideoQualityAnalyzer* analyzer); VideoReceiveStream* receive_stream_ = nullptr; FlexfecReceiveStream* flecfec_stream_ = nullptr; - std::unique_ptr> renderer_; + std::unique_ptr> renderer_; CallClient* const receiver_; const VideoStreamConfig config_; std::unique_ptr decoder_factory_; @@ -93,15 +97,18 @@ class VideoStreamPair { ~VideoStreamPair(); SendVideoStream* send() { return &send_stream_; } ReceiveVideoStream* receive() { return &receive_stream_; } + VideoQualityAnalyzer* analyzer() { return &analyzer_; } private: friend class Scenario; VideoStreamPair(CallClient* sender, CallClient* receiver, - VideoStreamConfig config); + VideoStreamConfig config, + std::string quality_log_file_name); const VideoStreamConfig config_; + VideoQualityAnalyzer analyzer_; SendVideoStream send_stream_; ReceiveVideoStream receive_stream_; };