blob: c1911ca81c7033e3e063d11338f5d5461d674f18 [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>
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000016#include <string>
17
18#define STATS_LINE_LENGTH 32
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +000019#define Y4M_FILE_HEADER_MAX_SIZE 200
20#define Y4M_FRAME_DELIMITER "FRAME"
21#define Y4M_FRAME_HEADER_SIZE 6
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000022
23namespace webrtc {
24namespace test {
25
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000026using std::string;
27
Henrik Kjellander67bcb602015-10-07 08:42:52 +020028ResultsContainer::ResultsContainer() {}
29ResultsContainer::~ResultsContainer() {}
30
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000031int GetI420FrameSize(int width, int height) {
32 int half_width = (width + 1) >> 1;
33 int half_height = (height + 1) >> 1;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000034
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000035 int y_plane = width * height; // I420 Y plane.
36 int u_plane = half_width * half_height; // I420 U plane.
37 int v_plane = half_width * half_height; // I420 V plane.
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000038
39 return y_plane + u_plane + v_plane;
40}
41
42int ExtractFrameSequenceNumber(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000043 size_t space_position = line.find(' ');
44 if (space_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000045 return -1;
46 }
47 std::string frame = line.substr(0, space_position);
48
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000049 size_t underscore_position = frame.find('_');
50 if (underscore_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000051 return -1;
52 }
53 std::string frame_number = frame.substr(underscore_position + 1);
54
55 return strtol(frame_number.c_str(), NULL, 10);
56}
57
58int ExtractDecodedFrameNumber(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000059 size_t space_position = line.find(' ');
60 if (space_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000061 return -1;
62 }
63 std::string decoded_number = line.substr(space_position + 1);
64
65 return strtol(decoded_number.c_str(), NULL, 10);
66}
67
68bool IsThereBarcodeError(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000069 size_t barcode_error_position = line.find("Barcode error");
70 if (barcode_error_position != string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000071 return true;
72 }
73 return false;
74}
75
76bool GetNextStatsLine(FILE* stats_file, char* line) {
77 int chars = 0;
78 char buf = 0;
79
80 while (buf != '\n') {
81 size_t chars_read = fread(&buf, 1, 1, stats_file);
82 if (chars_read != 1 || feof(stats_file)) {
83 return false;
84 }
85 line[chars] = buf;
86 ++chars;
87 }
88 line[chars-1] = '\0'; // Strip the trailing \n and put end of string.
89 return true;
90}
91
Peter Boström0c4e06b2015-10-07 12:23:21 +020092bool ExtractFrameFromYuvFile(const char* i420_file_name,
93 int width,
94 int height,
95 int frame_number,
96 uint8_t* result_frame) {
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000097 int frame_size = GetI420FrameSize(width, height);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000098 int offset = frame_number * frame_size; // Calculate offset for the frame.
99 bool errors = false;
100
101 FILE* input_file = fopen(i420_file_name, "rb");
102 if (input_file == NULL) {
103 fprintf(stderr, "Couldn't open input file for reading: %s\n",
104 i420_file_name);
105 return false;
106 }
107
108 // Change stream pointer to new offset.
109 fseek(input_file, offset, SEEK_SET);
110
111 size_t bytes_read = fread(result_frame, 1, frame_size, input_file);
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +0000112 if (bytes_read != static_cast<size_t>(frame_size) &&
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000113 ferror(input_file)) {
114 fprintf(stdout, "Error while reading frame no %d from file %s\n",
115 frame_number, i420_file_name);
116 errors = true;
117 }
118 fclose(input_file);
119 return !errors;
120}
121
Peter Boström0c4e06b2015-10-07 12:23:21 +0200122bool ExtractFrameFromY4mFile(const char* y4m_file_name,
123 int width,
124 int height,
125 int frame_number,
126 uint8_t* result_frame) {
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000127 int frame_size = GetI420FrameSize(width, height);
charujain1b5b22d2016-11-29 02:00:52 -0800128 int inital_offset = frame_number * (frame_size + Y4M_FRAME_HEADER_SIZE);
129 int frame_offset = 0;
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000130
131 FILE* input_file = fopen(y4m_file_name, "rb");
132 if (input_file == NULL) {
133 fprintf(stderr, "Couldn't open input file for reading: %s\n",
134 y4m_file_name);
135 return false;
136 }
137
138 // YUV4MPEG2, a.k.a. Y4M File format has a file header and a frame header. The
139 // file header has the aspect: "YUV4MPEG2 C420 W640 H360 Ip F30:1 A1:1".
charujain1b5b22d2016-11-29 02:00:52 -0800140 char frame_header[Y4M_FILE_HEADER_MAX_SIZE];
141 size_t bytes_read =
142 fread(frame_header, 1, Y4M_FILE_HEADER_MAX_SIZE - 1, input_file);
143 if (bytes_read != static_cast<size_t>(frame_size) && ferror(input_file)) {
144 fprintf(stdout, "Error while reading frame from file %s\n",
145 y4m_file_name);
146 fclose(input_file);
147 return false;
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000148 }
charujain1b5b22d2016-11-29 02:00:52 -0800149 frame_header[bytes_read] = '\0';
150 std::string header_contents(frame_header);
151 std::size_t found = header_contents.find(Y4M_FRAME_DELIMITER);
152 if (found == std::string::npos) {
153 fprintf(stdout, "Corrupted Y4M header, could not find \"FRAME\" in %s\n",
154 header_contents.c_str());
155 fclose(input_file);
156 return false;
157 }
158 frame_offset = static_cast<int>(found);
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000159
160 // Change stream pointer to new offset, skipping the frame header as well.
charujain1b5b22d2016-11-29 02:00:52 -0800161 fseek(input_file, inital_offset + frame_offset + Y4M_FRAME_HEADER_SIZE,
162 SEEK_SET);
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000163
charujain1b5b22d2016-11-29 02:00:52 -0800164 bytes_read = fread(result_frame, 1, frame_size, input_file);
165 if (feof(input_file)) {
166 fclose(input_file);
167 return false;
168 }
169 if (bytes_read != static_cast<size_t>(frame_size) && ferror(input_file)) {
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000170 fprintf(stdout, "Error while reading frame no %d from file %s\n",
171 frame_number, y4m_file_name);
charujain1b5b22d2016-11-29 02:00:52 -0800172 fclose(input_file);
173 return false;
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000174 }
175
176 fclose(input_file);
charujain1b5b22d2016-11-29 02:00:52 -0800177 return true;
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000178}
179
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000180double CalculateMetrics(VideoAnalysisMetricsType video_metrics_type,
Peter Boström0c4e06b2015-10-07 12:23:21 +0200181 const uint8_t* ref_frame,
182 const uint8_t* test_frame,
183 int width,
184 int height) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000185 if (!ref_frame || !test_frame)
186 return -1;
187 else if (height < 0 || width < 0)
188 return -1;
189 int half_width = (width + 1) >> 1;
190 int half_height = (height + 1) >> 1;
Peter Boström0c4e06b2015-10-07 12:23:21 +0200191 const uint8_t* src_y_a = ref_frame;
192 const uint8_t* src_u_a = src_y_a + width * height;
193 const uint8_t* src_v_a = src_u_a + half_width * half_height;
194 const uint8_t* src_y_b = test_frame;
195 const uint8_t* src_u_b = src_y_b + width * height;
196 const uint8_t* src_v_b = src_u_b + half_width * half_height;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000197
198 int stride_y = width;
199 int stride_uv = half_width;
200
201 double result = 0.0;
202
203 switch (video_metrics_type) {
204 case kPSNR:
205 // In the following: stride is determined by width.
206 result = libyuv::I420Psnr(src_y_a, width, src_u_a, half_width,
207 src_v_a, half_width, src_y_b, width,
208 src_u_b, half_width, src_v_b, half_width,
209 width, height);
210 // LibYuv sets the max psnr value to 128, we restrict it to 48.
211 // In case of 0 mse in one frame, 128 can skew the results significantly.
212 result = (result > 48.0) ? 48.0 : result;
213 break;
214 case kSSIM:
215 result = libyuv::I420Ssim(src_y_a, stride_y, src_u_a, stride_uv,
216 src_v_a, stride_uv, src_y_b, stride_y,
217 src_u_b, stride_uv, src_v_b, stride_uv,
218 width, height);
219 break;
220 default:
221 assert(false);
222 }
223
224 return result;
225}
226
227void RunAnalysis(const char* reference_file_name, const char* test_file_name,
228 const char* stats_file_name, int width, int height,
229 ResultsContainer* results) {
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000230 // Check if the reference_file_name ends with "y4m".
231 bool y4m_mode = false;
jbauch0f2e9392015-12-10 03:11:42 -0800232 if (std::string(reference_file_name).find("y4m") != std::string::npos) {
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000233 y4m_mode = true;
234 }
235
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000236 int size = GetI420FrameSize(width, height);
237 FILE* stats_file = fopen(stats_file_name, "r");
238
239 // String buffer for the lines in the stats file.
240 char line[STATS_LINE_LENGTH];
241
242 // Allocate buffers for test and reference frames.
Peter Boström0c4e06b2015-10-07 12:23:21 +0200243 uint8_t* test_frame = new uint8_t[size];
244 uint8_t* reference_frame = new uint8_t[size];
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000245 int previous_frame_number = -1;
246
247 // While there are entries in the stats file.
248 while (GetNextStatsLine(stats_file, line)) {
249 int extracted_test_frame = ExtractFrameSequenceNumber(line);
250 int decoded_frame_number = ExtractDecodedFrameNumber(line);
251
252 // If there was problem decoding the barcode in this frame or the frame has
253 // been duplicated, continue.
254 if (IsThereBarcodeError(line) ||
255 decoded_frame_number == previous_frame_number) {
256 continue;
257 }
258
259 assert(extracted_test_frame != -1);
260 assert(decoded_frame_number != -1);
261
mcasas@webrtc.org6e2d0122014-03-14 12:45:45 +0000262 ExtractFrameFromYuvFile(test_file_name, width, height, extracted_test_frame,
263 test_frame);
264 if (y4m_mode) {
265 ExtractFrameFromY4mFile(reference_file_name, width, height,
266 decoded_frame_number, reference_frame);
267 } else {
268 ExtractFrameFromYuvFile(reference_file_name, width, height,
269 decoded_frame_number, reference_frame);
270 }
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000271
272 // Calculate the PSNR and SSIM.
273 double result_psnr = CalculateMetrics(kPSNR, reference_frame, test_frame,
274 width, height);
275 double result_ssim = CalculateMetrics(kSSIM, reference_frame, test_frame,
276 width, height);
277
278 previous_frame_number = decoded_frame_number;
279
280 // Fill in the result struct.
281 AnalysisResult result;
282 result.frame_number = decoded_frame_number;
283 result.psnr_value = result_psnr;
284 result.ssim_value = result_ssim;
285
286 results->frames.push_back(result);
287 }
288
289 // Cleanup.
290 fclose(stats_file);
291 delete[] test_frame;
292 delete[] reference_frame;
293}
294
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000295void PrintMaxRepeatedAndSkippedFrames(const std::string& label,
296 const std::string& stats_file_name) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000297 PrintMaxRepeatedAndSkippedFrames(stdout, label, stats_file_name);
298}
299
300void PrintMaxRepeatedAndSkippedFrames(FILE* output, const std::string& label,
301 const std::string& stats_file_name) {
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000302 FILE* stats_file = fopen(stats_file_name.c_str(), "r");
303 if (stats_file == NULL) {
304 fprintf(stderr, "Couldn't open stats file for reading: %s\n",
305 stats_file_name.c_str());
306 return;
307 }
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000308 char line[STATS_LINE_LENGTH];
309
310 int repeated_frames = 1;
311 int max_repeated_frames = 1;
312 int max_skipped_frames = 1;
313 int previous_frame_number = -1;
314
315 while (GetNextStatsLine(stats_file, line)) {
316 int decoded_frame_number = ExtractDecodedFrameNumber(line);
317
318 if (decoded_frame_number == -1) {
319 continue;
320 }
321
322 // Calculate how many frames a cluster of repeated frames contains.
323 if (decoded_frame_number == previous_frame_number) {
324 ++repeated_frames;
325 if (repeated_frames > max_repeated_frames) {
326 max_repeated_frames = repeated_frames;
327 }
328 } else {
329 repeated_frames = 1;
330 }
331
332 // Calculate how much frames have been skipped.
333 if (decoded_frame_number != 0 && previous_frame_number != -1) {
334 int skipped_frames = decoded_frame_number - previous_frame_number - 1;
335 if (skipped_frames > max_skipped_frames) {
336 max_skipped_frames = skipped_frames;
337 }
338 }
339 previous_frame_number = decoded_frame_number;
340 }
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000341 fprintf(output, "RESULT Max_repeated: %s= %d\n", label.c_str(),
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000342 max_repeated_frames);
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000343 fprintf(output, "RESULT Max_skipped: %s= %d\n", label.c_str(),
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000344 max_skipped_frames);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000345 fclose(stats_file);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000346}
347
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000348void PrintAnalysisResults(const std::string& label, ResultsContainer* results) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000349 PrintAnalysisResults(stdout, label, results);
350}
351
352void PrintAnalysisResults(FILE* output, const std::string& label,
353 ResultsContainer* results) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000354 std::vector<AnalysisResult>::iterator iter;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000355
brandtrd658ede2016-07-12 04:53:04 -0700356 fprintf(output, "RESULT Unique_frames_count: %s= %u score\n", label.c_str(),
pbos@webrtc.org319c98d2013-09-10 15:23:50 +0000357 static_cast<unsigned int>(results->frames.size()));
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000358
359 if (results->frames.size() > 0u) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000360 fprintf(output, "RESULT PSNR: %s= [", label.c_str());
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000361 for (iter = results->frames.begin(); iter != results->frames.end() - 1;
362 ++iter) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000363 fprintf(output, "%f,", iter->psnr_value);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000364 }
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000365 fprintf(output, "%f] dB\n", iter->psnr_value);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000366
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000367 fprintf(output, "RESULT SSIM: %s= [", label.c_str());
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000368 for (iter = results->frames.begin(); iter != results->frames.end() - 1;
369 ++iter) {
kjellander@webrtc.orge2df8b72013-11-03 18:34:51 +0000370 fprintf(output, "%f,", iter->ssim_value);
kjellander@webrtc.orgf880f862013-09-10 12:10:01 +0000371 }
kjellander@webrtc.org22c2f052015-01-28 13:52:08 +0000372 fprintf(output, "%f] score\n", iter->ssim_value);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000373 }
374}
375
376} // namespace test
377} // namespace webrtc