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/tools/frame_analyzer/video_quality_analysis.h" 12 13 #include <assert.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 17 #include <string> 18 19 #define STATS_LINE_LENGTH 32 20 #define Y4M_FILE_HEADER_MAX_SIZE 200 21 #define Y4M_FRAME_DELIMITER "FRAME" 22 #define Y4M_FRAME_HEADER_SIZE 6 23 24 namespace webrtc { 25 namespace test { 26 27 using std::string; 28 29 int GetI420FrameSize(int width, int height) { 30 int half_width = (width + 1) >> 1; 31 int half_height = (height + 1) >> 1; 32 33 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. 36 37 return y_plane + u_plane + v_plane; 38 } 39 40 int ExtractFrameSequenceNumber(std::string line) { 41 size_t space_position = line.find(' '); 42 if (space_position == string::npos) { 43 return -1; 44 } 45 std::string frame = line.substr(0, space_position); 46 47 size_t underscore_position = frame.find('_'); 48 if (underscore_position == string::npos) { 49 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 56 int ExtractDecodedFrameNumber(std::string line) { 57 size_t space_position = line.find(' '); 58 if (space_position == string::npos) { 59 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 66 bool IsThereBarcodeError(std::string line) { 67 size_t barcode_error_position = line.find("Barcode error"); 68 if (barcode_error_position != string::npos) { 69 return true; 70 } 71 return false; 72 } 73 74 bool 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 90 bool ExtractFrameFromYuvFile(const char* i420_file_name, int width, int height, 91 int frame_number, uint8* result_frame) { 92 int frame_size = GetI420FrameSize(width, height); 93 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); 107 if (bytes_read != static_cast<size_t>(frame_size) && 108 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 117 bool ExtractFrameFromY4mFile(const char* y4m_file_name, int width, int height, 118 int frame_number, uint8* result_frame) { 119 int frame_size = GetI420FrameSize(width, height); 120 int frame_offset = frame_number * frame_size; 121 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 169 double 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 214 void RunAnalysis(const char* reference_file_name, const char* test_file_name, 215 const char* stats_file_name, int width, int height, 216 ResultsContainer* results) { 217 // 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 223 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 249 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 } 258 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 282 void PrintMaxRepeatedAndSkippedFrames(const std::string& label, 283 const std::string& stats_file_name) { 284 PrintMaxRepeatedAndSkippedFrames(stdout, label, stats_file_name); 285 } 286 287 void PrintMaxRepeatedAndSkippedFrames(FILE* output, const std::string& label, 288 const std::string& stats_file_name) { 289 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 } 295 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 } 328 fprintf(output, "RESULT Max_repeated: %s= %d\n", label.c_str(), 329 max_repeated_frames); 330 fprintf(output, "RESULT Max_skipped: %s= %d\n", label.c_str(), 331 max_skipped_frames); 332 fclose(stats_file); 333 } 334 335 void PrintAnalysisResults(const std::string& label, ResultsContainer* results) { 336 PrintAnalysisResults(stdout, label, results); 337 } 338 339 void PrintAnalysisResults(FILE* output, const std::string& label, 340 ResultsContainer* results) { 341 std::vector<AnalysisResult>::iterator iter; 342 343 fprintf(output, "RESULT Unique_frames_count: %s= %u\n", label.c_str(), 344 static_cast<unsigned int>(results->frames.size())); 345 346 if (results->frames.size() > 0u) { 347 fprintf(output, "RESULT PSNR: %s= [", label.c_str()); 348 for (iter = results->frames.begin(); iter != results->frames.end() - 1; 349 ++iter) { 350 fprintf(output, "%f,", iter->psnr_value); 351 } 352 fprintf(output, "%f] dB\n", iter->psnr_value); 353 354 fprintf(output, "RESULT SSIM: %s= [", label.c_str()); 355 for (iter = results->frames.begin(); iter != results->frames.end() - 1; 356 ++iter) { 357 fprintf(output, "%f,", iter->ssim_value); 358 } 359 fprintf(output, "%f]\n", iter->ssim_value); 360 } 361 } 362 363 } // namespace test 364 } // namespace webrtc 365