blob: adbd97a87670c21455eea901fca838479ab38f75 [file] [log] [blame]
/*
* Copyright (c) 2021 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_frames_comparator.h"
#include <map>
#include <vector>
#include "api/test/create_frame_generator.h"
#include "api/units/timestamp.h"
#include "rtc_base/strings/string_builder.h"
#include "system_wrappers/include/clock.h"
#include "system_wrappers/include/sleep.h"
#include "test/gtest.h"
#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_cpu_measurer.h"
#include "test/pc/e2e/analyzer/video/default_video_quality_analyzer_shared_objects.h"
namespace webrtc {
namespace {
using StatsSample = ::webrtc::SamplesStatsCounter::StatsSample;
constexpr int kMaxFramesInFlightPerStream = 10;
DefaultVideoQualityAnalyzerOptions AnalyzerOptionsForTest() {
DefaultVideoQualityAnalyzerOptions options;
options.heavy_metrics_computation_enabled = false;
options.adjust_cropping_before_comparing_frames = false;
options.max_frames_in_flight_per_stream_count = kMaxFramesInFlightPerStream;
return options;
}
StreamCodecInfo Vp8CodecForOneFrame(uint16_t frame_id, Timestamp time) {
StreamCodecInfo info;
info.codec_name = "VP8";
info.first_frame_id = frame_id;
info.last_frame_id = frame_id;
info.switched_on_at = time;
info.switched_from_at = time;
return info;
}
FrameStats FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame(
Timestamp captured_time) {
FrameStats frame_stats(captured_time);
frame_stats.pre_encode_time = captured_time + TimeDelta::Millis(10);
frame_stats.encoded_time = captured_time + TimeDelta::Millis(20);
frame_stats.received_time = captured_time + TimeDelta::Millis(30);
frame_stats.decode_start_time = captured_time + TimeDelta::Millis(40);
frame_stats.decode_end_time = captured_time + TimeDelta::Millis(50);
frame_stats.rendered_time = captured_time + TimeDelta::Millis(60);
frame_stats.used_encoder = Vp8CodecForOneFrame(1, frame_stats.encoded_time);
frame_stats.used_decoder =
Vp8CodecForOneFrame(1, frame_stats.decode_end_time);
frame_stats.rendered_frame_width = 10;
frame_stats.rendered_frame_height = 10;
return frame_stats;
}
FrameStats ShiftStatsOn(const FrameStats& stats, TimeDelta delta) {
FrameStats frame_stats(stats.captured_time + delta);
frame_stats.pre_encode_time = stats.pre_encode_time + delta;
frame_stats.encoded_time = stats.encoded_time + delta;
frame_stats.received_time = stats.received_time + delta;
frame_stats.decode_start_time = stats.decode_start_time + delta;
frame_stats.decode_end_time = stats.decode_end_time + delta;
frame_stats.rendered_time = stats.rendered_time + delta;
frame_stats.used_encoder = stats.used_encoder;
frame_stats.used_decoder = stats.used_decoder;
frame_stats.rendered_frame_width = stats.rendered_frame_width;
frame_stats.rendered_frame_height = stats.rendered_frame_height;
return frame_stats;
}
double GetFirstOrDie(const SamplesStatsCounter& counter) {
EXPECT_TRUE(!counter.IsEmpty()) << "Counter has to be not empty";
return counter.GetSamples()[0];
}
std::string ToString(const SamplesStatsCounter& counter) {
rtc::StringBuilder out;
for (const StatsSample& s : counter.GetTimedSamples()) {
out << "{ time_ms=" << s.time.ms() << "; value=" << s.value << "}, ";
}
return out.str();
}
TEST(DefaultVideoQualityAnalyzerFramesComparatorTest,
StatsPresentedAfterAddingOneComparison) {
DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer;
DefaultVideoQualityAnalyzerFramesComparator comparator(
Clock::GetRealTimeClock(), cpu_measurer, AnalyzerOptionsForTest());
Timestamp stream_start_time = Clock::GetRealTimeClock()->CurrentTime();
size_t stream = 0;
size_t sender = 0;
size_t receiver = 1;
size_t peers_count = 2;
InternalStatsKey stats_key(stream, sender, receiver);
FrameStats frame_stats =
FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame(stream_start_time);
comparator.Start(1);
comparator.EnsureStatsForStream(stream, sender, peers_count,
stream_start_time, stream_start_time);
comparator.AddComparison(stats_key,
/*captured=*/absl::nullopt,
/*rendered=*/absl::nullopt,
FrameComparisonType::kRegular, frame_stats);
comparator.Stop(/*last_rendered_frame_times=*/{});
std::map<InternalStatsKey, StreamStats> stats = comparator.stream_stats();
EXPECT_DOUBLE_EQ(GetFirstOrDie(stats.at(stats_key).transport_time_ms), 20.0);
EXPECT_DOUBLE_EQ(
GetFirstOrDie(stats.at(stats_key).total_delay_incl_transport_ms), 60.0);
EXPECT_DOUBLE_EQ(GetFirstOrDie(stats.at(stats_key).encode_time_ms), 10.0);
EXPECT_DOUBLE_EQ(GetFirstOrDie(stats.at(stats_key).decode_time_ms), 10.0);
EXPECT_DOUBLE_EQ(GetFirstOrDie(stats.at(stats_key).receive_to_render_time_ms),
30.0);
EXPECT_DOUBLE_EQ(
GetFirstOrDie(stats.at(stats_key).resolution_of_rendered_frame), 100.0);
}
TEST(DefaultVideoQualityAnalyzerFramesComparatorTest,
MultiFrameStatsPresentedAfterAddingTwoComparisonWith10msDelay) {
DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer;
DefaultVideoQualityAnalyzerFramesComparator comparator(
Clock::GetRealTimeClock(), cpu_measurer, AnalyzerOptionsForTest());
Timestamp stream_start_time = Clock::GetRealTimeClock()->CurrentTime();
size_t stream = 0;
size_t sender = 0;
size_t receiver = 1;
size_t peers_count = 2;
InternalStatsKey stats_key(stream, sender, receiver);
FrameStats frame_stats1 =
FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame(stream_start_time);
FrameStats frame_stats2 = FrameStatsWith10msDeltaBetweenPhasesAnd10x10Frame(
stream_start_time + TimeDelta::Millis(15));
frame_stats2.prev_frame_rendered_time = frame_stats1.rendered_time;
comparator.Start(1);
comparator.EnsureStatsForStream(stream, sender, peers_count,
stream_start_time, stream_start_time);
comparator.AddComparison(stats_key,
/*captured=*/absl::nullopt,
/*rendered=*/absl::nullopt,
FrameComparisonType::kRegular, frame_stats1);
comparator.AddComparison(stats_key,
/*captured=*/absl::nullopt,
/*rendered=*/absl::nullopt,
FrameComparisonType::kRegular, frame_stats2);
comparator.Stop(/*last_rendered_frame_times=*/{});
std::map<InternalStatsKey, StreamStats> stats = comparator.stream_stats();
EXPECT_DOUBLE_EQ(
GetFirstOrDie(stats.at(stats_key).time_between_rendered_frames_ms), 15.0);
EXPECT_DOUBLE_EQ(stats.at(stats_key).encode_frame_rate.GetEventsPerSecond(),
2.0 / 15 * 1000)
<< "There should be 2 events with interval of 15 ms";
;
}
TEST(DefaultVideoQualityAnalyzerFramesComparatorTest,
FrameInFlightStatsAreHandledCorrectly) {
DefaultVideoQualityAnalyzerCpuMeasurer cpu_measurer;
DefaultVideoQualityAnalyzerFramesComparator comparator(
Clock::GetRealTimeClock(), cpu_measurer, AnalyzerOptionsForTest());
Timestamp stream_start_time = Clock::GetRealTimeClock()->CurrentTime();
size_t stream = 0;
size_t sender = 0;
size_t receiver = 1;
size_t peers_count = 2;
InternalStatsKey stats_key(stream, sender, receiver);
// 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 frame stats with interval of 15 ms, where for each stat next
// timings will be set
// * 1st - captured
// * 2nd - captured, pre_encode
// * 3rd - captured, pre_encode, encoded
// * 4th - captured, pre_encode, encoded, received, decode_start
// * 5th - captured, pre_encode, encoded, received, decode_start, decode_end
// * 6th - all of them set
std::vector<FrameStats> stats;
// 1st stat
FrameStats frame_stats(stream_start_time);
stats.push_back(frame_stats);
// 2nd stat
frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15));
frame_stats.pre_encode_time =
frame_stats.captured_time + TimeDelta::Millis(10);
stats.push_back(frame_stats);
// 3rd stat
frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15));
frame_stats.encoded_time = frame_stats.captured_time + TimeDelta::Millis(20);
frame_stats.used_encoder = Vp8CodecForOneFrame(1, frame_stats.encoded_time);
stats.push_back(frame_stats);
// 4th stat
frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15));
frame_stats.received_time = frame_stats.captured_time + TimeDelta::Millis(30);
frame_stats.decode_start_time =
frame_stats.captured_time + TimeDelta::Millis(40);
stats.push_back(frame_stats);
// 5th stat
frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15));
frame_stats.decode_end_time =
frame_stats.captured_time + TimeDelta::Millis(50);
frame_stats.used_decoder =
Vp8CodecForOneFrame(1, frame_stats.decode_end_time);
stats.push_back(frame_stats);
// 6th stat
frame_stats = ShiftStatsOn(frame_stats, TimeDelta::Millis(15));
frame_stats.rendered_time = frame_stats.captured_time + TimeDelta::Millis(60);
frame_stats.rendered_frame_width = 10;
frame_stats.rendered_frame_height = 10;
stats.push_back(frame_stats);
comparator.Start(1);
comparator.EnsureStatsForStream(stream, sender, peers_count,
stream_start_time, stream_start_time);
for (size_t i = 0; i < stats.size() - 1; ++i) {
comparator.AddComparison(stats_key,
/*captured=*/absl::nullopt,
/*rendered=*/absl::nullopt,
FrameComparisonType::kFrameInFlight, stats[i]);
}
comparator.AddComparison(stats_key,
/*captured=*/absl::nullopt,
/*rendered=*/absl::nullopt,
FrameComparisonType::kRegular,
stats[stats.size() - 1]);
comparator.Stop(/*last_rendered_frame_times=*/{});
EXPECT_EQ(comparator.stream_stats().size(), 1lu);
StreamStats result_stats = comparator.stream_stats().at(stats_key);
EXPECT_DOUBLE_EQ(result_stats.transport_time_ms.GetAverage(), 20.0)
<< ToString(result_stats.transport_time_ms);
EXPECT_EQ(result_stats.transport_time_ms.NumSamples(), 3);
EXPECT_DOUBLE_EQ(result_stats.total_delay_incl_transport_ms.GetAverage(),
60.0)
<< ToString(result_stats.total_delay_incl_transport_ms);
EXPECT_EQ(result_stats.total_delay_incl_transport_ms.NumSamples(), 1);
EXPECT_DOUBLE_EQ(result_stats.encode_time_ms.GetAverage(), 10)
<< ToString(result_stats.encode_time_ms);
EXPECT_EQ(result_stats.encode_time_ms.NumSamples(), 4);
EXPECT_DOUBLE_EQ(result_stats.decode_time_ms.GetAverage(), 10)
<< ToString(result_stats.decode_time_ms);
EXPECT_EQ(result_stats.decode_time_ms.NumSamples(), 2);
EXPECT_DOUBLE_EQ(result_stats.receive_to_render_time_ms.GetAverage(), 30)
<< ToString(result_stats.receive_to_render_time_ms);
EXPECT_EQ(result_stats.receive_to_render_time_ms.NumSamples(), 1);
EXPECT_DOUBLE_EQ(result_stats.resolution_of_rendered_frame.GetAverage(), 100)
<< ToString(result_stats.resolution_of_rendered_frame);
EXPECT_EQ(result_stats.resolution_of_rendered_frame.NumSamples(), 1);
EXPECT_DOUBLE_EQ(result_stats.encode_frame_rate.GetEventsPerSecond(),
4.0 / 45 * 1000)
<< "There should be 4 events with interval of 15 ms";
}
} // namespace
} // namespace webrtc