Adds support for reading and writing 10/12-bit y4m

The y4m extension used is the same as the one used in ffmpeg/x264.
The patch is adapted from the highbitdepth branch.

Also adds unit tests for y4m header parsing and md5 check
of the raw frame data, as well as y4m writing.

Change-Id: Ie2794daf6dbafd2f128464f9b9da520fc54c0dd6
diff --git a/test/md5_helper.h b/test/md5_helper.h
index dd446f4..dc95582 100644
--- a/test/md5_helper.h
+++ b/test/md5_helper.h
@@ -28,10 +28,11 @@
       // plane, we never want to round down and thus skip a pixel so if
       // we are shifting by 1 (chroma_shift) we add 1 before doing the shift.
       // This works only for chroma_shift of 0 and 1.
+      const int bytes_per_sample = (img->fmt & VPX_IMG_FMT_HIGH) ? 2 : 1;
       const int h = plane ? (img->d_h + img->y_chroma_shift) >>
                     img->y_chroma_shift : img->d_h;
-      const int w = plane ? (img->d_w + img->x_chroma_shift) >>
-                    img->x_chroma_shift : img->d_w;
+      const int w = (plane ? (img->d_w + img->x_chroma_shift) >>
+                     img->x_chroma_shift : img->d_w) * bytes_per_sample;
 
       for (int y = 0; y < h; ++y) {
         MD5Update(&md5_, buf, w);
diff --git a/test/test-data.sha1 b/test/test-data.sha1
index af1815c..f9c09c6 100644
--- a/test/test-data.sha1
+++ b/test/test-data.sha1
@@ -6,6 +6,15 @@
 2dadee5306245fa5eeb0f99652d0e17afbcba96d  invalid-vp90-02.webm.res
 df1a1453feb3c00d7d89746c7003b4163523bff3  invalid-vp90-03.webm
 8fe6fd82bf537340f586f97a7ae31fb37ccda302  invalid-vp90-03.webm.res
+a432f96ff0a787268e2f94a8092ab161a18d1b06  park_joy_90p_10_420.y4m
+0b194cc312c3a2e84d156a221b0a5eb615dfddc5  park_joy_90p_10_422.y4m
+ff0e0a21dc2adc95b8c1b37902713700655ced17  park_joy_90p_10_444.y4m
+614c32ae1eca391e867c70d19974f0d62664dd99  park_joy_90p_12_420.y4m
+c92825f1ea25c5c37855083a69faac6ac4641a9e  park_joy_90p_12_422.y4m
+b592189b885b6cc85db55cc98512a197d73d3b34  park_joy_90p_12_444.y4m
+4e0eb61e76f0684188d9bc9f3ce61f6b6b77bb2c  park_joy_90p_8_420.y4m
+7a193ff7dfeb96ba5f82b2afd7afa9e1fe83d947  park_joy_90p_8_422.y4m
+bdb7856e6bc93599bdda05c2e773a9f22b6c6d03  park_joy_90p_8_444.y4m
 b1f1c3ec79114b9a0651af24ce634afb44a9a419  rush_hour_444.y4m
 5184c46ddca8b1fadd16742e8500115bc8f749da  vp80-00-comprehensive-001.ivf
 65bf1bbbced81b97bd030f376d1b7f61a224793f  vp80-00-comprehensive-002.ivf
diff --git a/test/test.mk b/test/test.mk
index f06e28e..85212d9 100644
--- a/test/test.mk
+++ b/test/test.mk
@@ -15,7 +15,7 @@
 ##
 ## Black box tests only use the public API.
 ##
-LIBVPX_TEST_SRCS-$(CONFIG_DECODERS)    += ../md5_utils.h ../md5_utils.c
+LIBVPX_TEST_SRCS-yes                   += ../md5_utils.h ../md5_utils.c
 LIBVPX_TEST_SRCS-$(CONFIG_DECODERS)    += ivf_video_source.h
 LIBVPX_TEST_SRCS-$(CONFIG_ENCODERS)    += ../y4minput.h ../y4minput.c
 LIBVPX_TEST_SRCS-$(CONFIG_ENCODERS)    += aq_segment_test.cc
@@ -42,6 +42,9 @@
 LIBVPX_TEST_SRCS-yes                   += encode_test_driver.cc
 LIBVPX_TEST_SRCS-yes                   += encode_test_driver.h
 
+## Y4m parsing.
+LIBVPX_TEST_SRCS-$(CONFIG_ENCODERS)    += y4m_test.cc ../y4menc.c ../y4menc.h
+
 ## WebM Parsing
 ifeq ($(CONFIG_WEBM_IO), yes)
 LIBWEBM_PARSER_SRCS                    += ../third_party/libwebm/mkvparser.cpp
@@ -134,6 +137,17 @@
 ##
 LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += hantro_collage_w352h288.yuv
 LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += hantro_odd.yuv
+
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += park_joy_90p_10_420.y4m
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += park_joy_90p_10_422.y4m
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += park_joy_90p_10_444.y4m
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += park_joy_90p_12_420.y4m
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += park_joy_90p_12_422.y4m
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += park_joy_90p_12_444.y4m
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += park_joy_90p_8_420.y4m
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += park_joy_90p_8_422.y4m
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += park_joy_90p_8_444.y4m
+
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += rush_hour_444.y4m
 LIBVPX_TEST_DATA-$(CONFIG_VP9_ENCODER) += screendata.y4m
 
diff --git a/test/video_source.h b/test/video_source.h
index 6d1855a..4250cb7 100644
--- a/test/video_source.h
+++ b/test/video_source.h
@@ -50,6 +50,15 @@
   return fopen(path_to_source.c_str(), "rb");
 }
 
