blob: c2a5e4991f8ca3878818e78931eb0b452f81fb33 [file] [log] [blame]
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +00001/*
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
pbos@webrtc.orgba7f6a82013-06-04 08:14:10 +000011#include "webrtc/tools/frame_analyzer/video_quality_analysis.h"
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000012
pbos@webrtc.org12dc1a32013-08-05 16:22:53 +000013#include <assert.h>
14#include <stdio.h>
15#include <stdlib.h>
16
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000017#include <string>
18
19#define STATS_LINE_LENGTH 32
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +000020#define Y4M_FILE_HEADER_MAX_SIZE 200
21#define Y4M_FRAME_DELIMITER "FRAME"
22#define Y4M_FRAME_HEADER_SIZE 6
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000023
24namespace webrtc {
25namespace test {
26
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000027using std::string;
28
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000029int GetI420FrameSize(int width, int height) {
30 int half_width = (width + 1) >> 1;
31 int half_height = (height + 1) >> 1;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000032
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000033 int y_plane = width * height; // I420 Y plane.
34 int u_plane = half_width * half_height; // I420 U plane.
35 int v_plane = half_width * half_height; // I420 V plane.
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000036
37 return y_plane + u_plane + v_plane;
38}
39
40int ExtractFrameSequenceNumber(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000041 size_t space_position = line.find(' ');
42 if (space_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000043 return -1;
44 }
45 std::string frame = line.substr(0, space_position);
46
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000047 size_t underscore_position = frame.find('_');
48 if (underscore_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000049 return -1;
50 }
51 std::string frame_number = frame.substr(underscore_position + 1);
52
53 return strtol(frame_number.c_str(), NULL, 10);
54}
55
56int ExtractDecodedFrameNumber(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000057 size_t space_position = line.find(' ');
58 if (space_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000059 return -1;
60 }
61 std::string decoded_number = line.substr(space_position + 1);
62
63 return strtol(decoded_number.c_str(), NULL, 10);
64}
65
66bool IsThereBarcodeError(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000067 size_t barcode_error_position = line.find("Barcode error");
68 if (barcode_error_position != string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000069 return true;
70 }
71 return false;
72}
73
74bool GetNextStatsLine(FILE* stats_file, char* line) {
75 int chars = 0;
76 char buf = 0;
77
78 while (buf != '\n') {
79 size_t chars_read = fread(&buf, 1, 1, stats_file);
80 if (chars_read != 1 || feof(stats_file)) {
81 return false;
82 }
83 line[chars] = buf;
84 ++chars;
85 }
86 line[chars-1] = '\0'; // Strip the trailing \n and put end of string.
87 return true;
88}
89
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +000090bool ExtractFrameFromYuvFile(const char* i420_file_name, int width, int height,
91 int frame_number, uint8* result_frame) {
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000092 int frame_size = GetI420FrameSize(width, height);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000093 int offset = frame_number * frame_size; // Calculate offset for the frame.
94 bool errors = false;
95
96 FILE* input_file = fopen(i420_file_name, "rb");
97 if (input_file == NULL) {
98 fprintf(stderr, "Couldn't open input file for reading: %s\n",
99 i420_file_name);
100 return false;
101 }
102
103 // Change stream pointer to new offset.
104 fseek(input_file, offset, SEEK_SET);
105
106 size_t bytes_read = fread(result_frame, 1, frame_size, input_file);
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +0000107 if (bytes_read != static_cast<size_t>(frame_size) &&
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000108 ferror(input_file)) {
109 fprintf(stdout, "Error while reading frame no %d from file %s\n",
110 frame_number, i420_file_name);
111 errors = true;
112 }
113 fclose(input_file);
114 return !errors;
115}
116
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000117bool ExtractFrameFromY4mFile(const char* y4m_file_name, int width, int height,
118 int frame_number, uint8* result_frame) {
119 int frame_size = GetI420FrameSize(width, height);
mcasas@webrtc.org4375e1a2014-03-14 12:51:02 +0000120 int frame_offset = frame_number * frame_size;
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000121 bool errors = false;
122
123 FILE* input_file = fopen(y4m_file_name, "rb");
124 if (input_file == NULL) {
125 fprintf(stderr, "Couldn't open input file for reading: %s\n",
126 y4m_file_name);
127 return false;
128 }
129
130 // YUV4MPEG2, a.k.a. Y4M File format has a file header and a frame header. The
131 // file header has the aspect: "YUV4MPEG2 C420 W640 H360 Ip F30:1 A1:1".
132 // Skip the header if this is the first frame of the file.
133 if (frame_number == 0) {
134 char frame_header[Y4M_FILE_HEADER_MAX_SIZE];
135 size_t bytes_read =
136 fread(frame_header, 1, Y4M_FILE_HEADER_MAX_SIZE, input_file);
137 if (bytes_read != static_cast<size_t>(frame_size) && ferror(input_file)) {
138 fprintf(stdout, "Error while reading first frame from file %s\n",
139 y4m_file_name);
140 fclose(input_file);
141 return false;
142 }
143 std::string header_contents(frame_header);
144 std::size_t found = header_contents.find(Y4M_FRAME_DELIMITER);
145 if (found == std::string::npos) {
146 fprintf(stdout, "Corrupted Y4M header, could not find \"FRAME\" in %s\n",
147 header_contents.c_str());
148 fclose(input_file);
149 return false;
150 }
151 frame_offset = static_cast<int>(found);
152 }
153
154 // Change stream pointer to new offset, skipping the frame header as well.
155 fseek(input_file, frame_offset + Y4M_FRAME_HEADER_SIZE, SEEK_SET);
156
157 size_t bytes_read = fread(result_frame, 1, frame_size, input_file);
158 if (bytes_read != static_cast<size_t>(frame_size) &&
159 ferror(input_file)) {
160 fprintf(stdout, "Error while reading frame no %d from file %s\n",
161 frame_number, y4m_file_name);
162 errors = true;
163 }
164
165 fclose(input_file);
166 return !errors;
167}
168
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000169double CalculateMetrics(VideoAnalysisMetricsType video_metrics_type,
170 const uint8* ref_frame, const uint8* test_frame,
171 int width, int height) {
172 if (!ref_frame || !test_frame)
173 return -1;
174 else if (height < 0 || width < 0)
175 return -1;
176 int half_width = (width + 1) >> 1;
177 int half_height = (height + 1) >> 1;
178 const uint8* src_y_a = ref_frame;
179 const uint8* src_u_a = src_y_a + width * height;
180 const uint8* src_v_a = src_u_a + half_width * half_height;
181 const uint8* src_y_b = test_frame;
182 const uint8* src_u_b = src_y_b + width * height;
183 const uint8* src_v_b = src_u_b + half_width * half_height;
184
185 int stride_y = width;
186 int stride_uv = half_width;
187
188 double result = 0.0;
189
190 switch (video_metrics_type) {
191 case kPSNR:
192 // In the following: stride is determined by width.
193 result = libyuv::I420Psnr(src_y_a, width, src_u_a, half_width,
194 src_v_a, half_width, src_y_b, width,
195 src_u_b, half_width, src_v_b, half_width,
196 width, height);
197 // LibYuv sets the max psnr value to 128, we restrict it to 48.
198 // In case of 0 mse in one frame, 128 can skew the results significantly.
199 result = (result > 48.0) ? 48.0 : result;
200 break;
201 case kSSIM:
202 result = libyuv::I420Ssim(src_y_a, stride_y, src_u_a, stride_uv,
203 src_v_a, stride_uv, src_y_b, stride_y,
204 src_u_b, stride_uv, src_v_b, stride_uv,
205 width, height);
206 break;
207 default:
208 assert(false);
209 }
210
211 return result;
212}
213
214void RunAnalysis(const char* reference_file_name, const char* test_file_name,
215 const char* stats_file_name, int width, int height,
216 ResultsContainer* results) {
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000217 // Check if the reference_file_name ends with "y4m".
218 bool y4m_mode = false;
219 if (std::string(reference_file_name).find("y4m") != std::string::npos){
220 y4m_mode = true;
221 }
222
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000223 int size = GetI420FrameSize(width, height);
224 FILE* stats_file = fopen(stats_file_name, "r");
225
226 // String buffer for the lines in the stats file.
227 char line[STATS_LINE_LENGTH];
228
229 // Allocate buffers for test and reference frames.
230 uint8* test_frame = new uint8[size];
231 uint8* reference_frame = new uint8[size];
232 int previous_frame_number = -1;
233
234 // While there are entries in the stats file.
235 while (GetNextStatsLine(stats_file, line)) {
236 int extracted_test_frame = ExtractFrameSequenceNumber(line);
237 int decoded_frame_number = ExtractDecodedFrameNumber(line);
238
239 // If there was problem decoding the barcode in this frame or the frame has
240 // been duplicated, continue.
241 if (IsThereBarcodeError(line) ||
242 decoded_frame_number == previous_frame_number) {
243 continue;
244 }
245
246 assert(extracted_test_frame != -1);
247 assert(decoded_frame_number != -1);
248
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000249 ExtractFrameFromYuvFile(test_file_name, width, height, extracted_test_frame,
250 test_frame);
251 if (y4m_mode) {
252 ExtractFrameFromY4mFile(reference_file_name, width, height,
253 decoded_frame_number, reference_frame);
254 } else {
255 ExtractFrameFromYuvFile(reference_file_name, width, height,
256 decoded_frame_number, reference_frame);
257 }
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000258
259 // Calculate the PSNR and SSIM.
260 double result_psnr = CalculateMetrics(kPSNR, reference_frame, test_frame,
261 width, height);
262 double result_ssim = CalculateMetrics(kSSIM, reference_frame, test_frame,
263 width, height);
264
265 previous_frame_number = decoded_frame_number;
266
267 // Fill in the result struct.
268 AnalysisResult result;
269 result.frame_number = decoded_frame_number;
270 result.psnr_value = result_psnr;
271 result.ssim_value = result_ssim;
272
273 results->frames.push_back(result);
274 }
275
276 // Cleanup.
277 fclose(stats_file);
278 delete[] test_frame;
279 delete[] reference_frame;
280}
281
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000282void PrintMaxRepeatedAndSkippedFrames(const std::string& label,
283 const std::string& stats_file_name) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000284 PrintMaxRepeatedAndSkippedFrames(stdout, label, stats_file_name);
285}
286
287void PrintMaxRepeatedAndSkippedFrames(FILE* output, const std::string& label,
288 const std::string& stats_file_name) {
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000289 FILE* stats_file = fopen(stats_file_name.c_str(), "r");
290 if (stats_file == NULL) {
291 fprintf(stderr, "Couldn't open stats file for reading: %s\n",
292 stats_file_name.c_str());
293 return;
294 }
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000295 char line[STATS_LINE_LENGTH];
296
297 int repeated_frames = 1;
298 int max_repeated_frames = 1;
299 int max_skipped_frames = 1;
300 int previous_frame_number = -1;
301
302 while (GetNextStatsLine(stats_file, line)) {
303 int decoded_frame_number = ExtractDecodedFrameNumber(line);
304
305 if (decoded_frame_number == -1) {
306 continue;
307 }
308
309 // Calculate how many frames a cluster of repeated frames contains.
310 if (decoded_frame_number == previous_frame_number) {
311 ++repeated_frames;
312 if (repeated_frames > max_repeated_frames) {
313 max_repeated_frames = repeated_frames;
314 }
315 } else {
316 repeated_frames = 1;
317 }
318
319 // Calculate how much frames have been skipped.
320 if (decoded_frame_number != 0 && previous_frame_number != -1) {
321 int skipped_frames = decoded_frame_number - previous_frame_number - 1;
322 if (skipped_frames > max_skipped_frames) {
323 max_skipped_frames = skipped_frames;
324 }
325 }
326 previous_frame_number = decoded_frame_number;
327 }
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000328 fprintf(output, "RESULT Max_repeated: %s= %d\n", label.c_str(),
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000329 max_repeated_frames);
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000330 fprintf(output, "RESULT Max_skipped: %s= %d\n", label.c_str(),
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000331 max_skipped_frames);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000332 fclose(stats_file);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000333}
334
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000335void PrintAnalysisResults(const std::string& label, ResultsContainer* results) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000336 PrintAnalysisResults(stdout, label, results);
337}
338
339void PrintAnalysisResults(FILE* output, const std::string& label,
340 ResultsContainer* results) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000341 std::vector<AnalysisResult>::iterator iter;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000342
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000343 fprintf(output, "RESULT Unique_frames_count: %s= %u\n", label.c_str(),
pbos@webrtc.org319c98d2013-09-10 15:23:50 +0000344 static_cast<unsigned int>(results->frames.size()));
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000345
346 if (results->frames.size() > 0u) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000347 fprintf(output, "RESULT PSNR: %s= [", label.c_str());
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000348 for (iter = results->frames.begin(); iter != results->frames.end() - 1;
349 ++iter) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000350 fprintf(output, "%f,", iter->psnr_value);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000351 }
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000352 fprintf(output, "%f] dB\n", iter->psnr_value);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000353
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000354 fprintf(output, "RESULT SSIM: %s= [", label.c_str());
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000355 for (iter = results->frames.begin(); iter != results->frames.end() - 1;
356 ++iter) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000357 fprintf(output, "%f,", iter->ssim_value);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000358 }
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000359 fprintf(output, "%f]\n", iter->ssim_value);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000360 }
361}
362
363} // namespace test
364} // namespace webrtc