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;