diff --git a/api/test/peerconnection_quality_test_fixture.h b/api/test/peerconnection_quality_test_fixture.h index 031b795971..4f691d7449 100644 --- a/api/test/peerconnection_quality_test_fixture.h +++ b/api/test/peerconnection_quality_test_fixture.h @@ -233,9 +233,31 @@ class PeerConnectionE2EQualityTestFixture { // written to the dump file. The value must be greater than 0. int input_dump_sampling_modulo = 1; // If specified this file will be used as output on the receiver side for - // this stream. If multiple streams will be produced by input stream, - // output files will be appended with indexes. The produced files contains - // what was rendered for this video stream on receiver side. + // this stream. + // + // If multiple output streams will be produced by this stream (e.g. when the + // stream represented by this `VideoConfig` is received by more than one + // peer), output files will be appended with receiver names. If the second + // and other receivers will be added in the middle of the call after the + // first frame for this stream has been already written to the output file, + // then only dumps for newly added peers will be appended with receiver + // name, the dump for the first receiver will have name equal to the + // specified one. For example: + // * If we have peers A and B and A has `VideoConfig` V_a with + // V_a.output_dump_file_name = "/foo/a_output.yuv", then the stream + // related to V_a will be written into "/foo/a_output.yuv". + // * If we have peers A, B and C and A has `VideoConfig` V_a with + // V_a.output_dump_file_name = "/foo/a_output.yuv", then the stream + // related to V_a will be written for peer B into "/foo/a_output.yuv.B" + // and for peer C into "/foo/a_output.yuv.C" + // * If we have peers A and B and A has `VideoConfig` V_a with + // V_a.output_dump_file_name = "/foo/a_output.yuv", then if after B + // received the first frame related to V_a peer C joined the call, then + // the stream related to V_a will be written for peer B into + // "/foo/a_output.yuv" and for peer C into "/foo/a_output.yuv.C" + // + // The produced files contains what was rendered for this video stream on + // receiver side. absl::optional output_dump_file_name; // Used only if `output_dump_file_name` is set. Specifies the module for the // video frames to be dumped. Modulo equals X means every Xth frame will be diff --git a/test/pc/e2e/BUILD.gn b/test/pc/e2e/BUILD.gn index 20e59a5a15..11c95d0e47 100644 --- a/test/pc/e2e/BUILD.gn +++ b/test/pc/e2e/BUILD.gn @@ -187,6 +187,7 @@ if (!build_with_chromium) { "../../../api/video:video_rtp_headers", "../../../api/video_codecs:video_codecs_api", "../../../rtc_base:criticalsection", + "../../../rtc_base:stringutils", "../../../rtc_base/synchronization:mutex", "../../../test:video_test_common", "../../../test:video_test_support", diff --git a/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc b/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc index b1a22209be..d89640acdd 100644 --- a/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc +++ b/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.cc @@ -16,6 +16,7 @@ #include "absl/memory/memory.h" #include "absl/strings/string_view.h" #include "api/array_view.h" +#include "rtc_base/strings/string_builder.h" #include "test/pc/e2e/analyzer/video/quality_analyzing_video_decoder.h" #include "test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h" #include "test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h" @@ -134,7 +135,7 @@ VideoQualityAnalyzerInjectionHelper::CreateFramePreprocessor( config.width, config.height))); } { - MutexLock lock(&lock_); + MutexLock lock(&mutex_); known_video_configs_.insert({*config.stream_label, config}); } return std::make_unique( @@ -154,6 +155,16 @@ void VideoQualityAnalyzerInjectionHelper::Start( int max_threads_count) { analyzer_->Start(std::move(test_case_name), peer_names, max_threads_count); extractor_->Start(peer_names.size()); + MutexLock lock(&mutex_); + peers_count_ = peer_names.size(); +} + +void VideoQualityAnalyzerInjectionHelper::RegisterParticipantInCall( + absl::string_view peer_name) { + analyzer_->RegisterParticipantInCall(peer_name); + extractor_->AddParticipantInCall(); + MutexLock lock(&mutex_); + peers_count_++; } void VideoQualityAnalyzerInjectionHelper::OnStatsReports( @@ -203,7 +214,7 @@ void VideoQualityAnalyzerInjectionHelper::OnFrame(absl::string_view peer_name, std::string stream_label = analyzer_->GetStreamLabel(frame.id()); std::vector>>* sinks = - PopulateSinks(stream_label); + PopulateSinks(ReceiverStream(peer_name, stream_label)); if (sinks == nullptr) { return; } @@ -214,20 +225,27 @@ void VideoQualityAnalyzerInjectionHelper::OnFrame(absl::string_view peer_name, std::vector>>* VideoQualityAnalyzerInjectionHelper::PopulateSinks( - const std::string& stream_label) { - MutexLock lock(&lock_); - auto sinks_it = sinks_.find(stream_label); + const ReceiverStream& receiver_stream) { + MutexLock lock(&mutex_); + auto sinks_it = sinks_.find(receiver_stream); if (sinks_it != sinks_.end()) { return &sinks_it->second; } - auto it = known_video_configs_.find(stream_label); + auto it = known_video_configs_.find(receiver_stream.stream_label); RTC_DCHECK(it != known_video_configs_.end()) - << "No video config for stream " << stream_label; + << "No video config for stream " << receiver_stream.stream_label; const VideoConfig& config = it->second; std::vector>> sinks; + absl::optional output_dump_file_name = + config.output_dump_file_name; + if (output_dump_file_name.has_value() && peers_count_ > 2) { + rtc::StringBuilder builder(*output_dump_file_name); + builder << "." << receiver_stream.peer_name; + output_dump_file_name = builder.str(); + } test::VideoFrameWriter* writer = - MaybeCreateVideoWriter(config.output_dump_file_name, config); + MaybeCreateVideoWriter(output_dump_file_name, config); if (writer) { sinks.push_back(std::make_unique( writer, config.output_dump_sampling_modulo)); @@ -237,8 +255,8 @@ VideoQualityAnalyzerInjectionHelper::PopulateSinks( test::VideoRenderer::Create((*config.stream_label + "-render").c_str(), config.width, config.height))); } - sinks_.insert({stream_label, std::move(sinks)}); - return &(sinks_.find(stream_label)->second); + sinks_.insert({receiver_stream, std::move(sinks)}); + return &(sinks_.find(receiver_stream)->second); } } // namespace webrtc_pc_e2e diff --git a/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h b/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h index 997b8ad061..f130efc193 100644 --- a/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h +++ b/test/pc/e2e/analyzer/video/video_quality_analyzer_injection_helper.h @@ -45,13 +45,6 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface { EncodedImageDataExtractor* extractor); ~VideoQualityAnalyzerInjectionHelper() override; - // Registers new call participant to the underlying video quality analyzer. - // The method should be called before the participant is actually added. - void RegisterParticipantInCall(absl::string_view peer_name) { - analyzer_->RegisterParticipantInCall(peer_name); - extractor_->AddParticipantInCall(); - } - // Wraps video encoder factory to give video quality analyzer access to frames // before encoding and encoded images after. std::unique_ptr WrapVideoEncoderFactory( @@ -83,6 +76,9 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface { void Start(std::string test_case_name, rtc::ArrayView peer_names, int max_threads_count = 1); + // Registers new call participant to the underlying video quality analyzer. + // The method should be called before the participant is actually added. + void RegisterParticipantInCall(absl::string_view peer_name); // Forwards `stats_reports` for Peer Connection `pc_label` to // `analyzer_`. @@ -111,6 +107,23 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface { VideoQualityAnalyzerInjectionHelper* const helper_; }; + struct ReceiverStream { + ReceiverStream(absl::string_view peer_name, absl::string_view stream_label) + : peer_name(peer_name), stream_label(stream_label) {} + + std::string peer_name; + std::string stream_label; + + // Define operators required to use ReceiverStream as std::map key. + bool operator==(const ReceiverStream& o) const { + return peer_name == o.peer_name && stream_label == o.stream_label; + } + bool operator<(const ReceiverStream& o) const { + return (peer_name == o.peer_name) ? stream_label < o.stream_label + : peer_name < o.peer_name; + } + }; + test::VideoFrameWriter* MaybeCreateVideoWriter( absl::optional file_name, const PeerConnectionE2EQualityTestFixture::VideoConfig& config); @@ -118,7 +131,7 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface { // passing real frame to the sinks void OnFrame(absl::string_view peer_name, const VideoFrame& frame); std::vector>>* - PopulateSinks(const std::string& stream_label); + PopulateSinks(const ReceiverStream& receiver_stream); std::unique_ptr analyzer_; EncodedImageDataInjector* injector_; @@ -126,11 +139,14 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface { std::vector> video_writers_; - Mutex lock_; - std::map known_video_configs_ RTC_GUARDED_BY(lock_); - std::map known_video_configs_ + RTC_GUARDED_BY(mutex_); + std::map>>> - sinks_ RTC_GUARDED_BY(lock_); + sinks_ RTC_GUARDED_BY(mutex_); }; } // namespace webrtc_pc_e2e