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 <assert.h> 12 #include <stdio.h> 13 #include <time.h> 14 15 #include <stdarg.h> 16 #include <sys/stat.h> // To check for directory existence. 17 18 #ifndef S_ISDIR // Not defined in stat.h on Windows. 19 #define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) 20 #endif 21 22 #include "gflags/gflags.h" 23 #include "webrtc/base/format_macros.h" 24 #include "webrtc/common_types.h" 25 #include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h" 26 #include "webrtc/modules/video_coding/codecs/test/stats.h" 27 #include "webrtc/modules/video_coding/codecs/test/videoprocessor.h" 28 #include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h" 29 #include "webrtc/modules/video_coding/include/video_coding.h" 30 #include "webrtc/system_wrappers/include/trace.h" 31 #include "webrtc/test/testsupport/frame_reader.h" 32 #include "webrtc/test/testsupport/frame_writer.h" 33 #include "webrtc/test/testsupport/metrics/video_metrics.h" 34 #include "webrtc/test/testsupport/packet_reader.h" 35 36 DEFINE_string(test_name, "Quality test", "The name of the test to run. "); 37 DEFINE_string(test_description, 38 "", 39 "A more detailed description about what " 40 "the current test is about."); 41 DEFINE_string(input_filename, 42 "", 43 "Input file. " 44 "The source video file to be encoded and decoded. Must be in " 45 ".yuv format"); 46 DEFINE_int32(width, -1, "Width in pixels of the frames in the input file."); 47 DEFINE_int32(height, -1, "Height in pixels of the frames in the input file."); 48 DEFINE_int32(framerate, 49 30, 50 "Frame rate of the input file, in FPS " 51 "(frames-per-second). "); 52 DEFINE_string(output_dir, 53 ".", 54 "Output directory. " 55 "The directory where the output file will be put. Must already " 56 "exist."); 57 DEFINE_bool(use_single_core, 58 false, 59 "Force using a single core. If set to " 60 "true, only one core will be used for processing. Using a single " 61 "core is necessary to get a deterministic behavior for the" 62 "encoded frames - using multiple cores will produce different " 63 "encoded frames since multiple cores are competing to consume the " 64 "byte budget for each frame in parallel. If set to false, " 65 "the maximum detected number of cores will be used. "); 66 DEFINE_bool(disable_fixed_random_seed, 67 false, 68 "Set this flag to disable the" 69 "usage of a fixed random seed for the random generator used " 70 "for packet loss. Disabling this will cause consecutive runs " 71 "loose packets at different locations, which is bad for " 72 "reproducibility."); 73 DEFINE_string(output_filename, 74 "", 75 "Output file. " 76 "The name of the output video file resulting of the processing " 77 "of the source file. By default this is the same name as the " 78 "input file with '_out' appended before the extension."); 79 DEFINE_int32(bitrate, 500, "Bit rate in kilobits/second."); 80 DEFINE_int32(keyframe_interval, 81 0, 82 "Forces a keyframe every Nth frame. " 83 "0 means the encoder decides when to insert keyframes. Note that " 84 "the encoder may create a keyframe in other locations in addition " 85 "to the interval that is set using this parameter."); 86 DEFINE_int32(temporal_layers, 87 0, 88 "The number of temporal layers to use " 89 "(VP8 specific codec setting). Must be 0-4."); 90 DEFINE_int32(packet_size, 91 1500, 92 "Simulated network packet size in bytes (MTU). " 93 "Used for packet loss simulation."); 94 DEFINE_int32(max_payload_size, 95 1440, 96 "Max payload size in bytes for the " 97 "encoder."); 98 DEFINE_string(packet_loss_mode, 99 "uniform", 100 "Packet loss mode. Two different " 101 "packet loss models are supported: uniform or burst. This " 102 "setting has no effect unless packet_loss_rate is >0. "); 103 DEFINE_double(packet_loss_probability, 104 0.0, 105 "Packet loss probability. A value " 106 "between 0.0 and 1.0 that defines the probability of a packet " 107 "being lost. 0.1 means 10% and so on."); 108 DEFINE_int32(packet_loss_burst_length, 109 1, 110 "Packet loss burst length. Defines " 111 "how many packets will be lost in a burst when a packet has been " 112 "decided to be lost. Must be >=1."); 113 DEFINE_bool(csv, 114 false, 115 "CSV output. Enabling this will output all frame " 116 "statistics at the end of execution. Recommended to run combined " 117 "with --noverbose to avoid mixing output."); 118 DEFINE_bool(python, 119 false, 120 "Python output. Enabling this will output all frame " 121 "statistics as a Python script at the end of execution. " 122 "Recommended to run combine with --noverbose to avoid mixing " 123 "output."); 124 DEFINE_bool(verbose, 125 true, 126 "Verbose mode. Prints a lot of debugging info. " 127 "Suitable for tracking progress but not for capturing output. " 128 "Disable with --noverbose flag."); 129 130 // Custom log method that only prints if the verbose flag is given. 131 // Supports all the standard printf parameters and formatting (just forwarded). 132 int Log(const char* format, ...) { 133 int result = 0; 134 if (FLAGS_verbose) { 135 va_list args; 136 va_start(args, format); 137 result = vprintf(format, args); 138 va_end(args); 139 } 140 return result; 141 } 142 143 // Validates the arguments given as command line flags and fills in the 144 // TestConfig struct with all configurations needed for video processing. 145 // Returns 0 if everything is OK, otherwise an exit code. 146 int HandleCommandLineFlags(webrtc::test::TestConfig* config) { 147 // Validate the mandatory flags: 148 if (FLAGS_input_filename.empty() || FLAGS_width == -1 || FLAGS_height == -1) { 149 printf("%s\n", google::ProgramUsage()); 150 return 1; 151 } 152 config->name = FLAGS_test_name; 153 config->description = FLAGS_test_description; 154 155 // Verify the input file exists and is readable. 156 FILE* test_file; 157 test_file = fopen(FLAGS_input_filename.c_str(), "rb"); 158 if (test_file == NULL) { 159 fprintf(stderr, "Cannot read the specified input file: %s\n", 160 FLAGS_input_filename.c_str()); 161 return 2; 162 } 163 fclose(test_file); 164 config->input_filename = FLAGS_input_filename; 165 166 // Verify the output dir exists. 167 struct stat dir_info; 168 if (!(stat(FLAGS_output_dir.c_str(), &dir_info) == 0 && 169 S_ISDIR(dir_info.st_mode))) { 170 fprintf(stderr, "Cannot find output directory: %s\n", 171 FLAGS_output_dir.c_str()); 172 return 3; 173 } 174 config->output_dir = FLAGS_output_dir; 175 176 // Manufacture an output filename if none was given. 177 if (FLAGS_output_filename.empty()) { 178 // Cut out the filename without extension from the given input file 179 // (which may include a path) 180 int startIndex = FLAGS_input_filename.find_last_of("/") + 1; 181 if (startIndex == 0) { 182 startIndex = 0; 183 } 184 FLAGS_output_filename = 185 FLAGS_input_filename.substr( 186 startIndex, FLAGS_input_filename.find_last_of(".") - startIndex) + 187 "_out.yuv"; 188 } 189 190 // Verify output file can be written. 191 if (FLAGS_output_dir == ".") { 192 config->output_filename = FLAGS_output_filename; 193 } else { 194 config->output_filename = FLAGS_output_dir + "/" + FLAGS_output_filename; 195 } 196 test_file = fopen(config->output_filename.c_str(), "wb"); 197 if (test_file == NULL) { 198 fprintf(stderr, "Cannot write output file: %s\n", 199 config->output_filename.c_str()); 200 return 4; 201 } 202 fclose(test_file); 203 204 // Check single core flag. 205 config->use_single_core = FLAGS_use_single_core; 206 207 // Get codec specific configuration. 208 webrtc::VideoCodingModule::Codec(webrtc::kVideoCodecVP8, 209 config->codec_settings); 210 211 // Check the temporal layers. 212 if (FLAGS_temporal_layers < 0 || 213 FLAGS_temporal_layers > webrtc::kMaxTemporalStreams) { 214 fprintf(stderr, "Temporal layers number must be 0-4, was: %d\n", 215 FLAGS_temporal_layers); 216 return 13; 217 } 218 config->codec_settings->codecSpecific.VP8.numberOfTemporalLayers = 219 FLAGS_temporal_layers; 220 221 // Check the bit rate. 222 if (FLAGS_bitrate <= 0) { 223 fprintf(stderr, "Bit rate must be >0 kbps, was: %d\n", FLAGS_bitrate); 224 return 5; 225 } 226 config->codec_settings->startBitrate = FLAGS_bitrate; 227 228 // Check the keyframe interval. 229 if (FLAGS_keyframe_interval < 0) { 230 fprintf(stderr, "Keyframe interval must be >=0, was: %d\n", 231 FLAGS_keyframe_interval); 232 return 6; 233 } 234 config->keyframe_interval = FLAGS_keyframe_interval; 235 236 // Check packet size and max payload size. 237 if (FLAGS_packet_size <= 0) { 238 fprintf(stderr, "Packet size must be >0 bytes, was: %d\n", 239 FLAGS_packet_size); 240 return 7; 241 } 242 config->networking_config.packet_size_in_bytes = 243 static_cast<size_t>(FLAGS_packet_size); 244 245 if (FLAGS_max_payload_size <= 0) { 246 fprintf(stderr, "Max payload size must be >0 bytes, was: %d\n", 247 FLAGS_max_payload_size); 248 return 8; 249 } 250 config->networking_config.max_payload_size_in_bytes = 251 static_cast<size_t>(FLAGS_max_payload_size); 252 253 // Check the width and height 254 if (FLAGS_width <= 0 || FLAGS_height <= 0) { 255 fprintf(stderr, "Width and height must be >0."); 256 return 9; 257 } 258 config->codec_settings->width = FLAGS_width; 259 config->codec_settings->height = FLAGS_height; 260 config->codec_settings->maxFramerate = FLAGS_framerate; 261 262 // Calculate the size of each frame to read (according to YUV spec). 263 config->frame_length_in_bytes = 264 3 * config->codec_settings->width * config->codec_settings->height / 2; 265 266 // Check packet loss settings 267 if (FLAGS_packet_loss_mode != "uniform" && 268 FLAGS_packet_loss_mode != "burst") { 269 fprintf(stderr, 270 "Unsupported packet loss mode, must be 'uniform' or " 271 "'burst'\n."); 272 return 10; 273 } 274 config->networking_config.packet_loss_mode = webrtc::test::kUniform; 275 if (FLAGS_packet_loss_mode == "burst") { 276 config->networking_config.packet_loss_mode = webrtc::test::kBurst; 277 } 278 279 if (FLAGS_packet_loss_probability < 0.0 || 280 FLAGS_packet_loss_probability > 1.0) { 281 fprintf(stderr, 282 "Invalid packet loss probability. Must be 0.0 - 1.0, " 283 "was: %f\n", 284 FLAGS_packet_loss_probability); 285 return 11; 286 } 287 config->networking_config.packet_loss_probability = 288 FLAGS_packet_loss_probability; 289 290 if (FLAGS_packet_loss_burst_length < 1) { 291 fprintf(stderr, 292 "Invalid packet loss burst length, must be >=1, " 293 "was: %d\n", 294 FLAGS_packet_loss_burst_length); 295 return 12; 296 } 297 config->networking_config.packet_loss_burst_length = 298 FLAGS_packet_loss_burst_length; 299 config->verbose = FLAGS_verbose; 300 return 0; 301 } 302 303 void CalculateSsimVideoMetrics(webrtc::test::TestConfig* config, 304 webrtc::test::QualityMetricsResult* result) { 305 Log("Calculating SSIM...\n"); 306 I420SSIMFromFiles( 307 config->input_filename.c_str(), config->output_filename.c_str(), 308 config->codec_settings->width, config->codec_settings->height, result); 309 Log(" Average: %3.2f\n", result->average); 310 Log(" Min : %3.2f (frame %d)\n", result->min, result->min_frame_number); 311 Log(" Max : %3.2f (frame %d)\n", result->max, result->max_frame_number); 312 } 313 314 void CalculatePsnrVideoMetrics(webrtc::test::TestConfig* config, 315 webrtc::test::QualityMetricsResult* result) { 316 Log("Calculating PSNR...\n"); 317 I420PSNRFromFiles( 318 config->input_filename.c_str(), config->output_filename.c_str(), 319 config->codec_settings->width, config->codec_settings->height, result); 320 Log(" Average: %3.2f\n", result->average); 321 Log(" Min : %3.2f (frame %d)\n", result->min, result->min_frame_number); 322 Log(" Max : %3.2f (frame %d)\n", result->max, result->max_frame_number); 323 } 324 325 void PrintConfigurationSummary(const webrtc::test::TestConfig& config) { 326 Log("Quality test with parameters:\n"); 327 Log(" Test name : %s\n", config.name.c_str()); 328 Log(" Description : %s\n", config.description.c_str()); 329 Log(" Input filename : %s\n", config.input_filename.c_str()); 330 Log(" Output directory : %s\n", config.output_dir.c_str()); 331 Log(" Output filename : %s\n", config.output_filename.c_str()); 332 Log(" Frame length : %" PRIuS " bytes\n", config.frame_length_in_bytes); 333 Log(" Packet size : %" PRIuS " bytes\n", 334 config.networking_config.packet_size_in_bytes); 335 Log(" Max payload size : %" PRIuS " bytes\n", 336 config.networking_config.max_payload_size_in_bytes); 337 Log(" Packet loss:\n"); 338 Log(" Mode : %s\n", 339 PacketLossModeToStr(config.networking_config.packet_loss_mode)); 340 Log(" Probability : %2.1f\n", 341 config.networking_config.packet_loss_probability); 342 Log(" Burst length : %d packets\n", 343 config.networking_config.packet_loss_burst_length); 344 } 345 346 void PrintCsvOutput(const webrtc::test::Stats& stats, 347 const webrtc::test::QualityMetricsResult& ssim_result, 348 const webrtc::test::QualityMetricsResult& psnr_result) { 349 Log( 350 "\nCSV output (recommended to run with --noverbose to skip the " 351 "above output)\n"); 352 printf( 353 "frame_number encoding_successful decoding_successful " 354 "encode_return_code decode_return_code " 355 "encode_time_in_us decode_time_in_us " 356 "bit_rate_in_kbps encoded_frame_length_in_bytes frame_type " 357 "packets_dropped total_packets " 358 "ssim psnr\n"); 359 360 for (unsigned int i = 0; i < stats.stats_.size(); ++i) { 361 const webrtc::test::FrameStatistic& f = stats.stats_[i]; 362 const webrtc::test::FrameResult& ssim = ssim_result.frames[i]; 363 const webrtc::test::FrameResult& psnr = psnr_result.frames[i]; 364 printf("%4d, %d, %d, %2d, %2d, %6d, %6d, %5d, %7" PRIuS 365 ", %d, %2d, %2" PRIuS ", %5.3f, %5.2f\n", 366 f.frame_number, f.encoding_successful, f.decoding_successful, 367 f.encode_return_code, f.decode_return_code, f.encode_time_in_us, 368 f.decode_time_in_us, f.bit_rate_in_kbps, 369 f.encoded_frame_length_in_bytes, f.frame_type, f.packets_dropped, 370 f.total_packets, ssim.value, psnr.value); 371 } 372 } 373 374 void PrintPythonOutput(const webrtc::test::TestConfig& config, 375 const webrtc::test::Stats& stats, 376 const webrtc::test::QualityMetricsResult& ssim_result, 377 const webrtc::test::QualityMetricsResult& psnr_result) { 378 Log( 379 "\nPython output (recommended to run with --noverbose to skip the " 380 "above output)\n"); 381 printf( 382 "test_configuration = [" 383 "{'name': 'name', 'value': '%s'},\n" 384 "{'name': 'description', 'value': '%s'},\n" 385 "{'name': 'test_number', 'value': '%d'},\n" 386 "{'name': 'input_filename', 'value': '%s'},\n" 387 "{'name': 'output_filename', 'value': '%s'},\n" 388 "{'name': 'output_dir', 'value': '%s'},\n" 389 "{'name': 'packet_size_in_bytes', 'value': '%" PRIuS 390 "'},\n" 391 "{'name': 'max_payload_size_in_bytes', 'value': '%" PRIuS 392 "'},\n" 393 "{'name': 'packet_loss_mode', 'value': '%s'},\n" 394 "{'name': 'packet_loss_probability', 'value': '%f'},\n" 395 "{'name': 'packet_loss_burst_length', 'value': '%d'},\n" 396 "{'name': 'exclude_frame_types', 'value': '%s'},\n" 397 "{'name': 'frame_length_in_bytes', 'value': '%" PRIuS 398 "'},\n" 399 "{'name': 'use_single_core', 'value': '%s'},\n" 400 "{'name': 'keyframe_interval;', 'value': '%d'},\n" 401 "{'name': 'video_codec_type', 'value': '%s'},\n" 402 "{'name': 'width', 'value': '%d'},\n" 403 "{'name': 'height', 'value': '%d'},\n" 404 "{'name': 'bit_rate_in_kbps', 'value': '%d'},\n" 405 "]\n", 406 config.name.c_str(), config.description.c_str(), config.test_number, 407 config.input_filename.c_str(), config.output_filename.c_str(), 408 config.output_dir.c_str(), config.networking_config.packet_size_in_bytes, 409 config.networking_config.max_payload_size_in_bytes, 410 PacketLossModeToStr(config.networking_config.packet_loss_mode), 411 config.networking_config.packet_loss_probability, 412 config.networking_config.packet_loss_burst_length, 413 ExcludeFrameTypesToStr(config.exclude_frame_types), 414 config.frame_length_in_bytes, config.use_single_core ? "True " : "False", 415 config.keyframe_interval, 416 webrtc::test::VideoCodecTypeToStr(config.codec_settings->codecType), 417 config.codec_settings->width, config.codec_settings->height, 418 config.codec_settings->startBitrate); 419 printf( 420 "frame_data_types = {" 421 "'frame_number': ('number', 'Frame number'),\n" 422 "'encoding_successful': ('boolean', 'Encoding successful?'),\n" 423 "'decoding_successful': ('boolean', 'Decoding successful?'),\n" 424 "'encode_time': ('number', 'Encode time (us)'),\n" 425 "'decode_time': ('number', 'Decode time (us)'),\n" 426 "'encode_return_code': ('number', 'Encode return code'),\n" 427 "'decode_return_code': ('number', 'Decode return code'),\n" 428 "'bit_rate': ('number', 'Bit rate (kbps)'),\n" 429 "'encoded_frame_length': " 430 "('number', 'Encoded frame length (bytes)'),\n" 431 "'frame_type': ('string', 'Frame type'),\n" 432 "'packets_dropped': ('number', 'Packets dropped'),\n" 433 "'total_packets': ('number', 'Total packets'),\n" 434 "'ssim': ('number', 'SSIM'),\n" 435 "'psnr': ('number', 'PSNR (dB)'),\n" 436 "}\n"); 437 printf("frame_data = ["); 438 for (unsigned int i = 0; i < stats.stats_.size(); ++i) { 439 const webrtc::test::FrameStatistic& f = stats.stats_[i]; 440 const webrtc::test::FrameResult& ssim = ssim_result.frames[i]; 441 const webrtc::test::FrameResult& psnr = psnr_result.frames[i]; 442 printf( 443 "{'frame_number': %d, " 444 "'encoding_successful': %s, 'decoding_successful': %s, " 445 "'encode_time': %d, 'decode_time': %d, " 446 "'encode_return_code': %d, 'decode_return_code': %d, " 447 "'bit_rate': %d, 'encoded_frame_length': %" PRIuS 448 ", " 449 "'frame_type': %s, 'packets_dropped': %d, " 450 "'total_packets': %" PRIuS ", 'ssim': %f, 'psnr': %f},\n", 451 f.frame_number, f.encoding_successful ? "True " : "False", 452 f.decoding_successful ? "True " : "False", f.encode_time_in_us, 453 f.decode_time_in_us, f.encode_return_code, f.decode_return_code, 454 f.bit_rate_in_kbps, f.encoded_frame_length_in_bytes, 455 f.frame_type == webrtc::kVideoFrameDelta ? "'Delta'" : "'Other'", 456 f.packets_dropped, f.total_packets, ssim.value, psnr.value); 457 } 458 printf("]\n"); 459 } 460 461 // Runs a quality measurement on the input file supplied to the program. 462 // The input file must be in YUV format. 463 int main(int argc, char* argv[]) { 464 std::string program_name = argv[0]; 465 std::string usage = 466 "Quality test application for video comparisons.\n" 467 "Run " + 468 program_name + 469 " --helpshort for usage.\n" 470 "Example usage:\n" + 471 program_name + 472 " --input_filename=filename.yuv --width=352 --height=288\n"; 473 google::SetUsageMessage(usage); 474 475 google::ParseCommandLineFlags(&argc, &argv, true); 476 477 // Create TestConfig and codec settings struct. 478 webrtc::test::TestConfig config; 479 webrtc::VideoCodec codec_settings; 480 config.codec_settings = &codec_settings; 481 482 int return_code = HandleCommandLineFlags(&config); 483 // Exit if an invalid argument is supplied. 484 if (return_code != 0) { 485 return return_code; 486 } 487 488 PrintConfigurationSummary(config); 489 490 webrtc::VP8Encoder* encoder = webrtc::VP8Encoder::Create(); 491 webrtc::VP8Decoder* decoder = webrtc::VP8Decoder::Create(); 492 webrtc::test::Stats stats; 493 webrtc::test::FrameReaderImpl frame_reader(config.input_filename, 494 config.frame_length_in_bytes); 495 webrtc::test::FrameWriterImpl frame_writer(config.output_filename, 496 config.frame_length_in_bytes); 497 frame_reader.Init(); 498 frame_writer.Init(); 499 webrtc::test::PacketReader packet_reader; 500 501 webrtc::test::PacketManipulatorImpl packet_manipulator( 502 &packet_reader, config.networking_config, config.verbose); 503 // By default the packet manipulator is seeded with a fixed random. 504 // If disabled we must generate a new seed. 505 if (FLAGS_disable_fixed_random_seed) { 506 packet_manipulator.InitializeRandomSeed(time(NULL)); 507 } 508 webrtc::test::VideoProcessor* processor = 509 new webrtc::test::VideoProcessorImpl(encoder, decoder, &frame_reader, 510 &frame_writer, &packet_manipulator, 511 config, &stats); 512 processor->Init(); 513 514 int frame_number = 0; 515 while (processor->ProcessFrame(frame_number)) { 516 if (frame_number % 80 == 0) { 517 Log("\n"); // make the output a bit nicer. 518 } 519 Log("."); 520 frame_number++; 521 } 522 Log("\n"); 523 Log("Processed %d frames\n", frame_number); 524 525 // Release encoder and decoder to make sure they have finished processing. 526 encoder->Release(); 527 decoder->Release(); 528 529 // Verify statistics are correct: 530 assert(frame_number == static_cast<int>(stats.stats_.size())); 531 532 // Close the files before we start using them for SSIM/PSNR calculations. 533 frame_reader.Close(); 534 frame_writer.Close(); 535 536 stats.PrintSummary(); 537 538 webrtc::test::QualityMetricsResult ssim_result; 539 CalculateSsimVideoMetrics(&config, &ssim_result); 540 webrtc::test::QualityMetricsResult psnr_result; 541 CalculatePsnrVideoMetrics(&config, &psnr_result); 542 543 if (FLAGS_csv) { 544 PrintCsvOutput(stats, ssim_result, psnr_result); 545 } 546 if (FLAGS_python) { 547 PrintPythonOutput(config, stats, ssim_result, psnr_result); 548 } 549 delete processor; 550 delete encoder; 551 delete decoder; 552 Log("Quality test finished!"); 553 return 0; 554 } 555