| /* |
| * 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 "rtc_tools/frame_analyzer/video_quality_analysis.h" |
| |
| #include <algorithm> |
| #include <numeric> |
| |
| #include "rtc_base/logging.h" |
| #include "rtc_base/strings/string_builder.h" |
| #include "rtc_tools/frame_analyzer/video_color_aligner.h" |
| #include "rtc_tools/frame_analyzer/video_temporal_aligner.h" |
| #include "test/testsupport/perf_test.h" |
| #include "third_party/libyuv/include/libyuv/compare.h" |
| #include "third_party/libyuv/include/libyuv/convert.h" |
| |
| namespace webrtc { |
| namespace test { |
| |
| ResultsContainer::ResultsContainer() {} |
| ResultsContainer::~ResultsContainer() {} |
| |
| template <typename FrameMetricFunction> |
| static double CalculateMetric( |
| const FrameMetricFunction& frame_metric_function, |
| const rtc::scoped_refptr<I420BufferInterface>& ref_buffer, |
| const rtc::scoped_refptr<I420BufferInterface>& test_buffer) { |
| RTC_CHECK_EQ(ref_buffer->width(), test_buffer->width()); |
| RTC_CHECK_EQ(ref_buffer->height(), test_buffer->height()); |
| return frame_metric_function( |
| ref_buffer->DataY(), ref_buffer->StrideY(), ref_buffer->DataU(), |
| ref_buffer->StrideU(), ref_buffer->DataV(), ref_buffer->StrideV(), |
| test_buffer->DataY(), test_buffer->StrideY(), test_buffer->DataU(), |
| test_buffer->StrideU(), test_buffer->DataV(), test_buffer->StrideV(), |
| test_buffer->width(), test_buffer->height()); |
| } |
| |
| double Psnr(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer, |
| const rtc::scoped_refptr<I420BufferInterface>& test_buffer) { |
| // LibYuv sets the max psnr value to 128, we restrict it to 48. |
| // In case of 0 mse in one frame, 128 can skew the results significantly. |
| return std::min(48.0, |
| CalculateMetric(&libyuv::I420Psnr, ref_buffer, test_buffer)); |
| } |
| |
| double Ssim(const rtc::scoped_refptr<I420BufferInterface>& ref_buffer, |
| const rtc::scoped_refptr<I420BufferInterface>& test_buffer) { |
| return CalculateMetric(&libyuv::I420Ssim, ref_buffer, test_buffer); |
| } |
| |
| std::vector<AnalysisResult> RunAnalysis( |
| const rtc::scoped_refptr<webrtc::test::Video>& reference_video, |
| const rtc::scoped_refptr<webrtc::test::Video>& test_video, |
| const std::vector<size_t>& test_frame_indices) { |
| const rtc::scoped_refptr<Video> temporally_aligned_reference_video = |
| ReorderVideo(reference_video, test_frame_indices); |
| |
| const ColorTransformationMatrix color_transformation = |
| CalculateColorTransformationMatrix(temporally_aligned_reference_video, |
| test_video); |
| |
| char buf[256]; |
| rtc::SimpleStringBuilder string_builder(buf); |
| for (int i = 0; i < 3; ++i) { |
| string_builder << "\n"; |
| for (int j = 0; j < 4; ++j) |
| string_builder.AppendFormat("%6.2f ", color_transformation[i][j]); |
| } |
| RTC_LOG(LS_INFO) << "Adjusting test video with color transformation: " |
| << string_builder.str(); |
| |
| const rtc::scoped_refptr<Video> color_adjusted_test_video = |
| AdjustColors(color_transformation, test_video); |
| |
| std::vector<AnalysisResult> results; |
| for (size_t i = 0; i < color_adjusted_test_video->number_of_frames(); ++i) { |
| const rtc::scoped_refptr<I420BufferInterface>& test_frame = |
| color_adjusted_test_video->GetFrame(i); |
| const rtc::scoped_refptr<I420BufferInterface>& reference_frame = |
| temporally_aligned_reference_video->GetFrame(i); |
| |
| // Fill in the result struct. |
| AnalysisResult result; |
| result.frame_number = test_frame_indices[i]; |
| result.psnr_value = Psnr(reference_frame, test_frame); |
| result.ssim_value = Ssim(reference_frame, test_frame); |
| results.push_back(result); |
| } |
| |
| return results; |
| } |
| |
| std::vector<Cluster> CalculateFrameClusters( |
| const std::vector<size_t>& indices) { |
| std::vector<Cluster> clusters; |
| |
| for (size_t index : indices) { |
| if (!clusters.empty() && clusters.back().index == index) { |
| // This frame belongs to the previous cluster. |
| ++clusters.back().number_of_repeated_frames; |
| } else { |
| // Start a new cluster. |
| clusters.push_back({index, /* number_of_repeated_frames= */ 1}); |
| } |
| } |
| |
| return clusters; |
| } |
| |
| int GetMaxRepeatedFrames(const std::vector<Cluster>& clusters) { |
| int max_number_of_repeated_frames = 0; |
| for (const Cluster& cluster : clusters) { |
| max_number_of_repeated_frames = std::max(max_number_of_repeated_frames, |
| cluster.number_of_repeated_frames); |
| } |
| return max_number_of_repeated_frames; |
| } |
| |
| int GetMaxSkippedFrames(const std::vector<Cluster>& clusters) { |
| size_t max_skipped_frames = 0; |
| for (size_t i = 1; i < clusters.size(); ++i) { |
| const size_t skipped_frames = clusters[i].index - clusters[i - 1].index - 1; |
| max_skipped_frames = std::max(max_skipped_frames, skipped_frames); |
| } |
| return static_cast<int>(max_skipped_frames); |
| } |
| |
| int GetTotalNumberOfSkippedFrames(const std::vector<Cluster>& clusters) { |
| // The number of reference frames the test video spans. |
| const size_t number_ref_frames = |
| clusters.empty() ? 0 : 1 + clusters.back().index - clusters.front().index; |
| return static_cast<int>(number_ref_frames - clusters.size()); |
| } |
| |
| void PrintAnalysisResults(const std::string& label, ResultsContainer* results) { |
| PrintAnalysisResults(stdout, label, results); |
| } |
| |
| void PrintAnalysisResults(FILE* output, |
| const std::string& label, |
| ResultsContainer* results) { |
| SetPerfResultsOutput(output); |
| |
| if (results->frames.size() > 0u) { |
| PrintResult("Unique_frames_count", "", label, results->frames.size(), |
| "score", false); |
| |
| std::vector<double> psnr_values; |
| std::vector<double> ssim_values; |
| for (const auto& frame : results->frames) { |
| psnr_values.push_back(frame.psnr_value); |
| ssim_values.push_back(frame.ssim_value); |
| } |
| |
| PrintResultList("PSNR", "", label, psnr_values, "dB", false); |
| PrintResultList("SSIM", "", label, ssim_values, "score", false); |
| } |
| |
| PrintResult("Max_repeated", "", label, results->max_repeated_frames, "", |
| false); |
| PrintResult("Max_skipped", "", label, results->max_skipped_frames, "", false); |
| PrintResult("Total_skipped", "", label, results->total_skipped_frames, "", |
| false); |
| PrintResult("Decode_errors_reference", "", label, results->decode_errors_ref, |
| "", false); |
| PrintResult("Decode_errors_test", "", label, results->decode_errors_test, "", |
| false); |
| } |
| |
| } // namespace test |
| } // namespace webrtc |