/*
 * libjingle
 * Copyright 2004 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.
 */

#ifndef TALK_MEDIA_BASE_VIDEOFRAME_UNITTEST_H_
#define TALK_MEDIA_BASE_VIDEOFRAME_UNITTEST_H_

#include <algorithm>
#include <string>

#include "libyuv/convert.h"
#include "libyuv/convert_from.h"
#include "libyuv/planar_functions.h"
#include "libyuv/rotate.h"
#include "talk/media/base/testutils.h"
#include "talk/media/base/videocommon.h"
#include "talk/media/base/videoframe.h"
#include "webrtc/base/gunit.h"
#include "webrtc/base/pathutils.h"
#include "webrtc/base/stream.h"
#include "webrtc/base/stringutils.h"
#include "webrtc/common_video/rotation.h"

#if defined(_MSC_VER)
#define ALIGN16(var) __declspec(align(16)) var
#else
#define ALIGN16(var) var __attribute__((aligned(16)))
#endif

#define kImageFilename "faces.1280x720_P420.yuv"
#define kJpeg420Filename "faces_I420.jpg"
#define kJpeg422Filename "faces_I422.jpg"
#define kJpeg444Filename "faces_I444.jpg"
#define kJpeg411Filename "faces_I411.jpg"
#define kJpeg400Filename "faces_I400.jpg"

// Generic test class for testing various video frame implementations.
template <class T>
class VideoFrameTest : public testing::Test {
 public:
  VideoFrameTest() : repeat_(1) {}

 protected:
  static const int kWidth = 1280;
  static const int kHeight = 720;
  static const int kAlignment = 16;
  static const int kMinWidthAll = 1;  // Constants for ConstructYUY2AllSizes.
  static const int kMinHeightAll = 1;
  static const int kMaxWidthAll = 17;
  static const int kMaxHeightAll = 23;

  // Load a video frame from disk.
  bool LoadFrameNoRepeat(T* frame) {
    int save_repeat = repeat_;  // This LoadFrame disables repeat.
    repeat_ = 1;
    bool success = LoadFrame(kImageFilename, cricket::FOURCC_I420,
                            kWidth, kHeight, frame);
    repeat_ = save_repeat;
    return success;
  }

  bool LoadFrame(const std::string& filename,
                 uint32_t format,
                 int32_t width,
                 int32_t height,
                 T* frame) {
    return LoadFrame(filename, format, width, height, width, abs(height),
                     webrtc::kVideoRotation_0, frame);
  }
  bool LoadFrame(const std::string& filename,
                 uint32_t format,
                 int32_t width,
                 int32_t height,
                 int dw,
                 int dh,
                 webrtc::VideoRotation rotation,
                 T* frame) {
    rtc::scoped_ptr<rtc::MemoryStream> ms(LoadSample(filename));
    return LoadFrame(ms.get(), format, width, height, dw, dh, rotation, frame);
  }
  // Load a video frame from a memory stream.
  bool LoadFrame(rtc::MemoryStream* ms,
                 uint32_t format,
                 int32_t width,
                 int32_t height,
                 T* frame) {
    return LoadFrame(ms, format, width, height, width, abs(height),
                     webrtc::kVideoRotation_0, frame);
  }
  bool LoadFrame(rtc::MemoryStream* ms,
                 uint32_t format,
                 int32_t width,
                 int32_t height,
                 int dw,
                 int dh,
                 webrtc::VideoRotation rotation,
                 T* frame) {
    if (!ms) {
      return false;
    }
    size_t data_size;
    bool ret = ms->GetSize(&data_size);
    EXPECT_TRUE(ret);
    if (ret) {
      ret = LoadFrame(reinterpret_cast<uint8_t*>(ms->GetBuffer()), data_size,
                      format, width, height, dw, dh, rotation, frame);
    }
    return ret;
  }
  // Load a frame from a raw buffer.
  bool LoadFrame(uint8_t* sample,
                 size_t sample_size,
                 uint32_t format,
                 int32_t width,
                 int32_t height,
                 T* frame) {
    return LoadFrame(sample, sample_size, format, width, height, width,
                     abs(height), webrtc::kVideoRotation_0, frame);
  }
  bool LoadFrame(uint8_t* sample,
                 size_t sample_size,
                 uint32_t format,
                 int32_t width,
                 int32_t height,
                 int dw,
                 int dh,
                 webrtc::VideoRotation rotation,
                 T* frame) {
    bool ret = false;
    for (int i = 0; i < repeat_; ++i) {
      ret = frame->Init(format, width, height, dw, dh,
                        sample, sample_size, 1, 1, 0, rotation);
    }
    return ret;
  }

  rtc::MemoryStream* LoadSample(const std::string& filename) {
    rtc::Pathname path(cricket::GetTestFilePath(filename));
    rtc::scoped_ptr<rtc::FileStream> fs(
        rtc::Filesystem::OpenFile(path, "rb"));
    if (!fs.get()) {
      LOG(LS_ERROR) << "Could not open test file path: " << path.pathname()
                    << " from current dir "
                    << rtc::Filesystem::GetCurrentDirectory().pathname();
      return NULL;
    }

    char buf[4096];
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        new rtc::MemoryStream());
    rtc::StreamResult res = Flow(fs.get(), buf, sizeof(buf), ms.get());
    if (res != rtc::SR_SUCCESS) {
      LOG(LS_ERROR) << "Could not load test file path: " << path.pathname();
      return NULL;
    }