+static FILE *OpenTestOutFile(const std::string& file_name) {
+  const std::string path_to_source = GetDataPath() + "/" + file_name;
+  return fopen(path_to_source.c_str(), "wb");
+}
+
+static FILE *OpenTempOutFile() {
+  return tmpfile();
+}
+
 // Abstract base class for test video sources, which provide a stream of
 // vpx_image_t images with associated timestamps and duration.
 class VideoSource {
diff --git a/test/y4m_test.cc b/test/y4m_test.cc
new file mode 100644
index 0000000..cfa30e8
--- /dev/null
+++ b/test/y4m_test.cc
@@ -0,0 +1,193 @@
+/*
+ *  Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <string>
+#include "test/md5_helper.h"
+#include "test/util.h"
+#include "test/y4m_video_source.h"
+#include "third_party/googletest/src/include/gtest/gtest.h"
+#include "./vpx_config.h"
+#include "./y4menc.h"
+
+namespace {
+
+using std::string;
+using std::tr1::make_tuple;
+
+static const unsigned int kWidth  = 160;
+static const unsigned int kHeight = 90;
+static const unsigned int kFrames = 10;
+
+typedef std::tr1::tuple<const char *, const unsigned int,
+        const vpx_img_fmt, const char *> test_entry_type;
+
+static const test_entry_type kY4mTestVectors[] = {
+  make_tuple("park_joy_90p_8_420.y4m", 8, VPX_IMG_FMT_I420,
+             "e5406275b9fc6bb3436c31d4a05c1cab"),
+  make_tuple("park_joy_90p_8_422.y4m", 8, VPX_IMG_FMT_I422,
+             "284a47a47133b12884ec3a14e959a0b6"),
+  make_tuple("park_joy_90p_8_444.y4m", 8, VPX_IMG_FMT_I444,
+             "90517ff33843d85de712fd4fe60dbed0"),
+  make_tuple("park_joy_90p_10_420.y4m", 10, VPX_IMG_FMT_I42016,
+             "63f21f9f717d8b8631bd2288ee87137b"),
+  make_tuple("park_joy_90p_10_422.y4m", 10, VPX_IMG_FMT_I42216,
+             "48ab51fb540aed07f7ff5af130c9b605"),
+  make_tuple("park_joy_90p_10_444.y4m", 10, VPX_IMG_FMT_I44416,
+             "067bfd75aa85ff9bae91fa3e0edd1e3e"),
+  make_tuple("park_joy_90p_12_420.y4m", 12, VPX_IMG_FMT_I42016,
+             "9e6d8f6508c6e55625f6b697bc461cef"),
+  make_tuple("park_joy_90p_12_422.y4m", 12, VPX_IMG_FMT_I42216,
+             "b239c6b301c0b835485be349ca83a7e3"),
+  make_tuple("park_joy_90p_12_444.y4m", 12, VPX_IMG_FMT_I44416,
+             "5a6481a550821dab6d0192f5c63845e9")
+};
+
+static void write_image_file(const vpx_image_t *img, FILE *file) {
+  int plane, y;
+  for (plane = 0; plane < 3; ++plane) {
+    const unsigned char *buf = img->planes[plane];
+    const int stride = img->stride[plane];
+    const int bytes_per_sample = (img->fmt & VPX_IMG_FMT_HIGH) ? 2 : 1;
+    const int h = (plane ? (img->d_h + img->y_chroma_shift) >>
+                   img->y_chroma_shift : img->d_h);
+    const int w = (plane ? (img->d_w + img->x_chroma_shift) >>
+                   img->x_chroma_shift : img->d_w);
+    for (y = 0; y < h; ++y) {
+      fwrite(buf, bytes_per_sample, w, file);
+      buf += stride;
+    }
+  }
+}
+
+class Y4mVideoSourceTest
+    : public ::testing::TestWithParam<test_entry_type>,
+      public ::libvpx_test::Y4mVideoSource {
+ protected:
+  Y4mVideoSourceTest() : Y4mVideoSource("", 0, 0) {}
+
+  virtual ~Y4mVideoSourceTest() {
+    CloseSource();
+  }
+
+  virtual void Init(const std::string &file_name, int limit) {
+    file_name_ = file_name;
+    start_ = 0;
+    limit_ = limit;
+    frame_ = 0;
+    Begin();
+  }
+
+  // Checks y4m header information
+  void HeaderChecks(unsigned int bit_depth, vpx_img_fmt_t fmt) {
+    ASSERT_TRUE(input_file_ != NULL);
+    ASSERT_EQ(y4m_.pic_w, (int)kWidth);
+    ASSERT_EQ(y4m_.pic_h, (int)kHeight);
+    ASSERT_EQ(img()->d_w, kWidth);
+    ASSERT_EQ(img()->d_h, kHeight);
+    ASSERT_EQ(y4m_.bit_depth, bit_depth);
+    ASSERT_EQ(y4m_.vpx_fmt, fmt);
+    if (fmt == VPX_IMG_FMT_I420 || fmt == VPX_IMG_FMT_I42016) {
+      ASSERT_EQ(y4m_.bps, (int)y4m_.bit_depth * 3 / 2);
+      ASSERT_EQ(img()->x_chroma_shift, 1U);
+      ASSERT_EQ(img()->y_chroma_shift, 1U);
+    }
+    if (fmt == VPX_IMG_FMT_I422 || fmt == VPX_IMG_FMT_I42216) {
+      ASSERT_EQ(y4m_.bps, (int)y4m_.bit_depth * 2);
+      ASSERT_EQ(img()->x_chroma_shift, 1U);
+      ASSERT_EQ(img()->y_chroma_shift, 0U);
+    }
+    if (fmt == VPX_IMG_FMT_I444 || fmt == VPX_IMG_FMT_I44416) {
+      ASSERT_EQ(y4m_.bps, (int)y4m_.bit_depth * 3);
+      ASSERT_EQ(img()->x_chroma_shift, 0U);
+      ASSERT_EQ(img()->y_chroma_shift, 0U);
+    }
+  }
+
+  // Checks MD5 of the raw frame data
+  void Md5Check(const string &expected_md5) {
+    ASSERT_TRUE(input_file_ != NULL);
+    libvpx_test::MD5 md5;
+    for (unsigned int i = start_; i < limit_; i++) {
+      md5.Add(img());
+      Next();
+    }
+    ASSERT_EQ(string(md5.Get()), expected_md5);
+  }
+};
+
+TEST_P(Y4mVideoSourceTest, SourceTest) {
+  const char *filename = GET_PARAM(0);
+  const unsigned int bit_depth = GET_PARAM(1);
+  const vpx_img_fmt format = GET_PARAM(2);
+  const char *md5raw = GET_PARAM(3);
+
+  Init(filename, kFrames);
+  HeaderChecks(bit_depth, format);
+  Md5Check(md5raw);
+}
+
+INSTANTIATE_TEST_CASE_P(C, Y4mVideoSourceTest,
+                        ::testing::ValuesIn(kY4mTestVectors));
+
+class Y4mVideoWriteTest
+    : public Y4mVideoSourceTest {
+ protected:
+  Y4mVideoWriteTest() : Y4mVideoSourceTest() {}
+
+  virtual void ReplaceInputFp(FILE *input_file) {
+    CloseSource();
+    frame_ = 0;
+    input_file_ = input_file;
+    rewind(input_file_);
+    ReadSourceToStart();
+  }
+
+  // Writes out a y4m file and then reads it back
+  void WriteY4mAndReadBack() {
+    ASSERT_TRUE(input_file_ != NULL);
+    char buf[Y4M_BUFFER_SIZE] = {0};
+    const struct VpxRational framerate = {y4m_.fps_n, y4m_.fps_d};
+    FILE *out_file = libvpx_test::OpenTempOutFile();
+    ASSERT_TRUE(out_file != NULL);
+    y4m_write_file_header(buf, sizeof(buf),
+                          kWidth, kHeight,
+                          &framerate, y4m_.vpx_fmt,
+                          y4m_.bit_depth);
+    fputs(buf, out_file);
+    for (unsigned int i = start_; i < limit_; i++) {
+      y4m_write_frame_header(buf, sizeof(buf));
+      fputs(buf, out_file);
+      write_image_file(img(), out_file);
+      Next();
+    }
+    ReplaceInputFp(out_file);
+  }
+
+  virtual void Init(const std::string &file_name, int limit) {
+    Y4mVideoSourceTest::Init(file_name, limit);
+    WriteY4mAndReadBack();
+  }
+};
+
+TEST_P(Y4mVideoWriteTest, WriteTest) {
+  const char *filename = GET_PARAM(0);
+  const unsigned int bit_depth = GET_PARAM(1);
+  const vpx_img_fmt format = GET_PARAM(2);
+  const char *md5raw = GET_PARAM(3);
+
+  Init(filename, kFrames);
+  HeaderChecks(bit_depth, format);
+  Md5Check(md5raw);
+}
+
+INSTANTIATE_TEST_CASE_P(C, Y4mVideoWriteTest,
+                        ::testing::ValuesIn(kY4mTestVectors));
+
+}  // namespace
diff --git a/test/y4m_video_source.h b/test/y4m_video_source.h
index 7419043..378e75b 100644
--- a/test/y4m_video_source.h
+++ b/test/y4m_video_source.h
@@ -38,24 +38,30 @@
     CloseSource();
   }
 
-  virtual void Begin() {
+  virtual void OpenSource() {
     CloseSource();
     input_file_ = OpenTestDataFile(file_name_);
     ASSERT_TRUE(input_file_ != NULL) << "Input file open failed. Filename: "
-        << file_name_;
+                                     << file_name_;
+  }
 
-    y4m_input_open(&y4m_, input_file_, NULL, 0, 0);
+  virtual void ReadSourceToStart() {
+    ASSERT_TRUE(input_file_ != NULL);
+    ASSERT_FALSE(y4m_input_open(&y4m_, input_file_, NULL, 0, 0));
     framerate_numerator_ = y4m_.fps_n;
     framerate_denominator_ = y4m_.fps_d;
-
     frame_ = 0;
     for (unsigned int i = 0; i < start_; i++) {
-        Next();
+      Next();
     }
-
     FillFrame();
   }
 
+  virtual void Begin() {
+    OpenSource();
+    ReadSourceToStart();
+  }
+
   virtual void Next() {
     ++frame_;
     FillFrame();