root/media/base/yuv_convert_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ReadData
  2. ReadYV12Data
  3. ReadYV16Data
  4. ReadRGB24Data
  5. ReadYUY2Data
  6. SwapRedAndBlueChannels
  7. TEST
  8. TEST
  9. y_plane
  10. u_plane
  11. v_plane
  12. TEST_P
  13. TEST_P
  14. TEST_P
  15. TEST_P
  16. TEST_P
  17. TEST
  18. TEST
  19. TEST
  20. TEST
  21. TEST
  22. TEST
  23. TEST
  24. TEST
  25. TEST
  26. TEST
  27. TEST
  28. TEST
  29. TEST
  30. TEST
  31. TEST
  32. TEST
  33. TEST
  34. TEST
  35. TEST

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/base_paths.h"
#include "base/cpu.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "media/base/djb2.h"
#include "media/base/simd/convert_rgb_to_yuv.h"
#include "media/base/simd/convert_yuv_to_rgb.h"
#include "media/base/simd/filter_yuv.h"
#include "media/base/yuv_convert.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/rect.h"

// Size of raw image.
static const int kSourceWidth = 640;
static const int kSourceHeight = 360;
static const int kSourceYSize = kSourceWidth * kSourceHeight;
static const int kSourceUOffset = kSourceYSize;
static const int kSourceVOffset = kSourceYSize * 5 / 4;
static const int kScaledWidth = 1024;
static const int kScaledHeight = 768;
static const int kDownScaledWidth = 512;
static const int kDownScaledHeight = 320;
static const int kBpp = 4;

// Surface sizes for various test files.
static const int kYUV12Size = kSourceYSize * 12 / 8;
static const int kYUV16Size = kSourceYSize * 16 / 8;
static const int kYUY2Size =  kSourceYSize * 16 / 8;
static const int kRGBSize = kSourceYSize * kBpp;
static const int kRGBSizeScaled = kScaledWidth * kScaledHeight * kBpp;
static const int kRGB24Size = kSourceYSize * 3;
static const int kRGBSizeConverted = kSourceYSize * kBpp;

// Helper for reading test data into a scoped_ptr<uint8[]>.
static void ReadData(const base::FilePath::CharType* filename,
                     int expected_size,
                     scoped_ptr<uint8[]>* data) {
  data->reset(new uint8[expected_size]);

  base::FilePath path;
  CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &path));
  path = path.Append(FILE_PATH_LITERAL("media"))
             .Append(FILE_PATH_LITERAL("test"))
             .Append(FILE_PATH_LITERAL("data"))
             .Append(filename);

  // Verify file size is correct.
  int64 actual_size = 0;
  base::GetFileSize(path, &actual_size);
  CHECK_EQ(actual_size, expected_size);

  // Verify bytes read are correct.
  int bytes_read = base::ReadFile(
      path, reinterpret_cast<char*>(data->get()), expected_size);
  CHECK_EQ(bytes_read, expected_size);
}

static void ReadYV12Data(scoped_ptr<uint8[]>* data) {
  ReadData(FILE_PATH_LITERAL("bali_640x360_P420.yuv"), kYUV12Size, data);
}

static void ReadYV16Data(scoped_ptr<uint8[]>* data) {
  ReadData(FILE_PATH_LITERAL("bali_640x360_P422.yuv"), kYUV16Size, data);
}

static void ReadRGB24Data(scoped_ptr<uint8[]>* data) {
  ReadData(FILE_PATH_LITERAL("bali_640x360_RGB24.rgb"), kRGB24Size, data);
}

static void ReadYUY2Data(scoped_ptr<uint8[]>* data) {
  ReadData(FILE_PATH_LITERAL("bali_640x360_YUY2.yuv"), kYUY2Size, data);
}

#if defined(OS_ANDROID)
// Helper for swapping red and blue channels of RGBA or BGRA.
static void SwapRedAndBlueChannels(unsigned char* pixels, size_t buffer_size) {
  for (size_t i = 0; i < buffer_size; i += 4) {
    std::swap(pixels[i], pixels[i + 2]);
  }
}
#endif

