[PCLF] Use resolution from video subscription to dump video

Bug: b/240540204
Change-Id: I8f91cc68fc52de457e89f3b6247970b479b5f118
No-Try: true
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/280420
Reviewed-by: Andrey Logvin <landrey@google.com>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#38491}
This commit is contained in:
Artem Titov
2022-10-28 10:54:32 +02:00
committed by WebRTC LUCI CQ
parent 55bb93d4c7
commit d393543110
13 changed files with 1284 additions and 14 deletions

View File

@ -51,6 +51,8 @@ if (!build_with_chromium) {
testonly = true
deps = [
":analyzing_video_sink_test",
":analyzing_video_sinks_helper_test",
":default_video_quality_analyzer_frames_comparator_test",
":default_video_quality_analyzer_metric_names_test",
":default_video_quality_analyzer_stream_state_test",
@ -59,6 +61,7 @@ if (!build_with_chromium) {
":names_collection_test",
":peer_connection_e2e_smoke_test",
":peer_connection_quality_test_metric_names_test",
":peer_connection_quality_test_test",
":simulcast_dummy_buffer_helper_test",
":single_process_encoded_image_data_injector_unittest",
":stats_poller_test",
@ -222,6 +225,53 @@ if (!build_with_chromium) {
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
rtc_library("analyzing_video_sinks_helper") {
testonly = true
sources = [
"analyzer/video/analyzing_video_sinks_helper.cc",
"analyzer/video/analyzing_video_sinks_helper.h",
]
deps = [
"../../../api:peer_connection_quality_test_fixture_api",
"../../../api/test/video:video_frame_writer",
"../../../rtc_base:macromagic",
"../../../rtc_base/synchronization:mutex",
]
absl_deps = [
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
]
}
rtc_library("analyzing_video_sink") {
testonly = true
sources = [
"analyzer/video/analyzing_video_sink.cc",
"analyzer/video/analyzing_video_sink.h",
]
deps = [
":analyzing_video_sinks_helper",
":simulcast_dummy_buffer_helper",
":video_dumping",
"../..:fixed_fps_video_frame_writer_adapter",
"../..:test_renderer",
"../../../api:peer_connection_quality_test_fixture_api",
"../../../api:video_quality_analyzer_api",
"../../../api/test/video:video_frame_writer",
"../../../api/video:video_frame",
"../../../rtc_base:checks",
"../../../rtc_base:logging",
"../../../rtc_base:macromagic",
"../../../rtc_base/synchronization:mutex",
"../../../system_wrappers",
]
absl_deps = [
"//third_party/abseil-cpp/absl/memory:memory",
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
]
}
rtc_library("video_quality_analyzer_injection_helper") {
visibility = [ "*" ]
testonly = true
@ -230,6 +280,8 @@ if (!build_with_chromium) {
"analyzer/video/video_quality_analyzer_injection_helper.h",
]
deps = [
":analyzing_video_sink",
":analyzing_video_sinks_helper",
":encoded_image_data_injector_api",
":quality_analyzing_video_decoder",
":quality_analyzing_video_encoder",
@ -506,6 +558,44 @@ if (!build_with_chromium) {
]
}
rtc_library("analyzing_video_sinks_helper_test") {
testonly = true
sources = [ "analyzer/video/analyzing_video_sinks_helper_test.cc" ]
deps = [
":analyzing_video_sinks_helper",
"../..:test_support",
"../../../api:peer_connection_quality_test_fixture_api",
]
absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ]
}
rtc_library("analyzing_video_sink_test") {
testonly = true
sources = [ "analyzer/video/analyzing_video_sink_test.cc" ]
deps = [
":analyzing_video_sink",
":example_video_quality_analyzer",
"../..:fileutils",
"../..:test_support",
"../..:video_test_support",
"../../../api:create_frame_generator",
"../../../api:frame_generator_api",
"../../../api:peer_connection_quality_test_fixture_api",
"../../../api:scoped_refptr",
"../../../api/units:time_delta",
"../../../api/units:timestamp",
"../../../api/video:video_frame",
"../../../common_video",
"../../../rtc_base:timeutils",
"../../../system_wrappers",
"../../time_controller",
]
absl_deps = [
"//third_party/abseil-cpp/absl/strings",
"//third_party/abseil-cpp/absl/types:optional",
]
}
peer_connection_e2e_smoke_test_resources = [
"../../../resources/pc_quality_smoke_test_alice_source.wav",
"../../../resources/pc_quality_smoke_test_bob_source.wav",
@ -586,6 +676,23 @@ if (!build_with_chromium) {
]
}
rtc_library("peer_connection_quality_test_test") {
testonly = true
sources = [ "peer_connection_quality_test_test.cc" ]
deps = [
":peerconnection_quality_test",
"../..:fileutils",
"../..:test_support",
"../..:video_test_support",
"../../../api:create_network_emulation_manager",
"../../../api:network_emulation_manager_api",
"../../../api:peer_connection_quality_test_fixture_api",
"../../../api/test/metrics:global_metrics_logger_and_exporter",
"../../../api/units:time_delta",
"../../../rtc_base:timeutils",
]
}
rtc_library("stats_provider") {
visibility = [ "*" ]
testonly = true

View File

@ -0,0 +1,153 @@
/*
* Copyright (c) 2022 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/pc/e2e/analyzer/video/analyzing_video_sink.h"
#include <memory>
#include <set>
#include <utility>
#include "absl/memory/memory.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/test/peerconnection_quality_test_fixture.h"
#include "api/test/video/video_frame_writer.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/synchronization/mutex.h"
#include "test/pc/e2e/analyzer/video/simulcast_dummy_buffer_helper.h"
#include "test/pc/e2e/analyzer/video/video_dumping.h"
#include "test/testsupport/fixed_fps_video_frame_writer_adapter.h"
#include "test/video_renderer.h"
namespace webrtc {
namespace webrtc_pc_e2e {
using VideoSubscription = ::webrtc::webrtc_pc_e2e::
PeerConnectionE2EQualityTestFixture::VideoSubscription;
using VideoResolution = ::webrtc::webrtc_pc_e2e::
PeerConnectionE2EQualityTestFixture::VideoResolution;
using VideoConfig =
::webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTestFixture::VideoConfig;
AnalyzingVideoSink::AnalyzingVideoSink(absl::string_view peer_name,
Clock* clock,
VideoQualityAnalyzerInterface& analyzer,
AnalyzingVideoSinksHelper& sinks_helper,
const VideoSubscription& subscription)
: peer_name_(peer_name),
clock_(clock),
analyzer_(&analyzer),
sinks_helper_(&sinks_helper),
subscription_(subscription) {}
void AnalyzingVideoSink::UpdateSubscription(
const VideoSubscription& subscription) {
// For peers with changed resolutions we need to close current writers and
// open new ones. This is done by removing existing sinks, which will force
// creation of the new sinks when next frame will be received.
std::set<test::VideoFrameWriter*> writers_to_close;
{
MutexLock lock(&mutex_);
subscription_ = subscription;
for (auto it = stream_sinks_.cbegin(); it != stream_sinks_.cend();) {
absl::optional<VideoResolution> new_requested_resolution =
subscription_.GetResolutionForPeer(it->second.sender_peer_name);
if (!new_requested_resolution.has_value() ||
(*new_requested_resolution != it->second.resolution)) {
writers_to_close.insert(it->second.video_frame_writer);
it = stream_sinks_.erase(it);
} else {
++it;
}
}
}
sinks_helper_->CloseAndRemoveVideoWriters(writers_to_close);
}
void AnalyzingVideoSink::OnFrame(const VideoFrame& frame) {
if (IsDummyFrame(frame)) {
// This is dummy frame, so we don't need to process it further.
return;
}
// Copy entire video frame including video buffer to ensure that analyzer
// won't hold any WebRTC internal buffers.
VideoFrame frame_copy = frame;
frame_copy.set_video_frame_buffer(
I420Buffer::Copy(*frame.video_frame_buffer()->ToI420()));
analyzer_->OnFrameRendered(peer_name_, frame_copy);
if (frame.id() != VideoFrame::kNotSetId) {
std::string stream_label = analyzer_->GetStreamLabel(frame.id());
SinksDescriptor* sinks_descriptor = PopulateSinks(stream_label);
if (sinks_descriptor == nullptr) {
return;
}
for (auto& sink : sinks_descriptor->sinks) {
sink->OnFrame(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;
}
// Slow pass: we need to create and save sinks
absl::optional<std::pair<std::string, VideoConfig>> peer_and_config =
sinks_helper_->GetPeerAndConfig(stream_label);
RTC_CHECK(peer_and_config.has_value())
<< "No video config for stream " << stream_label;
const std::string& sender_peer_name = peer_and_config->first;
const VideoConfig& config = peer_and_config->second;
absl::optional<VideoResolution> resolution =
subscription_.GetResolutionForPeer(sender_peer_name);
if (!resolution.has_value()) {
RTC_LOG(LS_ERROR) << peer_name_ << " received stream " << stream_label
<< " from " << sender_peer_name
<< " for which they were not subscribed";
resolution = config.GetResolution();
}
RTC_CHECK(resolution.has_value());
SinksDescriptor sinks_descriptor(sender_peer_name, *resolution);
if (config.output_dump_options.has_value()) {
std::unique_ptr<test::VideoFrameWriter> writer =
config.output_dump_options->CreateOutputDumpVideoFrameWriter(
stream_label, peer_name_, *resolution);
if (config.output_dump_use_fixed_framerate) {
writer = std::make_unique<test::FixedFpsVideoFrameWriterAdapter>(
resolution->fps(), clock_, std::move(writer));
}
sinks_descriptor.sinks.push_back(std::make_unique<VideoWriter>(
writer.get(), config.output_dump_options->sampling_modulo()));
sinks_descriptor.video_frame_writer =
sinks_helper_->AddVideoWriter(std::move(writer));
}
if (config.show_on_screen) {
sinks_descriptor.sinks.push_back(
absl::WrapUnique(test::VideoRenderer::Create(
(*config.stream_label + "-render").c_str(), resolution->width(),
resolution->height())));
}
return &stream_sinks_.emplace(stream_label, std::move(sinks_descriptor))
.first->second;
}
} // namespace webrtc_pc_e2e
} // namespace webrtc

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2022 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_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINK_H_
#define TEST_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINK_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.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"
#include "api/video/video_frame.h"
#include "api/video/video_sink_interface.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread_annotations.h"
#include "system_wrappers/include/clock.h"
#include "test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h"
namespace webrtc {
namespace webrtc_pc_e2e {
// A sink to inject video quality analyzer as a sink into WebRTC.
class AnalyzingVideoSink : public rtc::VideoSinkInterface<VideoFrame> {
public:
AnalyzingVideoSink(
absl::string_view peer_name,
Clock* clock,
VideoQualityAnalyzerInterface& analyzer,
AnalyzingVideoSinksHelper& sinks_helper,
const PeerConnectionE2EQualityTestFixture::VideoSubscription&
subscription);
// Updates subscription used by this peer to render received video.
void UpdateSubscription(
const PeerConnectionE2EQualityTestFixture::VideoSubscription&
subscription);
void OnFrame(const VideoFrame& frame) override;
private:
struct SinksDescriptor {
SinksDescriptor(
absl::string_view sender_peer_name,
const PeerConnectionE2EQualityTestFixture::VideoResolution& resolution)
: sender_peer_name(sender_peer_name), resolution(resolution) {}
// Required to be able to resolve resolutions on new subscription and
// understand if we need to recreate `video_frame_writer` and `sinks`.
std::string sender_peer_name;
// Resolution which was used to create `video_frame_writer` and `sinks`.
PeerConnectionE2EQualityTestFixture::VideoResolution resolution;
// Is set if dumping of output video was requested;
test::VideoFrameWriter* video_frame_writer = nullptr;
std::vector<std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>> sinks;
};
// Populates sink for specified stream and caches them in `stream_sinks_`.
SinksDescriptor* PopulateSinks(absl::string_view stream_label);
const std::string peer_name_;
Clock* const clock_;
VideoQualityAnalyzerInterface* const analyzer_;
AnalyzingVideoSinksHelper* const sinks_helper_;
Mutex mutex_;
PeerConnectionE2EQualityTestFixture::VideoSubscription subscription_
RTC_GUARDED_BY(mutex_);
std::map<std::string, SinksDescriptor> stream_sinks_ RTC_GUARDED_BY(mutex_);
};
} // namespace webrtc_pc_e2e
} // namespace webrtc
#endif // TEST_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINK_H_

View File

@ -0,0 +1,419 @@
/*
* Copyright (c) 2022 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/pc/e2e/analyzer/video/analyzing_video_sink.h"
#include <stdio.h>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/scoped_refptr.h"
#include "api/test/create_frame_generator.h"
#include "api/test/frame_generator_interface.h"
#include "api/test/peerconnection_quality_test_fixture.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "common_video/libyuv/include/webrtc_libyuv.h"
#include "rtc_base/time_utils.h"
#include "system_wrappers/include/clock.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/pc/e2e/analyzer/video/example_video_quality_analyzer.h"
#include "test/testsupport/file_utils.h"
#include "test/testsupport/frame_reader.h"
#include "test/time_controller/simulated_time_controller.h"
namespace webrtc {
namespace webrtc_pc_e2e {
namespace {
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::Test;
using VideoConfig =
::webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTestFixture::VideoConfig;
using VideoSubscription = ::webrtc::webrtc_pc_e2e::
PeerConnectionE2EQualityTestFixture::VideoSubscription;
using VideoResolution = ::webrtc::webrtc_pc_e2e::
PeerConnectionE2EQualityTestFixture::VideoResolution;
using VideoDumpOptions = ::webrtc::webrtc_pc_e2e::
PeerConnectionE2EQualityTestFixture::VideoDumpOptions;
// Remove files and directories in a directory non-recursively.
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";
} else {
ASSERT_TRUE(dir_content.has_value()) << "Test directory is empty!";
EXPECT_EQ(dir_content->size(), expected_output_files_count);
for (const auto& entry : *dir_content) {
if (test::DirExists(entry)) {
EXPECT_TRUE(test::RemoveDir(entry))
<< "Failed to remove sub directory: " << entry;
} else if (test::FileExists(entry)) {
EXPECT_TRUE(test::RemoveFile(entry))
<< "Failed to remove file: " << entry;
} else {
FAIL() << "Can't remove unknown file type: " << entry;
}
}
}
EXPECT_TRUE(test::RemoveDir(dir)) << "Failed to remove directory: " << dir;
}
VideoFrame CreateFrame(test::FrameGeneratorInterface& frame_generator) {
test::FrameGeneratorInterface::VideoFrameData frame_data =
frame_generator.NextFrame();
return VideoFrame::Builder()
.set_video_frame_buffer(frame_data.buffer)
.set_update_rect(frame_data.update_rect)
.build();
}
std::unique_ptr<test::FrameGeneratorInterface> CreateFrameGenerator(
size_t width,
size_t height) {
return test::CreateSquareFrameGenerator(width, height,
/*type=*/absl::nullopt,
/*num_squares=*/absl::nullopt);
}
void AssertFrameIdsAre(const std::string& filename,
std::vector<std::string> expected_ids) {
FILE* file = fopen(filename.c_str(), "r");
ASSERT_TRUE(file != nullptr) << "Failed to open frame ids file: " << filename;
std::vector<std::string> actual_ids;
char buffer[8];
while (fgets(buffer, sizeof buffer, file) != nullptr) {
std::string current_id(buffer);
EXPECT_GE(current_id.size(), 2lu)
<< "Found invalid frame id: [" << current_id << "]";
if (current_id.size() < 2) {
continue;
}
// Trim "\n" at the end.
actual_ids.push_back(current_id.substr(0, current_id.size() - 1));
}
fclose(file);
EXPECT_THAT(actual_ids, ElementsAreArray(expected_ids));
}
class AnalyzingVideoSinkTest : public Test {
protected:
~AnalyzingVideoSinkTest() override = default;
void SetUp() override {
// Create an empty temporary directory for this test.
test_directory_ = test::JoinFilename(
test::OutputPath(),
"TestDir_AnalyzingVideoSinkTest_" +
std::string(
testing::UnitTest::GetInstance()->current_test_info()->name()));
test::CreateDir(test_directory_);
}
void TearDown() override {
CleanDir(test_directory_, expected_output_files_count_);
}
void ExpectOutputFilesCount(size_t count) {
expected_output_files_count_ = count;
}
std::string test_directory_;
size_t expected_output_files_count_ = 0;
};
TEST_F(AnalyzingVideoSinkTest, VideoFramesAreDumpedCorrectly) {
VideoSubscription subscription;
subscription.SubscribeToPeer(
"alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
VideoConfig video_config("alice_video", /*width=*/1280, /*height=*/720,
/*fps=*/30);
video_config.output_dump_options = VideoDumpOptions(test_directory_);
ExampleVideoQualityAnalyzer analyzer;
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
CreateFrameGenerator(/*width=*/1280, /*height=*/720);
VideoFrame frame = CreateFrame(*frame_generator);
frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
{
// `helper` and `sink` has to be destroyed so all frames will be written
// to the disk.
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription);
sink.OnFrame(frame);
}
EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));
test::Y4mFrameReaderImpl frame_reader(
test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m"),
/*width=*/640,
/*height=*/360);
ASSERT_TRUE(frame_reader.Init());
EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
rtc::scoped_refptr<I420BufferInterface> expected_frame =
frame.video_frame_buffer()->ToI420();
double psnr = I420PSNR(*expected_frame, *actual_frame);
double ssim = I420SSIM(*expected_frame, *actual_frame);
// Actual should be downscaled version of expected.
EXPECT_GT(ssim, 0.98);
EXPECT_GT(psnr, 38);
ExpectOutputFilesCount(1);
}
TEST_F(AnalyzingVideoSinkTest,
FallbackOnConfigResolutionIfNoSucscriptionProvided) {
VideoSubscription subscription;
VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
/*fps=*/30);
video_config.output_dump_options = VideoDumpOptions(test_directory_);
ExampleVideoQualityAnalyzer analyzer;
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
CreateFrameGenerator(/*width=*/320, /*height=*/240);
VideoFrame frame = CreateFrame(*frame_generator);
frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
{
// `helper` and `sink` has to be destroyed so all frames will be written
// to the disk.
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription);
sink.OnFrame(frame);
}
EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(1)));
test::Y4mFrameReaderImpl frame_reader(
test::JoinFilename(test_directory_, "alice_video_bob_320x240_30.y4m"),
/*width=*/320,
/*height=*/240);
ASSERT_TRUE(frame_reader.Init());
EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
rtc::scoped_refptr<I420BufferInterface> expected_frame =
frame.video_frame_buffer()->ToI420();
double psnr = I420PSNR(*expected_frame, *actual_frame);
double ssim = I420SSIM(*expected_frame, *actual_frame);
// Frames should be equal.
EXPECT_DOUBLE_EQ(ssim, 1.00);
EXPECT_DOUBLE_EQ(psnr, 48);
ExpectOutputFilesCount(1);
}
TEST_F(AnalyzingVideoSinkTest,
VideoFramesAreDumpedCorrectlyWhenSubscriptionChanged) {
VideoSubscription subscription_before;
subscription_before.SubscribeToPeer(
"alice", VideoResolution(/*width=*/1280, /*height=*/720, /*fps=*/30));
VideoSubscription subscription_after;
subscription_after.SubscribeToPeer(
"alice", VideoResolution(/*width=*/640, /*height=*/360, /*fps=*/30));
VideoConfig video_config("alice_video", /*width=*/1280, /*height=*/720,
/*fps=*/30);
video_config.output_dump_options = VideoDumpOptions(test_directory_);
ExampleVideoQualityAnalyzer analyzer;
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
CreateFrameGenerator(/*width=*/1280, /*height=*/720);
VideoFrame frame_before = CreateFrame(*frame_generator);
frame_before.set_id(
analyzer.OnFrameCaptured("alice", "alice_video", frame_before));
VideoFrame frame_after = CreateFrame(*frame_generator);
frame_after.set_id(
analyzer.OnFrameCaptured("alice", "alice_video", frame_after));
{
// `helper` and `sink` has to be destroyed so all frames will be written
// to the disk.
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription_before);
sink.OnFrame(frame_before);
sink.UpdateSubscription(subscription_after);
sink.OnFrame(frame_after);
}
EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2)));
{
test::Y4mFrameReaderImpl frame_reader(
test::JoinFilename(test_directory_, "alice_video_bob_1280x720_30.y4m"),
/*width=*/1280,
/*height=*/720);
ASSERT_TRUE(frame_reader.Init());
EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
rtc::scoped_refptr<I420BufferInterface> expected_frame =
frame_before.video_frame_buffer()->ToI420();
double psnr = I420PSNR(*expected_frame, *actual_frame);
double ssim = I420SSIM(*expected_frame, *actual_frame);
// Frames should be equal.
EXPECT_DOUBLE_EQ(ssim, 1.00);
EXPECT_DOUBLE_EQ(psnr, 48);
}
{
test::Y4mFrameReaderImpl frame_reader(
test::JoinFilename(test_directory_, "alice_video_bob_640x360_30.y4m"),
/*width=*/640,
/*height=*/360);
ASSERT_TRUE(frame_reader.Init());
EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(1));
rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
rtc::scoped_refptr<I420BufferInterface> expected_frame =
frame_after.video_frame_buffer()->ToI420();
double psnr = I420PSNR(*expected_frame, *actual_frame);
double ssim = I420SSIM(*expected_frame, *actual_frame);
// Actual should be downscaled version of expected.
EXPECT_GT(ssim, 0.98);
EXPECT_GT(psnr, 38);
}
ExpectOutputFilesCount(2);
}
TEST_F(AnalyzingVideoSinkTest, VideoFramesIdsAreDumpedWhenRequested) {
VideoSubscription subscription;
subscription.SubscribeToPeer(
"alice", VideoResolution(/*width=*/320, /*height=*/240, /*fps=*/30));
VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
/*fps=*/30);
video_config.output_dump_options =
VideoDumpOptions(test_directory_, /*export_frame_ids=*/true);
ExampleVideoQualityAnalyzer analyzer;
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
CreateFrameGenerator(/*width=*/320, /*height=*/240);
std::vector<std::string> expected_frame_ids;
{
// `helper` and `sink` has to be destroyed so all frames will be written
// to the disk.
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", Clock::GetRealTimeClock(), analyzer, helper,
subscription);
for (int i = 0; i < 10; ++i) {
VideoFrame frame = CreateFrame(*frame_generator);
frame.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame));
expected_frame_ids.push_back(std::to_string(frame.id()));
sink.OnFrame(frame);
}
}
EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(10)));
AssertFrameIdsAre(
test::JoinFilename(test_directory_,
"alice_video_bob_320x240_30.frame_ids.txt"),
expected_frame_ids);
ExpectOutputFilesCount(2);
}
TEST_F(AnalyzingVideoSinkTest,
VideoFramesAndIdsAreDumpedWithFixedFpsWhenRequested) {
GlobalSimulatedTimeController simulated_time(Timestamp::Seconds(100000));
VideoSubscription subscription;
subscription.SubscribeToPeer(
"alice", VideoResolution(/*width=*/320, /*height=*/240, /*fps=*/10));
VideoConfig video_config("alice_video", /*width=*/320, /*height=*/240,
/*fps=*/10);
video_config.output_dump_options =
VideoDumpOptions(test_directory_, /*export_frame_ids=*/true);
video_config.output_dump_use_fixed_framerate = true;
ExampleVideoQualityAnalyzer analyzer;
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
CreateFrameGenerator(/*width=*/320, /*height=*/240);
VideoFrame frame1 = CreateFrame(*frame_generator);
frame1.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame1));
VideoFrame frame2 = CreateFrame(*frame_generator);
frame2.set_id(analyzer.OnFrameCaptured("alice", "alice_video", frame2));
{
// `helper` and `sink` has to be destroyed so all frames will be written
// to the disk.
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", video_config);
AnalyzingVideoSink sink("bob", simulated_time.GetClock(), analyzer, helper,
subscription);
sink.OnFrame(frame1);
// Advance almost 1 second, so the first frame has to be repeated 9 time
// more.
simulated_time.AdvanceTime(TimeDelta::Millis(990));
sink.OnFrame(frame2);
simulated_time.AdvanceTime(TimeDelta::Millis(100));
}
EXPECT_THAT(analyzer.frames_rendered(), Eq(static_cast<uint64_t>(2)));
test::Y4mFrameReaderImpl frame_reader(
test::JoinFilename(test_directory_, "alice_video_bob_320x240_10.y4m"),
/*width=*/320,
/*height=*/240);
ASSERT_TRUE(frame_reader.Init());
EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(11));
for (int i = 0; i < 10; ++i) {
rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
rtc::scoped_refptr<I420BufferInterface> expected_frame =
frame1.video_frame_buffer()->ToI420();
double psnr = I420PSNR(*expected_frame, *actual_frame);
double ssim = I420SSIM(*expected_frame, *actual_frame);
// Frames should be equal.
EXPECT_DOUBLE_EQ(ssim, 1.00);
EXPECT_DOUBLE_EQ(psnr, 48);
}
rtc::scoped_refptr<I420Buffer> actual_frame = frame_reader.ReadFrame();
rtc::scoped_refptr<I420BufferInterface> expected_frame =
frame2.video_frame_buffer()->ToI420();
double psnr = I420PSNR(*expected_frame, *actual_frame);
double ssim = I420SSIM(*expected_frame, *actual_frame);
// Frames should be equal.
EXPECT_DOUBLE_EQ(ssim, 1.00);
EXPECT_DOUBLE_EQ(psnr, 48);
AssertFrameIdsAre(
test::JoinFilename(test_directory_,
"alice_video_bob_320x240_10.frame_ids.txt"),
{std::to_string(frame1.id()), std::to_string(frame1.id()),
std::to_string(frame1.id()), std::to_string(frame1.id()),
std::to_string(frame1.id()), std::to_string(frame1.id()),
std::to_string(frame1.id()), std::to_string(frame1.id()),
std::to_string(frame1.id()), std::to_string(frame1.id()),
std::to_string(frame2.id())});
ExpectOutputFilesCount(2);
}
} // namespace
} // namespace webrtc_pc_e2e
} // namespace webrtc

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2022 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/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h"
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "absl/strings/string_view.h"
#include "api/test/peerconnection_quality_test_fixture.h"
#include "api/test/video/video_frame_writer.h"
#include "rtc_base/synchronization/mutex.h"
namespace webrtc {
namespace webrtc_pc_e2e {
using VideoConfig =
::webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTestFixture::VideoConfig;
void AnalyzingVideoSinksHelper::AddConfig(absl::string_view sender_peer_name,
VideoConfig config) {
MutexLock lock(&mutex_);
auto it = video_configs_.find(*config.stream_label);
if (it == video_configs_.end()) {
std::string stream_label = *config.stream_label;
video_configs_.emplace(
std::move(stream_label),
std::pair{std::string(sender_peer_name), std::move(config)});
} else {
it->second = std::pair{std::string(sender_peer_name), std::move(config)};
}
}
absl::optional<std::pair<std::string, VideoConfig>>
AnalyzingVideoSinksHelper::GetPeerAndConfig(absl::string_view stream_label) {
MutexLock lock(&mutex_);
auto it = video_configs_.find(std::string(stream_label));
if (it == video_configs_.end()) {
return absl::nullopt;
}
return it->second;
}
void AnalyzingVideoSinksHelper::RemoveConfig(absl::string_view stream_label) {
MutexLock lock(&mutex_);
video_configs_.erase(std::string(stream_label));
}
test::VideoFrameWriter* AnalyzingVideoSinksHelper::AddVideoWriter(
std::unique_ptr<test::VideoFrameWriter> video_writer) {
MutexLock lock(&mutex_);
test::VideoFrameWriter* out = video_writer.get();
video_writers_.push_back(std::move(video_writer));
return out;
}
void AnalyzingVideoSinksHelper::CloseAndRemoveVideoWriters(
std::set<test::VideoFrameWriter*> writers_to_close) {
MutexLock lock(&mutex_);
for (auto it = video_writers_.cbegin(); it != video_writers_.cend();) {
if (writers_to_close.find(it->get()) != writers_to_close.end()) {
(*it)->Close();
it = video_writers_.erase(it);
} else {
++it;
}
}
}
void AnalyzingVideoSinksHelper::Clear() {
MutexLock lock(&mutex_);
video_configs_.clear();
for (const auto& video_writer : video_writers_) {
video_writer->Close();
}
video_writers_.clear();
}
} // namespace webrtc_pc_e2e
} // namespace webrtc

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2022 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_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINKS_HELPER_H_
#define TEST_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINKS_HELPER_H_
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/test/peerconnection_quality_test_fixture.h"
#include "api/test/video/video_frame_writer.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread_annotations.h"
namespace webrtc {
namespace webrtc_pc_e2e {
// Registry of known video configs and video writers.
// This class is thread safe.
class AnalyzingVideoSinksHelper {
public:
// Adds config in the registry. If config with such stream label was
// registered before, the new value will override the old one.
void AddConfig(absl::string_view sender_peer_name,
PeerConnectionE2EQualityTestFixture::VideoConfig config);
absl::optional<
std::pair<std::string, PeerConnectionE2EQualityTestFixture::VideoConfig>>
GetPeerAndConfig(absl::string_view stream_label);
// Removes video config for specified stream label. If there are no know video
// config for such stream label - does nothing.
void RemoveConfig(absl::string_view stream_label);
// Takes ownership of the provided video writer. All video writers owned by
// this class will be closed during `AnalyzingVideoSinksHelper` destruction
// and guaranteed to be alive either until explicitly removed by
// `CloseAndRemoveVideoWriters` or until `AnalyzingVideoSinksHelper` is
// destroyed.
//
// Returns pointer to the added writer. Ownership is maintained by
// `AnalyzingVideoSinksHelper`.
test::VideoFrameWriter* AddVideoWriter(
std::unique_ptr<test::VideoFrameWriter> video_writer);
// For each provided `writers_to_close`, if it is known, will close and
// destroy it, otherwise does nothing with it.
void CloseAndRemoveVideoWriters(
std::set<test::VideoFrameWriter*> writers_to_close);
// Removes all added configs and close and removes all added writers.
void Clear();
private:
Mutex mutex_;
std::map<
std::string,
std::pair<std::string, PeerConnectionE2EQualityTestFixture::VideoConfig>>
video_configs_ RTC_GUARDED_BY(mutex_);
std::list<std::unique_ptr<test::VideoFrameWriter>> video_writers_
RTC_GUARDED_BY(mutex_);
};
} // namespace webrtc_pc_e2e
} // namespace webrtc
#endif // TEST_PC_E2E_ANALYZER_VIDEO_ANALYZING_VIDEO_SINKS_HELPER_H_

