[PCLF] Add support for dumping video with multiple receivers
Bug: b/197896468
Change-Id: I7896246eedb2e9efe847df4dddfc8ef05f7d152b
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/230424
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34866}
diff --git a/api/test/peerconnection_quality_test_fixture.h b/api/test/peerconnection_quality_test_fixture.h
index 031b795..4f691d7 100644
--- a/api/test/peerconnection_quality_test_fixture.h
+++ b/api/test/peerconnection_quality_test_fixture.h
@@ -233,9 +233,31 @@
// 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<std::string> 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 20e59a5..11c95d0 100644
--- a/test/pc/e2e/BUILD.gn
+++ b/test/pc/e2e/BUILD.gn
@@ -187,6 +187,7 @@
"../../../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 b1a2220..d89640a 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 @@
config.width, config.height)));
}
{
- MutexLock lock(&lock_);
+ MutexLock lock(&mutex_);
known_video_configs_.insert({*config.stream_label, config});
}
return std::make_unique<AnalyzingFramePreprocessor>(
@@ -154,6 +155,16 @@
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 @@
std::string stream_label = analyzer_->GetStreamLabel(frame.id());
std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>* sinks =
- PopulateSinks(stream_label);
+ PopulateSinks(ReceiverStream(peer_name, stream_label));
if (sinks == nullptr) {
return;
}
@@ -214,20 +225,27 @@
std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>*
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<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> sinks;
+ absl::optional<std::string> 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<VideoWriter>(
writer, config.output_dump_sampling_modulo));
@@ -237,8 +255,8 @@
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 997b8ad..f130efc 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 @@
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<VideoEncoderFactory> WrapVideoEncoderFactory(
@@ -83,6 +76,9 @@
void Start(std::string test_case_name,
rtc::ArrayView<const std::string> 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 @@
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<std::string> file_name,
const PeerConnectionE2EQualityTestFixture::VideoConfig& config);
@@ -118,7 +131,7 @@
// passing real frame to the sinks
void OnFrame(absl::string_view peer_name, const VideoFrame& frame);
std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>*
- PopulateSinks(const std::string& stream_label);
+ PopulateSinks(const ReceiverStream& receiver_stream);
std::unique_ptr<VideoQualityAnalyzerInterface> analyzer_;
EncodedImageDataInjector* injector_;
@@ -126,11 +139,14 @@
std::vector<std::unique_ptr<test::VideoFrameWriter>> video_writers_;
- Mutex lock_;
- std::map<std::string, VideoConfig> known_video_configs_ RTC_GUARDED_BY(lock_);
- std::map<std::string,
+ Mutex mutex_;
+ int peers_count_ RTC_GUARDED_BY(mutex_);
+ // Map from stream label to the video config.
+ std::map<std::string, VideoConfig> known_video_configs_
+ RTC_GUARDED_BY(mutex_);
+ std::map<ReceiverStream,
std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>>
- sinks_ RTC_GUARDED_BY(lock_);
+ sinks_ RTC_GUARDED_BY(mutex_);
};
} // namespace webrtc_pc_e2e