namespace media {

TEST(YUVConvertTest, YV12) {
  // Allocate all surfaces.
  scoped_ptr<uint8[]> yuv_bytes;
  scoped_ptr<uint8[]> rgb_bytes(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_converted_bytes(new uint8[kRGBSizeConverted]);

  // Read YUV reference data from file.
  ReadYV12Data(&yuv_bytes);

  // Convert a frame of YUV to 32 bit ARGB.
  media::ConvertYUVToRGB32(yuv_bytes.get(),
                           yuv_bytes.get() + kSourceUOffset,
                           yuv_bytes.get() + kSourceVOffset,
                           rgb_converted_bytes.get(),            // RGB output
                           kSourceWidth, kSourceHeight,          // Dimensions
                           kSourceWidth,                         // YStride
                           kSourceWidth / 2,                     // UVStride
                           kSourceWidth * kBpp,                  // RGBStride
                           media::YV12);

#if defined(OS_ANDROID)
  SwapRedAndBlueChannels(rgb_converted_bytes.get(), kRGBSizeConverted);
#endif

  uint32 rgb_hash = DJB2Hash(rgb_converted_bytes.get(), kRGBSizeConverted,
                             kDJB2HashSeed);
  EXPECT_EQ(2413171226u, rgb_hash);
}

TEST(YUVConvertTest, YV16) {
  // Allocate all surfaces.
  scoped_ptr<uint8[]> yuv_bytes;
  scoped_ptr<uint8[]> rgb_bytes(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_converted_bytes(new uint8[kRGBSizeConverted]);

  // Read YUV reference data from file.
  ReadYV16Data(&yuv_bytes);

  // Convert a frame of YUV to 32 bit ARGB.
  media::ConvertYUVToRGB32(yuv_bytes.get(),                        // Y
                           yuv_bytes.get() + kSourceUOffset,       // U
                           yuv_bytes.get() + kSourceYSize * 3 / 2, // V
                           rgb_converted_bytes.get(),              // RGB output
                           kSourceWidth, kSourceHeight,            // Dimensions
                           kSourceWidth,                           // YStride
                           kSourceWidth / 2,                       // UVStride
                           kSourceWidth * kBpp,                    // RGBStride
                           media::YV16);

#if defined(OS_ANDROID)
  SwapRedAndBlueChannels(rgb_converted_bytes.get(), kRGBSizeConverted);
#endif

  uint32 rgb_hash = DJB2Hash(rgb_converted_bytes.get(), kRGBSizeConverted,
                             kDJB2HashSeed);
  EXPECT_EQ(4222342047u, rgb_hash);
}

struct YUVScaleTestData {
  YUVScaleTestData(media::YUVType y, media::ScaleFilter s, uint32 r)
      : yuv_type(y),
        scale_filter(s),
        rgb_hash(r) {
  }

  media::YUVType yuv_type;
  media::ScaleFilter scale_filter;
  uint32 rgb_hash;
};

class YUVScaleTest : public ::testing::TestWithParam<YUVScaleTestData> {
 public:
  YUVScaleTest() {
    switch (GetParam().yuv_type) {
      case media::YV12:
        ReadYV12Data(&yuv_bytes_);
        break;
      case media::YV16:
        ReadYV16Data(&yuv_bytes_);
        break;
    }

    rgb_bytes_.reset(new uint8[kRGBSizeScaled]);
  }

  // Helpers for getting the proper Y, U and V plane offsets.
  uint8* y_plane() { return yuv_bytes_.get(); }
  uint8* u_plane() { return yuv_bytes_.get() + kSourceYSize; }
  uint8* v_plane() {
    switch (GetParam().yuv_type) {
      case media::YV12:
        return yuv_bytes_.get() + kSourceVOffset;
      case media::YV16:
        return yuv_bytes_.get() + kSourceYSize * 3 / 2;
    }
    return NULL;
  }

  scoped_ptr<uint8[]> yuv_bytes_;
  scoped_ptr<uint8[]> rgb_bytes_;
};

TEST_P(YUVScaleTest, NoScale) {
  media::ScaleYUVToRGB32(y_plane(),                    // Y
                         u_plane(),                    // U
                         v_plane(),                    // V
                         rgb_bytes_.get(),             // RGB output
                         kSourceWidth, kSourceHeight,  // Dimensions
                         kSourceWidth, kSourceHeight,  // Dimensions
                         kSourceWidth,                 // YStride
                         kSourceWidth / 2,             // UvStride
                         kSourceWidth * kBpp,          // RgbStride
                         GetParam().yuv_type,
                         media::ROTATE_0,
                         GetParam().scale_filter);

  uint32 yuv_hash = DJB2Hash(rgb_bytes_.get(), kRGBSize, kDJB2HashSeed);

  media::ConvertYUVToRGB32(y_plane(),                    // Y
                           u_plane(),                    // U
                           v_plane(),                    // V
                           rgb_bytes_.get(),             // RGB output
                           kSourceWidth, kSourceHeight,  // Dimensions
                           kSourceWidth,                 // YStride
                           kSourceWidth / 2,             // UVStride
                           kSourceWidth * kBpp,          // RGBStride
                           GetParam().yuv_type);

  uint32 rgb_hash = DJB2Hash(rgb_bytes_.get(), kRGBSize, kDJB2HashSeed);

  EXPECT_EQ(yuv_hash, rgb_hash);
}

TEST_P(YUVScaleTest, Normal) {
  media::ScaleYUVToRGB32(y_plane(),                    // Y
                         u_plane(),                    // U
                         v_plane(),                    // V
                         rgb_bytes_.get(),             // RGB output
                         kSourceWidth, kSourceHeight,  // Dimensions
                         kScaledWidth, kScaledHeight,  // Dimensions
                         kSourceWidth,                 // YStride
                         kSourceWidth / 2,             // UvStride
                         kScaledWidth * kBpp,          // RgbStride
                         GetParam().yuv_type,
                         media::ROTATE_0,
                         GetParam().scale_filter);

#if defined(OS_ANDROID)
  SwapRedAndBlueChannels(rgb_bytes_.get(), kRGBSizeScaled);
#endif

  uint32 rgb_hash = DJB2Hash(rgb_bytes_.get(), kRGBSizeScaled, kDJB2HashSeed);
  EXPECT_EQ(GetParam().rgb_hash, rgb_hash);
}

TEST_P(YUVScaleTest, ZeroSourceSize) {
  media::ScaleYUVToRGB32(y_plane(),                    // Y
                         u_plane(),                    // U
                         v_plane(),                    // V
                         rgb_bytes_.get(),             // RGB output
                         0, 0,                         // Dimensions
                         kScaledWidth, kScaledHeight,  // Dimensions
                         kSourceWidth,                 // YStride
                         kSourceWidth / 2,             // UvStride
                         kScaledWidth * kBpp,          // RgbStride
                         GetParam().yuv_type,
                         media::ROTATE_0,
                         GetParam().scale_filter);

  // Testing for out-of-bound read/writes with AddressSanitizer.
}

TEST_P(YUVScaleTest, ZeroDestinationSize) {
  media::ScaleYUVToRGB32(y_plane(),                    // Y
                         u_plane(),                    // U
                         v_plane(),                    // V
                         rgb_bytes_.get(),             // RGB output
                         kSourceWidth, kSourceHeight,  // Dimensions
                         0, 0,                         // Dimensions
                         kSourceWidth,                 // YStride
                         kSourceWidth / 2,             // UvStride
                         kScaledWidth * kBpp,          // RgbStride
                         GetParam().yuv_type,
                         media::ROTATE_0,
                         GetParam().scale_filter);

  // Testing for out-of-bound read/writes with AddressSanitizer.
}

TEST_P(YUVScaleTest, OddWidthAndHeightNotCrash) {
  media::ScaleYUVToRGB32(y_plane(),                    // Y
                         u_plane(),                    // U
                         v_plane(),                    // V
                         rgb_bytes_.get(),             // RGB output
                         kSourceWidth, kSourceHeight,  // Dimensions
                         3, 3,                         // Dimensions
                         kSourceWidth,                 // YStride
                         kSourceWidth / 2,             // UvStride
                         kScaledWidth * kBpp,          // RgbStride
                         GetParam().yuv_type,
                         media::ROTATE_0,
                         GetParam().scale_filter);
}

INSTANTIATE_TEST_CASE_P(
    YUVScaleFormats, YUVScaleTest,
    ::testing::Values(
        YUVScaleTestData(media::YV12, media::FILTER_NONE, 4136904952u),
        YUVScaleTestData(media::YV16, media::FILTER_NONE, 1501777547u),
        YUVScaleTestData(media::YV12, media::FILTER_BILINEAR, 3164274689u),
        YUVScaleTestData(media::YV16, media::FILTER_BILINEAR, 3095878046u)));

// This tests a known worst case YUV value, and for overflow.
TEST(YUVConvertTest, Clamp) {
  // Allocate all surfaces.
  scoped_ptr<uint8[]> yuv_bytes(new uint8[1]);
  scoped_ptr<uint8[]> rgb_bytes(new uint8[1]);
  scoped_ptr<uint8[]> rgb_converted_bytes(new uint8[1]);

  // Values that failed previously in bug report.
  unsigned char y = 255u;
  unsigned char u = 255u;
  unsigned char v = 19u;

  // Prefill extra large destination buffer to test for overflow.
  unsigned char rgb[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
  unsigned char expected[8] = { 255, 255, 104, 255, 4, 5, 6, 7 };
  // Convert a frame of YUV to 32 bit ARGB.
  media::ConvertYUVToRGB32(&y,       // Y
                           &u,       // U
                           &v,       // V
                           &rgb[0],  // RGB output
                           1, 1,     // Dimensions
                           0,        // YStride
                           0,        // UVStride
                           0,        // RGBStride
                           media::YV12);

#if defined(OS_ANDROID)
  SwapRedAndBlueChannels(rgb, kBpp);
#endif

  int expected_test = memcmp(rgb, expected, sizeof(expected));
  EXPECT_EQ(0, expected_test);
}

TEST(YUVConvertTest, RGB24ToYUV) {
  // Allocate all surfaces.
  scoped_ptr<uint8[]> rgb_bytes;
  scoped_ptr<uint8[]> yuv_converted_bytes(new uint8[kYUV12Size]);

  // Read RGB24 reference data from file.
  ReadRGB24Data(&rgb_bytes);

  // Convert to I420.
  media::ConvertRGB24ToYUV(rgb_bytes.get(),
                           yuv_converted_bytes.get(),
                           yuv_converted_bytes.get() + kSourceUOffset,
                           yuv_converted_bytes.get() + kSourceVOffset,
                           kSourceWidth, kSourceHeight,        // Dimensions
                           kSourceWidth * 3,                   // RGBStride
                           kSourceWidth,                       // YStride
                           kSourceWidth / 2);                  // UVStride

  uint32 rgb_hash = DJB2Hash(yuv_converted_bytes.get(), kYUV12Size,
                             kDJB2HashSeed);
  EXPECT_EQ(320824432u, rgb_hash);
}

TEST(YUVConvertTest, RGB32ToYUV) {
  // Allocate all surfaces.
  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> yuv_converted_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_converted_bytes(new uint8[kRGBSize]);

  // Read YUV reference data from file.
  base::FilePath yuv_url;
  EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &yuv_url));
  yuv_url = yuv_url.Append(FILE_PATH_LITERAL("media"))
                   .Append(FILE_PATH_LITERAL("test"))
                   .Append(FILE_PATH_LITERAL("data"))
                   .Append(FILE_PATH_LITERAL("bali_640x360_P420.yuv"));
  EXPECT_EQ(static_cast<int>(kYUV12Size),
            base::ReadFile(yuv_url,
                           reinterpret_cast<char*>(yuv_bytes.get()),
                           static_cast<int>(kYUV12Size)));

  // Convert a frame of YUV to 32 bit ARGB.
  media::ConvertYUVToRGB32(yuv_bytes.get(),
                           yuv_bytes.get() + kSourceUOffset,
                           yuv_bytes.get() + kSourceVOffset,
                           rgb_bytes.get(),            // RGB output
                           kSourceWidth, kSourceHeight,          // Dimensions
                           kSourceWidth,                         // YStride
                           kSourceWidth / 2,                     // UVStride
                           kSourceWidth * kBpp,                  // RGBStride
                           media::YV12);

  // Convert RGB32 to YV12.
  media::ConvertRGB32ToYUV(rgb_bytes.get(),
                           yuv_converted_bytes.get(),
                           yuv_converted_bytes.get() + kSourceUOffset,
                           yuv_converted_bytes.get() + kSourceVOffset,
                           kSourceWidth, kSourceHeight,        // Dimensions
                           kSourceWidth * 4,                   // RGBStride
                           kSourceWidth,                       // YStride
                           kSourceWidth / 2);                  // UVStride

  // Convert YV12 back to RGB32.
  media::ConvertYUVToRGB32(yuv_converted_bytes.get(),
                           yuv_converted_bytes.get() + kSourceUOffset,
                           yuv_converted_bytes.get() + kSourceVOffset,
                           rgb_converted_bytes.get(),            // RGB output
                           kSourceWidth, kSourceHeight,          // Dimensions
                           kSourceWidth,                         // YStride
                           kSourceWidth / 2,                     // UVStride
                           kSourceWidth * kBpp,                  // RGBStride
                           media::YV12);

  int error = 0;
  for (int i = 0; i < kRGBSize; ++i) {
    int diff = rgb_converted_bytes[i] - rgb_bytes[i];
    if (diff < 0)
      diff = -diff;
    error += diff;
  }

  // Make sure error is within bound.
  DVLOG(1) << "Average error per channel: " << error / kRGBSize;
  EXPECT_GT(5, error / kRGBSize);
}

TEST(YUVConvertTest, YUY2ToYUV) {
  // Allocate all surfaces.
  scoped_ptr<uint8[]> yuy_bytes;
  scoped_ptr<uint8[]> yuv_converted_bytes(new uint8[kYUV12Size]);

  // Read YUY reference data from file.
  ReadYUY2Data(&yuy_bytes);

  // Convert to I420.
  media::ConvertYUY2ToYUV(yuy_bytes.get(),
                          yuv_converted_bytes.get(),
                          yuv_converted_bytes.get() + kSourceUOffset,
                          yuv_converted_bytes.get() + kSourceVOffset,
                          kSourceWidth, kSourceHeight);

  uint32 yuy_hash = DJB2Hash(yuv_converted_bytes.get(), kYUV12Size,
                             kDJB2HashSeed);
  EXPECT_EQ(666823187u, yuy_hash);
}

TEST(YUVConvertTest, DownScaleYUVToRGB32WithRect) {
  // Read YUV reference data from file.
  base::FilePath yuv_url;
  EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &yuv_url));
  yuv_url = yuv_url.Append(FILE_PATH_LITERAL("media"))
                   .Append(FILE_PATH_LITERAL("test"))
                   .Append(FILE_PATH_LITERAL("data"))
                   .Append(FILE_PATH_LITERAL("bali_640x360_P420.yuv"));
  const size_t size_of_yuv = kSourceYSize * 12 / 8;  // 12 bpp.
  scoped_ptr<uint8[]> yuv_bytes(new uint8[size_of_yuv]);
  EXPECT_EQ(static_cast<int>(size_of_yuv),
            base::ReadFile(yuv_url,
                           reinterpret_cast<char*>(yuv_bytes.get()),
                           static_cast<int>(size_of_yuv)));