    return ms.release();
  }

  // Write an I420 frame out to disk.
  bool DumpFrame(const std::string& prefix,
                 const cricket::VideoFrame& frame) {
    char filename[256];
    rtc::sprintfn(filename, sizeof(filename), "%s.%dx%d_P420.yuv",
                        prefix.c_str(), frame.GetWidth(), frame.GetHeight());
    size_t out_size = cricket::VideoFrame::SizeOf(frame.GetWidth(),
                                                  frame.GetHeight());
    rtc::scoped_ptr<uint8_t[]> out(new uint8_t[out_size]);
    frame.CopyToBuffer(out.get(), out_size);
    return DumpSample(filename, out.get(), out_size);
  }

  bool DumpSample(const std::string& filename, const void* buffer, int size) {
    rtc::Pathname path(filename);
    rtc::scoped_ptr<rtc::FileStream> fs(
        rtc::Filesystem::OpenFile(path, "wb"));
    if (!fs.get()) {
      return false;
    }

    return (fs->Write(buffer, size, NULL, NULL) == rtc::SR_SUCCESS);
  }

  // Create a test image in the desired color space.
  // The image is a checkerboard pattern with 63x63 squares, which allows
  // I420 chroma artifacts to easily be seen on the square boundaries.
  // The pattern is { { green, orange }, { blue, purple } }
  // There is also a gradient within each square to ensure that the luma
  // values are handled properly.
  rtc::MemoryStream* CreateYuv422Sample(uint32_t fourcc,
                                        uint32_t width,
                                        uint32_t height) {
    int y1_pos, y2_pos, u_pos, v_pos;
    if (!GetYuv422Packing(fourcc, &y1_pos, &y2_pos, &u_pos, &v_pos)) {
      return NULL;
    }

    rtc::scoped_ptr<rtc::MemoryStream> ms(
        new rtc::MemoryStream);
    int awidth = (width + 1) & ~1;
    int size = awidth * 2 * height;
    if (!ms->ReserveSize(size)) {
      return NULL;
    }
    for (uint32_t y = 0; y < height; ++y) {
      for (int x = 0; x < awidth; x += 2) {
        uint8_t quad[4];
        quad[y1_pos] = (x % 63 + y % 63) + 64;
        quad[y2_pos] = ((x + 1) % 63 + y % 63) + 64;
        quad[u_pos] = ((x / 63) & 1) ? 192 : 64;
        quad[v_pos] = ((y / 63) & 1) ? 192 : 64;
        ms->Write(quad, sizeof(quad), NULL, NULL);
      }
    }
    return ms.release();
  }

  // Create a test image for YUV 420 formats with 12 bits per pixel.
  rtc::MemoryStream* CreateYuvSample(uint32_t width,
                                     uint32_t height,
                                     uint32_t bpp) {
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        new rtc::MemoryStream);
    if (!ms->ReserveSize(width * height * bpp / 8)) {
      return NULL;
    }

    for (uint32_t i = 0; i < width * height * bpp / 8; ++i) {
      char value = ((i / 63) & 1) ? 192 : 64;
      ms->Write(&value, sizeof(value), NULL, NULL);
    }
    return ms.release();
  }

  rtc::MemoryStream* CreateRgbSample(uint32_t fourcc,
                                     uint32_t width,
                                     uint32_t height) {
    int r_pos, g_pos, b_pos, bytes;
    if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) {
      return NULL;
    }

    rtc::scoped_ptr<rtc::MemoryStream> ms(
        new rtc::MemoryStream);
    if (!ms->ReserveSize(width * height * bytes)) {
      return NULL;
    }

    for (uint32_t y = 0; y < height; ++y) {
      for (uint32_t x = 0; x < width; ++x) {
        uint8_t rgb[4] = {255, 255, 255, 255};
        rgb[r_pos] = ((x / 63) & 1) ? 224 : 32;
        rgb[g_pos] = (x % 63 + y % 63) + 96;
        rgb[b_pos] = ((y / 63) & 1) ? 224 : 32;
        ms->Write(rgb, bytes, NULL, NULL);
      }
    }
    return ms.release();
  }

  // Simple conversion routines to verify the optimized VideoFrame routines.
  // Converts from the specified colorspace to I420.
  bool ConvertYuv422(const rtc::MemoryStream* ms,
                     uint32_t fourcc,
                     uint32_t width,
                     uint32_t height,
                     T* frame) {
    int y1_pos, y2_pos, u_pos, v_pos;
    if (!GetYuv422Packing(fourcc, &y1_pos, &y2_pos, &u_pos, &v_pos)) {
      return false;
    }

    const uint8_t* start = reinterpret_cast<const uint8_t*>(ms->GetBuffer());
    int awidth = (width + 1) & ~1;
    frame->InitToBlack(width, height, 1, 1, 0);
    int stride_y = frame->GetYPitch();
    int stride_u = frame->GetUPitch();
    int stride_v = frame->GetVPitch();
    for (uint32_t y = 0; y < height; ++y) {
      for (uint32_t x = 0; x < width; x += 2) {
        const uint8_t* quad1 = start + (y * awidth + x) * 2;
        frame->GetYPlane()[stride_y * y + x] = quad1[y1_pos];
        if ((x + 1) < width) {
          frame->GetYPlane()[stride_y * y + x + 1] = quad1[y2_pos];
        }
        if ((y & 1) == 0) {
          const uint8_t* quad2 = quad1 + awidth * 2;
          if ((y + 1) >= height) {
            quad2 = quad1;
          }
          frame->GetUPlane()[stride_u * (y / 2) + x / 2] =
              (quad1[u_pos] + quad2[u_pos] + 1) / 2;
          frame->GetVPlane()[stride_v * (y / 2) + x / 2] =
              (quad1[v_pos] + quad2[v_pos] + 1) / 2;
        }
      }
    }
    return true;
  }

  // Convert RGB to 420.
  // A negative height inverts the image.
  bool ConvertRgb(const rtc::MemoryStream* ms,
                  uint32_t fourcc,
                  int32_t width,
                  int32_t height,
                  T* frame) {
    int r_pos, g_pos, b_pos, bytes;
    if (!GetRgbPacking(fourcc, &r_pos, &g_pos, &b_pos, &bytes)) {
      return false;
    }
    int pitch = width * bytes;
    const uint8_t* start = reinterpret_cast<const uint8_t*>(ms->GetBuffer());
    if (height < 0) {
      height = -height;
      start = start + pitch * (height - 1);
      pitch = -pitch;
    }
    frame->InitToBlack(width, height, 1, 1, 0);
    int stride_y = frame->GetYPitch();
    int stride_u = frame->GetUPitch();
    int stride_v = frame->GetVPitch();
    for (int32_t y = 0; y < height; y += 2) {
      for (int32_t x = 0; x < width; x += 2) {
        const uint8_t* rgb[4];
        uint8_t yuv[4][3];
        rgb[0] = start + y * pitch + x * bytes;
        rgb[1] = rgb[0] + ((x + 1) < width ? bytes : 0);
        rgb[2] = rgb[0] + ((y + 1) < height ? pitch : 0);
        rgb[3] = rgb[2] + ((x + 1) < width ? bytes : 0);
        for (size_t i = 0; i < 4; ++i) {
          ConvertRgbPixel(rgb[i][r_pos], rgb[i][g_pos], rgb[i][b_pos],
                          &yuv[i][0], &yuv[i][1], &yuv[i][2]);
        }
        frame->GetYPlane()[stride_y * y + x] = yuv[0][0];
        if ((x + 1) < width) {
          frame->GetYPlane()[stride_y * y + x + 1] = yuv[1][0];
        }
        if ((y + 1) < height) {
          frame->GetYPlane()[stride_y * (y + 1) + x] = yuv[2][0];
          if ((x + 1) < width) {
            frame->GetYPlane()[stride_y * (y + 1) + x + 1] = yuv[3][0];
          }
        }
        frame->GetUPlane()[stride_u * (y / 2) + x / 2] =
            (yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1] + 2) / 4;
        frame->GetVPlane()[stride_v * (y / 2) + x / 2] =
            (yuv[0][2] + yuv[1][2] + yuv[2][2] + yuv[3][2] + 2) / 4;
      }
    }
    return true;
  }

  // Simple and slow RGB->YUV conversion. From NTSC standard, c/o Wikipedia.
  void ConvertRgbPixel(uint8_t r,
                       uint8_t g,
                       uint8_t b,
                       uint8_t* y,
                       uint8_t* u,
                       uint8_t* v) {
    *y = static_cast<int>(.257 * r + .504 * g + .098 * b) + 16;
    *u = static_cast<int>(-.148 * r - .291 * g + .439 * b) + 128;
    *v = static_cast<int>(.439 * r - .368 * g - .071 * b) + 128;
  }

  bool GetYuv422Packing(uint32_t fourcc,
                        int* y1_pos,
                        int* y2_pos,
                        int* u_pos,
                        int* v_pos) {
    if (fourcc == cricket::FOURCC_YUY2) {
      *y1_pos = 0; *u_pos = 1; *y2_pos = 2; *v_pos = 3;
    } else if (fourcc == cricket::FOURCC_UYVY) {
      *u_pos = 0; *y1_pos = 1; *v_pos = 2; *y2_pos = 3;
    } else {
      return false;
    }
    return true;
  }

  bool GetRgbPacking(uint32_t fourcc,
                     int* r_pos,
                     int* g_pos,
                     int* b_pos,
                     int* bytes) {
    if (fourcc == cricket::FOURCC_RAW) {
      *r_pos = 0; *g_pos = 1; *b_pos = 2; *bytes = 3;  // RGB in memory.
    } else if (fourcc == cricket::FOURCC_24BG) {
      *r_pos = 2; *g_pos = 1; *b_pos = 0; *bytes = 3;  // BGR in memory.
    } else if (fourcc == cricket::FOURCC_ABGR) {
      *r_pos = 0; *g_pos = 1; *b_pos = 2; *bytes = 4;  // RGBA in memory.
    } else if (fourcc == cricket::FOURCC_BGRA) {
      *r_pos = 1; *g_pos = 2; *b_pos = 3; *bytes = 4;  // ARGB in memory.
    } else if (fourcc == cricket::FOURCC_ARGB) {
      *r_pos = 2; *g_pos = 1; *b_pos = 0; *bytes = 4;  // BGRA in memory.
    } else {
      return false;
    }
    return true;
  }

  // Comparison functions for testing.
  static bool IsNull(const cricket::VideoFrame& frame) {
    return !frame.GetYPlane();
  }

  static bool IsSize(const cricket::VideoFrame& frame,
                     uint32_t width,
                     uint32_t height) {
    return !IsNull(frame) && frame.GetYPitch() >= static_cast<int32_t>(width) &&
           frame.GetUPitch() >= static_cast<int32_t>(width) / 2 &&
           frame.GetVPitch() >= static_cast<int32_t>(width) / 2 &&
           frame.GetWidth() == width && frame.GetHeight() == height;
  }

  static bool IsPlaneEqual(const std::string& name,
                           const uint8_t* plane1,
                           uint32_t pitch1,
                           const uint8_t* plane2,
                           uint32_t pitch2,
                           uint32_t width,
                           uint32_t height,
                           int max_error) {
    const uint8_t* r1 = plane1;
    const uint8_t* r2 = plane2;
    for (uint32_t y = 0; y < height; ++y) {
      for (uint32_t x = 0; x < width; ++x) {
        if (abs(static_cast<int>(r1[x] - r2[x])) > max_error) {
          LOG(LS_INFO) << "IsPlaneEqual(" << name << "): pixel["
                       << x << "," << y << "] differs: "
                       << static_cast<int>(r1[x]) << " vs "
                       << static_cast<int>(r2[x]);
          return false;
        }
      }
      r1 += pitch1;
      r2 += pitch2;
    }
    return true;
  }

  static bool IsEqual(const cricket::VideoFrame& frame,
                      size_t width,
                      size_t height,
                      size_t pixel_width,
                      size_t pixel_height,
                      int64_t time_stamp,
                      const uint8_t* y,
                      uint32_t ypitch,
                      const uint8_t* u,
                      uint32_t upitch,
                      const uint8_t* v,
                      uint32_t vpitch,
                      int max_error) {
    return IsSize(frame, static_cast<uint32_t>(width),
                  static_cast<uint32_t>(height)) &&
           frame.GetPixelWidth() == pixel_width &&
           frame.GetPixelHeight() == pixel_height &&
           frame.GetTimeStamp() == time_stamp &&
           IsPlaneEqual("y", frame.GetYPlane(), frame.GetYPitch(), y, ypitch,
                        static_cast<uint32_t>(width),
                        static_cast<uint32_t>(height), max_error) &&
           IsPlaneEqual("u", frame.GetUPlane(), frame.GetUPitch(), u, upitch,
                        static_cast<uint32_t>((width + 1) / 2),
                        static_cast<uint32_t>((height + 1) / 2), max_error) &&
           IsPlaneEqual("v", frame.GetVPlane(), frame.GetVPitch(), v, vpitch,
                        static_cast<uint32_t>((width + 1) / 2),
                        static_cast<uint32_t>((height + 1) / 2), max_error);
  }

  static bool IsEqual(const cricket::VideoFrame& frame1,
                      const cricket::VideoFrame& frame2,
                      int max_error) {
    return IsEqual(frame1,
                   frame2.GetWidth(), frame2.GetHeight(),
                   frame2.GetPixelWidth(), frame2.GetPixelHeight(),
                   frame2.GetTimeStamp(),
                   frame2.GetYPlane(), frame2.GetYPitch(),
                   frame2.GetUPlane(), frame2.GetUPitch(),
                   frame2.GetVPlane(), frame2.GetVPitch(),
                   max_error);
  }

  static bool IsEqualWithCrop(const cricket::VideoFrame& frame1,
                              const cricket::VideoFrame& frame2,
                              int hcrop, int vcrop, int max_error) {
    return frame1.GetWidth() <= frame2.GetWidth() &&
           frame1.GetHeight() <= frame2.GetHeight() &&
           IsEqual(frame1,
                   frame2.GetWidth() - hcrop * 2,
                   frame2.GetHeight() - vcrop * 2,
                   frame2.GetPixelWidth(), frame2.GetPixelHeight(),
                   frame2.GetTimeStamp(),
                   frame2.GetYPlane() + vcrop * frame2.GetYPitch()
                       + hcrop,
                   frame2.GetYPitch(),
                   frame2.GetUPlane() + vcrop * frame2.GetUPitch() / 2
                       + hcrop / 2,
                   frame2.GetUPitch(),
                   frame2.GetVPlane() + vcrop * frame2.GetVPitch() / 2
                       + hcrop / 2,
                   frame2.GetVPitch(),
                   max_error);
  }

  static bool IsBlack(const cricket::VideoFrame& frame) {
    return !IsNull(frame) &&
        *frame.GetYPlane() == 16 &&
        *frame.GetUPlane() == 128 &&
        *frame.GetVPlane() == 128;
  }

  ////////////////////////
  // Construction tests //
  ////////////////////////

  // Test constructing an image from a I420 buffer.
  void ConstructI420() {
    T frame;
    EXPECT_TRUE(IsNull(frame));
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuvSample(kWidth, kHeight, 12));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420,
                          kWidth, kHeight, &frame));

    const uint8_t* y = reinterpret_cast<uint8_t*>(ms.get()->GetBuffer());
    const uint8_t* u = y + kWidth * kHeight;
    const uint8_t* v = u + kWidth * kHeight / 4;
    EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, 1, 1, 0, y, kWidth, u,
                        kWidth / 2, v, kWidth / 2, 0));
  }

  // Test constructing an image from a YV12 buffer.
  void ConstructYV12() {
    T frame;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuvSample(kWidth, kHeight, 12));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YV12,
                          kWidth, kHeight, &frame));

    const uint8_t* y = reinterpret_cast<uint8_t*>(ms.get()->GetBuffer());
    const uint8_t* v = y + kWidth * kHeight;
    const uint8_t* u = v + kWidth * kHeight / 4;
    EXPECT_TRUE(IsEqual(frame, kWidth, kHeight, 1, 1, 0, y, kWidth, u,
                        kWidth / 2, v, kWidth / 2, 0));
  }

  // Test constructing an image from a I422 buffer.
  void ConstructI422() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    size_t buf_size = kWidth * kHeight * 2;
    rtc::scoped_ptr<uint8_t[]> buf(new uint8_t[buf_size + kAlignment]);
    uint8_t* y = ALIGNP(buf.get(), kAlignment);
    uint8_t* u = y + kWidth * kHeight;
    uint8_t* v = u + (kWidth / 2) * kHeight;
    EXPECT_EQ(0, libyuv::I420ToI422(frame1.GetYPlane(), frame1.GetYPitch(),
                                    frame1.GetUPlane(), frame1.GetUPitch(),
                                    frame1.GetVPlane(), frame1.GetVPitch(),
                                    y, kWidth,
                                    u, kWidth / 2,
                                    v, kWidth / 2,
                                    kWidth, kHeight));
    EXPECT_TRUE(LoadFrame(y, buf_size, cricket::FOURCC_I422,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 1));
  }

  // Test constructing an image from a YUY2 buffer.
  void ConstructYuy2() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    size_t buf_size = kWidth * kHeight * 2;
    rtc::scoped_ptr<uint8_t[]> buf(new uint8_t[buf_size + kAlignment]);
    uint8_t* yuy2 = ALIGNP(buf.get(), kAlignment);
    EXPECT_EQ(0, libyuv::I420ToYUY2(frame1.GetYPlane(), frame1.GetYPitch(),
                                    frame1.GetUPlane(), frame1.GetUPitch(),
                                    frame1.GetVPlane(), frame1.GetVPitch(),
                                    yuy2, kWidth * 2,
                                    kWidth, kHeight));
    EXPECT_TRUE(LoadFrame(yuy2, buf_size, cricket::FOURCC_YUY2,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
  }

  // Test constructing an image from a YUY2 buffer with buffer unaligned.
  void ConstructYuy2Unaligned() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    size_t buf_size = kWidth * kHeight * 2;
    rtc::scoped_ptr<uint8_t[]> buf(new uint8_t[buf_size + kAlignment + 1]);
    uint8_t* yuy2 = ALIGNP(buf.get(), kAlignment) + 1;
    EXPECT_EQ(0, libyuv::I420ToYUY2(frame1.GetYPlane(), frame1.GetYPitch(),
                                    frame1.GetUPlane(), frame1.GetUPitch(),
                                    frame1.GetVPlane(), frame1.GetVPitch(),
                                    yuy2, kWidth * 2,
                                    kWidth, kHeight));
    EXPECT_TRUE(LoadFrame(yuy2, buf_size, cricket::FOURCC_YUY2,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
  }

  // Test constructing an image from a wide YUY2 buffer.
  // Normal is 1280x720.  Wide is 12800x72
  void ConstructYuy2Wide() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth * 10, kHeight / 10));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2,
                              kWidth * 10, kHeight / 10,
                              &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2,
                          kWidth * 10, kHeight / 10, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
  }

  // Test constructing an image from a UYVY buffer.
  void ConstructUyvy() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight,
                              &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
  }

  // Test constructing an image from a random buffer.
  // We are merely verifying that the code succeeds and is free of crashes.
  void ConstructM420() {
    T frame;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuvSample(kWidth, kHeight, 12));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_M420,
                          kWidth, kHeight, &frame));
  }

  void ConstructNV21() {
    T frame;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuvSample(kWidth, kHeight, 12));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_NV21,
                          kWidth, kHeight, &frame));
  }

  void ConstructNV12() {
    T frame;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuvSample(kWidth, kHeight, 12));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_NV12,
                          kWidth, kHeight, &frame));
  }

  // Test constructing an image from a ABGR buffer
  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
  void ConstructABGR() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateRgbSample(cricket::FOURCC_ABGR, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ABGR, kWidth, kHeight,
                           &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ABGR,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
  }

  // Test constructing an image from a ARGB buffer
  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
  void ConstructARGB() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB, kWidth, kHeight,
                           &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
  }

  // Test constructing an image from a wide ARGB buffer
  // Normal is 1280x720.  Wide is 12800x72
  void ConstructARGBWide() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateRgbSample(cricket::FOURCC_ARGB, kWidth * 10, kHeight / 10));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB,
                           kWidth * 10, kHeight / 10, &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB,
                          kWidth * 10, kHeight / 10, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
  }

  // Test constructing an image from an BGRA buffer.
  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
  void ConstructBGRA() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateRgbSample(cricket::FOURCC_BGRA, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_BGRA, kWidth, kHeight,
                           &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_BGRA,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
  }

  // Test constructing an image from a 24BG buffer.
  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
  void Construct24BG() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateRgbSample(cricket::FOURCC_24BG, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_24BG, kWidth, kHeight,
                           &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_24BG,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
  }

  // Test constructing an image from a raw RGB buffer.
  // Due to rounding, some pixels may differ slightly from the VideoFrame impl.
  void ConstructRaw() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateRgbSample(cricket::FOURCC_RAW, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_RAW, kWidth, kHeight,
                           &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_RAW,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 2));
  }

  // Test constructing an image from a RGB565 buffer
  void ConstructRGB565() {
    T frame1, frame2;
    size_t out_size = kWidth * kHeight * 2;
    rtc::scoped_ptr<uint8_t[]> outbuf(new uint8_t[out_size + kAlignment]);
    uint8_t* out = ALIGNP(outbuf.get(), kAlignment);
    T frame;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_RGBP,
                                                 out,
                                                 out_size, kWidth * 2));
    EXPECT_TRUE(LoadFrame(out, out_size, cricket::FOURCC_RGBP,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 20));
  }

  // Test constructing an image from a ARGB1555 buffer
  void ConstructARGB1555() {
    T frame1, frame2;
    size_t out_size = kWidth * kHeight * 2;
    rtc::scoped_ptr<uint8_t[]> outbuf(new uint8_t[out_size + kAlignment]);
    uint8_t* out = ALIGNP(outbuf.get(), kAlignment);
    T frame;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_RGBO,
                                                 out,
                                                 out_size, kWidth * 2));
    EXPECT_TRUE(LoadFrame(out, out_size, cricket::FOURCC_RGBO,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 20));
  }

  // Test constructing an image from a ARGB4444 buffer
  void ConstructARGB4444() {
    T frame1, frame2;
    size_t out_size = kWidth * kHeight * 2;
    rtc::scoped_ptr<uint8_t[]> outbuf(new uint8_t[out_size + kAlignment]);
    uint8_t* out = ALIGNP(outbuf.get(), kAlignment);
    T frame;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(cricket::FOURCC_R444,
                                                 out,
                                                 out_size, kWidth * 2));
    EXPECT_TRUE(LoadFrame(out, out_size, cricket::FOURCC_R444,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 20));
  }

