[PCLF] Add infra metrics to the AnalyzingVideoSink

Bug: b/240540204
Change-Id: If3f5436d701336b0bc122477c61b97b5dc28f422
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/282001
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38561}
This commit is contained in:
Artem Titov 2022-11-06 20:19:57 +01:00 committed by WebRTC LUCI CQ
parent e91d4bc517
commit 2af96059a3
7 changed files with 126 additions and 20 deletions

View File

@ -233,7 +233,9 @@ if (!build_with_chromium) {
"../..:test_renderer",
"../../../api:peer_connection_quality_test_fixture_api",
"../../../api:video_quality_analyzer_api",
"../../../api/numerics",
"../../../api/test/video:video_frame_writer",
"../../../api/units:timestamp",
"../../../api/video:video_frame",
"../../../rtc_base:checks",
"../../../rtc_base:logging",

View File

@ -18,6 +18,7 @@
#include "absl/types/optional.h"
#include "api/test/peerconnection_quality_test_fixture.h"
#include "api/test/video/video_frame_writer.h"
#include "api/units/timestamp.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "rtc_base/checks.h"
@ -42,8 +43,10 @@ AnalyzingVideoSink::AnalyzingVideoSink(absl::string_view peer_name,
Clock* clock,
VideoQualityAnalyzerInterface& analyzer,
AnalyzingVideoSinksHelper& sinks_helper,
const VideoSubscription& subscription)
const VideoSubscription& subscription,
bool report_infra_stats)
: peer_name_(peer_name),
report_infra_stats_(report_infra_stats),
clock_(clock),
analyzer_(&analyzer),
sinks_helper_(&sinks_helper),
@ -92,6 +95,8 @@ void AnalyzingVideoSink::OnFrame(const VideoFrame& frame) {
AnalyzeFrame(frame);
} else {
std::string stream_label = analyzer_->GetStreamLabel(frame.id());
MutexLock lock(&mutex_);
Timestamp processing_started = clock_->CurrentTime();
SinksDescriptor* sinks_descriptor = PopulateSinks(stream_label);
RTC_CHECK(sinks_descriptor != nullptr);
@ -101,14 +106,30 @@ void AnalyzingVideoSink::OnFrame(const VideoFrame& frame) {
for (auto& sink : sinks_descriptor->sinks) {
sink->OnFrame(scaled_frame);
}
Timestamp processing_finished = clock_->CurrentTime();
if (report_infra_stats_) {
stats_.analyzing_sink_processing_time_ms.AddSample(
(processing_finished - processing_started).ms<double>());
}
}
}
AnalyzingVideoSink::Stats AnalyzingVideoSink::stats() const {
MutexLock lock(&mutex_);
return stats_;
}
VideoFrame AnalyzingVideoSink::ScaleVideoFrame(
const VideoFrame& frame,
const VideoResolution& required_resolution) {
Timestamp processing_started = clock_->CurrentTime();
if (required_resolution.width() == static_cast<size_t>(frame.width()) &&
required_resolution.height() == static_cast<size_t>(frame.height())) {
if (report_infra_stats_) {
stats_.scaling_tims_ms.AddSample(
(clock_->CurrentTime() - processing_started).ms<double>());
}
return frame;
}
@ -131,6 +152,10 @@ VideoFrame AnalyzingVideoSink::ScaleVideoFrame(
VideoFrame scaled_frame = frame;
scaled_frame.set_video_frame_buffer(scaled_buffer);
if (report_infra_stats_) {
stats_.scaling_tims_ms.AddSample(
(clock_->CurrentTime() - processing_started).ms<double>());
}
return scaled_frame;
}
@ -144,7 +169,6 @@ void AnalyzingVideoSink::AnalyzeFrame(const VideoFrame& frame) {
AnalyzingVideoSink::SinksDescriptor* AnalyzingVideoSink::PopulateSinks(
absl::string_view stream_label) {
// Fast pass: sinks already exists.
MutexLock lock(&mutex_);
auto sinks_it = stream_sinks_.find(std::string(stream_label));
if (sinks_it != stream_sinks_.end()) {
return &sinks_it->second;

View File

@ -17,6 +17,7 @@
#include <vector>
#include "absl/strings/string_view.h"
#include "api/numerics/samples_stats_counter.h"
#include "api/test/peerconnection_quality_test_fixture.h"
#include "api/test/video/video_frame_writer.h"
#include "api/test/video_quality_analyzer_interface.h"
@ -33,13 +34,24 @@ namespace webrtc_pc_e2e {
// A sink to inject video quality analyzer as a sink into WebRTC.
class AnalyzingVideoSink : public rtc::VideoSinkInterface<VideoFrame> {
public:
struct Stats {
// Time required to scale video frame to the requested rendered resolution.
// Collected only for frames with ID set and iff `report_infra_stats` is
// true.
SamplesStatsCounter scaling_tims_ms;
// Time required to process single video frame. Collected only for frames
// with ID set and iff `report_infra_stats` is true.
SamplesStatsCounter analyzing_sink_processing_time_ms;
};
AnalyzingVideoSink(
absl::string_view peer_name,
Clock* clock,
VideoQualityAnalyzerInterface& analyzer,
AnalyzingVideoSinksHelper& sinks_helper,
const PeerConnectionE2EQualityTestFixture::VideoSubscription&
subscription);
subscription,
bool report_infra_stats);
// Updates subscription used by this peer to render received video.
void UpdateSubscription(
@ -48,6 +60,8 @@ class AnalyzingVideoSink : public rtc::VideoSinkInterface<VideoFrame> {
void OnFrame(const VideoFrame& frame) override;
Stats stats() const;
private:
struct SinksDescriptor {
SinksDescriptor(
@ -71,23 +85,26 @@ class AnalyzingVideoSink : public rtc::VideoSinkInterface<VideoFrame> {
VideoFrame ScaleVideoFrame(
const VideoFrame& frame,
const PeerConnectionE2EQualityTestFixture::VideoResolution&
required_resolution);
required_resolution) RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
// Creates full copy of the frame to free any frame owned internal buffers
// and passes created copy to analyzer. Uses `I420Buffer` to represent
// frame content.
void AnalyzeFrame(const VideoFrame& frame);
// Populates sink for specified stream and caches them in `stream_sinks_`.
SinksDescriptor* PopulateSinks(absl::string_view stream_label);
SinksDescriptor* PopulateSinks(absl::string_view stream_label)
RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
const std::string peer_name_;
const bool report_infra_stats_;
Clock* const clock_;
VideoQualityAnalyzerInterface* const analyzer_;
AnalyzingVideoSinksHelper* const sinks_helper_;
Mutex mutex_;
mutable Mutex mutex_;
PeerConnectionE2EQualityTestFixture::VideoSubscription subscription_
RTC_GUARDED_BY(mutex_);
std::map<std::string, SinksDescriptor> stream_sinks_ RTC_GUARDED_BY(mutex_);
Stats stats_ RTC_GUARDED_BY(mutex_);
};
} // namespace webrtc_pc_e2e

View File

@ -40,6 +40,8 @@ namespace {
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::Gt;
using ::testing::Test;
using VideoConfig =
@ -56,7 +58,8 @@ void CleanDir(absl::string_view dir, size_t expected_output_files_count) {
absl::optional<std::vector<std::string>> dir_content =
test::ReadDirectory(dir);
if (expected_output_files_count == 0) {
ASSERT_FALSE(dir_content.has_value()) << "Empty directory is expected";
ASSERT_TRUE(!dir_content.has_value() || dir_content->empty())
<< "Empty directory is expected";
} else {
ASSERT_TRUE(dir_content.has_value()) << "Test directory is empty!";
EXPECT_EQ(dir_content->size(), expected_output_files_count);
@ -158,7 +161,7 @@ TEST_F(AnalyzingVideoSinkTest, VideoFramesAreDumpedCorrectly) {
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription);
subscription, /*report_infra_stats=*/false);
sink.OnFrame(frame);
}
@ -201,7 +204,7 @@ TEST_F(AnalyzingVideoSinkTest,
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription);
subscription, /*report_infra_stats=*/false);
sink.OnFrame(frame);
}
@ -246,7 +249,7 @@ TEST_F(AnalyzingVideoSinkTest,
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription);
subscription, /*report_infra_stats=*/false);
sink.OnFrame(frame);
}
@ -298,7 +301,7 @@ TEST_F(AnalyzingVideoSinkTest,
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription_before);
subscription_before, /*report_infra_stats=*/false);
sink.OnFrame(frame_before);
sink.UpdateSubscription(subscription_after);
@ -371,7 +374,7 @@ TEST_F(AnalyzingVideoSinkTest,
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription_before);
subscription_before, /*report_infra_stats=*/false);
sink.OnFrame(frame_before);
sink.UpdateSubscription(subscription_after);
@ -426,7 +429,7 @@ TEST_F(AnalyzingVideoSinkTest, SmallDiviationsInAspectRationAreAllowed) {
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription);
subscription, /*report_infra_stats=*/false);
sink.OnFrame(frame);
}
@ -473,7 +476,7 @@ TEST_F(AnalyzingVideoSinkTest, VideoFramesIdsAreDumpedWhenRequested) {
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription);
subscription, /*report_infra_stats=*/false);
for (int i = 0; i < 10; ++i) {
VideoFrame frame = CreateFrame(*frame_generator);
frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
@ -520,7 +523,7 @@ TEST_F(AnalyzingVideoSinkTest,
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", simulated_time.GetClock(), analyzer, helper,
subscription);
subscription, /*report_infra_stats=*/false);
sink.OnFrame(frame1);
// Advance almost 1 second, so the first frame has to be repeated 9 time
// more.
@ -569,6 +572,61 @@ TEST_F(AnalyzingVideoSinkTest,
ExpectOutputFilesCount(2);
}
TEST_F(AnalyzingVideoSinkTest, InfraMetricsCollectedWhenRequested) {
VideoSubscription subscription;
subscription.SubscribeToPeer(
"alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30));
VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360,
/*fps=*/30);
ExampleVideoQualityAnalyzer analyzer;
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
CreateFrameGenerator(/*width=*/640, /*height=*/360);
VideoFrame frame = CreateFrame(*frame_generator);
frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription, /*report_infra_stats=*/true);
sink.OnFrame(frame);
AnalyzingVideoSink::Stats stats = sink.stats();
EXPECT_THAT(stats.scaling_tims_ms.NumSamples(), Eq(1));
EXPECT_THAT(stats.scaling_tims_ms.GetAverage(), Gt(0));
EXPECT_THAT(stats.analyzing_sink_processing_time_ms.NumSamples(), Eq(1));
EXPECT_THAT(stats.analyzing_sink_processing_time_ms.GetAverage(),
Ge(stats.scaling_tims_ms.GetAverage()));
ExpectOutputFilesCount(0);
}
TEST_F(AnalyzingVideoSinkTest, InfraMetricsNotCollectedWhenNotRequested) {
VideoSubscription subscription;
subscription.SubscribeToPeer(
"alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30));
VideoConfig video_config("alice_video", /*width=*/640, /*height=*/360,
/*fps=*/30);
ExampleVideoQualityAnalyzer analyzer;
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
CreateFrameGenerator(/*width=*/640, /*height=*/360);
VideoFrame frame = CreateFrame(*frame_generator);
frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription, /*report_infra_stats=*/false);
sink.OnFrame(frame);
AnalyzingVideoSink::Stats stats = sink.stats();
EXPECT_THAT(stats.scaling_tims_ms.NumSamples(), Eq(0));
EXPECT_THAT(stats.analyzing_sink_processing_time_ms.NumSamples(), Eq(0));
ExpectOutputFilesCount(0);
}
} // namespace
} // namespace webrtc_pc_e2e
} // namespace webrtc

