libyuv_unittest.cc (17891B)
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 <cmath> 12 #include <cstdint> 13 #include <cstdio> 14 #include <cstring> 15 #include <limits> 16 #include <memory> 17 #include <string> 18 #include <vector> 19 20 #include "api/scoped_refptr.h" 21 #include "api/video/i010_buffer.h" 22 #include "api/video/i210_buffer.h" 23 #include "api/video/i410_buffer.h" 24 #include "api/video/i420_buffer.h" 25 #include "api/video/i422_buffer.h" 26 #include "api/video/i444_buffer.h" 27 #include "api/video/nv12_buffer.h" 28 #include "api/video/video_frame.h" 29 #include "api/video/video_frame_buffer.h" 30 #include "api/video/video_rotation.h" 31 #include "common_video/libyuv/include/webrtc_libyuv.h" 32 #include "test/frame_utils.h" 33 #include "test/gmock.h" 34 #include "test/gtest.h" 35 #include "test/testsupport/file_utils.h" 36 #include "third_party/libyuv/include/libyuv/convert.h" 37 #include "third_party/libyuv/include/libyuv/rotate.h" 38 39 namespace webrtc { 40 41 namespace { 42 void Calc16ByteAlignedStride(int width, int* stride_y, int* stride_uv) { 43 *stride_y = 16 * ((width + 15) / 16); 44 *stride_uv = 16 * ((width + 31) / 32); 45 } 46 47 int PrintPlane(const uint8_t* buf, 48 int width, 49 int height, 50 int stride, 51 FILE* file) { 52 for (int i = 0; i < height; i++, buf += stride) { 53 if (fwrite(buf, 1, width, file) != static_cast<unsigned int>(width)) 54 return -1; 55 } 56 return 0; 57 } 58 59 int PrintVideoFrame(const I420BufferInterface& frame, FILE* file) { 60 int width = frame.width(); 61 int height = frame.height(); 62 int chroma_width = frame.ChromaWidth(); 63 int chroma_height = frame.ChromaHeight(); 64 65 if (PrintPlane(frame.DataY(), width, height, frame.StrideY(), file) < 0) { 66 return -1; 67 } 68 if (PrintPlane(frame.DataU(), chroma_width, chroma_height, frame.StrideU(), 69 file) < 0) { 70 return -1; 71 } 72 if (PrintPlane(frame.DataV(), chroma_width, chroma_height, frame.StrideV(), 73 file) < 0) { 74 return -1; 75 } 76 return 0; 77 } 78 79 } // Anonymous namespace 80 81 class TestLibYuv : public ::testing::Test { 82 protected: 83 TestLibYuv(); 84 void SetUp() override; 85 void TearDown() override; 86 87 FILE* source_file_; 88 std::unique_ptr<VideoFrame> orig_frame_; 89 const int width_; 90 const int height_; 91 const int size_y_; 92 const int size_uv_; 93 const size_t frame_length_; 94 }; 95 96 TestLibYuv::TestLibYuv() 97 : source_file_(nullptr), 98 orig_frame_(), 99 width_(352), 100 height_(288), 101 size_y_(width_ * height_), 102 size_uv_(((width_ + 1) / 2) * ((height_ + 1) / 2)), 103 frame_length_(CalcBufferSize(VideoType::kI420, 352, 288)) {} 104 105 void TestLibYuv::SetUp() { 106 const std::string input_file_name = test::ResourcePath("foreman_cif", "yuv"); 107 source_file_ = fopen(input_file_name.c_str(), "rb"); 108 ASSERT_TRUE(source_file_ != nullptr) 109 << "Cannot read file: " << input_file_name << "\n"; 110 111 scoped_refptr<I420BufferInterface> buffer( 112 test::ReadI420Buffer(width_, height_, source_file_)); 113 114 orig_frame_ = std::make_unique<VideoFrame>(VideoFrame::Builder() 115 .set_video_frame_buffer(buffer) 116 .set_rotation(kVideoRotation_0) 117 .set_timestamp_us(0) 118 .build()); 119 } 120 121 void TestLibYuv::TearDown() { 122 if (source_file_ != nullptr) { 123 ASSERT_EQ(0, fclose(source_file_)); 124 } 125 source_file_ = nullptr; 126 } 127 128 TEST_F(TestLibYuv, ConvertTest) { 129 // Reading YUV frame - testing on the first frame of the foreman sequence 130 int j = 0; 131 std::string output_file_name = 132 test::OutputPath() + "LibYuvTest_conversion.yuv"; 133 FILE* output_file = fopen(output_file_name.c_str(), "wb"); 134 ASSERT_TRUE(output_file != nullptr); 135 136 double psnr = 0.0; 137 138 scoped_refptr<I420Buffer> res_i420_buffer = 139 I420Buffer::Create(width_, height_); 140 141 printf("\nConvert #%d I420 <-> I420 \n", j); 142 std::unique_ptr<uint8_t[]> out_i420_buffer(new uint8_t[frame_length_]); 143 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kI420, 0, 144 out_i420_buffer.get())); 145 int y_size = width_ * height_; 146 int u_size = res_i420_buffer->ChromaWidth() * res_i420_buffer->ChromaHeight(); 147 int ret = libyuv::I420Copy( 148 out_i420_buffer.get(), width_, out_i420_buffer.get() + y_size, 149 width_ >> 1, out_i420_buffer.get() + y_size + u_size, width_ >> 1, 150 res_i420_buffer->MutableDataY(), res_i420_buffer->StrideY(), 151 res_i420_buffer->MutableDataU(), res_i420_buffer->StrideU(), 152 res_i420_buffer->MutableDataV(), res_i420_buffer->StrideV(), width_, 153 height_); 154 EXPECT_EQ(0, ret); 155 156 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) { 157 return; 158 } 159 psnr = 160 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer); 161 EXPECT_EQ(48.0, psnr); 162 j++; 163 164 printf("\nConvert #%d I420 <-> RGB24\n", j); 165 std::unique_ptr<uint8_t[]> res_rgb_buffer2(new uint8_t[width_ * height_ * 3]); 166 // Align the stride values for the output frame. 167 int stride_y = 0; 168 int stride_uv = 0; 169 Calc16ByteAlignedStride(width_, &stride_y, &stride_uv); 170 res_i420_buffer = 171 I420Buffer::Create(width_, height_, stride_y, stride_uv, stride_uv); 172 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kRGB24, 0, 173 res_rgb_buffer2.get())); 174 175 ret = libyuv::ConvertToI420( 176 res_rgb_buffer2.get(), 0, res_i420_buffer->MutableDataY(), 177 res_i420_buffer->StrideY(), res_i420_buffer->MutableDataU(), 178 res_i420_buffer->StrideU(), res_i420_buffer->MutableDataV(), 179 res_i420_buffer->StrideV(), 0, 0, width_, height_, 180 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0, 181 ConvertVideoType(VideoType::kRGB24)); 182 183 EXPECT_EQ(0, ret); 184 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) { 185 return; 186 } 187 psnr = 188 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer); 189 190 // Optimization Speed- quality trade-off => 45 dB only (platform dependant). 191 EXPECT_GT(ceil(psnr), 44); 192 j++; 193 194 printf("\nConvert #%d I420 <-> UYVY\n", j); 195 std::unique_ptr<uint8_t[]> out_uyvy_buffer(new uint8_t[width_ * height_ * 2]); 196 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kUYVY, 0, 197 out_uyvy_buffer.get())); 198 199 ret = libyuv::ConvertToI420( 200 out_uyvy_buffer.get(), 0, res_i420_buffer->MutableDataY(), 201 res_i420_buffer->StrideY(), res_i420_buffer->MutableDataU(), 202 res_i420_buffer->StrideU(), res_i420_buffer->MutableDataV(), 203 res_i420_buffer->StrideV(), 0, 0, width_, height_, 204 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0, 205 ConvertVideoType(VideoType::kUYVY)); 206 207 EXPECT_EQ(0, ret); 208 psnr = 209 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer); 210 EXPECT_EQ(48.0, psnr); 211 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) { 212 return; 213 } 214 j++; 215 216 printf("\nConvert #%d I420 <-> YUY2\n", j); 217 std::unique_ptr<uint8_t[]> out_yuy2_buffer(new uint8_t[width_ * height_ * 2]); 218 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kYUY2, 0, 219 out_yuy2_buffer.get())); 220 221 ret = libyuv::ConvertToI420( 222 out_yuy2_buffer.get(), 0, res_i420_buffer->MutableDataY(), 223 res_i420_buffer->StrideY(), res_i420_buffer->MutableDataU(), 224 res_i420_buffer->StrideU(), res_i420_buffer->MutableDataV(), 225 res_i420_buffer->StrideV(), 0, 0, width_, height_, 226 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0, 227 ConvertVideoType(VideoType::kYUY2)); 228 229 EXPECT_EQ(0, ret); 230 231 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) { 232 return; 233 } 234 235 psnr = 236 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer); 237 EXPECT_EQ(48.0, psnr); 238 239 printf("\nConvert #%d I420 <-> RGB565\n", j); 240 std::unique_ptr<uint8_t[]> out_rgb565_buffer( 241 new uint8_t[width_ * height_ * 2]); 242 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kRGB565, 0, 243 out_rgb565_buffer.get())); 244 245 ret = libyuv::ConvertToI420( 246 out_rgb565_buffer.get(), 0, res_i420_buffer->MutableDataY(), 247 res_i420_buffer->StrideY(), res_i420_buffer->MutableDataU(), 248 res_i420_buffer->StrideU(), res_i420_buffer->MutableDataV(), 249 res_i420_buffer->StrideV(), 0, 0, width_, height_, 250 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0, 251 ConvertVideoType(VideoType::kRGB565)); 252 253 EXPECT_EQ(0, ret); 254 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) { 255 return; 256 } 257 j++; 258 259 psnr = 260 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer); 261 // TODO(leozwang) Investigate the right psnr should be set for I420ToRGB565, 262 // Another example is I420ToRGB24, the psnr is 44 263 // TODO(mikhal): Add psnr for RGB565, 1555, 4444, convert to ARGB. 264 EXPECT_GT(ceil(psnr), 40); 265 266 printf("\nConvert #%d I420 <-> ARGB8888\n", j); 267 std::unique_ptr<uint8_t[]> out_argb8888_buffer( 268 new uint8_t[width_ * height_ * 4]); 269 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kARGB, 0, 270 out_argb8888_buffer.get())); 271 272 ret = libyuv::ConvertToI420( 273 out_argb8888_buffer.get(), 0, res_i420_buffer->MutableDataY(), 274 res_i420_buffer->StrideY(), res_i420_buffer->MutableDataU(), 275 res_i420_buffer->StrideU(), res_i420_buffer->MutableDataV(), 276 res_i420_buffer->StrideV(), 0, 0, width_, height_, 277 res_i420_buffer->width(), res_i420_buffer->height(), libyuv::kRotate0, 278 ConvertVideoType(VideoType::kARGB)); 279 280 EXPECT_EQ(0, ret); 281 282 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) { 283 return; 284 } 285 286 psnr = 287 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer); 288 // TODO(leozwang) Investigate the right psnr should be set for 289 // I420ToARGB8888, 290 EXPECT_GT(ceil(psnr), 42); 291 292 ASSERT_EQ(0, fclose(output_file)); 293 } 294 295 TEST_F(TestLibYuv, ConvertAlignedFrame) { 296 // Reading YUV frame - testing on the first frame of the foreman sequence 297 std::string output_file_name = 298 test::OutputPath() + "LibYuvTest_conversion.yuv"; 299 FILE* output_file = fopen(output_file_name.c_str(), "wb"); 300 ASSERT_TRUE(output_file != nullptr); 301 302 double psnr = 0.0; 303 304 int stride_y = 0; 305 int stride_uv = 0; 306 Calc16ByteAlignedStride(width_, &stride_y, &stride_uv); 307 308 scoped_refptr<I420Buffer> res_i420_buffer = 309 I420Buffer::Create(width_, height_, stride_y, stride_uv, stride_uv); 310 std::unique_ptr<uint8_t[]> out_i420_buffer(new uint8_t[frame_length_]); 311 EXPECT_EQ(0, ConvertFromI420(*orig_frame_, VideoType::kI420, 0, 312 out_i420_buffer.get())); 313 int y_size = width_ * height_; 314 int u_size = res_i420_buffer->ChromaWidth() * res_i420_buffer->ChromaHeight(); 315 int ret = libyuv::I420Copy( 316 out_i420_buffer.get(), width_, out_i420_buffer.get() + y_size, 317 width_ >> 1, out_i420_buffer.get() + y_size + u_size, width_ >> 1, 318 res_i420_buffer->MutableDataY(), res_i420_buffer->StrideY(), 319 res_i420_buffer->MutableDataU(), res_i420_buffer->StrideU(), 320 res_i420_buffer->MutableDataV(), res_i420_buffer->StrideV(), width_, 321 height_); 322 323 EXPECT_EQ(0, ret); 324 325 if (PrintVideoFrame(*res_i420_buffer, output_file) < 0) { 326 return; 327 } 328 psnr = 329 I420PSNR(*orig_frame_->video_frame_buffer()->GetI420(), *res_i420_buffer); 330 EXPECT_EQ(48.0, psnr); 331 } 332 333 static uint8_t Average(int a, int b, int c, int d) { 334 return (a + b + c + d + 2) / 4; 335 } 336 337 TEST_F(TestLibYuv, NV12Scale2x2to2x2) { 338 const std::vector<uint8_t> src_y = {0, 1, 2, 3}; 339 const std::vector<uint8_t> src_uv = {0, 1}; 340 std::vector<uint8_t> dst_y(4); 341 std::vector<uint8_t> dst_uv(2); 342 343 uint8_t* tmp_buffer = nullptr; 344 345 NV12Scale(tmp_buffer, src_y.data(), 2, src_uv.data(), 2, 2, 2, dst_y.data(), 346 2, dst_uv.data(), 2, 2, 2); 347 348 EXPECT_THAT(dst_y, ::testing::ContainerEq(src_y)); 349 EXPECT_THAT(dst_uv, ::testing::ContainerEq(src_uv)); 350 } 351 352 TEST_F(TestLibYuv, NV12Scale4x4to2x2) { 353 const uint8_t src_y[] = {0, 1, 2, 3, 4, 5, 6, 7, 354 8, 9, 10, 11, 12, 13, 14, 15}; 355 const uint8_t src_uv[] = {0, 1, 2, 3, 4, 5, 6, 7}; 356 std::vector<uint8_t> dst_y(4); 357 std::vector<uint8_t> dst_uv(2); 358 359 std::vector<uint8_t> tmp_buffer; 360 const int src_chroma_width = (4 + 1) / 2; 361 const int src_chroma_height = (4 + 1) / 2; 362 const int dst_chroma_width = (2 + 1) / 2; 363 const int dst_chroma_height = (2 + 1) / 2; 364 tmp_buffer.resize(src_chroma_width * src_chroma_height * 2 + 365 dst_chroma_width * dst_chroma_height * 2); 366 tmp_buffer.shrink_to_fit(); 367 368 NV12Scale(tmp_buffer.data(), src_y, 4, src_uv, 4, 4, 4, dst_y.data(), 2, 369 dst_uv.data(), 2, 2, 2); 370 371 EXPECT_THAT(dst_y, ::testing::ElementsAre( 372 Average(0, 1, 4, 5), Average(2, 3, 6, 7), 373 Average(8, 9, 12, 13), Average(10, 11, 14, 15))); 374 EXPECT_THAT(dst_uv, 375 ::testing::ElementsAre(Average(0, 2, 4, 6), Average(1, 3, 5, 7))); 376 } 377 378 TEST(I420WeightedPSNRTest, SmokeTest) { 379 uint8_t ref_y[] = {0, 0, 0, 0}; 380 uint8_t ref_uv[] = {0}; 381 scoped_refptr<I420Buffer> ref_buffer = 382 I420Buffer::Copy(/*width=*/2, /*height=*/2, ref_y, /*stride_y=*/2, ref_uv, 383 /*stride_u=*/1, ref_uv, /*stride_v=*/1); 384 385 uint8_t test_y[] = {1, 1, 1, 1}; 386 uint8_t test_uv[] = {2}; 387 scoped_refptr<I420Buffer> test_buffer = I420Buffer::Copy( 388 /*width=*/2, /*height=*/2, test_y, /*stride_y=*/2, test_uv, 389 /*stride_u=*/1, test_uv, /*stride_v=*/1); 390 391 auto psnr = [](double mse) { return 10.0 * log10(255.0 * 255.0 / mse); }; 392 EXPECT_NEAR(I420WeightedPSNR(*ref_buffer, *test_buffer), 393 (6.0 * psnr(1.0) + psnr(4.0) + psnr(4.0)) / 8.0, 394 /*abs_error=*/0.001); 395 } 396 397 #if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) 398 // Check that we catch int overflow if invalid dimensions get passed to 399 // `I420Buffer::Create()`. 400 TEST_F(TestLibYuv, I420DimensionsTooLarge) { 401 // Dimensions large enough to cause overflow. 402 constexpr int kWidth = 0xFFFF; 403 constexpr int kHeight = 0xAAB0; 404 // Sanity check for this test. This calculation, which is part of what 405 // `I420Buffer::Create()` will do, should cause an `int` overflow. 406 static_assert( 407 (int64_t{kWidth} * int64_t{kHeight}) > std::numeric_limits<int>::max(), 408 ""); 409 410 EXPECT_DEATH(I010Buffer::Create(kWidth, kHeight), 411 "IsValueInRangeForNumericType"); 412 EXPECT_DEATH(I210Buffer::Create(kWidth, kHeight), 413 "IsValueInRangeForNumericType"); 414 415 int stride_uv = (kWidth + 1) / 2; 416 EXPECT_DEATH(I410Buffer::Create(kWidth, kHeight, /*stride_y=*/kWidth, 417 stride_uv, stride_uv), 418 "IsValueInRangeForNumericType"); 419 EXPECT_DEATH(I420Buffer::Create(kWidth, kHeight, /*stride_y=*/kWidth, 420 stride_uv, stride_uv), 421 "IsValueInRangeForNumericType"); 422 EXPECT_DEATH(I422Buffer::Create(kWidth, kHeight, /*stride_y=*/kWidth, 423 stride_uv, stride_uv), 424 "IsValueInRangeForNumericType"); 425 EXPECT_DEATH(I444Buffer::Create(kWidth, kHeight, /*stride_y=*/kWidth, 426 stride_uv, stride_uv), 427 "IsValueInRangeForNumericType"); 428 EXPECT_DEATH( 429 NV12Buffer::Create(kWidth, kHeight, /*stride_y=*/kWidth, stride_uv), 430 "IsValueInRangeForNumericType"); 431 } 432 433 template <typename T> 434 void TestInvalidDimensions5Params() { 435 EXPECT_DEATH(T::Create(-11, 1, /*stride_y=*/1, 436 /*stride_u=*/1, 437 /*stride_v=*/1), 438 "> 0"); 439 EXPECT_DEATH(T::Create(1, -11, /*stride_y=*/1, 440 /*stride_u=*/1, 441 /*stride_v=*/1), 442 "> 0"); 443 EXPECT_DEATH(T::Create(1, 1, /*stride_y=*/-12, 444 /*stride_u=*/1, 445 /*stride_v=*/1), 446 ">= width"); 447 EXPECT_DEATH(T::Create(1, 1, /*stride_y=*/1, 448 /*stride_u=*/-12, 449 /*stride_v=*/1), 450 "> 0"); 451 EXPECT_DEATH(T::Create(1, 1, /*stride_y=*/1, 452 /*stride_u=*/1, 453 /*stride_v=*/-12), 454 "> 0"); 455 } 456 457 template <typename T> 458 void TestInvalidDimensions4Params() { 459 EXPECT_DEATH(T::Create(-11, 1, /*stride_y=*/1, 460 /*stride_uv=*/1), 461 "> 0"); 462 EXPECT_DEATH(T::Create(1, -11, /*stride_y=*/1, 463 /*stride_uv=*/1), 464 "> 0"); 465 EXPECT_DEATH(T::Create(1, 1, /*stride_y=*/-12, 466 /*stride_uv=*/1), 467 ">= width"); 468 EXPECT_DEATH(T::Create(1, 1, /*stride_y=*/1, 469 /*stride_uv=*/-12), 470 "> 0"); 471 } 472 473 template <typename T> 474 void TestInvalidDimensions2Param() { 475 EXPECT_DEATH(T::Create(-11, 1), "> 0"); 476 EXPECT_DEATH(T::Create(1, -11), "> 0"); 477 } 478 479 TEST_F(TestLibYuv, I420InvalidDimensions) { 480 // Only width and height provided to `Create()`. 481 TestInvalidDimensions2Param<I010Buffer>(); 482 TestInvalidDimensions2Param<I210Buffer>(); 483 // `Create() is provided with width, height, y, u, v. 484 TestInvalidDimensions5Params<I410Buffer>(); 485 TestInvalidDimensions5Params<I420Buffer>(); 486 TestInvalidDimensions5Params<I422Buffer>(); 487 TestInvalidDimensions5Params<I444Buffer>(); 488 // `Create() is provided with width, height, y, u_and_v. 489 TestInvalidDimensions4Params<NV12Buffer>(); 490 } 491 492 #endif // GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) 493 494 } // namespace webrtc