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