shithub: libvpx

Download patch

ref: 64485398d86a4b81ce19c35f791cbf0478bde0ee
parent: 3bc58f13cc4ae0881ce483a8dcd7789a2d6f325d
author: Jerome Jiang <jianj@google.com>
date: Wed May 27 09:57:50 EDT 2020

Add NV12 support

Change-Id: Ia2a8221a156e0882079c5a252f59bc84d8f516b1

--- a/test/test-data.mk
+++ b/test/test-data.mk
@@ -2,6 +2,7 @@
 
 # Encoder test source
 LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += hantro_collage_w352h288.yuv
+LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += hantro_collage_w352h288_nv12.yuv
 LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += hantro_odd.yuv
 LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += desktop_office1.1280_720-020.yuv
 LIBVPX_TEST_DATA-$(CONFIG_ENCODERS) += slides_code_term_web_plot.1920_1080.yuv
--- a/test/test-data.sha1
+++ b/test/test-data.sha1
@@ -868,3 +868,4 @@
 094be4b80fa30bd227149ea16ab6476d549ea092 *slides_code_term_web_plot.1920_1080.yuv
 518a0be998afece76d3df76047d51e256c591ff2 *invalid-bug-148271109.ivf
 d3964f9dad9f60363c81b688324d95b4ec7c8038 *invalid-bug-148271109.ivf.res
+ad18ca16f0a249fb3b7c38de0d9b327fed273f96 *hantro_collage_w352h288_nv12.yuv
--- a/test/vp8_datarate_test.cc
+++ b/test/vp8_datarate_test.cc
@@ -408,6 +408,28 @@
       << " The datarate for the file missed the target!";
 }
 
+TEST_P(DatarateTestRealTime, NV12) {
+  denoiser_on_ = 0;
+  cfg_.rc_buf_initial_sz = 500;
+  cfg_.rc_dropframe_thresh = 0;
+  cfg_.rc_max_quantizer = 56;
+  cfg_.rc_end_usage = VPX_CBR;
+  cfg_.g_error_resilient = 0;
+  ::libvpx_test::YUVVideoSource video("hantro_collage_w352h288_nv12.yuv",
+                                      VPX_IMG_FMT_NV12, 352, 288, 30, 1, 0,
+                                      100);
+
+  cfg_.rc_target_bitrate = 200;
+  ResetModel();
+
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+  ASSERT_GE(cfg_.rc_target_bitrate, effective_datarate_ * 0.95)
+      << " The datarate for the file exceeds the target!";
+
+  ASSERT_LE(cfg_.rc_target_bitrate, file_datarate_ * 1.4)
+      << " The datarate for the file missed the target!";
+}
+
 VP8_INSTANTIATE_TEST_CASE(DatarateTestLarge, ALL_TEST_MODES,
                           ::testing::Values(0));
 VP8_INSTANTIATE_TEST_CASE(DatarateTestRealTime,
--- a/test/vp9_end_to_end_test.cc
+++ b/test/vp9_end_to_end_test.cc
@@ -59,6 +59,10 @@
 #endif  // CONFIG_VP9_HIGHBITDEPTH
 };
 
+const TestVideoParam kTestVectorsNv12[] = {
+  { "hantro_collage_w352h288_nv12.yuv", 8, VPX_IMG_FMT_NV12, VPX_BITS_8, 0 },
+};
+
 // Encoding modes tested
 const libvpx_test::TestMode kEncodingModeVectors[] = {
   ::libvpx_test::kTwoPassGood, ::libvpx_test::kOnePassGood,
@@ -237,6 +241,27 @@
 };
 #endif  // CONFIG_VP9_DECODER
 