// Macro to help test different rotations
#define TEST_MIRROR(FOURCC, BPP)                                               \
  void Construct##FOURCC##Mirror() {                                           \
    T frame1, frame2, frame3;                                                  \
    rtc::scoped_ptr<rtc::MemoryStream> ms(                                     \
        CreateYuvSample(kWidth, kHeight, BPP));                                \
    ASSERT_TRUE(ms.get() != NULL);                                             \
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_##FOURCC, kWidth,          \
                          -kHeight, kWidth, kHeight,                           \
                          webrtc::kVideoRotation_180, &frame1));               \
    size_t data_size;                                                          \
    bool ret = ms->GetSize(&data_size);                                        \
    EXPECT_TRUE(ret);                                                          \
    EXPECT_TRUE(frame2.Init(cricket::FOURCC_##FOURCC, kWidth, kHeight, kWidth, \
                            kHeight,                                           \
                            reinterpret_cast<uint8_t*>(ms->GetBuffer()),       \
                            data_size, 1, 1, 0, webrtc::kVideoRotation_0));    \
    int width_rotate = static_cast<int>(frame1.GetWidth());                    \
    int height_rotate = static_cast<int>(frame1.GetHeight());                  \
    EXPECT_TRUE(frame3.InitToBlack(width_rotate, height_rotate, 1, 1, 0));     \
    libyuv::I420Mirror(                                                        \
        frame2.GetYPlane(), frame2.GetYPitch(), frame2.GetUPlane(),            \
        frame2.GetUPitch(), frame2.GetVPlane(), frame2.GetVPitch(),            \
        frame3.GetYPlane(), frame3.GetYPitch(), frame3.GetUPlane(),            \
        frame3.GetUPitch(), frame3.GetVPlane(), frame3.GetVPitch(), kWidth,    \
        kHeight);                                                              \
    EXPECT_TRUE(IsEqual(frame1, frame3, 0));                                   \
  }

  TEST_MIRROR(I420, 420)

