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 "rtc_base/checks.h"
18 #include "rtc_base/logging.h"
19 #include "test/testsupport/perf_test.h"
20 #include "third_party/libyuv/include/libyuv/compare.h"
21 
22 namespace webrtc {
23 namespace test {
24 
ResultsContainer()25 ResultsContainer::ResultsContainer() {}
~ResultsContainer()26 ResultsContainer::~ResultsContainer() {}
27 
28 template <typename FrameMetricFunction>
CalculateMetric(const FrameMetricFunction & frame_metric_function,const rtc::scoped_refptr<I420BufferInterface> & ref_buffer,const rtc::scoped_refptr<I420BufferInterface> & test_buffer)29 static double CalculateMetric(
30     const FrameMetricFunction& frame_metric_function,
31     const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
32     const rtc::scoped_refptr<I420BufferInterface>& test_buffer) {
33   RTC_CHECK_EQ(ref_buffer->width(), test_buffer->width());
34   RTC_CHECK_EQ(ref_buffer->height(), test_buffer->height());
35   return frame_metric_function(
36       ref_buffer->DataY(), ref_buffer->StrideY(), ref_buffer->DataU(),
37       ref_buffer->StrideU(), ref_buffer->DataV(), ref_buffer->StrideV(),
38       test_buffer->DataY(), test_buffer->StrideY(), test_buffer->DataU(),
39       test_buffer->StrideU(), test_buffer->DataV(), test_buffer->StrideV(),
40       test_buffer->width(), test_buffer->height());
41 }
42 
Psnr(const rtc::scoped_refptr<I420BufferInterface> & ref_buffer,const rtc::scoped_refptr<I420BufferInterface> & test_buffer)43 double Psnr(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
44             const rtc::scoped_refptr<I420BufferInterface>& test_buffer) {
45   // LibYuv sets the max psnr value to 128, we restrict it to 48.
46   // In case of 0 mse in one frame, 128 can skew the results significantly.
47   return std::min(48.0,
48                   CalculateMetric(&libyuv::I420Psnr, ref_buffer, test_buffer));
49 }
50 
Ssim(const rtc::scoped_refptr<I420BufferInterface> & ref_buffer,const rtc::scoped_refptr<I420BufferInterface> & test_buffer)51 double Ssim(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer,
52             const rtc::scoped_refptr<I420BufferInterface>& test_buffer) {
53   return CalculateMetric(&libyuv::I420Ssim, ref_buffer, test_buffer);
54 }
55 
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)56 std::vector<AnalysisResult> RunAnalysis(
57     const rtc::scoped_refptr<webrtc::test::Video>& reference_video,
58     const rtc::scoped_refptr<webrtc::test::Video>& test_video,
59     const std::vector<size_t>& test_frame_indices) {
60   std::vector<AnalysisResult> results;
61   for (size_t i = 0; i < test_video->number_of_frames(); ++i) {
62     const rtc::scoped_refptr<I420BufferInterface>& test_frame =
63         test_video->GetFrame(i);
64     const rtc::scoped_refptr<I420BufferInterface>& reference_frame =
65         reference_video->GetFrame(i);
66 
67     // Fill in the result struct.
68     AnalysisResult result;
69     result.frame_number = test_frame_indices[i];
70     result.psnr_value = Psnr(reference_frame, test_frame);
71     result.ssim_value = Ssim(reference_frame, test_frame);
72     results.push_back(result);
73   }
74 
75   return results;
76 }
77 
CalculateFrameClusters(const std::vector<size_t> & indices)78 std::vector<Cluster> CalculateFrameClusters(
79     const std::vector<size_t>& indices) {
80   std::vector<Cluster> clusters;
81 
82   for (size_t index : indices) {
83     if (!clusters.empty() && clusters.back().index == index) {
84       // This frame belongs to the previous cluster.
85       ++clusters.back().number_of_repeated_frames;
86     } else {
87       // Start a new cluster.
88       clusters.push_back({index, /* number_of_repeated_frames= */ 1});
89     }
90   }
91 
92   return clusters;
93 }
94 
GetMaxRepeatedFrames(const std::vector<Cluster> & clusters)95 int GetMaxRepeatedFrames(const std::vector<Cluster>& clusters) {
96   int max_number_of_repeated_frames = 0;
97   for (const Cluster& cluster : clusters) {
98     max_number_of_repeated_frames = std::max(max_number_of_repeated_frames,
99                                              cluster.number_of_repeated_frames);
100   }
101   return max_number_of_repeated_frames;
102 }
103 
GetMaxSkippedFrames(const std::vector<Cluster> & clusters)104 int GetMaxSkippedFrames(const std::vector<Cluster>& clusters) {
105   size_t max_skipped_frames = 0;
106   for (size_t i = 1; i < clusters.size(); ++i) {
107     const size_t skipped_frames = clusters[i].index - clusters[i - 1].index - 1;
108     max_skipped_frames = std::max(max_skipped_frames, skipped_frames);
109   }
110   return static_cast<int>(max_skipped_frames);
111 }
112 
GetTotalNumberOfSkippedFrames(const std::vector<Cluster> & clusters)113 int GetTotalNumberOfSkippedFrames(const std::vector<Cluster>& clusters) {
114   // The number of reference frames the test video spans.
115   const size_t number_ref_frames =
116       clusters.empty() ? 0 : 1 + clusters.back().index - clusters.front().index;
117   return static_cast<int>(number_ref_frames - clusters.size());
118 }
119 
PrintAnalysisResults(const std::string & label,ResultsContainer * results)120 void PrintAnalysisResults(const std::string& label, ResultsContainer* results) {
121   PrintAnalysisResults(stdout, label, results);
122 }
123 
PrintAnalysisResults(FILE * output,const std::string & label,ResultsContainer * results)124 void PrintAnalysisResults(FILE* output,
125                           const std::string& label,
126                           ResultsContainer* results) {
127   SetPerfResultsOutput(output);
128 
129   if (results->frames.size() > 0u) {
130     PrintResult("Unique_frames_count", "", label, results->frames.size(),
131                 "score", false);
132 
133     std::vector<double> psnr_values;
134     std::vector<double> ssim_values;
135     for (const auto& frame : results->frames) {
136       psnr_values.push_back(frame.psnr_value);
137       ssim_values.push_back(frame.ssim_value);
138     }
139 
140     PrintResultList("PSNR", "", label, psnr_values, "dB", false);
141     PrintResultList("SSIM", "", label, ssim_values, "score", false);
142   }
143 
144   PrintResult("Max_repeated", "", label, results->max_repeated_frames, "",
145               false);
146   PrintResult("Max_skipped", "", label, results->max_skipped_frames, "", false);
147   PrintResult("Total_skipped", "", label, results->total_skipped_frames, "",
148               false);
149   PrintResult("Decode_errors_reference", "", label, results->decode_errors_ref,
150               "", false);
151   PrintResult("Decode_errors_test", "", label, results->decode_errors_test, "",
152               false);
153 }
154 
155 }  // namespace test
156 }  // namespace webrtc
157