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