  // Scale the full frame of YUV to 32 bit ARGB.
  // The API currently only supports down-scaling, so we don't test up-scaling.
  const size_t size_of_rgb_scaled = kDownScaledWidth * kDownScaledHeight * kBpp;
  scoped_ptr<uint8[]> rgb_scaled_bytes(new uint8[size_of_rgb_scaled]);
  gfx::Rect sub_rect(0, 0, kDownScaledWidth, kDownScaledHeight);

  // We can't compare with the full-frame scaler because it uses slightly
  // different sampling coordinates.
  media::ScaleYUVToRGB32WithRect(
      yuv_bytes.get(),                          // Y
      yuv_bytes.get() + kSourceUOffset,         // U
      yuv_bytes.get() + kSourceVOffset,         // V
      rgb_scaled_bytes.get(),                   // Rgb output
      kSourceWidth, kSourceHeight,              // Dimensions
      kDownScaledWidth, kDownScaledHeight,      // Dimensions
      sub_rect.x(), sub_rect.y(),               // Dest rect
      sub_rect.right(), sub_rect.bottom(),      // Dest rect
      kSourceWidth,                             // YStride
      kSourceWidth / 2,                         // UvStride
      kDownScaledWidth * kBpp);                 // RgbStride

  uint32 rgb_hash_full_rect = DJB2Hash(rgb_scaled_bytes.get(),
                                       size_of_rgb_scaled,
                                       kDJB2HashSeed);