View File

@ -0,0 +1,163 @@
/*
* Copyright (c) 2022 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/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h"
#include <memory>
#include <string>
#include <utility>
#include "absl/types/optional.h"
#include "api/test/peerconnection_quality_test_fixture.h"
#include "test/gmock.h"
#include "test/gtest.h"
namespace webrtc {
namespace webrtc_pc_e2e {
namespace {
using ::testing::Eq;
using VideoConfig =
::webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTestFixture::VideoConfig;
// Asserts equality of the main fields of the video config. We don't compare
// the full config due to the lack of equality definition for a lot of subtypes.
void AssertConfigsAreEquals(const VideoConfig& actual,
const VideoConfig& expected) {
EXPECT_THAT(actual.stream_label, Eq(expected.stream_label));
EXPECT_THAT(actual.width, Eq(expected.width));
EXPECT_THAT(actual.height, Eq(expected.height));
EXPECT_THAT(actual.fps, Eq(expected.fps));
}
TEST(AnalyzingVideoSinksHelperTest, ConfigsCanBeAdded) {
VideoConfig config("alice_video", /*width=*/1280, /*height=*/720, /*fps=*/30);
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", config);
absl::optional<std::pair<std::string, VideoConfig>> registred_config =
helper.GetPeerAndConfig("alice_video");
ASSERT_TRUE(registred_config.has_value());
EXPECT_THAT(registred_config->first, Eq("alice"));
AssertConfigsAreEquals(registred_config->second, config);
}
TEST(AnalyzingVideoSinksHelperTest, AddingForExistingLabelWillOverwriteValue) {
VideoConfig config_before("alice_video", /*width=*/1280, /*height=*/720,
/*fps=*/30);
VideoConfig config_after("alice_video", /*width=*/640, /*height=*/360,
/*fps=*/15);
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", config_before);
absl::optional<std::pair<std::string, VideoConfig>> registred_config =
helper.GetPeerAndConfig("alice_video");
ASSERT_TRUE(registred_config.has_value());
EXPECT_THAT(registred_config->first, Eq("alice"));
AssertConfigsAreEquals(registred_config->second, config_before);
helper.AddConfig("alice", config_after);
registred_config = helper.GetPeerAndConfig("alice_video");
ASSERT_TRUE(registred_config.has_value());
EXPECT_THAT(registred_config->first, Eq("alice"));
AssertConfigsAreEquals(registred_config->second, config_after);
}
TEST(AnalyzingVideoSinksHelperTest, ConfigsCanBeRemoved) {
VideoConfig config("alice_video", /*width=*/1280, /*height=*/720, /*fps=*/30);
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", config);
ASSERT_TRUE(helper.GetPeerAndConfig("alice_video").has_value());
helper.RemoveConfig("alice_video");
ASSERT_FALSE(helper.GetPeerAndConfig("alice_video").has_value());
}
TEST(AnalyzingVideoSinksHelperTest, RemoveOfNonExistingConfigDontCrash) {
AnalyzingVideoSinksHelper helper;
helper.RemoveConfig("alice_video");
}
TEST(AnalyzingVideoSinksHelperTest, ClearRemovesAllConfigs) {
VideoConfig config1("alice_video", /*width=*/640, /*height=*/360, /*fps=*/30);
VideoConfig config2("bob_video", /*width=*/640, /*height=*/360, /*fps=*/30);
AnalyzingVideoSinksHelper helper;
helper.AddConfig("alice", config1);
helper.AddConfig("bob", config2);
ASSERT_TRUE(helper.GetPeerAndConfig("alice_video").has_value());
ASSERT_TRUE(helper.GetPeerAndConfig("bob_video").has_value());
helper.Clear();
ASSERT_FALSE(helper.GetPeerAndConfig("alice_video").has_value());
ASSERT_FALSE(helper.GetPeerAndConfig("bob_video").has_value());
}
struct TestVideoFrameWriterFactory {
int closed_writers_count = 0;
int deleted_writers_count = 0;
std::unique_ptr<test::VideoFrameWriter> CreateWriter() {
return std::make_unique<TestVideoFrameWriter>(this);
}
private:
class TestVideoFrameWriter : public test::VideoFrameWriter {
public:
explicit TestVideoFrameWriter(TestVideoFrameWriterFactory* factory)
: factory_(factory) {}
~TestVideoFrameWriter() override { factory_->deleted_writers_count++; }
bool WriteFrame(const VideoFrame& frame) override { return true; }
void Close() override { factory_->closed_writers_count++; }
private:
TestVideoFrameWriterFactory* factory_;
};
};
TEST(AnalyzingVideoSinksHelperTest, RemovingWritersCloseAndDestroyAllOfThem) {
TestVideoFrameWriterFactory factory;
AnalyzingVideoSinksHelper helper;
test::VideoFrameWriter* writer1 =
helper.AddVideoWriter(factory.CreateWriter());
test::VideoFrameWriter* writer2 =
helper.AddVideoWriter(factory.CreateWriter());
helper.CloseAndRemoveVideoWriters({writer1, writer2});
EXPECT_THAT(factory.closed_writers_count, Eq(2));
EXPECT_THAT(factory.deleted_writers_count, Eq(2));
}
TEST(AnalyzingVideoSinksHelperTest, ClearCloseAndDestroyAllWriters) {
TestVideoFrameWriterFactory factory;
AnalyzingVideoSinksHelper helper;
helper.AddVideoWriter(factory.CreateWriter());
helper.AddVideoWriter(factory.CreateWriter());
helper.Clear();
EXPECT_THAT(factory.closed_writers_count, Eq(2));
EXPECT_THAT(factory.deleted_writers_count, Eq(2));
}
} // namespace
} // namespace webrtc_pc_e2e
} // namespace webrtc

