aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/encode_api_test.cc191
-rw-r--r--vp9/vp9_cx_iface.c62
-rw-r--r--vpx/vpx_encoder.h11
3 files changed, 228 insertions, 36 deletions
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc
index ee7d8d27c..a25dbc625 100644
--- a/test/encode_api_test.cc
+++ b/test/encode_api_test.cc
@@ -12,6 +12,7 @@
#include <cstring>
#include <initializer_list>
#include <new>
+#include <vector>
#include "third_party/googletest/src/include/gtest/gtest.h"
#include "test/codec_factory.h"
@@ -25,6 +26,7 @@
#include "vpx/vpx_encoder.h"
#include "vpx/vpx_image.h"
#include "vpx/vpx_tpl.h"
+#include "vpx_ports/msvc.h"
namespace {
@@ -43,6 +45,32 @@ bool IsVP9(vpx_codec_iface_t *iface) {
0;
}
+void InitCodec(vpx_codec_iface_t &iface, int width, int height,
+ vpx_codec_ctx_t *enc, vpx_codec_enc_cfg_t *cfg) {
+ cfg->g_w = width;
+ cfg->g_h = height;
+ cfg->g_lag_in_frames = 0;
+ cfg->g_pass = VPX_RC_ONE_PASS;
+ ASSERT_EQ(vpx_codec_enc_init(enc, &iface, cfg, 0), VPX_CODEC_OK);
+
+ ASSERT_EQ(vpx_codec_control_(enc, VP8E_SET_CPUUSED, 2), VPX_CODEC_OK);
+}
+
+// Encodes 1 frame of size |cfg.g_w| x |cfg.g_h| setting |enc|'s configuration
+// to |cfg|.
+void EncodeWithConfig(const vpx_codec_enc_cfg_t &cfg, vpx_codec_ctx_t *enc) {
+ libvpx_test::DummyVideoSource video;
+ video.SetSize(cfg.g_w, cfg.g_h);
+ video.Begin();
+ EXPECT_EQ(vpx_codec_enc_config_set(enc, &cfg), VPX_CODEC_OK)
+ << vpx_codec_error_detail(enc);
+
+ EXPECT_EQ(vpx_codec_encode(enc, video.img(), video.pts(), video.duration(),
+ /*flags=*/0, VPX_DL_GOOD_QUALITY),
+ VPX_CODEC_OK)
+ << vpx_codec_error_detail(enc);
+}
+
TEST(EncodeAPI, InvalidParams) {
uint8_t buf[1] = { 0 };
vpx_image_t img;
@@ -181,7 +209,26 @@ TEST(EncodeAPI, HugeFramerateVp8) {
vpx_img_free(image);
ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}
-#endif
+
+TEST(EncodeAPI, VP8GlobalHeaders) {
+ constexpr int kWidth = 320;
+ constexpr int kHeight = 240;
+
+ vpx_codec_enc_cfg_t cfg = {};
+ struct Encoder {
+ ~Encoder() { EXPECT_EQ(vpx_codec_destroy(&ctx), VPX_CODEC_OK); }
+ vpx_codec_ctx_t ctx = {};
+ } enc;
+
+ ASSERT_EQ(vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &cfg, 0),
+ VPX_CODEC_OK);
+ ASSERT_NO_FATAL_FAILURE(
+ InitCodec(*vpx_codec_vp8_cx(), kWidth, kHeight, &enc.ctx, &cfg));
+ EXPECT_EQ(vpx_codec_get_global_headers(&enc.ctx), nullptr);
+ EXPECT_NO_FATAL_FAILURE(EncodeWithConfig(cfg, &enc.ctx));
+ EXPECT_EQ(vpx_codec_get_global_headers(&enc.ctx), nullptr);
+}
+#endif // CONFIG_VP8_ENCODER
// Set up 2 spatial streams with 2 temporal layers per stream, and generate
// invalid configuration by setting the temporal layer rate allocation
@@ -365,32 +412,6 @@ TEST(EncodeAPI, SetRoi) {
}
}
-void InitCodec(vpx_codec_iface_t &iface, int width, int height,
- vpx_codec_ctx_t *enc, vpx_codec_enc_cfg_t *cfg) {
- cfg->g_w = width;
- cfg->g_h = height;
- cfg->g_lag_in_frames = 0;
- cfg->g_pass = VPX_RC_ONE_PASS;
- ASSERT_EQ(vpx_codec_enc_init(enc, &iface, cfg, 0), VPX_CODEC_OK);
-
- ASSERT_EQ(vpx_codec_control_(enc, VP8E_SET_CPUUSED, 2), VPX_CODEC_OK);
-}
-
-// Encodes 1 frame of size |cfg.g_w| x |cfg.g_h| setting |enc|'s configuration
-// to |cfg|.
-void EncodeWithConfig(const vpx_codec_enc_cfg_t &cfg, vpx_codec_ctx_t *enc) {
- libvpx_test::DummyVideoSource video;
- video.SetSize(cfg.g_w, cfg.g_h);
- video.Begin();
- EXPECT_EQ(vpx_codec_enc_config_set(enc, &cfg), VPX_CODEC_OK)
- << vpx_codec_error_detail(enc);
-
- EXPECT_EQ(vpx_codec_encode(enc, video.img(), video.pts(), video.duration(),
- /*flags=*/0, VPX_DL_GOOD_QUALITY),
- VPX_CODEC_OK)
- << vpx_codec_error_detail(enc);
-}
-
TEST(EncodeAPI, ConfigChangeThreadCount) {
constexpr int kWidth = 1920;
constexpr int kHeight = 1080;
@@ -912,6 +933,122 @@ TEST(EncodeAPI, Buganizer311294795) {
encoder.Encode(false);
encoder.Encode(false);
}
+
+TEST(EncodeAPI, VP9GlobalHeaders) {
+ constexpr int kWidth = 320;
+ constexpr int kHeight = 240;
+
+ libvpx_test::DummyVideoSource video;
+ video.SetSize(kWidth, kHeight);
+
+#if CONFIG_VP9_HIGHBITDEPTH
+ const int profiles[] = { 0, 1, 2, 3 };
+#else
+ const int profiles[] = { 0, 1 };
+#endif
+ char str[80];
+ for (const int profile : profiles) {
+ std::vector<vpx_bit_depth_t> bitdepths;
+ std::vector<vpx_img_fmt_t> formats;
+ switch (profile) {
+ case 0:
+ bitdepths = { VPX_BITS_8 };
+ formats = { VPX_IMG_FMT_I420 };
+ break;
+ case 1:
+ bitdepths = { VPX_BITS_8 };
+ formats = { VPX_IMG_FMT_I422, VPX_IMG_FMT_I444 };
+ break;
+#if CONFIG_VP9_HIGHBITDEPTH
+ case 2:
+ bitdepths = { VPX_BITS_10, VPX_BITS_12 };
+ formats = { VPX_IMG_FMT_I42016 };
+ break;
+ case 3:
+ bitdepths = { VPX_BITS_10, VPX_BITS_12 };
+ formats = { VPX_IMG_FMT_I42216, VPX_IMG_FMT_I44416 };
+ break;
+#endif
+ }
+
+ for (const auto format : formats) {
+ for (const auto bitdepth : bitdepths) {
+ snprintf(str, sizeof(str), "profile: %d bitdepth: %d format: %d",
+ profile, bitdepth, format);
+ SCOPED_TRACE(str);
+
+ vpx_codec_enc_cfg_t cfg = {};
+ struct Encoder {
+ ~Encoder() { EXPECT_EQ(vpx_codec_destroy(&ctx), VPX_CODEC_OK); }
+ vpx_codec_ctx_t ctx = {};
+ } enc;
+ vpx_codec_ctx_t *const ctx = &enc.ctx;
+
+ ASSERT_EQ(vpx_codec_enc_config_default(vpx_codec_vp9_cx(), &cfg, 0),
+ VPX_CODEC_OK);
+ cfg.g_w = kWidth;
+ cfg.g_h = kHeight;
+ cfg.g_lag_in_frames = 0;
+ cfg.g_pass = VPX_RC_ONE_PASS;
+ cfg.g_profile = profile;
+ cfg.g_bit_depth = bitdepth;
+ ASSERT_EQ(
+ vpx_codec_enc_init(ctx, vpx_codec_vp9_cx(), &cfg,
+ bitdepth == 8 ? 0 : VPX_CODEC_USE_HIGHBITDEPTH),
+ VPX_CODEC_OK);
+ ASSERT_EQ(vpx_codec_control_(ctx, VP8E_SET_CPUUSED, 2), VPX_CODEC_OK);
+ ASSERT_EQ(vpx_codec_control_(ctx, VP9E_SET_TARGET_LEVEL, 62),
+ VPX_CODEC_OK);
+
+ vpx_fixed_buf_t *global_headers = vpx_codec_get_global_headers(ctx);
+ EXPECT_NE(global_headers, nullptr);
+ EXPECT_EQ(global_headers->sz, size_t{ 9 });
+
+ video.SetImageFormat(format);
+ video.Begin();
+ EXPECT_EQ(
+ vpx_codec_encode(ctx, video.img(), video.pts(), video.duration(),
+ /*flags=*/0, VPX_DL_GOOD_QUALITY),
+ VPX_CODEC_OK)
+ << vpx_codec_error_detail(ctx);
+
+ global_headers = vpx_codec_get_global_headers(ctx);
+ EXPECT_NE(global_headers, nullptr);
+ EXPECT_EQ(global_headers->sz, size_t{ 12 });
+ uint8_t chroma_subsampling;
+ if ((format & VPX_IMG_FMT_I420) == VPX_IMG_FMT_I420) {
+ chroma_subsampling = 1;
+ } else if ((format & VPX_IMG_FMT_I422) == VPX_IMG_FMT_I422) {
+ chroma_subsampling = 2;
+ } else { // VPX_IMG_FMT_I444
+ chroma_subsampling = 3;
+ }
+ const uint8_t expected_headers[] = { 1,
+ 1,
+ static_cast<uint8_t>(profile),
+ 2,
+ 1,
+ /*level,*/ 3,
+ 1,
+ static_cast<uint8_t>(bitdepth),
+ 4,
+ 1,
+ chroma_subsampling };
+ const uint8_t *actual_headers =
+ reinterpret_cast<const uint8_t *>(global_headers->buf);
+ for (int i = 0; i < 5; ++i) {
+ EXPECT_EQ(expected_headers[i], actual_headers[i]) << "index: " << i;
+ }
+ EXPECT_NE(actual_headers[6], 0); // level
+ for (int i = 5; i < 11; ++i) {
+ EXPECT_EQ(expected_headers[i], actual_headers[i + 1])
+ << "index: " << i + 1;
+ }
+ }
+ }
+ }
+}
+
#endif // CONFIG_VP9_ENCODER
} // namespace
diff --git a/vp9/vp9_cx_iface.c b/vp9/vp9_cx_iface.c
index e738feda0..3a462c372 100644
--- a/vp9/vp9_cx_iface.c
+++ b/vp9/vp9_cx_iface.c
@@ -129,6 +129,8 @@ struct vpx_codec_alg_priv {
vpx_codec_priv_output_cx_pkt_cb_pair_t output_cx_pkt_cb;
// BufferPool that holds all reference frames.
BufferPool *buffer_pool;
+ vpx_fixed_buf_t global_headers;
+ int global_header_subsampling;
};
// Called by encoder_set_config() and encoder_encode() only. Must not be called
@@ -1137,6 +1139,7 @@ static vpx_codec_err_t encoder_init(vpx_codec_ctx_t *ctx,
if (res == VPX_CODEC_OK) {
priv->pts_offset_initialized = 0;
+ priv->global_header_subsampling = -1;
// TODO(angiebird): Replace priv->timestamp_ratio by
// oxcf->g_timebase_in_ts
priv->timestamp_ratio = get_g_timebase_in_ts(priv->cfg.g_timebase);
@@ -1157,6 +1160,7 @@ static vpx_codec_err_t encoder_init(vpx_codec_ctx_t *ctx,
static vpx_codec_err_t encoder_destroy(vpx_codec_alg_priv_t *ctx) {
free(ctx->cx_data);
+ free(ctx->global_headers.buf);
vp9_remove_compressor(ctx->cpi);
vpx_free(ctx->buffer_pool);
vpx_free(ctx);
@@ -1341,6 +1345,20 @@ static vpx_codec_err_t encoder_encode(vpx_codec_alg_priv_t *ctx,
return VPX_CODEC_MEM_ERROR;
}
}
+
+ int chroma_subsampling = -1;
+ if ((img->fmt & VPX_IMG_FMT_I420) == VPX_IMG_FMT_I420 ||
+ (img->fmt & VPX_IMG_FMT_NV12) == VPX_IMG_FMT_NV12 ||
+ (img->fmt & VPX_IMG_FMT_YV12) == VPX_IMG_FMT_YV12) {
+ chroma_subsampling = 1; // matches default for Codec Parameter String
+ } else if ((img->fmt & VPX_IMG_FMT_I422) == VPX_IMG_FMT_I422) {
+ chroma_subsampling = 2;
+ } else if ((img->fmt & VPX_IMG_FMT_I444) == VPX_IMG_FMT_I444) {
+ chroma_subsampling = 3;
+ }
+ if (chroma_subsampling > ctx->global_header_subsampling) {
+ ctx->global_header_subsampling = chroma_subsampling;
+ }
}
}
@@ -1632,6 +1650,34 @@ static vpx_codec_err_t ctrl_set_previewpp(vpx_codec_alg_priv_t *ctx,
#endif
}
+// Returns the contents of CodecPrivate described in:
+// https://www.webmproject.org/docs/container/#vp9-codec-feature-metadata-codecprivate
+// This includes Profile, Level, Bit depth and Chroma subsampling. Each entry
+// is 3 bytes. 1 byte ID, 1 byte length (= 1) and 1 byte value.
+static vpx_fixed_buf_t *encoder_get_global_headers(vpx_codec_alg_priv_t *ctx) {
+ if (!ctx->cpi) return NULL;
+
+ const unsigned int profile = ctx->cfg.g_profile;
+ const VP9_LEVEL level = vp9_get_level(&ctx->cpi->level_info.level_spec);
+ const vpx_bit_depth_t bit_depth = ctx->cfg.g_bit_depth;
+ const int subsampling = ctx->global_header_subsampling;
+ const uint8_t buf[12] = {
+ 1, 1, (uint8_t)profile, 2, 1, (uint8_t)level,
+ 3, 1, (uint8_t)bit_depth, 4, 1, (uint8_t)subsampling
+ };
+
+ if (ctx->global_headers.buf) free(ctx->global_headers.buf);
+ ctx->global_headers.buf = malloc(sizeof(buf));
+ if (!ctx->global_headers.buf) return NULL;
+
+ ctx->global_headers.sz = sizeof(buf);
+ // No data or I440, which isn't mapped.
+ if (ctx->global_header_subsampling == -1) ctx->global_headers.sz -= 3;
+ memcpy(ctx->global_headers.buf, buf, ctx->global_headers.sz);
+
+ return &ctx->global_headers;
+}
+
static vpx_image_t *encoder_get_preview(vpx_codec_alg_priv_t *ctx) {
YV12_BUFFER_CONFIG sd;
vp9_ppflags_t flags;
@@ -2175,14 +2221,14 @@ CODEC_INTERFACE(vpx_codec_vp9_cx) = {
},
{
// NOLINT
- 1, // 1 cfg map
- encoder_usage_cfg_map, // vpx_codec_enc_cfg_map_t
- encoder_encode, // vpx_codec_encode_fn_t
- encoder_get_cxdata, // vpx_codec_get_cx_data_fn_t
- encoder_set_config, // vpx_codec_enc_config_set_fn_t
- NULL, // vpx_codec_get_global_headers_fn_t
- encoder_get_preview, // vpx_codec_get_preview_frame_fn_t
- NULL // vpx_codec_enc_mr_get_mem_loc_fn_t
+ 1, // 1 cfg map
+ encoder_usage_cfg_map, // vpx_codec_enc_cfg_map_t
+ encoder_encode, // vpx_codec_encode_fn_t
+ encoder_get_cxdata, // vpx_codec_get_cx_data_fn_t
+ encoder_set_config, // vpx_codec_enc_config_set_fn_t
+ encoder_get_global_headers, // vpx_codec_get_global_headers_fn_t
+ encoder_get_preview, // vpx_codec_get_preview_frame_fn_t
+ NULL // vpx_codec_enc_mr_get_mem_loc_fn_t
}
};
diff --git a/vpx/vpx_encoder.h b/vpx/vpx_encoder.h
index c45d1a2ba..e65d27221 100644
--- a/vpx/vpx_encoder.h
+++ b/vpx/vpx_encoder.h
@@ -970,12 +970,21 @@ vpx_codec_err_t vpx_codec_enc_config_set(vpx_codec_ctx_t *ctx,
*
* Retrieves a stream level global header packet, if supported by the codec.
*
+ * \li VP8: Unsupported
+ * \li VP9: Returns a buffer of <tt>ID (1 byte)|Length (1 byte)|Length
+ * bytes</tt> values. The function should be called after encoding to retrieve
+ * the most accurate information.
+ *
* \param[in] ctx Pointer to this instance's context
*
* \retval NULL
* Encoder does not support global header
* \retval Non-NULL
- * Pointer to buffer containing global header packet
+ * Pointer to buffer containing global header packet. The buffer pointer
+ * and its contents are only valid for the lifetime of \a ctx. The contents
+ * may change in subsequent calls to the function.
+ * \sa
+ * https://www.webmproject.org/docs/container/#vp9-codec-feature-metadata-codecprivate
*/
vpx_fixed_buf_t *vpx_codec_get_global_headers(vpx_codec_ctx_t *ctx);