  // Re-scale sub-rectangles and verify the results are the same.
  int next_sub_rect = 0;
  while (!sub_rect.IsEmpty()) {
    // Scale a partial rectangle.
    media::ScaleYUVToRGB32WithRect(
        yuv_bytes.get(),                          // Y
        yuv_bytes.get() + kSourceUOffset,         // U
        yuv_bytes.get() + kSourceVOffset,         // V
        rgb_scaled_bytes.get(),                   // Rgb output
        kSourceWidth, kSourceHeight,              // Dimensions
        kDownScaledWidth, kDownScaledHeight,      // Dimensions
        sub_rect.x(), sub_rect.y(),               // Dest rect
        sub_rect.right(), sub_rect.bottom(),      // Dest rect
        kSourceWidth,                             // YStride
        kSourceWidth / 2,                         // UvStride
        kDownScaledWidth * kBpp);                 // RgbStride
    uint32 rgb_hash_sub_rect = DJB2Hash(rgb_scaled_bytes.get(),
                                        size_of_rgb_scaled,
                                        kDJB2HashSeed);

    EXPECT_EQ(rgb_hash_full_rect, rgb_hash_sub_rect);

    // Now pick choose a quarter rect of this sub-rect.
    if (next_sub_rect & 1)
      sub_rect.set_x(sub_rect.x() + sub_rect.width() / 2);
    if (next_sub_rect & 2)
      sub_rect.set_y(sub_rect.y() + sub_rect.height() / 2);
    sub_rect.set_width(sub_rect.width() / 2);
    sub_rect.set_height(sub_rect.height() / 2);
    next_sub_rect++;
  }
}

