• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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).
Log(const char * format,...)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.
HandleCommandLineFlags(webrtc::test::TestConfig * config)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 
CalculateSsimVideoMetrics(webrtc::test::TestConfig * config,webrtc::test::QualityMetricsResult * result)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 
CalculatePsnrVideoMetrics(webrtc::test::TestConfig * config,webrtc::test::QualityMetricsResult * result)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 
PrintConfigurationSummary(const webrtc::test::TestConfig & config)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 
PrintCsvOutput(const webrtc::test::Stats & stats,const webrtc::test::QualityMetricsResult & ssim_result,const webrtc::test::QualityMetricsResult & psnr_result)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 
PrintPythonOutput(const webrtc::test::TestConfig & config,const webrtc::test::Stats & stats,const webrtc::test::QualityMetricsResult & ssim_result,const webrtc::test::QualityMetricsResult & psnr_result)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.
main(int argc,char * argv[])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