OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (c) 2004 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 #ifndef WEBRTC_MEDIA_BASE_VIDEOFRAME_UNITTEST_H_ | |
12 #define WEBRTC_MEDIA_BASE_VIDEOFRAME_UNITTEST_H_ | |
13 | |
14 #include <algorithm> | |
15 #include <memory> | |
16 #include <string> | |
17 | |
18 #include "libyuv/convert.h" | |
19 #include "libyuv/convert_from.h" | |
20 #include "libyuv/planar_functions.h" | |
21 #include "libyuv/rotate.h" | |
22 #include "webrtc/base/gunit.h" | |
23 #include "webrtc/base/pathutils.h" | |
24 #include "webrtc/base/stream.h" | |
25 #include "webrtc/base/stringutils.h" | |
26 #include "webrtc/common_video/rotation.h" | |
27 #include "webrtc/media/base/testutils.h" | |
28 #include "webrtc/media/base/videocommon.h" | |
29 #include "webrtc/media/base/videoframe.h" | |
30 #include "webrtc/test/testsupport/fileutils.h" | |
31 | |
32 #if defined(_MSC_VER) | |
33 #define ALIGN16(var) __declspec(align(16)) var | |
34 #else | |
35 #define ALIGN16(var) var __attribute__((aligned(16))) | |
36 #endif | |
37 | |
38 #define kImageFilename "media/faces.1280x720_P420" | |
39 #define kYuvExtension "yuv" | |
40 #define kJpeg420Filename "media/faces_I420" | |
41 #define kJpeg422Filename "media/faces_I422" | |
42 #define kJpeg444Filename "media/faces_I444" | |
43 #define kJpeg411Filename "media/faces_I411" | |
44 #define kJpeg400Filename "media/faces_I400" | |
45 #define kJpegExtension "jpg" | |
46 | |
47 // Generic test class for testing various video frame implementations. | |
48 template <class T> | |
49 class VideoFrameTest : public testing::Test { | |
50 public: | |
51 VideoFrameTest() : repeat_(1) {} | |
52 | |
53 protected: | |
54 static const int kWidth = 1280; | |
55 static const int kHeight = 720; | |
56 static const int kAlignment = 16; | |
57 static const int kMinWidthAll = 1; // Constants for ConstructYUY2AllSizes. | |
58 static const int kMinHeightAll = 1; | |
59 static const int kMaxWidthAll = 17; | |
60 static const int kMaxHeightAll = 23; | |
61 | |
62 // Load a video frame from disk. | |
63 bool LoadFrameNoRepeat(T* frame) { | |
64 int save_repeat = repeat_; // This LoadFrame disables repeat. | |
65 repeat_ = 1; | |
66 bool success = LoadFrame(LoadSample(kImageFilename, kYuvExtension).get(), | |
67 cricket::FOURCC_I420, | |
68 kWidth, kHeight, frame); | |
69 repeat_ = save_repeat; | |
70 return success; | |
71 } | |
72 | |
73 bool LoadFrame(const std::string& filename, | |
74 uint32_t format, | |
75 int32_t width, | |
76 int32_t height, | |
77 T* frame) { | |
78 return LoadFrame(filename, format, width, height, width, abs(height), | |
79 webrtc::kVideoRotation_0, frame); | |
80 } | |
81 bool LoadFrame(const std::string& filename, | |
82 uint32_t format, | |
83 int32_t width, | |
84 int32_t height, | |
85 int dw, | |
86 int dh, | |
87 webrtc::VideoRotation rotation, | |
88 T* frame) { | |
89 std::unique_ptr<rtc::MemoryStream> ms(LoadSample(filename)); | |
90 return LoadFrame(ms.get(), format, width, height, dw, dh, rotation, frame); | |
91 } | |
92 // Load a video frame from a memory stream. | |
93 bool LoadFrame(rtc::MemoryStream* ms, | |
94 uint32_t format, | |
95 int32_t width, | |
96 int32_t height, | |
97 T* frame) { | |
98 return LoadFrame(ms, format, width, height, width, abs(height), | |
99 webrtc::kVideoRotation_0, frame); | |
100 } | |
101 bool LoadFrame(rtc::MemoryStream* ms, | |
102 uint32_t format, | |
103 int32_t width, | |
104 int32_t height, | |
105 int dw, | |
106 int dh, | |
107 webrtc::VideoRotation rotation, | |
108 T* frame) { | |
109 if (!ms) { | |
110 return false; | |
111 } | |
112 size_t data_size; | |
113 bool ret = ms->GetSize(&data_size); | |
114 EXPECT_TRUE(ret); | |
115 if (ret) { | |
116 ret = LoadFrame(reinterpret_cast<uint8_t*>(ms->GetBuffer()), data_size, | |
117 format, width, height, dw, dh, rotation, frame); | |
118 } | |
119 return ret; | |
120 } | |
121 // Load a frame from a raw buffer. | |
122 bool LoadFrame(uint8_t* sample, | |
123 size_t sample_size, | |
124 uint32_t format, | |
125 int32_t width, | |
126 int32_t height, | |
127 T* frame) { | |
128 return LoadFrame(sample, sample_size, format, width, height, width, | |
129 abs(height), webrtc::kVideoRotation_0, frame); | |
130 } | |
131 bool LoadFrame(uint8_t* sample, | |
132 size_t sample_size, | |
133 uint32_t format, | |
134 int32_t width, | |
135 int32_t height, | |
136 int dw, | |
137 int dh, | |
138 webrtc::VideoRotation rotation, | |
139 T* frame) { | |
140 bool ret = false; | |
141 for (int i = 0; i < repeat_; ++i) { | |
142 ret = frame->Init(format, width, height, dw, dh, | |
143 sample, sample_size, 0, rotation); | |
144 } | |
145 return ret; | |
146 } | |
147 | |
148 std::unique_ptr<rtc::MemoryStream> LoadSample(const std::string& filename, | |
149 const std::string& extension) { | |
150 rtc::Pathname path(webrtc::test::ResourcePath(filename, extension)); | |
151 std::unique_ptr<rtc::FileStream> fs( | |
152 rtc::Filesystem::OpenFile(path, "rb")); | |
153 if (!fs.get()) { | |
154 LOG(LS_ERROR) << "Could not open test file path: " << path.pathname() | |
155 << " from current dir " | |
156 << rtc::Filesystem::GetCurrentDirectory().pathname(); | |
157 return NULL; | |
158 } | |
159 | |
160 char buf[4096]; | |
161 std::unique_ptr<rtc::MemoryStream> ms( | |
162 new rtc::MemoryStream()); | |
163 rtc::StreamResult res = Flow(fs.get(), buf, sizeof(buf), ms.get()); | |
164 if (res != rtc::SR_SUCCESS) { | |
165 LOG(LS_ERROR) << "Could not load test file path: " << path.pathname(); | |
166 return NULL; | |
167 } | |
168 | |
169 return ms; | |
170 } | |
171 | |
172 bool DumpSample(const std::string& filename, const void* buffer, int size) { | |
173 rtc::Pathname path(filename); | |
174 std::unique_ptr<rtc::FileStream> fs( | |
175 rtc::Filesystem::OpenFile(path, "wb")); | |
176 if (!fs.get()) { | |
177 return false; | |
178 } | |
179 | |
180 return (fs->Write(buffer, size, NULL, NULL) == rtc::SR_SUCCESS); | |
181 } | |
182 | |
183 // Create a test image in the desired color space. | |
184 // The image is a checkerboard pattern with 63x63 squares, which allows | |
185 // I420 chroma artifacts to easily be seen on the square boundaries. | |
186 // The pattern is { { green, orange }, { blue, purple } } | |
187 // There is also a gradient within each square to ensure that the luma | |
188 // values are handled properly. | |
189 std::unique_ptr<rtc::MemoryStream> CreateYuv422Sample(uint32_t fourcc, | |
190 uint32_t width, | |
191 uint32_t height) { | |
192 int y1_pos, y2_pos, u_pos, v_pos; | |
193 if (!GetYuv422Packing(fourcc, &y1_pos, &y2_pos, &u_pos, &v_pos)) { | |
194 return NULL; | |
195 } | |
196 | |
197 std::unique_ptr<rtc::MemoryStream> ms( | |
198 new rtc::MemoryStream); | |
199 int awidth = (width + 1) & ~1; | |
200 int size = awidth * 2 * height; | |
201 if (!ms->ReserveSize(size)) { | |
202 return NULL; | |
203 } | |
204 for (uint32_t y = 0; y < height; ++y) { | |
205 for (int x = 0; x < awidth; x += 2) { | |
206 uint8_t quad[4]; | |
207 quad[y1_pos] = (x % 63 + y % 63) + 64; | |
208 quad[y2_pos] = ((x + 1) % 63 + y % 63) + 64; | |
209 quad[u_pos] = ((x / 63) & 1) ? 192 : 64; | |
210 quad[v_pos] = ((y / 63) & 1) ? 192 : 64; | |
211 ms->Write(quad, sizeof(quad), NULL, NULL); | |
212 } | |
213 } | |
214 return ms; | |
215 } | |
216 | |
217 // Create a test image for YUV 420 formats with 12 bits per pixel. | |
218 std::unique_ptr<rtc::MemoryStream> CreateYuvSample(uint32_t width, | |
219 uint32_t height, | |
220 uint32_t bpp) { | |
221 std::unique_ptr<rtc::MemoryStream> ms( | |
222 new rtc::MemoryStream); | |
223 if (!ms->ReserveSize(width * height * bpp / 8)) { | |
224 return NULL; | |
225 } | |
226 | |
227 for (uint32_t i = 0; i < width * height * bpp / 8; ++i) { | |
228 uint8_t value = ((i / 63) & 1) ? 192 : 64; | |
229 ms->Write(&value, sizeof(value), NULL, NULL); | |
230 } | |
231 return ms; | |
232 } | |
233 | |
234 std::unique_ptr<rtc::MemoryStream> CreateRgbSample(uint32_t fourcc, | |
235 uint32_t width, | |
236 uint32_t height) { | |
237 int r_pos, g_pos, b_pos, bytes; | |
238 if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) { | |
239 return NULL; | |
240 } | |
241 | |
242 std::unique_ptr<rtc::MemoryStream> ms( | |
243 new rtc::MemoryStream); | |
244 if (!ms->ReserveSize(width * height * bytes)) { | |
245 return NULL; | |
246 } | |
247 | |
248 for (uint32_t y = 0; y < height; ++y) { | |
249 for (uint32_t x = 0; x < width; ++x) { | |
250 uint8_t rgb[4] = {255, 255, 255, 255}; | |
251 rgb[r_pos] = ((x / 63) & 1) ? 224 : 32; | |
252 rgb[g_pos] = (x % 63 + y % 63) + 96; | |
253 rgb[b_pos] = ((y / 63) & 1) ? 224 : 32; | |
254 ms->Write(rgb, bytes, NULL, NULL); | |
255 } | |
256 } | |
257 return ms; | |
258 } | |
259 | |
260 // Simple conversion routines to verify the optimized VideoFrame routines. | |
261 // Converts from the specified colorspace to I420. | |
262 std::unique_ptr<T> ConvertYuv422(const rtc::MemoryStream* ms, | |
263 uint32_t fourcc, | |
264 uint32_t width, | |
265 uint32_t height) { | |
266 int y1_pos, y2_pos, u_pos, v_pos; | |
267 if (!GetYuv422Packing(fourcc, &y1_pos, &y2_pos, &u_pos, &v_pos)) { | |
268 return nullptr; | |
269 } | |
270 | |
271 rtc::scoped_refptr<webrtc::I420Buffer> buffer( | |
272 new rtc::RefCountedObject<webrtc::I420Buffer>(width, height)); | |
273 | |
274 buffer->SetToBlack(); | |
275 | |
276 const uint8_t* start = reinterpret_cast<const uint8_t*>(ms->GetBuffer()); | |
277 int awidth = (width + 1) & ~1; | |
278 int stride_y = buffer->StrideY(); | |
279 int stride_u = buffer->StrideU(); | |
280 int stride_v = buffer->StrideV(); | |
281 uint8_t* plane_y = buffer->MutableDataY(); | |
282 uint8_t* plane_u = buffer->MutableDataU(); | |
283 uint8_t* plane_v = buffer->MutableDataV(); | |
284 for (uint32_t y = 0; y < height; ++y) { | |
285 for (uint32_t x = 0; x < width; x += 2) { | |
286 const uint8_t* quad1 = start + (y * awidth + x) * 2; | |
287 plane_y[stride_y * y + x] = quad1[y1_pos]; | |
288 if ((x + 1) < width) { | |
289 plane_y[stride_y * y + x + 1] = quad1[y2_pos]; | |
290 } | |
291 if ((y & 1) == 0) { | |
292 const uint8_t* quad2 = quad1 + awidth * 2; | |
293 if ((y + 1) >= height) { | |
294 quad2 = quad1; | |
295 } | |
296 plane_u[stride_u * (y / 2) + x / 2] = | |
297 (quad1[u_pos] + quad2[u_pos] + 1) / 2; | |
298 plane_v[stride_v * (y / 2) + x / 2] = | |
299 (quad1[v_pos] + quad2[v_pos] + 1) / 2; | |
300 } | |
301 } | |
302 } | |
303 return std::unique_ptr<T>(new T(buffer, 0, webrtc::kVideoRotation_0)); | |
304 } | |
305 | |
306 // Convert RGB to 420. | |
307 // A negative height inverts the image. | |
308 std::unique_ptr<T> ConvertRgb(const rtc::MemoryStream* ms, | |
309 uint32_t fourcc, | |
310 int32_t width, | |
311 int32_t height) { | |
312 int r_pos, g_pos, b_pos, bytes; | |
313 if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) { | |
314 return nullptr; | |
315 } | |
316 int pitch = width * bytes; | |
317 const uint8_t* start = reinterpret_cast<const uint8_t*>(ms->GetBuffer()); | |
318 if (height < 0) { | |
319 height = -height; | |
320 start = start + pitch * (height - 1); | |
321 pitch = -pitch; | |
322 } | |
323 rtc::scoped_refptr<webrtc::I420Buffer> buffer( | |
324 new rtc::RefCountedObject<webrtc::I420Buffer>(width, height)); | |
325 | |
326 buffer->SetToBlack(); | |
327 | |
328 int stride_y = buffer->StrideY(); | |
329 int stride_u = buffer->StrideU(); | |
330 int stride_v = buffer->StrideV(); | |
331 uint8_t* plane_y = buffer->MutableDataY(); | |
332 uint8_t* plane_u = buffer->MutableDataU(); | |
333 uint8_t* plane_v = buffer->MutableDataV(); | |
334 for (int32_t y = 0; y < height; y += 2) { | |
335 for (int32_t x = 0; x < width; x += 2) { | |
336 const uint8_t* rgb[4]; | |
337 uint8_t yuv[4][3]; | |
338 rgb[0] = start + y * pitch + x * bytes; | |
339 rgb[1] = rgb[0] + ((x + 1) < width ? bytes : 0); | |
340 rgb[2] = rgb[0] + ((y + 1) < height ? pitch : 0); | |
341 rgb[3] = rgb[2] + ((x + 1) < width ? bytes : 0); | |
342 for (size_t i = 0; i < 4; ++i) { | |
343 ConvertRgbPixel(rgb[i][r_pos], rgb[i][g_pos], rgb[i][b_pos], | |
344 &yuv[i][0], &yuv[i][1], &yuv[i][2]); | |
345 } | |
346 plane_y[stride_y * y + x] = yuv[0][0]; | |
347 if ((x + 1) < width) { | |
348 plane_y[stride_y * y + x + 1] = yuv[1][0]; | |
349 } | |
350 if ((y + 1) < height) { | |
351 plane_y[stride_y * (y + 1) + x] = yuv[2][0]; | |
352 if ((x + 1) < width) { | |
353 plane_y[stride_y * (y + 1) + x + 1] = yuv[3][0]; | |
354 } | |
355 } | |
356 plane_u[stride_u * (y / 2) + x / 2] = | |
357 (yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1] + 2) / 4; | |
358 plane_v[stride_v * (y / 2) + x / 2] = | |
359 (yuv[0][2] + yuv[1][2] + yuv[2][2] + yuv[3][2] + 2) / 4; | |
360 } | |
361 } | |
362 return std::unique_ptr<T>(new T(buffer, 0, webrtc::kVideoRotation_0)); | |
363 } | |
364 | |
365 // Simple and slow RGB->YUV conversion. From NTSC standard, c/o Wikipedia. | |
366 void ConvertRgbPixel(uint8_t r, | |
367 uint8_t g, | |
368 uint8_t b, | |
369 uint8_t* y, | |
370 uint8_t* u, | |
371 uint8_t* v) { | |
372 *y = static_cast<int>(.257 * r + .504 * g + .098 * b) + 16; | |
373 *u = static_cast<int>(-.148 * r - .291 * g + .439 * b) + 128; | |
374 *v = static_cast<int>(.439 * r - .368 * g - .071 * b) + 128; | |
375 } | |
376 | |
377 bool GetYuv422Packing(uint32_t fourcc, | |
378 int* y1_pos, | |
379 int* y2_pos, | |
380 int* u_pos, | |
381 int* v_pos) { | |
382 if (fourcc == cricket::FOURCC_YUY2) { | |
383 *y1_pos = 0; *u_pos = 1; *y2_pos = 2; *v_pos = 3; | |
384 } else if (fourcc == cricket::FOURCC_UYVY) { | |
385 *u_pos = 0; *y1_pos = 1; *v_pos = 2; *y2_pos = 3; | |
386 } else { | |
387 return false; | |
388 } | |
389 return true; | |
390 } | |
391 | |
392 bool GetRgbPacking(uint32_t fourcc, | |
393 int* r_pos, | |
394 int* g_pos, | |
395 int* b_pos, | |
396 int* bytes) { | |
397 if (fourcc == cricket::FOURCC_RAW) { | |
398 *r_pos = 0; *g_pos = 1; *b_pos = 2; *bytes = 3; // RGB in memory. | |
399 } else if (fourcc == cricket::FOURCC_24BG) { | |
400 *r_pos = 2; *g_pos = 1; *b_pos = 0; *bytes = 3; // BGR in memory. | |
401 } else if (fourcc == cricket::FOURCC_ABGR) { | |
402 *r_pos = 0; *g_pos = 1; *b_pos = 2; *bytes = 4; // RGBA in memory. | |
403 } else if (fourcc == cricket::FOURCC_BGRA) { | |
404 *r_pos = 1; *g_pos = 2; *b_pos = 3; *bytes = 4; // ARGB in memory. | |
405 } else if (fourcc == cricket::FOURCC_ARGB) { | |
406 *r_pos = 2; *g_pos = 1; *b_pos = 0; *bytes = 4; // BGRA in memory. | |
407 } else { | |
408 return false; | |
409 } | |
410 return true; | |
411 } | |
412 | |
413 // Comparison functions for testing. | |
414 static bool IsNull(const cricket::VideoFrame& frame) { | |
415 return !frame.video_frame_buffer(); | |
416 } | |
417 | |
418 static bool IsSize(const cricket::VideoFrame& frame, | |
419 int width, | |
420 int height) { | |
421 return !IsNull(frame) && frame.video_frame_buffer()->StrideY() >= width && | |
422 frame.video_frame_buffer()->StrideU() >= width / 2 && | |
423 frame.video_frame_buffer()->StrideV() >= width / 2 && | |
424 frame.width() == width && frame.height() == height; | |
425 } | |
426 | |
427 static bool IsPlaneEqual(const std::string& name, | |
428 const uint8_t* plane1, | |
429 uint32_t pitch1, | |
430 const uint8_t* plane2, | |
431 uint32_t pitch2, | |
432 uint32_t width, | |
433 uint32_t height, | |
434 int max_error) { | |
435 const uint8_t* r1 = plane1; | |
436 const uint8_t* r2 = plane2; | |
437 for (uint32_t y = 0; y < height; ++y) { | |
438 for (uint32_t x = 0; x < width; ++x) { | |
439 if (abs(static_cast<int>(r1[x] - r2[x])) > max_error) { | |
440 LOG(LS_INFO) << "IsPlaneEqual(" << name << "): pixel[" | |
441 << x << "," << y << "] differs: " | |
442 << static_cast<int>(r1[x]) << " vs " | |
443 << static_cast<int>(r2[x]); | |
444 return false; | |
445 } | |
446 } | |
447 r1 += pitch1; | |
448 r2 += pitch2; | |
449 } | |
450 return true; | |
451 } | |
452 | |
453 static bool IsEqual(const cricket::VideoFrame& frame, | |
454 int width, | |
455 int height, | |
456 const uint8_t* y, | |
457 uint32_t ypitch, | |
458 const uint8_t* u, | |
459 uint32_t upitch, | |
460 const uint8_t* v, | |
461 uint32_t vpitch, | |
462 int max_error) { | |
463 return IsSize(frame, width, height) && | |
464 IsPlaneEqual("y", frame.video_frame_buffer()->DataY(), | |
465 frame.video_frame_buffer()->StrideY(), y, ypitch, | |
466 static_cast<uint32_t>(width), | |
467 static_cast<uint32_t>(height), max_error) && | |
468 IsPlaneEqual("u", frame.video_frame_buffer()->DataU(), | |
469 frame.video_frame_buffer()->StrideU(), u, upitch, | |
470 static_cast<uint32_t>((width + 1) / 2), | |
471 static_cast<uint32_t>((height + 1) / 2), max_error) && | |
472 IsPlaneEqual("v", frame.video_frame_buffer()->DataV(), | |
473 frame.video_frame_buffer()->StrideV(), v, vpitch, | |
474 static_cast<uint32_t>((width + 1) / 2), | |
475 static_cast<uint32_t>((height + 1) / 2), max_error); | |
476 } | |
477 | |
478 static bool IsEqual(const cricket::VideoFrame& frame1, | |
479 const cricket::VideoFrame& frame2, | |
480 int max_error) { | |
481 return frame1.timestamp_us() == frame2.timestamp_us() && | |
482 IsEqual(frame1, | |
483 frame2.width(), frame2.height(), | |
484 frame2.video_frame_buffer()->DataY(), | |
485 frame2.video_frame_buffer()->StrideY(), | |
486 frame2.video_frame_buffer()->DataU(), | |
487 frame2.video_frame_buffer()->StrideU(), | |
488 frame2.video_frame_buffer()->DataV(), | |
489 frame2.video_frame_buffer()->StrideV(), max_error); | |
490 } | |
491 | |
492 static bool IsEqual( | |
493 const cricket::VideoFrame& frame1, | |
494 const rtc::scoped_refptr<webrtc::VideoFrameBuffer>& buffer, | |
495 int max_error) { | |
496 return IsEqual(frame1, buffer->width(), buffer->height(), | |
497 buffer->DataY(), buffer->StrideY(), | |
498 buffer->DataU(), buffer->StrideU(), | |
499 buffer->DataV(), buffer->StrideV(), | |
500 max_error); | |
501 } | |
502 | |
503 static bool IsEqualWithCrop(const cricket::VideoFrame& frame1, | |
504 const cricket::VideoFrame& frame2, | |
505 int hcrop, int vcrop, int max_error) { | |
506 return frame1.width() <= frame2.width() && | |
507 frame1.height() <= frame2.height() && | |
508 frame1.timestamp_us() == frame2.timestamp_us() && | |
509 IsEqual(frame1, | |
510 frame2.width() - hcrop * 2, | |
511 frame2.height() - vcrop * 2, | |
512 frame2.video_frame_buffer()->DataY() | |
513 + vcrop * frame2.video_frame_buffer()->StrideY() | |
514 + hcrop, | |
515 frame2.video_frame_buffer()->StrideY(), | |
516 frame2.video_frame_buffer()->DataU() | |
517 + vcrop * frame2.video_frame_buffer()->StrideU() / 2 | |
518 + hcrop / 2, | |
519 frame2.video_frame_buffer()->StrideU(), | |
520 frame2.video_frame_buffer()->DataV() | |
521 + vcrop * frame2.video_frame_buffer()->StrideV() / 2 | |
522 + hcrop / 2, | |
523 frame2.video_frame_buffer()->StrideV(), | |
524 max_error); | |
525 } | |
526 | |
527 static bool IsBlack(const cricket::VideoFrame& frame) { | |
528 return !IsNull(frame) && | |
529 *frame.video_frame_buffer()->DataY() <= 16 && | |
530 *frame.video_frame_buffer()->DataU() == 128 && | |
531 *frame.video_frame_buffer()->DataV() == 128; | |
532 } | |
533 | |
534 //////////////////////// | |
535 // Construction tests // | |
536 //////////////////////// | |
537 | |
538 // Test constructing an image from a I420 buffer. | |
539 void ConstructI420() { | |
540 T frame; | |
541 EXPECT_TRUE(IsNull(frame)); | |
542 std::unique_ptr<rtc::MemoryStream> ms( | |
543 CreateYuvSample(kWidth, kHeight, 12)); | |
544 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420, | |
545 kWidth, kHeight, &frame)); | |
546 | |
547 const uint8_t* y = reinterpret_cast<uint8_t*>(ms.get()->GetBuffer()); | |
548 const uint8_t* u = y + kWidth * kHeight; | |
549 const uint8_t* v = u + kWidth * kHeight / 4; | |
550 EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, y, kWidth, u, kWidth / 2, v, | |
551 kWidth / 2, 0)); | |
552 } | |
553 | |
554 // Test constructing an image from a YV12 buffer. | |
555 void ConstructYV12() { | |
556 T frame; | |
557 std::unique_ptr<rtc::MemoryStream> ms( | |
558 CreateYuvSample(kWidth, kHeight, 12)); | |
559 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YV12, | |
560 kWidth, kHeight, &frame)); | |
561 | |
562 const uint8_t* y = reinterpret_cast<uint8_t*>(ms.get()->GetBuffer()); | |
563 const uint8_t* v = y + kWidth * kHeight; | |
564 const uint8_t* u = v + kWidth * kHeight / 4; | |
565 EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, y, kWidth, u, kWidth / 2, v, | |
566 kWidth / 2, 0)); | |
567 } | |
568 | |
569 // Test constructing an image from a I422 buffer. | |
570 void ConstructI422() { | |
571 T frame1, frame2; | |
572 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
573 size_t buf_size = kWidth * kHeight * 2; | |
574 std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size + kAlignment]); | |
575 uint8_t* y = ALIGNP(buf.get(), kAlignment); | |
576 uint8_t* u = y + kWidth * kHeight; | |
577 uint8_t* v = u + (kWidth / 2) * kHeight; | |
578 EXPECT_EQ(0, libyuv::I420ToI422(frame1.video_frame_buffer()->DataY(), | |
579 frame1.video_frame_buffer()->StrideY(), | |
580 frame1.video_frame_buffer()->DataU(), | |
581 frame1.video_frame_buffer()->StrideU(), | |
582 frame1.video_frame_buffer()->DataV(), | |
583 frame1.video_frame_buffer()->StrideV(), | |
584 y, kWidth, | |
585 u, kWidth / 2, | |
586 v, kWidth / 2, | |
587 kWidth, kHeight)); | |
588 EXPECT_TRUE(LoadFrame(y, buf_size, cricket::FOURCC_I422, | |
589 kWidth, kHeight, &frame2)); | |
590 EXPECT_TRUE(IsEqual(frame1, frame2, 1)); | |
591 } | |
592 | |
593 // Test constructing an image from a YUY2 buffer. | |
594 void ConstructYuy2() { | |
595 T frame1, frame2; | |
596 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
597 size_t buf_size = kWidth * kHeight * 2; | |
598 std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size + kAlignment]); | |
599 uint8_t* yuy2 = ALIGNP(buf.get(), kAlignment); | |
600 EXPECT_EQ(0, libyuv::I420ToYUY2(frame1.video_frame_buffer()->DataY(), | |
601 frame1.video_frame_buffer()->StrideY(), | |
602 frame1.video_frame_buffer()->DataU(), | |
603 frame1.video_frame_buffer()->StrideU(), | |
604 frame1.video_frame_buffer()->DataV(), | |
605 frame1.video_frame_buffer()->StrideV(), | |
606 yuy2, kWidth * 2, | |
607 kWidth, kHeight)); | |
608 EXPECT_TRUE(LoadFrame(yuy2, buf_size, cricket::FOURCC_YUY2, | |
609 kWidth, kHeight, &frame2)); | |
610 EXPECT_TRUE(IsEqual(frame1, frame2, 0)); | |
611 } | |
612 | |
613 // Test constructing an image from a YUY2 buffer with buffer unaligned. | |
614 void ConstructYuy2Unaligned() { | |
615 T frame1, frame2; | |
616 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
617 size_t buf_size = kWidth * kHeight * 2; | |
618 std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size + kAlignment + 1]); | |
619 uint8_t* yuy2 = ALIGNP(buf.get(), kAlignment) + 1; | |
620 EXPECT_EQ(0, libyuv::I420ToYUY2(frame1.video_frame_buffer()->DataY(), | |
621 frame1.video_frame_buffer()->StrideY(), | |
622 frame1.video_frame_buffer()->DataU(), | |
623 frame1.video_frame_buffer()->StrideU(), | |
624 frame1.video_frame_buffer()->DataV(), | |
625 frame1.video_frame_buffer()->StrideV(), | |
626 yuy2, kWidth * 2, | |
627 kWidth, kHeight)); | |
628 EXPECT_TRUE(LoadFrame(yuy2, buf_size, cricket::FOURCC_YUY2, | |
629 kWidth, kHeight, &frame2)); | |
630 EXPECT_TRUE(IsEqual(frame1, frame2, 0)); | |
631 } | |
632 | |
633 // Test constructing an image from a wide YUY2 buffer. | |
634 // Normal is 1280x720. Wide is 12800x72 | |
635 void ConstructYuy2Wide() { | |
636 std::unique_ptr<rtc::MemoryStream> ms( | |
637 CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth * 10, kHeight / 10)); | |
638 ASSERT_TRUE(ms.get() != NULL); | |
639 std::unique_ptr<T> frame1 = ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, | |
640 kWidth * 10, kHeight / 10); | |
641 ASSERT_TRUE(frame1); | |
642 T frame2; | |
643 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, | |
644 kWidth * 10, kHeight / 10, &frame2)); | |
645 EXPECT_TRUE(IsEqual(*frame1, frame2, 0)); | |
646 } | |
647 | |
648 // Test constructing an image from a UYVY buffer. | |
649 void ConstructUyvy() { | |
650 std::unique_ptr<rtc::MemoryStream> ms( | |
651 CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight)); | |
652 ASSERT_TRUE(ms.get() != NULL); | |
653 std::unique_ptr<T> frame1 = ConvertYuv422(ms.get(), cricket::FOURCC_UYVY, | |
654 kWidth, kHeight); | |
655 T frame2; | |
656 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY, | |
657 kWidth, kHeight, &frame2)); | |
658 EXPECT_TRUE(IsEqual(*frame1, frame2, 0)); | |
659 } | |
660 | |
661 // Test constructing an image from a random buffer. | |
662 // We are merely verifying that the code succeeds and is free of crashes. | |
663 void ConstructM420() { | |
664 T frame; | |
665 std::unique_ptr<rtc::MemoryStream> ms( | |
666 CreateYuvSample(kWidth, kHeight, 12)); | |
667 ASSERT_TRUE(ms.get() != NULL); | |
668 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_M420, | |
669 kWidth, kHeight, &frame)); | |
670 } | |
671 | |
672 void ConstructNV21() { | |
673 T frame; | |
674 std::unique_ptr<rtc::MemoryStream> ms( | |
675 CreateYuvSample(kWidth, kHeight, 12)); | |
676 ASSERT_TRUE(ms.get() != NULL); | |
677 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_NV21, | |
678 kWidth, kHeight, &frame)); | |
679 } | |
680 | |
681 void ConstructNV12() { | |
682 T frame; | |
683 std::unique_ptr<rtc::MemoryStream> ms( | |
684 CreateYuvSample(kWidth, kHeight, 12)); | |
685 ASSERT_TRUE(ms.get() != NULL); | |
686 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_NV12, | |
687 kWidth, kHeight, &frame)); | |
688 } | |
689 | |
690 // Test constructing an image from a ABGR buffer | |
691 // Due to rounding, some pixels may differ slightly from the VideoFrame impl. | |
692 void ConstructABGR() { | |
693 std::unique_ptr<rtc::MemoryStream> ms( | |
694 CreateRgbSample(cricket::FOURCC_ABGR, kWidth, kHeight)); | |
695 ASSERT_TRUE(ms.get() != NULL); | |
696 std::unique_ptr<T> frame1 = ConvertRgb(ms.get(), cricket::FOURCC_ABGR, | |
697 kWidth, kHeight); | |
698 ASSERT_TRUE(frame1); | |
699 T frame2; | |
700 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ABGR, | |
701 kWidth, kHeight, &frame2)); | |
702 EXPECT_TRUE(IsEqual(*frame1, frame2, 2)); | |
703 } | |
704 | |
705 // Test constructing an image from a ARGB buffer | |
706 // Due to rounding, some pixels may differ slightly from the VideoFrame impl. | |
707 void ConstructARGB() { | |
708 std::unique_ptr<rtc::MemoryStream> ms( | |
709 CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight)); | |
710 ASSERT_TRUE(ms.get() != NULL); | |
711 std::unique_ptr<T> frame1 = ConvertRgb(ms.get(), cricket::FOURCC_ARGB, | |
712 kWidth, kHeight); | |
713 ASSERT_TRUE(frame1); | |
714 T frame2; | |
715 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB, | |
716 kWidth, kHeight, &frame2)); | |
717 EXPECT_TRUE(IsEqual(*frame1, frame2, 2)); | |
718 } | |
719 | |
720 // Test constructing an image from a wide ARGB buffer | |
721 // Normal is 1280x720. Wide is 12800x72 | |
722 void ConstructARGBWide() { | |
723 std::unique_ptr<rtc::MemoryStream> ms( | |
724 CreateRgbSample(cricket::FOURCC_ARGB, kWidth * 10, kHeight / 10)); | |
725 ASSERT_TRUE(ms.get() != NULL); | |
726 std::unique_ptr<T> frame1 = ConvertRgb(ms.get(), cricket::FOURCC_ARGB, | |
727 kWidth * 10, kHeight / 10); | |
728 ASSERT_TRUE(frame1); | |
729 T frame2; | |
730 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB, | |
731 kWidth * 10, kHeight / 10, &frame2)); | |
732 EXPECT_TRUE(IsEqual(*frame1, frame2, 2)); | |
733 } | |
734 | |
735 // Test constructing an image from an BGRA buffer. | |
736 // Due to rounding, some pixels may differ slightly from the VideoFrame impl. | |
737 void ConstructBGRA() { | |
738 std::unique_ptr<rtc::MemoryStream> ms( | |
739 CreateRgbSample(cricket::FOURCC_BGRA, kWidth, kHeight)); | |
740 ASSERT_TRUE(ms.get() != NULL); | |
741 std::unique_ptr<T> frame1 = ConvertRgb(ms.get(), cricket::FOURCC_BGRA, | |
742 kWidth, kHeight); | |
743 ASSERT_TRUE(frame1); | |
744 T frame2; | |
745 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_BGRA, | |
746 kWidth, kHeight, &frame2)); | |
747 EXPECT_TRUE(IsEqual(*frame1, frame2, 2)); | |
748 } | |
749 | |
750 // Test constructing an image from a 24BG buffer. | |
751 // Due to rounding, some pixels may differ slightly from the VideoFrame impl. | |
752 void Construct24BG() { | |
753 std::unique_ptr<rtc::MemoryStream> ms( | |
754 CreateRgbSample(cricket::FOURCC_24BG, kWidth, kHeight)); | |
755 ASSERT_TRUE(ms.get() != NULL); | |
756 std::unique_ptr<T> frame1 = ConvertRgb(ms.get(), cricket::FOURCC_24BG, | |
757 kWidth, kHeight); | |
758 ASSERT_TRUE(frame1); | |
759 T frame2; | |
760 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_24BG, | |
761 kWidth, kHeight, &frame2)); | |
762 EXPECT_TRUE(IsEqual(*frame1, frame2, 2)); | |
763 } | |
764 | |
765 // Test constructing an image from a raw RGB buffer. | |
766 // Due to rounding, some pixels may differ slightly from the VideoFrame impl. | |
767 void ConstructRaw() { | |
768 std::unique_ptr<rtc::MemoryStream> ms( | |
769 CreateRgbSample(cricket::FOURCC_RAW, kWidth, kHeight)); | |
770 ASSERT_TRUE(ms.get() != NULL); | |
771 std::unique_ptr<T> frame1 = ConvertRgb(ms.get(), cricket::FOURCC_RAW, | |
772 kWidth, kHeight); | |
773 ASSERT_TRUE(frame1); | |
774 T frame2; | |
775 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_RAW, | |
776 kWidth, kHeight, &frame2)); | |
777 EXPECT_TRUE(IsEqual(*frame1, frame2, 2)); | |
778 } | |
779 | |
780 // Macro to help test different rotations | |
781 #define TEST_MIRROR(FOURCC, BPP) \ | |
782 void Construct##FOURCC##Mirror() { \ | |
783 T frame1, frame2; \ | |
784 rtc::scoped_refptr<webrtc::I420Buffer> res_buffer; \ | |
785 std::unique_ptr<rtc::MemoryStream> ms( \ | |
786 CreateYuvSample(kWidth, kHeight, BPP)); \ | |
787 ASSERT_TRUE(ms.get() != NULL); \ | |
788 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_##FOURCC, kWidth, \ | |
789 -kHeight, kWidth, kHeight, \ | |
790 webrtc::kVideoRotation_180, &frame1)); \ | |
791 size_t data_size; \ | |
792 bool ret = ms->GetSize(&data_size); \ | |
793 EXPECT_TRUE(ret); \ | |
794 EXPECT_TRUE(frame2.Init(cricket::FOURCC_##FOURCC, kWidth, kHeight, kWidth, \ | |
795 kHeight, \ | |
796 reinterpret_cast<uint8_t*>(ms->GetBuffer()), \ | |
797 data_size, 0, webrtc::kVideoRotation_0)); \ | |
798 int width_rotate = frame1.width(); \ | |
799 int height_rotate = frame1.height(); \ | |
800 res_buffer = webrtc::I420Buffer::Create(width_rotate, height_rotate); \ | |
801 libyuv::I420Mirror(frame2.video_frame_buffer()->DataY(), \ | |
802 frame2.video_frame_buffer()->StrideY(), \ | |
803 frame2.video_frame_buffer()->DataU(), \ | |
804 frame2.video_frame_buffer()->StrideU(), \ | |
805 frame2.video_frame_buffer()->DataV(), \ | |
806 frame2.video_frame_buffer()->StrideV(), \ | |
807 res_buffer->MutableDataY(), res_buffer->StrideY(), \ | |
808 res_buffer->MutableDataU(), res_buffer->StrideU(), \ | |
809 res_buffer->MutableDataV(), res_buffer->StrideV(), \ | |
810 kWidth, kHeight); \ | |
811 EXPECT_TRUE(IsEqual(frame1, res_buffer, 0)); \ | |
812 } | |
813 | |
814 TEST_MIRROR(I420, 420) | |
815 | |
816 // Macro to help test different rotations | |
817 #define TEST_ROTATE(FOURCC, BPP, ROTATE) \ | |
818 void Construct##FOURCC##Rotate##ROTATE() { \ | |
819 T frame1, frame2; \ | |
820 rtc::scoped_refptr<webrtc::I420Buffer> res_buffer; \ | |
821 std::unique_ptr<rtc::MemoryStream> ms( \ | |
822 CreateYuvSample(kWidth, kHeight, BPP)); \ | |
823 ASSERT_TRUE(ms.get() != NULL); \ | |
824 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_##FOURCC, kWidth, kHeight, \ | |
825 kWidth, kHeight, webrtc::kVideoRotation_##ROTATE, \ | |
826 &frame1)); \ | |
827 size_t data_size; \ | |
828 bool ret = ms->GetSize(&data_size); \ | |
829 EXPECT_TRUE(ret); \ | |
830 EXPECT_TRUE(frame2.Init(cricket::FOURCC_##FOURCC, kWidth, kHeight, kWidth, \ | |
831 kHeight, \ | |
832 reinterpret_cast<uint8_t*>(ms->GetBuffer()), \ | |
833 data_size, 0, webrtc::kVideoRotation_0)); \ | |
834 int width_rotate = frame1.width(); \ | |
835 int height_rotate = frame1.height(); \ | |
836 res_buffer = webrtc::I420Buffer::Create(width_rotate, height_rotate); \ | |
837 libyuv::I420Rotate(frame2.video_frame_buffer()->DataY(), \ | |
838 frame2.video_frame_buffer()->StrideY(), \ | |
839 frame2.video_frame_buffer()->DataU(), \ | |
840 frame2.video_frame_buffer()->StrideU(), \ | |
841 frame2.video_frame_buffer()->DataV(), \ | |
842 frame2.video_frame_buffer()->StrideV(), \ | |
843 res_buffer->MutableDataY(), res_buffer->StrideY(), \ | |
844 res_buffer->MutableDataU(), res_buffer->StrideU(), \ | |
845 res_buffer->MutableDataV(), res_buffer->StrideV(), \ | |
846 kWidth, kHeight, libyuv::kRotate##ROTATE); \ | |
847 EXPECT_TRUE(IsEqual(frame1, res_buffer, 0)); \ | |
848 } | |
849 | |
850 // Test constructing an image with rotation. | |
851 TEST_ROTATE(I420, 12, 0) | |
852 TEST_ROTATE(I420, 12, 90) | |
853 TEST_ROTATE(I420, 12, 180) | |
854 TEST_ROTATE(I420, 12, 270) | |
855 TEST_ROTATE(YV12, 12, 0) | |
856 TEST_ROTATE(YV12, 12, 90) | |
857 TEST_ROTATE(YV12, 12, 180) | |
858 TEST_ROTATE(YV12, 12, 270) | |
859 TEST_ROTATE(NV12, 12, 0) | |
860 TEST_ROTATE(NV12, 12, 90) | |
861 TEST_ROTATE(NV12, 12, 180) | |
862 TEST_ROTATE(NV12, 12, 270) | |
863 TEST_ROTATE(NV21, 12, 0) | |
864 TEST_ROTATE(NV21, 12, 90) | |
865 TEST_ROTATE(NV21, 12, 180) | |
866 TEST_ROTATE(NV21, 12, 270) | |
867 TEST_ROTATE(UYVY, 16, 0) | |
868 TEST_ROTATE(UYVY, 16, 90) | |
869 TEST_ROTATE(UYVY, 16, 180) | |
870 TEST_ROTATE(UYVY, 16, 270) | |
871 TEST_ROTATE(YUY2, 16, 0) | |
872 TEST_ROTATE(YUY2, 16, 90) | |
873 TEST_ROTATE(YUY2, 16, 180) | |
874 TEST_ROTATE(YUY2, 16, 270) | |
875 | |
876 // Test constructing an image from a UYVY buffer rotated 90 degrees. | |
877 void ConstructUyvyRotate90() { | |
878 T frame2; | |
879 std::unique_ptr<rtc::MemoryStream> ms( | |
880 CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight)); | |
881 ASSERT_TRUE(ms.get() != NULL); | |
882 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight, | |
883 kWidth, kHeight, webrtc::kVideoRotation_90, &frame2)); | |
884 } | |
885 | |
886 // Test constructing an image from a UYVY buffer rotated 180 degrees. | |
887 void ConstructUyvyRotate180() { | |
888 T frame2; | |
889 std::unique_ptr<rtc::MemoryStream> ms( | |
890 CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight)); | |
891 ASSERT_TRUE(ms.get() != NULL); | |
892 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight, | |
893 kWidth, kHeight, webrtc::kVideoRotation_180, | |
894 &frame2)); | |
895 } | |
896 | |
897 // Test constructing an image from a UYVY buffer rotated 270 degrees. | |
898 void ConstructUyvyRotate270() { | |
899 T frame2; | |
900 std::unique_ptr<rtc::MemoryStream> ms( | |
901 CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight)); | |
902 ASSERT_TRUE(ms.get() != NULL); | |
903 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight, | |
904 kWidth, kHeight, webrtc::kVideoRotation_270, | |
905 &frame2)); | |
906 } | |
907 | |
908 // Test constructing an image from a YUY2 buffer rotated 90 degrees. | |
909 void ConstructYuy2Rotate90() { | |
910 T frame2; | |
911 std::unique_ptr<rtc::MemoryStream> ms( | |
912 CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight)); | |
913 ASSERT_TRUE(ms.get() != NULL); | |
914 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight, | |
915 kWidth, kHeight, webrtc::kVideoRotation_90, &frame2)); | |
916 } | |
917 | |
918 // Test constructing an image from a YUY2 buffer rotated 180 degrees. | |
919 void ConstructYuy2Rotate180() { | |
920 T frame2; | |
921 std::unique_ptr<rtc::MemoryStream> ms( | |
922 CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight)); | |
923 ASSERT_TRUE(ms.get() != NULL); | |
924 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight, | |
925 kWidth, kHeight, webrtc::kVideoRotation_180, | |
926 &frame2)); | |
927 } | |
928 | |
929 // Test constructing an image from a YUY2 buffer rotated 270 degrees. | |
930 void ConstructYuy2Rotate270() { | |
931 T frame2; | |
932 std::unique_ptr<rtc::MemoryStream> ms( | |
933 CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight)); | |
934 ASSERT_TRUE(ms.get() != NULL); | |
935 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight, | |
936 kWidth, kHeight, webrtc::kVideoRotation_270, | |
937 &frame2)); | |
938 } | |
939 | |
940 // Test 1 pixel edge case image I420 buffer. | |
941 void ConstructI4201Pixel() { | |
942 T frame; | |
943 uint8_t pixel[3] = {1, 2, 3}; | |
944 for (int i = 0; i < repeat_; ++i) { | |
945 EXPECT_TRUE(frame.Init(cricket::FOURCC_I420, 1, 1, 1, 1, pixel, | |
946 sizeof(pixel), 0, webrtc::kVideoRotation_0)); | |
947 } | |
948 const uint8_t* y = pixel; | |
949 const uint8_t* u = y + 1; | |
950 const uint8_t* v = u + 1; | |
951 EXPECT_TRUE(IsEqual(frame, 1, 1, y, 1, u, 1, v, 1, 0)); | |
952 } | |
953 | |
954 // Test 5 pixel edge case image. | |
955 void ConstructI4205Pixel() { | |
956 T frame; | |
957 uint8_t pixels5x5[5 * 5 + ((5 + 1) / 2 * (5 + 1) / 2) * 2]; | |
958 memset(pixels5x5, 1, 5 * 5 + ((5 + 1) / 2 * (5 + 1) / 2) * 2); | |
959 for (int i = 0; i < repeat_; ++i) { | |
960 EXPECT_TRUE(frame.Init(cricket::FOURCC_I420, 5, 5, 5, 5, pixels5x5, | |
961 sizeof(pixels5x5), 0, | |
962 webrtc::kVideoRotation_0)); | |
963 } | |
964 EXPECT_EQ(5, frame.width()); | |
965 EXPECT_EQ(5, frame.height()); | |
966 EXPECT_EQ(5, frame.video_frame_buffer()->StrideY()); | |
967 EXPECT_EQ(3, frame.video_frame_buffer()->StrideU()); | |
968 EXPECT_EQ(3, frame.video_frame_buffer()->StrideV()); | |
969 } | |
970 | |
971 // Test constructing an image from an I420 buffer with horizontal cropping. | |
972 void ConstructI420CropHorizontal() { | |
973 T frame1, frame2; | |
974 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
975 ASSERT_TRUE(LoadFrame(kImageFilename, kYuvExtension, | |
976 cricket::FOURCC_I420, kWidth, kHeight, | |
977 kWidth * 3 / 4, kHeight, webrtc::kVideoRotation_0, | |
978 &frame2)); | |
979 EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, kWidth / 8, 0, 0)); | |
980 } | |
981 | |
982 // Test constructing an image from a YUY2 buffer with horizontal cropping. | |
983 void ConstructYuy2CropHorizontal() { | |
984 T frame1, frame2; | |
985 std::unique_ptr<rtc::MemoryStream> ms( | |
986 CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight)); | |
987 ASSERT_TRUE(ms.get() != NULL); | |
988 EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight, | |
989 &frame1)); | |
990 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight, | |
991 kWidth * 3 / 4, kHeight, webrtc::kVideoRotation_0, | |
992 &frame2)); | |
993 EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, kWidth / 8, 0, 0)); | |
994 } | |
995 | |
996 // Test constructing an image from an ARGB buffer with horizontal cropping. | |
997 void ConstructARGBCropHorizontal() { | |
998 std::unique_ptr<rtc::MemoryStream> ms( | |
999 CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight)); | |
1000 ASSERT_TRUE(ms.get() != NULL); | |
1001 std::unique_ptr<T> frame1 = ConvertRgb(ms.get(), cricket::FOURCC_ARGB, | |
1002 kWidth, kHeight); | |
1003 ASSERT_TRUE(frame1); | |
1004 T frame2; | |
1005 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB, kWidth, kHeight, | |
1006 kWidth * 3 / 4, kHeight, webrtc::kVideoRotation_0, | |
1007 &frame2)); | |
1008 EXPECT_TRUE(IsEqualWithCrop(frame2, *frame1, kWidth / 8, 0, 2)); | |
1009 } | |
1010 | |
1011 // Test constructing an image from an I420 buffer, cropping top and bottom. | |
1012 void ConstructI420CropVertical() { | |
1013 T frame1, frame2; | |
1014 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
1015 ASSERT_TRUE(LoadFrame(LoadSample(kImageFilename, kYuvExtension).get(), | |
1016 cricket::FOURCC_I420, kWidth, kHeight, | |
1017 kWidth, kHeight * 3 / 4, webrtc::kVideoRotation_0, | |
1018 &frame2)); | |
1019 EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, 0, kHeight / 8, 0)); | |
1020 } | |
1021 | |
1022 // Test constructing an image from I420 synonymous formats. | |
1023 void ConstructI420Aliases() { | |
1024 T frame1, frame2, frame3; | |
1025 ASSERT_TRUE(LoadFrame(LoadSample(kImageFilename, kYuvExtension), | |
1026 cricket::FOURCC_I420, kWidth, kHeight, | |
1027 &frame1)); | |
1028 ASSERT_TRUE(LoadFrame(kImageFilename, kYuvExtension, | |
1029 cricket::FOURCC_IYUV, kWidth, kHeight, | |
1030 &frame2)); | |
1031 ASSERT_TRUE(LoadFrame(kImageFilename, kYuvExtension, | |
1032 cricket::FOURCC_YU12, kWidth, kHeight, | |
1033 &frame3)); | |
1034 EXPECT_TRUE(IsEqual(frame1, frame2, 0)); | |
1035 EXPECT_TRUE(IsEqual(frame1, frame3, 0)); | |
1036 } | |
1037 | |
1038 // Test constructing an image from an I420 MJPG buffer. | |
1039 void ConstructMjpgI420() { | |
1040 T frame1, frame2; | |
1041 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
1042 ASSERT_TRUE(LoadFrame(kJpeg420Filename, kJpegExtension, | |
1043 cricket::FOURCC_MJPG, kWidth, kHeight, &frame2)); | |
1044 EXPECT_TRUE(IsEqual(frame1, frame2, 32)); | |
1045 } | |
1046 | |
1047 // Test constructing an image from an I422 MJPG buffer. | |
1048 void ConstructMjpgI422() { | |
1049 T frame1, frame2; | |
1050 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
1051 ASSERT_TRUE(LoadFrame(LoadSample(kJpeg422Filename, kJpegExtension).get(), | |
1052 cricket::FOURCC_MJPG, kWidth, kHeight, &frame2)); | |
1053 EXPECT_TRUE(IsEqual(frame1, frame2, 32)); | |
1054 } | |
1055 | |
1056 // Test constructing an image from an I444 MJPG buffer. | |
1057 void ConstructMjpgI444() { | |
1058 T frame1, frame2; | |
1059 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
1060 ASSERT_TRUE(LoadFrame(LoadSample(kJpeg444Filename, kJpegExtension), | |
1061 cricket::FOURCC_MJPG, kWidth, kHeight, &frame2)); | |
1062 EXPECT_TRUE(IsEqual(frame1, frame2, 32)); | |
1063 } | |
1064 | |
1065 // Test constructing an image from an I444 MJPG buffer. | |
1066 void ConstructMjpgI411() { | |
1067 T frame1, frame2; | |
1068 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
1069 ASSERT_TRUE(LoadFrame(kJpeg411Filename, kJpegExtension, | |
1070 cricket::FOURCC_MJPG, kWidth, kHeight, &frame2)); | |
1071 EXPECT_TRUE(IsEqual(frame1, frame2, 32)); | |
1072 } | |
1073 | |
1074 // Test constructing an image from an I400 MJPG buffer. | |
1075 // TODO(fbarchard): Stronger compare on chroma. Compare agaisnt a grey image. | |
1076 void ConstructMjpgI400() { | |
1077 T frame1, frame2; | |
1078 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
1079 ASSERT_TRUE(LoadFrame(kJpeg400Filename, kJpegExtension, | |
1080 cricket::FOURCC_MJPG, kWidth, kHeight, &frame2)); | |
1081 EXPECT_TRUE(IsPlaneEqual("y", frame1.video_frame_buffer()->DataY(), | |
1082 frame1.video_frame_buffer()->StrideY(), | |
1083 frame2.video_frame_buffer()->DataY(), | |
1084 frame2.video_frame_buffer()->StrideY(), | |
1085 kWidth, kHeight, 32)); | |
1086 EXPECT_TRUE(IsEqual(frame1, frame2, 128)); | |
1087 } | |
1088 | |
1089 // Test constructing an image from an I420 MJPG buffer. | |
1090 void ValidateFrame(const char* name, | |
1091 const char* extension, | |
1092 uint32_t fourcc, | |
1093 int data_adjust, | |
1094 int size_adjust, | |
1095 bool expected_result) { | |
1096 T frame; | |
1097 std::unique_ptr<rtc::MemoryStream> ms(LoadSample(name, extension)); | |
1098 ASSERT_TRUE(ms.get() != NULL); | |
1099 const uint8_t* sample = | |
1100 reinterpret_cast<const uint8_t*>(ms.get()->GetBuffer()); | |
1101 size_t sample_size; | |
1102 ms->GetSize(&sample_size); | |
1103 // Optional adjust size to test invalid size. | |
1104 size_t data_size = sample_size + data_adjust; | |
1105 | |
1106 // Allocate a buffer with end page aligned. | |
1107 const int kPadToHeapSized = 16 * 1024 * 1024; | |
1108 std::unique_ptr<uint8_t[]> page_buffer( | |
1109 new uint8_t[((data_size + kPadToHeapSized + 4095) & ~4095)]); | |
1110 uint8_t* data_ptr = page_buffer.get(); | |
1111 if (!data_ptr) { | |
1112 LOG(LS_WARNING) << "Failed to allocate memory for ValidateFrame test."; | |
1113 EXPECT_FALSE(expected_result); // NULL is okay if failure was expected. | |
1114 return; | |
1115 } | |
1116 data_ptr += kPadToHeapSized + (-(static_cast<int>(data_size)) & 4095); | |
1117 memcpy(data_ptr, sample, std::min(data_size, sample_size)); | |
1118 for (int i = 0; i < repeat_; ++i) { | |
1119 EXPECT_EQ(expected_result, frame.Validate(fourcc, kWidth, kHeight, | |
1120 data_ptr, | |
1121 sample_size + size_adjust)); | |
1122 } | |
1123 } | |
1124 | |
1125 // Test validate for I420 MJPG buffer. | |
1126 void ValidateMjpgI420() { | |
1127 ValidateFrame(kJpeg420Filename, kJpegExtension, | |
1128 cricket::FOURCC_MJPG, 0, 0, true); | |
1129 } | |
1130 | |
1131 // Test validate for I422 MJPG buffer. | |
1132 void ValidateMjpgI422() { | |
1133 ValidateFrame(kJpeg422Filename, kJpegExtension, | |
1134 cricket::FOURCC_MJPG, 0, 0, true); | |
1135 } | |
1136 | |
1137 // Test validate for I444 MJPG buffer. | |
1138 void ValidateMjpgI444() { | |
1139 ValidateFrame(kJpeg444Filename, kJpegExtension, | |
1140 cricket::FOURCC_MJPG, 0, 0, true); | |
1141 } | |
1142 | |
1143 // Test validate for I411 MJPG buffer. | |
1144 void ValidateMjpgI411() { | |
1145 ValidateFrame(kJpeg411Filename, kJpegExtension, | |
1146 cricket::FOURCC_MJPG, 0, 0, true); | |
1147 } | |
1148 | |
1149 // Test validate for I400 MJPG buffer. | |
1150 void ValidateMjpgI400() { | |
1151 ValidateFrame(kJpeg400Filename, kJpegExtension, | |
1152 cricket::FOURCC_MJPG, 0, 0, true); | |
1153 } | |
1154 | |
1155 // Test validate for I420 buffer. | |
1156 void ValidateI420() { | |
1157 ValidateFrame(kImageFilename, kYuvExtension, | |
1158 cricket::FOURCC_I420, 0, 0, true); | |
1159 } | |
1160 | |
1161 // Test validate for I420 buffer where size is too small | |
1162 void ValidateI420SmallSize() { | |
1163 ValidateFrame(kImageFilename, kYuvExtension, | |
1164 cricket::FOURCC_I420, 0, -16384, false); | |
1165 } | |
1166 | |
1167 // Test validate for I420 buffer where size is too large (16 MB) | |
1168 // Will produce warning but pass. | |
1169 void ValidateI420LargeSize() { | |
1170 ValidateFrame(kImageFilename, kYuvExtension, | |
1171 cricket::FOURCC_I420, 16000000, 16000000, | |
1172 true); | |
1173 } | |
1174 | |
1175 // Test validate for I420 buffer where size is 1 GB (not reasonable). | |
1176 void ValidateI420HugeSize() { | |
1177 #ifndef WIN32 // TODO(fbarchard): Reenable when fixing bug 9603762. | |
1178 ValidateFrame(kImageFilename, kYuvExtension, | |
1179 cricket::FOURCC_I420, 1000000000u, | |
1180 1000000000u, false); | |
1181 #endif | |
1182 } | |
1183 | |
1184 // The following test that Validate crashes if the size is greater than the | |
1185 // actual buffer size. | |
1186 // TODO(fbarchard): Consider moving a filter into the capturer/plugin. | |
1187 #if defined(_MSC_VER) && !defined(NDEBUG) | |
1188 int ExceptionFilter(unsigned int code, struct _EXCEPTION_POINTERS *ep) { | |
1189 if (code == EXCEPTION_ACCESS_VIOLATION) { | |
1190 LOG(LS_INFO) << "Caught EXCEPTION_ACCESS_VIOLATION as expected."; | |
1191 return EXCEPTION_EXECUTE_HANDLER; | |
1192 } else { | |
1193 LOG(LS_INFO) << "Did not catch EXCEPTION_ACCESS_VIOLATION. Unexpected."; | |
1194 return EXCEPTION_CONTINUE_SEARCH; | |
1195 } | |
1196 } | |
1197 | |
1198 // Test validate fails for truncated MJPG data buffer. If ValidateFrame | |
1199 // crashes the exception handler will return and unittest passes with OK. | |
1200 void ValidateMjpgI420InvalidSize() { | |
1201 __try { | |
1202 ValidateFrame(kJpeg420Filename, kJpegExtension, | |
1203 cricket::FOURCC_MJPG, -16384, 0, false); | |
1204 FAIL() << "Validate was expected to cause EXCEPTION_ACCESS_VIOLATION."; | |
1205 } __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) { | |
1206 return; // Successfully crashed in ValidateFrame. | |
1207 } | |
1208 } | |
1209 | |
1210 // Test validate fails for truncated I420 buffer. | |
1211 void ValidateI420InvalidSize() { | |
1212 __try { | |
1213 ValidateFrame(kImageFilename, kYuvExtension, | |
1214 cricket::FOURCC_I420, -16384, 0, false); | |
1215 FAIL() << "Validate was expected to cause EXCEPTION_ACCESS_VIOLATION."; | |
1216 } __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) { | |
1217 return; // Successfully crashed in ValidateFrame. | |
1218 } | |
1219 } | |
1220 #endif | |
1221 | |
1222 // Test constructing an image from a YUY2 buffer (and synonymous formats). | |
1223 void ConstructYuy2Aliases() { | |
1224 T frame1, frame2, frame3, frame4; | |
1225 std::unique_ptr<rtc::MemoryStream> ms( | |
1226 CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight)); | |
1227 ASSERT_TRUE(ms.get() != NULL); | |
1228 EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight, | |
1229 &frame1)); | |
1230 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, | |
1231 kWidth, kHeight, &frame2)); | |
1232 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUVS, | |
1233 kWidth, kHeight, &frame3)); | |
1234 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUYV, | |
1235 kWidth, kHeight, &frame4)); | |
1236 EXPECT_TRUE(IsEqual(frame1, frame2, 0)); | |
1237 EXPECT_TRUE(IsEqual(frame1, frame3, 0)); | |
1238 EXPECT_TRUE(IsEqual(frame1, frame4, 0)); | |
1239 } | |
1240 | |
1241 // Test constructing an image from a UYVY buffer (and synonymous formats). | |
1242 void ConstructUyvyAliases() { | |
1243 T frame1, frame2, frame3, frame4; | |
1244 std::unique_ptr<rtc::MemoryStream> ms( | |
1245 CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight)); | |
1246 ASSERT_TRUE(ms.get() != NULL); | |
1247 EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight, | |
1248 &frame1)); | |
1249 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY, | |
1250 kWidth, kHeight, &frame2)); | |
1251 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_2VUY, | |
1252 kWidth, kHeight, &frame3)); | |
1253 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_HDYC, | |
1254 kWidth, kHeight, &frame4)); | |
1255 EXPECT_TRUE(IsEqual(frame1, frame2, 0)); | |
1256 EXPECT_TRUE(IsEqual(frame1, frame3, 0)); | |
1257 EXPECT_TRUE(IsEqual(frame1, frame4, 0)); | |
1258 } | |
1259 | |
1260 // Test creating a copy. | |
1261 void ConstructCopy() { | |
1262 T frame1, frame2; | |
1263 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
1264 for (int i = 0; i < repeat_; ++i) { | |
1265 EXPECT_TRUE(frame2.Init(frame1)); | |
1266 } | |
1267 EXPECT_TRUE(IsEqual(frame1, frame2, 0)); | |
1268 } | |
1269 | |
1270 // Test creating a copy and check that it just increments the refcount. | |
1271 void ConstructCopyIsRef() { | |
1272 T frame1, frame2; | |
1273 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
1274 for (int i = 0; i < repeat_; ++i) { | |
1275 EXPECT_TRUE(frame2.Init(frame1)); | |
1276 } | |
1277 EXPECT_TRUE(IsEqual(frame1, frame2, 0)); | |
1278 EXPECT_EQ(frame1.video_frame_buffer(), frame2.video_frame_buffer()); | |
1279 } | |
1280 | |
1281 // Test constructing an image from a YUY2 buffer with a range of sizes. | |
1282 // Only tests that conversion does not crash or corrupt heap. | |
1283 void ConstructYuy2AllSizes() { | |
1284 T frame1, frame2; | |
1285 for (int height = kMinHeightAll; height <= kMaxHeightAll; ++height) { | |
1286 for (int width = kMinWidthAll; width <= kMaxWidthAll; ++width) { | |
1287 std::unique_ptr<rtc::MemoryStream> ms( | |
1288 CreateYuv422Sample(cricket::FOURCC_YUY2, width, height)); | |
1289 ASSERT_TRUE(ms.get() != NULL); | |
1290 EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, width, height, | |
1291 &frame1)); | |
1292 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, | |
1293 width, height, &frame2)); | |
1294 EXPECT_TRUE(IsEqual(frame1, frame2, 0)); | |
1295 } | |
1296 } | |
1297 } | |
1298 | |
1299 // Test constructing an image from a ARGB buffer with a range of sizes. | |
1300 // Only tests that conversion does not crash or corrupt heap. | |
1301 void ConstructARGBAllSizes() { | |
1302 for (int height = kMinHeightAll; height <= kMaxHeightAll; ++height) { | |
1303 for (int width = kMinWidthAll; width <= kMaxWidthAll; ++width) { | |
1304 std::unique_ptr<rtc::MemoryStream> ms( | |
1305 CreateRgbSample(cricket::FOURCC_ARGB, width, height)); | |
1306 ASSERT_TRUE(ms.get() != NULL); | |
1307 std::unique_ptr<T> frame1 = ConvertRgb(ms.get(), cricket::FOURCC_ARGB, | |
1308 width, height); | |
1309 ASSERT_TRUE(frame1); | |
1310 T frame2; | |
1311 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB, | |
1312 width, height, &frame2)); | |
1313 EXPECT_TRUE(IsEqual(*frame1, frame2, 64)); | |
1314 } | |
1315 } | |
1316 // Test a practical window size for screencasting usecase. | |
1317 const int kOddWidth = 1228; | |
1318 const int kOddHeight = 260; | |
1319 for (int j = 0; j < 2; ++j) { | |
1320 for (int i = 0; i < 2; ++i) { | |
1321 std::unique_ptr<rtc::MemoryStream> ms( | |
1322 CreateRgbSample(cricket::FOURCC_ARGB, kOddWidth + i, kOddHeight + j)); | |
1323 ASSERT_TRUE(ms.get() != NULL); | |
1324 std::unique_ptr<T> frame1 = ConvertRgb(ms.get(), cricket::FOURCC_ARGB, | |
1325 kOddWidth + i, kOddHeight + j); | |
1326 ASSERT_TRUE(frame1); | |
1327 T frame2; | |
1328 EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB, | |
1329 kOddWidth + i, kOddHeight + j, &frame2)); | |
1330 EXPECT_TRUE(IsEqual(*frame1, frame2, 64)); | |
1331 } | |
1332 } | |
1333 } | |
1334 | |
1335 ////////////////////// | |
1336 // Conversion tests // | |
1337 ////////////////////// | |
1338 | |
1339 // Test converting from I420 to I422. | |
1340 void ConvertToI422Buffer() { | |
1341 T frame1, frame2; | |
1342 size_t out_size = kWidth * kHeight * 2; | |
1343 std::unique_ptr<uint8_t[]> buf(new uint8_t[out_size + kAlignment]); | |
1344 uint8_t* y = ALIGNP(buf.get(), kAlignment); | |
1345 uint8_t* u = y + kWidth * kHeight; | |
1346 uint8_t* v = u + (kWidth / 2) * kHeight; | |
1347 ASSERT_TRUE(LoadFrameNoRepeat(&frame1)); | |
1348 for (int i = 0; i < repeat_; ++i) { | |
1349 EXPECT_EQ(0, libyuv::I420ToI422(frame1.video_frame_buffer()->DataY(), | |
1350 frame1.video_frame_buffer()->StrideY(), | |
1351 frame1.video_frame_buffer()->DataU(), | |
1352 frame1.video_frame_buffer()->StrideU(), | |
1353 frame1.video_frame_buffer()->DataV(), | |
1354 frame1.video_frame_buffer()->StrideV(), | |
1355 y, kWidth, | |
1356 u, kWidth / 2, | |
1357 v, kWidth / 2, | |
1358 kWidth, kHeight)); | |
1359 } | |
1360 EXPECT_TRUE(frame2.Init(cricket::FOURCC_I422, kWidth, kHeight, kWidth, | |
1361 kHeight, y, out_size, 1, 1, 0, | |
1362 webrtc::kVideoRotation_0)); | |
1363 EXPECT_TRUE(IsEqual(frame1, frame2, 1)); | |
1364 } | |
1365 | |
1366 int repeat_; | |
1367 }; | |
1368 | |
1369 #endif // WEBRTC_MEDIA_BASE_VIDEOFRAME_UNITTEST_H_ | |
OLD | NEW |