#if !defined(ARCH_CPU_ARM_FAMILY) && !defined(ARCH_CPU_MIPS_FAMILY)
TEST(YUVConvertTest, RGB32ToYUV_SSE2_MatchReference) {
  base::CPU cpu;
  if (!cpu.has_sse2()) {
    LOG(WARNING) << "System doesn't support SSE2, test not executed.";
    return;
  }

  // Allocate all surfaces.
  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> yuv_converted_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> yuv_reference_bytes(new uint8[kYUV12Size]);

  ReadYV12Data(&yuv_bytes);

  // Convert a frame of YUV to 32 bit ARGB.
  media::ConvertYUVToRGB32(
      yuv_bytes.get(),
      yuv_bytes.get() + kSourceUOffset,
      yuv_bytes.get() + kSourceVOffset,
      rgb_bytes.get(),            // RGB output
      kSourceWidth, kSourceHeight,          // Dimensions
      kSourceWidth,                         // YStride
      kSourceWidth / 2,                     // UVStride
      kSourceWidth * kBpp,                  // RGBStride
      media::YV12);

  // Convert RGB32 to YV12 with SSE2 version.
  media::ConvertRGB32ToYUV_SSE2(
      rgb_bytes.get(),
      yuv_converted_bytes.get(),
      yuv_converted_bytes.get() + kSourceUOffset,
      yuv_converted_bytes.get() + kSourceVOffset,
      kSourceWidth, kSourceHeight,        // Dimensions
      kSourceWidth * 4,                   // RGBStride
      kSourceWidth,                       // YStride
      kSourceWidth / 2);                  // UVStride

  // Convert RGB32 to YV12 with reference version.
  media::ConvertRGB32ToYUV_SSE2_Reference(
      rgb_bytes.get(),
      yuv_reference_bytes.get(),
      yuv_reference_bytes.get() + kSourceUOffset,
      yuv_reference_bytes.get() + kSourceVOffset,
      kSourceWidth, kSourceHeight,        // Dimensions
      kSourceWidth * 4,                   // RGBStride
      kSourceWidth,                       // YStride
      kSourceWidth / 2);                  // UVStride

  // Now convert a odd width and height, this overrides part of the buffer
  // generated above but that is fine because the point of this test is to
  // match the result with the reference code.

  // Convert RGB32 to YV12 with SSE2 version.
  media::ConvertRGB32ToYUV_SSE2(
      rgb_bytes.get(),
      yuv_converted_bytes.get(),
      yuv_converted_bytes.get() + kSourceUOffset,
      yuv_converted_bytes.get() + kSourceVOffset,
      7, 7,                               // Dimensions
      kSourceWidth * 4,                   // RGBStride
      kSourceWidth,                       // YStride
      kSourceWidth / 2);                  // UVStride

  // Convert RGB32 to YV12 with reference version.
  media::ConvertRGB32ToYUV_SSE2_Reference(
      rgb_bytes.get(),
      yuv_reference_bytes.get(),
      yuv_reference_bytes.get() + kSourceUOffset,
      yuv_reference_bytes.get() + kSourceVOffset,
      7, 7,                               // Dimensions
      kSourceWidth * 4,                   // RGBStride
      kSourceWidth,                       // YStride
      kSourceWidth / 2);                  // UVStride

  int error = 0;
  for (int i = 0; i < kYUV12Size; ++i) {
    int diff = yuv_reference_bytes[i] - yuv_converted_bytes[i];
    if (diff < 0)
      diff = -diff;
    error += diff;
  }

  // Make sure there's no difference from the reference.
  EXPECT_EQ(0, error);
}

