[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}
This commit is contained in:
Artem Titov 2021-08-27 23:43:09 +02:00 committed by WebRTC LUCI CQ
parent 6eb30e40af
commit 8177f58dde
4 changed files with 82 additions and 25 deletions

View File

@ -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<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

View File

@ -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",

View File

@ -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<AnalyzingFramePreprocessor>(
@ -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<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>>* 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<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 @@ 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

View File

@ -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<VideoEncoderFactory> WrapVideoEncoderFactory(
@ -83,6 +76,9 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface {
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 @@ 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<std::string> 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<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 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface {
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