blob: c38ad84cdfbaeb9c7041097b69827c32915488b6 [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
vspasova@webrtc.orgf61dc9b2012-08-22 08:12:00 +000011#include "tools/frame_analyzer/video_quality_analysis.h"
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000012
13#include <cassert>
14#include <cstdio>
15#include <cstdlib>
16#include <string>
17
18#define STATS_LINE_LENGTH 32
19
20namespace webrtc {
21namespace test {
22
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000023using std::string;
24
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000025int GetI420FrameSize(int width, int height) {
26 int half_width = (width + 1) >> 1;
27 int half_height = (height + 1) >> 1;
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000028
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000029 int y_plane = width * height; // I420 Y plane.
30 int u_plane = half_width * half_height; // I420 U plane.
31 int v_plane = half_width * half_height; // I420 V plane.
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000032
33 return y_plane + u_plane + v_plane;
34}
35
36int ExtractFrameSequenceNumber(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000037 size_t space_position = line.find(' ');
38 if (space_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000039 return -1;
40 }
41 std::string frame = line.substr(0, space_position);
42
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000043 size_t underscore_position = frame.find('_');
44 if (underscore_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000045 return -1;
46 }
47 std::string frame_number = frame.substr(underscore_position + 1);
48
49 return strtol(frame_number.c_str(), NULL, 10);
50}
51
52int ExtractDecodedFrameNumber(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000053 size_t space_position = line.find(' ');
54 if (space_position == string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000055 return -1;
56 }
57 std::string decoded_number = line.substr(space_position + 1);
58
59 return strtol(decoded_number.c_str(), NULL, 10);
60}
61
62bool IsThereBarcodeError(std::string line) {
kjellander@webrtc.orgb2d74972013-01-26 16:36:40 +000063 size_t barcode_error_position = line.find("Barcode error");
64 if (barcode_error_position != string::npos) {
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000065 return true;
66 }
67 return false;
68}
69
70bool GetNextStatsLine(FILE* stats_file, char* line) {
71 int chars = 0;
72 char buf = 0;
73
74 while (buf != '\n') {
75 size_t chars_read = fread(&buf, 1, 1, stats_file);
76 if (chars_read != 1 || feof(stats_file)) {
77 return false;
78 }
79 line[chars] = buf;
80 ++chars;
81 }
82 line[chars-1] = '\0'; // Strip the trailing \n and put end of string.
83 return true;
84}
85
86bool GetNextI420Frame(FILE* input_file, int width, int height,
87 uint8* result_frame) {
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000088 int frame_size = GetI420FrameSize(width, height);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000089 bool errors = false;
90
91 size_t bytes_read = fread(result_frame, 1, frame_size, input_file);
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +000092 if (bytes_read != static_cast<size_t>(frame_size)) {
93 // If end-of-file is reached, don't print an error.
94 if (feof(input_file)) {
95 return false;
96 }
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +000097 fprintf(stdout, "Error while reading frame from file\n");
98 errors = true;
99 }
100 return !errors;
101}
102
103bool ExtractFrameFromI420(const char* i420_file_name, int width, int height,
104 int frame_number, uint8* result_frame) {
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +0000105 int frame_size = GetI420FrameSize(width, height);
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000106 int offset = frame_number * frame_size; // Calculate offset for the frame.
107 bool errors = false;
108
109 FILE* input_file = fopen(i420_file_name, "rb");
110 if (input_file == NULL) {
111 fprintf(stderr, "Couldn't open input file for reading: %s\n",
112 i420_file_name);
113 return false;
114 }
115
116 // Change stream pointer to new offset.
117 fseek(input_file, offset, SEEK_SET);
118
119 size_t bytes_read = fread(result_frame, 1, frame_size, input_file);
vspasova@webrtc.orgac410e22012-08-27 14:57:19 +0000120 if (bytes_read != static_cast<size_t>(frame_size) &&
vspasova@webrtc.orgfd800702012-08-16 14:07:02 +0000121 ferror(input_file)) {
122 fprintf(stdout, "Error while reading frame no %d from file %s\n",
123 frame_number, i420_file_name);
124 errors = true;
125 }
126 fclose(input_file);
127 return !errors;
128}
129
130double CalculateMetrics(VideoAnalysisMetricsType video_metrics_type,
131 const uint8* ref_frame, const uint8* test_frame,
132 int width, int height) {
133 if (!ref_frame || !test_frame)
134 return -1;
135 else if (height < 0 || width < 0)
136 return -1;
137 int half_width = (width + 1) >> 1;
138 int half_height = (height + 1) >> 1;
139 const uint8* src_y_a = ref_frame;
140 const uint8* src_u_a = src_y_a + width * height;
141 const uint8* src_v_a = src_u_a + half_width * half_height;
142 const uint8* src_y_b = test_frame;
143 const uint8* src_u_b = src_y_b + width * height;
144 const uint8* src_v_b = src_u_b + half_width * half_height;
145
146 int stride_y = width;
147 int stride_uv = half_width;
148
149 double result = 0.0;
150
151 switch (video_metrics_type) {
152 case kPSNR:
153 // In the following: stride is determined by width.
154 result = libyuv::I420Psnr(src_y_a, width, src_u_a, half_width,
155 src_v_a, half_width, src_y_b, width,
156 src_u_b, half_width, src_v_b, half_width,
157 width, height);
158 // LibYuv sets the max psnr value to 128, we restrict it to 48.
159 // In case of 0 mse in one frame, 128 can skew the results significantly.
160 result = (result > 48.0) ? 48.0 : result;
161 break;
162 case kSSIM:
163 result = libyuv::I420Ssim(src_y_a, stride_y, src_u_a, stride_uv,
164 src_v_a, stride_uv, src_y_b, stride_y,
165 src_u_b, stride_uv, src_v_b, stride_uv,
166 width, height);
167 break;
168 default:
169 assert(false);
170 }
171
172 return result;
173}
174
175void RunAnalysis(const char* reference_file_name, const char* test_file_name,
176 const char* stats_file_name, int width, int height,
177 ResultsContainer* results) {
178 int size = GetI420FrameSize(width, height);
179 FILE* stats_file = fopen(stats_file_name, "r");
180
181 // String buffer for the lines in the stats file.
182 char line[STATS_LINE_LENGTH];
183
184 // Allocate buffers for test and reference frames.
185 uint8* test_frame = new uint8[size];
186 uint8* reference_frame = new uint8[size];
187 int previous_frame_number = -1;
188
189 // While there are entries in the stats file.
190 while (GetNextStatsLine(stats_file, line)) {
191 int extracted_test_frame = ExtractFrameSequenceNumber(line);
192 int decoded_frame_number = ExtractDecodedFrameNumber(line);
193
194 // If there was problem decoding the barcode in this frame or the frame has
195 // been duplicated, continue.
196 if (IsThereBarcodeError(line) ||
197 decoded_frame_number == previous_frame_number) {
198 continue;
199 }
200
201 assert(extracted_test_frame != -1);
202 assert(decoded_frame_number != -1);
203
204 ExtractFrameFromI420(test_file_name, width, height, extracted_test_frame,
205 test_frame);
206 ExtractFrameFromI420(reference_file_name, width, height,
207 decoded_frame_number, reference_frame);
208
209 // Calculate the PSNR and SSIM.
210 double result_psnr = CalculateMetrics(kPSNR, reference_frame, test_frame,
211 width, height);
212 double result_ssim = CalculateMetrics(kSSIM, reference_frame, test_frame,
213 width, height);
214
215 previous_frame_number = decoded_frame_number;
216
217 // Fill in the result struct.
218 AnalysisResult result;
219 result.frame_number = decoded_frame_number;
220 result.psnr_value = result_psnr;
221 result.ssim_value = result_ssim;
222
223 results->frames.push_back(result);
224 }
225
226 // Cleanup.
227 fclose(stats_file);
228 delete[] test_frame;
229 delete[] reference_frame;
230}
231
232void PrintMaxRepeatedAndSkippedFrames(const char* stats_file_name) {
233 FILE* stats_file = fopen(stats_file_name, "r");
234 char line[STATS_LINE_LENGTH];
235
236 int repeated_frames = 1;
237 int max_repeated_frames = 1;
238 int max_skipped_frames = 1;
239 int previous_frame_number = -1;
240
241 while (GetNextStatsLine(stats_file, line)) {
242 int decoded_frame_number = ExtractDecodedFrameNumber(line);
243
244 if (decoded_frame_number == -1) {
245 continue;
246 }
247
248 // Calculate how many frames a cluster of repeated frames contains.
249 if (decoded_frame_number == previous_frame_number) {
250 ++repeated_frames;
251 if (repeated_frames > max_repeated_frames) {
252 max_repeated_frames = repeated_frames;
253 }
254 } else {
255 repeated_frames = 1;
256 }
257
258 // Calculate how much frames have been skipped.
259 if (decoded_frame_number != 0 && previous_frame_number != -1) {
260 int skipped_frames = decoded_frame_number - previous_frame_number - 1;
261 if (skipped_frames > max_skipped_frames) {
262 max_skipped_frames = skipped_frames;
263 }
264 }
265 previous_frame_number = decoded_frame_number;
266 }
267 fprintf(stdout, "Max_repeated:%d Max_skipped:%d\n", max_repeated_frames,
268 max_skipped_frames);
269}
270
271void PrintAnalysisResults(ResultsContainer* results) {
272 std::vector<AnalysisResult>::iterator iter;
273 int frames_counter = 0;
274
275 fprintf(stdout, "BSTATS\n");
276 for (iter = results->frames.begin(); iter != results->frames.end(); ++iter) {
277 ++frames_counter;
278 fprintf(stdout, "%f %f;", iter->psnr_value, iter->ssim_value);
279 }
280 fprintf(stdout, "ESTATS\n");
281 if (frames_counter > 0) {
282 fprintf(stdout, "Unique_frames_count:%d\n", frames_counter);
283 } else {
284 fprintf(stdout, "Unique_frames_count:undef\n");
285 }
286}
287
288} // namespace test
289} // namespace webrtc