TEST(YUVConvertTest, ConvertYUVToRGB32Row_MMX) {
  base::CPU cpu;
  if (!cpu.has_mmx()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes_reference(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_bytes_converted(new uint8[kRGBSize]);
  ReadYV12Data(&yuv_bytes);

  const int kWidth = 167;
  ConvertYUVToRGB32Row_C(yuv_bytes.get(),
                         yuv_bytes.get() + kSourceUOffset,
                         yuv_bytes.get() + kSourceVOffset,
                         rgb_bytes_reference.get(),
                         kWidth);
  ConvertYUVToRGB32Row_MMX(yuv_bytes.get(),
                           yuv_bytes.get() + kSourceUOffset,
                           yuv_bytes.get() + kSourceVOffset,
                           rgb_bytes_converted.get(),
                           kWidth);
  media::EmptyRegisterState();
  EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
                      rgb_bytes_converted.get(),
                      kWidth * kBpp));
}

TEST(YUVConvertTest, ConvertYUVToRGB32Row_SSE) {
  base::CPU cpu;
  if (!cpu.has_sse()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes_reference(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_bytes_converted(new uint8[kRGBSize]);
  ReadYV12Data(&yuv_bytes);

  const int kWidth = 167;
  ConvertYUVToRGB32Row_C(yuv_bytes.get(),
                         yuv_bytes.get() + kSourceUOffset,
                         yuv_bytes.get() + kSourceVOffset,
                         rgb_bytes_reference.get(),
                         kWidth);
  ConvertYUVToRGB32Row_SSE(yuv_bytes.get(),
                           yuv_bytes.get() + kSourceUOffset,
                           yuv_bytes.get() + kSourceVOffset,
                           rgb_bytes_converted.get(),
                           kWidth);
  media::EmptyRegisterState();
  EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
                      rgb_bytes_converted.get(),
                      kWidth * kBpp));
}

TEST(YUVConvertTest, ScaleYUVToRGB32Row_MMX) {
  base::CPU cpu;
  if (!cpu.has_mmx()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes_reference(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_bytes_converted(new uint8[kRGBSize]);
  ReadYV12Data(&yuv_bytes);

  const int kWidth = 167;
  const int kSourceDx = 80000;  // This value means a scale down.
  ScaleYUVToRGB32Row_C(yuv_bytes.get(),
                       yuv_bytes.get() + kSourceUOffset,
                       yuv_bytes.get() + kSourceVOffset,
                       rgb_bytes_reference.get(),
                       kWidth,
                       kSourceDx);
  ScaleYUVToRGB32Row_MMX(yuv_bytes.get(),
                         yuv_bytes.get() + kSourceUOffset,
                         yuv_bytes.get() + kSourceVOffset,
                         rgb_bytes_converted.get(),
                         kWidth,
                         kSourceDx);
  media::EmptyRegisterState();
  EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
                      rgb_bytes_converted.get(),
                      kWidth * kBpp));
}

