diff --git a/src/tools/OWNERS b/src/tools/OWNERS new file mode 100644 index 0000000000..7b4acbba6c --- /dev/null +++ b/src/tools/OWNERS @@ -0,0 +1,2 @@ +phoglund@webrtc.org +kjellander@webrtc.org \ No newline at end of file diff --git a/src/tools/frame_analyzer/frame_analyzer.cc b/src/tools/frame_analyzer/frame_analyzer.cc index d6c833cc03..2e9154c05d 100644 --- a/src/tools/frame_analyzer/frame_analyzer.cc +++ b/src/tools/frame_analyzer/frame_analyzer.cc @@ -8,10 +8,8 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include #include #include -#include #include #include #include @@ -19,15 +17,38 @@ #include "tools/frame_analyzer/video_quality_analysis.h" #include "tools/simple_command_line_parser.h" -#define STATS_LINE_LENGTH 25 - +/* + * A command line tool running PSNR and SSIM on a reference video and a test + * video. The test video is a record of the reference video which can start at + * an arbitrary point. It is possible that there will be repeated frames or + * skipped frames as well. In order to have a way to compare corresponding + * frames from the two videos, a stats file should be provided. The stats file + * is a text file assumed to be in the format: + * frame_xxxx yyyy + * where xxxx is the frame number in the test video and yyyy is the + * corresponding frame number in the original video. + * The video files should be 1420 YUV videos. + * The tool prints the result to the standard output in the following format: + * BSTATS + * ; ; .... + * ESTATS + * Unique_frames_count: + * Max_repeated: + * Max_skipped + * + * The max value for PSNR is 48.0 (between equal frames), as for SSIM it is 1.0. + * + * Usage: + * frame_analyzer --reference_file= --test_file= + * --stats_file= --width= --height= + */ int main(int argc, char** argv) { std::string program_name = argv[0]; std::string usage = "Compares the output video with the initially sent video." "\nExample usage:\n" + program_name + " --stats_file=stats.txt " "--reference_file=ref.yuv --test_file=test.yuv --width=320 --height=240\n" "Command line flags:\n" - " - width(int): The width of the refence and test files. Default: -1\n" + " - width(int): The width of the reference and test files. Default: -1\n" " - height(int): The height of the reference and test files. " " Default: -1\n" " - stats_file(string): The full name of the file containing the stats" diff --git a/src/tools/frame_analyzer/video_quality_analysis.cc b/src/tools/frame_analyzer/video_quality_analysis.cc index 2d556fab68..8314e91208 100644 --- a/src/tools/frame_analyzer/video_quality_analysis.cc +++ b/src/tools/frame_analyzer/video_quality_analysis.cc @@ -20,13 +20,13 @@ namespace webrtc { namespace test { -unsigned int GetI420FrameSize(unsigned int width, unsigned int height) { - unsigned int half_width = (width + 1) >> 1; - unsigned int half_height = (height + 1) >> 1; +int GetI420FrameSize(int width, int height) { + int half_width = (width + 1) >> 1; + int half_height = (height + 1) >> 1; - unsigned int y_plane = width * height; // I420 Y plane. - unsigned int u_plane = half_width * half_height; // I420 U plane. - unsigned int v_plane = half_width * half_height; // I420 V plane. + int y_plane = width * height; // I420 Y plane. + int u_plane = half_width * half_height; // I420 U plane. + int v_plane = half_width * half_height; // I420 V plane. return y_plane + u_plane + v_plane; } @@ -83,11 +83,15 @@ bool GetNextStatsLine(FILE* stats_file, char* line) { bool GetNextI420Frame(FILE* input_file, int width, int height, uint8* result_frame) { - unsigned int frame_size = GetI420FrameSize(width, height); + int frame_size = GetI420FrameSize(width, height); bool errors = false; size_t bytes_read = fread(result_frame, 1, frame_size, input_file); - if (bytes_read != frame_size) { + if (bytes_read != static_cast(frame_size)) { + // If end-of-file is reached, don't print an error. + if (feof(input_file)) { + return false; + } fprintf(stdout, "Error while reading frame from file\n"); errors = true; } @@ -96,7 +100,7 @@ bool GetNextI420Frame(FILE* input_file, int width, int height, bool ExtractFrameFromI420(const char* i420_file_name, int width, int height, int frame_number, uint8* result_frame) { - unsigned int frame_size = GetI420FrameSize(width, height); + int frame_size = GetI420FrameSize(width, height); int offset = frame_number * frame_size; // Calculate offset for the frame. bool errors = false; @@ -111,7 +115,7 @@ bool ExtractFrameFromI420(const char* i420_file_name, int width, int height, fseek(input_file, offset, SEEK_SET); size_t bytes_read = fread(result_frame, 1, frame_size, input_file); - if (bytes_read != frame_size && + if (bytes_read != static_cast(frame_size) && ferror(input_file)) { fprintf(stdout, "Error while reading frame no %d from file %s\n", frame_number, i420_file_name); diff --git a/src/tools/frame_analyzer/video_quality_analysis.h b/src/tools/frame_analyzer/video_quality_analysis.h index ea8a5f53a1..a385b77832 100644 --- a/src/tools/frame_analyzer/video_quality_analysis.h +++ b/src/tools/frame_analyzer/video_quality_analysis.h @@ -67,7 +67,7 @@ void PrintMaxRepeatedAndSkippedFrames(const char* stats_file_name); bool GetNextStatsLine(FILE* stats_file, char* line); // Calculates the size of a I420 frame if given the width and height. -unsigned int GetI420FrameSize(unsigned int width, unsigned int height); +int GetI420FrameSize(int width, int height); // Extract the sequence of the frame in the video. I.e. if line is // frame_0023 0284, we will get 23. diff --git a/src/tools/psnr_ssim_analyzer/psnr_ssim_analyzer.cc b/src/tools/psnr_ssim_analyzer/psnr_ssim_analyzer.cc new file mode 100644 index 0000000000..00fdf63566 --- /dev/null +++ b/src/tools/psnr_ssim_analyzer/psnr_ssim_analyzer.cc @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include + +#include "tools/frame_analyzer/video_quality_analysis.h" +#include "tools/simple_command_line_parser.h" + +void CompareFiles(const char* reference_file_name, const char* test_file_name, + const char* results_file_name, int width, int height) { + FILE* ref_file = fopen(reference_file_name, "rb"); + FILE* test_file = fopen(test_file_name, "rb"); + FILE* results_file = fopen(results_file_name, "w"); + + int size = webrtc::test::GetI420FrameSize(width, height); + + // Allocate buffers for test and reference frames. + uint8* test_frame = new uint8[size]; + uint8* ref_frame = new uint8[size]; + + int frame_counter = 0; + + while (webrtc::test::GetNextI420Frame(ref_file, width, height, ref_frame) && + webrtc::test::GetNextI420Frame(test_file, width, height, test_frame)) { + // Calculate the PSNR and SSIM. + double result_psnr = webrtc::test::CalculateMetrics( + webrtc::test::kPSNR, ref_frame, test_frame, width, height); + double result_ssim = webrtc::test::CalculateMetrics( + webrtc::test::kSSIM, ref_frame, test_frame, width, height); + fprintf(results_file, "Frame: %d, PSNR: %f, SSIM: %f\n", frame_counter, + result_psnr, result_ssim); + ++frame_counter; + } + delete[] test_frame; + delete[] ref_frame; + + fclose(ref_file); + fclose(test_file); + fclose(results_file); +} + +/* + * A tool running PSNR and SSIM analysis on two videos - a reference video and a + * test video. The two videos should be I420 YUV videos. + * The tool just runs PSNR and SSIM on the corresponding frames in the test and + * the reference videos until either the first or the second video runs out of + * frames. The result is written in a results text file in the format: + * Frame: , PSNR: , SSIM: + * Frame: , ........ + * + * The max value for PSNR is 48.0 (between equal frames), as for SSIM it is 1.0. + * + * Usage: + * psnr_ssim_analyzer --reference_file= --test_file= + * --results_file= --width= + * --height= + */ +int main(int argc, char** argv) { + std::string program_name = argv[0]; + std::string usage = "Runs PSNR and SSIM on two I420 videos and write the" + "results in a file.\n" + "Example usage:\n" + program_name + " --reference_file=ref.yuv " + "--test_file=test.yuv --results_file=results.txt --width=320 " + "--height=240\n" + "Command line flags:\n" + " - width(int): The width of the reference and test files. Default: -1\n" + " - height(int): The height of the reference and test files. " + " Default: -1\n" + " - reference_file(string): The reference YUV file to compare against." + " Default: ref.yuv\n" + " - test_file(string): The test YUV file to run the analysis for." + " Default: test_file.yuv\n" + " - results_file(string): The full name of the file where the results " + "will be written. Default: results.txt\n"; + + webrtc::test::CommandLineParser parser; + + // Init the parser and set the usage message + parser.Init(argc, argv); + parser.SetUsageMessage(usage); + + parser.SetFlag("width", "-1"); + parser.SetFlag("height", "-1"); + parser.SetFlag("results_file", "results.txt"); + parser.SetFlag("reference_file", "ref.yuv"); + parser.SetFlag("test_file", "test.yuv"); + parser.SetFlag("results_file", "results.txt"); + parser.SetFlag("help", "false"); + + parser.ProcessFlags(); + if (parser.GetFlag("help") == "true") { + parser.PrintUsageMessage(); + } + parser.PrintEnteredFlags(); + + int width = strtol((parser.GetFlag("width")).c_str(), NULL, 10); + int height = strtol((parser.GetFlag("height")).c_str(), NULL, 10); + + if (width <= 0 || height <= 0) { + fprintf(stderr, "Error: width or height cannot be <= 0!\n"); + return -1; + } + + CompareFiles(parser.GetFlag("reference_file").c_str(), + parser.GetFlag("test_file").c_str(), + parser.GetFlag("results_file").c_str(), width, height); +} diff --git a/src/tools/tools.gyp b/src/tools/tools.gyp index 5380043e18..bff24a5349 100644 --- a/src/tools/tools.gyp +++ b/src/tools/tools.gyp @@ -11,33 +11,78 @@ '../build/common.gypi', ], 'targets': [ - { - 'target_name': 'frame_analyzer', - 'type': 'executable', - 'dependencies': [ - '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', + { + 'target_name': 'command_line_parser', + 'type': '<(library)', + 'include_dirs': [ + '.', ], + 'direct_dependent_settings': { + 'include_dirs': [ + '.', + ], + }, 'sources': [ - 'frame_analyzer/frame_analyzer.cc', - 'frame_analyzer/video_quality_analysis.h', - 'frame_analyzer/video_quality_analysis.cc', 'simple_command_line_parser.h', 'simple_command_line_parser.cc', ], - }, + }, # command_line_parser + { + 'target_name': 'video_quality_analysis', + 'type': '<(library)', + 'dependencies': [ + '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', + ], + 'include_dirs': [ + 'frame_analyzer', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'frame_analyzer', + ], + }, + 'export_dependent_settings': [ + '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', + ], + 'sources': [ + 'frame_analyzer/video_quality_analysis.h', + 'frame_analyzer/video_quality_analysis.cc', + ], + }, # video_quality_analysis + { + 'target_name': 'frame_analyzer', + 'type': 'executable', + 'dependencies': [ + 'command_line_parser', + 'video_quality_analysis', + ], + 'sources': [ + 'frame_analyzer/frame_analyzer.cc', + ], + }, # frame_analyzer + { + 'target_name': 'psnr_ssim_analyzer', + 'type': 'executable', + 'dependencies': [ + 'command_line_parser', + 'video_quality_analysis', + ], + 'sources': [ + 'psnr_ssim_analyzer/psnr_ssim_analyzer.cc', + ], + }, # psnr_ssim_analyzer { 'target_name': 'rgba_to_i420_converter', 'type': 'executable', 'dependencies': [ + 'command_line_parser', '<(DEPTH)/third_party/libyuv/libyuv.gyp:libyuv', ], 'sources': [ 'converter/converter.h', 'converter/converter.cc', 'converter/rgba_to_i420_converter.cc', - 'simple_command_line_parser.h', - 'simple_command_line_parser.cc', ], - }, + }, # rgba_to_i420_converter ], }