View File

@ -150,10 +150,11 @@ VideoQualityAnalyzerInjectionHelper::CreateVideoSink(
std::unique_ptr<AnalyzingVideoSink>
VideoQualityAnalyzerInjectionHelper::CreateVideoSink(
absl::string_view peer_name,
const PeerConnectionE2EQualityTestFixture::VideoSubscription&
subscription) {
const PeerConnectionE2EQualityTestFixture::VideoSubscription& subscription,
bool report_infra_metrics) {
return std::make_unique<AnalyzingVideoSink>(peer_name, clock_, *analyzer_,
sinks_helper_, subscription);
sinks_helper_, subscription,
report_infra_metrics);
}
void VideoQualityAnalyzerInjectionHelper::Start(

View File

@ -77,12 +77,15 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface {
// `output_dump_file_name` in its VideoConfig, which was used for
// CreateFramePreprocessor(...), then video also will be written
// into that file.
// TODO(titovartem): Remove method with `peer_name` only parameter.
std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> CreateVideoSink(
absl::string_view peer_name);
// TODO(titovartem): Remove default value for `report_infra_metrics`.
std::unique_ptr<AnalyzingVideoSink> CreateVideoSink(
absl::string_view peer_name,
const PeerConnectionE2EQualityTestFixture::VideoSubscription&
subscription);
subscription,
bool report_infra_metrics = false);
void Start(std::string test_case_name,
rtc::ArrayView<const std::string> peer_names,

View File

@ -473,7 +473,8 @@ void PeerConnectionE2EQualityTest::OnTrackCallback(
// track->kind() is kVideoKind.
auto* video_track = static_cast<VideoTrackInterface*>(track.get());
std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> video_sink =
video_quality_analyzer_injection_helper_->CreateVideoSink(peer_name);
video_quality_analyzer_injection_helper_->CreateVideoSink(
peer_name, peer_subscription, /*report_infra_stats=*/false);
video_track->AddOrUpdateSink(video_sink.get(), rtc::VideoSinkWants());
output_video_sinks_.push_back(std::move(video_sink));
}