TEST(YUVConvertTest, ScaleYUVToRGB32Row_SSE) {
  base::CPU cpu;
  if (!cpu.has_sse()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes_reference(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_bytes_converted(new uint8[kRGBSize]);
  ReadYV12Data(&yuv_bytes);

  const int kWidth = 167;
  const int kSourceDx = 80000;  // This value means a scale down.
  ScaleYUVToRGB32Row_C(yuv_bytes.get(),
                       yuv_bytes.get() + kSourceUOffset,
                       yuv_bytes.get() + kSourceVOffset,
                       rgb_bytes_reference.get(),
                       kWidth,
                       kSourceDx);
  ScaleYUVToRGB32Row_SSE(yuv_bytes.get(),
                         yuv_bytes.get() + kSourceUOffset,
                         yuv_bytes.get() + kSourceVOffset,
                         rgb_bytes_converted.get(),
                         kWidth,
                         kSourceDx);
  media::EmptyRegisterState();
  EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
                      rgb_bytes_converted.get(),
                      kWidth * kBpp));
}

TEST(YUVConvertTest, LinearScaleYUVToRGB32Row_MMX) {
  base::CPU cpu;
  if (!cpu.has_mmx()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes_reference(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_bytes_converted(new uint8[kRGBSize]);
  ReadYV12Data(&yuv_bytes);

  const int kWidth = 167;
  const int kSourceDx = 80000;  // This value means a scale down.
  LinearScaleYUVToRGB32Row_C(yuv_bytes.get(),
                             yuv_bytes.get() + kSourceUOffset,
                             yuv_bytes.get() + kSourceVOffset,
                             rgb_bytes_reference.get(),
                             kWidth,
                             kSourceDx);
  LinearScaleYUVToRGB32Row_MMX(yuv_bytes.get(),
                               yuv_bytes.get() + kSourceUOffset,
                               yuv_bytes.get() + kSourceVOffset,
                               rgb_bytes_converted.get(),
                               kWidth,
                               kSourceDx);
  media::EmptyRegisterState();
  EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
                      rgb_bytes_converted.get(),
                      kWidth * kBpp));
}

TEST(YUVConvertTest, LinearScaleYUVToRGB32Row_SSE) {
  base::CPU cpu;
  if (!cpu.has_sse()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes_reference(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_bytes_converted(new uint8[kRGBSize]);
  ReadYV12Data(&yuv_bytes);

  const int kWidth = 167;
  const int kSourceDx = 80000;  // This value means a scale down.
  LinearScaleYUVToRGB32Row_C(yuv_bytes.get(),
                             yuv_bytes.get() + kSourceUOffset,
                             yuv_bytes.get() + kSourceVOffset,
                             rgb_bytes_reference.get(),
                             kWidth,
                             kSourceDx);
  LinearScaleYUVToRGB32Row_SSE(yuv_bytes.get(),
                               yuv_bytes.get() + kSourceUOffset,
                               yuv_bytes.get() + kSourceVOffset,
                               rgb_bytes_converted.get(),
                               kWidth,
                               kSourceDx);
  media::EmptyRegisterState();
  EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
                      rgb_bytes_converted.get(),
                      kWidth * kBpp));
}

TEST(YUVConvertTest, FilterYUVRows_C_OutOfBounds) {
  scoped_ptr<uint8[]> src(new uint8[16]);
  scoped_ptr<uint8[]> dst(new uint8[16]);

  memset(src.get(), 0xff, 16);
  memset(dst.get(), 0, 16);

  media::FilterYUVRows_C(dst.get(), src.get(), src.get(), 1, 255);

  EXPECT_EQ(255u, dst[0]);
  for (int i = 1; i < 16; ++i) {
    EXPECT_EQ(0u, dst[i]) << " not equal at " << i;
  }
}