// Macro to help test different rotations
#define TEST_ROTATE(FOURCC, BPP, ROTATE)                                       \
  void Construct##FOURCC##Rotate##ROTATE() {                                   \
    T frame1, frame2, frame3;                                                  \
    rtc::scoped_ptr<rtc::MemoryStream> ms(                                     \
        CreateYuvSample(kWidth, kHeight, BPP));                                \
    ASSERT_TRUE(ms.get() != NULL);                                             \
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_##FOURCC, kWidth, kHeight, \
                          kWidth, kHeight, webrtc::kVideoRotation_##ROTATE,    \
                          &frame1));                                           \
    size_t data_size;                                                          \
    bool ret = ms->GetSize(&data_size);                                        \
    EXPECT_TRUE(ret);                                                          \
    EXPECT_TRUE(frame2.Init(cricket::FOURCC_##FOURCC, kWidth, kHeight, kWidth, \
                            kHeight,                                           \
                            reinterpret_cast<uint8_t*>(ms->GetBuffer()),       \
                            data_size, 1, 1, 0, webrtc::kVideoRotation_0));    \
    int width_rotate = static_cast<int>(frame1.GetWidth());                    \
    int height_rotate = static_cast<int>(frame1.GetHeight());                  \
    EXPECT_TRUE(frame3.InitToBlack(width_rotate, height_rotate, 1, 1, 0));     \
    libyuv::I420Rotate(                                                        \
        frame2.GetYPlane(), frame2.GetYPitch(), frame2.GetUPlane(),            \
        frame2.GetUPitch(), frame2.GetVPlane(), frame2.GetVPitch(),            \
        frame3.GetYPlane(), frame3.GetYPitch(), frame3.GetUPlane(),            \
        frame3.GetUPitch(), frame3.GetVPlane(), frame3.GetVPitch(), kWidth,    \
        kHeight, libyuv::kRotate##ROTATE);                                     \
    EXPECT_TRUE(IsEqual(frame1, frame3, 0));                                   \
  }

  // Test constructing an image with rotation.
  TEST_ROTATE(I420, 12, 0)
  TEST_ROTATE(I420, 12, 90)
  TEST_ROTATE(I420, 12, 180)
  TEST_ROTATE(I420, 12, 270)
  TEST_ROTATE(YV12, 12, 0)
  TEST_ROTATE(YV12, 12, 90)
  TEST_ROTATE(YV12, 12, 180)
  TEST_ROTATE(YV12, 12, 270)
  TEST_ROTATE(NV12, 12, 0)
  TEST_ROTATE(NV12, 12, 90)
  TEST_ROTATE(NV12, 12, 180)
  TEST_ROTATE(NV12, 12, 270)
  TEST_ROTATE(NV21, 12, 0)
  TEST_ROTATE(NV21, 12, 90)
  TEST_ROTATE(NV21, 12, 180)
  TEST_ROTATE(NV21, 12, 270)
  TEST_ROTATE(UYVY, 16, 0)
  TEST_ROTATE(UYVY, 16, 90)
  TEST_ROTATE(UYVY, 16, 180)
  TEST_ROTATE(UYVY, 16, 270)
  TEST_ROTATE(YUY2, 16, 0)
  TEST_ROTATE(YUY2, 16, 90)
  TEST_ROTATE(YUY2, 16, 180)
  TEST_ROTATE(YUY2, 16, 270)

  // Test constructing an image from a UYVY buffer rotated 90 degrees.
  void ConstructUyvyRotate90() {
    T frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight,
                          kWidth, kHeight, webrtc::kVideoRotation_90, &frame2));
  }

  // Test constructing an image from a UYVY buffer rotated 180 degrees.
  void ConstructUyvyRotate180() {
    T frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight,
                          kWidth, kHeight, webrtc::kVideoRotation_180,
                          &frame2));
  }

  // Test constructing an image from a UYVY buffer rotated 270 degrees.
  void ConstructUyvyRotate270() {
    T frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight,
                          kWidth, kHeight, webrtc::kVideoRotation_270,
                          &frame2));
  }

  // Test constructing an image from a YUY2 buffer rotated 90 degrees.
  void ConstructYuy2Rotate90() {
    T frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight,
                          kWidth, kHeight, webrtc::kVideoRotation_90, &frame2));
  }

  // Test constructing an image from a YUY2 buffer rotated 180 degrees.
  void ConstructYuy2Rotate180() {
    T frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight,
                          kWidth, kHeight, webrtc::kVideoRotation_180,
                          &frame2));
  }

  // Test constructing an image from a YUY2 buffer rotated 270 degrees.
  void ConstructYuy2Rotate270() {
    T frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight,
                          kWidth, kHeight, webrtc::kVideoRotation_270,
                          &frame2));
  }

  // Test 1 pixel edge case image I420 buffer.
  void ConstructI4201Pixel() {
    T frame;
    uint8_t pixel[3] = {1, 2, 3};
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_TRUE(frame.Init(cricket::FOURCC_I420, 1, 1, 1, 1, pixel,
                             sizeof(pixel), 1, 1, 0, webrtc::kVideoRotation_0));
    }
    const uint8_t* y = pixel;
    const uint8_t* u = y + 1;
    const uint8_t* v = u + 1;
    EXPECT_TRUE(IsEqual(frame, 1, 1, 1, 1, 0, y, 1, u, 1, v, 1, 0));
  }

  // Test 5 pixel edge case image.
  void ConstructI4205Pixel() {
    T frame;
    uint8_t pixels5x5[5 * 5 + ((5 + 1) / 2 * (5 + 1) / 2) * 2];
    memset(pixels5x5, 1, 5 * 5 + ((5 + 1) / 2 * (5 + 1) / 2) *  2);
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_TRUE(frame.Init(cricket::FOURCC_I420, 5, 5, 5, 5, pixels5x5,
                             sizeof(pixels5x5), 1, 1, 0,
                             webrtc::kVideoRotation_0));
    }
    EXPECT_EQ(5u, frame.GetWidth());
    EXPECT_EQ(5u, frame.GetHeight());
    EXPECT_EQ(5, frame.GetYPitch());
    EXPECT_EQ(3, frame.GetUPitch());
    EXPECT_EQ(3, frame.GetVPitch());
  }

  // Test 1 pixel edge case image ARGB buffer.
  void ConstructARGB1Pixel() {
    T frame;
    uint8_t pixel[4] = {64, 128, 192, 255};
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_TRUE(frame.Init(cricket::FOURCC_ARGB, 1, 1, 1, 1, pixel,
                             sizeof(pixel), 1, 1, 0,
                             webrtc::kVideoRotation_0));
    }
    // Convert back to ARGB.
    size_t out_size = 4;
    rtc::scoped_ptr<uint8_t[]> outbuf(new uint8_t[out_size + kAlignment]);
    uint8_t* out = ALIGNP(outbuf.get(), kAlignment);

    EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB,
                                                 out,
                                                 out_size,    // buffer size
                                                 out_size));  // stride
  #ifdef USE_LMI_CONVERT
    // TODO(fbarchard): Expected to fail, but not crash.
    EXPECT_FALSE(IsPlaneEqual("argb", pixel, 4, out, 4, 3, 1, 2));
  #else
    // TODO(fbarchard): Check for overwrite.
    EXPECT_TRUE(IsPlaneEqual("argb", pixel, 4, out, 4, 3, 1, 2));
  #endif
  }

  // Test Black, White and Grey pixels.
  void ConstructARGBBlackWhitePixel() {
    T frame;
    uint8_t pixel[10 * 4] = {0,   0,   0,   255,   // Black.
                             0,   0,   0,   255,   // Black.
                             64,  64,  64,  255,   // Dark Grey.
                             64,  64,  64,  255,   // Dark Grey.
                             128, 128, 128, 255,   // Grey.
                             128, 128, 128, 255,   // Grey.
                             196, 196, 196, 255,   // Light Grey.
                             196, 196, 196, 255,   // Light Grey.
                             255, 255, 255, 255,   // White.
                             255, 255, 255, 255};  // White.

    for (int i = 0; i < repeat_; ++i) {
      EXPECT_TRUE(frame.Init(cricket::FOURCC_ARGB, 10, 1, 10, 1, pixel,
                             sizeof(pixel), 1, 1, 0,
                             webrtc::kVideoRotation_0));
    }
    // Convert back to ARGB
    size_t out_size = 10 * 4;
    rtc::scoped_ptr<uint8_t[]> outbuf(new uint8_t[out_size + kAlignment]);
    uint8_t* out = ALIGNP(outbuf.get(), kAlignment);

    EXPECT_EQ(out_size, frame.ConvertToRgbBuffer(cricket::FOURCC_ARGB,
                                                 out,
                                                 out_size,    // buffer size.
                                                 out_size));  // stride.
    EXPECT_TRUE(IsPlaneEqual("argb", pixel, out_size,
                             out, out_size,
                             out_size, 1, 2));
  }

  // Test constructing an image from an I420 buffer with horizontal cropping.
  void ConstructI420CropHorizontal() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_I420, kWidth, kHeight,
                          kWidth * 3 / 4, kHeight, webrtc::kVideoRotation_0,
                          &frame2));
    EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, kWidth / 8, 0, 0));
  }

  // Test constructing an image from a YUY2 buffer with horizontal cropping.
  void ConstructYuy2CropHorizontal() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight,
                              &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight,
                          kWidth * 3 / 4, kHeight, webrtc::kVideoRotation_0,
                          &frame2));
    EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, kWidth / 8, 0, 0));
  }

  // Test constructing an image from an ARGB buffer with horizontal cropping.
  void ConstructARGBCropHorizontal() {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateRgbSample(cricket::FOURCC_ARGB, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB, kWidth, kHeight,
                           &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB, kWidth, kHeight,
                          kWidth * 3 / 4, kHeight, webrtc::kVideoRotation_0,
                          &frame2));
    EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, kWidth / 8, 0, 2));
  }

  // Test constructing an image from an I420 buffer, cropping top and bottom.
  void ConstructI420CropVertical() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_I420, kWidth, kHeight,
                          kWidth, kHeight * 3 / 4, webrtc::kVideoRotation_0,
                          &frame2));
    EXPECT_TRUE(IsEqualWithCrop(frame2, frame1, 0, kHeight / 8, 0));
  }

  // Test constructing an image from I420 synonymous formats.
  void ConstructI420Aliases() {
    T frame1, frame2, frame3;
    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_I420, kWidth, kHeight,
                          &frame1));
    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_IYUV, kWidth, kHeight,
                          &frame2));
    ASSERT_TRUE(LoadFrame(kImageFilename, cricket::FOURCC_YU12, kWidth, kHeight,
                          &frame3));
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
    EXPECT_TRUE(IsEqual(frame1, frame3, 0));
  }

  // Test constructing an image from an I420 MJPG buffer.
  void ConstructMjpgI420() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    ASSERT_TRUE(LoadFrame(kJpeg420Filename,
                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 32));
  }

  // Test constructing an image from an I422 MJPG buffer.
  void ConstructMjpgI422() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    ASSERT_TRUE(LoadFrame(kJpeg422Filename,
                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 32));
  }

  // Test constructing an image from an I444 MJPG buffer.
  void ConstructMjpgI444() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    ASSERT_TRUE(LoadFrame(kJpeg444Filename,
                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 32));
  }

  // Test constructing an image from an I444 MJPG buffer.
  void ConstructMjpgI411() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    ASSERT_TRUE(LoadFrame(kJpeg411Filename,
                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsEqual(frame1, frame2, 32));
  }

  // Test constructing an image from an I400 MJPG buffer.
  // TODO(fbarchard): Stronger compare on chroma.  Compare agaisnt a grey image.
  void ConstructMjpgI400() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    ASSERT_TRUE(LoadFrame(kJpeg400Filename,
                          cricket::FOURCC_MJPG, kWidth, kHeight, &frame2));
    EXPECT_TRUE(IsPlaneEqual("y", frame1.GetYPlane(), frame1.GetYPitch(),
                             frame2.GetYPlane(), frame2.GetYPitch(),
                             kWidth, kHeight, 32));
    EXPECT_TRUE(IsEqual(frame1, frame2, 128));
  }

  // Test constructing an image from an I420 MJPG buffer.
  void ValidateFrame(const char* name,
                     uint32_t fourcc,
                     int data_adjust,
                     int size_adjust,
                     bool expected_result) {
    T frame;
    rtc::scoped_ptr<rtc::MemoryStream> ms(LoadSample(name));
    ASSERT_TRUE(ms.get() != NULL);
    const uint8_t* sample =
        reinterpret_cast<const uint8_t*>(ms.get()->GetBuffer());
    size_t sample_size;
    ms->GetSize(&sample_size);
    // Optional adjust size to test invalid size.
    size_t data_size = sample_size + data_adjust;

    // Allocate a buffer with end page aligned.
    const int kPadToHeapSized = 16 * 1024 * 1024;
    rtc::scoped_ptr<uint8_t[]> page_buffer(
        new uint8_t[((data_size + kPadToHeapSized + 4095) & ~4095)]);
    uint8_t* data_ptr = page_buffer.get();
    if (!data_ptr) {
      LOG(LS_WARNING) << "Failed to allocate memory for ValidateFrame test.";
      EXPECT_FALSE(expected_result);  // NULL is okay if failure was expected.
      return;
    }
    data_ptr += kPadToHeapSized + (-(static_cast<int>(data_size)) & 4095);
    memcpy(data_ptr, sample, std::min(data_size, sample_size));
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_EQ(expected_result, frame.Validate(fourcc, kWidth, kHeight,
                                                data_ptr,
                                                sample_size + size_adjust));
    }
  }

  // Test validate for I420 MJPG buffer.
  void ValidateMjpgI420() {
    ValidateFrame(kJpeg420Filename, cricket::FOURCC_MJPG, 0, 0, true);
  }

  // Test validate for I422 MJPG buffer.
  void ValidateMjpgI422() {
    ValidateFrame(kJpeg422Filename, cricket::FOURCC_MJPG, 0, 0, true);
  }

  // Test validate for I444 MJPG buffer.
  void ValidateMjpgI444() {
    ValidateFrame(kJpeg444Filename, cricket::FOURCC_MJPG, 0, 0, true);
  }

  // Test validate for I411 MJPG buffer.
  void ValidateMjpgI411() {
    ValidateFrame(kJpeg411Filename, cricket::FOURCC_MJPG, 0, 0, true);
  }

  // Test validate for I400 MJPG buffer.
  void ValidateMjpgI400() {
    ValidateFrame(kJpeg400Filename, cricket::FOURCC_MJPG, 0, 0, true);
  }

  // Test validate for I420 buffer.
  void ValidateI420() {
    ValidateFrame(kImageFilename, cricket::FOURCC_I420, 0, 0, true);
  }

  // Test validate for I420 buffer where size is too small
  void ValidateI420SmallSize() {
    ValidateFrame(kImageFilename, cricket::FOURCC_I420, 0, -16384, false);
  }

  // Test validate for I420 buffer where size is too large (16 MB)
  // Will produce warning but pass.
  void ValidateI420LargeSize() {
    ValidateFrame(kImageFilename, cricket::FOURCC_I420, 16000000, 16000000,
                  true);
  }

  // Test validate for I420 buffer where size is 1 GB (not reasonable).
  void ValidateI420HugeSize() {
#ifndef WIN32  // TODO(fbarchard): Reenable when fixing bug 9603762.
    ValidateFrame(kImageFilename, cricket::FOURCC_I420, 1000000000u,
                  1000000000u, false);
#endif
  }

  // The following test that Validate crashes if the size is greater than the
  // actual buffer size.
  // TODO(fbarchard): Consider moving a filter into the capturer/plugin.
