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 <math.h>
12 #include <string.h>
13 
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "webrtc/base/scoped_ptr.h"
16 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
17 #include "webrtc/system_wrappers/include/tick_util.h"
18 #include "webrtc/test/testsupport/fileutils.h"
19 #include "webrtc/video_frame.h"
20 
21 namespace webrtc {
22 
PrintBuffer(const uint8_t * buffer,int width,int height,int stride)23 int PrintBuffer(const uint8_t* buffer, int width, int height, int stride) {
24   if (buffer == NULL)
25     return -1;
26   int k;
27   const uint8_t* tmp_buffer = buffer;
28   for (int i = 0; i < height; i++) {
29     k = 0;
30     for (int j = 0; j < width; j++) {
31       printf("%d ", tmp_buffer[k++]);
32     }
33     tmp_buffer += stride;
34     printf(" \n");
35   }
36   printf(" \n");
37   return 0;
38 }
39 
PrintFrame(const VideoFrame * frame,const char * str)40 int PrintFrame(const VideoFrame* frame, const char* str) {
41   if (frame == NULL)
42      return -1;
43   printf("%s %dx%d \n", str, frame->width(), frame->height());
44 
45   int ret = 0;
46   for (int plane_num = 0; plane_num < kNumOfPlanes; ++plane_num) {
47     PlaneType plane_type = static_cast<PlaneType>(plane_num);
48     int width = (plane_num ? (frame->width() + 1) / 2 : frame->width());
49     int height = (plane_num ? (frame->height() + 1) / 2 : frame->height());
50     ret += PrintBuffer(frame->buffer(plane_type), width, height,
51                        frame->stride(plane_type));
52   }
53   return ret;
54 }
55 
56 
57 // Create an image from on a YUV frame. Every plane value starts with a start
58 // value, and will be set to increasing values.
CreateImage(VideoFrame * frame,int plane_offset[kNumOfPlanes])59 void CreateImage(VideoFrame* frame, int plane_offset[kNumOfPlanes]) {
60   if (frame == NULL)
61     return;
62   for (int plane_num = 0; plane_num < kNumOfPlanes; ++plane_num) {
63     int width = (plane_num != kYPlane ? (frame->width() + 1) / 2 :
64       frame->width());
65     int height = (plane_num != kYPlane ? (frame->height() + 1) / 2 :
66       frame->height());
67     PlaneType plane_type = static_cast<PlaneType>(plane_num);
68     uint8_t *data = frame->buffer(plane_type);
69     for (int i = 0; i < height; i++) {
70       for (int j = 0; j < width; j++) {
71         data[j] = static_cast<uint8_t>(i + plane_offset[plane_num] + j);
72       }
73       data += frame->stride(plane_type);
74     }
75   }
76 }
77 
78 class TestLibYuv : public ::testing::Test {
79  protected:
80   TestLibYuv();
81   virtual void SetUp();
82   virtual void TearDown();
83 
84   FILE* source_file_;
85   VideoFrame orig_frame_;
86   rtc::scoped_ptr<uint8_t[]> orig_buffer_;
87   const int width_;
88   const int height_;
89   const int size_y_;
90   const int size_uv_;
91   const size_t frame_length_;
92 };
93 
TestLibYuv()94 TestLibYuv::TestLibYuv()
95     : source_file_(NULL),
96       orig_frame_(),
97       width_(352),
98       height_(288),
99       size_y_(width_ * height_),
100       size_uv_(((width_ + 1) / 2) * ((height_ + 1) / 2)),
101       frame_length_(CalcBufferSize(kI420, 352, 288)) {
102   orig_buffer_.reset(new uint8_t[frame_length_]);
103 }
104 
SetUp()105 void TestLibYuv::SetUp() {
106   const std::string input_file_name = webrtc::test::ProjectRootPath() +
107                                       "resources/foreman_cif.yuv";
108   source_file_  = fopen(input_file_name.c_str(), "rb");
109   ASSERT_TRUE(source_file_ != NULL) << "Cannot read file: "<<
110                                        input_file_name << "\n";
111 
112   EXPECT_EQ(frame_length_,
113             fread(orig_buffer_.get(), 1, frame_length_, source_file_));
114   EXPECT_EQ(0, orig_frame_.CreateFrame(orig_buffer_.get(),
115                                        orig_buffer_.get() + size_y_,
116                                        orig_buffer_.get() +
117                                        size_y_ + size_uv_,
118                                        width_, height_,
119                                        width_, (width_ + 1) / 2,
120                                        (width_ + 1) / 2));
121 }
122 
TearDown()123 void TestLibYuv::TearDown() {
124   if (source_file_ != NULL) {
125     ASSERT_EQ(0, fclose(source_file_));
126   }
127   source_file_ = NULL;
128 }
129 
TEST_F(TestLibYuv,ConvertSanityTest)130 TEST_F(TestLibYuv, ConvertSanityTest) {
131   // TODO(mikhal)
132 }
133 
TEST_F(TestLibYuv,ConvertTest)134 TEST_F(TestLibYuv, ConvertTest) {
135   // Reading YUV frame - testing on the first frame of the foreman sequence
136   int j = 0;
137   std::string output_file_name = webrtc::test::OutputPath() +
138                                  "LibYuvTest_conversion.yuv";
139   FILE*  output_file = fopen(output_file_name.c_str(), "wb");
140   ASSERT_TRUE(output_file != NULL);
141 
142   double psnr = 0.0;
143 
144   VideoFrame res_i420_frame;
145   EXPECT_EQ(0, res_i420_frame.CreateEmptyFrame(width_, height_, width_,
146                                                (width_ + 1) / 2,
147                                                (width_ + 1) / 2));
148   printf("\nConvert #%d I420 <-> I420 \n", j);
149   rtc::scoped_ptr<uint8_t[]> out_i420_buffer(new uint8_t[frame_length_]);
150   EXPECT_EQ(0, ConvertFromI420(orig_frame_, kI420, 0,
151                                out_i420_buffer.get()));
152   EXPECT_EQ(0, ConvertToI420(kI420, out_i420_buffer.get(), 0, 0, width_,
153                              height_, 0, kVideoRotation_0, &res_i420_frame));
154 
155   if (PrintVideoFrame(res_i420_frame, output_file) < 0) {
156     return;
157   }
158   psnr = I420PSNR(&orig_frame_, &res_i420_frame);
159   EXPECT_EQ(48.0, psnr);
160   j++;
161 
162   printf("\nConvert #%d I420 <-> RGB24\n", j);
163   rtc::scoped_ptr<uint8_t[]> res_rgb_buffer2(new uint8_t[width_ * height_ * 3]);
164   // Align the stride values for the output frame.
165   int stride_y = 0;
166   int stride_uv = 0;
167   Calc16ByteAlignedStride(width_, &stride_y, &stride_uv);
168   res_i420_frame.CreateEmptyFrame(width_, height_, stride_y,
169                                   stride_uv, stride_uv);
170   EXPECT_EQ(0, ConvertFromI420(orig_frame_, kRGB24, 0, res_rgb_buffer2.get()));
171 
172   EXPECT_EQ(0, ConvertToI420(kRGB24, res_rgb_buffer2.get(), 0, 0, width_,
173                              height_, 0, kVideoRotation_0, &res_i420_frame));
174 
175   if (PrintVideoFrame(res_i420_frame, output_file) < 0) {
176     return;
177   }
178   psnr = I420PSNR(&orig_frame_, &res_i420_frame);
179 
180   // Optimization Speed- quality trade-off => 45 dB only (platform dependant).
181   EXPECT_GT(ceil(psnr), 44);
182   j++;
183 
184   printf("\nConvert #%d I420 <-> UYVY\n", j);
185   rtc::scoped_ptr<uint8_t[]> out_uyvy_buffer(new uint8_t[width_ * height_ * 2]);
186   EXPECT_EQ(0, ConvertFromI420(orig_frame_,  kUYVY, 0, out_uyvy_buffer.get()));
187   EXPECT_EQ(0, ConvertToI420(kUYVY, out_uyvy_buffer.get(), 0, 0, width_,
188                              height_, 0, kVideoRotation_0, &res_i420_frame));
189   psnr = I420PSNR(&orig_frame_, &res_i420_frame);
190   EXPECT_EQ(48.0, psnr);
191   if (PrintVideoFrame(res_i420_frame, output_file) < 0) {
192     return;
193   }
194   j++;
195 
196   printf("\nConvert #%d I420 <-> YV12\n", j);
197   rtc::scoped_ptr<uint8_t[]> outYV120Buffer(new uint8_t[frame_length_]);
198   rtc::scoped_ptr<uint8_t[]> res_i420_buffer(new uint8_t[frame_length_]);
199   VideoFrame yv12_frame;
200   EXPECT_EQ(0, ConvertFromI420(orig_frame_, kYV12, 0, outYV120Buffer.get()));
201   yv12_frame.CreateFrame(outYV120Buffer.get(),
202                          outYV120Buffer.get() + size_y_,
203                          outYV120Buffer.get() + size_y_ + size_uv_,
204                          width_, height_,
205                          width_, (width_ + 1) / 2, (width_ + 1) / 2);
206   EXPECT_EQ(0, ConvertFromYV12(yv12_frame, kI420, 0, res_i420_buffer.get()));
207   if (fwrite(res_i420_buffer.get(), 1, frame_length_, output_file) !=
208       frame_length_) {
209     return;
210   }
211 
212   ConvertToI420(kI420, res_i420_buffer.get(), 0, 0, width_, height_, 0,
213                 kVideoRotation_0, &res_i420_frame);
214   psnr = I420PSNR(&orig_frame_, &res_i420_frame);
215   EXPECT_EQ(48.0, psnr);
216   j++;
217 
218   printf("\nConvert #%d I420 <-> YUY2\n", j);
219   rtc::scoped_ptr<uint8_t[]> out_yuy2_buffer(new uint8_t[width_ * height_ * 2]);
220   EXPECT_EQ(0, ConvertFromI420(orig_frame_,  kYUY2, 0, out_yuy2_buffer.get()));
221 
222   EXPECT_EQ(0, ConvertToI420(kYUY2, out_yuy2_buffer.get(), 0, 0, width_,
223                              height_, 0, kVideoRotation_0, &res_i420_frame));
224 
225   if (PrintVideoFrame(res_i420_frame, output_file) < 0) {
226     return;
227   }
228 
229   psnr = I420PSNR(&orig_frame_, &res_i420_frame);
230   EXPECT_EQ(48.0, psnr);
231   printf("\nConvert #%d I420 <-> RGB565\n", j);
232   rtc::scoped_ptr<uint8_t[]> out_rgb565_buffer(
233       new uint8_t[width_ * height_ * 2]);
234   EXPECT_EQ(0, ConvertFromI420(orig_frame_, kRGB565, 0,
235                                out_rgb565_buffer.get()));
236 
237   EXPECT_EQ(0, ConvertToI420(kRGB565, out_rgb565_buffer.get(), 0, 0, width_,
238                              height_, 0, kVideoRotation_0, &res_i420_frame));
239 
240   if (PrintVideoFrame(res_i420_frame, output_file) < 0) {
241     return;
242   }
243   j++;
244 
245   psnr = I420PSNR(&orig_frame_, &res_i420_frame);
246   // TODO(leozwang) Investigate the right psnr should be set for I420ToRGB565,
247   // Another example is I420ToRGB24, the psnr is 44
248   // TODO(mikhal): Add psnr for RGB565, 1555, 4444, convert to ARGB.
249   EXPECT_GT(ceil(psnr), 40);
250 
251   printf("\nConvert #%d I420 <-> ARGB8888\n", j);
252   rtc::scoped_ptr<uint8_t[]> out_argb8888_buffer(
253       new uint8_t[width_ * height_ * 4]);
254   EXPECT_EQ(0, ConvertFromI420(orig_frame_, kARGB, 0,
255                                out_argb8888_buffer.get()));
256 
257   EXPECT_EQ(0, ConvertToI420(kARGB, out_argb8888_buffer.get(), 0, 0, width_,
258                              height_, 0, kVideoRotation_0, &res_i420_frame));
259 
260   if (PrintVideoFrame(res_i420_frame, output_file) < 0) {
261     return;
262   }
263 
264   psnr = I420PSNR(&orig_frame_, &res_i420_frame);
265   // TODO(leozwang) Investigate the right psnr should be set for I420ToARGB8888,
266   EXPECT_GT(ceil(psnr), 42);
267 
268   ASSERT_EQ(0, fclose(output_file));
269 }
270 
TEST_F(TestLibYuv,ConvertAlignedFrame)271 TEST_F(TestLibYuv, ConvertAlignedFrame) {
272   // Reading YUV frame - testing on the first frame of the foreman sequence
273   std::string output_file_name = webrtc::test::OutputPath() +
274                                  "LibYuvTest_conversion.yuv";
275   FILE*  output_file = fopen(output_file_name.c_str(), "wb");
276   ASSERT_TRUE(output_file != NULL);
277 
278   double psnr = 0.0;
279 
280   VideoFrame res_i420_frame;
281   int stride_y = 0;
282   int stride_uv = 0;
283   Calc16ByteAlignedStride(width_, &stride_y, &stride_uv);
284   EXPECT_EQ(0, res_i420_frame.CreateEmptyFrame(width_, height_,
285                                                stride_y, stride_uv, stride_uv));
286   rtc::scoped_ptr<uint8_t[]> out_i420_buffer(new uint8_t[frame_length_]);
287   EXPECT_EQ(0, ConvertFromI420(orig_frame_, kI420, 0,
288                                out_i420_buffer.get()));
289   EXPECT_EQ(0, ConvertToI420(kI420, out_i420_buffer.get(), 0, 0, width_,
290                              height_, 0, kVideoRotation_0, &res_i420_frame));
291 
292   if (PrintVideoFrame(res_i420_frame, output_file) < 0) {
293     return;
294   }
295   psnr = I420PSNR(&orig_frame_, &res_i420_frame);
296   EXPECT_EQ(48.0, psnr);
297 }
298 
299 
TEST_F(TestLibYuv,RotateTest)300 TEST_F(TestLibYuv, RotateTest) {
301   // Use ConvertToI420 for multiple roatations - see that nothing breaks, all
302   // memory is properly allocated and end result is equal to the starting point.
303   VideoFrame rotated_res_i420_frame;
304   int rotated_width = height_;
305   int rotated_height = width_;
306   int stride_y;
307   int stride_uv;
308   Calc16ByteAlignedStride(rotated_width, &stride_y, &stride_uv);
309   EXPECT_EQ(0, rotated_res_i420_frame.CreateEmptyFrame(rotated_width,
310                                                        rotated_height,
311                                                        stride_y,
312                                                        stride_uv,
313                                                        stride_uv));
314   EXPECT_EQ(0, ConvertToI420(kI420, orig_buffer_.get(), 0, 0, width_, height_,
315                              0, kVideoRotation_90, &rotated_res_i420_frame));
316   EXPECT_EQ(0, ConvertToI420(kI420, orig_buffer_.get(), 0, 0, width_, height_,
317                              0, kVideoRotation_270, &rotated_res_i420_frame));
318   EXPECT_EQ(0, rotated_res_i420_frame.CreateEmptyFrame(width_, height_,
319                                                        width_, (width_ + 1) / 2,
320                                                        (width_ + 1) / 2));
321   EXPECT_EQ(0, ConvertToI420(kI420, orig_buffer_.get(), 0, 0, width_, height_,
322                              0, kVideoRotation_180, &rotated_res_i420_frame));
323 }
324 
TEST_F(TestLibYuv,alignment)325 TEST_F(TestLibYuv, alignment) {
326   int value = 0x3FF;  // 1023
327   EXPECT_EQ(0x400, AlignInt(value, 128));  // Low 7 bits are zero.
328   EXPECT_EQ(0x400, AlignInt(value, 64));  // Low 6 bits are zero.
329   EXPECT_EQ(0x400, AlignInt(value, 32));  // Low 5 bits are zero.
330 }
331 
TEST_F(TestLibYuv,StrideAlignment)332 TEST_F(TestLibYuv, StrideAlignment) {
333   int stride_y = 0;
334   int stride_uv = 0;
335   int width = 52;
336   Calc16ByteAlignedStride(width, &stride_y, &stride_uv);
337   EXPECT_EQ(64, stride_y);
338   EXPECT_EQ(32, stride_uv);
339   width = 128;
340   Calc16ByteAlignedStride(width, &stride_y, &stride_uv);
341   EXPECT_EQ(128, stride_y);
342   EXPECT_EQ(64, stride_uv);
343   width = 127;
344   Calc16ByteAlignedStride(width, &stride_y, &stride_uv);
345   EXPECT_EQ(128, stride_y);
346   EXPECT_EQ(64, stride_uv);
347 }
348 
349 }  // namespace webrtc
350