/* * 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 #include #include #include #include "absl/flags/flag.h" #include "absl/flags/parse.h" #include "absl/strings/match.h" #include "api/array_view.h" #include "api/scoped_refptr.h" #include "api/test/metrics/chrome_perf_dashboard_metrics_exporter.h" #include "api/test/metrics/global_metrics_logger_and_exporter.h" #include "api/test/metrics/metric.h" #include "api/test/metrics/metrics_exporter.h" #include "rtc_base/strings/string_builder.h" #include "rtc_tools/frame_analyzer/video_color_aligner.h" #include "rtc_tools/frame_analyzer/video_geometry_aligner.h" #include "rtc_tools/frame_analyzer/video_quality_analysis.h" #include "rtc_tools/frame_analyzer/video_temporal_aligner.h" #include "rtc_tools/video_file_reader.h" #include "rtc_tools/video_file_writer.h" ABSL_FLAG(int32_t, width, -1, "The width of the reference and test files"); ABSL_FLAG(int32_t, height, -1, "The height of the reference and test files"); ABSL_FLAG(std::string, label, "MY_TEST", "The label to use for the perf output"); ABSL_FLAG(std::string, reference_file, "ref.yuv", "The reference YUV file to run the analysis against"); ABSL_FLAG(std::string, test_file, "test.yuv", "The test YUV file to run the analysis for"); ABSL_FLAG(std::string, aligned_output_file, "", "Where to write aligned YUV/Y4M output file, f not present, no files " "will be written"); ABSL_FLAG(std::string, yuv_directory, "", "Where to write aligned YUV ref+test output files, if not present, " "no files will be written"); ABSL_FLAG(std::string, chartjson_result_file, "", "Where to store perf result in chartjson format, if not present, no " "perf result will be stored"); namespace { #ifdef WIN32 const char* const kPathDelimiter = "\\"; #else const char* const kPathDelimiter = "/"; #endif std::string JoinFilename(std::string directory, std::string filename) { return directory + kPathDelimiter + filename; } // FrameAnalyzerMetricsExporter is a fork of // webrtc::test::StdoutMetricsExporter. The fork was required because: // 1. frame_analyzer must be compiled with build_with_chromium=true // 2. The api/metrics:stdout_metrics_exporter depends on absl/flags which cannot // be build when build_with_chromium=true unless the target is an // rtc_executable (which is not the case for // api/metrics:stdout_metrics_exporter). So this fork allows to cut the // dependency. // Returns positive integral part of the number. int64_t IntegralPart(double value) { return std::lround(std::floor(std::abs(value))); } void AppendWithPrecision(double value, int digits_after_comma, webrtc::StringBuilder& out) { int64_t multiplier = std::lround(std::pow(10, digits_after_comma)); int64_t integral_part = IntegralPart(value); double decimal_part = std::abs(value) - integral_part; // If decimal part has leading zeros then when it will be multiplied on // `multiplier`, leading zeros will be lost. To preserve them we add "1" // so then leading digit will be greater than 0 and won't be removed. // // During conversion to the string leading digit has to be stripped. // // Also due to rounding it may happen that leading digit may be incremented, // like with `digits_after_comma` 3 number 1.9995 will be rounded to 2. In // such case this increment has to be propagated to the `integral_part`. int64_t decimal_holder = std::lround((1 + decimal_part) * multiplier); if (decimal_holder >= 2 * multiplier) { // Rounding incremented added leading digit, so we need to transfer 1 to // integral part. integral_part++; decimal_holder -= multiplier; } // Remove trailing zeros. while (decimal_holder % 10 == 0) { decimal_holder /= 10; } // Print serialized number to output. if (value < 0) { out << "-"; } out << integral_part; if (decimal_holder != 1) { out << "." << std::to_string(decimal_holder).substr(1, digits_after_comma); } } class FrameAnalyzerMetricsExporter : public webrtc::test::MetricsExporter { public: FrameAnalyzerMetricsExporter() : output_(stdout) {} ~FrameAnalyzerMetricsExporter() override = default; FrameAnalyzerMetricsExporter(const FrameAnalyzerMetricsExporter&) = delete; FrameAnalyzerMetricsExporter& operator=(const FrameAnalyzerMetricsExporter&) = delete; bool Export(webrtc::ArrayView metrics) override { for (const webrtc::test::Metric& metric : metrics) { PrintMetric(metric); } return true; } private: void PrintMetric(const webrtc::test::Metric& metric) { webrtc::StringBuilder value_stream; value_stream << metric.test_case << " / " << metric.name << "= {mean="; if (metric.stats.mean.has_value()) { AppendWithPrecision(*metric.stats.mean, 8, value_stream); } else { value_stream << "-"; } value_stream << ", stddev="; if (metric.stats.stddev.has_value()) { AppendWithPrecision(*metric.stats.stddev, 8, value_stream); } else { value_stream << "-"; } value_stream << "} " << ToString(metric.unit) << " (" << ToString(metric.improvement_direction) << ")"; fprintf(output_, "RESULT: %s\n", value_stream.str().c_str()); } FILE* const output_; }; } // namespace /* * 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. The video files should be I420 .y4m or .yuv videos. * If both files are .y4m, it's not needed to specify width/height. The tool * prints the result to standard output in the Chromium perf format: * RESULT :