|  | /* | 
|  | *  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 <array> | 
|  | #include <cstddef> | 
|  |  | 
|  | #include "api/numerics/samples_stats_counter.h" | 
|  | #include "api/test/metrics/metric.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/logging.h" | 
|  | #include "third_party/libyuv/include/libyuv/compare.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) { | 
|  | std::vector<AnalysisResult> results; | 
|  | for (size_t i = 0; i < test_video->number_of_frames(); ++i) { | 
|  | const rtc::scoped_refptr<I420BufferInterface>& test_frame = | 
|  | test_video->GetFrame(i); | 
|  | const rtc::scoped_refptr<I420BufferInterface>& reference_frame = | 
|  | 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, | 
|  | MetricsLogger& logger) { | 
|  | if (results.frames.size() > 0u) { | 
|  | logger.LogSingleValueMetric("Unique_frames_count", label, | 
|  | results.frames.size(), Unit::kUnitless, | 
|  | ImprovementDirection::kNeitherIsBetter); | 
|  |  | 
|  | SamplesStatsCounter psnr_values; | 
|  | SamplesStatsCounter ssim_values; | 
|  | for (const auto& frame : results.frames) { | 
|  | psnr_values.AddSample(frame.psnr_value); | 
|  | ssim_values.AddSample(frame.ssim_value); | 
|  | } | 
|  |  | 
|  | logger.LogMetric("PSNR_dB", label, psnr_values, Unit::kUnitless, | 
|  | ImprovementDirection::kNeitherIsBetter); | 
|  | logger.LogMetric("SSIM", label, ssim_values, Unit::kUnitless, | 
|  | ImprovementDirection::kNeitherIsBetter); | 
|  | } | 
|  |  | 
|  | logger.LogSingleValueMetric("Max_repeated", label, | 
|  | results.max_repeated_frames, Unit::kUnitless, | 
|  | ImprovementDirection::kNeitherIsBetter); | 
|  | logger.LogSingleValueMetric("Max_skipped", label, results.max_skipped_frames, | 
|  | Unit::kUnitless, | 
|  | ImprovementDirection::kNeitherIsBetter); | 
|  | logger.LogSingleValueMetric("Total_skipped", label, | 
|  | results.total_skipped_frames, Unit::kUnitless, | 
|  | ImprovementDirection::kNeitherIsBetter); | 
|  | logger.LogSingleValueMetric("Decode_errors_reference", label, | 
|  | results.decode_errors_ref, Unit::kUnitless, | 
|  | ImprovementDirection::kNeitherIsBetter); | 
|  | logger.LogSingleValueMetric("Decode_errors_test", label, | 
|  | results.decode_errors_test, Unit::kUnitless, | 
|  | ImprovementDirection::kNeitherIsBetter); | 
|  | } | 
|  |  | 
|  | }  // namespace test | 
|  | }  // namespace webrtc |