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