xref: /aosp_15_r20/external/webrtc/rtc_tools/frame_analyzer/video_quality_analysis.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "rtc_tools/frame_analyzer/video_quality_analysis.h"
12 
13 #include <algorithm>
14 #include <array>
15 #include <cstddef>
16 
17 #include "api/numerics/samples_stats_counter.h"
18 #include "api/test/metrics/metric.h"
19 #include "rtc_base/checks.h"
20 #include "rtc_base/logging.h"
21 #include "third_party/libyuv/include/libyuv/compare.h"
22 
23 namespace webrtc {
24 namespace test {
25 
ResultsContainer()26 ResultsContainer::ResultsContainer() {}
~ResultsContainer()27 ResultsContainer::~ResultsContainer() {}
28 
29 template <typename FrameMetricFunction>
CalculateMetric(const FrameMetricFunction & frame_metric_function,const rtc::scoped_refptr<I420BufferInterface> & ref_buffer,const rtc::scoped_refptr<I420BufferInterface> & test_buffer)30 static double CalculateMetric(
31     const FrameMetricFunction& frame_metric_function,
32     const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
33     const rtc::scoped_refptr<I420BufferInterface>& test_buffer) {
34   RTC_CHECK_EQ(ref_buffer->width(), test_buffer->width());
35   RTC_CHECK_EQ(ref_buffer->height(), test_buffer->height());
36   return frame_metric_function(
37       ref_buffer->DataY(), ref_buffer->StrideY(), ref_buffer->DataU(),
38       ref_buffer->StrideU(), ref_buffer->DataV(), ref_buffer->StrideV(),
39       test_buffer->DataY(), test_buffer->StrideY(), test_buffer->DataU(),
40       test_buffer->StrideU(), test_buffer->DataV(), test_buffer->StrideV(),
41       test_buffer->width(), test_buffer->height());
42 }
43 
Psnr(const rtc::scoped_refptr<I420BufferInterface> & ref_buffer,const rtc::scoped_refptr<I420BufferInterface> & test_buffer)44 double Psnr(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
45             const rtc::scoped_refptr<I420BufferInterface>& test_buffer) {
46   // LibYuv sets the max psnr value to 128, we restrict it to 48.
47   // In case of 0 mse in one frame, 128 can skew the results significantly.
48   return std::min(48.0,
49                   CalculateMetric(&libyuv::I420Psnr, ref_buffer, test_buffer));
50 }
51 
Ssim(const rtc::scoped_refptr<I420BufferInterface> & ref_buffer,const rtc::scoped_refptr<I420BufferInterface> & test_buffer)52 double Ssim(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
53             const rtc::scoped_refptr<I420BufferInterface>& test_buffer) {
54   return CalculateMetric(&libyuv::I420Ssim, ref_buffer, test_buffer);
55 }
56 
RunAnalysis(const rtc::scoped_refptr<webrtc::test::Video> & reference_video,const rtc::scoped_refptr<webrtc::test::Video> & test_video,const std::vector<size_t> & test_frame_indices)57 std::vector<AnalysisResult> RunAnalysis(
58     const rtc::scoped_refptr<webrtc::test::Video>& reference_video,
59     const rtc::scoped_refptr<webrtc::test::Video>& test_video,
60     const std::vector<size_t>& test_frame_indices) {
61   std::vector<AnalysisResult> results;
62   for (size_t i = 0; i < test_video->number_of_frames(); ++i) {
63     const rtc::scoped_refptr<I420BufferInterface>& test_frame =
64         test_video->GetFrame(i);
65     const rtc::scoped_refptr<I420BufferInterface>& reference_frame =
66         reference_video->GetFrame(i);
67 
68     // Fill in the result struct.
69     AnalysisResult result;
70     result.frame_number = test_frame_indices[i];
71     result.psnr_value = Psnr(reference_frame, test_frame);
72     result.ssim_value = Ssim(reference_frame, test_frame);
73     results.push_back(result);
74   }
75 
76   return results;
77 }
78 
CalculateFrameClusters(const std::vector<size_t> & indices)79 std::vector<Cluster> CalculateFrameClusters(
80     const std::vector<size_t>& indices) {
81   std::vector<Cluster> clusters;
82 
83   for (size_t index : indices) {
84     if (!clusters.empty() && clusters.back().index == index) {
85       // This frame belongs to the previous cluster.
86       ++clusters.back().number_of_repeated_frames;
87     } else {
88       // Start a new cluster.
89       clusters.push_back({index, /* number_of_repeated_frames= */ 1});
90     }
91   }
92 
93   return clusters;
94 }
95 
GetMaxRepeatedFrames(const std::vector<Cluster> & clusters)96 int GetMaxRepeatedFrames(const std::vector<Cluster>& clusters) {
97   int max_number_of_repeated_frames = 0;
98   for (const Cluster& cluster : clusters) {
99     max_number_of_repeated_frames = std::max(max_number_of_repeated_frames,
100                                              cluster.number_of_repeated_frames);
101   }
102   return max_number_of_repeated_frames;
103 }
104 
GetMaxSkippedFrames(const std::vector<Cluster> & clusters)105 int GetMaxSkippedFrames(const std::vector<Cluster>& clusters) {
106   size_t max_skipped_frames = 0;
107   for (size_t i = 1; i < clusters.size(); ++i) {
108     const size_t skipped_frames = clusters[i].index - clusters[i - 1].index - 1;
109     max_skipped_frames = std::max(max_skipped_frames, skipped_frames);
110   }
111   return static_cast<int>(max_skipped_frames);
112 }
113 
GetTotalNumberOfSkippedFrames(const std::vector<Cluster> & clusters)114 int GetTotalNumberOfSkippedFrames(const std::vector<Cluster>& clusters) {
115   // The number of reference frames the test video spans.
116   const size_t number_ref_frames =
117       clusters.empty() ? 0 : 1 + clusters.back().index - clusters.front().index;
118   return static_cast<int>(number_ref_frames - clusters.size());
119 }
120 
PrintAnalysisResults(const std::string & label,ResultsContainer & results,MetricsLogger & logger)121 void PrintAnalysisResults(const std::string& label,
122                           ResultsContainer& results,
123                           MetricsLogger& logger) {
124   if (results.frames.size() > 0u) {
125     logger.LogSingleValueMetric("Unique_frames_count", label,
126                                 results.frames.size(), Unit::kUnitless,
127                                 ImprovementDirection::kNeitherIsBetter);
128 
129     SamplesStatsCounter psnr_values;
130     SamplesStatsCounter ssim_values;
131     for (const auto& frame : results.frames) {
132       psnr_values.AddSample(frame.psnr_value);
133       ssim_values.AddSample(frame.ssim_value);
134     }
135 
136     logger.LogMetric("PSNR_dB", label, psnr_values, Unit::kUnitless,
137                      ImprovementDirection::kNeitherIsBetter);
138     logger.LogMetric("SSIM", label, ssim_values, Unit::kUnitless,
139                      ImprovementDirection::kNeitherIsBetter);
140   }
141 
142   logger.LogSingleValueMetric("Max_repeated", label,
143                               results.max_repeated_frames, Unit::kUnitless,
144                               ImprovementDirection::kNeitherIsBetter);
145   logger.LogSingleValueMetric("Max_skipped", label, results.max_skipped_frames,
146                               Unit::kUnitless,
147                               ImprovementDirection::kNeitherIsBetter);
148   logger.LogSingleValueMetric("Total_skipped", label,
149                               results.total_skipped_frames, Unit::kUnitless,
150                               ImprovementDirection::kNeitherIsBetter);
151   logger.LogSingleValueMetric("Decode_errors_reference", label,
152                               results.decode_errors_ref, Unit::kUnitless,
153                               ImprovementDirection::kNeitherIsBetter);
154   logger.LogSingleValueMetric("Decode_errors_test", label,
155                               results.decode_errors_test, Unit::kUnitless,
156                               ImprovementDirection::kNeitherIsBetter);
157 }
158 
159 }  // namespace test
160 }  // namespace webrtc
161