#if defined(_MSC_VER) && !defined(NDEBUG)
  int ExceptionFilter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {
    if (code == EXCEPTION_ACCESS_VIOLATION) {
      LOG(LS_INFO) << "Caught EXCEPTION_ACCESS_VIOLATION as expected.";
      return EXCEPTION_EXECUTE_HANDLER;
    } else {
      LOG(LS_INFO) << "Did not catch EXCEPTION_ACCESS_VIOLATION.  Unexpected.";
      return EXCEPTION_CONTINUE_SEARCH;
    }
  }

  // Test validate fails for truncated MJPG data buffer.  If ValidateFrame
  // crashes the exception handler will return and unittest passes with OK.
  void ValidateMjpgI420InvalidSize() {
    __try {
      ValidateFrame(kJpeg420Filename, cricket::FOURCC_MJPG, -16384, 0, false);
      FAIL() << "Validate was expected to cause EXCEPTION_ACCESS_VIOLATION.";
    } __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
      return;  // Successfully crashed in ValidateFrame.
    }
  }

  // Test validate fails for truncated I420 buffer.
  void ValidateI420InvalidSize() {
    __try {
      ValidateFrame(kImageFilename, cricket::FOURCC_I420, -16384, 0, false);
      FAIL() << "Validate was expected to cause EXCEPTION_ACCESS_VIOLATION.";
    } __except(ExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
      return;  // Successfully crashed in ValidateFrame.
    }
  }