+class EndToEndNV12 : public EndToEndTestLarge {};
+
+TEST_P(EndToEndNV12, EndtoEndNV12Test) {
+  cfg_.rc_target_bitrate = kBitrate;
+  cfg_.g_error_resilient = 0;
+  cfg_.g_profile = test_video_param_.profile;
+  cfg_.g_input_bit_depth = test_video_param_.input_bit_depth;
+  cfg_.g_bit_depth = test_video_param_.bit_depth;
+  init_flags_ = VPX_CODEC_USE_PSNR;
+  if (cfg_.g_bit_depth > 8) init_flags_ |= VPX_CODEC_USE_HIGHBITDEPTH;
+
+  std::unique_ptr<libvpx_test::VideoSource> video;
+
+  video.reset(new libvpx_test::YUVVideoSource(test_video_param_.filename,
+                                              test_video_param_.fmt, 352, 288,
+                                              30, 1, 0, 100));
+  ASSERT_TRUE(video.get() != NULL);
+
+  ASSERT_NO_FATAL_FAILURE(RunLoop(video.get()));
+}
+
 TEST_P(EndToEndTestLarge, EndtoEndPSNRTest) {
   cfg_.rc_target_bitrate = kBitrate;
   cfg_.g_error_resilient = 0;
@@ -313,6 +338,11 @@
                           ::testing::ValuesIn(kEncodingModeVectors),
                           ::testing::ValuesIn(kTestVectors),
                           ::testing::ValuesIn(kCpuUsedVectors));
+
+VP9_INSTANTIATE_TEST_CASE(EndToEndNV12,
+                          ::testing::Values(::libvpx_test::kRealTime),
+                          ::testing::ValuesIn(kTestVectorsNv12),
+                          ::testing::ValuesIn({ 6, 7, 8 }));
 
 VP9_INSTANTIATE_TEST_CASE(EndToEndTestAdaptiveRDThresh,
                           ::testing::Values(5, 6, 7), ::testing::Values(8, 9));
