Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion examples/simple_decoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,15 @@
// This example does not special case any error return codes. If there was
// an error, a descriptive message is printed and the program exits. With
// few exceptions, vpx_codec functions return an enumerated error status,
// with the value `0` indicating success.
// with the value `0` indicating success. VP8 may return success for a
// corrupted frame, so the VP8D_GET_FRAME_CORRUPTED control is checked after
// every decode.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "vpx/vp8dx.h"
#include "vpx/vpx_decoder.h"

#include "../tools_common.h"
Expand Down Expand Up @@ -124,10 +127,14 @@ int main(int argc, char **argv) {
vpx_codec_iter_t iter = NULL;
vpx_image_t *img = NULL;
size_t frame_size = 0;
int corrupted = 0;
const unsigned char *frame =
vpx_video_reader_get_frame(reader, &frame_size);
if (vpx_codec_decode(&codec, frame, (unsigned int)frame_size, NULL, 0))
die_codec(&codec, "Failed to decode frame.");
if (vpx_codec_control(&codec, VP8D_GET_FRAME_CORRUPTED, &corrupted))
die_codec(&codec, "Failed to get frame corrupted state.");
if (corrupted) die("Frame %d is corrupted.", frame_cnt);

while ((img = vpx_codec_get_frame(&codec, &iter)) != NULL) {
vpx_img_write(img, outfile);
Expand Down
69 changes: 69 additions & 0 deletions test/encode_api_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,54 @@ TEST(EncodeAPI, SetReferenceSizeValidationVp8) {
EXPECT_EQ(vpx_codec_destroy(&enc), VPX_CODEC_OK);
}

// Bug: 564326761.
TEST(EncodeAPI, ValidateConfigVp8) {
vpx_codec_iface_t *const iface = vpx_codec_vp8_cx();
vpx_codec_enc_cfg_t default_cfg;
ASSERT_EQ(vpx_codec_enc_config_default(iface, &default_cfg, 0), VPX_CODEC_OK);

const vpx_rational_t dsf = { 1, 1 };
vpx_codec_ctx_t enc = {};

// Invalid ss_number_layers (0 and > VPX_SS_MAX_LAYERS).
for (const unsigned int layers : { 0U, VPX_SS_MAX_LAYERS + 1U }) {
vpx_codec_enc_cfg_t cfg = default_cfg;
cfg.ss_number_layers = layers;
EXPECT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0),
VPX_CODEC_INVALID_PARAM);
EXPECT_EQ(vpx_codec_enc_init_multi(&enc, iface, &cfg, 1, 0, &dsf),
VPX_CODEC_INVALID_PARAM);
}

// Invalid boost factor denominators (0).
{
vpx_codec_enc_cfg_t cfg = default_cfg;
cfg.kf_frame_max_boost_first_factor.den = 0;
EXPECT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0),
VPX_CODEC_INVALID_PARAM);
}
{
vpx_codec_enc_cfg_t cfg = default_cfg;
cfg.kf_max_total_boost_factor.den = 0;
EXPECT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0),
VPX_CODEC_INVALID_PARAM);
}

// Ensure scalar range checks run before dereferencing rc_twopass_stats_in.buf
// when g_pass == VPX_RC_LAST_PASS.
{
uint8_t dummy_buf[1] = { 0 };
constexpr size_t kDummyStatsSize = 52992;
vpx_codec_enc_cfg_t cfg = default_cfg;
cfg.g_pass = VPX_RC_LAST_PASS;
cfg.ss_number_layers = VPX_SS_MAX_LAYERS + 1;
cfg.rc_twopass_stats_in.buf = dummy_buf;
cfg.rc_twopass_stats_in.sz = kDummyStatsSize;
EXPECT_EQ(vpx_codec_enc_init_multi(&enc, iface, &cfg, 1, 0, &dsf),
VPX_CODEC_INVALID_PARAM);
}
}