#endif

  // Test constructing an image from a YUY2 buffer (and synonymous formats).
  void ConstructYuy2Aliases() {
    T frame1, frame2, frame3, frame4;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_YUY2, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, kWidth, kHeight,
                              &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUVS,
                          kWidth, kHeight, &frame3));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUYV,
                          kWidth, kHeight, &frame4));
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
    EXPECT_TRUE(IsEqual(frame1, frame3, 0));
    EXPECT_TRUE(IsEqual(frame1, frame4, 0));
  }

  // Test constructing an image from a UYVY buffer (and synonymous formats).
  void ConstructUyvyAliases() {
    T frame1, frame2, frame3, frame4;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateYuv422Sample(cricket::FOURCC_UYVY, kWidth, kHeight));
    ASSERT_TRUE(ms.get() != NULL);
    EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_UYVY, kWidth, kHeight,
                              &frame1));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_UYVY,
                          kWidth, kHeight, &frame2));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_2VUY,
                          kWidth, kHeight, &frame3));
    EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_HDYC,
                          kWidth, kHeight, &frame4));
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
    EXPECT_TRUE(IsEqual(frame1, frame3, 0));
    EXPECT_TRUE(IsEqual(frame1, frame4, 0));
  }

  // Test creating a copy.
  void ConstructCopy() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_TRUE(frame2.Init(frame1));
    }
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
  }

  // Test creating a copy and check that it just increments the refcount.
  void ConstructCopyIsRef() {
    T frame1, frame2;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_TRUE(frame2.Init(frame1));
    }
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
    EXPECT_EQ(frame1.GetYPlane(), frame2.GetYPlane());
    EXPECT_EQ(frame1.GetUPlane(), frame2.GetUPlane());
    EXPECT_EQ(frame1.GetVPlane(), frame2.GetVPlane());
  }

  // Test creating an empty image and initing it to black.
  void ConstructBlack() {
    T frame;
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_TRUE(frame.InitToBlack(kWidth, kHeight, 1, 1, 0));
    }
    EXPECT_TRUE(IsSize(frame, kWidth, kHeight));
    EXPECT_TRUE(IsBlack(frame));
  }

  // Test constructing an image from a YUY2 buffer with a range of sizes.
  // Only tests that conversion does not crash or corrupt heap.
  void ConstructYuy2AllSizes() {
    T frame1, frame2;
    for (int height = kMinHeightAll; height <= kMaxHeightAll; ++height) {
      for (int width = kMinWidthAll; width <= kMaxWidthAll; ++width) {
        rtc::scoped_ptr<rtc::MemoryStream> ms(
            CreateYuv422Sample(cricket::FOURCC_YUY2, width, height));
        ASSERT_TRUE(ms.get() != NULL);
        EXPECT_TRUE(ConvertYuv422(ms.get(), cricket::FOURCC_YUY2, width, height,
                                  &frame1));
        EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_YUY2,
                              width, height, &frame2));
        EXPECT_TRUE(IsEqual(frame1, frame2, 0));
      }
    }
  }

  // Test constructing an image from a ARGB buffer with a range of sizes.
  // Only tests that conversion does not crash or corrupt heap.
  void ConstructARGBAllSizes() {
    T frame1, frame2;
    for (int height = kMinHeightAll; height <= kMaxHeightAll; ++height) {
      for (int width = kMinWidthAll; width <= kMaxWidthAll; ++width) {
        rtc::scoped_ptr<rtc::MemoryStream> ms(
            CreateRgbSample(cricket::FOURCC_ARGB, width, height));
        ASSERT_TRUE(ms.get() != NULL);
        EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB, width, height,
                               &frame1));
        EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB,
                              width, height, &frame2));
        EXPECT_TRUE(IsEqual(frame1, frame2, 64));
      }
    }
    // Test a practical window size for screencasting usecase.
    const int kOddWidth = 1228;
    const int kOddHeight = 260;
    for (int j = 0; j < 2; ++j) {
      for (int i = 0; i < 2; ++i) {
        rtc::scoped_ptr<rtc::MemoryStream> ms(
        CreateRgbSample(cricket::FOURCC_ARGB, kOddWidth + i, kOddHeight + j));
        ASSERT_TRUE(ms.get() != NULL);
        EXPECT_TRUE(ConvertRgb(ms.get(), cricket::FOURCC_ARGB,
                               kOddWidth + i, kOddHeight + j,
                               &frame1));
        EXPECT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_ARGB,
                              kOddWidth + i, kOddHeight + j, &frame2));
        EXPECT_TRUE(IsEqual(frame1, frame2, 64));
      }
    }
  }

  // Tests re-initing an existing image.
  void Reset(webrtc::VideoRotation rotation, bool apply_rotation) {
    T frame1, frame2;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        LoadSample(kImageFilename));
    ASSERT_TRUE(ms.get() != NULL);
    size_t data_size;
    ms->GetSize(&data_size);
    EXPECT_TRUE(frame1.InitToBlack(kWidth, kHeight, 1, 1, 0));
    EXPECT_TRUE(frame2.InitToBlack(kWidth, kHeight, 1, 1, 0));
    EXPECT_TRUE(IsBlack(frame1));
    EXPECT_TRUE(IsEqual(frame1, frame2, 0));
    EXPECT_TRUE(frame1.Reset(cricket::FOURCC_I420, kWidth, kHeight, kWidth,
                             kHeight,
                             reinterpret_cast<uint8_t*>(ms->GetBuffer()),
                             data_size, 1, 1, 0, rotation, apply_rotation));
    if (apply_rotation)
      EXPECT_EQ(webrtc::kVideoRotation_0, frame1.GetVideoRotation());
    else
      EXPECT_EQ(rotation, frame1.GetVideoRotation());

    // Swapp width and height if the frame is rotated 90 or 270 degrees.
    if (apply_rotation && (rotation == webrtc::kVideoRotation_90
        || rotation == webrtc::kVideoRotation_270)) {
      EXPECT_TRUE(kHeight == frame1.GetWidth());
      EXPECT_TRUE(kWidth == frame1.GetHeight());
    } else {
      EXPECT_TRUE(kWidth == frame1.GetWidth());
      EXPECT_TRUE(kHeight == frame1.GetHeight());
    }
    EXPECT_FALSE(IsBlack(frame1));
    EXPECT_FALSE(IsEqual(frame1, frame2, 0));
  }

  void ResetAndApplyRotation() {
    Reset(webrtc::kVideoRotation_90, true);
  }

  void ResetAndDontApplyRotation() {
    Reset(webrtc::kVideoRotation_90, false);
  }

  //////////////////////
  // Conversion tests //
  //////////////////////

  enum ToFrom { TO, FROM };

  // Helper function for test converting from I420 to packed formats.
  inline void ConvertToBuffer(int bpp,
                              int rowpad,
                              bool invert,
                              ToFrom to_from,
                              int error,
                              uint32_t fourcc,
                              int (*RGBToI420)(const uint8_t* src_frame,
                                               int src_stride_frame,
                                               uint8_t* dst_y,
                                               int dst_stride_y,
                                               uint8_t* dst_u,
                                               int dst_stride_u,
                                               uint8_t* dst_v,
                                               int dst_stride_v,
                                               int width,
                                               int height)) {
    T frame1, frame2;
    int repeat_to = (to_from == TO) ? repeat_ : 1;
    int repeat_from  = (to_from == FROM) ? repeat_ : 1;

    int astride = kWidth * bpp + rowpad;
    size_t out_size = astride * kHeight;
    rtc::scoped_ptr<uint8_t[]> outbuf(new uint8_t[out_size + kAlignment + 1]);
    memset(outbuf.get(), 0, out_size + kAlignment + 1);
    uint8_t* outtop = ALIGNP(outbuf.get(), kAlignment);
    uint8_t* out = outtop;
    int stride = astride;
    if (invert) {
      out += (kHeight - 1) * stride;  // Point to last row.
      stride = -stride;
    }
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));

    for (int i = 0; i < repeat_to; ++i) {
      EXPECT_EQ(out_size, frame1.ConvertToRgbBuffer(fourcc,
                                                    out,
                                                    out_size, stride));
    }
    EXPECT_TRUE(frame2.InitToBlack(kWidth, kHeight, 1, 1, 0));
    for (int i = 0; i < repeat_from; ++i) {
      EXPECT_EQ(0, RGBToI420(out, stride,
                             frame2.GetYPlane(), frame2.GetYPitch(),
                             frame2.GetUPlane(), frame2.GetUPitch(),
                             frame2.GetVPlane(), frame2.GetVPitch(),
                             kWidth, kHeight));
    }
    if (rowpad) {
      EXPECT_EQ(0, outtop[kWidth * bpp]);  // Ensure stride skipped end of row.
      EXPECT_NE(0, outtop[astride]);       // Ensure pixel at start of 2nd row.
    } else {
      EXPECT_NE(0, outtop[kWidth * bpp]);  // Expect something to be here.
    }
    EXPECT_EQ(0, outtop[out_size]);      // Ensure no overrun.
    EXPECT_TRUE(IsEqual(frame1, frame2, error));
  }

  static const int kError = 20;
  static const int kErrorHigh = 40;
  static const int kOddStride = 23;

  // Tests ConvertToRGBBuffer formats.
  void ConvertToARGBBuffer() {
    ConvertToBuffer(4, 0, false, TO, kError,
                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
  }
  void ConvertToBGRABuffer() {
    ConvertToBuffer(4, 0, false, TO, kError,
                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
  }
  void ConvertToABGRBuffer() {
    ConvertToBuffer(4, 0, false, TO, kError,
                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
  }
  void ConvertToRGB24Buffer() {
    ConvertToBuffer(3, 0, false, TO, kError,
                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
  }
  void ConvertToRAWBuffer() {
    ConvertToBuffer(3, 0, false, TO, kError,
                    cricket::FOURCC_RAW, libyuv::RAWToI420);
  }
  void ConvertToRGB565Buffer() {
    ConvertToBuffer(2, 0, false, TO, kError,
                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
  }
  void ConvertToARGB1555Buffer() {
    ConvertToBuffer(2, 0, false, TO, kError,
                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
  }
  void ConvertToARGB4444Buffer() {
    ConvertToBuffer(2, 0, false, TO, kError,
                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
  }
  void ConvertToI400Buffer() {
    ConvertToBuffer(1, 0, false, TO, 128,
                    cricket::FOURCC_I400, libyuv::I400ToI420);
  }
  void ConvertToYUY2Buffer() {
    ConvertToBuffer(2, 0, false, TO, kError,
                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
  }
  void ConvertToUYVYBuffer() {
    ConvertToBuffer(2, 0, false, TO, kError,
                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
  }

  // Tests ConvertToRGBBuffer formats with odd stride.
  void ConvertToARGBBufferStride() {
    ConvertToBuffer(4, kOddStride, false, TO, kError,
                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
  }
  void ConvertToBGRABufferStride() {
    ConvertToBuffer(4, kOddStride, false, TO, kError,
                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
  }
  void ConvertToABGRBufferStride() {
    ConvertToBuffer(4, kOddStride, false, TO, kError,
                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
  }
  void ConvertToRGB24BufferStride() {
    ConvertToBuffer(3, kOddStride, false, TO, kError,
                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
  }
  void ConvertToRAWBufferStride() {
    ConvertToBuffer(3, kOddStride, false, TO, kError,
                    cricket::FOURCC_RAW, libyuv::RAWToI420);
  }
  void ConvertToRGB565BufferStride() {
    ConvertToBuffer(2, kOddStride, false, TO, kError,
                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
  }
  void ConvertToARGB1555BufferStride() {
    ConvertToBuffer(2, kOddStride, false, TO, kError,
                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
  }
  void ConvertToARGB4444BufferStride() {
    ConvertToBuffer(2, kOddStride, false, TO, kError,
                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
  }
  void ConvertToI400BufferStride() {
    ConvertToBuffer(1, kOddStride, false, TO, 128,
                    cricket::FOURCC_I400, libyuv::I400ToI420);
  }
  void ConvertToYUY2BufferStride() {
    ConvertToBuffer(2, kOddStride, false, TO, kError,
                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
  }
  void ConvertToUYVYBufferStride() {
    ConvertToBuffer(2, kOddStride, false, TO, kError,
                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
  }

  // Tests ConvertToRGBBuffer formats with negative stride to invert image.
  void ConvertToARGBBufferInverted() {
    ConvertToBuffer(4, 0, true, TO, kError,
                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
  }
  void ConvertToBGRABufferInverted() {
    ConvertToBuffer(4, 0, true, TO, kError,
                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
  }
  void ConvertToABGRBufferInverted() {
    ConvertToBuffer(4, 0, true, TO, kError,
                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
  }
  void ConvertToRGB24BufferInverted() {
    ConvertToBuffer(3, 0, true, TO, kError,
                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
  }
  void ConvertToRAWBufferInverted() {
    ConvertToBuffer(3, 0, true, TO, kError,
                    cricket::FOURCC_RAW, libyuv::RAWToI420);
  }
  void ConvertToRGB565BufferInverted() {
    ConvertToBuffer(2, 0, true, TO, kError,
                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
  }
  void ConvertToARGB1555BufferInverted() {
    ConvertToBuffer(2, 0, true, TO, kError,
                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
  }
  void ConvertToARGB4444BufferInverted() {
    ConvertToBuffer(2, 0, true, TO, kError,
                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
  }
  void ConvertToI400BufferInverted() {
    ConvertToBuffer(1, 0, true, TO, 128,
                    cricket::FOURCC_I400, libyuv::I400ToI420);
  }
  void ConvertToYUY2BufferInverted() {
    ConvertToBuffer(2, 0, true, TO, kError,
                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
  }
  void ConvertToUYVYBufferInverted() {
    ConvertToBuffer(2, 0, true, TO, kError,
                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
  }

  // Tests ConvertFrom formats.
  void ConvertFromARGBBuffer() {
    ConvertToBuffer(4, 0, false, FROM, kError,
                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
  }
  void ConvertFromBGRABuffer() {
    ConvertToBuffer(4, 0, false, FROM, kError,
                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
  }
  void ConvertFromABGRBuffer() {
    ConvertToBuffer(4, 0, false, FROM, kError,
                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
  }
  void ConvertFromRGB24Buffer() {
    ConvertToBuffer(3, 0, false, FROM, kError,
                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
  }
  void ConvertFromRAWBuffer() {
    ConvertToBuffer(3, 0, false, FROM, kError,
                    cricket::FOURCC_RAW, libyuv::RAWToI420);
  }
  void ConvertFromRGB565Buffer() {
    ConvertToBuffer(2, 0, false, FROM, kError,
                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
  }
  void ConvertFromARGB1555Buffer() {
    ConvertToBuffer(2, 0, false, FROM, kError,
                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
  }
  void ConvertFromARGB4444Buffer() {
    ConvertToBuffer(2, 0, false, FROM, kError,
                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
  }
  void ConvertFromI400Buffer() {
    ConvertToBuffer(1, 0, false, FROM, 128,
                    cricket::FOURCC_I400, libyuv::I400ToI420);
  }
  void ConvertFromYUY2Buffer() {
    ConvertToBuffer(2, 0, false, FROM, kError,
                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
  }
  void ConvertFromUYVYBuffer() {
    ConvertToBuffer(2, 0, false, FROM, kError,
                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
  }

  // Tests ConvertFrom formats with odd stride.
  void ConvertFromARGBBufferStride() {
    ConvertToBuffer(4, kOddStride, false, FROM, kError,
                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
  }
  void ConvertFromBGRABufferStride() {
    ConvertToBuffer(4, kOddStride, false, FROM, kError,
                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
  }
  void ConvertFromABGRBufferStride() {
    ConvertToBuffer(4, kOddStride, false, FROM, kError,
                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
  }
  void ConvertFromRGB24BufferStride() {
    ConvertToBuffer(3, kOddStride, false, FROM, kError,
                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
  }
  void ConvertFromRAWBufferStride() {
    ConvertToBuffer(3, kOddStride, false, FROM, kError,
                    cricket::FOURCC_RAW, libyuv::RAWToI420);
  }
  void ConvertFromRGB565BufferStride() {
    ConvertToBuffer(2, kOddStride, false, FROM, kError,
                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
  }
  void ConvertFromARGB1555BufferStride() {
    ConvertToBuffer(2, kOddStride, false, FROM, kError,
                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
  }
  void ConvertFromARGB4444BufferStride() {
    ConvertToBuffer(2, kOddStride, false, FROM, kError,
                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
  }
  void ConvertFromI400BufferStride() {
    ConvertToBuffer(1, kOddStride, false, FROM, 128,
                    cricket::FOURCC_I400, libyuv::I400ToI420);
  }
  void ConvertFromYUY2BufferStride() {
    ConvertToBuffer(2, kOddStride, false, FROM, kError,
                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
  }
  void ConvertFromUYVYBufferStride() {
    ConvertToBuffer(2, kOddStride, false, FROM, kError,
                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
  }

  // Tests ConvertFrom formats with negative stride to invert image.
  void ConvertFromARGBBufferInverted() {
    ConvertToBuffer(4, 0, true, FROM, kError,
                    cricket::FOURCC_ARGB, libyuv::ARGBToI420);
  }
  void ConvertFromBGRABufferInverted() {
    ConvertToBuffer(4, 0, true, FROM, kError,
                    cricket::FOURCC_BGRA, libyuv::BGRAToI420);
  }
  void ConvertFromABGRBufferInverted() {
    ConvertToBuffer(4, 0, true, FROM, kError,
                    cricket::FOURCC_ABGR, libyuv::ABGRToI420);
  }
  void ConvertFromRGB24BufferInverted() {
    ConvertToBuffer(3, 0, true, FROM, kError,
                    cricket::FOURCC_24BG, libyuv::RGB24ToI420);
  }
  void ConvertFromRAWBufferInverted() {
    ConvertToBuffer(3, 0, true, FROM, kError,
                    cricket::FOURCC_RAW, libyuv::RAWToI420);
  }
  void ConvertFromRGB565BufferInverted() {
    ConvertToBuffer(2, 0, true, FROM, kError,
                    cricket::FOURCC_RGBP, libyuv::RGB565ToI420);
  }
  void ConvertFromARGB1555BufferInverted() {
    ConvertToBuffer(2, 0, true, FROM, kError,
                    cricket::FOURCC_RGBO, libyuv::ARGB1555ToI420);
  }
  void ConvertFromARGB4444BufferInverted() {
    ConvertToBuffer(2, 0, true, FROM, kError,
                    cricket::FOURCC_R444, libyuv::ARGB4444ToI420);
  }
  void ConvertFromI400BufferInverted() {
    ConvertToBuffer(1, 0, true, FROM, 128,
                    cricket::FOURCC_I400, libyuv::I400ToI420);
  }
  void ConvertFromYUY2BufferInverted() {
    ConvertToBuffer(2, 0, true, FROM, kError,
                    cricket::FOURCC_YUY2, libyuv::YUY2ToI420);
  }
  void ConvertFromUYVYBufferInverted() {
    ConvertToBuffer(2, 0, true, FROM, kError,
                    cricket::FOURCC_UYVY, libyuv::UYVYToI420);
  }

  // Test converting from I420 to I422.
  void ConvertToI422Buffer() {
    T frame1, frame2;
    size_t out_size = kWidth * kHeight * 2;
    rtc::scoped_ptr<uint8_t[]> buf(new uint8_t[out_size + kAlignment]);
    uint8_t* y = ALIGNP(buf.get(), kAlignment);
    uint8_t* u = y + kWidth * kHeight;
    uint8_t* v = u + (kWidth / 2) * kHeight;
    ASSERT_TRUE(LoadFrameNoRepeat(&frame1));
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_EQ(0, libyuv::I420ToI422(frame1.GetYPlane(), frame1.GetYPitch(),
                                      frame1.GetUPlane(), frame1.GetUPitch(),
                                      frame1.GetVPlane(), frame1.GetVPitch(),
                                      y, kWidth,
                                      u, kWidth / 2,
                                      v, kWidth / 2,
                                      kWidth, kHeight));
    }
    EXPECT_TRUE(frame2.Init(cricket::FOURCC_I422, kWidth, kHeight, kWidth,
                            kHeight, y, out_size, 1, 1, 0,
                            webrtc::kVideoRotation_0));
    EXPECT_TRUE(IsEqual(frame1, frame2, 1));
  }

  ///////////////////
  // General tests //
  ///////////////////

  void Copy() {
    rtc::scoped_ptr<T> source(new T);
    rtc::scoped_ptr<cricket::VideoFrame> target;
    ASSERT_TRUE(LoadFrameNoRepeat(source.get()));
    target.reset(source->Copy());
    EXPECT_TRUE(IsEqual(*source, *target, 0));
    source.reset();
    EXPECT_TRUE(target->GetYPlane() != NULL);
  }

  void CopyIsRef() {
    rtc::scoped_ptr<T> source(new T);
    rtc::scoped_ptr<const cricket::VideoFrame> target;
    ASSERT_TRUE(LoadFrameNoRepeat(source.get()));
    target.reset(source->Copy());
    EXPECT_TRUE(IsEqual(*source, *target, 0));
    const T* const_source = source.get();
    EXPECT_EQ(const_source->GetYPlane(), target->GetYPlane());
    EXPECT_EQ(const_source->GetUPlane(), target->GetUPlane());
    EXPECT_EQ(const_source->GetVPlane(), target->GetVPlane());
  }

  void MakeExclusive() {
    rtc::scoped_ptr<T> source(new T);
    rtc::scoped_ptr<cricket::VideoFrame> target;
    ASSERT_TRUE(LoadFrameNoRepeat(source.get()));
    target.reset(source->Copy());
    EXPECT_TRUE(target->MakeExclusive());
    EXPECT_TRUE(IsEqual(*source, *target, 0));
    EXPECT_NE(target->GetYPlane(), source->GetYPlane());
    EXPECT_NE(target->GetUPlane(), source->GetUPlane());
    EXPECT_NE(target->GetVPlane(), source->GetVPlane());
  }

  void CopyToBuffer() {
    T frame;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        LoadSample(kImageFilename));
    ASSERT_TRUE(ms.get() != NULL);
    ASSERT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420, kWidth, kHeight,
                          &frame));
    size_t out_size = kWidth * kHeight * 3 / 2;
    rtc::scoped_ptr<uint8_t[]> out(new uint8_t[out_size]);
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_EQ(out_size, frame.CopyToBuffer(out.get(), out_size));
    }
    EXPECT_EQ(0, memcmp(out.get(), ms->GetBuffer(), out_size));
  }

  void CopyToFrame() {
    T source;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        LoadSample(kImageFilename));
    ASSERT_TRUE(ms.get() != NULL);
    ASSERT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420, kWidth, kHeight,
                          &source));

    // Create the target frame by loading from a file.
    T target;
    ASSERT_TRUE(LoadFrameNoRepeat(&target));
    EXPECT_FALSE(IsBlack(target));

    // Stretch and check if the stretched target is black.
    source.CopyToFrame(&target);

    EXPECT_TRUE(IsEqual(source, target, 0));
  }

  void Write() {
    T frame;
    rtc::scoped_ptr<rtc::MemoryStream> ms(
        LoadSample(kImageFilename));
    ASSERT_TRUE(ms.get() != NULL);
    rtc::MemoryStream ms2;
    size_t size;
    ASSERT_TRUE(ms->GetSize(&size));
    ASSERT_TRUE(ms2.ReserveSize(size));
    ASSERT_TRUE(LoadFrame(ms.get(), cricket::FOURCC_I420, kWidth, kHeight,
                          &frame));
    for (int i = 0; i < repeat_; ++i) {
      ms2.SetPosition(0u);  // Useful when repeat_ > 1.
      int error;
      EXPECT_EQ(rtc::SR_SUCCESS, frame.Write(&ms2, &error));
    }
    size_t out_size = cricket::VideoFrame::SizeOf(kWidth, kHeight);
    EXPECT_EQ(0, memcmp(ms2.GetBuffer(), ms->GetBuffer(), out_size));
  }

  void CopyToBuffer1Pixel() {
    size_t out_size = 3;
    rtc::scoped_ptr<uint8_t[]> out(new uint8_t[out_size + 1]);
    memset(out.get(), 0xfb, out_size + 1);  // Fill buffer
    uint8_t pixel[3] = {1, 2, 3};
    T frame;
    EXPECT_TRUE(frame.Init(cricket::FOURCC_I420, 1, 1, 1, 1, pixel,
                           sizeof(pixel), 1, 1, 0,
                           webrtc::kVideoRotation_0));
    for (int i = 0; i < repeat_; ++i) {
      EXPECT_EQ(out_size, frame.CopyToBuffer(out.get(), out_size));
    }
    EXPECT_EQ(1, out.get()[0]);  // Check Y.  Should be 1.
    EXPECT_EQ(2, out.get()[1]);  // Check U.  Should be 2.
    EXPECT_EQ(3, out.get()[2]);  // Check V.  Should be 3.
    EXPECT_EQ(0xfb, out.get()[3]);  // Check sentinel is still intact.
  }

  void StretchToFrame() {
    // Create the source frame as a black frame.
    T source;
    EXPECT_TRUE(source.InitToBlack(kWidth * 2, kHeight * 2, 1, 1, 0));
    EXPECT_TRUE(IsSize(source, kWidth * 2, kHeight * 2));

    // Create the target frame by loading from a file.
    T target1;
    ASSERT_TRUE(LoadFrameNoRepeat(&target1));
    EXPECT_FALSE(IsBlack(target1));

    // Stretch and check if the stretched target is black.
    source.StretchToFrame(&target1, true, false);
    EXPECT_TRUE(IsBlack(target1));

    // Crop and stretch and check if the stretched target is black.
    T target2;
    ASSERT_TRUE(LoadFrameNoRepeat(&target2));
    source.StretchToFrame(&target2, true, true);
    EXPECT_TRUE(IsBlack(target2));
    EXPECT_EQ(source.GetTimeStamp(), target2.GetTimeStamp());
  }

  int repeat_;
};

#endif  // TALK_MEDIA_BASE_VIDEOFRAME_UNITTEST_H_