--- a/test/yuv_video_source.h
+++ b/test/yuv_video_source.h
@@ -84,6 +84,7 @@
       height_ = height;
       format_ = format;
       switch (format) {
+        case VPX_IMG_FMT_NV12:
         case VPX_IMG_FMT_I420: raw_size_ = width * height * 3 / 2; break;
         case VPX_IMG_FMT_I422: raw_size_ = width * height * 2; break;
         case VPX_IMG_FMT_I440: raw_size_ = width * height * 2; break;
--- a/tools_common.c
+++ b/tools_common.c
@@ -91,10 +91,13 @@
 
   for (plane = 0; plane < 3; ++plane) {
     uint8_t *ptr;
-    const int w = vpx_img_plane_width(yuv_frame, plane);
+    int w = vpx_img_plane_width(yuv_frame, plane);
     const int h = vpx_img_plane_height(yuv_frame, plane);
     int r;
-
+    // Assuming that for nv12 we read all chroma data at one time
+    if (yuv_frame->fmt == VPX_IMG_FMT_NV12 && plane > 1) break;
+    // Fixing NV12 chroma width it is odd
+    if (yuv_frame->fmt == VPX_IMG_FMT_NV12 && plane == 1) w = (w + 1) & ~1;
     /* Determine the correct plane based on the image format. The for-loop
      * always counts in Y,U,V order, but this may not match the order of
      * the data on disk.
--- a/vp8/common/extend.c
+++ b/vp8/common/extend.c
@@ -11,30 +11,40 @@
 #include "extend.h"
 #include "vpx_mem/vpx_mem.h"
 
-static void copy_and_extend_plane(unsigned char *s, /* source */
-                                  int sp,           /* source pitch */
-                                  unsigned char *d, /* destination */
-                                  int dp,           /* destination pitch */
-                                  int h,            /* height */
-                                  int w,            /* width */
-                                  int et,           /* extend top border */
-                                  int el,           /* extend left border */
-                                  int eb,           /* extend bottom border */
-                                  int er) {         /* extend right border */
-  int i;
+static void copy_and_extend_plane(
+    unsigned char *s,      /* source */
+    int sp,                /* source pitch */
+    unsigned char *d,      /* destination */
+    int dp,                /* destination pitch */
+    int h,                 /* height */
+    int w,                 /* width */
+    int et,                /* extend top border */
+    int el,                /* extend left border */
+    int eb,                /* extend bottom border */
+    int er,                /* extend right border */
+    int interleave_step) { /* step between pixels of the current plane */
+  int i, j;
   unsigned char *src_ptr1, *src_ptr2;
   unsigned char *dest_ptr1, *dest_ptr2;
   int linesize;
 
+  if (interleave_step < 1) interleave_step = 1;
+
   /* copy the left and right most columns out */
   src_ptr1 = s;
-  src_ptr2 = s + w - 1;
+  src_ptr2 = s + (w - 1) * interleave_step;
   dest_ptr1 = d - el;
   dest_ptr2 = d + w;
 
   for (i = 0; i < h; ++i) {
     memset(dest_ptr1, src_ptr1[0], el);
-    memcpy(dest_ptr1 + el, src_ptr1, w);
+    if (interleave_step == 1) {
+      memcpy(dest_ptr1 + el, src_ptr1, w);
+    } else {
+      for (j = 0; j < w; j++) {
+        dest_ptr1[el + j] = src_ptr1[interleave_step * j];
+      }
+    }
     memset(dest_ptr2, src_ptr2[0], er);
     src_ptr1 += sp;
     src_ptr2 += sp;
@@ -69,9 +79,12 @@
   int eb = dst->border + dst->y_height - src->y_height;
   int er = dst->border + dst->y_width - src->y_width;
 
+  // detect nv12 colorspace
+  int chroma_step = src->v_buffer - src->u_buffer == 1 ? 2 : 1;
+
   copy_and_extend_plane(src->y_buffer, src->y_stride, dst->y_buffer,
                         dst->y_stride, src->y_height, src->y_width, et, el, eb,
-                        er);
+                        er, 1);
 
   et = dst->border >> 1;
   el = dst->border >> 1;
@@ -80,11 +93,11 @@
 
   copy_and_extend_plane(src->u_buffer, src->uv_stride, dst->u_buffer,
                         dst->uv_stride, src->uv_height, src->uv_width, et, el,
-                        eb, er);
+                        eb, er, chroma_step);
 
   copy_and_extend_plane(src->v_buffer, src->uv_stride, dst->v_buffer,
                         dst->uv_stride, src->uv_height, src->uv_width, et, el,
-                        eb, er);
+                        eb, er, chroma_step);
 }
 
 void vp8_copy_and_extend_frame_with_rect(YV12_BUFFER_CONFIG *src,
@@ -98,6 +111,8 @@
   int dst_y_offset = srcy * dst->y_stride + srcx;
   int src_uv_offset = ((srcy * src->uv_stride) >> 1) + (srcx >> 1);
   int dst_uv_offset = ((srcy * dst->uv_stride) >> 1) + (srcx >> 1);
+  // detect nv12 colorspace
+  int chroma_step = src->v_buffer - src->u_buffer == 1 ? 2 : 1;
 
   /* If the side is not touching the bounder then don't extend. */
   if (srcy) et = 0;
@@ -107,7 +122,7 @@
 
   copy_and_extend_plane(src->y_buffer + src_y_offset, src->y_stride,
                         dst->y_buffer + dst_y_offset, dst->y_stride, srch, srcw,
-                        et, el, eb, er);
+                        et, el, eb, er, 1);
 
   et = (et + 1) >> 1;
   el = (el + 1) >> 1;
@@ -118,11 +133,11 @@
 
   copy_and_extend_plane(src->u_buffer + src_uv_offset, src->uv_stride,
                         dst->u_buffer + dst_uv_offset, dst->uv_stride, srch,
-                        srcw, et, el, eb, er);
+                        srcw, et, el, eb, er, chroma_step);
 
   copy_and_extend_plane(src->v_buffer + src_uv_offset, src->uv_stride,
                         dst->v_buffer + dst_uv_offset, dst->uv_stride, srch,
-                        srcw, et, el, eb, er);
+                        srcw, et, el, eb, er, chroma_step);
 }
 
 /* note the extension is only for the last row, for intra prediction purpose */
