1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <math.h>
6 #include <utility>
7 
8 #include "media/base/video_frame.h"
9 #include "media/base/video_types.h"
10 #include "media/gpu/test/video_frame_helpers.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 #include "third_party/libyuv/include/libyuv/compare.h"
13 #include "ui/gfx/geometry/point.h"
14 
15 #define ASSERT_TRUE_OR_RETURN(predicate, return_value) \
16   do {                                                 \
17     if (!(predicate)) {                                \
18       ADD_FAILURE();                                   \
19       return (return_value);                           \
20     }                                                  \
21   } while (0)
22 
23 namespace media {
24 namespace test {
25 namespace {
26 // The metrics of the similarity of two images.
27 enum SimilarityMetrics {
28   PSNR,  // Peak Signal-to-Noise Ratio. For detail see
29          // https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio
30   SSIM,  // Structural Similarity. For detail see
31          // https://en.wikipedia.org/wiki/Structural_similarity
32 };
33 
ComputeSimilarity(const VideoFrame * frame1,const VideoFrame * frame2,SimilarityMetrics mode)34 double ComputeSimilarity(const VideoFrame* frame1,
35                          const VideoFrame* frame2,
36                          SimilarityMetrics mode) {
37   ASSERT_TRUE_OR_RETURN(frame1->IsMappable() && frame2->IsMappable(),
38                         std::numeric_limits<std::size_t>::max());
39   // TODO(crbug.com/1044509): Remove these assumptions.
40   ASSERT_TRUE_OR_RETURN(frame1->visible_rect() == frame2->visible_rect(),
41                         std::numeric_limits<std::size_t>::max());
42   ASSERT_TRUE_OR_RETURN(frame1->visible_rect().origin() == gfx::Point(0, 0),
43                         std::numeric_limits<std::size_t>::max());
44   // These are used, only if frames are converted to I420, for keeping converted
45   // frames alive until the end of function.
46   scoped_refptr<VideoFrame> converted_frame1;
47   scoped_refptr<VideoFrame> converted_frame2;
48 
49   if (frame1->format() != PIXEL_FORMAT_I420) {
50     converted_frame1 = ConvertVideoFrame(frame1, PIXEL_FORMAT_I420);
51     frame1 = converted_frame1.get();
52   }
53   if (frame2->format() != PIXEL_FORMAT_I420) {
54     converted_frame2 = ConvertVideoFrame(frame2, PIXEL_FORMAT_I420);
55     frame2 = converted_frame2.get();
56   }
57 
58   decltype(&libyuv::I420Psnr) metric_func = nullptr;
59   switch (mode) {
60     case SimilarityMetrics::PSNR:
61       metric_func = &libyuv::I420Psnr;
62       break;
63     case SimilarityMetrics::SSIM:
64       metric_func = &libyuv::I420Ssim;
65       break;
66   }
67   ASSERT_TRUE_OR_RETURN(metric_func, std::numeric_limits<double>::max());
68 
69   return metric_func(
70       frame1->data(0), frame1->stride(0), frame1->data(1), frame1->stride(1),
71       frame1->data(2), frame1->stride(2), frame2->data(0), frame2->stride(0),
72       frame2->data(1), frame2->stride(1), frame2->data(2), frame2->stride(2),
73       frame1->visible_rect().width(), frame1->visible_rect().height());
74 }
75 }  // namespace
76 
CompareFramesWithErrorDiff(const VideoFrame & frame1,const VideoFrame & frame2,uint8_t tolerance)77 size_t CompareFramesWithErrorDiff(const VideoFrame& frame1,
78                                   const VideoFrame& frame2,
79                                   uint8_t tolerance) {
80   ASSERT_TRUE_OR_RETURN(frame1.format() == frame2.format(),
81                         std::numeric_limits<std::size_t>::max());
82   // TODO(crbug.com/1044509): Remove these assumption.
83   ASSERT_TRUE_OR_RETURN(frame1.visible_rect() == frame2.visible_rect(),
84                         std::numeric_limits<std::size_t>::max());
85   ASSERT_TRUE_OR_RETURN(frame1.visible_rect().origin() == gfx::Point(0, 0),
86                         std::numeric_limits<std::size_t>::max());
87   ASSERT_TRUE_OR_RETURN(frame1.IsMappable() && frame2.IsMappable(),
88                         std::numeric_limits<std::size_t>::max());
89   size_t diff_cnt = 0;
90 
91   const VideoPixelFormat format = frame1.format();
92   const size_t num_planes = VideoFrame::NumPlanes(format);
93   const gfx::Size& visible_size = frame1.visible_rect().size();
94   for (size_t i = 0; i < num_planes; ++i) {
95     const uint8_t* data1 = frame1.data(i);
96     const int stride1 = frame1.stride(i);
97     const uint8_t* data2 = frame2.data(i);
98     const int stride2 = frame2.stride(i);
99     const size_t rows = VideoFrame::Rows(i, format, visible_size.height());
100     const int row_bytes = VideoFrame::RowBytes(i, format, visible_size.width());
101     for (size_t r = 0; r < rows; ++r) {
102       for (int c = 0; c < row_bytes; c++) {
103         uint8_t b1 = data1[(stride1 * r) + c];
104         uint8_t b2 = data2[(stride2 * r) + c];
105         uint8_t diff = std::max(b1, b2) - std::min(b1, b2);
106         diff_cnt += diff > tolerance;
107       }
108     }
109   }
110   return diff_cnt;
111 }
112 
ComputePSNR(const VideoFrame & frame1,const VideoFrame & frame2)113 double ComputePSNR(const VideoFrame& frame1, const VideoFrame& frame2) {
114   return ComputeSimilarity(&frame1, &frame2, SimilarityMetrics::PSNR);
115 }
116 
ComputeSSIM(const VideoFrame & frame1,const VideoFrame & frame2)117 double ComputeSSIM(const VideoFrame& frame1, const VideoFrame& frame2) {
118   return ComputeSimilarity(&frame1, &frame2, SimilarityMetrics::SSIM);
119 }
120 }  // namespace test
121 }  // namespace media
122