// Emulates the WebCodecs VideoEncoder interface.
class VP8Encoder {
public:
Expand Down Expand Up @@ -1111,6 +1159,27 @@ TEST(EncodeAPI, Vp8SSIMMultipleFrames) {
}
#endif // CONFIG_VP8_ENCODER

#if CONFIG_VP9_ENCODER
// Ports the VP8 fix for Bug 564326761 to VP9: validate
// kf_frame_max_boost_first_factor.den before use as a divisor.
TEST(EncodeAPI, ValidateConfigVp9) {
vpx_codec_iface_t *const iface = vpx_codec_vp9_cx();
vpx_codec_enc_cfg_t default_cfg;
ASSERT_EQ(vpx_codec_enc_config_default(iface, &default_cfg, 0), VPX_CODEC_OK);

vpx_codec_ctx_t enc = {};

// Invalid boost factor denominator (0).
{
vpx_codec_enc_cfg_t cfg = default_cfg;
cfg.kf_frame_max_boost_first_factor.den = 0;
EXPECT_EQ(vpx_codec_enc_init(&enc, iface, &cfg, 0),
VPX_CODEC_INVALID_PARAM);
}
}

#endif // CONFIG_VP9_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
Expand Down
144 changes: 144 additions & 0 deletions test/mfqe_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2026 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 <array>
#include <cstring>
#include <memory>
#include <new>

#include "gtest/gtest.h"
#include "vpx_config.h"
#include "vp9/common/vp9_mfqe.h"
#include "vp9/common/vp9_onyxc_int.h"
#include "vpx_scale/yv12config.h"

namespace {

constexpr int kFrameSize = 64;
constexpr int kCheckerboardSquareSize = 4;

class TmpFrameBuffer {
public:
// Returns an initialized frame, or nullptr if allocation fails.
static std::unique_ptr<TmpFrameBuffer> Create(int width, int height,
int stride,
bool fill_checkerboard);

TmpFrameBuffer(const TmpFrameBuffer &) = delete;
TmpFrameBuffer &operator=(const TmpFrameBuffer &) = delete;
~TmpFrameBuffer() { vpx_free_frame_buffer(&buffer_); }

// Returns a borrowed reference valid for this object's lifetime.
YV12_BUFFER_CONFIG &buffer() { return buffer_; }

private:
// Takes ownership of buffer's allocation.
explicit TmpFrameBuffer(const YV12_BUFFER_CONFIG &buffer) : buffer_(buffer) {}

YV12_BUFFER_CONFIG buffer_ = {};
};

std::unique_ptr<TmpFrameBuffer> TmpFrameBuffer::Create(int width, int height,
int stride,
bool fill_checkerboard) {
YV12_BUFFER_CONFIG buffer = {};
#if CONFIG_VP9_HIGHBITDEPTH
const int allocation_status =
vpx_alloc_frame_buffer(&buffer, stride, height, 1, 1, 0, 0, 0);
#else
const int allocation_status =
vpx_alloc_frame_buffer(&buffer, stride, height, 1, 1, 0, 0);
#endif
if (allocation_status != 0) return nullptr;

buffer.y_width = width;
buffer.y_height = height;
buffer.y_crop_width = width;
buffer.y_crop_height = height;
buffer.uv_width = width >> 1;
buffer.uv_height = height >> 1;
buffer.uv_crop_width = width >> 1;
buffer.uv_crop_height = height >> 1;
buffer.render_width = width;
buffer.render_height = height;

if (fill_checkerboard) {
// Fill the luma plane with a checkerboard pattern.
for (int row = 0; row < height; ++row) {
for (int col = 0; col < width; ++col) {
const int checkerboard_row = row / kCheckerboardSquareSize;
const int checkerboard_col = col / kCheckerboardSquareSize;
int pixel_value = 0x20;
if ((checkerboard_row + checkerboard_col) % 2 != 0) {
pixel_value = 0xe0;
}
buffer.y_buffer[row * buffer.y_stride + col] = pixel_value;
}
}
} else {
// Use a distinct initial luma value so copying is observable.
for (int row = 0; row < height; ++row) {
memset(buffer.y_buffer + row * buffer.y_stride, 0, width);
}
}

// Use neutral chroma so the checkerboard pattern is luma-only.
for (int row = 0; row < height / 2; ++row) {
memset(buffer.u_buffer + row * buffer.uv_stride, 0x80, width / 2);
memset(buffer.v_buffer + row * buffer.uv_stride, 0x80, width / 2);
}
std::unique_ptr<TmpFrameBuffer> frame(new (std::nothrow)
TmpFrameBuffer(buffer));
if (frame == nullptr) {
vpx_free_frame_buffer(&buffer);
}
return frame;
}

TEST(MfqeTest, Copy64x64WithDifferentStrides) {
auto source = TmpFrameBuffer::Create(kFrameSize, kFrameSize, kFrameSize,
/*fill_checkerboard=*/true);
ASSERT_NE(source, nullptr);
// The wider allocation produces a different stride while preserving the
// visible dimensions of the image.
auto destination =
TmpFrameBuffer::Create(kFrameSize, kFrameSize, kFrameSize + 16,
/*fill_checkerboard=*/false);
ASSERT_NE(destination, nullptr);
ASSERT_NE(source->buffer().y_stride, destination->buffer().y_stride);

// One MODE_INFO entry per 8x8 MI unit in this 64x64 superblock.
std::array<MODE_INFO, MI_BLOCK_SIZE * MI_BLOCK_SIZE> mi = {};
mi[0].sb_type = BLOCK_64X64;
mi[0].mode = DC_PRED; // Force the copy path rather than MFQE filtering.

VP9_COMMON cm = {};
cm.frame_type = INTER_FRAME;
cm.frame_to_show = &source->buffer();
cm.post_proc_buffer = destination->buffer();
cm.mi_rows = MI_BLOCK_SIZE;
cm.mi_cols = MI_BLOCK_SIZE;
cm.mi_stride = MI_BLOCK_SIZE;
cm.mi = mi.data();

vp9_mfqe(&cm);

for (int row = 0; row < kFrameSize; ++row) {
EXPECT_EQ(
std::memcmp(
source->buffer().y_buffer + row * source->buffer().y_stride,
cm.post_proc_buffer.y_buffer + row * cm.post_proc_buffer.y_stride,
kFrameSize),
0)
<< "row " << row;
}
}

} // namespace
3 changes: 3 additions & 0 deletions test/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += vp9_tokenize_test.cc
LIBVPX_TEST_SRCS-yes += vp9_intrapred_test.cc
LIBVPX_TEST_SRCS-$(CONFIG_VP9_DECODER) += vp9_decrypt_test.cc
LIBVPX_TEST_SRCS-$(CONFIG_VP9_DECODER) += vp9_thread_test.cc
ifeq ($(CONFIG_VP9_DECODER)$(CONFIG_VP9_POSTPROC),yesyes)
LIBVPX_TEST_SRCS-yes += mfqe_test.cc
endif
LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += avg_test.cc
LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += comp_avg_pred_test.cc
LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += dct16x16_test.cc
Expand Down
52 changes: 29 additions & 23 deletions vp8/vp8_cx_iface.c
Original file line number Diff line number Diff line change
Expand Up @@ -222,29 +222,7 @@ static vpx_codec_err_t validate_config(vpx_codec_alg_priv_t *ctx,
RANGE_CHECK(vp8_cfg, cq_level, cfg->rc_min_quantizer,
cfg->rc_max_quantizer);

#if !(CONFIG_REALTIME_ONLY)
if (cfg->g_pass == VPX_RC_LAST_PASS) {
size_t packet_sz = sizeof(FIRSTPASS_STATS);
int n_packets = (int)(cfg->rc_twopass_stats_in.sz / packet_sz);
FIRSTPASS_STATS *stats;

if (!cfg->rc_twopass_stats_in.buf)
ERROR("rc_twopass_stats_in.buf not set.");

if (cfg->rc_twopass_stats_in.sz % packet_sz)
ERROR("rc_twopass_stats_in.sz indicates truncated packet.");

if (cfg->rc_twopass_stats_in.sz < 2 * packet_sz)
ERROR("rc_twopass_stats_in requires at least two packets.");

stats = (void *)((char *)cfg->rc_twopass_stats_in.buf +
(n_packets - 1) * packet_sz);

if ((int)(stats->count + 0.5) != n_packets - 1)
ERROR("rc_twopass_stats_in missing EOS stats packet");
}
#endif

RANGE_CHECK(cfg, ss_number_layers, 1, VPX_SS_MAX_LAYERS);
RANGE_CHECK(cfg, ts_number_layers, 1, 5);

if (cfg->ts_number_layers > 1) {
Expand Down Expand Up @@ -281,6 +259,7 @@ static vpx_codec_err_t validate_config(vpx_codec_alg_priv_t *ctx,
RANGE_CHECK(cfg, sr_diff_factor.den, 1, 1000);
RANGE_CHECK(cfg, kf_err_per_mb_factor.den, 1, 1000);
RANGE_CHECK(cfg, kf_frame_min_boost_factor.den, 1, 1000);
RANGE_CHECK(cfg, kf_frame_max_boost_first_factor.den, 1, 1000);
RANGE_CHECK(cfg, kf_frame_max_boost_subs_factor.den, 1, 1000);
RANGE_CHECK(cfg, kf_max_total_boost_factor.den, 1, 1000);
RANGE_CHECK(cfg, gf_max_total_boost_factor.den, 1, 1000);
Expand All @@ -290,6 +269,33 @@ static vpx_codec_err_t validate_config(vpx_codec_alg_priv_t *ctx,
RANGE_CHECK(cfg, rd_mult_arf_qp_fac.den, 1, 1000);
RANGE_CHECK(cfg, rd_mult_key_qp_fac.den, 1, 1000);

#if !(CONFIG_REALTIME_ONLY)
if (cfg->g_pass == VPX_RC_LAST_PASS) {
const size_t packet_sz = sizeof(FIRSTPASS_STATS);
const int n_packets = (int)(cfg->rc_twopass_stats_in.sz / packet_sz);
const FIRSTPASS_STATS *stats;

if (!cfg->rc_twopass_stats_in.buf) {
ERROR("rc_twopass_stats_in.buf not set.");
}

if (cfg->rc_twopass_stats_in.sz % packet_sz) {
ERROR("rc_twopass_stats_in.sz indicates truncated packet.");
}

if (cfg->rc_twopass_stats_in.sz < 2 * packet_sz) {
ERROR("rc_twopass_stats_in requires at least two packets.");
}

stats =
(const FIRSTPASS_STATS *)cfg->rc_twopass_stats_in.buf + n_packets - 1;

if ((int)(stats->count + 0.5) != n_packets - 1) {
ERROR("rc_twopass_stats_in missing EOS stats packet");
}
}
#endif

return VPX_CODEC_OK;
}

Expand Down
4 changes: 2 additions & 2 deletions vp9/common/vp9_mfqe.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,10 @@ static void copy_mem64x64(const uint8_t *src, int src_stride, uint8_t *dst,
int dst_stride) {
copy_mem32x32(src, src_stride, dst, dst_stride);
copy_mem32x32(src + 32, src_stride, dst + 32, dst_stride);
copy_mem32x32(src + src_stride * 32, src_stride, dst + src_stride * 32,
copy_mem32x32(src + src_stride * 32, src_stride, dst + dst_stride * 32,
dst_stride);
copy_mem32x32(src + src_stride * 32 + 32, src_stride,
dst + src_stride * 32 + 32, dst_stride);
dst + dst_stride * 32 + 32, dst_stride);
}

static void copy_block(const uint8_t *y, const uint8_t *u, const uint8_t *v,
Expand Down
2 changes: 1 addition & 1 deletion vp9/encoder/vp9_rdopt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,7 @@ static int64_t rd_pick_intra4x4block(VP9_COMP *cpi, MACROBLOCK *x, int row,
const int src_stride = p->src.stride;
const int dst_stride = pd->dst.stride;
const uint8_t *src_init = &p->src.buf[row * 4 * src_stride + col * 4];
uint8_t *dst_init = &pd->dst.buf[row * 4 * src_stride + col * 4];
uint8_t *dst_init = &pd->dst.buf[row * 4 * dst_stride + col * 4];
ENTROPY_CONTEXT ta[2], tempa[2];
ENTROPY_CONTEXT tl[2], templ[2];
const int num_4x4_blocks_wide = num_4x4_blocks_wide_lookup[bsize];
Expand Down
Loading
Loading