--- a/vp8/vp8_cx_iface.c
+++ b/vp8/vp8_cx_iface.c
@@ -264,9 +264,12 @@
                                     const vpx_image_t *img) {
   switch (img->fmt) {
     case VPX_IMG_FMT_YV12:
-    case VPX_IMG_FMT_I420: break;
+    case VPX_IMG_FMT_I420:
+    case VPX_IMG_FMT_NV12: break;
     default:
-      ERROR("Invalid image format. Only YV12 and I420 images are supported");
+      ERROR(
+          "Invalid image format. Only YV12, I420 and NV12 images are "
+          "supported");
   }
 
   if ((img->d_w != ctx->cfg.g_w) || (img->d_h != ctx->cfg.g_h))
--- a/vp9/encoder/vp9_extend.c
+++ b/vp9/encoder/vp9_extend.c
@@ -18,18 +18,26 @@
 static void copy_and_extend_plane(const uint8_t *src, int src_pitch,
                                   uint8_t *dst, int dst_pitch, int w, int h,
                                   int extend_top, int extend_left,
-                                  int extend_bottom, int extend_right) {
-  int i, linesize;
+                                  int extend_bottom, int extend_right,
+                                  int interleave_step) {
+  int i, j, linesize;
+  const int step = interleave_step < 1 ? 1 : interleave_step;
 
   // copy the left and right most columns out
   const uint8_t *src_ptr1 = src;
-  const uint8_t *src_ptr2 = src + w - 1;
+  const uint8_t *src_ptr2 = src + (w - 1) * step;
   uint8_t *dst_ptr1 = dst - extend_left;
   uint8_t *dst_ptr2 = dst + w;
 
   for (i = 0; i < h; i++) {
     memset(dst_ptr1, src_ptr1[0], extend_left);
-    memcpy(dst_ptr1 + extend_left, src_ptr1, w);
+    if (step == 1) {
+      memcpy(dst_ptr1 + extend_left, src_ptr1, w);
+    } else {
+      for (j = 0; j < w; j++) {
+        dst_ptr1[extend_left + j] = src_ptr1[step * j];
+      }
+    }
     memset(dst_ptr2, src_ptr2[0], extend_right);
     src_ptr1 += src_pitch;
     src_ptr2 += src_pitch;
@@ -122,6 +130,8 @@
   const int el_uv = el_y >> uv_width_subsampling;
   const int eb_uv = eb_y >> uv_height_subsampling;
   const int er_uv = er_y >> uv_width_subsampling;
+  // detect nv12 colorspace
+  const int chroma_step = src->v_buffer - src->u_buffer == 1 ? 2 : 1;
 
 #if CONFIG_VP9_HIGHBITDEPTH
   if (src->flags & YV12_FLAG_HIGHBITDEPTH) {
@@ -142,15 +152,15 @@
 
   copy_and_extend_plane(src->y_buffer, src->y_stride, dst->y_buffer,
                         dst->y_stride, src->y_crop_width, src->y_crop_height,
-                        et_y, el_y, eb_y, er_y);
+                        et_y, el_y, eb_y, er_y, 1);
 
   copy_and_extend_plane(src->u_buffer, src->uv_stride, dst->u_buffer,
                         dst->uv_stride, src->uv_crop_width, src->uv_crop_height,
-                        et_uv, el_uv, eb_uv, er_uv);
+                        et_uv, el_uv, eb_uv, er_uv, chroma_step);
 
   copy_and_extend_plane(src->v_buffer, src->uv_stride, dst->v_buffer,
                         dst->uv_stride, src->uv_crop_width, src->uv_crop_height,
-                        et_uv, el_uv, eb_uv, er_uv);
+                        et_uv, el_uv, eb_uv, er_uv, chroma_step);
 }
 
 void vp9_copy_and_extend_frame_with_rect(const YV12_BUFFER_CONFIG *src,
@@ -176,16 +186,18 @@
   const int dst_uv_offset = ((srcy * dst->uv_stride) >> 1) + (srcx >> 1);
   const int srch_uv = ROUND_POWER_OF_TWO(srch, 1);
   const int srcw_uv = ROUND_POWER_OF_TWO(srcw, 1);
+  // detect nv12 colorspace
+  const int chroma_step = src->v_buffer - src->u_buffer == 1 ? 2 : 1;
 
   copy_and_extend_plane(src->y_buffer + src_y_offset, src->y_stride,
                         dst->y_buffer + dst_y_offset, dst->y_stride, srcw, srch,
-                        et_y, el_y, eb_y, er_y);
+                        et_y, el_y, eb_y, er_y, 1);
 
   copy_and_extend_plane(src->u_buffer + src_uv_offset, src->uv_stride,
                         dst->u_buffer + dst_uv_offset, dst->uv_stride, srcw_uv,
-                        srch_uv, et_uv, el_uv, eb_uv, er_uv);
+                        srch_uv, et_uv, el_uv, eb_uv, er_uv, chroma_step);
 
   copy_and_extend_plane(src->v_buffer + src_uv_offset, src->uv_stride,
                         dst->v_buffer + dst_uv_offset, dst->uv_stride, srcw_uv,
-                        srch_uv, et_uv, el_uv, eb_uv, er_uv);
+                        srch_uv, et_uv, el_uv, eb_uv, er_uv, chroma_step);
 }