View File

@ -32,6 +32,7 @@ class VideoFrameIdsWriter final : public test::VideoFrameWriter {
explicit VideoFrameIdsWriter(absl::string_view file_name)
: file_name_(file_name) {
output_file_ = fopen(file_name_.c_str(), "wb");
RTC_LOG(LS_INFO) << "Writing VideoFrame IDs into " << file_name_;
RTC_CHECK(output_file_ != nullptr)
<< "Failed to open file to dump frame ids for writing: " << file_name_;
}
@ -50,6 +51,7 @@ class VideoFrameIdsWriter final : public test::VideoFrameWriter {
void Close() override {
if (output_file_ != nullptr) {
RTC_LOG(LS_INFO) << "Closing file for VideoFrame IDs: " << file_name_;
fclose(output_file_);
output_file_ = nullptr;
}

View File

@ -24,6 +24,7 @@
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
#include "system_wrappers/include/clock.h"
#include "test/pc/e2e/analyzer/video/analyzing_video_sink.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"
@ -37,6 +38,8 @@ namespace {
using EmulatedSFUConfigMap =
::webrtc::webrtc_pc_e2e::QualityAnalyzingVideoEncoder::EmulatedSFUConfigMap;
using VideoSubscription = ::webrtc::webrtc_pc_e2e::
PeerConnectionE2EQualityTestFixture::VideoSubscription;
class AnalyzingFramePreprocessor
: public test::TestVideoCapturer::FramePreprocessor {
@ -128,6 +131,7 @@ VideoQualityAnalyzerInjectionHelper::CreateFramePreprocessor(
test::VideoRenderer::Create((*config.stream_label + "-capture").c_str(),
config.width, config.height)));
}
sinks_helper_.AddConfig(peer_name, config);
{
MutexLock lock(&mutex_);
known_video_configs_.insert({*config.stream_label, config});
@ -140,7 +144,16 @@ VideoQualityAnalyzerInjectionHelper::CreateFramePreprocessor(
std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>>
VideoQualityAnalyzerInjectionHelper::CreateVideoSink(
absl::string_view peer_name) {
return std::make_unique<AnalyzingVideoSink>(peer_name, this);
return std::make_unique<AnalyzingVideoSink2>(peer_name, this);
}
std::unique_ptr<AnalyzingVideoSink>
VideoQualityAnalyzerInjectionHelper::CreateVideoSink(
absl::string_view peer_name,
const PeerConnectionE2EQualityTestFixture::VideoSubscription&
subscription) {
return std::make_unique<AnalyzingVideoSink>(peer_name, clock_, *analyzer_,
sinks_helper_, subscription);
}
void VideoQualityAnalyzerInjectionHelper::Start(
@ -181,6 +194,7 @@ void VideoQualityAnalyzerInjectionHelper::Stop() {
video_writer->Close();
}
video_writers_.clear();
sinks_helper_.Clear();
}
void VideoQualityAnalyzerInjectionHelper::OnFrame(absl::string_view peer_name,

View File

@ -29,6 +29,8 @@
#include "api/video_codecs/video_encoder_factory.h"
#include "rtc_base/synchronization/mutex.h"
#include "system_wrappers/include/clock.h"
#include "test/pc/e2e/analyzer/video/analyzing_video_sink.h"
#include "test/pc/e2e/analyzer/video/analyzing_video_sinks_helper.h"
#include "test/pc/e2e/analyzer/video/encoded_image_data_injector.h"
#include "test/pc/e2e/analyzer/video/quality_analyzing_video_encoder.h"
#include "test/test_video_capturer.h"
@ -77,6 +79,10 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface {
// into that file.
std::unique_ptr<rtc::VideoSinkInterface<VideoFrame>> CreateVideoSink(
absl::string_view peer_name);
std::unique_ptr<AnalyzingVideoSink> CreateVideoSink(
absl::string_view peer_name,
const PeerConnectionE2EQualityTestFixture::VideoSubscription&
subscription);
void Start(std::string test_case_name,
rtc::ArrayView<const std::string> peer_names,
@ -101,12 +107,13 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface {
void Stop();
private:
class AnalyzingVideoSink final : public rtc::VideoSinkInterface<VideoFrame> {
// Deprecated, to be removed when old API isn't used anymore.
class AnalyzingVideoSink2 final : public rtc::VideoSinkInterface<VideoFrame> {
public:
explicit AnalyzingVideoSink(absl::string_view peer_name,
VideoQualityAnalyzerInjectionHelper* helper)
explicit AnalyzingVideoSink2(absl::string_view peer_name,
VideoQualityAnalyzerInjectionHelper* helper)
: peer_name_(peer_name), helper_(helper) {}
~AnalyzingVideoSink() override = default;
~AnalyzingVideoSink2() override = default;
void OnFrame(const VideoFrame& frame) override {
helper_->OnFrame(peer_name_, frame);
@ -147,6 +154,7 @@ class VideoQualityAnalyzerInjectionHelper : public StatsObserverInterface {
std::vector<std::unique_ptr<test::VideoFrameWriter>> video_writers_;
AnalyzingVideoSinksHelper sinks_helper_;
Mutex mutex_;
int peers_count_ RTC_GUARDED_BY(mutex_);
// Map from stream label to the video config.

View File

@ -48,8 +48,8 @@ namespace {
using ::webrtc::test::ImprovementDirection;
using ::webrtc::test::Unit;
using VideoConfig = PeerConnectionE2EQualityTestFixture::VideoConfig;
using VideoCodecConfig = PeerConnectionE2EQualityTestFixture::VideoCodecConfig;
using VideoConfig =
::webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTestFixture::VideoConfig;
constexpr TimeDelta kDefaultTimeout = TimeDelta::Seconds(10);
constexpr char kSignalThreadName[] = "signaling_thread";
@ -262,11 +262,15 @@ void PeerConnectionE2EQualityTest::Run(RunParams run_params) {
RemotePeerAudioConfig::Create(bob_configurer->params()->audio_config);
absl::optional<RemotePeerAudioConfig> bob_remote_audio_config =
RemotePeerAudioConfig::Create(alice_configurer->params()->audio_config);
// Copy Alice and Bob video configs and names to correctly pass them into
// lambdas.
// Copy Alice and Bob video configs, subscriptions and names to correctly pass
// them into lambdas.
VideoSubscription alice_subscription =
alice_configurer->configurable_params()->video_subscription;
std::vector<VideoConfig> alice_video_configs =
alice_configurer->configurable_params()->video_configs;
std::string alice_name = alice_configurer->params()->name.value();
VideoSubscription bob_subscription =
alice_configurer->configurable_params()->video_subscription;
std::vector<VideoConfig> bob_video_configs =
bob_configurer->configurable_params()->video_configs;
std::string bob_name = bob_configurer->params()->name.value();
@ -277,18 +281,20 @@ void PeerConnectionE2EQualityTest::Run(RunParams run_params) {
alice_ = test_peer_factory.CreateTestPeer(
std::move(alice_configurer),
std::make_unique<FixturePeerConnectionObserver>(
[this, bob_video_configs, alice_name](
[this, alice_name, alice_subscription, bob_video_configs](
rtc::scoped_refptr<RtpTransceiverInterface> transceiver) {
OnTrackCallback(alice_name, transceiver, bob_video_configs);
OnTrackCallback(alice_name, alice_subscription, transceiver,
bob_video_configs);
},
[this]() { StartVideo(alice_video_sources_); }),
alice_remote_audio_config, run_params.echo_emulation_config);
bob_ = test_peer_factory.CreateTestPeer(
std::move(bob_configurer),
std::make_unique<FixturePeerConnectionObserver>(
[this, alice_video_configs,
bob_name](rtc::scoped_refptr<RtpTransceiverInterface> transceiver) {
OnTrackCallback(bob_name, transceiver, alice_video_configs);
[this, bob_name, bob_subscription, alice_video_configs](
rtc::scoped_refptr<RtpTransceiverInterface> transceiver) {
OnTrackCallback(bob_name, bob_subscription, transceiver,
alice_video_configs);
},
[this]() { StartVideo(bob_video_sources_); }),
bob_remote_audio_config, run_params.echo_emulation_config);
@ -448,6 +454,7 @@ std::string PeerConnectionE2EQualityTest::GetFieldTrials(
void PeerConnectionE2EQualityTest::OnTrackCallback(
absl::string_view peer_name,
VideoSubscription peer_subscription,
rtc::scoped_refptr<RtpTransceiverInterface> transceiver,
std::vector<VideoConfig> remote_video_configs) {
const rtc::scoped_refptr<MediaStreamTrackInterface>& track =

View File

@ -96,6 +96,7 @@ class PeerConnectionE2EQualityTest
// enabled in Run().
std::string GetFieldTrials(const RunParams& run_params);
void OnTrackCallback(absl::string_view peer_name,
VideoSubscription peer_subscription,
rtc::scoped_refptr<RtpTransceiverInterface> transceiver,
std::vector<VideoConfig> remote_video_configs);
// Have to be run on the signaling thread.

View File

@ -0,0 +1,145 @@
/*
* Copyright (c) 2022 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/pc/e2e/peer_connection_quality_test.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "api/test/create_network_emulation_manager.h"
#include "api/test/metrics/global_metrics_logger_and_exporter.h"
#include "api/test/network_emulation_manager.h"
#include "api/test/peerconnection_quality_test_fixture.h"
#include "api/units/time_delta.h"
#include "rtc_base/time_utils.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
#include "test/testsupport/frame_reader.h"
namespace webrtc {
namespace webrtc_pc_e2e {
namespace {
using ::testing::Eq;
using ::testing::Test;
using RunParams =
::webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTestFixture::RunParams;
using VideoConfig =
::webrtc::webrtc_pc_e2e::PeerConnectionE2EQualityTestFixture::VideoConfig;
using PeerConfigurer = ::webrtc::webrtc_pc_e2e::
PeerConnectionE2EQualityTestFixture::PeerConfigurer;
using VideoDumpOptions = ::webrtc::webrtc_pc_e2e::
PeerConnectionE2EQualityTestFixture::VideoDumpOptions;
// Remove files and directories in a directory non-recursively.
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";
} else {
ASSERT_TRUE(dir_content.has_value()) << "Test directory is empty!";
EXPECT_EQ(dir_content->size(), expected_output_files_count);
for (const auto& entry : *dir_content) {
if (test::DirExists(entry)) {
EXPECT_TRUE(test::RemoveDir(entry))
<< "Failed to remove sub directory: " << entry;
} else if (test::FileExists(entry)) {
EXPECT_TRUE(test::RemoveFile(entry))
<< "Failed to remove file: " << entry;
} else {
FAIL() << "Can't remove unknown file type: " << entry;
}
}
}
EXPECT_TRUE(test::RemoveDir(dir)) << "Failed to remove directory: " << dir;
}
class PeerConnectionE2EQualityTestTest : public Test {
protected:
~PeerConnectionE2EQualityTestTest() override = default;
void SetUp() override {
// Create an empty temporary directory for this test.
test_directory_ = test::JoinFilename(
test::OutputPath(),
"TestDir_PeerConnectionE2EQualityTestTest_" +
std::string(
testing::UnitTest::GetInstance()->current_test_info()->name()));
test::CreateDir(test_directory_);
}
void TearDown() override {
CleanDir(test_directory_, expected_output_files_count_);
}
void ExpectOutputFilesCount(size_t count) {
expected_output_files_count_ = count;
}
std::string test_directory_;
size_t expected_output_files_count_ = 0;
};
TEST_F(PeerConnectionE2EQualityTestTest, OutputVideoIsDumpedWhenRequested) {
std::unique_ptr<NetworkEmulationManager> network_emulation =
CreateNetworkEmulationManager(TimeMode::kSimulated);
PeerConnectionE2EQualityTest fixture(
"test_case", *network_emulation->time_controller(),
/*audio_quality_analyzer=*/nullptr, /*video_quality_analyzer=*/nullptr,
test::GetGlobalMetricsLogger());
EmulatedEndpoint* alice_endpoint =
network_emulation->CreateEndpoint(EmulatedEndpointConfig());
EmulatedEndpoint* bob_endpoint =
network_emulation->CreateEndpoint(EmulatedEndpointConfig());
network_emulation->CreateRoute(
alice_endpoint, {network_emulation->CreateUnconstrainedEmulatedNode()},
bob_endpoint);
network_emulation->CreateRoute(
bob_endpoint, {network_emulation->CreateUnconstrainedEmulatedNode()},
alice_endpoint);
EmulatedNetworkManagerInterface* alice_network =
network_emulation->CreateEmulatedNetworkManagerInterface(
{alice_endpoint});
EmulatedNetworkManagerInterface* bob_network =
network_emulation->CreateEmulatedNetworkManagerInterface({bob_endpoint});
fixture.AddPeer(
alice_network->network_dependencies(), [&](PeerConfigurer* peer) {
peer->SetName("alice");
VideoConfig video("alice_video", 320, 180, 15);
video.output_dump_options = VideoDumpOptions(test_directory_);
peer->AddVideoConfig(std::move(video));
});
fixture.AddPeer(bob_network->network_dependencies(),
[&](PeerConfigurer* peer) { peer->SetName("bob"); });
fixture.Run(RunParams(TimeDelta::Seconds(2)));
test::Y4mFrameReaderImpl frame_reader(
test::JoinFilename(test_directory_, "alice_video_bob_320x180_15.y4m"),
/*width=*/320,
/*height=*/180);
ASSERT_TRUE(frame_reader.Init());
EXPECT_THAT(frame_reader.NumberOfFrames(), Eq(31)); // 2 seconds 15 fps + 1
ExpectOutputFilesCount(1);
}
} // namespace
} // namespace webrtc_pc_e2e
} // namespace webrtc