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 "webrtc/test/testsupport/metrics/video_metrics.h"
12 
13 #include <assert.h>
14 #include <stdio.h>
15 
16 #include <algorithm>  // min_element, max_element
17 #include <memory>
18 
19 #include "webrtc/api/video/i420_buffer.h"
20 #include "webrtc/api/video/video_frame.h"
21 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
22 #include "webrtc/test/frame_utils.h"
23 #include "libyuv/convert.h"
24 
25 namespace webrtc {
26 namespace test {
27 
28 // Copy here so our callers won't need to include libyuv for this constant.
29 double kMetricsPerfectPSNR = kPerfectPSNR;
30 
31 // Used for calculating min and max values.
LessForFrameResultValue(const FrameResult & s1,const FrameResult & s2)32 static bool LessForFrameResultValue(const FrameResult& s1,
33                                     const FrameResult& s2) {
34   return s1.value < s2.value;
35 }
36 
37 enum VideoMetricsType { kPSNR, kSSIM, kBoth };
38 
39 // Calculates metrics for a frame and adds statistics to the result for it.
CalculateFrame(VideoMetricsType video_metrics_type,const VideoFrameBuffer & ref,const VideoFrameBuffer & test,int frame_number,QualityMetricsResult * result)40 void CalculateFrame(VideoMetricsType video_metrics_type,
41                     const VideoFrameBuffer& ref,
42                     const VideoFrameBuffer& test,
43                     int frame_number,
44                     QualityMetricsResult* result) {
45   FrameResult frame_result = {0, 0};
46   frame_result.frame_number = frame_number;
47   switch (video_metrics_type) {
48     case kPSNR:
49       frame_result.value = I420PSNR(ref, test);
50       break;
51     case kSSIM:
52       frame_result.value = I420SSIM(ref, test);
53       break;
54     default:
55       assert(false);
56   }
57   result->frames.push_back(frame_result);
58 }
59 
60 // Calculates average, min and max values for the supplied struct, if non-NULL.
CalculateStats(QualityMetricsResult * result)61 void CalculateStats(QualityMetricsResult* result) {
62   if (result == NULL || result->frames.size() == 0) {
63     return;
64   }
65   // Calculate average.
66   std::vector<FrameResult>::iterator iter;
67   double metrics_values_sum = 0.0;
68   for (iter = result->frames.begin(); iter != result->frames.end(); ++iter) {
69     metrics_values_sum += iter->value;
70   }
71   result->average = metrics_values_sum / result->frames.size();
72 
73   // Calculate min/max statistics.
74   iter = std::min_element(result->frames.begin(), result->frames.end(),
75                      LessForFrameResultValue);
76   result->min = iter->value;
77   result->min_frame_number = iter->frame_number;
78   iter = std::max_element(result->frames.begin(), result->frames.end(),
79                      LessForFrameResultValue);
80   result->max = iter->value;
81   result->max_frame_number = iter->frame_number;
82 }
83 
84 // Single method that handles all combinations of video metrics calculation, to
85 // minimize code duplication. Either psnr_result or ssim_result may be NULL,
86 // depending on which VideoMetricsType is targeted.
CalculateMetrics(VideoMetricsType video_metrics_type,const char * ref_filename,const char * test_filename,int width,int height,QualityMetricsResult * psnr_result,QualityMetricsResult * ssim_result)87 int CalculateMetrics(VideoMetricsType video_metrics_type,
88                      const char* ref_filename,
89                      const char* test_filename,
90                      int width,
91                      int height,
92                      QualityMetricsResult* psnr_result,
93                      QualityMetricsResult* ssim_result) {
94   assert(ref_filename != NULL);
95   assert(test_filename != NULL);
96   assert(width > 0);
97   assert(height > 0);
98 
99   FILE* ref_fp = fopen(ref_filename, "rb");
100   if (ref_fp == NULL) {
101     // Cannot open reference file.
102     fprintf(stderr, "Cannot open file %s\n", ref_filename);
103     return -1;
104   }
105   FILE* test_fp = fopen(test_filename, "rb");
106   if (test_fp == NULL) {
107     // Cannot open test file.
108     fprintf(stderr, "Cannot open file %s\n", test_filename);
109     fclose(ref_fp);
110     return -2;
111   }
112   int frame_number = 0;
113 
114   // Read reference and test frames.
115   for (;;) {
116     rtc::scoped_refptr<I420Buffer> ref_i420_buffer(
117         test::ReadI420Buffer(width, height, ref_fp));
118     if (!ref_i420_buffer)
119       break;
120 
121     rtc::scoped_refptr<I420Buffer> test_i420_buffer(
122         test::ReadI420Buffer(width, height, test_fp));
123 
124     if (!test_i420_buffer)
125       break;
126 
127     switch (video_metrics_type) {
128       case kPSNR:
129         CalculateFrame(kPSNR, *ref_i420_buffer, *test_i420_buffer, frame_number,
130                        psnr_result);
131         break;
132       case kSSIM:
133         CalculateFrame(kSSIM, *ref_i420_buffer, *test_i420_buffer, frame_number,
134                        ssim_result);
135         break;
136       case kBoth:
137         CalculateFrame(kPSNR, *ref_i420_buffer, *test_i420_buffer, frame_number,
138                        psnr_result);
139         CalculateFrame(kSSIM, *ref_i420_buffer, *test_i420_buffer, frame_number,
140                        ssim_result);
141         break;
142     }
143     frame_number++;
144   }
145   int return_code = 0;
146   if (frame_number == 0) {
147     fprintf(stderr, "Tried to measure video metrics from empty files "
148             "(reference file: %s  test file: %s)\n", ref_filename,
149             test_filename);
150     return_code = -3;
151   } else {
152     CalculateStats(psnr_result);
153     CalculateStats(ssim_result);
154   }
155   fclose(ref_fp);
156   fclose(test_fp);
157   return return_code;
158 }
159 
I420MetricsFromFiles(const char * ref_filename,const char * test_filename,int width,int height,QualityMetricsResult * psnr_result,QualityMetricsResult * ssim_result)160 int I420MetricsFromFiles(const char* ref_filename,
161                          const char* test_filename,
162                          int width,
163                          int height,
164                          QualityMetricsResult* psnr_result,
165                          QualityMetricsResult* ssim_result) {
166   assert(psnr_result != NULL);
167   assert(ssim_result != NULL);
168   return CalculateMetrics(kBoth, ref_filename, test_filename, width, height,
169                           psnr_result, ssim_result);
170 }
171 
I420PSNRFromFiles(const char * ref_filename,const char * test_filename,int width,int height,QualityMetricsResult * result)172 int I420PSNRFromFiles(const char* ref_filename,
173                       const char* test_filename,
174                       int width,
175                       int height,
176                       QualityMetricsResult* result) {
177   assert(result != NULL);
178   return CalculateMetrics(kPSNR, ref_filename, test_filename, width, height,
179                           result, NULL);
180 }
181 
I420SSIMFromFiles(const char * ref_filename,const char * test_filename,int width,int height,QualityMetricsResult * result)182 int I420SSIMFromFiles(const char* ref_filename,
183                       const char* test_filename,
184                       int width,
185                       int height,
186                       QualityMetricsResult* result) {
187   assert(result != NULL);
188   return CalculateMetrics(kSSIM, ref_filename, test_filename, width, height,
189                           NULL, result);
190 }
191 
192 }  // namespace test
193 }  // namespace webrtc
194