blob: dfb7d900657540ca87adc6e38e79b143a721abe6 [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>
mandermo74568172017-01-17 03:24:57 -080016#include <algorithm>
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000017#include <string>
mandermo74568172017-01-17 03:24:57 -080018#include <map>
19#include <utility>
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000020
21#define STATS_LINE_LENGTH 32
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +000022#define Y4M_FILE_HEADER_MAX_SIZE 200
23#define Y4M_FRAME_DELIMITER "FRAME"
24#define Y4M_FRAME_HEADER_SIZE 6
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000025
26namespace webrtc {
27namespace test {
28
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000029using std::string;
30
Henrik Kjellander67bcb602015-10-07 08:42:52 +020031ResultsContainer::ResultsContainer() {}
32ResultsContainer::~ResultsContainer() {}
33
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000034int GetI420FrameSize(int width, int height) {
35 int half_width = (width + 1) >> 1;
36 int half_height = (height + 1) >> 1;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000037
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000038 int y_plane = width * height; // I420 Y plane.
39 int u_plane = half_width * half_height; // I420 U plane.
40 int v_plane = half_width * half_height; // I420 V plane.
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000041
42 return y_plane + u_plane + v_plane;
43}
44
45int ExtractFrameSequenceNumber(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000046 size_t space_position = line.find(' ');
47 if (space_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000048 return -1;
49 }
50 std::string frame = line.substr(0, space_position);
51
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000052 size_t underscore_position = frame.find('_');
53 if (underscore_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000054 return -1;
55 }
56 std::string frame_number = frame.substr(underscore_position + 1);
57
58 return strtol(frame_number.c_str(), NULL, 10);
59}
60
61int ExtractDecodedFrameNumber(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000062 size_t space_position = line.find(' ');
63 if (space_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000064 return -1;
65 }
66 std::string decoded_number = line.substr(space_position + 1);
67
68 return strtol(decoded_number.c_str(), NULL, 10);
69}
70
71bool IsThereBarcodeError(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000072 size_t barcode_error_position = line.find("Barcode error");
73 if (barcode_error_position != string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000074 return true;
75 }
76 return false;
77}
78
79bool GetNextStatsLine(FILE* stats_file, char* line) {
80 int chars = 0;
81 char buf = 0;
82
83 while (buf != '\n') {
84 size_t chars_read = fread(&buf, 1, 1, stats_file);
85 if (chars_read != 1 || feof(stats_file)) {
86 return false;
87 }
88 line[chars] = buf;
89 ++chars;
90 }
91 line[chars-1] = '\0'; // Strip the trailing \n and put end of string.
92 return true;
93}
94
Peter Boström0c4e06b2015-10-07 12:23:21 +020095bool ExtractFrameFromYuvFile(const char* i420_file_name,
96 int width,
97 int height,
98 int frame_number,
99 uint8_t* result_frame) {
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +0000100 int frame_size = GetI420FrameSize(width, height);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000101 int offset = frame_number * frame_size; // Calculate offset for the frame.
102 bool errors = false;
103
104 FILE* input_file = fopen(i420_file_name, "rb");
105 if (input_file == NULL) {
106 fprintf(stderr, "Couldn't open input file for reading: %s\n",
107 i420_file_name);
108 return false;
109 }
110
111 // Change stream pointer to new offset.
112 fseek(input_file, offset, SEEK_SET);
113
114 size_t bytes_read = fread(result_frame, 1, frame_size, input_file);
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +0000115 if (bytes_read != static_cast<size_t>(frame_size) &&
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000116 ferror(input_file)) {
117 fprintf(stdout, "Error while reading frame no %d from file %s\n",
118 frame_number, i420_file_name);
119 errors = true;
120 }
121 fclose(input_file);
122 return !errors;
123}
124
Peter Boström0c4e06b2015-10-07 12:23:21 +0200125bool ExtractFrameFromY4mFile(const char* y4m_file_name,
126 int width,
127 int height,
128 int frame_number,
129 uint8_t* result_frame) {
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000130 int frame_size = GetI420FrameSize(width, height);
charujain1b5b22d2016-11-29 02:00:52 -0800131 int inital_offset = frame_number * (frame_size + Y4M_FRAME_HEADER_SIZE);
132 int frame_offset = 0;
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000133
134 FILE* input_file = fopen(y4m_file_name, "rb");
135 if (input_file == NULL) {
136 fprintf(stderr, "Couldn't open input file for reading: %s\n",
137 y4m_file_name);
138 return false;
139 }
140
141 // YUV4MPEG2, a.k.a. Y4M File format has a file header and a frame header. The
142 // file header has the aspect: "YUV4MPEG2 C420 W640 H360 Ip F30:1 A1:1".
charujain1b5b22d2016-11-29 02:00:52 -0800143 char frame_header[Y4M_FILE_HEADER_MAX_SIZE];
144 size_t bytes_read =
145 fread(frame_header, 1, Y4M_FILE_HEADER_MAX_SIZE - 1, input_file);
146 if (bytes_read != static_cast<size_t>(frame_size) && ferror(input_file)) {
147 fprintf(stdout, "Error while reading frame from file %s\n",
148 y4m_file_name);
149 fclose(input_file);
150 return false;
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000151 }
charujain1b5b22d2016-11-29 02:00:52 -0800152 frame_header[bytes_read] = '\0';
153 std::string header_contents(frame_header);
154 std::size_t found = header_contents.find(Y4M_FRAME_DELIMITER);
155 if (found == std::string::npos) {
156 fprintf(stdout, "Corrupted Y4M header, could not find \"FRAME\" in %s\n",
157 header_contents.c_str());
158 fclose(input_file);
159 return false;
160 }
161 frame_offset = static_cast<int>(found);
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000162
163 // Change stream pointer to new offset, skipping the frame header as well.
charujain1b5b22d2016-11-29 02:00:52 -0800164 fseek(input_file, inital_offset + frame_offset + Y4M_FRAME_HEADER_SIZE,
165 SEEK_SET);
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000166
charujain1b5b22d2016-11-29 02:00:52 -0800167 bytes_read = fread(result_frame, 1, frame_size, input_file);
168 if (feof(input_file)) {
169 fclose(input_file);
170 return false;
171 }
172 if (bytes_read != static_cast<size_t>(frame_size) && ferror(input_file)) {
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000173 fprintf(stdout, "Error while reading frame no %d from file %s\n",
174 frame_number, y4m_file_name);
charujain1b5b22d2016-11-29 02:00:52 -0800175 fclose(input_file);
176 return false;
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000177 }
178
179 fclose(input_file);
charujain1b5b22d2016-11-29 02:00:52 -0800180 return true;
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000181}
182
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000183double CalculateMetrics(VideoAnalysisMetricsType video_metrics_type,
Peter Boström0c4e06b2015-10-07 12:23:21 +0200184 const uint8_t* ref_frame,
185 const uint8_t* test_frame,
186 int width,
187 int height) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000188 if (!ref_frame || !test_frame)
189 return -1;
190 else if (height < 0 || width < 0)
191 return -1;
192 int half_width = (width + 1) >> 1;
193 int half_height = (height + 1) >> 1;
Peter Boström0c4e06b2015-10-07 12:23:21 +0200194 const uint8_t* src_y_a = ref_frame;
195 const uint8_t* src_u_a = src_y_a + width * height;
196 const uint8_t* src_v_a = src_u_a + half_width * half_height;
197 const uint8_t* src_y_b = test_frame;
198 const uint8_t* src_u_b = src_y_b + width * height;
199 const uint8_t* src_v_b = src_u_b + half_width * half_height;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000200
201 int stride_y = width;
202 int stride_uv = half_width;
203
204 double result = 0.0;
205
206 switch (video_metrics_type) {
207 case kPSNR:
208 // In the following: stride is determined by width.
209 result = libyuv::I420Psnr(src_y_a, width, src_u_a, half_width,
210 src_v_a, half_width, src_y_b, width,
211 src_u_b, half_width, src_v_b, half_width,
212 width, height);
213 // LibYuv sets the max psnr value to 128, we restrict it to 48.
214 // In case of 0 mse in one frame, 128 can skew the results significantly.
215 result = (result > 48.0) ? 48.0 : result;
216 break;
217 case kSSIM:
218 result = libyuv::I420Ssim(src_y_a, stride_y, src_u_a, stride_uv,
219 src_v_a, stride_uv, src_y_b, stride_y,
220 src_u_b, stride_uv, src_v_b, stride_uv,
221 width, height);
222 break;
223 default:
224 assert(false);
225 }
226
227 return result;
228}
229
mandermo74568172017-01-17 03:24:57 -0800230void RunAnalysis(const char* reference_file_name,
231 const char* test_file_name,
232 const char* stats_file_reference_name,
233 const char* stats_file_test_name,
234 int width,
235 int height,
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000236 ResultsContainer* results) {
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000237 // Check if the reference_file_name ends with "y4m".
238 bool y4m_mode = false;
jbauch0f2e9392015-12-10 03:11:42 -0800239 if (std::string(reference_file_name).find("y4m") != std::string::npos) {
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000240 y4m_mode = true;
241 }
242
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000243 int size = GetI420FrameSize(width, height);
mandermo74568172017-01-17 03:24:57 -0800244 FILE* stats_file_ref = fopen(stats_file_reference_name, "r");
245 FILE* stats_file_test = fopen(stats_file_test_name, "r");
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000246
247 // String buffer for the lines in the stats file.
248 char line[STATS_LINE_LENGTH];
249
250 // Allocate buffers for test and reference frames.
Peter Boström0c4e06b2015-10-07 12:23:21 +0200251 uint8_t* test_frame = new uint8_t[size];
252 uint8_t* reference_frame = new uint8_t[size];
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000253 int previous_frame_number = -1;
254
mandermo74568172017-01-17 03:24:57 -0800255 // Maps barcode id to the frame id for the reference video.
256 // In case two frames have same id, then we only save the first one.
257 std::map<int, int> ref_barcode_to_frame;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000258 // While there are entries in the stats file.
mandermo74568172017-01-17 03:24:57 -0800259 while (GetNextStatsLine(stats_file_ref, line)) {
260 int extracted_ref_frame = ExtractFrameSequenceNumber(line);
261 int decoded_frame_number = ExtractDecodedFrameNumber(line);
262
263 // Insert will only add if it is not in map already.
264 ref_barcode_to_frame.insert(
265 std::make_pair(decoded_frame_number, extracted_ref_frame));
266 }
267
268 while (GetNextStatsLine(stats_file_test, line)) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000269 int extracted_test_frame = ExtractFrameSequenceNumber(line);
270 int decoded_frame_number = ExtractDecodedFrameNumber(line);
mandermo74568172017-01-17 03:24:57 -0800271 auto it = ref_barcode_to_frame.find(decoded_frame_number);
272 if (it == ref_barcode_to_frame.end()) {
273 // Not found in the reference video.
274 // TODO(mandermo) print
275 continue;
276 }
277 int extracted_ref_frame = it->second;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000278
279 // If there was problem decoding the barcode in this frame or the frame has
280 // been duplicated, continue.
281 if (IsThereBarcodeError(line) ||
282 decoded_frame_number == previous_frame_number) {
283 continue;
284 }
285
286 assert(extracted_test_frame != -1);
287 assert(decoded_frame_number != -1);
288
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000289 ExtractFrameFromYuvFile(test_file_name, width, height, extracted_test_frame,
290 test_frame);
291 if (y4m_mode) {
292 ExtractFrameFromY4mFile(reference_file_name, width, height,
mandermo74568172017-01-17 03:24:57 -0800293 extracted_ref_frame, reference_frame);
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000294 } else {
295 ExtractFrameFromYuvFile(reference_file_name, width, height,
mandermo74568172017-01-17 03:24:57 -0800296 extracted_ref_frame, reference_frame);
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000297 }
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000298
299 // Calculate the PSNR and SSIM.
mandermo74568172017-01-17 03:24:57 -0800300 double result_psnr =
301 CalculateMetrics(kPSNR, reference_frame, test_frame, width, height);
302 double result_ssim =
303 CalculateMetrics(kSSIM, reference_frame, test_frame, width, height);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000304
305 previous_frame_number = decoded_frame_number;
306
307 // Fill in the result struct.
308 AnalysisResult result;
309 result.frame_number = decoded_frame_number;
310 result.psnr_value = result_psnr;
311 result.ssim_value = result_ssim;
312
313 results->frames.push_back(result);
314 }
315
316 // Cleanup.
mandermo74568172017-01-17 03:24:57 -0800317 fclose(stats_file_ref);
318 fclose(stats_file_test);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000319 delete[] test_frame;
320 delete[] reference_frame;
321}
322
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000323void PrintMaxRepeatedAndSkippedFrames(const std::string& label,
mandermo74568172017-01-17 03:24:57 -0800324 const std::string& stats_file_ref_name,
325 const std::string& stats_file_test_name) {
326 PrintMaxRepeatedAndSkippedFrames(stdout, label, stats_file_ref_name,
327 stats_file_test_name);
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000328}
329
mandermo74568172017-01-17 03:24:57 -0800330namespace {
331// Clusters the frames in the file. First in the pair is the frame number and
332// second is the number
333// of frames in that cluster. So if first frame in video has number 100 and it
334// is repeated 3 after
335// each other, then the first entry in the returned vector has first set to 100
336// and second set
337// to 3.
338std::vector<std::pair<int, int> > CalculateFrameClusters(FILE* file) {
339 std::vector<std::pair<int, int> > frame_cnt;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000340 char line[STATS_LINE_LENGTH];
mandermo74568172017-01-17 03:24:57 -0800341 while (GetNextStatsLine(file, line)) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000342 int decoded_frame_number = ExtractDecodedFrameNumber(line);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000343 if (decoded_frame_number == -1) {
344 continue;
345 }
mandermo74568172017-01-17 03:24:57 -0800346 if (frame_cnt.empty() || frame_cnt.back().first != decoded_frame_number) {
347 frame_cnt.push_back(std::make_pair(decoded_frame_number, 1));
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000348 } else {
mandermo74568172017-01-17 03:24:57 -0800349 ++frame_cnt.back().second;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000350 }
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000351 }
mandermo74568172017-01-17 03:24:57 -0800352 return frame_cnt;
353}
354} // namespace
355
356void PrintMaxRepeatedAndSkippedFrames(FILE* output,
357 const std::string& label,
358 const std::string& stats_file_ref_name,
359 const std::string& stats_file_test_name) {
360 FILE* stats_file_ref = fopen(stats_file_ref_name.c_str(), "r");
361 FILE* stats_file_test = fopen(stats_file_test_name.c_str(), "r");
362 if (stats_file_ref == NULL) {
363 fprintf(stderr, "Couldn't open reference stats file for reading: %s\n",
364 stats_file_ref_name.c_str());
365 return;
366 }
367 if (stats_file_test == NULL) {
368 fprintf(stderr, "Couldn't open test stats file for reading: %s\n",
369 stats_file_test_name.c_str());
370 fclose(stats_file_ref);
371 return;
372 }
373
374 int max_repeated_frames = 1;
375 int max_skipped_frames = 1;
376
377 std::vector<std::pair<int, int> > frame_cnt_ref =
378 CalculateFrameClusters(stats_file_ref);
379
380 std::vector<std::pair<int, int> > frame_cnt_test =
381 CalculateFrameClusters(stats_file_test);
382
383 fclose(stats_file_ref);
384 fclose(stats_file_test);
385
386 auto it_ref = frame_cnt_ref.begin();
387 auto it_test = frame_cnt_test.begin();
388 auto end_ref = frame_cnt_ref.end();
389 auto end_test = frame_cnt_test.end();
390
391 if (it_test == end_test || it_ref == end_ref) {
392 fprintf(stderr, "Either test or ref file is empty, nothing to print\n");
393 return;
394 }
395
396 // Find the first frame in the reference video that match the first frame in
397 // the test video.
398 while (it_ref != end_ref && it_ref->first != it_test->first) {
399 ++it_ref;
400 }
401 if (it_ref == end_ref) {
402 fprintf(stderr,
403 "The barcode in the test video's first frame is not in the "
404 "reference video.\n");
405 return;
406 }
407
408 for (;;) {
409 max_repeated_frames =
410 std::max(max_repeated_frames, it_test->second - it_ref->second + 1);
411 ++it_test;
412 if (it_test == end_test) {
413 break;
414 }
415 int skipped_frames = 0;
416 ++it_ref;
417 while (it_ref != end_ref && it_ref->first != it_test->first) {
418 skipped_frames += it_ref->second;
419 ++it_ref;
420 }
421 if (it_ref == end_ref) {
422 fprintf(stderr,
423 "The barcode in the test video is not in the reference video.\n");
424 return;
425 }
426 if (skipped_frames > max_skipped_frames) {
427 max_skipped_frames = skipped_frames;
428 }
429 }
430
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000431 fprintf(output, "RESULT Max_repeated: %s= %d\n", label.c_str(),
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000432 max_repeated_frames);
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000433 fprintf(output, "RESULT Max_skipped: %s= %d\n", label.c_str(),
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000434 max_skipped_frames);
435}
436
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000437void PrintAnalysisResults(const std::string& label, ResultsContainer* results) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000438 PrintAnalysisResults(stdout, label, results);
439}
440
441void PrintAnalysisResults(FILE* output, const std::string& label,
442 ResultsContainer* results) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000443 std::vector<AnalysisResult>::iterator iter;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000444
brandtrd658ede2016-07-12 04:53:04 -0700445 fprintf(output, "RESULT Unique_frames_count: %s= %u score\n", label.c_str(),
pbos@webrtc.org319c98d2013-09-10 15:23:50 +0000446 static_cast<unsigned int>(results->frames.size()));
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000447
448 if (results->frames.size() > 0u) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000449 fprintf(output, "RESULT PSNR: %s= [", label.c_str());
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000450 for (iter = results->frames.begin(); iter != results->frames.end() - 1;
451 ++iter) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000452 fprintf(output, "%f,", iter->psnr_value);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000453 }
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000454 fprintf(output, "%f] dB\n", iter->psnr_value);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000455
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000456 fprintf(output, "RESULT SSIM: %s= [", label.c_str());
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000457 for (iter = results->frames.begin(); iter != results->frames.end() - 1;
458 ++iter) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000459 fprintf(output, "%f,", iter->ssim_value);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000460 }
kjellander@webrtc.org22c2f052015-01-28 13:52:08 +0000461 fprintf(output, "%f] score\n", iter->ssim_value);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000462 }
463}
464
465} // namespace test
466} // namespace webrtc