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 24namespace webrtc { 25namespace test { 26 27using std::string; 28 29ResultsContainer::ResultsContainer() {} 30ResultsContainer::~ResultsContainer() {} 31 32int GetI420FrameSize(int width, int height) { 33 int half_width = (width + 1) >> 1; 34 int half_height = (height + 1) >> 1; 35 36 int y_plane = width * height; // I420 Y plane. 37 int u_plane = half_width * half_height; // I420 U plane. 38 int v_plane = half_width * half_height; // I420 V plane. 39 40 return y_plane + u_plane + v_plane; 41} 42 43int ExtractFrameSequenceNumber(std::string line) { 44 size_t space_position = line.find(' '); 45 if (space_position == string::npos) { 46 return -1; 47 } 48 std::string frame = line.substr(0, space_position); 49 50 size_t underscore_position = frame.find('_'); 51 if (underscore_position == string::npos) { 52 return -1; 53 } 54 std::string frame_number = frame.substr(underscore_position + 1); 55 56 return strtol(frame_number.c_str(), NULL, 10); 57} 58 59int ExtractDecodedFrameNumber(std::string line) { 60 size_t space_position = line.find(' '); 61 if (space_position == string::npos) { 62 return -1; 63 } 64 std::string decoded_number = line.substr(space_position + 1); 65 66 return strtol(decoded_number.c_str(), NULL, 10); 67} 68 69bool IsThereBarcodeError(std::string line) { 70 size_t barcode_error_position = line.find("Barcode error"); 71 if (barcode_error_position != string::npos) { 72 return true; 73 } 74 return false; 75} 76 77bool GetNextStatsLine(FILE* stats_file, char* line) { 78 int chars = 0; 79 char buf = 0; 80 81 while (buf != '\n') { 82 size_t chars_read = fread(&buf, 1, 1, stats_file); 83 if (chars_read != 1 || feof(stats_file)) { 84 return false; 85 } 86 line[chars] = buf; 87 ++chars; 88 } 89 line[chars-1] = '\0'; // Strip the trailing \n and put end of string. 90 return true; 91} 92 93bool ExtractFrameFromYuvFile(const char* i420_file_name, 94 int width, 95 int height, 96 int frame_number, 97 uint8_t* result_frame) { 98 int frame_size = GetI420FrameSize(width, height); 99 int offset = frame_number * frame_size; // Calculate offset for the frame. 100 bool errors = false; 101 102 FILE* input_file = fopen(i420_file_name, "rb"); 103 if (input_file == NULL) { 104 fprintf(stderr, "Couldn't open input file for reading: %s\n", 105 i420_file_name); 106 return false; 107 } 108 109 // Change stream pointer to new offset. 110 fseek(input_file, offset, SEEK_SET); 111 112 size_t bytes_read = fread(result_frame, 1, frame_size, input_file); 113 if (bytes_read != static_cast<size_t>(frame_size) && 114 ferror(input_file)) { 115 fprintf(stdout, "Error while reading frame no %d from file %s\n", 116 frame_number, i420_file_name); 117 errors = true; 118 } 119 fclose(input_file); 120 return !errors; 121} 122 123bool ExtractFrameFromY4mFile(const char* y4m_file_name, 124 int width, 125 int height, 126 int frame_number, 127 uint8_t* result_frame) { 128 int frame_size = GetI420FrameSize(width, height); 129 int frame_offset = frame_number * frame_size; 130 bool errors = false; 131 132 FILE* input_file = fopen(y4m_file_name, "rb"); 133 if (input_file == NULL) { 134 fprintf(stderr, "Couldn't open input file for reading: %s\n", 135 y4m_file_name); 136 return false; 137 } 138 139 // YUV4MPEG2, a.k.a. Y4M File format has a file header and a frame header. The 140 // file header has the aspect: "YUV4MPEG2 C420 W640 H360 Ip F30:1 A1:1". 141 // Skip the header if this is the first frame of the file. 142 if (frame_number == 0) { 143 char frame_header[Y4M_FILE_HEADER_MAX_SIZE]; 144 size_t bytes_read = 145 fread(frame_header, 1, Y4M_FILE_HEADER_MAX_SIZE, input_file); 146 if (bytes_read != static_cast<size_t>(frame_size) && ferror(input_file)) { 147 fprintf(stdout, "Error while reading first frame from file %s\n", 148 y4m_file_name); 149 fclose(input_file); 150 return false; 151 } 152 std::string header_contents(frame_header); 153 std::size_t found = header_contents.find(Y4M_FRAME_DELIMITER); 154 if (found == std::string::npos) { 155 fprintf(stdout, "Corrupted Y4M header, could not find \"FRAME\" in %s\n", 156 header_contents.c_str()); 157 fclose(input_file); 158 return false; 159 } 160 frame_offset = static_cast<int>(found); 161 } 162 163 // Change stream pointer to new offset, skipping the frame header as well. 164 fseek(input_file, frame_offset + Y4M_FRAME_HEADER_SIZE, SEEK_SET); 165 166 size_t bytes_read = fread(result_frame, 1, frame_size, input_file); 167 if (bytes_read != static_cast<size_t>(frame_size) && 168 ferror(input_file)) { 169 fprintf(stdout, "Error while reading frame no %d from file %s\n", 170 frame_number, y4m_file_name); 171 errors = true; 172 } 173 174 fclose(input_file); 175 return !errors; 176} 177 178double CalculateMetrics(VideoAnalysisMetricsType video_metrics_type, 179 const uint8_t* ref_frame, 180 const uint8_t* test_frame, 181 int width, 182 int height) { 183 if (!ref_frame || !test_frame) 184 return -1; 185 else if (height < 0 || width < 0) 186 return -1; 187 int half_width = (width + 1) >> 1; 188 int half_height = (height + 1) >> 1; 189 const uint8_t* src_y_a = ref_frame; 190 const uint8_t* src_u_a = src_y_a + width * height; 191 const uint8_t* src_v_a = src_u_a + half_width * half_height; 192 const uint8_t* src_y_b = test_frame; 193 const uint8_t* src_u_b = src_y_b + width * height; 194 const uint8_t* src_v_b = src_u_b + half_width * half_height; 195 196 int stride_y = width; 197 int stride_uv = half_width; 198 199 double result = 0.0; 200 201 switch (video_metrics_type) { 202 case kPSNR: 203 // In the following: stride is determined by width. 204 result = libyuv::I420Psnr(src_y_a, width, src_u_a, half_width, 205 src_v_a, half_width, src_y_b, width, 206 src_u_b, half_width, src_v_b, half_width, 207 width, height); 208 // LibYuv sets the max psnr value to 128, we restrict it to 48. 209 // In case of 0 mse in one frame, 128 can skew the results significantly. 210 result = (result > 48.0) ? 48.0 : result; 211 break; 212 case kSSIM: 213 result = libyuv::I420Ssim(src_y_a, stride_y, src_u_a, stride_uv, 214 src_v_a, stride_uv, src_y_b, stride_y, 215 src_u_b, stride_uv, src_v_b, stride_uv, 216 width, height); 217 break; 218 default: 219 assert(false); 220 } 221 222 return result; 223} 224 225void RunAnalysis(const char* reference_file_name, const char* test_file_name, 226 const char* stats_file_name, int width, int height, 227 ResultsContainer* results) { 228 // Check if the reference_file_name ends with "y4m". 229 bool y4m_mode = false; 230 if (std::string(reference_file_name).find("y4m") != std::string::npos) { 231 y4m_mode = true; 232 } 233 234 int size = GetI420FrameSize(width, height); 235 FILE* stats_file = fopen(stats_file_name, "r"); 236 237 // String buffer for the lines in the stats file. 238 char line[STATS_LINE_LENGTH]; 239 240 // Allocate buffers for test and reference frames. 241 uint8_t* test_frame = new uint8_t[size]; 242 uint8_t* reference_frame = new uint8_t[size]; 243 int previous_frame_number = -1; 244 245 // While there are entries in the stats file. 246 while (GetNextStatsLine(stats_file, line)) { 247 int extracted_test_frame = ExtractFrameSequenceNumber(line); 248 int decoded_frame_number = ExtractDecodedFrameNumber(line); 249 250 // If there was problem decoding the barcode in this frame or the frame has 251 // been duplicated, continue. 252 if (IsThereBarcodeError(line) || 253 decoded_frame_number == previous_frame_number) { 254 continue; 255 } 256 257 assert(extracted_test_frame != -1); 258 assert(decoded_frame_number != -1); 259 260 ExtractFrameFromYuvFile(test_file_name, width, height, extracted_test_frame, 261 test_frame); 262 if (y4m_mode) { 263 ExtractFrameFromY4mFile(reference_file_name, width, height, 264 decoded_frame_number, reference_frame); 265 } else { 266 ExtractFrameFromYuvFile(reference_file_name, width, height, 267 decoded_frame_number, reference_frame); 268 } 269 270 // Calculate the PSNR and SSIM. 271 double result_psnr = CalculateMetrics(kPSNR, reference_frame, test_frame, 272 width, height); 273 double result_ssim = CalculateMetrics(kSSIM, reference_frame, test_frame, 274 width, height); 275 276 previous_frame_number = decoded_frame_number; 277 278 // Fill in the result struct. 279 AnalysisResult result; 280 result.frame_number = decoded_frame_number; 281 result.psnr_value = result_psnr; 282 result.ssim_value = result_ssim; 283 284 results->frames.push_back(result); 285 } 286 287 // Cleanup. 288 fclose(stats_file); 289 delete[] test_frame; 290 delete[] reference_frame; 291} 292 293void PrintMaxRepeatedAndSkippedFrames(const std::string& label, 294 const std::string& stats_file_name) { 295 PrintMaxRepeatedAndSkippedFrames(stdout, label, stats_file_name); 296} 297 298void PrintMaxRepeatedAndSkippedFrames(FILE* output, const std::string& label, 299 const std::string& stats_file_name) { 300 FILE* stats_file = fopen(stats_file_name.c_str(), "r"); 301 if (stats_file == NULL) { 302 fprintf(stderr, "Couldn't open stats file for reading: %s\n", 303 stats_file_name.c_str()); 304 return; 305 } 306 char line[STATS_LINE_LENGTH]; 307 308 int repeated_frames = 1; 309 int max_repeated_frames = 1; 310 int max_skipped_frames = 1; 311 int previous_frame_number = -1; 312 313 while (GetNextStatsLine(stats_file, line)) { 314 int decoded_frame_number = ExtractDecodedFrameNumber(line); 315 316 if (decoded_frame_number == -1) { 317 continue; 318 } 319 320 // Calculate how many frames a cluster of repeated frames contains. 321 if (decoded_frame_number == previous_frame_number) { 322 ++repeated_frames; 323 if (repeated_frames > max_repeated_frames) { 324 max_repeated_frames = repeated_frames; 325 } 326 } else { 327 repeated_frames = 1; 328 } 329 330 // Calculate how much frames have been skipped. 331 if (decoded_frame_number != 0 && previous_frame_number != -1) { 332 int skipped_frames = decoded_frame_number - previous_frame_number - 1; 333 if (skipped_frames > max_skipped_frames) { 334 max_skipped_frames = skipped_frames; 335 } 336 } 337 previous_frame_number = decoded_frame_number; 338 } 339 fprintf(output, "RESULT Max_repeated: %s= %d\n", label.c_str(), 340 max_repeated_frames); 341 fprintf(output, "RESULT Max_skipped: %s= %d\n", label.c_str(), 342 max_skipped_frames); 343 fclose(stats_file); 344} 345 346void PrintAnalysisResults(const std::string& label, ResultsContainer* results) { 347 PrintAnalysisResults(stdout, label, results); 348} 349 350void PrintAnalysisResults(FILE* output, const std::string& label, 351 ResultsContainer* results) { 352 std::vector<AnalysisResult>::iterator iter; 353 354 fprintf(output, "RESULT Unique_frames_count: %s= %u\n", label.c_str(), 355 static_cast<unsigned int>(results->frames.size())); 356 357 if (results->frames.size() > 0u) { 358 fprintf(output, "RESULT PSNR: %s= [", label.c_str()); 359 for (iter = results->frames.begin(); iter != results->frames.end() - 1; 360 ++iter) { 361 fprintf(output, "%f,", iter->psnr_value); 362 } 363 fprintf(output, "%f] dB\n", iter->psnr_value); 364 365 fprintf(output, "RESULT SSIM: %s= [", label.c_str()); 366 for (iter = results->frames.begin(); iter != results->frames.end() - 1; 367 ++iter) { 368 fprintf(output, "%f,", iter->ssim_value); 369 } 370 fprintf(output, "%f] score\n", iter->ssim_value); 371 } 372} 373 374} // namespace test 375} // namespace webrtc 376