| /* |
| * Copyright (c) 2016 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 <cassert> |
| #include <climits> |
| #include <cstdint> |
| #include <cstring> |
| #include <initializer_list> |
| #include <new> |
| #include <vector> |
| |
| #include "third_party/googletest/src/include/gtest/gtest.h" |
| #include "test/acm_random.h" |
| #include "test/codec_factory.h" |
| #include "test/encode_test_driver.h" |
| #include "test/i420_video_source.h" |
| #include "test/video_source.h" |
| |
| #include "./vpx_config.h" |
| #include "vpx/vp8cx.h" |
| #include "vpx/vpx_codec.h" |
| #include "vpx/vpx_encoder.h" |
| #include "vpx/vpx_image.h" |
| #include "vpx/vpx_tpl.h" |
| #include "vpx_ports/msvc.h" |
| |
| namespace { |
| |
| vpx_codec_iface_t *kCodecIfaces[] = { |
| #if CONFIG_VP8_ENCODER |
| &vpx_codec_vp8_cx_algo, |
| #endif |
| #if CONFIG_VP9_ENCODER |
| &vpx_codec_vp9_cx_algo, |
| #endif |
| }; |
| |
| bool IsVP9(vpx_codec_iface_t *iface) { |
| static const char kVP9Name[] = "WebM Project VP9"; |
| return strncmp(kVP9Name, vpx_codec_iface_name(iface), sizeof(kVP9Name) - 1) == |
| 0; |
| } |
| |
| void *Memset16(void *dest, int val, size_t length) { |
| uint16_t *dest16 = reinterpret_cast<uint16_t *>(dest); |
| for (size_t i = 0; i < length; i++) { |
| *dest16++ = val; |
| } |
| return dest; |
| } |
| |
| vpx_image_t *CreateImage(vpx_bit_depth_t bit_depth, vpx_img_fmt_t fmt, |
| unsigned int width, unsigned int height) { |
| assert(fmt != VPX_IMG_FMT_NV12); |
| if (bit_depth > VPX_BITS_8) { |
| fmt = static_cast<vpx_img_fmt_t>(fmt | VPX_IMG_FMT_HIGHBITDEPTH); |
| } |
| vpx_image_t *image = vpx_img_alloc(nullptr, fmt, width, height, 1); |
| if (!image) return image; |
| |
| const int val = 1 << (bit_depth - 1); |
| const unsigned int uv_h = |
| (image->d_h + image->y_chroma_shift) >> image->y_chroma_shift; |
| const unsigned int uv_w = |
| (image->d_w + image->x_chroma_shift) >> image->x_chroma_shift; |
| if (bit_depth > VPX_BITS_8) { |
| for (unsigned int i = 0; i < image->d_h; ++i) { |
| Memset16(image->planes[0] + i * image->stride[0], val, image->d_w); |
| } |
| for (unsigned int i = 0; i < uv_h; ++i) { |
| Memset16(image->planes[1] + i * image->stride[1], val, uv_w); |
| Memset16(image->planes[2] + i * image->stride[2], val, uv_w); |
| } |
| } else { |
| for (unsigned int i = 0; i < image->d_h; ++i) { |
| memset(image->planes[0] + i * image->stride[0], val, image->d_w); |
| } |
| for (unsigned int i = 0; i < uv_h; ++i) { |
| memset(image->planes[1] + i * image->stride[1], val, uv_w); |
| memset(image->planes[2] + i * image->stride[2], val, uv_w); |
| } |
| } |
| |
| return image; |
| } |
| |
| 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; |
| vpx_codec_ctx_t enc; |
| vpx_codec_enc_cfg_t cfg; |
| |
| EXPECT_EQ(&img, vpx_img_wrap(&img, VPX_IMG_FMT_I420, 1, 1, 1, buf)); |
| |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, |
| vpx_codec_enc_init(nullptr, nullptr, nullptr, 0)); |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, |
| vpx_codec_enc_init(&enc, nullptr, nullptr, 0)); |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, |
| vpx_codec_encode(nullptr, nullptr, 0, 0, 0, 0)); |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, |
| vpx_codec_encode(nullptr, &img, 0, 0, 0, 0)); |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, vpx_codec_destroy(nullptr)); |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, |
| vpx_codec_enc_config_default(nullptr, nullptr, 0)); |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, |
| vpx_codec_enc_config_default(nullptr, &cfg, 0)); |
| EXPECT_NE(vpx_codec_error(nullptr), nullptr); |
| |
| for (const auto *iface : kCodecIfaces) { |
| SCOPED_TRACE(vpx_codec_iface_name(iface)); |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, |
| vpx_codec_enc_init(nullptr, iface, nullptr, 0)); |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, |
| vpx_codec_enc_init(&enc, iface, nullptr, 0)); |
| EXPECT_EQ(VPX_CODEC_INVALID_PARAM, |
| vpx_codec_enc_config_default(iface, &cfg, 1)); |
| |
| EXPECT_EQ(VPX_CODEC_OK, vpx_codec_enc_config_default(iface, &cfg, 0)); |
| EXPECT_EQ(VPX_CODEC_OK, vpx_codec_enc_init(&enc, iface, &cfg, 0)); |
| EXPECT_EQ(VPX_CODEC_OK, vpx_codec_encode(&enc, nullptr, 0, 0, 0, 0)); |
| |
| EXPECT_EQ(VPX_CODEC_OK, vpx_codec_destroy(&enc)); |
| } |
| } |
| |
| TEST(EncodeAPI, HighBitDepthCapability) { |
| // VP8 should not claim VP9 HBD as a capability. |
| #if CONFIG_VP8_ENCODER |
| const vpx_codec_caps_t vp8_caps = vpx_codec_get_caps(&vpx_codec_vp8_cx_algo); |
| EXPECT_EQ(vp8_caps & VPX_CODEC_CAP_HIGHBITDEPTH, 0); |
| #endif |
| |
| #if CONFIG_VP9_ENCODER |
| const vpx_codec_caps_t vp9_caps = vpx_codec_get_caps(&vpx_codec_vp9_cx_algo); |
| #if CONFIG_VP9_HIGHBITDEPTH |
| EXPECT_EQ(vp9_caps & VPX_CODEC_CAP_HIGHBITDEPTH, VPX_CODEC_CAP_HIGHBITDEPTH); |
| #else |
| EXPECT_EQ(vp9_caps & VPX_CODEC_CAP_HIGHBITDEPTH, 0); |
| #endif |
| #endif |
| } |
| |
| #if CONFIG_VP8_ENCODER |
| TEST(EncodeAPI, ImageSizeSetting) { |
| const int width = 711; |
| const int height = 360; |
| const int bps = 12; |
| vpx_image_t img; |
| vpx_codec_ctx_t enc; |
| vpx_codec_enc_cfg_t cfg; |
| uint8_t *img_buf = reinterpret_cast<uint8_t *>( |
| calloc(width * height * bps / 8, sizeof(*img_buf))); |
| vpx_codec_enc_config_default(vpx_codec_vp8_cx(), &cfg, 0); |
| |
| cfg.g_w = width; |
| cfg.g_h = height; |
| |
| vpx_img_wrap(&img, VPX_IMG_FMT_I420, width, height, 1, img_buf); |
| |
| vpx_codec_enc_init(&enc, vpx_codec_vp8_cx(), &cfg, 0); |
| |
| EXPECT_EQ(VPX_CODEC_OK, vpx_codec_encode(&enc, &img, 0, 1, 0, 0)); |
| |
| free(img_buf); |
| |
| vpx_codec_destroy(&enc); |
| } |
| |
| // Verifies the fix for a float-cast-overflow in vp8_change_config(). |
| // |
| // Causes cpi->framerate to become the largest possible value (10,000,000) in |
| // VP8 by setting cfg.g_timebase to 1/10000000 and passing a duration of 1 to |
| // vpx_codec_encode(). |
| TEST(EncodeAPI, HugeFramerateVp8) { |
| vpx_codec_iface_t *const iface = vpx_codec_vp8_cx(); |
| vpx_codec_enc_cfg_t cfg; |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK); |
| cfg.g_w = 271; |
| cfg.g_h = 1080; |
| cfg.g_timebase.num = 1; |
| // Largest value (VP8's TICKS_PER_SEC) such that frame duration is nonzero (1 |
| // tick). |
| cfg.g_timebase.den = 10000000; |
| cfg.g_pass = VPX_RC_ONE_PASS; |
| cfg.g_lag_in_frames = 0; |
| cfg.rc_end_usage = VPX_CBR; |
| |
| vpx_codec_ctx_t enc; |
| // Before we encode the first frame, cpi->framerate is set to a guess (the |
| // reciprocal of cfg.g_timebase). If this guess doesn't seem reasonable |
| // (> 180), cpi->framerate is set to 30. |
| ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK); |
| |
| ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, -12), VPX_CODEC_OK); |
| |
| vpx_image_t *const image = |
| vpx_img_alloc(nullptr, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1); |
| ASSERT_NE(image, nullptr); |
| |
| for (unsigned int i = 0; i < image->d_h; ++i) { |
| memset(image->planes[0] + i * image->stride[0], 128, image->d_w); |
| } |
| const unsigned int uv_h = (image->d_h + 1) / 2; |
| const unsigned int uv_w = (image->d_w + 1) / 2; |
| for (unsigned int i = 0; i < uv_h; ++i) { |
| memset(image->planes[1] + i * image->stride[1], 128, uv_w); |
| memset(image->planes[2] + i * image->stride[2], 128, uv_w); |
| } |
| |
| // Encode a frame. |
| // Up to this point cpi->framerate is 30. Now pass a duration of only 1. This |
| // causes cpi->framerate to become 10,000,000. |
| ASSERT_EQ(vpx_codec_encode(&enc, image, 0, 1, 0, VPX_DL_REALTIME), |
| VPX_CODEC_OK); |
| |
| // Change to the same config. Since cpi->framerate is now huge, when it is |
| // used to calculate raw_target_rate (bit rate of uncompressed frames), the |
| // result is likely to overflow an unsigned int. |
| ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK); |
| |
| vpx_img_free(image); |
| ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK); |
| } |
| |
| // A test that reproduces https://crbug.com/webm/1831. |
| TEST(EncodeAPI, RandomPixelsVp8) { |
| // Initialize libvpx encoder |
| vpx_codec_iface_t *const iface = vpx_codec_vp8_cx(); |
| vpx_codec_enc_cfg_t cfg; |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK); |
| |
| cfg.rc_target_bitrate = 2000; |
| cfg.g_w = 1280; |
| cfg.g_h = 720; |
| |
| vpx_codec_ctx_t enc; |
| ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK); |
| |
| // Generate random frame data and encode |
| libvpx_test::RandomVideoSource video; |
| video.SetSize(cfg.g_w, cfg.g_h); |
| video.SetImageFormat(VPX_IMG_FMT_I420); |
| video.Begin(); |
| ASSERT_EQ(vpx_codec_encode(&enc, video.img(), video.pts(), video.duration(), |
| /*flags=*/0, VPX_DL_BEST_QUALITY), |
| VPX_CODEC_OK); |
| |
| // Destroy libvpx encoder |
| vpx_codec_destroy(&enc); |
| } |
| |
| TEST(EncodeAPI, ChangeToL1T3AndSetBitrateVp8) { |
| // Initialize libvpx encoder |
| vpx_codec_iface_t *const iface = vpx_codec_vp8_cx(); |
| vpx_codec_enc_cfg_t cfg; |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK); |
| |
| cfg.g_threads = 1; |
| cfg.g_profile = 0; |
| cfg.g_w = 1; |
| cfg.g_h = 64; |
| cfg.g_bit_depth = VPX_BITS_8; |
| cfg.g_input_bit_depth = 8; |
| cfg.g_timebase.num = 1; |
| cfg.g_timebase.den = 1000000; |
| cfg.g_pass = VPX_RC_ONE_PASS; |
| cfg.g_lag_in_frames = 0; |
| cfg.rc_dropframe_thresh = 0; // Don't drop frames |
| cfg.rc_resize_allowed = 0; |
| cfg.rc_end_usage = VPX_VBR; |
| cfg.rc_target_bitrate = 10; |
| cfg.rc_min_quantizer = 2; |
| cfg.rc_max_quantizer = 58; |
| cfg.kf_mode = VPX_KF_AUTO; |
| cfg.kf_min_dist = 0; |
| cfg.kf_max_dist = 10000; |
| |
| vpx_codec_ctx_t enc; |
| ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK); |
| |
| ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_CPUUSED, -6), VPX_CODEC_OK); |
| |
| // Generate random frame data and encode |
| uint8_t img[1 * 64 * 3 / 2]; |
| libvpx_test::ACMRandom rng; |
| for (size_t i = 0; i < sizeof(img); ++i) { |
| img[i] = rng.Rand8(); |
| } |
| vpx_image_t img_wrapper; |
| ASSERT_EQ( |
| vpx_img_wrap(&img_wrapper, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1, img), |
| &img_wrapper); |
| vpx_enc_frame_flags_t flags = VPX_EFLAG_FORCE_KF; |
| ASSERT_EQ( |
| vpx_codec_encode(&enc, &img_wrapper, 0, 500000, flags, VPX_DL_REALTIME), |
| VPX_CODEC_OK); |
| ASSERT_EQ(vpx_codec_encode(&enc, nullptr, -1, 0, 0, 0), VPX_CODEC_OK); |
| |
| cfg.rc_target_bitrate = 4294967; |
| // Set the scalability mode to L1T3. |
| cfg.ts_number_layers = 3; |
| cfg.ts_periodicity = 4; |
| cfg.ts_layer_id[0] = 0; |
| cfg.ts_layer_id[1] = 2; |
| cfg.ts_layer_id[2] = 1; |
| cfg.ts_layer_id[3] = 2; |
| cfg.ts_rate_decimator[0] = 4; |
| cfg.ts_rate_decimator[1] = 2; |
| cfg.ts_rate_decimator[2] = 1; |
| // Bitrate allocation L0: 50% L1: 20% L2: 30% |
| cfg.layer_target_bitrate[0] = cfg.ts_target_bitrate[0] = |
| 50 * cfg.rc_target_bitrate / 100; |
| cfg.layer_target_bitrate[1] = cfg.ts_target_bitrate[1] = |
| 70 * cfg.rc_target_bitrate / 100; |
| cfg.layer_target_bitrate[2] = cfg.ts_target_bitrate[2] = |
| cfg.rc_target_bitrate; |
| cfg.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_0212; |
| cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT; |
| ASSERT_EQ(vpx_codec_enc_config_set(&enc, &cfg), VPX_CODEC_OK); |
| |
| ASSERT_EQ(vpx_codec_control(&enc, VP8E_SET_TEMPORAL_LAYER_ID, 2), |
| VPX_CODEC_OK); |
| |
| constexpr vpx_enc_frame_flags_t VP8_UPDATE_NOTHING = |
| VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_LAST; |
| // Layer 2: only reference last frame, no updates |
| // It only depends on layer 0 |
| flags = VP8_UPDATE_NOTHING | VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF; |
| ASSERT_EQ( |
| vpx_codec_encode(&enc, &img_wrapper, 0, 500000, flags, VPX_DL_REALTIME), |
| VPX_CODEC_OK); |
| |
| // Destroy libvpx encoder |
| vpx_codec_destroy(&enc); |
| } |
| |
| // Emulates the WebCodecs VideoEncoder interface. |
| class VP8Encoder { |
| public: |
| explicit VP8Encoder(int speed) : speed_(speed) {} |
| ~VP8Encoder(); |
| |
| void Configure(unsigned int threads, unsigned int width, unsigned int height, |
| vpx_rc_mode end_usage, unsigned long deadline); |
| void Encode(bool key_frame); |
| |
| private: |
| const int speed_; |
| bool initialized_ = false; |
| vpx_codec_enc_cfg_t cfg_; |
| vpx_codec_ctx_t enc_; |
| int frame_index_ = 0; |
| unsigned long deadline_ = 0; |
| }; |
| |
| VP8Encoder::~VP8Encoder() { |
| if (initialized_) { |
| EXPECT_EQ(vpx_codec_destroy(&enc_), VPX_CODEC_OK); |
| } |
| } |
| |
| void VP8Encoder::Configure(unsigned int threads, unsigned int width, |
| unsigned int height, vpx_rc_mode end_usage, |
| unsigned long deadline) { |
| deadline_ = deadline; |
| |
| if (!initialized_) { |
| vpx_codec_iface_t *const iface = vpx_codec_vp8_cx(); |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg_, /*usage=*/0), |
| VPX_CODEC_OK); |
| cfg_.g_threads = threads; |
| cfg_.g_w = width; |
| cfg_.g_h = height; |
| cfg_.g_timebase.num = 1; |
| cfg_.g_timebase.den = 1000 * 1000; // microseconds |
| cfg_.g_pass = VPX_RC_ONE_PASS; |
| cfg_.g_lag_in_frames = 0; |
| cfg_.rc_end_usage = end_usage; |
| cfg_.rc_min_quantizer = 2; |
| cfg_.rc_max_quantizer = 58; |
| ASSERT_EQ(vpx_codec_enc_init(&enc_, iface, &cfg_, 0), VPX_CODEC_OK); |
| ASSERT_EQ(vpx_codec_control(&enc_, VP8E_SET_CPUUSED, speed_), VPX_CODEC_OK); |
| initialized_ = true; |
| return; |
| } |
| |
| cfg_.g_threads = threads; |
| cfg_.g_w = width; |
| cfg_.g_h = height; |
| cfg_.rc_end_usage = end_usage; |
| ASSERT_EQ(vpx_codec_enc_config_set(&enc_, &cfg_), VPX_CODEC_OK) |
| << vpx_codec_error_detail(&enc_); |
| } |
| |
| void VP8Encoder::Encode(bool key_frame) { |
| assert(initialized_); |
| const vpx_codec_cx_pkt_t *pkt; |
| vpx_image_t *image = |
| CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg_.g_w, cfg_.g_h); |
| ASSERT_NE(image, nullptr); |
| const vpx_enc_frame_flags_t flags = key_frame ? VPX_EFLAG_FORCE_KF : 0; |
| ASSERT_EQ(vpx_codec_encode(&enc_, image, frame_index_, 1, flags, deadline_), |
| VPX_CODEC_OK); |
| ++frame_index_; |
| vpx_codec_iter_t iter = nullptr; |
| while ((pkt = vpx_codec_get_cx_data(&enc_, &iter)) != nullptr) { |
| ASSERT_EQ(pkt->kind, VPX_CODEC_CX_FRAME_PKT); |
| if (key_frame) { |
| ASSERT_EQ(pkt->data.frame.flags & VPX_FRAME_IS_KEY, VPX_FRAME_IS_KEY); |
| } |
| } |
| vpx_img_free(image); |
| } |
| |
| // This is the reproducer testcase for crbug.com/324459561. However, |
| // just running this test is not enough to reproduce the bug. We also |
| // need to send signals to the test. |
| TEST(EncodeAPI, Chromium324459561) { |
| VP8Encoder encoder(-12); |
| |
| encoder.Configure(11, 1685, 652, VPX_CBR, VPX_DL_REALTIME); |
| |
| encoder.Encode(true); |
| encoder.Encode(true); |
| encoder.Encode(true); |
| |
| encoder.Configure(0, 1685, 1, VPX_VBR, VPX_DL_REALTIME); |
| } |
| |
| 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); |
| } |
| |
| TEST(EncodeAPI, AomediaIssue3509VbrMinSection2PercentVP8) { |
| // Initialize libvpx encoder. |
| vpx_codec_iface_t *const iface = vpx_codec_vp8_cx(); |
| vpx_codec_ctx_t enc; |
| vpx_codec_enc_cfg_t cfg; |
| |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK); |
| |
| cfg.g_w = 1920; |
| cfg.g_h = 1080; |
| cfg.g_lag_in_frames = 0; |
| cfg.rc_target_bitrate = 1000000; |
| // Set this to more than 1 percent to cause a signed integer overflow in the |
| // multiplication cpi->av_per_frame_bandwidth * |
| // cpi->oxcf.two_pass_vbrmin_section in vp8_new_framerate() if the |
| // multiplication is done in the `int` type. |
| cfg.rc_2pass_vbr_minsection_pct = 2; |
| |
| ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK); |
| |
| // Create input image. |
| vpx_image_t *const image = |
| CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h); |
| ASSERT_NE(image, nullptr); |
| |
| // Encode frame. |
| // `duration` can go as high as 300, but the UBSan error is gone if |
| // `duration` is 301 or higher. |
| ASSERT_EQ( |
| vpx_codec_encode(&enc, image, 0, /*duration=*/300, 0, VPX_DL_REALTIME), |
| VPX_CODEC_OK); |
| |
| // Free resources. |
| vpx_img_free(image); |
| ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK); |
| } |
| |
| TEST(EncodeAPI, AomediaIssue3509VbrMinSection101PercentVP8) { |
| // Initialize libvpx encoder. |
| vpx_codec_iface_t *const iface = vpx_codec_vp8_cx(); |
| vpx_codec_ctx_t enc; |
| vpx_codec_enc_cfg_t cfg; |
| |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK); |
| |
| cfg.g_w = 1920; |
| cfg.g_h = 1080; |
| cfg.g_lag_in_frames = 0; |
| cfg.rc_target_bitrate = 1000000; |
| // Set this to more than 100 percent to cause an error when vbr_min_bits is |
| // cast to `int` in vp8_new_framerate() if vbr_min_bits is not clamped to |
| // INT_MAX. |
| cfg.rc_2pass_vbr_minsection_pct = 101; |
| |
| ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK); |
| |
| // Create input image. |
| vpx_image_t *const image = |
| CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h); |
| ASSERT_NE(image, nullptr); |
| |
| // Encode frame. |
| // `duration` can go as high as 300, but the UBSan error is gone if |
| // `duration` is 301 or higher. |
| ASSERT_EQ( |
| vpx_codec_encode(&enc, image, 0, /*duration=*/300, 0, VPX_DL_REALTIME), |
| VPX_CODEC_OK); |
| |
| // Free resources. |
| vpx_img_free(image); |
| ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK); |
| } |
| #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 |
| // (ts_target_bitrate[]) to 0 for both layers. This should fail independent of |
| // CONFIG_MULTI_RES_ENCODING. |
| TEST(EncodeAPI, MultiResEncode) { |
| const int width = 1280; |
| const int height = 720; |
| const int width_down = width / 2; |
| const int height_down = height / 2; |
| const int target_bitrate = 1000; |
| const int framerate = 30; |
| |
| for (const auto *iface : kCodecIfaces) { |
| vpx_codec_ctx_t enc[2]; |
| vpx_codec_enc_cfg_t cfg[2]; |
| vpx_rational_t dsf[2] = { { 2, 1 }, { 2, 1 } }; |
| |
| memset(enc, 0, sizeof(enc)); |
| |
| for (int i = 0; i < 2; i++) { |
| vpx_codec_enc_config_default(iface, &cfg[i], 0); |
| } |
| |
| /* Highest-resolution encoder settings */ |
| cfg[0].g_w = width; |
| cfg[0].g_h = height; |
| cfg[0].rc_dropframe_thresh = 0; |
| cfg[0].rc_end_usage = VPX_CBR; |
| cfg[0].rc_resize_allowed = 0; |
| cfg[0].rc_min_quantizer = 2; |
| cfg[0].rc_max_quantizer = 56; |
| cfg[0].rc_undershoot_pct = 100; |
| cfg[0].rc_overshoot_pct = 15; |
| cfg[0].rc_buf_initial_sz = 500; |
| cfg[0].rc_buf_optimal_sz = 600; |
| cfg[0].rc_buf_sz = 1000; |
| cfg[0].g_error_resilient = 1; /* Enable error resilient mode */ |
| cfg[0].g_lag_in_frames = 0; |
| |
| cfg[0].kf_mode = VPX_KF_AUTO; |
| cfg[0].kf_min_dist = 3000; |
| cfg[0].kf_max_dist = 3000; |
| |
| cfg[0].rc_target_bitrate = target_bitrate; /* Set target bitrate */ |
| cfg[0].g_timebase.num = 1; /* Set fps */ |
| cfg[0].g_timebase.den = framerate; |
| |
| memcpy(&cfg[1], &cfg[0], sizeof(cfg[0])); |
| cfg[1].rc_target_bitrate = 500; |
| cfg[1].g_w = width_down; |
| cfg[1].g_h = height_down; |
| |
| for (int i = 0; i < 2; i++) { |
| cfg[i].ts_number_layers = 2; |
| cfg[i].ts_periodicity = 2; |
| cfg[i].ts_rate_decimator[0] = 2; |
| cfg[i].ts_rate_decimator[1] = 1; |
| cfg[i].ts_layer_id[0] = 0; |
| cfg[i].ts_layer_id[1] = 1; |
| // Invalid parameters. |
| cfg[i].ts_target_bitrate[0] = 0; |
| cfg[i].ts_target_bitrate[1] = 0; |
| } |
| |
| // VP9 should report incapable, VP8 invalid for all configurations. |
| EXPECT_EQ(IsVP9(iface) ? VPX_CODEC_INCAPABLE : VPX_CODEC_INVALID_PARAM, |
| vpx_codec_enc_init_multi(&enc[0], iface, &cfg[0], 2, 0, &dsf[0])); |
| |
| for (int i = 0; i < 2; i++) { |
| vpx_codec_destroy(&enc[i]); |
| } |
| } |
| } |
| |
| TEST(EncodeAPI, SetRoi) { |
| static struct { |
| vpx_codec_iface_t *iface; |
| int ctrl_id; |
| } kCodecs[] = { |
| #if CONFIG_VP8_ENCODER |
| { &vpx_codec_vp8_cx_algo, VP8E_SET_ROI_MAP }, |
| #endif |
| #if CONFIG_VP9_ENCODER |
| { &vpx_codec_vp9_cx_algo, VP9E_SET_ROI_MAP }, |
| #endif |
| }; |
| constexpr int kWidth = 64; |
| constexpr int kHeight = 64; |
| |
| for (const auto &codec : kCodecs) { |
| SCOPED_TRACE(vpx_codec_iface_name(codec.iface)); |
| vpx_codec_ctx_t enc; |
| vpx_codec_enc_cfg_t cfg; |
| |
| EXPECT_EQ(vpx_codec_enc_config_default(codec.iface, &cfg, 0), VPX_CODEC_OK); |
| cfg.g_w = kWidth; |
| cfg.g_h = kHeight; |
| EXPECT_EQ(vpx_codec_enc_init(&enc, codec.iface, &cfg, 0), VPX_CODEC_OK); |
| |
| vpx_roi_map_t roi = {}; |
| uint8_t roi_map[kWidth * kHeight] = {}; |
| if (IsVP9(codec.iface)) { |
| roi.rows = (cfg.g_w + 7) >> 3; |
| roi.cols = (cfg.g_h + 7) >> 3; |
| } else { |
| roi.rows = (cfg.g_w + 15) >> 4; |
| roi.cols = (cfg.g_h + 15) >> 4; |
| } |
| EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), VPX_CODEC_OK); |
| |
| roi.roi_map = roi_map; |
| // VP8 only. This value isn't range checked. |
| roi.static_threshold[1] = 1000; |
| roi.static_threshold[2] = UINT_MAX / 2 + 1; |
| roi.static_threshold[3] = UINT_MAX; |
| |
| for (const auto delta : { -63, -1, 0, 1, 63 }) { |
| for (int i = 0; i < 8; ++i) { |
| roi.delta_q[i] = delta; |
| roi.delta_lf[i] = delta; |
| // VP9 only. |
| roi.skip[i] ^= 1; |
| roi.ref_frame[i] = (roi.ref_frame[i] + 1) % 4; |
| EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), VPX_CODEC_OK); |
| } |
| } |
| |
| vpx_codec_err_t expected_error; |
| for (const auto delta : { -64, 64, INT_MIN, INT_MAX }) { |
| expected_error = VPX_CODEC_INVALID_PARAM; |
| for (int i = 0; i < 8; ++i) { |
| roi.delta_q[i] = delta; |
| // The max segment count for VP8 is 4, the remainder of the entries are |
| // ignored. |
| if (i >= 4 && !IsVP9(codec.iface)) expected_error = VPX_CODEC_OK; |
| |
| EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), expected_error) |
| << "delta_q[" << i << "]: " << delta; |
| roi.delta_q[i] = 0; |
| |
| roi.delta_lf[i] = delta; |
| EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), expected_error) |
| << "delta_lf[" << i << "]: " << delta; |
| roi.delta_lf[i] = 0; |
| } |
| } |
| |
| // VP8 should ignore skip[] and ref_frame[] values. |
| expected_error = |
| IsVP9(codec.iface) ? VPX_CODEC_INVALID_PARAM : VPX_CODEC_OK; |
| for (const auto skip : { -2, 2, INT_MIN, INT_MAX }) { |
| for (int i = 0; i < 8; ++i) { |
| roi.skip[i] = skip; |
| EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), expected_error) |
| << "skip[" << i << "]: " << skip; |
| roi.skip[i] = 0; |
| } |
| } |
| |
| // VP9 allows negative values to be used to disable segmentation. |
| for (int ref_frame = -3; ref_frame < 0; ++ref_frame) { |
| for (int i = 0; i < 8; ++i) { |
| roi.ref_frame[i] = ref_frame; |
| EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), VPX_CODEC_OK) |
| << "ref_frame[" << i << "]: " << ref_frame; |
| roi.ref_frame[i] = 0; |
| } |
| } |
| |
| for (const auto ref_frame : { 4, INT_MIN, INT_MAX }) { |
| for (int i = 0; i < 8; ++i) { |
| roi.ref_frame[i] = ref_frame; |
| EXPECT_EQ(vpx_codec_control_(&enc, codec.ctrl_id, &roi), expected_error) |
| << "ref_frame[" << i << "]: " << ref_frame; |
| roi.ref_frame[i] = 0; |
| } |
| } |
| |
| EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK); |
| } |
| } |
| |
| TEST(EncodeAPI, ConfigChangeThreadCount) { |
| constexpr int kWidth = 1920; |
| constexpr int kHeight = 1080; |
| |
| for (const auto *iface : kCodecIfaces) { |
| SCOPED_TRACE(vpx_codec_iface_name(iface)); |
| for (int i = 0; i < (IsVP9(iface) ? 2 : 1); ++i) { |
| 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(iface, &cfg, 0), VPX_CODEC_OK); |
| EXPECT_NO_FATAL_FAILURE( |
| InitCodec(*iface, kWidth, kHeight, &enc.ctx, &cfg)); |
| if (IsVP9(iface)) { |
| EXPECT_EQ(vpx_codec_control_(&enc.ctx, VP9E_SET_TILE_COLUMNS, 6), |
| VPX_CODEC_OK); |
| EXPECT_EQ(vpx_codec_control_(&enc.ctx, VP9E_SET_ROW_MT, i), |
| VPX_CODEC_OK); |
| } |
| |
| for (const auto threads : { 1, 4, 8, 6, 2, 1 }) { |
| cfg.g_threads = threads; |
| EXPECT_NO_FATAL_FAILURE(EncodeWithConfig(cfg, &enc.ctx)) |
| << "iteration: " << i << " threads: " << threads; |
| } |
| } |
| } |
| } |
| |
| TEST(EncodeAPI, ConfigResizeChangeThreadCount) { |
| constexpr int kInitWidth = 1024; |
| constexpr int kInitHeight = 1024; |
| |
| for (const auto *iface : kCodecIfaces) { |
| SCOPED_TRACE(vpx_codec_iface_name(iface)); |
| for (int i = 0; i < (IsVP9(iface) ? 2 : 1); ++i) { |
| 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(iface, &cfg, 0), VPX_CODEC_OK); |
| // Start in threaded mode to ensure resolution and thread related |
| // allocations are updated correctly across changes in resolution and |
| // thread counts. See https://crbug.com/1486441. |
| cfg.g_threads = 4; |
| EXPECT_NO_FATAL_FAILURE( |
| InitCodec(*iface, kInitWidth, kInitHeight, &enc.ctx, &cfg)); |
| if (IsVP9(iface)) { |
| EXPECT_EQ(vpx_codec_control_(&enc.ctx, VP9E_SET_TILE_COLUMNS, 6), |
| VPX_CODEC_OK); |
| EXPECT_EQ(vpx_codec_control_(&enc.ctx, VP9E_SET_ROW_MT, i), |
| VPX_CODEC_OK); |
| } |
| |
| cfg.g_w = 1000; |
| cfg.g_h = 608; |
| EXPECT_EQ(vpx_codec_enc_config_set(&enc.ctx, &cfg), VPX_CODEC_OK) |
| << vpx_codec_error_detail(&enc.ctx); |
| |
| cfg.g_w = 1000; |
| cfg.g_h = 720; |
| |
| for (const auto threads : { 1, 4, 8, 6, 2, 1 }) { |
| cfg.g_threads = threads; |
| EXPECT_NO_FATAL_FAILURE(EncodeWithConfig(cfg, &enc.ctx)) |
| << "iteration: " << i << " threads: " << threads; |
| } |
| } |
| } |
| } |
| |
| TEST(EncodeAPI, ConfigResizeBiggerAfterInit) { |
| for (const auto *iface : kCodecIfaces) { |
| SCOPED_TRACE(vpx_codec_iface_name(iface)); |
| vpx_codec_enc_cfg_t cfg; |
| vpx_codec_ctx_t enc; |
| |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK); |
| EXPECT_NO_FATAL_FAILURE(InitCodec(*iface, 1, 1, &enc, &cfg)); |
| |
| cfg.g_w = 1920; |
| cfg.g_h = 1; |
| EXPECT_EQ(vpx_codec_enc_config_set(&enc, &cfg), |
| IsVP9(iface) ? VPX_CODEC_OK : VPX_CODEC_INVALID_PARAM); |
| |
| EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK); |
| } |
| } |
| |
| TEST(EncodeAPI, ConfigResizeBiggerAfterEncode) { |
| for (const auto *iface : kCodecIfaces) { |
| SCOPED_TRACE(vpx_codec_iface_name(iface)); |
| vpx_codec_enc_cfg_t cfg; |
| vpx_codec_ctx_t enc; |
| |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK); |
| EXPECT_NO_FATAL_FAILURE(InitCodec(*iface, 1, 1, &enc, &cfg)); |
| EXPECT_NO_FATAL_FAILURE(EncodeWithConfig(cfg, &enc)); |
| |
| cfg.g_w = 1920; |
| cfg.g_h = 1; |
| EXPECT_EQ(vpx_codec_enc_config_set(&enc, &cfg), |
| IsVP9(iface) ? VPX_CODEC_OK : VPX_CODEC_INVALID_PARAM); |
| |
| cfg.g_w = 1920; |
| cfg.g_h = 1080; |
| EXPECT_EQ(vpx_codec_enc_config_set(&enc, &cfg), |
| IsVP9(iface) ? VPX_CODEC_OK : VPX_CODEC_INVALID_PARAM); |
| |
| EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK); |
| } |
| } |
| |
| #if CONFIG_VP9_ENCODER |
| // Frame size needed to trigger the overflow exceeds the max buffer allowed on |
| // 32-bit systems defined by VPX_MAX_ALLOCABLE_MEMORY |
| #if VPX_ARCH_X86_64 || VPX_ARCH_AARCH64 |
| TEST(EncodeAPI, ConfigLargeTargetBitrateVp9) { |
| constexpr int kWidth = 12383; |
| constexpr int kHeight = 8192; |
| constexpr auto *iface = &vpx_codec_vp9_cx_algo; |
| SCOPED_TRACE(vpx_codec_iface_name(iface)); |
| 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(iface, &cfg, 0), VPX_CODEC_OK); |
| // The following setting will cause avg_frame_bandwidth in rate control to be |
| // larger than INT_MAX |
| cfg.rc_target_bitrate = INT_MAX; |
| // Framerate 0.1 (equivalent to timebase 10) is the smallest framerate allowed |
| // by libvpx |
| cfg.g_timebase.den = 1; |
| cfg.g_timebase.num = 10; |
| EXPECT_NO_FATAL_FAILURE(InitCodec(*iface, kWidth, kHeight, &enc.ctx, &cfg)) |
| << "target bitrate: " << cfg.rc_target_bitrate << " framerate: " |
| << static_cast<double>(cfg.g_timebase.den) / cfg.g_timebase.num; |
| } |
| #endif // VPX_ARCH_X86_64 || VPX_ARCH_AARCH64 |
| |
| // Emulates the WebCodecs VideoEncoder interface. |
| class VP9Encoder { |
| public: |
| explicit VP9Encoder(int speed) |
| : speed_(speed), row_mt_(0), bit_depth_(VPX_BITS_8), |
| fmt_(VPX_IMG_FMT_I420) {} |
| // The image format `fmt` must not have the VPX_IMG_FMT_HIGHBITDEPTH bit set. |
| // If bit_depth > 8, we will set the VPX_IMG_FMT_HIGHBITDEPTH bit before |
| // passing the image format to vpx_img_alloc(). |
| VP9Encoder(int speed, unsigned int row_mt, vpx_bit_depth_t bit_depth, |
| vpx_img_fmt_t fmt) |
| : speed_(speed), row_mt_(row_mt), bit_depth_(bit_depth), fmt_(fmt) {} |
| ~VP9Encoder(); |
| |
| void Configure(unsigned int threads, unsigned int width, unsigned int height, |
| vpx_rc_mode end_usage, unsigned long deadline); |
| void Encode(bool key_frame); |
| |
| private: |
| const int speed_; |
| const unsigned int row_mt_; |
| const vpx_bit_depth_t bit_depth_; |
| const vpx_img_fmt_t fmt_; |
| bool initialized_ = false; |
| vpx_codec_enc_cfg_t cfg_; |
| vpx_codec_ctx_t enc_; |
| int frame_index_ = 0; |
| unsigned long deadline_ = 0; |
| }; |
| |
| VP9Encoder::~VP9Encoder() { |
| if (initialized_) { |
| EXPECT_EQ(vpx_codec_destroy(&enc_), VPX_CODEC_OK); |
| } |
| } |
| |
| void VP9Encoder::Configure(unsigned int threads, unsigned int width, |
| unsigned int height, vpx_rc_mode end_usage, |
| unsigned long deadline) { |
| deadline_ = deadline; |
| |
| if (!initialized_) { |
| ASSERT_EQ(fmt_ & VPX_IMG_FMT_HIGHBITDEPTH, 0); |
| const bool high_bit_depth = bit_depth_ > VPX_BITS_8; |
| const bool is_420 = fmt_ == VPX_IMG_FMT_I420; |
| vpx_codec_iface_t *const iface = vpx_codec_vp9_cx(); |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg_, /*usage=*/0), |
| VPX_CODEC_OK); |
| cfg_.g_threads = threads; |
| // In profiles 0 and 2, only 4:2:0 format is allowed. In profiles 1 and 3, |
| // all other subsampling formats are allowed. In profiles 0 and 1, only bit |
| // depth 8 is allowed. In profiles 2 and 3, only bit depths 10 and 12 are |
| // allowed. |
| cfg_.g_profile = 2 * high_bit_depth + !is_420; |
| cfg_.g_w = width; |
| cfg_.g_h = height; |
| cfg_.g_bit_depth = bit_depth_; |
| cfg_.g_input_bit_depth = bit_depth_; |
| cfg_.g_timebase.num = 1; |
| cfg_.g_timebase.den = 1000 * 1000; // microseconds |
| cfg_.g_pass = VPX_RC_ONE_PASS; |
| cfg_.g_lag_in_frames = 0; |
| cfg_.rc_end_usage = end_usage; |
| cfg_.rc_min_quantizer = 2; |
| cfg_.rc_max_quantizer = 58; |
| ASSERT_EQ( |
| vpx_codec_enc_init(&enc_, iface, &cfg_, |
| high_bit_depth ? VPX_CODEC_USE_HIGHBITDEPTH : 0), |
| VPX_CODEC_OK); |
| ASSERT_EQ(vpx_codec_control(&enc_, VP8E_SET_CPUUSED, speed_), VPX_CODEC_OK); |
| ASSERT_EQ(vpx_codec_control(&enc_, VP9E_SET_ROW_MT, row_mt_), VPX_CODEC_OK); |
| initialized_ = true; |
| return; |
| } |
| |
| cfg_.g_threads = threads; |
| cfg_.g_w = width; |
| cfg_.g_h = height; |
| cfg_.rc_end_usage = end_usage; |
| ASSERT_EQ(vpx_codec_enc_config_set(&enc_, &cfg_), VPX_CODEC_OK) |
| << vpx_codec_error_detail(&enc_); |
| } |
| |
| void VP9Encoder::Encode(bool key_frame) { |
| assert(initialized_); |
| const vpx_codec_cx_pkt_t *pkt; |
| vpx_image_t *image = CreateImage(bit_depth_, fmt_, cfg_.g_w, cfg_.g_h); |
| ASSERT_NE(image, nullptr); |
| const vpx_enc_frame_flags_t frame_flags = key_frame ? VPX_EFLAG_FORCE_KF : 0; |
| ASSERT_EQ( |
| vpx_codec_encode(&enc_, image, frame_index_, 1, frame_flags, deadline_), |
| VPX_CODEC_OK); |
| ++frame_index_; |
| vpx_codec_iter_t iter = nullptr; |
| while ((pkt = vpx_codec_get_cx_data(&enc_, &iter)) != nullptr) { |
| ASSERT_EQ(pkt->kind, VPX_CODEC_CX_FRAME_PKT); |
| } |
| vpx_img_free(image); |
| } |
| |
| // This is a test case from clusterfuzz. |
| TEST(EncodeAPI, PrevMiCheckNullptr) { |
| VP9Encoder encoder(0); |
| encoder.Configure(0, 1554, 644, VPX_VBR, VPX_DL_REALTIME); |
| |
| // First step: encode, without forcing KF. |
| encoder.Encode(false); |
| // Second step: change config |
| encoder.Configure(0, 1131, 644, VPX_CBR, VPX_DL_GOOD_QUALITY); |
| // Third step: encode, without forcing KF |
| encoder.Encode(false); |
| } |
| |
| // This is a test case from clusterfuzz: based on b/310477034. |
| // Encode a few frames with multiple change config calls |
| // with different frame sizes. |
| TEST(EncodeAPI, MultipleChangeConfigResize) { |
| VP9Encoder encoder(3); |
| |
| // Set initial config. |
| encoder.Configure(3, 41, 1, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode first frame. |
| encoder.Encode(true); |
| |
| // Change config. |
| encoder.Configure(16, 31, 1, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Change config again. |
| encoder.Configure(0, 17, 1, VPX_CBR, VPX_DL_REALTIME); |
| |
| // Encode 2nd frame with new config, set delta frame. |
| encoder.Encode(false); |
| |
| // Encode 3rd frame with same config, set delta frame. |
| encoder.Encode(false); |
| } |
| |
| // This is a test case from clusterfuzz: based on b/310663186. |
| // Encode set of frames while varying the deadline on the fly from |
| // good to realtime to best and back to realtime. |
| TEST(EncodeAPI, DynamicDeadlineChange) { |
| // Use realtime speed: 5 to 9. |
| VP9Encoder encoder(5); |
| |
| // Set initial config, in particular set deadline to GOOD mode. |
| encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 1st frame. |
| encoder.Encode(true); |
| |
| // Encode 2nd frame, delta frame. |
| encoder.Encode(false); |
| |
| // Change config: change deadline to REALTIME. |
| encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode 3rd frame with new config, set key frame. |
| encoder.Encode(true); |
| |
| // Encode 4th frame with same config, delta frame. |
| encoder.Encode(false); |
| |
| // Encode 5th frame with same config, key frame. |
| encoder.Encode(true); |
| |
| // Change config: change deadline to BEST. |
| encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_BEST_QUALITY); |
| |
| // Encode 6th frame with new config, set delta frame. |
| encoder.Encode(false); |
| |
| // Change config: change deadline to REALTIME. |
| encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode 7th frame with new config, set delta frame. |
| encoder.Encode(false); |
| |
| // Encode 8th frame with new config, set key frame. |
| encoder.Encode(true); |
| |
| // Encode 9th frame with new config, set delta frame. |
| encoder.Encode(false); |
| } |
| |
| TEST(EncodeAPI, Buganizer310340241) { |
| VP9Encoder encoder(-6); |
| |
| // Set initial config, in particular set deadline to GOOD mode. |
| encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 1st frame. |
| encoder.Encode(true); |
| |
| // Encode 2nd frame, delta frame. |
| encoder.Encode(false); |
| |
| // Change config: change deadline to REALTIME. |
| encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode 3rd frame with new config, set key frame. |
| encoder.Encode(true); |
| } |
| |
| // This is a test case from clusterfuzz: based on b/312517065. |
| TEST(EncodeAPI, Buganizer312517065) { |
| VP9Encoder encoder(4); |
| encoder.Configure(0, 1060, 437, VPX_CBR, VPX_DL_REALTIME); |
| encoder.Encode(true); |
| encoder.Configure(10, 33, 437, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| encoder.Encode(false); |
| encoder.Configure(6, 327, 269, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| encoder.Configure(15, 1060, 437, VPX_CBR, VPX_DL_REALTIME); |
| encoder.Encode(false); |
| } |
| |
| // This is a test case from clusterfuzz: based on b/311489136. |
| // Encode a few frames with multiple change config calls |
| // with different frame sizes. |
| TEST(EncodeAPI, Buganizer311489136) { |
| VP9Encoder encoder(1); |
| |
| // Set initial config. |
| encoder.Configure(12, 1678, 620, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode first frame. |
| encoder.Encode(true); |
| |
| // Change config. |
| encoder.Configure(3, 1678, 202, VPX_CBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 2nd frame with new config, set delta frame. |
| encoder.Encode(false); |
| |
| // Change config again. |
| encoder.Configure(8, 1037, 476, VPX_CBR, VPX_DL_REALTIME); |
| |
| // Encode 3rd frame with new config, set delta frame. |
| encoder.Encode(false); |
| |
| // Change config again. |
| encoder.Configure(0, 580, 620, VPX_CBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 4th frame with same config, set delta frame. |
| encoder.Encode(false); |
| } |
| |
| // This is a test case from clusterfuzz: based on b/312656387. |
| // Encode a few frames with multiple change config calls |
| // with different frame sizes. |
| TEST(EncodeAPI, Buganizer312656387) { |
| VP9Encoder encoder(1); |
| |
| // Set initial config. |
| encoder.Configure(16, 1, 1024, VPX_CBR, VPX_DL_REALTIME); |
| |
| // Change config. |
| encoder.Configure(15, 1, 1024, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode first frame. |
| encoder.Encode(true); |
| |
| // Change config again. |
| encoder.Configure(14, 1, 595, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 2nd frame with new config. |
| encoder.Encode(true); |
| |
| // Change config again. |
| encoder.Configure(2, 1, 1024, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 3rd frame with new config, set delta frame. |
| encoder.Encode(false); |
| } |
| |
| // This is a test case from clusterfuzz: based on b/310329177. |
| // Encode a few frames with multiple change config calls |
| // with different frame sizes. |
| TEST(EncodeAPI, Buganizer310329177) { |
| VP9Encoder encoder(6); |
| |
| // Set initial config. |
| encoder.Configure(10, 41, 1, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode first frame. |
| encoder.Encode(true); |
| |
| // Change config. |
| encoder.Configure(16, 1, 1, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode 2nd frame with new config, set delta frame. |
| encoder.Encode(false); |
| } |
| |
| // This is a test case from clusterfuzz: based on b/311394513. |
| // Encode a few frames with multiple change config calls |
| // with different frame sizes. |
| TEST(EncodeAPI, Buganizer311394513) { |
| VP9Encoder encoder(-7); |
| |
| // Set initial config. |
| encoder.Configure(0, 5, 9, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode first frame. |
| encoder.Encode(false); |
| |
| // Change config. |
| encoder.Configure(5, 2, 1, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode 2nd frame with new config. |
| encoder.Encode(true); |
| } |
| |
| TEST(EncodeAPI, Buganizer311985118) { |
| VP9Encoder encoder(0); |
| |
| // Set initial config, in particular set deadline to GOOD mode. |
| encoder.Configure(12, 1678, 620, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 1st frame. |
| encoder.Encode(false); |
| |
| // Change config: change threads and width. |
| encoder.Configure(0, 1574, 620, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Change config: change threads, width and height. |
| encoder.Configure(16, 837, 432, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 2nd frame. |
| encoder.Encode(false); |
| } |
| |
| // This is a test case from clusterfuzz: based on b/314857577. |
| // Encode a few frames with multiple change config calls |
| // with different frame sizes. |
| TEST(EncodeAPI, Buganizer314857577) { |
| VP9Encoder encoder(4); |
| |
| // Set initial config. |
| encoder.Configure(12, 1060, 437, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode first frame. |
| encoder.Encode(false); |
| |
| // Change config. |
| encoder.Configure(16, 1060, 1, VPX_CBR, VPX_DL_REALTIME); |
| |
| // Encode 2nd frame with new config. |
| encoder.Encode(false); |
| |
| // Encode 3rd frame with new config. |
| encoder.Encode(true); |
| |
| // Change config. |
| encoder.Configure(15, 33, 437, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 4th frame with new config. |
| encoder.Encode(true); |
| |
| // Encode 5th frame with new config. |
| encoder.Encode(false); |
| |
| // Change config. |
| encoder.Configure(5, 327, 269, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Change config. |
| encoder.Configure(15, 1060, 437, VPX_CBR, VPX_DL_REALTIME); |
| |
| // Encode 6th frame with new config. |
| encoder.Encode(false); |
| |
| // Encode 7th frame with new config. |
| encoder.Encode(false); |
| |
| // Change config. |
| encoder.Configure(4, 1060, 437, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode 8th frame with new config. |
| encoder.Encode(false); |
| } |
| |
| TEST(EncodeAPI, Buganizer312875957PredBufferStride) { |
| VP9Encoder encoder(-1); |
| |
| encoder.Configure(12, 1678, 620, VPX_VBR, VPX_DL_REALTIME); |
| encoder.Encode(true); |
| encoder.Encode(false); |
| encoder.Configure(0, 456, 486, VPX_VBR, VPX_DL_REALTIME); |
| encoder.Encode(true); |
| encoder.Configure(0, 1678, 620, VPX_CBR, 1000000); |
| encoder.Encode(false); |
| encoder.Encode(false); |
| } |
| |
| // This is a test case from clusterfuzz: based on b/311294795 |
| // Encode a few frames with multiple change config calls |
| // with different frame sizes. |
| TEST(EncodeAPI, Buganizer311294795) { |
| VP9Encoder encoder(1); |
| |
| // Set initial config. |
| encoder.Configure(12, 1678, 620, VPX_VBR, VPX_DL_REALTIME); |
| |
| // Encode first frame. |
| encoder.Encode(false); |
| |
| // Change config. |
| encoder.Configure(16, 632, 620, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 2nd frame with new config |
| encoder.Encode(true); |
| |
| // Change config. |
| encoder.Configure(16, 1678, 342, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| |
| // Encode 3rd frame with new config. |
| encoder.Encode(false); |
| |
| // Change config. |
| encoder.Configure(0, 1574, 618, VPX_VBR, VPX_DL_REALTIME); |
| // Encode more frames with new config. |
| encoder.Encode(false); |
| encoder.Encode(false); |
| } |
| |
| TEST(EncodeAPI, Buganizer317105128) { |
| VP9Encoder encoder(-9); |
| encoder.Configure(0, 1, 1, VPX_CBR, VPX_DL_GOOD_QUALITY); |
| encoder.Configure(16, 1920, 1, VPX_CBR, VPX_DL_REALTIME); |
| } |
| |
| TEST(EncodeAPI, Buganizer319964497) { |
| VP9Encoder encoder(7); |
| encoder.Configure(/*threads=*/1, /*width=*/320, /*height=*/240, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/1, /*width=*/1, /*height=*/1, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/1, /*width=*/2, /*height=*/2, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| } |
| |
| TEST(EncodeAPI, Buganizer329088759RowMT0) { |
| VP9Encoder encoder(8, 0, VPX_BITS_8, VPX_IMG_FMT_I444); |
| encoder.Configure(/*threads=*/8, /*width=*/1686, /*height=*/398, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/0, /*width=*/1686, /*height=*/1, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/0, /*width=*/1482, /*height=*/113, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/0, /*width=*/881, /*height=*/59, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Configure(/*threads=*/13, /*width=*/1271, /*height=*/385, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/2, /*width=*/1, /*height=*/62, VPX_VBR, |
| VPX_DL_REALTIME); |
| } |
| |
| TEST(EncodeAPI, Buganizer329088759RowMT1) { |
| VP9Encoder encoder(8, 1, VPX_BITS_8, VPX_IMG_FMT_I444); |
| encoder.Configure(/*threads=*/8, /*width=*/1686, /*height=*/398, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Encode(/*key_frame=*/false); |
| // Needs to set threads to non-zero to repro the issue. |
| encoder.Configure(/*threads=*/2, /*width=*/1686, /*height=*/1, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/2, /*width=*/1482, /*height=*/113, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/2, /*width=*/881, /*height=*/59, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Configure(/*threads=*/13, /*width=*/1271, /*height=*/385, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/2, /*width=*/1, /*height=*/62, VPX_VBR, |
| VPX_DL_REALTIME); |
| } |
| |
| TEST(EncodeAPI, Buganizer331086799) { |
| VP9Encoder encoder(6, 1, VPX_BITS_8, VPX_IMG_FMT_I420); |
| encoder.Configure(0, 1385, 1, VPX_CBR, VPX_DL_REALTIME); |
| encoder.Configure(0, 1, 1, VPX_VBR, VPX_DL_REALTIME); |
| encoder.Encode(false); |
| encoder.Configure(16, 1385, 1, VPX_VBR, VPX_DL_GOOD_QUALITY); |
| encoder.Encode(false); |
| encoder.Encode(false); |
| encoder.Configure(0, 1, 1, VPX_CBR, VPX_DL_REALTIME); |
| encoder.Encode(true); |
| } |
| |
| TEST(EncodeAPI, Buganizer331108729) { |
| VP9Encoder encoder(1, 1, VPX_BITS_8, VPX_IMG_FMT_I422); |
| encoder.Configure(0, 1919, 260, VPX_VBR, VPX_DL_REALTIME); |
| encoder.Configure(9, 440, 1, VPX_CBR, VPX_DL_GOOD_QUALITY); |
| encoder.Encode(true); |
| encoder.Configure(8, 1919, 260, VPX_VBR, VPX_DL_REALTIME); |
| encoder.Encode(false); |
| } |
| |
| TEST(EncodeAPI, Buganizer331108922BitDepth8) { |
| VP9Encoder encoder(9, 1, VPX_BITS_8, VPX_IMG_FMT_I420); |
| encoder.Configure(/*threads=*/1, /*width=*/1, /*height=*/1080, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/0, /*width=*/1, /*height=*/1080, VPX_CBR, |
| VPX_DL_GOOD_QUALITY); |
| encoder.Configure(/*threads=*/16, /*width=*/1, /*height=*/394, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/16, /*width=*/1, /*height=*/798, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| } |
| |
| #if CONFIG_VP9_HIGHBITDEPTH |
| TEST(EncodeAPI, Buganizer329674887RowMT0BitDepth12) { |
| VP9Encoder encoder(8, 0, VPX_BITS_12, VPX_IMG_FMT_I444); |
| encoder.Configure(/*threads=*/2, /*width=*/1030, /*height=*/583, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/0, /*width=*/1030, /*height=*/1, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/0, /*width=*/548, /*height=*/322, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/16, /*width=*/24, /*height=*/583, VPX_CBR, |
| VPX_DL_GOOD_QUALITY); |
| } |
| |
| TEST(EncodeAPI, Buganizer329179808RowMT0BitDepth10) { |
| VP9Encoder encoder(4, 0, VPX_BITS_10, VPX_IMG_FMT_I444); |
| encoder.Configure(/*threads=*/16, /*width=*/1488, /*height=*/5, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/16, /*width=*/839, /*height=*/1, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/11, /*width=*/657, /*height=*/5, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| } |
| |
| TEST(EncodeAPI, Buganizer329179808RowMT1BitDepth10) { |
| VP9Encoder encoder(4, 1, VPX_BITS_10, VPX_IMG_FMT_I444); |
| encoder.Configure(/*threads=*/16, /*width=*/1488, /*height=*/5, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/16, /*width=*/839, /*height=*/1, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/11, /*width=*/657, /*height=*/5, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| } |
| |
| TEST(EncodeAPI, Buganizer331108922BitDepth12) { |
| VP9Encoder encoder(9, 1, VPX_BITS_12, VPX_IMG_FMT_I444); |
| encoder.Configure(/*threads=*/1, /*width=*/1, /*height=*/1080, VPX_VBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Configure(/*threads=*/0, /*width=*/1, /*height=*/1080, VPX_CBR, |
| VPX_DL_GOOD_QUALITY); |
| encoder.Configure(/*threads=*/16, /*width=*/1, /*height=*/394, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| encoder.Encode(/*key_frame=*/true); |
| encoder.Configure(/*threads=*/16, /*width=*/1, /*height=*/798, VPX_CBR, |
| VPX_DL_REALTIME); |
| encoder.Encode(/*key_frame=*/false); |
| } |
| #endif |
| |
| 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; |
| } |
| } |
| } |
| } |
| } |
| |
| TEST(EncodeAPI, AomediaIssue3509VbrMinSection2PercentVP9) { |
| // Initialize libvpx encoder. |
| vpx_codec_iface_t *const iface = vpx_codec_vp9_cx(); |
| vpx_codec_ctx_t enc; |
| vpx_codec_enc_cfg_t cfg; |
| |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK); |
| |
| cfg.g_w = 1920; |
| cfg.g_h = 1080; |
| cfg.g_lag_in_frames = 0; |
| cfg.rc_target_bitrate = 1000000; |
| // Set this to more than 1 percent to cause a signed integer overflow in the |
| // multiplication rc->avg_frame_bandwidth * oxcf->rc_cfg.vbrmin_section in |
| // vp9_rc_update_framerate() if the multiplication is done in the `int` type. |
| cfg.rc_2pass_vbr_minsection_pct = 2; |
| |
| ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK); |
| |
| // Create input image. |
| vpx_image_t *const image = |
| CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h); |
| ASSERT_NE(image, nullptr); |
| |
| // Encode frame. |
| // `duration` can go as high as 300, but the UBSan error is gone if |
| // `duration` is 301 or higher. |
| ASSERT_EQ( |
| vpx_codec_encode(&enc, image, 0, /*duration=*/300, 0, VPX_DL_REALTIME), |
| VPX_CODEC_OK); |
| |
| // Free resources. |
| vpx_img_free(image); |
| ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK); |
| } |
| |
| TEST(EncodeAPI, AomediaIssue3509VbrMinSection101PercentVP9) { |
| // Initialize libvpx encoder. |
| vpx_codec_iface_t *const iface = vpx_codec_vp9_cx(); |
| vpx_codec_ctx_t enc; |
| vpx_codec_enc_cfg_t cfg; |
| |
| ASSERT_EQ(vpx_codec_enc_config_default(iface, &cfg, 0), VPX_CODEC_OK); |
| |
| cfg.g_w = 1920; |
| cfg.g_h = 1080; |
| cfg.g_lag_in_frames = 0; |
| cfg.rc_target_bitrate = 1000000; |
| // Set this to more than 100 percent to cause an error when vbr_min_bits is |
| // cast to `int` in vp9_rc_update_framerate() if vbr_min_bits is not clamped |
| // to INT_MAX. |
| cfg.rc_2pass_vbr_minsection_pct = 101; |
| |
| ASSERT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0), VPX_CODEC_OK); |
| |
| // Create input image. |
| vpx_image_t *const image = |
| CreateImage(VPX_BITS_8, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h); |
| ASSERT_NE(image, nullptr); |
| |
| // Encode frame. |
| // `duration` can go as high as 300, but the UBSan error is gone if |
| // `duration` is 301 or higher. |
| ASSERT_EQ( |
| vpx_codec_encode(&enc, image, 0, /*duration=*/300, 0, VPX_DL_REALTIME), |
| VPX_CODEC_OK); |
| |
| // Free resources. |
| vpx_img_free(image); |
| ASSERT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK); |
| } |
| |
| #endif // CONFIG_VP9_ENCODER |
| |
| } // namespace |