#if defined(MEDIA_MMX_INTRINSICS_AVAILABLE)
TEST(YUVConvertTest, FilterYUVRows_MMX_OutOfBounds) {
  base::CPU cpu;
  if (!cpu.has_mmx()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  scoped_ptr<uint8[]> src(new uint8[16]);
  scoped_ptr<uint8[]> dst(new uint8[16]);

  memset(src.get(), 0xff, 16);
  memset(dst.get(), 0, 16);

  media::FilterYUVRows_MMX(dst.get(), src.get(), src.get(), 1, 255);
  media::EmptyRegisterState();

  EXPECT_EQ(255u, dst[0]);
  for (int i = 1; i < 16; ++i) {
    EXPECT_EQ(0u, dst[i]);
  }
}
#endif  // defined(MEDIA_MMX_INTRINSICS_AVAILABLE)

TEST(YUVConvertTest, FilterYUVRows_SSE2_OutOfBounds) {
  base::CPU cpu;
  if (!cpu.has_sse2()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  scoped_ptr<uint8[]> src(new uint8[16]);
  scoped_ptr<uint8[]> dst(new uint8[16]);

  memset(src.get(), 0xff, 16);
  memset(dst.get(), 0, 16);

  media::FilterYUVRows_SSE2(dst.get(), src.get(), src.get(), 1, 255);

  EXPECT_EQ(255u, dst[0]);
  for (int i = 1; i < 16; ++i) {
    EXPECT_EQ(0u, dst[i]);
  }
}

#if defined(MEDIA_MMX_INTRINSICS_AVAILABLE)
TEST(YUVConvertTest, FilterYUVRows_MMX_UnalignedDestination) {
  base::CPU cpu;
  if (!cpu.has_mmx()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  const int kSize = 32;
  scoped_ptr<uint8[]> src(new uint8[kSize]);
  scoped_ptr<uint8[]> dst_sample(new uint8[kSize]);
  scoped_ptr<uint8[]> dst(new uint8[kSize]);

  memset(dst_sample.get(), 0, kSize);
  memset(dst.get(), 0, kSize);
  for (int i = 0; i < kSize; ++i)
    src[i] = 100 + i;

  media::FilterYUVRows_C(dst_sample.get(),
                         src.get(), src.get(), 17, 128);

  // Generate an unaligned output address.
  uint8* dst_ptr =
      reinterpret_cast<uint8*>(
          (reinterpret_cast<uintptr_t>(dst.get() + 8) & ~7) + 1);
  media::FilterYUVRows_MMX(dst_ptr, src.get(), src.get(), 17, 128);
  media::EmptyRegisterState();

  EXPECT_EQ(0, memcmp(dst_sample.get(), dst_ptr, 17));
}
#endif  // defined(MEDIA_MMX_INTRINSICS_AVAILABLE)

TEST(YUVConvertTest, FilterYUVRows_SSE2_UnalignedDestination) {
  base::CPU cpu;
  if (!cpu.has_sse2()) {
    LOG(WARNING) << "System not supported. Test skipped.";
    return;
  }

  const int kSize = 64;
  scoped_ptr<uint8[]> src(new uint8[kSize]);
  scoped_ptr<uint8[]> dst_sample(new uint8[kSize]);
  scoped_ptr<uint8[]> dst(new uint8[kSize]);

  memset(dst_sample.get(), 0, kSize);
  memset(dst.get(), 0, kSize);
  for (int i = 0; i < kSize; ++i)
    src[i] = 100 + i;

  media::FilterYUVRows_C(dst_sample.get(),
                         src.get(), src.get(), 37, 128);

  // Generate an unaligned output address.
  uint8* dst_ptr =
      reinterpret_cast<uint8*>(
          (reinterpret_cast<uintptr_t>(dst.get() + 16) & ~15) + 1);
  media::FilterYUVRows_SSE2(dst_ptr, src.get(), src.get(), 37, 128);
  media::EmptyRegisterState();

  EXPECT_EQ(0, memcmp(dst_sample.get(), dst_ptr, 37));
}

#if defined(ARCH_CPU_X86_64)

TEST(YUVConvertTest, ScaleYUVToRGB32Row_SSE2_X64) {
  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes_reference(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_bytes_converted(new uint8[kRGBSize]);
  ReadYV12Data(&yuv_bytes);

  const int kWidth = 167;
  const int kSourceDx = 80000;  // This value means a scale down.
  ScaleYUVToRGB32Row_C(yuv_bytes.get(),
                       yuv_bytes.get() + kSourceUOffset,
                       yuv_bytes.get() + kSourceVOffset,
                       rgb_bytes_reference.get(),
                       kWidth,
                       kSourceDx);
  ScaleYUVToRGB32Row_SSE2_X64(yuv_bytes.get(),
                              yuv_bytes.get() + kSourceUOffset,
                              yuv_bytes.get() + kSourceVOffset,
                              rgb_bytes_converted.get(),
                              kWidth,
                              kSourceDx);
  media::EmptyRegisterState();
  EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
                      rgb_bytes_converted.get(),
                      kWidth * kBpp));
}

TEST(YUVConvertTest, LinearScaleYUVToRGB32Row_MMX_X64) {
  scoped_ptr<uint8[]> yuv_bytes(new uint8[kYUV12Size]);
  scoped_ptr<uint8[]> rgb_bytes_reference(new uint8[kRGBSize]);
  scoped_ptr<uint8[]> rgb_bytes_converted(new uint8[kRGBSize]);
  ReadYV12Data(&yuv_bytes);

  const int kWidth = 167;
  const int kSourceDx = 80000;  // This value means a scale down.
  LinearScaleYUVToRGB32Row_C(yuv_bytes.get(),
                             yuv_bytes.get() + kSourceUOffset,
                             yuv_bytes.get() + kSourceVOffset,
                             rgb_bytes_reference.get(),
                             kWidth,
                             kSourceDx);
  LinearScaleYUVToRGB32Row_MMX_X64(yuv_bytes.get(),
                                   yuv_bytes.get() + kSourceUOffset,
                                   yuv_bytes.get() + kSourceVOffset,
                                   rgb_bytes_converted.get(),
                                   kWidth,
                                   kSourceDx);
  media::EmptyRegisterState();
  EXPECT_EQ(0, memcmp(rgb_bytes_reference.get(),
                      rgb_bytes_converted.get(),
                      kWidth * kBpp));
}

#endif  // defined(ARCH_CPU_X86_64)

#endif  // defined(ARCH_CPU_X86_FAMILY)

}  // namespace media

/* [<][>][^][v][top][bottom][index][help] */