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
PrintPlane(const uint8_t * buf,int width,int height,int stride,FILE * file)34 int PrintPlane(const uint8_t* buf,
35 int width,
36 int height,
37 int stride,
38 FILE* file) {
39 for (int i = 0; i < height; i++, buf += stride) {
40 if (fwrite(buf, 1, width, file) != static_cast<unsigned int>(width))
41 return -1;
42 }
43 return 0;
44 }
45
PrintVideoFrame(const I420BufferInterface & frame,FILE * file)46 int PrintVideoFrame(const I420BufferInterface& frame, FILE* file) {
47 int width = frame.width();
48 int height = frame.height();
49 int chroma_width = frame.ChromaWidth();
50 int chroma_height = frame.ChromaHeight();
51
52 if (PrintPlane(frame.DataY(), width, height, frame.StrideY(), file) < 0) {
53 return -1;
54 }
55 if (PrintPlane(frame.DataU(), chroma_width, chroma_height, frame.StrideU(),
56 file) < 0) {
57 return -1;
58 }
59 if (PrintPlane(frame.DataV(), chroma_width, chroma_height, frame.StrideV(),
60 file) < 0) {
61 return -1;
62 }
63 return 0;
64 }
65
66 } // Anonymous namespace
67
68 class TestLibYuv : public ::testing::Test {
69 protected:
70 TestLibYuv();
71 void SetUp() override;
72 void TearDown() override;
73
74 FILE* source_file_;
75 std::unique_ptr<VideoFrame> orig_frame_;
76 const int width_;
77 const int height_;
78 const int size_y_;
79 const int size_uv_;
80 const size_t frame_length_;
81 };
82
TestLibYuv()83 TestLibYuv::TestLibYuv()
84 : source_file_(NULL),
85 orig_frame_(),
86 width_(352),
87 height_(288),
88 size_y_(width_ * height_),
89 size_uv_(((width_ + 1) / 2) * ((height_ + 1) / 2)),
90 frame_length_(CalcBufferSize(VideoType::kI420, 352, 288)) {}
91
SetUp()92 void TestLibYuv::SetUp() {
93 const std::string input_file_name =
94 webrtc::test::ResourcePath("foreman_cif", "yuv");
95 source_file_ = fopen(input_file_name.c_str(), "rb");
96 ASSERT_TRUE(source_file_ != NULL)
97 << "Cannot read file: " << input_file_name << "\n";
98
99 rtc::scoped_refptr<I420BufferInterface> buffer(
100 test::ReadI420Buffer(width_, height_, source_file_));
101
102 orig_frame_ =
103 std::make_unique<VideoFrame>(VideoFrame::Builder()
104 .set_video_frame_buffer(buffer)
105 .set_rotation(webrtc::kVideoRotation_0)
106 .set_timestamp_us(0)
107 .build());
108 }
109
TearDown()110 void TestLibYuv::TearDown() {
111 if (source_file_ != NULL) {
112 ASSERT_EQ(0, fclose(source_file_));
113 }
114 source_file_ = NULL;
115 }
116
TEST_F(TestLibYuv,ConvertTest)117 TEST_F(TestLibYuv, ConvertTest) {
118 // Reading YUV frame - testing on the first frame of the foreman sequence
119 int j = 0;
120 std::string output_file_name =
121 webrtc::test::OutputPath() + "LibYuvTest_conversion.yuv";
122 FILE* output_file = fopen(output_file_name.c_str(), "wb");
123 ASSERT_TRUE(output_file != NULL);
124
125 double psnr = 0.0;
126
127 rtc::scoped_refptr<I420Buffer> res_i420_buffer =
128 I420Buffer::Create(width_, height_);
129
130 printf("\nConvert #%d I420 <-> I420 \n", j);
131 std::unique_ptr<uint8_t[]> out_i420_buffer(new uint8_t[frame_length_]);
132 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kI420, 0,
133 out_i420_buffer.get()));
134 int y_size = width_ * height_;
135 int u_size = res_i420_buffer->ChromaWidth() * res_i420_buffer->ChromaHeight();
136 int ret = libyuv::I420Copy(
137 out_i420_buffer.get(), width_, out_i420_buffer.get() + y_size,
138 width_ >> 1, out_i420_buffer.get() + y_size + u_size, width_ >> 1,
139 res_i420_buffer.get()->MutableDataY(), res_i420_buffer.get()->StrideY(),
140 res_i420_buffer.get()->MutableDataU(), res_i420_buffer.get()->StrideU(),
141 res_i420_buffer.get()->MutableDataV(), res_i420_buffer.get()->StrideV(),
142 width_, height_);
143 EXPECT_EQ(0, ret);
144
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 EXPECT_EQ(48.0, psnr);
151 j++;
152
153 printf("\nConvert #%d I420 <-> RGB24\n", j);
154 std::unique_ptr<uint8_t[]> res_rgb_buffer2(new uint8_t[width_ * height_ * 3]);
155 // Align the stride values for the output frame.
156 int stride_y = 0;
157 int stride_uv = 0;
158 Calc16ByteAlignedStride(width_, &stride_y, &stride_uv);
159 res_i420_buffer =
160 I420Buffer::Create(width_, height_, stride_y, stride_uv, stride_uv);
161 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kRGB24, 0,
162 res_rgb_buffer2.get()));
163
164 ret = libyuv::ConvertToI420(
165 res_rgb_buffer2.get(), 0, res_i420_buffer.get()->MutableDataY(),
166 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
167 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
168 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
169 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
170 ConvertVideoType(VideoType::kRGB24));
171
172 EXPECT_EQ(0, ret);
173 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
174 return;
175 }
176 psnr =
177 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
178
179 // Optimization Speed- quality trade-off => 45 dB only (platform dependant).
180 EXPECT_GT(ceil(psnr), 44);
181 j++;
182
183 printf("\nConvert #%d I420 <-> UYVY\n", j);
184 std::unique_ptr<uint8_t[]> out_uyvy_buffer(new uint8_t[width_ * height_ * 2]);
185 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kUYVY, 0,
186 out_uyvy_buffer.get()));
187
188 ret = libyuv::ConvertToI420(
189 out_uyvy_buffer.get(), 0, res_i420_buffer.get()->MutableDataY(),
190 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
191 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
192 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
193 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
194 ConvertVideoType(VideoType::kUYVY));
195
196 EXPECT_EQ(0, ret);
197 psnr =
198 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
199 EXPECT_EQ(48.0, psnr);
200 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
201 return;
202 }
203 j++;
204
205 printf("\nConvert #%d I420 <-> YUY2\n", j);
206 std::unique_ptr<uint8_t[]> out_yuy2_buffer(new uint8_t[width_ * height_ * 2]);
207 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kYUY2, 0,
208 out_yuy2_buffer.get()));
209
210 ret = libyuv::ConvertToI420(
211 out_yuy2_buffer.get(), 0, res_i420_buffer.get()->MutableDataY(),
212 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
213 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
214 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
215 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
216 ConvertVideoType(VideoType::kYUY2));
217
218 EXPECT_EQ(0, ret);
219
220 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
221 return;
222 }
223
224 psnr =
225 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
226 EXPECT_EQ(48.0, psnr);
227
228 printf("\nConvert #%d I420 <-> RGB565\n", j);
229 std::unique_ptr<uint8_t[]> out_rgb565_buffer(
230 new uint8_t[width_ * height_ * 2]);
231 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kRGB565, 0,
232 out_rgb565_buffer.get()));
233
234 ret = libyuv::ConvertToI420(
235 out_rgb565_buffer.get(), 0, res_i420_buffer.get()->MutableDataY(),
236 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
237 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
238 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
239 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
240 ConvertVideoType(VideoType::kRGB565));
241
242 EXPECT_EQ(0, ret);
243 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
244 return;
245 }
246 j++;
247
248 psnr =
249 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
250 // TODO(leozwang) Investigate the right psnr should be set for I420ToRGB565,
251 // Another example is I420ToRGB24, the psnr is 44
252 // TODO(mikhal): Add psnr for RGB565, 1555, 4444, convert to ARGB.
253 EXPECT_GT(ceil(psnr), 40);
254
255 printf("\nConvert #%d I420 <-> ARGB8888\n", j);
256 std::unique_ptr<uint8_t[]> out_argb8888_buffer(
257 new uint8_t[width_ * height_ * 4]);
258 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kARGB, 0,
259 out_argb8888_buffer.get()));
260
261 ret = libyuv::ConvertToI420(
262 out_argb8888_buffer.get(), 0, res_i420_buffer.get()->MutableDataY(),
263 res_i420_buffer.get()->StrideY(), res_i420_buffer.get()->MutableDataU(),
264 res_i420_buffer.get()->StrideU(), res_i420_buffer.get()->MutableDataV(),
265 res_i420_buffer.get()->StrideV(), 0, 0, width_, height_,
266 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0,
267 ConvertVideoType(VideoType::kARGB));
268
269 EXPECT_EQ(0, ret);
270
271 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
272 return;
273 }
274
275 psnr =
276 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
277 // TODO(leozwang) Investigate the right psnr should be set for
278 // I420ToARGB8888,
279 EXPECT_GT(ceil(psnr), 42);
280
281 ASSERT_EQ(0, fclose(output_file));
282 }
283
TEST_F(TestLibYuv,ConvertAlignedFrame)284 TEST_F(TestLibYuv, ConvertAlignedFrame) {
285 // Reading YUV frame - testing on the first frame of the foreman sequence
286 std::string output_file_name =
287 webrtc::test::OutputPath() + "LibYuvTest_conversion.yuv";
288 FILE* output_file = fopen(output_file_name.c_str(), "wb");
289 ASSERT_TRUE(output_file != NULL);
290
291 double psnr = 0.0;
292
293 int stride_y = 0;
294 int stride_uv = 0;
295 Calc16ByteAlignedStride(width_, &stride_y, &stride_uv);
296
297 rtc::scoped_refptr<I420Buffer> res_i420_buffer =
298 I420Buffer::Create(width_, height_, stride_y, stride_uv, stride_uv);
299 std::unique_ptr<uint8_t[]> out_i420_buffer(new uint8_t[frame_length_]);
300 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kI420, 0,
301 out_i420_buffer.get()));
302 int y_size = width_ * height_;
303 int u_size = res_i420_buffer->ChromaWidth() * res_i420_buffer->ChromaHeight();
304 int ret = libyuv::I420Copy(
305 out_i420_buffer.get(), width_, out_i420_buffer.get() + y_size,
306 width_ >> 1, out_i420_buffer.get() + y_size + u_size, width_ >> 1,
307 res_i420_buffer.get()->MutableDataY(), res_i420_buffer.get()->StrideY(),
308 res_i420_buffer.get()->MutableDataU(), res_i420_buffer.get()->StrideU(),
309 res_i420_buffer.get()->MutableDataV(), res_i420_buffer.get()->StrideV(),
310 width_, height_);
311
312 EXPECT_EQ(0, ret);
313
314 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) {
315 return;
316 }
317 psnr =
318 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer);
319 EXPECT_EQ(48.0, psnr);
320 }
321
Average(int a,int b,int c,int d)322 static uint8_t Average(int a, int b, int c, int d) {
323 return (a + b + c + d + 2) / 4;
324 }
325
TEST_F(TestLibYuv,NV12Scale2x2to2x2)326 TEST_F(TestLibYuv, NV12Scale2x2to2x2) {
327 const std::vector<uint8_t> src_y = {0, 1, 2, 3};
328 const std::vector<uint8_t> src_uv = {0, 1};
329 std::vector<uint8_t> dst_y(4);
330 std::vector<uint8_t> dst_uv(2);
331
332 uint8_t* tmp_buffer = nullptr;
333
334 NV12Scale(tmp_buffer, src_y.data(), 2, src_uv.data(), 2, 2, 2, dst_y.data(),
335 2, dst_uv.data(), 2, 2, 2);
336
337 EXPECT_THAT(dst_y, ::testing::ContainerEq(src_y));
338 EXPECT_THAT(dst_uv, ::testing::ContainerEq(src_uv));
339 }
340
TEST_F(TestLibYuv,NV12Scale4x4to2x2)341 TEST_F(TestLibYuv, NV12Scale4x4to2x2) {
342 const uint8_t src_y[] = {0, 1, 2, 3, 4, 5, 6, 7,
343 8, 9, 10, 11, 12, 13, 14, 15};
344 const uint8_t src_uv[] = {0, 1, 2, 3, 4, 5, 6, 7};
345 std::vector<uint8_t> dst_y(4);
346 std::vector<uint8_t> dst_uv(2);
347
348 std::vector<uint8_t> tmp_buffer;
349 const int src_chroma_width = (4 + 1) / 2;
350 const int src_chroma_height = (4 + 1) / 2;
351 const int dst_chroma_width = (2 + 1) / 2;
352 const int dst_chroma_height = (2 + 1) / 2;
353 tmp_buffer.resize(src_chroma_width * src_chroma_height * 2 +
354 dst_chroma_width * dst_chroma_height * 2);
355 tmp_buffer.shrink_to_fit();
356
357 NV12Scale(tmp_buffer.data(), src_y, 4, src_uv, 4, 4, 4, dst_y.data(), 2,
358 dst_uv.data(), 2, 2, 2);
359
360 EXPECT_THAT(dst_y, ::testing::ElementsAre(
361 Average(0, 1, 4, 5), Average(2, 3, 6, 7),
362 Average(8, 9, 12, 13), Average(10, 11, 14, 15)));
363 EXPECT_THAT(dst_uv,
364 ::testing::ElementsAre(Average(0, 2, 4, 6), Average(1, 3, 5, 7)));
365 }
366
TEST(I420WeightedPSNRTest,SmokeTest)367 TEST(I420WeightedPSNRTest, SmokeTest) {
368 uint8_t ref_y[] = {0, 0, 0, 0};
369 uint8_t ref_uv[] = {0};
370 rtc::scoped_refptr<I420Buffer> ref_buffer =
371 I420Buffer::Copy(/*width=*/2, /*height=*/2, ref_y, /*stride_y=*/2, ref_uv,
372 /*stride_u=*/1, ref_uv, /*stride_v=*/1);
373
374 uint8_t test_y[] = {1, 1, 1, 1};
375 uint8_t test_uv[] = {2};
376 rtc::scoped_refptr<I420Buffer> test_buffer = I420Buffer::Copy(
377 /*width=*/2, /*height=*/2, test_y, /*stride_y=*/2, test_uv,
378 /*stride_u=*/1, test_uv, /*stride_v=*/1);
379
380 auto psnr = [](double mse) { return 10.0 * log10(255.0 * 255.0 / mse); };
381 EXPECT_NEAR(I420WeightedPSNR(*ref_buffer, *test_buffer),
382 (6.0 * psnr(1.0) + psnr(4.0) + psnr(4.0)) / 8.0,
383 /*abs_error=*/0.001);
384 }
385
386 } // namespace webrtc
387