/*
* libjingle
* Copyright 2014 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <string>
#include "libyuv/convert.h"
#include "libyuv/convert_from.h"
#include "libyuv/convert_from_argb.h"
#include "libyuv/mjpeg_decoder.h"
#include "libyuv/planar_functions.h"
#include "talk/media/base/testutils.h"
#include "talk/media/base/videocommon.h"
#include "webrtc/base/flags.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/scoped_ptr.h"
// Undefine macros for the windows build.
#undef max
#undef min
using cricket::DumpPlanarYuvTestImage;
DEFINE_bool(planarfunctions_dump, false,
"whether to write out scaled images for inspection");
DEFINE_int(planarfunctions_repeat, 1,
"how many times to perform each scaling operation (for perf testing)");
namespace cricket {
// Number of testing colors in each color channel.
static const int kTestingColorChannelResolution = 6;
// The total number of testing colors
// kTestingColorNum = kTestingColorChannelResolution^3;
static const int kTestingColorNum = kTestingColorChannelResolution *
kTestingColorChannelResolution * kTestingColorChannelResolution;
static const int kWidth = 1280;
static const int kHeight = 720;
static const int kAlignment = 16;
class PlanarFunctionsTest : public testing::TestWithParam<int> {
protected:
PlanarFunctionsTest() : dump_(false), repeat_(1) {
InitializeColorBand();
}
virtual void SetUp() {
dump_ = FLAG_planarfunctions_dump;
repeat_ = FLAG_planarfunctions_repeat;
}
// Initialize the color band for testing.
void InitializeColorBand() {
testing_color_y_.reset(new uint8_t[kTestingColorNum]);
testing_color_u_.reset(new uint8_t[kTestingColorNum]);
testing_color_v_.reset(new uint8_t[kTestingColorNum]);
testing_color_r_.reset(new uint8_t[kTestingColorNum]);
testing_color_g_.reset(new uint8_t[kTestingColorNum]);
testing_color_b_.reset(new uint8_t[kTestingColorNum]);
int color_counter = 0;
for (int i = 0; i < kTestingColorChannelResolution; ++i) {
uint8_t color_r =
static_cast<uint8_t>(i * 255 / (kTestingColorChannelResolution - 1));
for (int j = 0; j < kTestingColorChannelResolution; ++j) {
uint8_t color_g = static_cast<uint8_t>(
j * 255 / (kTestingColorChannelResolution - 1));
for (int k = 0; k < kTestingColorChannelResolution; ++k) {
uint8_t color_b = static_cast<uint8_t>(
k * 255 / (kTestingColorChannelResolution - 1));
testing_color_r_[color_counter] = color_r;
testing_color_g_[color_counter] = color_g;
testing_color_b_[color_counter] = color_b;
// Converting the testing RGB colors to YUV colors.
ConvertRgbPixel(color_r, color_g, color_b,
&(testing_color_y_[color_counter]),
&(testing_color_u_[color_counter]),
&(testing_color_v_[color_counter]));
++color_counter;
}
}
}
}
// Simple and slow RGB->YUV conversion. From NTSC standard, c/o Wikipedia.
// (from lmivideoframe_unittest.cc)
void ConvertRgbPixel(uint8_t r,
uint8_t g,
uint8_t b,
uint8_t* y,
uint8_t* u,
uint8_t* v) {
*y = ClampUint8(.257 * r + .504 * g + .098 * b + 16);
*u = ClampUint8(-.148 * r - .291 * g + .439 * b + 128);
*v = ClampUint8(.439 * r - .368 * g - .071 * b + 128);
}
uint8_t ClampUint8(double value) {
value = std::max(0., std::min(255., value));
uint8_t uint8_value = static_cast<uint8_t>(value);
return uint8_value;
}
// Generate a Red-Green-Blue inter-weaving chessboard-like
// YUV testing image (I420/I422/I444).
// The pattern looks like c0 c1 c2 c3 ...
// c1 c2 c3 c4 ...
// c2 c3 c4 c5 ...
// ...............
// The size of each chrome block is (block_size) x (block_size).
uint8_t* CreateFakeYuvTestingImage(int height,
int width,
int block_size,
libyuv::JpegSubsamplingType subsample_type,
uint8_t*& y_pointer,
uint8_t*& u_pointer,
uint8_t*& v_pointer) {
if (height <= 0 || width <= 0 || block_size <= 0) { return NULL; }
int y_size = height * width;
int u_size, v_size;
int vertical_sample_ratio = 1, horizontal_sample_ratio = 1;
switch (subsample_type) {
case libyuv::kJpegYuv420:
u_size = ((height + 1) >> 1) * ((width + 1) >> 1);
v_size = u_size;
vertical_sample_ratio = 2, horizontal_sample_ratio = 2;
break;
case libyuv::kJpegYuv422:
u_size = height * ((width + 1) >> 1);
v_size = u_size;
vertical_sample_ratio = 1, horizontal_sample_ratio = 2;
break;
case libyuv::kJpegYuv444:
v_size = u_size = y_size;
vertical_sample_ratio = 1, horizontal_sample_ratio = 1;
break;
case libyuv::kJpegUnknown:
default:
return NULL;
break;
}
uint8_t* image_pointer = new uint8_t[y_size + u_size + v_size + kAlignment];
y_pointer = ALIGNP(image_pointer, kAlignment);
u_pointer = ALIGNP(&image_pointer[y_size], kAlignment);
v_pointer = ALIGNP(&image_pointer[y_size + u_size], kAlignment);
uint8_t* current_y_pointer = y_pointer;
uint8_t* current_u_pointer = u_pointer;
uint8_t* current_v_pointer = v_pointer;
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int color = ((i / block_size) + (j / block_size)) % kTestingColorNum;
*(current_y_pointer++) = testing_color_y_[color];
if (i % horizontal_sample_ratio == 0 &&
j % vertical_sample_ratio == 0) {
*(current_u_pointer++) = testing_color_u_[color];
*(current_v_pointer++) = testing_color_v_[color];
}
}
}
return image_pointer;
}
// Generate a Red-Green-Blue inter-weaving chessboard-like
// YUY2/UYVY testing image.
// The pattern looks like c0 c1 c2 c3 ...
// c1 c2 c3 c4 ...
// c2 c3 c4 c5 ...
// ...............
// The size of each chrome block is (block_size) x (block_size).
uint8_t* CreateFakeInterleaveYuvTestingImage(int height,
int width,
int block_size,
uint8_t*& yuv_pointer,
FourCC fourcc_type) {
if (height <= 0 || width <= 0 || block_size <= 0) { return NULL; }
if (fourcc_type != FOURCC_YUY2 && fourcc_type != FOURCC_UYVY) {
LOG(LS_ERROR) << "Format " << static_cast<int>(fourcc_type)
<< " is not supported.";
return NULL;
}
// Regularize the width of the output to be even.
int awidth = (width + 1) & ~1;
uint8_t* image_pointer = new uint8_t[2 * height * awidth + kAlignment];
yuv_pointer = ALIGNP(image_pointer, kAlignment);
uint8_t* current_yuv_pointer = yuv_pointer;
switch (fourcc_type) {
case FOURCC_YUY2: {
for (int j = 0; j < height; ++j) {
for (int i = 0; i < awidth; i += 2, current_yuv_pointer += 4) {
int color1 = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
int color2 = (((i + 1) / block_size) + (j / block_size)) %
kTestingColorNum;
current_yuv_pointer[0] = testing_color_y_[color1];
if (i < width) {
current_yuv_pointer[1] = static_cast<uint8_t>(
(static_cast<uint32_t>(testing_color_u_[color1]) +
static_cast<uint32_t>(testing_color_u_[color2])) /
2);
current_yuv_pointer[2] = testing_color_y_[color2];
current_yuv_pointer[3] = static_cast<uint8_t>(
(static_cast<uint32_t>(testing_color_v_[color1]) +
static_cast<uint32_t>(testing_color_v_[color2])) /
2);
} else {
current_yuv_pointer[1] = testing_color_u_[color1];
current_yuv_pointer[2] = 0;
current_yuv_pointer[3] = testing_color_v_[color1];
}
}
}
break;
}
case FOURCC_UYVY: {
for (int j = 0; j < height; ++j) {
for (int i = 0; i < awidth; i += 2, current_yuv_pointer += 4) {
int color1 = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
int color2 = (((i + 1) / block_size) + (j / block_size)) %
kTestingColorNum;
if (i < width) {
current_yuv_pointer[0] = static_cast<uint8_t>(
(static_cast<uint32_t>(testing_color_u_[color1]) +
static_cast<uint32_t>(testing_color_u_[color2])) /
2);
current_yuv_pointer[1] = testing_color_y_[color1];
current_yuv_pointer[2] = static_cast<uint8_t>(
(static_cast<uint32_t>(testing_color_v_[color1]) +
static_cast<uint32_t>(testing_color_v_[color2])) /
2);
current_yuv_pointer[3] = testing_color_y_[color2];
} else {
current_yuv_pointer[0] = testing_color_u_[color1];
current_yuv_pointer[1] = testing_color_y_[color1];
current_yuv_pointer[2] = testing_color_v_[color1];
current_yuv_pointer[3] = 0;
}
}
}
break;
}
}
return image_pointer;
}
// Generate a Red-Green-Blue inter-weaving chessboard-like
// NV12 testing image.
// (Note: No interpolation is used.)
// The pattern looks like c0 c1 c2 c3 ...
// c1 c2 c3 c4 ...
// c2 c3 c4 c5 ...
// ...............
// The size of each chrome block is (block_size) x (block_size).
uint8_t* CreateFakeNV12TestingImage(int height,
int width,
int block_size,
uint8_t*& y_pointer,
uint8_t*& uv_pointer) {
if (height <= 0 || width <= 0 || block_size <= 0) { return NULL; }
uint8_t* image_pointer =
new uint8_t[height * width +
((height + 1) / 2) * ((width + 1) / 2) * 2 + kAlignment];
y_pointer = ALIGNP(image_pointer, kAlignment);
uv_pointer = y_pointer + height * width;
uint8_t* current_uv_pointer = uv_pointer;
uint8_t* current_y_pointer = y_pointer;
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int color = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
*(current_y_pointer++) = testing_color_y_[color];
}
if (j % 2 == 0) {
for (int i = 0; i < width; i += 2, current_uv_pointer += 2) {
int color = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
current_uv_pointer[0] = testing_color_u_[color];
current_uv_pointer[1] = testing_color_v_[color];
}
}
}
return image_pointer;
}
// Generate a Red-Green-Blue inter-weaving chessboard-like
// M420 testing image.
// (Note: No interpolation is used.)
// The pattern looks like c0 c1 c2 c3 ...
// c1 c2 c3 c4 ...
// c2 c3 c4 c5 ...
// ...............
// The size of each chrome block is (block_size) x (block_size).
uint8_t* CreateFakeM420TestingImage(int height,
int width,
int block_size,
uint8_t*& m420_pointer) {
if (height <= 0 || width <= 0 || block_size <= 0) { return NULL; }
uint8_t* image_pointer =
new uint8_t[height * width +
((height + 1) / 2) * ((width + 1) / 2) * 2 + kAlignment];
m420_pointer = ALIGNP(image_pointer, kAlignment);
uint8_t* current_m420_pointer = m420_pointer;
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int color = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
*(current_m420_pointer++) = testing_color_y_[color];
}
if (j % 2 == 1) {
for (int i = 0; i < width; i += 2, current_m420_pointer += 2) {
int color = ((i / block_size) + ((j - 1) / block_size)) %
kTestingColorNum;
current_m420_pointer[0] = testing_color_u_[color];
current_m420_pointer[1] = testing_color_v_[color];
}
}
}
return image_pointer;
}
// Generate a Red-Green-Blue inter-weaving chessboard-like
// ARGB/ABGR/RAW/BG24 testing image.
// The pattern looks like c0 c1 c2 c3 ...
// c1 c2 c3 c4 ...
// c2 c3 c4 c5 ...
// ...............
// The size of each chrome block is (block_size) x (block_size).
uint8_t* CreateFakeArgbTestingImage(int height,
int width,
int block_size,
uint8_t*& argb_pointer,
FourCC fourcc_type) {
if (height <= 0 || width <= 0 || block_size <= 0) { return NULL; }
uint8_t* image_pointer = NULL;
if (fourcc_type == FOURCC_ABGR || fourcc_type == FOURCC_BGRA ||
fourcc_type == FOURCC_ARGB) {
image_pointer = new uint8_t[height * width * 4 + kAlignment];
} else if (fourcc_type == FOURCC_RAW || fourcc_type == FOURCC_24BG) {
image_pointer = new uint8_t[height * width * 3 + kAlignment];
} else {
LOG(LS_ERROR) << "Format " << static_cast<int>(fourcc_type)
<< " is not supported.";
return NULL;
}
argb_pointer = ALIGNP(image_pointer, kAlignment);
uint8_t* current_pointer = argb_pointer;
switch (fourcc_type) {
case FOURCC_ARGB: {
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int color = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
*(current_pointer++) = testing_color_b_[color];
*(current_pointer++) = testing_color_g_[color];
*(current_pointer++) = testing_color_r_[color];
*(current_pointer++) = 255;
}
}
break;
}
case FOURCC_ABGR: {
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int color = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
*(current_pointer++) = testing_color_r_[color];
*(current_pointer++) = testing_color_g_[color];
*(current_pointer++) = testing_color_b_[color];
*(current_pointer++) = 255;
}
}
break;
}
case FOURCC_BGRA: {
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int color = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
*(current_pointer++) = 255;
*(current_pointer++) = testing_color_r_[color];
*(current_pointer++) = testing_color_g_[color];
*(current_pointer++) = testing_color_b_[color];
}
}
break;
}
case FOURCC_24BG: {
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int color = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
*(current_pointer++) = testing_color_b_[color];
*(current_pointer++) = testing_color_g_[color];
*(current_pointer++) = testing_color_r_[color];
}
}
break;
}
case FOURCC_RAW: {
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int color = ((i / block_size) + (j / block_size)) %
kTestingColorNum;
*(current_pointer++) = testing_color_r_[color];
*(current_pointer++) = testing_color_g_[color];
*(current_pointer++) = testing_color_b_[color];
}
}
break;
}
default: {
LOG(LS_ERROR) << "Format " << static_cast<int>(fourcc_type)
<< " is not supported.";
}
}
return image_pointer;
}
// Check if two memory chunks are equal.
// (tolerate MSE errors within a threshold).
static bool IsMemoryEqual(const uint8_t* ibuf,
const uint8_t* obuf,
int osize,
double average_error) {
double sse = cricket::ComputeSumSquareError(ibuf, obuf, osize);
double error = sse / osize; // Mean Squared Error.
double PSNR = cricket::ComputePSNR(sse, osize);
LOG(LS_INFO) << "Image MSE: " << error << " Image PSNR: " << PSNR
<< " First Diff Byte: " << FindDiff(ibuf, obuf, osize);
return (error < average_error);
}
// Returns the index of the first differing byte. Easier to debug than memcmp.
static int FindDiff(const uint8_t* buf1, const uint8_t* buf2, int len) {
int i = 0;
while (i < len && buf1[i] == buf2[i]) {
i++;
}
return (i < len) ? i : -1;
}
// Dump the result image (ARGB format).
void DumpArgbImage(const uint8_t* obuf, int width, int height) {
DumpPlanarArgbTestImage(GetTestName(), obuf, width, height);
}
// Dump the result image (YUV420 format).
void DumpYuvImage(const uint8_t* obuf, int width, int height) {
DumpPlanarYuvTestImage(GetTestName(), obuf, width, height);
}
std::string GetTestName() {
const testing::TestInfo* const test_info =
testing::UnitTest::GetInstance()->current_test_info();
std::string test_name(test_info->name());
return test_name;
}
bool dump_;
int repeat_;
// Y, U, V and R, G, B channels of testing colors.
rtc::scoped_ptr<uint8_t[]> testing_color_y_;
rtc::scoped_ptr<uint8_t[]> testing_color_u_;
rtc::scoped_ptr<uint8_t[]> testing_color_v_;
rtc::scoped_ptr<uint8_t[]> testing_color_r_;
rtc::scoped_ptr<uint8_t[]> testing_color_g_;
rtc::scoped_ptr<uint8_t[]> testing_color_b_;
};
TEST_F(PlanarFunctionsTest, I420Copy) {
uint8_t* y_pointer = nullptr;
uint8_t* u_pointer = nullptr;
uint8_t* v_pointer = nullptr;
int y_pitch = kWidth;
int u_pitch = (kWidth + 1) >> 1;
int v_pitch = (kWidth + 1) >> 1;
int y_size = kHeight * kWidth;
int uv_size = ((kHeight + 1) >> 1) * ((kWidth + 1) >> 1);
int block_size = 3;
// Generate a fake input image.
rtc::scoped_ptr<uint8_t[]> yuv_input(CreateFakeYuvTestingImage(
kHeight, kWidth, block_size, libyuv::kJpegYuv420, y_pointer, u_pointer,
v_pointer));
// Allocate space for the output image.
rtc::scoped_ptr<uint8_t[]> yuv_output(
new uint8_t[I420_SIZE(kHeight, kWidth) + kAlignment]);
uint8_t* y_output_pointer = ALIGNP(yuv_output.get(), kAlignment);
uint8_t* u_output_pointer = y_output_pointer + y_size;
uint8_t* v_output_pointer = u_output_pointer + uv_size;
for (int i = 0; i < repeat_; ++i) {
libyuv::I420Copy(y_pointer, y_pitch,
u_pointer, u_pitch,
v_pointer, v_pitch,
y_output_pointer, y_pitch,
u_output_pointer, u_pitch,
v_output_pointer, v_pitch,
kWidth, kHeight);
}
// Expect the copied frame to be exactly the same.
EXPECT_TRUE(IsMemoryEqual(y_output_pointer, y_pointer,
I420_SIZE(kHeight, kWidth), 1.e-6));
if (dump_) { DumpYuvImage(y_output_pointer, kWidth, kHeight); }
}
TEST_F(PlanarFunctionsTest, I422ToI420) {
uint8_t* y_pointer = nullptr;
uint8_t* u_pointer = nullptr;
uint8_t* v_pointer = nullptr;
int y_pitch = kWidth;
int u_pitch = (kWidth + 1) >> 1;
int v_pitch = (kWidth + 1) >> 1;
int y_size = kHeight * kWidth;
int uv_size = ((kHeight + 1) >> 1) * ((kWidth + 1) >> 1);
int block_size = 2;
// Generate a fake input image.
rtc::scoped_ptr<uint8_t[]> yuv_input(CreateFakeYuvTestingImage(
kHeight, kWidth, block_size, libyuv::kJpegYuv422, y_pointer, u_pointer,
v_pointer));
// Allocate space for the output image.
rtc::scoped_ptr<uint8_t[]> yuv_output(
new uint8_t[I420_SIZE(kHeight, kWidth) + kAlignment]);
uint8_t* y_output_pointer = ALIGNP(yuv_output.get(), kAlignment);
uint8_t* u_output_pointer = y_output_pointer + y_size;
uint8_t* v_output_pointer = u_output_pointer + uv_size;
// Generate the expected output.
uint8_t* y_expected_pointer = nullptr;
uint8_t* u_expected_pointer = nullptr;
uint8_t* v_expected_pointer = nullptr;
rtc::scoped_ptr<uint8_t[]> yuv_output_expected(CreateFakeYuvTestingImage(
kHeight, kWidth, block_size, libyuv::kJpegYuv420, y_expected_pointer,
u_expected_pointer, v_expected_pointer));
for (int i = 0; i < repeat_; ++i) {
libyuv::I422ToI420(y_pointer, y_pitch,
u_pointer, u_pitch,
v_pointer, v_pitch,
y_output_pointer, y_pitch,
u_output_pointer, u_pitch,
v_output_pointer, v_pitch,
kWidth, kHeight);
}
// Compare the output frame with what is expected; expect exactly the same.
// Note: MSE should be set to a larger threshold if an odd block width
// is used, since the conversion will be lossy.
EXPECT_TRUE(IsMemoryEqual(y_output_pointer, y_expected_pointer,
I420_SIZE(kHeight, kWidth), 1.e-6));
if (dump_) { DumpYuvImage(y_output_pointer, kWidth, kHeight); }
}
TEST_P(PlanarFunctionsTest, M420ToI420) {
// Get the unalignment offset
int unalignment = GetParam();
uint8_t* m420_pointer = NULL;
int y_pitch = kWidth;
int m420_pitch = kWidth;
int u_pitch = (kWidth + 1) >> 1;
int v_pitch = (kWidth + 1) >> 1;
int y_size = kHeight * kWidth;
int uv_size = ((kHeight + 1) >> 1) * ((kWidth + 1) >> 1);
int block_size = 2;
// Generate a fake input image.
rtc::scoped_ptr<uint8_t[]> yuv_input(
CreateFakeM420TestingImage(kHeight, kWidth, block_size, m420_pointer));
// Allocate space for the output image.
rtc::scoped_ptr<uint8_t[]> yuv_output(
new uint8_t[I420_SIZE(kHeight, kWidth) + kAlignment + unalignment]);
uint8_t* y_output_pointer =
ALIGNP(yuv_output.get(), kAlignment) + unalignment;
uint8_t* u_output_pointer = y_output_pointer + y_size;
uint8_t* v_output_pointer = u_output_pointer + uv_size;
// Generate the expected output.
uint8_t* y_expected_pointer = nullptr;
uint8_t* u_expected_pointer = nullptr;
uint8_t* v_expected_pointer = nullptr;
rtc::scoped_ptr<uint8_t[]> yuv_output_expected(CreateFakeYuvTestingImage(
kHeight, kWidth, block_size, libyuv::kJpegYuv420, y_expected_pointer,
u_expected_pointer, v_expected_pointer));
for (int i = 0; i < repeat_; ++i) {
libyuv::M420ToI420(m420_pointer, m420_pitch,
y_output_pointer, y_pitch,
u_output_pointer, u_pitch,
v_output_pointer, v_pitch,
kWidth, kHeight);
}
// Compare the output frame with what is expected; expect exactly the same.
// Note: MSE should be set to a larger threshold if an odd block width
// is used, since the conversion will be lossy.
EXPECT_TRUE(IsMemoryEqual(y_output_pointer, y_expected_pointer,
I420_SIZE(kHeight, kWidth), 1.e-6));
if (dump_) { DumpYuvImage(y_output_pointer, kWidth, kHeight); }
}
TEST_P(PlanarFunctionsTest, NV12ToI420) {
// Get the unalignment offset
int unalignment = GetParam();
uint8_t* y_pointer = nullptr;
uint8_t* uv_pointer = nullptr;
int y_pitch = kWidth;
int uv_pitch = 2 * ((kWidth + 1) >> 1);
int u_pitch = (kWidth + 1) >> 1;
int v_pitch = (kWidth + 1) >> 1;
int y_size = kHeight * kWidth;
int uv_size = ((kHeight + 1) >> 1) * ((kWidth + 1) >> 1);
int block_size = 2;
// Generate a fake input image.
rtc::scoped_ptr<uint8_t[]> yuv_input(CreateFakeNV12TestingImage(
kHeight, kWidth, block_size, y_pointer, uv_pointer));
// Allocate space for the output image.
rtc::scoped_ptr<uint8_t[]> yuv_output(
new uint8_t[I420_SIZE(kHeight, kWidth) + kAlignment + unalignment]);
uint8_t* y_output_pointer =
ALIGNP(yuv_output.get(), kAlignment) + unalignment;
uint8_t* u_output_pointer = y_output_pointer + y_size;
uint8_t* v_output_pointer = u_output_pointer + uv_size;
// Generate the expected output.
uint8_t* y_expected_pointer = nullptr;
uint8_t* u_expected_pointer = nullptr;
uint8_t* v_expected_pointer = nullptr;
rtc::scoped_ptr<uint8_t[]> yuv_output_expected(CreateFakeYuvTestingImage(
kHeight, kWidth, block_size, libyuv::kJpegYuv420, y_expected_pointer,
u_expected_pointer, v_expected_pointer));
for (int i = 0; i < repeat_; ++i) {
libyuv::NV12ToI420(y_pointer, y_pitch,
uv_pointer, uv_pitch,
y_output_pointer, y_pitch,
u_output_pointer, u_pitch,
v_output_pointer, v_pitch,
kWidth, kHeight);
}
// Compare the output frame with what is expected; expect exactly the same.
// Note: MSE should be set to a larger threshold if an odd block width
// is used, since the conversion will be lossy.
EXPECT_TRUE(IsMemoryEqual(y_output_pointer, y_expected_pointer,
I420_SIZE(kHeight, kWidth), 1.e-6));
if (dump_) { DumpYuvImage(y_output_pointer, kWidth, kHeight); }
}
// A common macro for testing converting YUY2/UYVY to I420.
#define TEST_YUVTOI420(SRC_NAME, MSE, BLOCK_SIZE) \
TEST_P(PlanarFunctionsTest, SRC_NAME##ToI420) { \
/* Get the unalignment offset.*/ \
int unalignment = GetParam(); \
uint8_t* yuv_pointer = nullptr; \
int yuv_pitch = 2 * ((kWidth + 1) & ~1); \
int y_pitch = kWidth; \
int u_pitch = (kWidth + 1) >> 1; \
int v_pitch = (kWidth + 1) >> 1; \
int y_size = kHeight * kWidth; \
int uv_size = ((kHeight + 1) >> 1) * ((kWidth + 1) >> 1); \
int block_size = 2; \
/* Generate a fake input image.*/ \
rtc::scoped_ptr<uint8_t[]> yuv_input(CreateFakeInterleaveYuvTestingImage( \
kHeight, kWidth, BLOCK_SIZE, yuv_pointer, FOURCC_##SRC_NAME)); \
/* Allocate space for the output image.*/ \
rtc::scoped_ptr<uint8_t[]> yuv_output( \
new uint8_t[I420_SIZE(kHeight, kWidth) + kAlignment + unalignment]); \
uint8_t* y_output_pointer = \
ALIGNP(yuv_output.get(), kAlignment) + unalignment; \
uint8_t* u_output_pointer = y_output_pointer + y_size; \
uint8_t* v_output_pointer = u_output_pointer + uv_size; \
/* Generate the expected output.*/ \
uint8_t* y_expected_pointer = nullptr; \
uint8_t* u_expected_pointer = nullptr; \
uint8_t* v_expected_pointer = nullptr; \
rtc::scoped_ptr<uint8_t[]> yuv_output_expected(CreateFakeYuvTestingImage( \
kHeight, kWidth, block_size, libyuv::kJpegYuv420, y_expected_pointer, \
u_expected_pointer, v_expected_pointer)); \
for (int i = 0; i < repeat_; ++i) { \
libyuv::SRC_NAME##ToI420(yuv_pointer, yuv_pitch, y_output_pointer, \
y_pitch, u_output_pointer, u_pitch, \
v_output_pointer, v_pitch, kWidth, kHeight); \
} \
/* Compare the output frame with what is expected.*/ \
/* Note: MSE should be set to a larger threshold if an odd block width*/ \
/* is used, since the conversion will be lossy.*/ \
EXPECT_TRUE(IsMemoryEqual(y_output_pointer, y_expected_pointer, \
I420_SIZE(kHeight, kWidth), MSE)); \
if (dump_) { \
DumpYuvImage(y_output_pointer, kWidth, kHeight); \
} \
}
// TEST_P(PlanarFunctionsTest, YUV2ToI420)
TEST_YUVTOI420(YUY2, 1.e-6, 2);
// TEST_P(PlanarFunctionsTest, UYVYToI420)
TEST_YUVTOI420(UYVY, 1.e-6, 2);
// A common macro for testing converting I420 to ARGB, BGRA and ABGR.
#define TEST_YUVTORGB(SRC_NAME, DST_NAME, JPG_TYPE, MSE, BLOCK_SIZE) \
TEST_F(PlanarFunctionsTest, SRC_NAME##To##DST_NAME) { \
uint8_t* y_pointer = nullptr; \
uint8_t* u_pointer = nullptr; \
uint8_t* v_pointer = nullptr; \
uint8_t* argb_expected_pointer = NULL; \
int y_pitch = kWidth; \
int u_pitch = (kWidth + 1) >> 1; \
int v_pitch = (kWidth + 1) >> 1; \
/* Generate a fake input image.*/ \
rtc::scoped_ptr<uint8_t[]> yuv_input( \
CreateFakeYuvTestingImage(kHeight, kWidth, BLOCK_SIZE, JPG_TYPE, \
y_pointer, u_pointer, v_pointer)); \
/* Generate the expected output.*/ \
rtc::scoped_ptr<uint8_t[]> argb_expected( \
CreateFakeArgbTestingImage(kHeight, kWidth, BLOCK_SIZE, \
argb_expected_pointer, FOURCC_##DST_NAME)); \
/* Allocate space for the output.*/ \
rtc::scoped_ptr<uint8_t[]> argb_output( \
new uint8_t[kHeight * kWidth * 4 + kAlignment]); \
uint8_t* argb_pointer = ALIGNP(argb_expected.get(), kAlignment); \
for (int i = 0; i < repeat_; ++i) { \
libyuv::SRC_NAME##To##DST_NAME(y_pointer, y_pitch, u_pointer, u_pitch, \
v_pointer, v_pitch, argb_pointer, \
kWidth * 4, kWidth, kHeight); \
} \
EXPECT_TRUE(IsMemoryEqual(argb_expected_pointer, argb_pointer, \
kHeight* kWidth * 4, MSE)); \
if (dump_) { \
DumpArgbImage(argb_pointer, kWidth, kHeight); \
} \
}
// TEST_F(PlanarFunctionsTest, I420ToARGB)
TEST_YUVTORGB(I420, ARGB, libyuv::kJpegYuv420, 3., 2);
// TEST_F(PlanarFunctionsTest, I420ToABGR)
TEST_YUVTORGB(I420, ABGR, libyuv::kJpegYuv420, 3., 2);
// TEST_F(PlanarFunctionsTest, I420ToBGRA)
TEST_YUVTORGB(I420, BGRA, libyuv::kJpegYuv420, 3., 2);
// TEST_F(PlanarFunctionsTest, I422ToARGB)
TEST_YUVTORGB(I422, ARGB, libyuv::kJpegYuv422, 3., 2);
// TEST_F(PlanarFunctionsTest, I444ToARGB)
TEST_YUVTORGB(I444, ARGB, libyuv::kJpegYuv444, 3., 3);
// Note: an empirical MSE tolerance 3.0 is used here for the probable
// error from float-to-uint8_t type conversion.
TEST_F(PlanarFunctionsTest, I400ToARGB_Reference) {
uint8_t* y_pointer = nullptr;
uint8_t* u_pointer = nullptr;
uint8_t* v_pointer = nullptr;
int y_pitch = kWidth;
int u_pitch = (kWidth + 1) >> 1;
int v_pitch = (kWidth + 1) >> 1;
int block_size = 3;
// Generate a fake input image.
rtc::scoped_ptr<uint8_t[]> yuv_input(CreateFakeYuvTestingImage(
kHeight, kWidth, block_size, libyuv::kJpegYuv420, y_pointer, u_pointer,
v_pointer));
// As the comparison standard, we convert a grayscale image (by setting both
// U and V channels to be 128) using an I420 converter.
int uv_size = ((kHeight + 1) >> 1) * ((kWidth + 1) >> 1);
rtc::scoped_ptr<uint8_t[]> uv(new uint8_t[uv_size + kAlignment]);
u_pointer = v_pointer = ALIGNP(uv.get(), kAlignment);
memset(u_pointer, 128, uv_size);
// Allocate space for the output image and generate the expected output.
rtc::scoped_ptr<uint8_t[]> argb_expected(
new uint8_t[kHeight * kWidth * 4 + kAlignment]);
rtc::scoped_ptr<uint8_t[]> argb_output(
new uint8_t[kHeight * kWidth * 4 + kAlignment]);
uint8_t* argb_expected_pointer = ALIGNP(argb_expected.get(), kAlignment);
uint8_t* argb_pointer = ALIGNP(argb_output.get(), kAlignment);
libyuv::I420ToARGB(y_pointer, y_pitch,
u_pointer, u_pitch,
v_pointer, v_pitch,
argb_expected_pointer, kWidth * 4,
kWidth, kHeight);
for (int i = 0; i < repeat_; ++i) {
libyuv::I400ToARGB_Reference(y_pointer, y_pitch,
argb_pointer, kWidth * 4,
kWidth, kHeight);
}
// Note: I420ToARGB and I400ToARGB_Reference should produce identical results.
EXPECT_TRUE(IsMemoryEqual(argb_expected_pointer, argb_pointer,
kHeight * kWidth * 4, 2.));
if (dump_) { DumpArgbImage(argb_pointer, kWidth, kHeight); }
}
TEST_P(PlanarFunctionsTest, I400ToARGB) {
// Get the unalignment offset
int unalignment = GetParam();
uint8_t* y_pointer = nullptr;
uint8_t* u_pointer = nullptr;
uint8_t* v_pointer = nullptr;
int y_pitch = kWidth;
int u_pitch = (kWidth + 1) >> 1;
int v_pitch = (kWidth + 1) >> 1;
int block_size = 3;
// Generate a fake input image.
rtc::scoped_ptr<uint8_t[]> yuv_input(CreateFakeYuvTestingImage(
kHeight, kWidth, block_size, libyuv::kJpegYuv420, y_pointer, u_pointer,
v_pointer));
// As the comparison standard, we convert a grayscale image (by setting both
// U and V channels to be 128) using an I420 converter.
int uv_size = ((kHeight + 1) >> 1) * ((kWidth + 1) >> 1);
// 1 byte extra if in the unaligned mode.
rtc::scoped_ptr<uint8_t[]> uv(new uint8_t[uv_size * 2 + kAlignment]);
u_pointer = ALIGNP(uv.get(), kAlignment);
v_pointer = u_pointer + uv_size;
memset(u_pointer, 128, uv_size);
memset(v_pointer, 128, uv_size);
// Allocate space for the output image and generate the expected output.
rtc::scoped_ptr<uint8_t[]> argb_expected(
new uint8_t[kHeight * kWidth * 4 + kAlignment]);
// 1 byte extra if in the misalinged mode.
rtc::scoped_ptr<uint8_t[]> argb_output(
new uint8_t[kHeight * kWidth * 4 + kAlignment + unalignment]);
uint8_t* argb_expected_pointer = ALIGNP(argb_expected.get(), kAlignment);
uint8_t* argb_pointer = ALIGNP(argb_output.get(), kAlignment) + unalignment;
libyuv::I420ToARGB(y_pointer, y_pitch,
u_pointer, u_pitch,
v_pointer, v_pitch,
argb_expected_pointer, kWidth * 4,
kWidth, kHeight);
for (int i = 0; i < repeat_; ++i) {
libyuv::I400ToARGB(y_pointer, y_pitch,
argb_pointer, kWidth * 4,
kWidth, kHeight);
}
// Note: current I400ToARGB uses an approximate method,
// so the error tolerance is larger here.
EXPECT_TRUE(IsMemoryEqual(argb_expected_pointer, argb_pointer,
kHeight * kWidth * 4, 64.0));
if (dump_) { DumpArgbImage(argb_pointer, kWidth, kHeight); }
}
TEST_P(PlanarFunctionsTest, ARGBToI400) {
// Get the unalignment offset
int unalignment = GetParam();
// Create a fake ARGB input image.
uint8_t* y_pointer = NULL, * u_pointer = NULL, * v_pointer = NULL;
uint8_t* argb_pointer = NULL;
int block_size = 3;
// Generate a fake input image.
rtc::scoped_ptr<uint8_t[]> argb_input(CreateFakeArgbTestingImage(
kHeight, kWidth, block_size, argb_pointer, FOURCC_ARGB));
// Generate the expected output. Only Y channel is used
rtc::scoped_ptr<uint8_t[]> yuv_expected(CreateFakeYuvTestingImage(
kHeight, kWidth, block_size, libyuv::kJpegYuv420, y_pointer, u_pointer,
v_pointer));
// Allocate space for the Y output.
rtc::scoped_ptr<uint8_t[]> y_output(
new uint8_t[kHeight * kWidth + kAlignment + unalignment]);
uint8_t* y_output_pointer = ALIGNP(y_output.get(), kAlignment) + unalignment;
for (int i = 0; i < repeat_; ++i) {
libyuv::ARGBToI400(argb_pointer, kWidth * 4, y_output_pointer, kWidth,
kWidth, kHeight);
}
// Check if the output matches the input Y channel.
// Note: an empirical MSE tolerance 2.0 is used here for the probable
// error from float-to-uint8_t type conversion.
EXPECT_TRUE(IsMemoryEqual(y_output_pointer, y_pointer,
kHeight * kWidth, 2.));
if (dump_) { DumpArgbImage(argb_pointer, kWidth, kHeight); }
}
// A common macro for testing converting RAW, BG24, BGRA, and ABGR
// to ARGB.
#define TEST_ARGB(SRC_NAME, FC_ID, BPP, BLOCK_SIZE) \
TEST_P(PlanarFunctionsTest, SRC_NAME##ToARGB) { \
int unalignment = GetParam(); /* Get the unalignment offset.*/ \
uint8_t* argb_expected_pointer = NULL, * src_pointer = NULL; \
/* Generate a fake input image.*/ \
rtc::scoped_ptr<uint8_t[]> src_input(CreateFakeArgbTestingImage( \
kHeight, kWidth, BLOCK_SIZE, src_pointer, FOURCC_##FC_ID)); \
/* Generate the expected output.*/ \
rtc::scoped_ptr<uint8_t[]> argb_expected(CreateFakeArgbTestingImage( \
kHeight, kWidth, BLOCK_SIZE, argb_expected_pointer, FOURCC_ARGB)); \
/* Allocate space for the output; 1 byte extra if in the unaligned mode.*/ \
rtc::scoped_ptr<uint8_t[]> argb_output( \
new uint8_t[kHeight * kWidth * 4 + kAlignment + unalignment]); \
uint8_t* argb_pointer = \
ALIGNP(argb_output.get(), kAlignment) + unalignment; \
for (int i = 0; i < repeat_; ++i) { \
libyuv::SRC_NAME##ToARGB(src_pointer, kWidth*(BPP), argb_pointer, \
kWidth * 4, kWidth, kHeight); \
} \
/* Compare the result; expect identical.*/ \
EXPECT_TRUE(IsMemoryEqual(argb_expected_pointer, argb_pointer, \
kHeight* kWidth * 4, 1.e-6)); \
if (dump_) { \
DumpArgbImage(argb_pointer, kWidth, kHeight); \
} \
}
TEST_ARGB(RAW, RAW, 3, 3); // TEST_P(PlanarFunctionsTest, RAWToARGB)
TEST_ARGB(BG24, 24BG, 3, 3); // TEST_P(PlanarFunctionsTest, BG24ToARGB)
TEST_ARGB(ABGR, ABGR, 4, 3); // TEST_P(PlanarFunctionsTest, ABGRToARGB)
TEST_ARGB(BGRA, BGRA, 4, 3); // TEST_P(PlanarFunctionsTest, BGRAToARGB)
// Parameter Test: The parameter is the unalignment offset.
// Aligned data for testing assembly versions.
INSTANTIATE_TEST_CASE_P(PlanarFunctionsAligned, PlanarFunctionsTest,
::testing::Values(0));
// Purposely unalign the output argb pointer to test slow path (C version).
INSTANTIATE_TEST_CASE_P(PlanarFunctionsMisaligned, PlanarFunctionsTest,
::testing::Values(1));
} // namespace cricket