--- a/vp9/vp9_cx_iface.c
+++ b/vp9/vp9_cx_iface.c
@@ -355,13 +355,14 @@
   switch (img->fmt) {
     case VPX_IMG_FMT_YV12:
     case VPX_IMG_FMT_I420:
-    case VPX_IMG_FMT_I42016: break;
+    case VPX_IMG_FMT_I42016:
+    case VPX_IMG_FMT_NV12: break;
     case VPX_IMG_FMT_I422:
     case VPX_IMG_FMT_I444:
     case VPX_IMG_FMT_I440:
       if (ctx->cfg.g_profile != (unsigned int)PROFILE_1) {
         ERROR(
-            "Invalid image format. I422, I444, I440 images are "
+            "Invalid image format. I422, I444, I440, NV12 images are "
             "not supported in profile.");
       }
       break;
@@ -391,6 +392,7 @@
 static int get_image_bps(const vpx_image_t *img) {
   switch (img->fmt) {
     case VPX_IMG_FMT_YV12:
+    case VPX_IMG_FMT_NV12:
     case VPX_IMG_FMT_I420: return 12;
     case VPX_IMG_FMT_I422: return 16;
     case VPX_IMG_FMT_I444: return 24;
--- a/vp9/vp9_iface_common.c
+++ b/vp9/vp9_iface_common.c
@@ -88,8 +88,9 @@
   yv12->y_width = img->d_w;
   yv12->y_height = img->d_h;
 
-  yv12->uv_width =
-      img->x_chroma_shift == 1 ? (1 + yv12->y_width) / 2 : yv12->y_width;
+  yv12->uv_width = img->x_chroma_shift == 1 || img->fmt == VPX_IMG_FMT_NV12
+                       ? (1 + yv12->y_width) / 2
+                       : yv12->y_width;
   yv12->uv_height =
       img->y_chroma_shift == 1 ? (1 + yv12->y_height) / 2 : yv12->y_height;
   yv12->uv_crop_width = yv12->uv_width;
@@ -127,5 +128,9 @@
 #endif  // CONFIG_VP9_HIGHBITDEPTH
   yv12->subsampling_x = img->x_chroma_shift;
   yv12->subsampling_y = img->y_chroma_shift;
+  // When reading the data, UV are in one plane for NV12 format, thus
+  // x_chroma_shift is 0. After converting, UV are in separate planes, and
+  // subsampling_x should be set to 1.
+  if (img->fmt == VPX_IMG_FMT_NV12) yv12->subsampling_x = 1;
   return VPX_CODEC_OK;
 }
--- a/vpx/src/vpx_image.c
+++ b/vpx/src/vpx_image.c
@@ -39,7 +39,8 @@
   /* Get sample size for this format */
   switch (fmt) {
     case VPX_IMG_FMT_I420:
-    case VPX_IMG_FMT_YV12: bps = 12; break;
+    case VPX_IMG_FMT_YV12:
+    case VPX_IMG_FMT_NV12: bps = 12; break;
     case VPX_IMG_FMT_I422:
     case VPX_IMG_FMT_I440: bps = 16; break;
     case VPX_IMG_FMT_I444: bps = 24; break;
@@ -51,6 +52,8 @@
   }
 
   /* Get chroma shift values for this format */
+  // For VPX_IMG_FMT_NV12, xcs needs to be 0 such that UV data is all read at
+  // one time.
   switch (fmt) {
     case VPX_IMG_FMT_I420:
     case VPX_IMG_FMT_YV12:
@@ -62,6 +65,7 @@
 
   switch (fmt) {
     case VPX_IMG_FMT_I420:
+    case VPX_IMG_FMT_NV12:
     case VPX_IMG_FMT_I440:
     case VPX_IMG_FMT_YV12:
     case VPX_IMG_FMT_I42016:
@@ -173,7 +177,12 @@
           data + x * bytes_per_sample + y * img->stride[VPX_PLANE_Y];
       data += img->h * img->stride[VPX_PLANE_Y];
 
-      if (!(img->fmt & VPX_IMG_FMT_UV_FLIP)) {
+      if (img->fmt == VPX_IMG_FMT_NV12) {
+        img->planes[VPX_PLANE_U] =
+            data + (x >> img->x_chroma_shift) +
+            (y >> img->y_chroma_shift) * img->stride[VPX_PLANE_U];
+        img->planes[VPX_PLANE_V] = img->planes[VPX_PLANE_U] + 1;
+      } else if (!(img->fmt & VPX_IMG_FMT_UV_FLIP)) {
         img->planes[VPX_PLANE_U] =
             data + (x >> img->x_chroma_shift) * bytes_per_sample +
             (y >> img->y_chroma_shift) * img->stride[VPX_PLANE_U];
--- a/vpx/vpx_image.h
+++ b/vpx/vpx_image.h
@@ -43,6 +43,7 @@
   VPX_IMG_FMT_I422 = VPX_IMG_FMT_PLANAR | 5,
   VPX_IMG_FMT_I444 = VPX_IMG_FMT_PLANAR | 6,
   VPX_IMG_FMT_I440 = VPX_IMG_FMT_PLANAR | 7,
+  VPX_IMG_FMT_NV12 = VPX_IMG_FMT_PLANAR | 9,
   VPX_IMG_FMT_I42016 = VPX_IMG_FMT_I420 | VPX_IMG_FMT_HIGHBITDEPTH,
   VPX_IMG_FMT_I42216 = VPX_IMG_FMT_I422 | VPX_IMG_FMT_HIGHBITDEPTH,
   VPX_IMG_FMT_I44416 = VPX_IMG_FMT_I444 | VPX_IMG_FMT_HIGHBITDEPTH,
--- a/vpxenc.c
+++ b/vpxenc.c
@@ -95,6 +95,8 @@
     ARG_DEF("D", "debug", 0, "Debug mode (makes output deterministic)");
 static const arg_def_t outputfile =
     ARG_DEF("o", "output", 1, "Output filename");
+static const arg_def_t use_nv12 =
+    ARG_DEF(NULL, "nv12", 0, "Input file is NV12 ");
 static const arg_def_t use_yv12 =
     ARG_DEF(NULL, "yv12", 0, "Input file is YV12 ");
 static const arg_def_t use_i420 =
@@ -220,7 +222,8 @@
 static const arg_def_t lag_in_frames =
     ARG_DEF(NULL, "lag-in-frames", 1, "Max number of frames to lag");
 
-static const arg_def_t *global_args[] = { &use_yv12,
+static const arg_def_t *global_args[] = { &use_nv12,
+                                          &use_yv12,
                                           &use_i420,
                                           &use_i422,
                                           &use_i444,
@@ -696,6 +699,8 @@
       global->deadline = VPX_DL_REALTIME;
     else if (arg_match(&arg, &use_yv12, argi))
       global->color_type = YV12;
+    else if (arg_match(&arg, &use_nv12, argi))
+      global->color_type = NV12;
     else if (arg_match(&arg, &use_i420, argi))
       global->color_type = I420;
     else if (arg_match(&arg, &use_i422, argi))
@@ -1642,6 +1647,7 @@
     case I444: input.fmt = VPX_IMG_FMT_I444; break;
     case I440: input.fmt = VPX_IMG_FMT_I440; break;
     case YV12: input.fmt = VPX_IMG_FMT_YV12; break;
+    case NV12: input.fmt = VPX_IMG_FMT_NV12; break;
   }
 
   {
--- a/vpxenc.h
+++ b/vpxenc.h
@@ -28,6 +28,7 @@
   I444,  // 4:4:4 8+ bit-depth
   I440,  // 4:4:0 8+ bit-depth
   YV12,  // 4:2:0 with uv flipped, only 8-bit depth
+  NV12,  // 4:2:0 with uv interleaved
 } ColorInputType;
 
 struct VpxInterface;