1 /*
2 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "third_party/libyuv/include/libyuv.h"
12
13 #include <math.h>
14 #include <string.h>
15
16 #include <memory>
17
18 #include "api/video/i420_buffer.h"
19 #include "api/video/video_frame.h"
20 #include "common_video/libyuv/include/webrtc_libyuv.h"
21 #include "test/frame_utils.h"
22 #include "test/gmock.h"
23 #include "test/gtest.h"
24 #include "test/testsupport/file_utils.h"
25
26 namespace webrtc {
27
28 namespace {
Calc16ByteAlignedStride(int width,int * stride_y,int * stride_uv)29 void Calc16ByteAlignedStride(int width, int* stride_y, int* stride_uv) {
30 *stride_y = 16 * ((width + 15) / 16);
31 *stride_uv = 16 * ((width + 31) / 32);
32 }
33
34 } // Anonymous namespace
35
36 class TestLibYuv : public ::testing::Test {
37 protected:
38 TestLibYuv();
39 void SetUp() override;
40 void TearDown() override;
41
42 FILE* source_file_;
43 std::unique_ptr<VideoFrame> orig_frame_;
44 const int width_;
45 const int height_;
46 const int size_y_;
47 const int size_uv_;
48 const size_t frame_length_;
49 };
50
TestLibYuv()51 TestLibYuv::TestLibYuv()
52 : source_file_(NULL),
53 orig_frame_(),
54 width_(352),
55 height_(288),
56 size_y_(width_ * height_),
57 size_uv_(((width_ + 1) / 2) * ((height_ + 1) / 2)),
58 frame_length_(CalcBufferSize(VideoType::kI420, 352, 288)) {}
59
SetUp()60 void TestLibYuv::SetUp() {
61 const std::string input_file_name =
62 webrtc::test::ResourcePath("foreman_cif", "yuv");
63 source_file_ = fopen(input_file_name.c_str(), "rb");
64 ASSERT_TRUE(source_file_ != NULL)
65 << "Cannot read file: " << input_file_name << "\n";
66
67 rtc::scoped_refptr<I420BufferInterface> buffer(
68 test::ReadI420Buffer(width_, height_, source_file_));
69
70 orig_frame_ =
71 std::make_unique<VideoFrame>(VideoFrame::Builder()
72 .set_video_frame_buffer(buffer)
73 .set_rotation(webrtc::kVideoRotation_0)
74 .set_timestamp_us(0)
75 .build());
76 }
77
TearDown()78 void TestLibYuv::TearDown() {
79 if (source_file_ != NULL) {
80 ASSERT_EQ(0, fclose(source_file_));
81 }
82 source_file_ = NULL;
83 }
84
TEST_F(TestLibYuv,ConvertSanityTest)85 TEST_F(TestLibYuv, ConvertSanityTest) {
86 // TODO(mikhal)
87 }
88
TEST_F(TestLibYuv,ConvertTest)89 TEST_F(TestLibYuv, ConvertTest) {
90 // Reading YUV frame - testing on the first frame of the foreman sequence
91 int j = 0;
92 std::string output_file_name =
93 webrtc::test::OutputPath() + "LibYuvTest_conversion.yuv";
94 FILE* output_file = fopen(output_file_name.c_str(), "wb");
95 ASSERT_TRUE(output_file != NULL);
96
97 double psnr = 0.0;
98
99 rtc::scoped_refptr<I420Buffer> res_i420_buffer =
100 I420Buffer::Create(width_, height_);
101
102 printf("\nConvert #%d I420 <-> I420 \n", j);
103 std::unique_ptr<uint8_t[]> out_i420_buffer(new uint8_t[frame_length_]);
104 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kI420, 0,
105 out_i420_buffer.get()));
106 int y_size = width_ * height_;
107 int u_size = res_i420_buffer->ChromaWidth() * res_i420_buffer->ChromaHeight();
108 int ret = libyuv::I420Copy(
109 out_i420_buffer.get(), width_, out_i420_buffer.get() + y_size,
110 width_ >> 1, out_i420_buffer.get() + y_size + u_size, width_ >> 1,
111 res_i420_buffer.get()->MutableDataY(), res_i420_buffer.get()->StrideY(),
112 res_i420_buffer.get()->MutableDataU(), res_i420_buffer.get()->StrideU(),
113 res_i420_buffer.get()->MutableDataV(), res_i420_buffer.get()->StrideV(),
114 width_, height_);
115 EXPECT_EQ(0, ret);
116
117 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
118 return;
119 }
120 psnr =
121 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
122 EXPECT_EQ(48.0, psnr);
123 j++;
124
125 printf("\nConvert #%d I420 <-> RGB24\n", j);
126 std::unique_ptr<uint8_t[]> res_rgb_buffer2(new uint8_t[width_ * height_ * 3]);
127 // Align the stride values for the output frame.
128 int stride_y = 0;
129 int stride_uv = 0;
130 Calc16ByteAlignedStride(width_, &stride_y, &stride_uv);
131 res_i420_buffer =
132 I420Buffer::Create(width_, height_, stride_y, stride_uv, stride_uv);
133 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kRGB24, 0,
134 res_rgb_buffer2.get()));
135
136 ret = libyuv::ConvertToI420(
137 res_rgb_buffer2.get(), 0, res_i420_buffer.get()->MutableDataY(),
138 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
139 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
140 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
141 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
142 ConvertVideoType(VideoType::kRGB24));
143
144 EXPECT_EQ(0, ret);
145 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
146 return;
147 }
148 psnr =
149 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
150
151 // Optimization Speed- quality trade-off => 45 dB only (platform dependant).
152 EXPECT_GT(ceil(psnr), 44);
153 j++;
154
155 printf("\nConvert #%d I420 <-> UYVY\n", j);
156 std::unique_ptr<uint8_t[]> out_uyvy_buffer(new uint8_t[width_ * height_ * 2]);
157 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kUYVY, 0,
158 out_uyvy_buffer.get()));
159
160 ret = libyuv::ConvertToI420(
161 out_uyvy_buffer.get(), 0, res_i420_buffer.get()->MutableDataY(),
162 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
163 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
164 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
165 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
166 ConvertVideoType(VideoType::kUYVY));
167
168 EXPECT_EQ(0, ret);
169 psnr =
170 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
171 EXPECT_EQ(48.0, psnr);
172 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
173 return;
174 }
175 j++;
176
177 printf("\nConvert #%d I420 <-> YUY2\n", j);
178 std::unique_ptr<uint8_t[]> out_yuy2_buffer(new uint8_t[width_ * height_ * 2]);
179 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kYUY2, 0,
180 out_yuy2_buffer.get()));
181
182 ret = libyuv::ConvertToI420(
183 out_yuy2_buffer.get(), 0, res_i420_buffer.get()->MutableDataY(),
184 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
185 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
186 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
187 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
188 ConvertVideoType(VideoType::kYUY2));
189
190 EXPECT_EQ(0, ret);
191
192 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
193 return;
194 }
195
196 psnr =
197 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
198 EXPECT_EQ(48.0, psnr);
199
200 printf("\nConvert #%d I420 <-> RGB565\n", j);
201 std::unique_ptr<uint8_t[]> out_rgb565_buffer(
202 new uint8_t[width_ * height_ * 2]);
203 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kRGB565, 0,
204 out_rgb565_buffer.get()));
205
206 ret = libyuv::ConvertToI420(
207 out_rgb565_buffer.get(), 0, res_i420_buffer.get()->MutableDataY(),
208 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
209 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
210 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
211 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
212 ConvertVideoType(VideoType::kRGB565));
213
214 EXPECT_EQ(0, ret);
215 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
216 return;
217 }
218 j++;
219
220 psnr =
221 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
222 // TODO(leozwang) Investigate the right psnr should be set for I420ToRGB565,
223 // Another example is I420ToRGB24, the psnr is 44
224 // TODO(mikhal): Add psnr for RGB565, 1555, 4444, convert to ARGB.
225 EXPECT_GT(ceil(psnr), 40);
226
227 printf("\nConvert #%d I420 <-> ARGB8888\n", j);
228 std::unique_ptr<uint8_t[]> out_argb8888_buffer(
229 new uint8_t[width_ * height_ * 4]);
230 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kARGB, 0,
231 out_argb8888_buffer.get()));
232
233 ret = libyuv::ConvertToI420(
234 out_argb8888_buffer.get(), 0, res_i420_buffer.get()->MutableDataY(),
235 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
236 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
237 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
238 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
239 ConvertVideoType(VideoType::kARGB));
240
241 EXPECT_EQ(0, ret);
242
243 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
244 return;
245 }
246
247 psnr =
248 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
249 // TODO(leozwang) Investigate the right psnr should be set for
250 // I420ToARGB8888,
251 EXPECT_GT(ceil(psnr), 42);
252
253 ASSERT_EQ(0, fclose(output_file));
254 }
255
TEST_F(TestLibYuv,ConvertAlignedFrame)256 TEST_F(TestLibYuv, ConvertAlignedFrame) {
257 // Reading YUV frame - testing on the first frame of the foreman sequence
258 std::string output_file_name =
259 webrtc::test::OutputPath() + "LibYuvTest_conversion.yuv";
260 FILE* output_file = fopen(output_file_name.c_str(), "wb");
261 ASSERT_TRUE(output_file != NULL);
262
263 double psnr = 0.0;
264
265 int stride_y = 0;
266 int stride_uv = 0;
267 Calc16ByteAlignedStride(width_, &stride_y, &stride_uv);
268
269 rtc::scoped_refptr<I420Buffer> res_i420_buffer =
270 I420Buffer::Create(width_, height_, stride_y, stride_uv, stride_uv);
271 std::unique_ptr<uint8_t[]> out_i420_buffer(new uint8_t[frame_length_]);
272 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kI420, 0,
273 out_i420_buffer.get()));
274 int y_size = width_ * height_;
275 int u_size = res_i420_buffer->ChromaWidth() * res_i420_buffer->ChromaHeight();
276 int ret = libyuv::I420Copy(
277 out_i420_buffer.get(), width_, out_i420_buffer.get() + y_size,
278 width_ >> 1, out_i420_buffer.get() + y_size + u_size, width_ >> 1,
279 res_i420_buffer.get()->MutableDataY(), res_i420_buffer.get()->StrideY(),
280 res_i420_buffer.get()->MutableDataU(), res_i420_buffer.get()->StrideU(),
281 res_i420_buffer.get()->MutableDataV(), res_i420_buffer.get()->StrideV(),
282 width_, height_);
283
284 EXPECT_EQ(0, ret);
285
286 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
287 return;
288 }
289 psnr =
290 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
291 EXPECT_EQ(48.0, psnr);
292 }
293
Average(int a,int b,int c,int d)294 static uint8_t Average(int a, int b, int c, int d) {
295 return (a + b + c + d + 2) / 4;
296 }
297
TEST_F(TestLibYuv,NV12Scale2x2to2x2)298 TEST_F(TestLibYuv, NV12Scale2x2to2x2) {
299 const std::vector<uint8_t> src_y = {0, 1, 2, 3};
300 const std::vector<uint8_t> src_uv = {0, 1};
301 std::vector<uint8_t> dst_y(4);
302 std::vector<uint8_t> dst_uv(2);
303
304 uint8_t* tmp_buffer = nullptr;
305
306 NV12Scale(tmp_buffer, src_y.data(), 2, src_uv.data(), 2, 2, 2, dst_y.data(),
307 2, dst_uv.data(), 2, 2, 2);
308
309 EXPECT_THAT(dst_y, ::testing::ContainerEq(src_y));
310 EXPECT_THAT(dst_uv, ::testing::ContainerEq(src_uv));
311 }
312
TEST_F(TestLibYuv,NV12Scale4x4to2x2)313 TEST_F(TestLibYuv, NV12Scale4x4to2x2) {
314 const uint8_t src_y[] = {0, 1, 2, 3, 4, 5, 6, 7,
315 8, 9, 10, 11, 12, 13, 14, 15};
316 const uint8_t src_uv[] = {0, 1, 2, 3, 4, 5, 6, 7};
317 std::vector<uint8_t> dst_y(4);
318 std::vector<uint8_t> dst_uv(2);
319
320 std::vector<uint8_t> tmp_buffer;
321 const int src_chroma_width = (4 + 1) / 2;
322 const int src_chroma_height = (4 + 1) / 2;
323 const int dst_chroma_width = (2 + 1) / 2;
324 const int dst_chroma_height = (2 + 1) / 2;
325 tmp_buffer.resize(src_chroma_width * src_chroma_height * 2 +
326 dst_chroma_width * dst_chroma_height * 2);
327 tmp_buffer.shrink_to_fit();
328
329 NV12Scale(tmp_buffer.data(), src_y, 4, src_uv, 4, 4, 4, dst_y.data(), 2,
330 dst_uv.data(), 2, 2, 2);
331
332 EXPECT_THAT(dst_y, ::testing::ElementsAre(
333 Average(0, 1, 4, 5), Average(2, 3, 6, 7),
334 Average(8, 9, 12, 13), Average(10, 11, 14, 15)));
335 EXPECT_THAT(dst_uv,
336 ::testing::ElementsAre(Average(0, 2, 4, 6), Average(1, 3, 5, 7)));
337 }
338
339 } // namespace webrtc
340