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