diff options
-rw-r--r-- | test/encode_api_test.cc | 191 | ||||
-rw-r--r-- | vp9/vp9_cx_iface.c | 62 | ||||
-rw-r--r-- | vpx/vpx_encoder.h | 11 |
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); |