• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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