blob: 93b13f166f5b4c8a0491f1c3b6eb034366a9562a [file] [log] [blame]
/*
* Copyright (c) 2020 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/default_video_quality_analyzer.h"
#include <algorithm>
#include <map>
#include <memory>
#include <vector>
#include "api/rtp_packet_info.h"
#include "api/rtp_packet_infos.h"
#include "api/test/create_frame_generator.h"
#include "api/test/metrics/global_metrics_logger_and_exporter.h"
#include "api/test/time_controller.h"
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "api/video/encoded_image.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_frame.h"
#include "common_video/libyuv/include/webrtc_libyuv.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_tools/frame_analyzer/video_geometry_aligner.h"
#include "system_wrappers/include/sleep.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h"
#include "test/time_controller/simulated_time_controller.h"
namespace webrtc {
namespace {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Test;
using ::testing::TestWithParam;
using ::testing::ValuesIn;
using StatsSample = ::webrtc::SamplesStatsCounter::StatsSample;
constexpr int kAnalyzerMaxThreadsCount = 1;
constexpr TimeDelta kMaxFramesInFlightStorageDuration = TimeDelta::Seconds(3);
constexpr int kFrameWidth = 320;
constexpr int kFrameHeight = 240;
constexpr double kMaxSsim = 1;
constexpr char kStreamLabel[] = "video-stream";
constexpr char kSenderPeerName[] = "alice";
constexpr char kReceiverPeerName[] = "bob";
DefaultVideoQualityAnalyzerOptions AnalyzerOptionsForTest() {
DefaultVideoQualityAnalyzerOptions options;
options.compute_psnr = false;
options.compute_ssim = false;
options.adjust_cropping_before_comparing_frames = false;
options.max_frames_storage_duration = kMaxFramesInFlightStorageDuration;
return options;
}
VideoFrame NextFrame(test::FrameGeneratorInterface* frame_generator,
int64_t timestamp_us) {
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)
.set_timestamp_us(timestamp_us)
.build();
}
EncodedImage FakeEncode(const VideoFrame& frame) {
EncodedImage image;
std::vector<RtpPacketInfo> packet_infos;
packet_infos.push_back(RtpPacketInfo(
/*ssrc=*/1,
/*csrcs=*/{},
/*rtp_timestamp=*/frame.rtp_timestamp(),
/*receive_time=*/Timestamp::Micros(frame.timestamp_us() + 10000)));
image.SetPacketInfos(RtpPacketInfos(packet_infos));
return image;
}
VideoFrame DeepCopy(const VideoFrame& frame) {
VideoFrame copy = frame;
copy.set_video_frame_buffer(
I420Buffer::Copy(*frame.video_frame_buffer()->ToI420()));
return copy;
}
std::vector<StatsSample> GetSortedSamples(const SamplesStatsCounter& counter) {
rtc::ArrayView<const StatsSample> view = counter.GetTimedSamples();
std::vector<StatsSample> out(view.begin(), view.end());
std::sort(out.begin(), out.end(),
[](const StatsSample& a, const StatsSample& b) {
return a.time < b.time;
});
return out;
}
std::vector<double> GetTimeSortedValues(const SamplesStatsCounter& counter) {
rtc::ArrayView<const StatsSample> view = counter.GetTimedSamples();
std::vector<StatsSample> sorted(view.begin(), view.end());
std::sort(sorted.begin(), sorted.end(),
[](const StatsSample& a, const StatsSample& b) {
return a.time < b.time;
});
std::vector<double> out;
out.reserve(sorted.size());
for (const StatsSample& sample : sorted) {
out.push_back(sample.value);
}
return out;
}
void ExpectRateIs(const SamplesRateCounter& rate_couter, double expected_rate) {
ASSERT_FALSE(rate_couter.IsEmpty());
EXPECT_NEAR(rate_couter.GetEventsPerSecond(), expected_rate, 1e-5);
}
std::string ToString(const std::vector<StatsSample>& values) {
rtc::StringBuilder out;
for (const auto& v : values) {
out << "{ time_ms=" << v.time.ms() << "; value=" << v.value << "}, ";
}
return out.str();
}
void FakeCPULoad() {
std::vector<int> temp(1000000);
for (size_t i = 0; i < temp.size(); ++i) {
temp[i] = rand();
}
std::sort(temp.begin(), temp.end());
ASSERT_TRUE(std::is_sorted(temp.begin(), temp.end()));
}
void PassFramesThroughAnalyzerSenderOnly(
DefaultVideoQualityAnalyzer& analyzer,
absl::string_view sender,
std::vector<absl::string_view> stream_labels,
std::vector<absl::string_view> receivers,
int frames_count,
test::FrameGeneratorInterface& frame_generator,
int interframe_delay_ms = 0,
TimeController* time_controller = nullptr) {
for (int i = 0; i < frames_count; ++i) {
for (absl::string_view stream_label : stream_labels) {
VideoFrame frame = NextFrame(&frame_generator, /*timestamp_us=*/1);
uint16_t frame_id =
analyzer.OnFrameCaptured(sender, std::string(stream_label), frame);
frame.set_id(frame_id);
analyzer.OnFramePreEncode(sender, frame);
analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
if (i < frames_count - 1 && interframe_delay_ms > 0) {
if (time_controller == nullptr) {
SleepMs(interframe_delay_ms);
} else {
time_controller->AdvanceTime(TimeDelta::Millis(interframe_delay_ms));
}
}
}
}
void PassFramesThroughAnalyzer(DefaultVideoQualityAnalyzer& analyzer,
absl::string_view sender,
std::vector<absl::string_view> stream_labels,
std::vector<absl::string_view> receivers,
int frames_count,
test::FrameGeneratorInterface& frame_generator,
int interframe_delay_ms = 0,
TimeController* time_controller = nullptr) {
for (int i = 0; i < frames_count; ++i) {
for (absl::string_view stream_label : stream_labels) {
VideoFrame frame = NextFrame(&frame_generator, /*timestamp_us=*/1);
uint16_t frame_id =
analyzer.OnFrameCaptured(sender, std::string(stream_label), frame);
frame.set_id(frame_id);
analyzer.OnFramePreEncode(sender, frame);
analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
for (absl::string_view receiver : receivers) {
VideoFrame received_frame = DeepCopy(frame);
analyzer.OnFramePreDecode(receiver, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(receiver, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(receiver, received_frame);
}
}
if (i < frames_count - 1 && interframe_delay_ms > 0) {
if (time_controller == nullptr) {
SleepMs(interframe_delay_ms);
} else {
time_controller->AdvanceTime(TimeDelta::Millis(interframe_delay_ms));
}
}
}
}
TEST(DefaultVideoQualityAnalyzerTest, NormalScenario) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
AnalyzerOptionsForTest());
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
std::map<uint16_t, VideoFrame> captured_frames;
std::vector<uint16_t> frames_order;
for (int i = 0; i < 10; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), i);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
frames_order.push_back(frame.id());
captured_frames.insert({frame.id(), frame});
analyzer.OnFramePreEncode(kSenderPeerName, frame);
analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
for (size_t i = 1; i < frames_order.size(); i += 2) {
uint16_t frame_id = frames_order.at(i);
VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0);
EXPECT_EQ(stats.comparisons_done, 10);
std::vector<StatsSample> frames_in_flight_sizes =
GetSortedSamples(stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 0)
<< ToString(frames_in_flight_sizes);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 10);
EXPECT_EQ(frame_counters.received, 5);
EXPECT_EQ(frame_counters.decoded, 5);
EXPECT_EQ(frame_counters.rendered, 5);
EXPECT_EQ(frame_counters.dropped, 5);
}
TEST(DefaultVideoQualityAnalyzerTest, OneFrameReceivedTwice) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
AnalyzerOptionsForTest());
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
VideoFrame captured_frame = NextFrame(frame_generator.get(), 0);
captured_frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, captured_frame));
analyzer.OnFramePreEncode(kSenderPeerName, captured_frame);
analyzer.OnFrameEncoded(kSenderPeerName, captured_frame.id(),
FakeEncode(captured_frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
VideoFrame received_frame = DeepCopy(captured_frame);
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
received_frame = DeepCopy(captured_frame);
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0);
EXPECT_EQ(stats.comparisons_done, 1);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 1);
EXPECT_EQ(frame_counters.received, 1);
EXPECT_EQ(frame_counters.decoded, 1);
EXPECT_EQ(frame_counters.rendered, 1);
EXPECT_EQ(frame_counters.dropped, 0);
}
TEST(DefaultVideoQualityAnalyzerTest, NormalScenario2Receivers) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
constexpr char kAlice[] = "alice";
constexpr char kBob[] = "bob";
constexpr char kCharlie[] = "charlie";
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
AnalyzerOptionsForTest());
analyzer.Start("test_case", std::vector<std::string>{kAlice, kBob, kCharlie},
kAnalyzerMaxThreadsCount);
std::map<uint16_t, VideoFrame> captured_frames;
std::vector<uint16_t> frames_order;
for (int i = 0; i < 10; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), i);
frame.set_id(analyzer.OnFrameCaptured(kAlice, kStreamLabel, frame));
frames_order.push_back(frame.id());
captured_frames.insert({frame.id(), frame});
analyzer.OnFramePreEncode(kAlice, frame);
SleepMs(20);
analyzer.OnFrameEncoded(kAlice, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
SleepMs(50);
for (size_t i = 1; i < frames_order.size(); i += 2) {
uint16_t frame_id = frames_order.at(i);
VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kBob, received_frame.id(),
FakeEncode(received_frame));
SleepMs(30);
analyzer.OnFrameDecoded(kBob, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
SleepMs(10);
analyzer.OnFrameRendered(kBob, received_frame);
}
for (size_t i = 1; i < frames_order.size(); i += 2) {
uint16_t frame_id = frames_order.at(i);
VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kCharlie, received_frame.id(),
FakeEncode(received_frame));
SleepMs(40);
analyzer.OnFrameDecoded(kCharlie, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
SleepMs(5);
analyzer.OnFrameRendered(kCharlie, received_frame);
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(analyzer_stats.memory_overloaded_comparisons_done, 0);
EXPECT_EQ(analyzer_stats.comparisons_done, 20);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 10);
EXPECT_EQ(frame_counters.received, 10);
EXPECT_EQ(frame_counters.decoded, 10);
EXPECT_EQ(frame_counters.rendered, 10);
EXPECT_EQ(frame_counters.dropped, 10);
VideoStreamsInfo streams_info = analyzer.GetKnownStreams();
EXPECT_EQ(streams_info.GetStreams(), std::set<std::string>{kStreamLabel});
EXPECT_EQ(streams_info.GetStreams(kAlice),
std::set<std::string>{kStreamLabel});
EXPECT_EQ(streams_info.GetSender(kStreamLabel), kAlice);
EXPECT_EQ(streams_info.GetReceivers(kStreamLabel),
(std::set<std::string>{kBob, kCharlie}));
EXPECT_EQ(streams_info.GetStatsKeys().size(), 2lu);
for (auto stream_key : streams_info.GetStatsKeys()) {
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(stream_key);
// On some devices the pipeline can be too slow, so we actually can't
// force real constraints here. Lets just check, that at least 1
// frame passed whole pipeline.
EXPECT_GE(stream_conters.captured, 10);
EXPECT_GE(stream_conters.pre_encoded, 10);
EXPECT_GE(stream_conters.encoded, 10);
EXPECT_GE(stream_conters.received, 5);
EXPECT_GE(stream_conters.decoded, 5);
EXPECT_GE(stream_conters.rendered, 5);
EXPECT_GE(stream_conters.dropped, 5);
}
std::map<StatsKey, StreamStats> stats = analyzer.GetStats();
const StatsKey kAliceBobStats(kStreamLabel, kBob);
const StatsKey kAliceCharlieStats(kStreamLabel, kCharlie);
EXPECT_EQ(stats.size(), 2lu);
{
auto it = stats.find(kAliceBobStats);
EXPECT_FALSE(it == stats.end());
ASSERT_FALSE(it->second.encode_time_ms.IsEmpty());
EXPECT_GE(it->second.encode_time_ms.GetMin(), 20);
ASSERT_FALSE(it->second.decode_time_ms.IsEmpty());
EXPECT_GE(it->second.decode_time_ms.GetMin(), 30);
ASSERT_FALSE(it->second.resolution_of_decoded_frame.IsEmpty());
EXPECT_GE(it->second.resolution_of_decoded_frame.GetMin(),
kFrameWidth * kFrameHeight - 1);
EXPECT_LE(it->second.resolution_of_decoded_frame.GetMax(),
kFrameWidth * kFrameHeight + 1);
}
{
auto it = stats.find(kAliceCharlieStats);
EXPECT_FALSE(it == stats.end());
ASSERT_FALSE(it->second.encode_time_ms.IsEmpty());
EXPECT_GE(it->second.encode_time_ms.GetMin(), 20);
ASSERT_FALSE(it->second.decode_time_ms.IsEmpty());
EXPECT_GE(it->second.decode_time_ms.GetMin(), 30);
ASSERT_FALSE(it->second.resolution_of_decoded_frame.IsEmpty());
EXPECT_GE(it->second.resolution_of_decoded_frame.GetMin(),
kFrameWidth * kFrameHeight - 1);
EXPECT_LE(it->second.resolution_of_decoded_frame.GetMax(),
kFrameWidth * kFrameHeight + 1);
}
}
// Test the case which can happen when SFU is switching from one layer to
// another, so the same frame can be received twice by the same peer.
TEST(DefaultVideoQualityAnalyzerTest,
OneFrameReceivedTwiceBySamePeerWith2Receivers) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
constexpr char kAlice[] = "alice";
constexpr char kBob[] = "bob";
constexpr char kCharlie[] = "charlie";
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
AnalyzerOptionsForTest());
analyzer.Start("test_case", std::vector<std::string>{kAlice, kBob, kCharlie},
kAnalyzerMaxThreadsCount);
VideoFrame captured_frame = NextFrame(frame_generator.get(), 0);
captured_frame.set_id(
analyzer.OnFrameCaptured(kAlice, kStreamLabel, captured_frame));
analyzer.OnFramePreEncode(kAlice, captured_frame);
analyzer.OnFrameEncoded(kAlice, captured_frame.id(),
FakeEncode(captured_frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
VideoFrame received_frame = DeepCopy(captured_frame);
analyzer.OnFramePreDecode(kBob, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kBob, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kBob, received_frame);
received_frame = DeepCopy(captured_frame);
analyzer.OnFramePreDecode(kBob, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kBob, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kBob, received_frame);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0);
// We have 2 comparisons here because 1 for the frame received by Bob and
// 1 for the frame in flight from Alice to Charlie.
EXPECT_EQ(stats.comparisons_done, 2);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 1);
EXPECT_EQ(frame_counters.received, 1);
EXPECT_EQ(frame_counters.decoded, 1);
EXPECT_EQ(frame_counters.rendered, 1);
EXPECT_EQ(frame_counters.dropped, 0);
}
TEST(DefaultVideoQualityAnalyzerTest, HeavyQualityMetricsFromEqualFrames) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions analyzer_options;
analyzer_options.compute_psnr = true;
analyzer_options.compute_ssim = true;
analyzer_options.adjust_cropping_before_comparing_frames = false;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
analyzer_options);
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
for (int i = 0; i < 10; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), i);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
analyzer.OnFramePreEncode(kSenderPeerName, frame);
analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
VideoFrame received_frame = DeepCopy(frame);
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
}
// Give analyzer some time to process frames on async thread. Heavy metrics
// computation is turned on, so giving some extra time to be sure that
// computatio have ended.
SleepMs(500);
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0);
EXPECT_EQ(stats.comparisons_done, 10);
std::vector<StatsSample> frames_in_flight_sizes =
GetSortedSamples(stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 0)
<< ToString(frames_in_flight_sizes);
std::map<StatsKey, StreamStats> stream_stats = analyzer.GetStats();
const StatsKey kAliceBobStats(kStreamLabel, kReceiverPeerName);
EXPECT_EQ(stream_stats.size(), 1lu);
auto it = stream_stats.find(kAliceBobStats);
EXPECT_GE(it->second.psnr.GetMin(), kPerfectPSNR);
EXPECT_GE(it->second.ssim.GetMin(), kMaxSsim);
}
TEST(DefaultVideoQualityAnalyzerTest,
HeavyQualityMetricsFromShiftedFramesWithAdjustment) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions analyzer_options;
analyzer_options.compute_psnr = true;
analyzer_options.compute_ssim = true;
analyzer_options.adjust_cropping_before_comparing_frames = true;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
analyzer_options);
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
for (int i = 0; i < 10; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), i);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
analyzer.OnFramePreEncode(kSenderPeerName, frame);
analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
VideoFrame received_frame = frame;
// Shift frame by a few pixels.
test::CropRegion crop_region{0, 1, 3, 0};
rtc::scoped_refptr<VideoFrameBuffer> cropped_buffer =
CropAndZoom(crop_region, received_frame.video_frame_buffer()->ToI420());
received_frame.set_video_frame_buffer(cropped_buffer);
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
}
// Give analyzer some time to process frames on async thread. Heavy metrics
// computation is turned on, so giving some extra time to be sure that
// computatio have ended.
SleepMs(500);
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0);
EXPECT_EQ(stats.comparisons_done, 10);
std::vector<StatsSample> frames_in_flight_sizes =
GetSortedSamples(stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 0)
<< ToString(frames_in_flight_sizes);
std::map<StatsKey, StreamStats> stream_stats = analyzer.GetStats();
const StatsKey kAliceBobStats(kStreamLabel, kReceiverPeerName);
EXPECT_EQ(stream_stats.size(), 1lu);
auto it = stream_stats.find(kAliceBobStats);
EXPECT_GE(it->second.psnr.GetMin(), kPerfectPSNR);
EXPECT_GE(it->second.ssim.GetMin(), kMaxSsim);
}
TEST(DefaultVideoQualityAnalyzerTest, CpuUsage) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
AnalyzerOptionsForTest());
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
std::map<uint16_t, VideoFrame> captured_frames;
std::vector<uint16_t> frames_order;
for (int i = 0; i < 10; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), i);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
frames_order.push_back(frame.id());
captured_frames.insert({frame.id(), frame});
analyzer.OnFramePreEncode(kSenderPeerName, frame);
analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
// Windows CPU clock has low accuracy. We need to fake some additional load to
// be sure that the clock ticks (https://bugs.webrtc.org/12249).
FakeCPULoad();
for (size_t i = 1; i < frames_order.size(); i += 2) {
uint16_t frame_id = frames_order.at(i);
VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
double cpu_usage = analyzer.GetCpuUsagePercent();
ASSERT_GT(cpu_usage, 0);
SleepMs(100);
analyzer.Stop();
EXPECT_EQ(analyzer.GetCpuUsagePercent(), cpu_usage);
}
TEST(DefaultVideoQualityAnalyzerTest, RuntimeParticipantsAdding) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
constexpr char kAlice[] = "alice";
constexpr char kBob[] = "bob";
constexpr char kCharlie[] = "charlie";
constexpr char kKatie[] = "katie";
constexpr int kFramesCount = 9;
constexpr int kOneThirdFrames = kFramesCount / 3;
constexpr int kTwoThirdFrames = 2 * kOneThirdFrames;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
AnalyzerOptionsForTest());
analyzer.Start("test_case", {}, kAnalyzerMaxThreadsCount);
std::map<uint16_t, VideoFrame> captured_frames;
std::vector<uint16_t> frames_order;
analyzer.RegisterParticipantInCall(kAlice);
analyzer.RegisterParticipantInCall(kBob);
// Alice is sending frames.
for (int i = 0; i < kFramesCount; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), i);
frame.set_id(analyzer.OnFrameCaptured(kAlice, kStreamLabel, frame));
frames_order.push_back(frame.id());
captured_frames.insert({frame.id(), frame});
analyzer.OnFramePreEncode(kAlice, frame);
analyzer.OnFrameEncoded(kAlice, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
// Bob receives one third of the sent frames.
for (int i = 0; i < kOneThirdFrames; ++i) {
uint16_t frame_id = frames_order.at(i);
VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kBob, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kBob, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kBob, received_frame);
}
analyzer.RegisterParticipantInCall(kCharlie);
analyzer.RegisterParticipantInCall(kKatie);
// New participants were dynamically added. Bob and Charlie receive second
// third of the sent frames. Katie drops the frames.
for (int i = kOneThirdFrames; i < kTwoThirdFrames; ++i) {
uint16_t frame_id = frames_order.at(i);
VideoFrame bob_received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kBob, bob_received_frame.id(),
FakeEncode(bob_received_frame));
analyzer.OnFrameDecoded(kBob, bob_received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kBob, bob_received_frame);
VideoFrame charlie_received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kCharlie, charlie_received_frame.id(),
FakeEncode(charlie_received_frame));
analyzer.OnFrameDecoded(kCharlie, charlie_received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kCharlie, charlie_received_frame);
}
// Bob, Charlie and Katie receive the rest of the sent frames.
for (int i = kTwoThirdFrames; i < kFramesCount; ++i) {
uint16_t frame_id = frames_order.at(i);
VideoFrame bob_received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kBob, bob_received_frame.id(),
FakeEncode(bob_received_frame));
analyzer.OnFrameDecoded(kBob, bob_received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kBob, bob_received_frame);
VideoFrame charlie_received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kCharlie, charlie_received_frame.id(),
FakeEncode(charlie_received_frame));
analyzer.OnFrameDecoded(kCharlie, charlie_received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kCharlie, charlie_received_frame);
VideoFrame katie_received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kKatie, katie_received_frame.id(),
FakeEncode(katie_received_frame));
analyzer.OnFrameDecoded(kKatie, katie_received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kKatie, katie_received_frame);
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.memory_overloaded_comparisons_done, 0);
EXPECT_EQ(stats.comparisons_done, kFramesCount + 2 * kTwoThirdFrames);
std::vector<StatsSample> frames_in_flight_sizes =
GetSortedSamples(stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 0)
<< ToString(frames_in_flight_sizes);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, kFramesCount);
EXPECT_EQ(frame_counters.received, 2 * kFramesCount);
EXPECT_EQ(frame_counters.decoded, 2 * kFramesCount);
EXPECT_EQ(frame_counters.rendered, 2 * kFramesCount);
EXPECT_EQ(frame_counters.dropped, kOneThirdFrames);
const StatsKey kAliceBobStats(kStreamLabel, kBob);
const StatsKey kAliceCharlieStats(kStreamLabel, kCharlie);
const StatsKey kAliceKatieStats(kStreamLabel, kKatie);
EXPECT_EQ(analyzer.GetKnownStreams().GetStatsKeys(),
(std::set<StatsKey>{kAliceBobStats, kAliceCharlieStats,
kAliceKatieStats}));
{
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(kAliceBobStats);
EXPECT_EQ(stream_conters.captured, kFramesCount);
EXPECT_EQ(stream_conters.pre_encoded, kFramesCount);
EXPECT_EQ(stream_conters.encoded, kFramesCount);
EXPECT_EQ(stream_conters.received, kFramesCount);
EXPECT_EQ(stream_conters.decoded, kFramesCount);
EXPECT_EQ(stream_conters.rendered, kFramesCount);
}
{
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(kAliceCharlieStats);
EXPECT_EQ(stream_conters.captured, kFramesCount);
EXPECT_EQ(stream_conters.pre_encoded, kFramesCount);
EXPECT_EQ(stream_conters.encoded, kFramesCount);
EXPECT_EQ(stream_conters.received, kTwoThirdFrames);
EXPECT_EQ(stream_conters.decoded, kTwoThirdFrames);
EXPECT_EQ(stream_conters.rendered, kTwoThirdFrames);
}
{
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(kAliceKatieStats);
EXPECT_EQ(stream_conters.captured, kFramesCount);
EXPECT_EQ(stream_conters.pre_encoded, kFramesCount);
EXPECT_EQ(stream_conters.encoded, kFramesCount);
EXPECT_EQ(stream_conters.received, kOneThirdFrames);
EXPECT_EQ(stream_conters.decoded, kOneThirdFrames);
EXPECT_EQ(stream_conters.rendered, kOneThirdFrames);
}
}
TEST(DefaultVideoQualityAnalyzerTest,
SimulcastFrameWasFullyReceivedByAllPeersBeforeEncodeFinish) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
AnalyzerOptionsForTest());
constexpr char kAlice[] = "alice";
constexpr char kBob[] = "bob";
constexpr char kCharlie[] = "charlie";
analyzer.Start("test_case", std::vector<std::string>{kAlice, kBob, kCharlie},
kAnalyzerMaxThreadsCount);
VideoFrame frame = NextFrame(frame_generator.get(), 1);
frame.set_id(analyzer.OnFrameCaptured(kAlice, kStreamLabel, frame));
analyzer.OnFramePreEncode(kAlice, frame);
// Encode 1st simulcast layer
analyzer.OnFrameEncoded(kAlice, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
// Receive by Bob
VideoFrame received_frame = DeepCopy(frame);
analyzer.OnFramePreDecode(kBob, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kBob, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kBob, received_frame);
// Receive by Charlie
received_frame = DeepCopy(frame);
analyzer.OnFramePreDecode(kCharlie, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kCharlie, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kCharlie, received_frame);
// Encode 2nd simulcast layer
analyzer.OnFrameEncoded(kAlice, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.comparisons_done, 2);
std::vector<StatsSample> frames_in_flight_sizes =
GetSortedSamples(stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 0)
<< ToString(frames_in_flight_sizes);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 1);
EXPECT_EQ(frame_counters.rendered, 2);
}
TEST(DefaultVideoQualityAnalyzerTest,
FrameCanBeReceivedBySenderAfterItWasReceivedByReceiver) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.enable_receive_own_stream = true;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
std::vector<VideoFrame> frames;
for (int i = 0; i < 3; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), 1);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
frames.push_back(frame);
analyzer.OnFramePreEncode(kSenderPeerName, frame);
analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
// Receive by 2nd peer.
for (VideoFrame& frame : frames) {
VideoFrame received_frame = DeepCopy(frame);
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
}
// Check that we still have that frame in flight.
AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats();
std::vector<StatsSample> frames_in_flight_sizes =
GetSortedSamples(analyzer_stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 3)
<< "Expected that frame is still in flight, "
<< "because it wasn't received by sender"
<< ToString(frames_in_flight_sizes);
// Receive by sender
for (VideoFrame& frame : frames) {
VideoFrame received_frame = DeepCopy(frame);
analyzer.OnFramePreDecode(kSenderPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kSenderPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kSenderPeerName, received_frame);
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
analyzer_stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(analyzer_stats.comparisons_done, 6);
frames_in_flight_sizes =
GetSortedSamples(analyzer_stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 0)
<< ToString(frames_in_flight_sizes);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 3);
EXPECT_EQ(frame_counters.rendered, 6);
EXPECT_EQ(analyzer.GetStats().size(), 2lu);
{
FrameCounters stream_conters = analyzer.GetPerStreamCounters().at(
StatsKey(kStreamLabel, kReceiverPeerName));
EXPECT_EQ(stream_conters.captured, 3);
EXPECT_EQ(stream_conters.pre_encoded, 3);
EXPECT_EQ(stream_conters.encoded, 3);
EXPECT_EQ(stream_conters.received, 3);
EXPECT_EQ(stream_conters.decoded, 3);
EXPECT_EQ(stream_conters.rendered, 3);
}
{
FrameCounters stream_conters = analyzer.GetPerStreamCounters().at(
StatsKey(kStreamLabel, kSenderPeerName));
EXPECT_EQ(stream_conters.captured, 3);
EXPECT_EQ(stream_conters.pre_encoded, 3);
EXPECT_EQ(stream_conters.encoded, 3);
EXPECT_EQ(stream_conters.received, 3);
EXPECT_EQ(stream_conters.decoded, 3);
EXPECT_EQ(stream_conters.rendered, 3);
}
}
TEST(DefaultVideoQualityAnalyzerTest,
FrameCanBeReceivedByReceiverAfterItWasReceivedBySender) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.enable_receive_own_stream = true;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
std::vector<VideoFrame> frames;
for (int i = 0; i < 3; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), 1);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
frames.push_back(frame);
analyzer.OnFramePreEncode(kSenderPeerName, frame);
analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
// Receive by sender
for (VideoFrame& frame : frames) {
VideoFrame received_frame = DeepCopy(frame);
analyzer.OnFramePreDecode(kSenderPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kSenderPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kSenderPeerName, received_frame);
}
// Check that we still have that frame in flight.
AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats();
std::vector<StatsSample> frames_in_flight_sizes =
GetSortedSamples(analyzer_stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 3)
<< "Expected that frame is still in flight, "
<< "because it wasn't received by sender"
<< ToString(frames_in_flight_sizes);
// Receive by 2nd peer.
for (VideoFrame& frame : frames) {
VideoFrame received_frame = DeepCopy(frame);
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
analyzer_stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(analyzer_stats.comparisons_done, 6);
frames_in_flight_sizes =
GetSortedSamples(analyzer_stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 0)
<< ToString(frames_in_flight_sizes);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 3);
EXPECT_EQ(frame_counters.rendered, 6);
EXPECT_EQ(analyzer.GetStats().size(), 2lu);
{
FrameCounters stream_conters = analyzer.GetPerStreamCounters().at(
StatsKey(kStreamLabel, kReceiverPeerName));
EXPECT_EQ(stream_conters.captured, 3);
EXPECT_EQ(stream_conters.pre_encoded, 3);
EXPECT_EQ(stream_conters.encoded, 3);
EXPECT_EQ(stream_conters.received, 3);
EXPECT_EQ(stream_conters.decoded, 3);
EXPECT_EQ(stream_conters.rendered, 3);
}
{
FrameCounters stream_conters = analyzer.GetPerStreamCounters().at(
StatsKey(kStreamLabel, kSenderPeerName));
EXPECT_EQ(stream_conters.captured, 3);
EXPECT_EQ(stream_conters.pre_encoded, 3);
EXPECT_EQ(stream_conters.encoded, 3);
EXPECT_EQ(stream_conters.received, 3);
EXPECT_EQ(stream_conters.decoded, 3);
EXPECT_EQ(stream_conters.rendered, 3);
}
}
TEST(DefaultVideoQualityAnalyzerTest, CodecTrackedCorrectly) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
AnalyzerOptionsForTest());
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
VideoQualityAnalyzerInterface::EncoderStats encoder_stats;
std::vector<std::string> codec_names = {"codec_1", "codec_2"};
std::vector<VideoFrame> frames;
// Send 3 frame for each codec.
for (size_t i = 0; i < codec_names.size(); ++i) {
for (size_t j = 0; j < 3; ++j) {
VideoFrame frame = NextFrame(frame_generator.get(), 3 * i + j);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
analyzer.OnFramePreEncode(kSenderPeerName, frame);
encoder_stats.encoder_name = codec_names[i];
analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame),
encoder_stats, false);
frames.push_back(std::move(frame));
}
}
// Receive 3 frame for each codec.
VideoQualityAnalyzerInterface::DecoderStats decoder_stats;
for (size_t i = 0; i < codec_names.size(); ++i) {
for (size_t j = 0; j < 3; ++j) {
VideoFrame received_frame = DeepCopy(frames[3 * i + j]);
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
decoder_stats.decoder_name = codec_names[i];
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame, decoder_stats);
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
}
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
std::map<StatsKey, StreamStats> stats = analyzer.GetStats();
ASSERT_EQ(stats.size(), 1lu);
const StreamStats& stream_stats =
stats.at(StatsKey(kStreamLabel, kReceiverPeerName));
ASSERT_EQ(stream_stats.encoders.size(), 2lu);
EXPECT_EQ(stream_stats.encoders[0].codec_name, codec_names[0]);
EXPECT_EQ(stream_stats.encoders[0].first_frame_id, frames[0].id());
EXPECT_EQ(stream_stats.encoders[0].last_frame_id, frames[2].id());
EXPECT_EQ(stream_stats.encoders[1].codec_name, codec_names[1]);
EXPECT_EQ(stream_stats.encoders[1].first_frame_id, frames[3].id());
EXPECT_EQ(stream_stats.encoders[1].last_frame_id, frames[5].id());
ASSERT_EQ(stream_stats.decoders.size(), 2lu);
EXPECT_EQ(stream_stats.decoders[0].codec_name, codec_names[0]);
EXPECT_EQ(stream_stats.decoders[0].first_frame_id, frames[0].id());
EXPECT_EQ(stream_stats.decoders[0].last_frame_id, frames[2].id());
EXPECT_EQ(stream_stats.decoders[1].codec_name, codec_names[1]);
EXPECT_EQ(stream_stats.decoders[1].first_frame_id, frames[3].id());
EXPECT_EQ(stream_stats.decoders[1].last_frame_id, frames[5].id());
}
TEST(DefaultVideoQualityAnalyzerTest,
FramesInFlightAreCorrectlySentToTheComparatorAfterStop) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
// There are 7 different timings inside frame stats: captured, pre_encode,
// encoded, received, decode_start, decode_end, rendered. captured is always
// set and received is set together with decode_start. So we create 6
// different frames, where for each frame next timings will be set
// * 1st - all of them set
// * 2nd - captured, pre_encode, encoded, received, decode_start, decode_end
// * 3rd - captured, pre_encode, encoded, received, decode_start
// * 4th - captured, pre_encode, encoded
// * 5th - captured, pre_encode
// * 6th - captured
std::vector<VideoFrame> frames;
// Sender side actions
for (int i = 0; i < 6; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), 1);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
frames.push_back(frame);
}
for (int i = 0; i < 5; ++i) {
analyzer.OnFramePreEncode(kSenderPeerName, frames[i]);
}
for (int i = 0; i < 4; ++i) {
analyzer.OnFrameEncoded(
kSenderPeerName, frames[i].id(), FakeEncode(frames[i]),
VideoQualityAnalyzerInterface::EncoderStats(), false);
}
// Receiver side actions
for (int i = 0; i < 3; ++i) {
analyzer.OnFramePreDecode(kReceiverPeerName, frames[i].id(),
FakeEncode(frames[i]));
}
for (int i = 0; i < 2; ++i) {
analyzer.OnFrameDecoded(kReceiverPeerName, DeepCopy(frames[i]),
VideoQualityAnalyzerInterface::DecoderStats());
}
for (int i = 0; i < 1; ++i) {
analyzer.OnFrameRendered(kReceiverPeerName, DeepCopy(frames[i]));
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(analyzer_stats.comparisons_done, 6);
// The last frames in flight size has to reflect the amount of frame in flight
// before all of them were sent to the comparison when Stop() was invoked.
std::vector<StatsSample> frames_in_flight_sizes =
GetSortedSamples(analyzer_stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 5)
<< ToString(frames_in_flight_sizes);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 6);
EXPECT_EQ(frame_counters.pre_encoded, 5);
EXPECT_EQ(frame_counters.encoded, 4);
EXPECT_EQ(frame_counters.received, 3);
EXPECT_EQ(frame_counters.decoded, 2);
EXPECT_EQ(frame_counters.rendered, 1);
EXPECT_EQ(analyzer.GetStats().size(), 1lu);
{
FrameCounters stream_conters = analyzer.GetPerStreamCounters().at(
StatsKey(kStreamLabel, kReceiverPeerName));
EXPECT_EQ(stream_conters.captured, 6);
EXPECT_EQ(stream_conters.pre_encoded, 5);
EXPECT_EQ(stream_conters.encoded, 4);
EXPECT_EQ(stream_conters.received, 3);
EXPECT_EQ(stream_conters.decoded, 2);
EXPECT_EQ(stream_conters.rendered, 1);
}
}
TEST(
DefaultVideoQualityAnalyzerTest,
FramesInFlightAreCorrectlySentToTheComparatorAfterStopForSenderAndReceiver) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.enable_receive_own_stream = true;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
// There are 7 different timings inside frame stats: captured, pre_encode,
// encoded, received, decode_start, decode_end, rendered. captured is always
// set and received is set together with decode_start. So we create 6
// different frames, where for each frame next timings will be set
// * 1st - all of them set
// * 2nd - captured, pre_encode, encoded, received, decode_start, decode_end
// * 3rd - captured, pre_encode, encoded, received, decode_start
// * 4th - captured, pre_encode, encoded
// * 5th - captured, pre_encode
// * 6th - captured
std::vector<VideoFrame> frames;
// Sender side actions
for (int i = 0; i < 6; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), 1);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
frames.push_back(frame);
}
for (int i = 0; i < 5; ++i) {
analyzer.OnFramePreEncode(kSenderPeerName, frames[i]);
}
for (int i = 0; i < 4; ++i) {
analyzer.OnFrameEncoded(
kSenderPeerName, frames[i].id(), FakeEncode(frames[i]),
VideoQualityAnalyzerInterface::EncoderStats(), false);
}
// Receiver side actions
for (int i = 0; i < 3; ++i) {
analyzer.OnFramePreDecode(kSenderPeerName, frames[i].id(),
FakeEncode(frames[i]));
analyzer.OnFramePreDecode(kReceiverPeerName, frames[i].id(),
FakeEncode(frames[i]));
}
for (int i = 0; i < 2; ++i) {
analyzer.OnFrameDecoded(kSenderPeerName, DeepCopy(frames[i]),
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameDecoded(kReceiverPeerName, DeepCopy(frames[i]),
VideoQualityAnalyzerInterface::DecoderStats());
}
for (int i = 0; i < 1; ++i) {
analyzer.OnFrameRendered(kSenderPeerName, DeepCopy(frames[i]));
analyzer.OnFrameRendered(kReceiverPeerName, DeepCopy(frames[i]));
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats analyzer_stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(analyzer_stats.comparisons_done, 12);
// The last frames in flight size has to reflect the amount of frame in flight
// before all of them were sent to the comparison when Stop() was invoked.
std::vector<StatsSample> frames_in_flight_sizes =
GetSortedSamples(analyzer_stats.frames_in_flight_left_count);
EXPECT_EQ(frames_in_flight_sizes.back().value, 5)
<< ToString(frames_in_flight_sizes);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 6);
EXPECT_EQ(frame_counters.pre_encoded, 5);
EXPECT_EQ(frame_counters.encoded, 4);
EXPECT_EQ(frame_counters.received, 6);
EXPECT_EQ(frame_counters.decoded, 4);
EXPECT_EQ(frame_counters.rendered, 2);
EXPECT_EQ(analyzer.GetStats().size(), 2lu);
{
FrameCounters stream_conters = analyzer.GetPerStreamCounters().at(
StatsKey(kStreamLabel, kReceiverPeerName));
EXPECT_EQ(stream_conters.captured, 6);
EXPECT_EQ(stream_conters.pre_encoded, 5);
EXPECT_EQ(stream_conters.encoded, 4);
EXPECT_EQ(stream_conters.received, 3);
EXPECT_EQ(stream_conters.decoded, 2);
EXPECT_EQ(stream_conters.rendered, 1);
}
{
FrameCounters stream_conters = analyzer.GetPerStreamCounters().at(
StatsKey(kStreamLabel, kSenderPeerName));
EXPECT_EQ(stream_conters.captured, 6);
EXPECT_EQ(stream_conters.pre_encoded, 5);
EXPECT_EQ(stream_conters.encoded, 4);
EXPECT_EQ(stream_conters.received, 3);
EXPECT_EQ(stream_conters.decoded, 2);
EXPECT_EQ(stream_conters.rendered, 1);
}
}
TEST(DefaultVideoQualityAnalyzerTest, GetStreamFrames) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
// The order in which peers captured frames and passed them to analyzer.
std::vector<std::string> frame_capturers_sequence{
"alice", "alice", "bob", "bob", "bob",
"bob", "bob", "alice", "alice", "alice",
};
std::map<std::string, std::vector<uint16_t>> stream_to_frame_ids;
stream_to_frame_ids.emplace("alice_video", std::vector<uint16_t>{});
stream_to_frame_ids.emplace("bob_video", std::vector<uint16_t>{});
std::vector<VideoFrame> frames;
for (const std::string& sender : frame_capturers_sequence) {
VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id =
analyzer.OnFrameCaptured(sender, sender + "_video", frame);
frame.set_id(frame_id);
stream_to_frame_ids.find(sender + "_video")->second.push_back(frame_id);
frames.push_back(frame);
analyzer.OnFramePreEncode(sender, frame);
analyzer.OnFrameEncoded(sender, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
// We don't need to receive frames for stats to be gathered correctly.
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
EXPECT_EQ(analyzer.GetStreamFrames(), stream_to_frame_ids);
}
TEST(DefaultVideoQualityAnalyzerTest, ReceiverReceivedFramesWhenSenderRemoved) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame);
frame.set_id(frame_id);
analyzer.OnFramePreEncode("alice", frame);
analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
analyzer.UnregisterParticipantInCall("alice");
analyzer.OnFramePreDecode("bob", frame.id(), FakeEncode(frame));
analyzer.OnFrameDecoded("bob", DeepCopy(frame),
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered("bob", DeepCopy(frame));
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob"));
EXPECT_EQ(stream_conters.captured, 1);
EXPECT_EQ(stream_conters.pre_encoded, 1);
EXPECT_EQ(stream_conters.encoded, 1);
EXPECT_EQ(stream_conters.received, 1);
EXPECT_EQ(stream_conters.decoded, 1);
EXPECT_EQ(stream_conters.rendered, 1);
}
TEST(DefaultVideoQualityAnalyzerTest,
ReceiverReceivedFramesWhenSenderRemovedWithSelfview) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.enable_receive_own_stream = true;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame);
frame.set_id(frame_id);
analyzer.OnFramePreEncode("alice", frame);
analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
analyzer.UnregisterParticipantInCall("alice");
analyzer.OnFramePreDecode("bob", frame.id(), FakeEncode(frame));
analyzer.OnFrameDecoded("bob", DeepCopy(frame),
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered("bob", DeepCopy(frame));
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob"));
EXPECT_EQ(stream_conters.captured, 1);
EXPECT_EQ(stream_conters.pre_encoded, 1);
EXPECT_EQ(stream_conters.encoded, 1);
EXPECT_EQ(stream_conters.received, 1);
EXPECT_EQ(stream_conters.decoded, 1);
EXPECT_EQ(stream_conters.rendered, 1);
}
TEST(DefaultVideoQualityAnalyzerTest,
SenderReceivedFramesWhenReceiverRemovedWithSelfview) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.enable_receive_own_stream = true;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame);
frame.set_id(frame_id);
analyzer.OnFramePreEncode("alice", frame);
analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
analyzer.UnregisterParticipantInCall("bob");
analyzer.OnFramePreDecode("alice", frame.id(), FakeEncode(frame));
analyzer.OnFrameDecoded("alice", DeepCopy(frame),
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered("alice", DeepCopy(frame));
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "alice"));
EXPECT_EQ(stream_conters.captured, 1);
EXPECT_EQ(stream_conters.pre_encoded, 1);
EXPECT_EQ(stream_conters.encoded, 1);
EXPECT_EQ(stream_conters.received, 1);
EXPECT_EQ(stream_conters.decoded, 1);
EXPECT_EQ(stream_conters.rendered, 1);
}
TEST(DefaultVideoQualityAnalyzerTest,
SenderAndReceiverReceivedFramesWhenReceiverRemovedWithSelfview) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.enable_receive_own_stream = true;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame);
frame.set_id(frame_id);
analyzer.OnFramePreEncode("alice", frame);
analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
analyzer.OnFramePreDecode("bob", frame.id(), FakeEncode(frame));
analyzer.OnFrameDecoded("bob", DeepCopy(frame),
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered("bob", DeepCopy(frame));
analyzer.UnregisterParticipantInCall("bob");
analyzer.OnFramePreDecode("alice", frame.id(), FakeEncode(frame));
analyzer.OnFrameDecoded("alice", DeepCopy(frame),
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered("alice", DeepCopy(frame));
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters alice_alice_stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "alice"));
EXPECT_EQ(alice_alice_stream_conters.captured, 1);
EXPECT_EQ(alice_alice_stream_conters.pre_encoded, 1);
EXPECT_EQ(alice_alice_stream_conters.encoded, 1);
EXPECT_EQ(alice_alice_stream_conters.received, 1);
EXPECT_EQ(alice_alice_stream_conters.decoded, 1);
EXPECT_EQ(alice_alice_stream_conters.rendered, 1);
FrameCounters alice_bob_stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob"));
EXPECT_EQ(alice_bob_stream_conters.captured, 1);
EXPECT_EQ(alice_bob_stream_conters.pre_encoded, 1);
EXPECT_EQ(alice_bob_stream_conters.encoded, 1);
EXPECT_EQ(alice_bob_stream_conters.received, 1);
EXPECT_EQ(alice_bob_stream_conters.decoded, 1);
EXPECT_EQ(alice_bob_stream_conters.rendered, 1);
}
TEST(DefaultVideoQualityAnalyzerTest, ReceiverRemovedBeforeCapturing2ndFrame) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/1, *frame_generator);
analyzer.UnregisterParticipantInCall("bob");
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {},
/*frames_count=*/1, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters global_stream_conters = analyzer.GetGlobalCounters();
EXPECT_EQ(global_stream_conters.captured, 2);
EXPECT_EQ(global_stream_conters.pre_encoded, 2);
EXPECT_EQ(global_stream_conters.encoded, 2);
EXPECT_EQ(global_stream_conters.received, 1);
EXPECT_EQ(global_stream_conters.decoded, 1);
EXPECT_EQ(global_stream_conters.rendered, 1);
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob"));
EXPECT_EQ(stream_conters.captured, 2);
EXPECT_EQ(stream_conters.pre_encoded, 2);
EXPECT_EQ(stream_conters.encoded, 2);
EXPECT_EQ(stream_conters.received, 1);
EXPECT_EQ(stream_conters.decoded, 1);
EXPECT_EQ(stream_conters.rendered, 1);
}
TEST(DefaultVideoQualityAnalyzerTest, ReceiverRemovedBeforePreEncoded) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame);
frame.set_id(frame_id);
analyzer.UnregisterParticipantInCall("bob");
analyzer.OnFramePreEncode("alice", frame);
analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters global_stream_conters = analyzer.GetGlobalCounters();
EXPECT_EQ(global_stream_conters.captured, 1);
EXPECT_EQ(global_stream_conters.pre_encoded, 1);
EXPECT_EQ(global_stream_conters.encoded, 1);
EXPECT_EQ(global_stream_conters.received, 0);
EXPECT_EQ(global_stream_conters.decoded, 0);
EXPECT_EQ(global_stream_conters.rendered, 0);
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob"));
EXPECT_EQ(stream_conters.captured, 1);
EXPECT_EQ(stream_conters.pre_encoded, 1);
EXPECT_EQ(stream_conters.encoded, 1);
EXPECT_EQ(stream_conters.received, 0);
EXPECT_EQ(stream_conters.decoded, 0);
EXPECT_EQ(stream_conters.rendered, 0);
}
TEST(DefaultVideoQualityAnalyzerTest, ReceiverRemovedBeforeEncoded) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame);
frame.set_id(frame_id);
analyzer.OnFramePreEncode("alice", frame);
analyzer.UnregisterParticipantInCall("bob");
analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters global_stream_conters = analyzer.GetGlobalCounters();
EXPECT_EQ(global_stream_conters.captured, 1);
EXPECT_EQ(global_stream_conters.pre_encoded, 1);
EXPECT_EQ(global_stream_conters.encoded, 1);
EXPECT_EQ(global_stream_conters.received, 0);
EXPECT_EQ(global_stream_conters.decoded, 0);
EXPECT_EQ(global_stream_conters.rendered, 0);
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob"));
EXPECT_EQ(stream_conters.captured, 1);
EXPECT_EQ(stream_conters.pre_encoded, 1);
EXPECT_EQ(stream_conters.encoded, 1);
EXPECT_EQ(stream_conters.received, 0);
EXPECT_EQ(stream_conters.decoded, 0);
EXPECT_EQ(stream_conters.rendered, 0);
}
TEST(DefaultVideoQualityAnalyzerTest,
ReceiverRemovedBetweenSimulcastLayersEncoded) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame);
frame.set_id(frame_id);
analyzer.OnFramePreEncode("alice", frame);
// 1st simulcast layer encoded
analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
analyzer.UnregisterParticipantInCall("bob");
// 2nd simulcast layer encoded
analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters global_stream_conters = analyzer.GetGlobalCounters();
EXPECT_EQ(global_stream_conters.captured, 1);
EXPECT_EQ(global_stream_conters.pre_encoded, 1);
EXPECT_EQ(global_stream_conters.encoded, 1);
EXPECT_EQ(global_stream_conters.received, 0);
EXPECT_EQ(global_stream_conters.decoded, 0);
EXPECT_EQ(global_stream_conters.rendered, 0);
FrameCounters stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob"));
EXPECT_EQ(stream_conters.captured, 1);
EXPECT_EQ(stream_conters.pre_encoded, 1);
EXPECT_EQ(stream_conters.encoded, 1);
EXPECT_EQ(stream_conters.received, 0);
EXPECT_EQ(stream_conters.decoded, 0);
EXPECT_EQ(stream_conters.rendered, 0);
}
TEST(DefaultVideoQualityAnalyzerTest, UnregisterOneAndRegisterAnother) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case",
std::vector<std::string>{"alice", "bob", "charlie"},
kAnalyzerMaxThreadsCount);
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/2, *frame_generator);
analyzer.UnregisterParticipantInCall("bob");
analyzer.RegisterParticipantInCall("david");
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"charlie", "david"},
/*frames_count=*/4, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters global_stream_conters = analyzer.GetGlobalCounters();
EXPECT_EQ(global_stream_conters.captured, 6);
EXPECT_EQ(global_stream_conters.pre_encoded, 6);
EXPECT_EQ(global_stream_conters.encoded, 6);
EXPECT_EQ(global_stream_conters.received, 12);
EXPECT_EQ(global_stream_conters.decoded, 12);
EXPECT_EQ(global_stream_conters.rendered, 12);
FrameCounters alice_bob_stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob"));
EXPECT_EQ(alice_bob_stream_conters.captured, 6);
EXPECT_EQ(alice_bob_stream_conters.pre_encoded, 6);
EXPECT_EQ(alice_bob_stream_conters.encoded, 6);
EXPECT_EQ(alice_bob_stream_conters.received, 2);
EXPECT_EQ(alice_bob_stream_conters.decoded, 2);
EXPECT_EQ(alice_bob_stream_conters.rendered, 2);
FrameCounters alice_charlie_stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "charlie"));
EXPECT_EQ(alice_charlie_stream_conters.captured, 6);
EXPECT_EQ(alice_charlie_stream_conters.pre_encoded, 6);
EXPECT_EQ(alice_charlie_stream_conters.encoded, 6);
EXPECT_EQ(alice_charlie_stream_conters.received, 6);
EXPECT_EQ(alice_charlie_stream_conters.decoded, 6);
EXPECT_EQ(alice_charlie_stream_conters.rendered, 6);
FrameCounters alice_david_stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "david"));
EXPECT_EQ(alice_david_stream_conters.captured, 6);
EXPECT_EQ(alice_david_stream_conters.pre_encoded, 6);
EXPECT_EQ(alice_david_stream_conters.encoded, 6);
EXPECT_EQ(alice_david_stream_conters.received, 4);
EXPECT_EQ(alice_david_stream_conters.decoded, 4);
EXPECT_EQ(alice_david_stream_conters.rendered, 4);
}
TEST(DefaultVideoQualityAnalyzerTest,
UnregisterOneAndRegisterAnotherRegisterBack) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case",
std::vector<std::string>{"alice", "bob", "charlie"},
kAnalyzerMaxThreadsCount);
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/2, *frame_generator);
analyzer.UnregisterParticipantInCall("bob");
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"charlie"},
/*frames_count=*/4, *frame_generator);
analyzer.RegisterParticipantInCall("bob");
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/6, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
FrameCounters global_stream_conters = analyzer.GetGlobalCounters();
EXPECT_EQ(global_stream_conters.captured, 12);
EXPECT_EQ(global_stream_conters.pre_encoded, 12);
EXPECT_EQ(global_stream_conters.encoded, 12);
EXPECT_EQ(global_stream_conters.received, 20);
EXPECT_EQ(global_stream_conters.decoded, 20);
EXPECT_EQ(global_stream_conters.rendered, 20);
FrameCounters alice_bob_stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "bob"));
EXPECT_EQ(alice_bob_stream_conters.captured, 12);
EXPECT_EQ(alice_bob_stream_conters.pre_encoded, 12);
EXPECT_EQ(alice_bob_stream_conters.encoded, 12);
EXPECT_EQ(alice_bob_stream_conters.received, 8);
EXPECT_EQ(alice_bob_stream_conters.decoded, 8);
EXPECT_EQ(alice_bob_stream_conters.rendered, 8);
FrameCounters alice_charlie_stream_conters =
analyzer.GetPerStreamCounters().at(StatsKey("alice_video", "charlie"));
EXPECT_EQ(alice_charlie_stream_conters.captured, 12);
EXPECT_EQ(alice_charlie_stream_conters.pre_encoded, 12);
EXPECT_EQ(alice_charlie_stream_conters.encoded, 12);
EXPECT_EQ(alice_charlie_stream_conters.received, 12);
EXPECT_EQ(alice_charlie_stream_conters.decoded, 12);
EXPECT_EQ(alice_charlie_stream_conters.rendered, 12);
}
TEST(DefaultVideoQualityAnalyzerTest,
FramesInFlightAreAccountedForUnregisterPeers) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
// Add one frame in flight which has encode time >= 10ms.
VideoFrame frame = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id = analyzer.OnFrameCaptured("alice", "alice_video", frame);
frame.set_id(frame_id);
analyzer.OnFramePreEncode("alice", frame);
SleepMs(10);
analyzer.OnFrameEncoded("alice", frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
analyzer.UnregisterParticipantInCall("bob");
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
StreamStats stats = analyzer.GetStats().at(StatsKey("alice_video", "bob"));
ASSERT_EQ(stats.encode_time_ms.NumSamples(), 1);
EXPECT_GE(stats.encode_time_ms.GetAverage(), 10);
}
TEST(DefaultVideoQualityAnalyzerTest, InfraMetricsAreReportedWhenRequested) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.report_infra_metrics = true;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/1, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.on_frame_captured_processing_time_ms.NumSamples(), 1);
EXPECT_EQ(stats.on_frame_pre_encode_processing_time_ms.NumSamples(), 1);
EXPECT_EQ(stats.on_frame_encoded_processing_time_ms.NumSamples(), 1);
EXPECT_EQ(stats.on_frame_pre_decode_processing_time_ms.NumSamples(), 1);
EXPECT_EQ(stats.on_frame_decoded_processing_time_ms.NumSamples(), 1);
EXPECT_EQ(stats.on_frame_rendered_processing_time_ms.NumSamples(), 1);
EXPECT_EQ(stats.on_decoder_error_processing_time_ms.NumSamples(), 0);
}
TEST(DefaultVideoQualityAnalyzerTest, InfraMetricsNotCollectedByDefault) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.report_infra_metrics = false;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/1, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.on_frame_captured_processing_time_ms.NumSamples(), 0);
EXPECT_EQ(stats.on_frame_pre_encode_processing_time_ms.NumSamples(), 0);
EXPECT_EQ(stats.on_frame_encoded_processing_time_ms.NumSamples(), 0);
EXPECT_EQ(stats.on_frame_pre_decode_processing_time_ms.NumSamples(), 0);
EXPECT_EQ(stats.on_frame_decoded_processing_time_ms.NumSamples(), 0);
EXPECT_EQ(stats.on_frame_rendered_processing_time_ms.NumSamples(), 0);
EXPECT_EQ(stats.on_decoder_error_processing_time_ms.NumSamples(), 0);
}
TEST(DefaultVideoQualityAnalyzerTest,
FrameDroppedByDecoderIsAccountedCorrectly) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.report_infra_metrics = false;
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
VideoFrame to_be_dropped_frame =
NextFrame(frame_generator.get(), /*timestamp_us=*/1);
uint16_t frame_id =
analyzer.OnFrameCaptured("alice", "alice_video", to_be_dropped_frame);
to_be_dropped_frame.set_id(frame_id);
analyzer.OnFramePreEncode("alice", to_be_dropped_frame);
analyzer.OnFrameEncoded("alice", to_be_dropped_frame.id(),
FakeEncode(to_be_dropped_frame),
VideoQualityAnalyzerInterface::EncoderStats(), false);
VideoFrame received_to_be_dropped_frame = DeepCopy(to_be_dropped_frame);
analyzer.OnFramePreDecode("bob", received_to_be_dropped_frame.id(),
FakeEncode(received_to_be_dropped_frame));
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/1, *frame_generator);
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
StreamStats stats = analyzer.GetStats().at(StatsKey("alice_video", "bob"));
ASSERT_EQ(stats.dropped_by_phase[FrameDropPhase::kByDecoder], 1);
}
class DefaultVideoQualityAnalyzerTimeBetweenFreezesTest
: public TestWithParam<bool> {};
TEST_P(DefaultVideoQualityAnalyzerTimeBetweenFreezesTest,
TimeBetweenFreezesIsEqualToStreamDurationWhenThereAreNoFeeezes) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"bob"},
/*frames_count=*/5, *frame_generator,
/*interframe_delay_ms=*/50);
if (GetParam()) {
analyzer.UnregisterParticipantInCall("bob");
}
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(50);
analyzer.Stop();
StreamStats stats = analyzer.GetStats().at(StatsKey("alice_video", "bob"));
ASSERT_EQ(stats.time_between_freezes_ms.NumSamples(), 1);
EXPECT_GE(stats.time_between_freezes_ms.GetAverage(), 200);
}
INSTANTIATE_TEST_SUITE_P(WithRegisteredAndUnregisteredPeerAtTheEndOfTheCall,
DefaultVideoQualityAnalyzerTimeBetweenFreezesTest,
ValuesIn({true, false}));
class DefaultVideoQualityAnalyzerSimulatedTimeTest : public Test {
protected:
DefaultVideoQualityAnalyzerSimulatedTimeTest()
: time_controller_(std::make_unique<GlobalSimulatedTimeController>(
Timestamp::Seconds(1000))) {}
void AdvanceTime(TimeDelta time) { time_controller_->AdvanceTime(time); }
Clock* GetClock() { return time_controller_->GetClock(); }
TimeController* time_controller() { return time_controller_.get(); }
Timestamp Now() const { return time_controller_->GetClock()->CurrentTime(); }
private:
std::unique_ptr<TimeController> time_controller_;
};
TEST_F(DefaultVideoQualityAnalyzerSimulatedTimeTest,
PausedAndResumedStreamIsAccountedInStatsCorrectly) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.report_infra_metrics = false;
DefaultVideoQualityAnalyzer analyzer(GetClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case",
std::vector<std::string>{"alice", "bob", "charlie"},
kAnalyzerMaxThreadsCount);
// Pass 20 frames as 20 fps.
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Mark stream paused for Bob, but not for Charlie.
analyzer.OnPauseAllStreamsFrom("alice", "bob");
// Freeze for 1 second.
PassFramesThroughAnalyzerSenderOnly(
analyzer, "alice", {"alice_video"}, {"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Unpause stream for Bob.
analyzer.OnResumeAllStreamsFrom("alice", "bob");
// Pass 20 frames as 20 fps.
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
analyzer.Stop();
// Bob should have 20 fps without freeze and Charlie should have freeze of 1s
// and decreased fps.
std::map<StatsKey, StreamStats> streams_stats = analyzer.GetStats();
std::map<StatsKey, FrameCounters> frame_counters =
analyzer.GetPerStreamCounters();
StreamStats bob_stream_stats =
streams_stats.at(StatsKey("alice_video", "bob"));
FrameCounters bob_frame_counters =
frame_counters.at(StatsKey("alice_video", "bob"));
EXPECT_THAT(bob_frame_counters.dropped, Eq(0));
EXPECT_THAT(bob_frame_counters.rendered, Eq(40));
EXPECT_THAT(GetTimeSortedValues(bob_stream_stats.freeze_time_ms),
ElementsAre(0.0));
// TODO(bugs.webrtc.org/14995): value should exclude pause
EXPECT_THAT(GetTimeSortedValues(bob_stream_stats.time_between_freezes_ms),
ElementsAre(2950.0));
EXPECT_THAT(bob_stream_stats.time_between_captured_frames_ms.GetAverage(),
50.0);
EXPECT_THAT(bob_stream_stats.time_between_encoded_frames_ms.GetAverage(),
50.0);
ExpectRateIs(bob_stream_stats.capture_frame_rate, 13.559322);
ExpectRateIs(bob_stream_stats.encode_frame_rate, 13.559322);
EXPECT_DOUBLE_EQ(bob_stream_stats.harmonic_framerate_fps, 20);
StreamStats charlie_stream_stats =
streams_stats.at(StatsKey("alice_video", "charlie"));
FrameCounters charlie_frame_counters =
frame_counters.at(StatsKey("alice_video", "charlie"));
EXPECT_THAT(charlie_frame_counters.dropped, Eq(20));
EXPECT_THAT(charlie_frame_counters.rendered, Eq(40));
EXPECT_THAT(GetTimeSortedValues(charlie_stream_stats.freeze_time_ms),
ElementsAre(1050.0));
EXPECT_THAT(GetTimeSortedValues(charlie_stream_stats.time_between_freezes_ms),
ElementsAre(950.0, 950.0));
EXPECT_NEAR(charlie_stream_stats.harmonic_framerate_fps, 2.463465, 1e-6);
}
TEST_F(DefaultVideoQualityAnalyzerSimulatedTimeTest,
PausedAndResumedTwoStreamsAreAccountedInStatsCorrectly) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzer analyzer(
GetClock(), test::GetGlobalMetricsLogger(), AnalyzerOptionsForTest());
analyzer.Start("test_case", std::vector<std::string>{"alice", "bob"},
kAnalyzerMaxThreadsCount);
// Pass 20 frames as 20 fps on 2 streams.
PassFramesThroughAnalyzer(analyzer, "alice",
{"alice_video_1", "alice_video_2"}, {"bob"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Mark streams paused.
analyzer.OnPauseAllStreamsFrom("alice", "bob");
// Freeze for 1 second.
PassFramesThroughAnalyzerSenderOnly(
analyzer, "alice", {"alice_video_1", "alice_video_2"}, {"bob"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Unpause streams.
analyzer.OnResumeAllStreamsFrom("alice", "bob");
// Pass 20 frames as 20 fps on the 2 streams.
PassFramesThroughAnalyzer(analyzer, "alice",
{"alice_video_1", "alice_video_2"}, {"bob"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
analyzer.Stop();
// Bob should have 20 fps without freeze on both streams.
std::map<StatsKey, StreamStats> streams_stats = analyzer.GetStats();
std::map<StatsKey, FrameCounters> frame_counters =
analyzer.GetPerStreamCounters();
StreamStats bob_stream_stats_1 =
streams_stats.at(StatsKey("alice_video_1", "bob"));
FrameCounters bob_frame_counters1 =
frame_counters.at(StatsKey("alice_video_1", "bob"));
EXPECT_THAT(bob_frame_counters1.dropped, Eq(0));
EXPECT_THAT(bob_frame_counters1.rendered, Eq(40));
EXPECT_THAT(GetTimeSortedValues(bob_stream_stats_1.freeze_time_ms),
ElementsAre(0.0));
// TODO(bugs.webrtc.org/14995): value should exclude pause
EXPECT_THAT(GetTimeSortedValues(bob_stream_stats_1.time_between_freezes_ms),
ElementsAre(2950.0));
EXPECT_THAT(bob_stream_stats_1.time_between_captured_frames_ms.GetAverage(),
50.0);
EXPECT_THAT(bob_stream_stats_1.time_between_encoded_frames_ms.GetAverage(),
50.0);
EXPECT_DOUBLE_EQ(bob_stream_stats_1.harmonic_framerate_fps, 20.0);
// Bob should have 20 fps without freeze on both streams.
StreamStats bob_stream_stats_2 =
streams_stats.at(StatsKey("alice_video_2", "bob"));
FrameCounters bob_frame_counters_2 =
frame_counters.at(StatsKey("alice_video_2", "bob"));
EXPECT_THAT(bob_frame_counters_2.dropped, Eq(0));
EXPECT_THAT(bob_frame_counters_2.rendered, Eq(40));
EXPECT_THAT(GetTimeSortedValues(bob_stream_stats_2.freeze_time_ms),
ElementsAre(0.0));
// TODO(bugs.webrtc.org/14995): value should exclude pause
EXPECT_THAT(GetTimeSortedValues(bob_stream_stats_2.time_between_freezes_ms),
ElementsAre(2950.0));
EXPECT_THAT(bob_stream_stats_2.time_between_captured_frames_ms.GetAverage(),
50.0);
EXPECT_THAT(bob_stream_stats_2.time_between_encoded_frames_ms.GetAverage(),
50.0);
EXPECT_DOUBLE_EQ(bob_stream_stats_2.harmonic_framerate_fps, 20.0);
}
TEST_F(DefaultVideoQualityAnalyzerSimulatedTimeTest,
PausedStreamIsAccountedInStatsCorrectly) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzerOptions options = AnalyzerOptionsForTest();
options.report_infra_metrics = false;
DefaultVideoQualityAnalyzer analyzer(GetClock(),
test::GetGlobalMetricsLogger(), options);
analyzer.Start("test_case",
std::vector<std::string>{"alice", "bob", "charlie"},
kAnalyzerMaxThreadsCount);
// Pass 20 frames as 20 fps.
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"},
{"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Mark stream paused for Bob, but not for Charlie.
analyzer.OnPauseAllStreamsFrom("alice", "bob");
// Freeze for 1 second.
PassFramesThroughAnalyzerSenderOnly(
analyzer, "alice", {"alice_video"}, {"bob", "charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
AdvanceTime(TimeDelta::Millis(50));
// Pass 20 frames as 20 fps.
PassFramesThroughAnalyzer(analyzer, "alice", {"alice_video"}, {"charlie"},
/*frames_count=*/20, *frame_generator,
/*interframe_delay_ms=*/50, time_controller());
analyzer.Stop();
// Bob should have 20 fps without freeze and Charlie should have freeze of 1s
// and decreased fps.
std::map<StatsKey, StreamStats> streams_stats = analyzer.GetStats();
std::map<StatsKey, FrameCounters> frame_counters =
analyzer.GetPerStreamCounters();
StreamStats bob_stream_stats =
streams_stats.at(StatsKey("alice_video", "bob"));
FrameCounters bob_frame_counters =
frame_counters.at(StatsKey("alice_video", "bob"));
EXPECT_THAT(bob_frame_counters.dropped, Eq(0));
EXPECT_THAT(bob_frame_counters.rendered, Eq(20));
EXPECT_THAT(GetTimeSortedValues(bob_stream_stats.freeze_time_ms),
ElementsAre(0.0));
EXPECT_THAT(GetTimeSortedValues(bob_stream_stats.time_between_freezes_ms),
ElementsAre(950.0));
ExpectRateIs(bob_stream_stats.capture_frame_rate, 21.052631);
ExpectRateIs(bob_stream_stats.encode_frame_rate, 21.052631);
EXPECT_DOUBLE_EQ(bob_stream_stats.harmonic_framerate_fps, 20);
StreamStats charlie_stream_stats =
streams_stats.at(StatsKey("alice_video", "charlie"));
FrameCounters charlie_frame_counters =
frame_counters.at(StatsKey("alice_video", "charlie"));
EXPECT_THAT(charlie_frame_counters.dropped, Eq(20));
EXPECT_THAT(charlie_frame_counters.rendered, Eq(40));
EXPECT_THAT(GetTimeSortedValues(charlie_stream_stats.freeze_time_ms),
ElementsAre(1050.0));
EXPECT_THAT(GetTimeSortedValues(charlie_stream_stats.time_between_freezes_ms),
ElementsAre(950.0, 950.0));
EXPECT_NEAR(charlie_stream_stats.harmonic_framerate_fps, 2.463465, 1e-6);
}
TEST_F(DefaultVideoQualityAnalyzerSimulatedTimeTest,
MemoryOverloadedAndThenAllFramesReceived) {
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzer analyzer(
GetClock(), test::GetGlobalMetricsLogger(), AnalyzerOptionsForTest());
analyzer.Start("test_case",
std::vector<std::string>{kSenderPeerName, kReceiverPeerName},
kAnalyzerMaxThreadsCount);
std::map<uint16_t, VideoFrame> captured_frames;
std::vector<uint16_t> frames_order;
for (int i = 0; i < 5; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), i);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
frames_order.push_back(frame.id());
captured_frames.insert({frame.id(), frame});
analyzer.OnFramePreEncode(kSenderPeerName, frame);
analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
AdvanceTime(kMaxFramesInFlightStorageDuration + TimeDelta::Millis(1));
for (int i = 0; i < 5; ++i) {
VideoFrame frame = NextFrame(frame_generator.get(), i);
frame.set_id(
analyzer.OnFrameCaptured(kSenderPeerName, kStreamLabel, frame));
frames_order.push_back(frame.id());
captured_frames.insert({frame.id(), frame});
analyzer.OnFramePreEncode(kSenderPeerName, frame);
analyzer.OnFrameEncoded(kSenderPeerName, frame.id(), FakeEncode(frame),
VideoQualityAnalyzerInterface::EncoderStats(),
false);
}
for (const uint16_t& frame_id : frames_order) {
VideoFrame received_frame = DeepCopy(captured_frames.at(frame_id));
analyzer.OnFramePreDecode(kReceiverPeerName, received_frame.id(),
FakeEncode(received_frame));
analyzer.OnFrameDecoded(kReceiverPeerName, received_frame,
VideoQualityAnalyzerInterface::DecoderStats());
analyzer.OnFrameRendered(kReceiverPeerName, received_frame);
}
analyzer.Stop();
AnalyzerStats stats = analyzer.GetAnalyzerStats();
EXPECT_EQ(stats.memory_overloaded_comparisons_done, 5);
EXPECT_EQ(stats.comparisons_done, 10);
FrameCounters frame_counters = analyzer.GetGlobalCounters();
EXPECT_EQ(frame_counters.captured, 10);
EXPECT_EQ(frame_counters.rendered, 10);
EXPECT_EQ(frame_counters.dropped, 0);
}
TEST(DefaultVideoQualityAnalyzerTest, CheckFrameSenderPeerName) {
constexpr char kAlice[] = "alice";
constexpr char kBob[] = "bob";
constexpr char kAliceStreamLabel[] = "alice-video";
constexpr char kBobStreamLabel[] = "bob-video";
std::unique_ptr<test::FrameGeneratorInterface> frame_generator =
test::CreateSquareFrameGenerator(kFrameWidth, kFrameHeight,
/*type=*/std::nullopt,
/*num_squares=*/std::nullopt);
DefaultVideoQualityAnalyzer analyzer(Clock::GetRealTimeClock(),
test::GetGlobalMetricsLogger(),
AnalyzerOptionsForTest());
analyzer.Start("test_case", std::vector<std::string>{kAlice, kBob},
kAnalyzerMaxThreadsCount);
VideoFrame frame_alice = NextFrame(frame_generator.get(), /*timestamp_us=*/1);
VideoFrame frame_bob = NextFrame(frame_generator.get(), /*timestamp_us=*/2);
frame_alice.set_id(
analyzer.OnFrameCaptured(kAlice, kAliceStreamLabel, frame_alice));
frame_bob.set_id(analyzer.OnFrameCaptured(kBob, kBobStreamLabel, frame_bob));
std::string sender_alice = analyzer.GetSenderPeerName(frame_alice.id());
std::string sender_bob = analyzer.GetSenderPeerName(frame_bob.id());
// Give analyzer some time to process frames on async thread. The computations
// have to be fast (heavy metrics are disabled!), so if doesn't fit 100ms it
// means we have an issue!
SleepMs(100);
analyzer.Stop();
EXPECT_EQ(sender_alice, kAlice);
EXPECT_EQ(sender_bob, kBob);
}
} // namespace
} // namespace webrtc