Merge tag 'v3.15.1' into main branch Release v3.15.1 2026-09-21 v3.15.1 This release is ABI compatible with the last release. See https://aomedia.googlesource.com/aom/+log/v3.15.0..v3.15.1 for all the commits in this release. - Bug Fixes * b:558679217: Tune VMAF mode regression Bug: 564595707 Change-Id: I5158b893f5112f7888844b51ccd0a6f10dfb5fa6
diff --git a/aom/aomcx.h b/aom/aomcx.h index d1aa4ae..8b2d854 100644 --- a/aom/aomcx.h +++ b/aom/aomcx.h
@@ -1777,8 +1777,18 @@ typedef enum { AOM_TUNE_PSNR = 0, AOM_TUNE_SSIM = 1, - /* NOTE: enums 2 and 3 unused */ + /* NOTE: enums 2, 3 unused */ + /** Tune for Vmaf with preprocessing + * + * \deprecated This value is unsupported and will be removed in a future + * release. + */ AOM_TUNE_VMAF_WITH_PREPROCESSING = 4, + /** Tune for Vmaf without preprocessing + * + * \deprecated This value is unsupported and will be removed in a future + * release. + */ AOM_TUNE_VMAF_WITHOUT_PREPROCESSING = 5, AOM_TUNE_VMAF_MAX_GAIN = 6, AOM_TUNE_VMAF_NEG_MAX_GAIN = 7,
diff --git a/aom_dsp/aom_convolve.c b/aom_dsp/aom_convolve.c index 99594de..65adfb8 100644 --- a/aom_dsp/aom_convolve.c +++ b/aom_dsp/aom_convolve.c
@@ -148,7 +148,7 @@ void aom_convolve_copy_c(const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst, ptrdiff_t dst_stride, int w, int h) { for (int r = h; r > 0; --r) { - memmove(dst, src, w); + memcpy(dst, src, w); src += src_stride; dst += dst_stride; } @@ -245,7 +245,7 @@ uint16_t *dst, ptrdiff_t dst_stride, int w, int h) { for (int y = 0; y < h; ++y) { - memmove(dst, src, w * sizeof(src[0])); + memcpy(dst, src, w * sizeof(src[0])); src += src_stride; dst += dst_stride; }
diff --git a/aom_dsp/aom_dsp_rtcd_defs.pl b/aom_dsp/aom_dsp_rtcd_defs.pl index 5c274aa..a570954 100755 --- a/aom_dsp/aom_dsp_rtcd_defs.pl +++ b/aom_dsp/aom_dsp_rtcd_defs.pl
@@ -1746,7 +1746,7 @@ add_proto qw/double aom_compute_correlation/, "const unsigned char *frame1, int stride1, int x1, int y1, double mean1, double one_over_stddev1, const unsigned char *frame2, int stride2, int x2, int y2, double mean2, double one_over_stddev2"; specialize qw/aom_compute_correlation sse4_1 avx2/; - add_proto qw/void aom_compute_flow_at_point/, "const uint8_t *src, const uint8_t *ref, int x, int y, int width, int height, int stride, double *u, double *v"; + add_proto qw/void aom_compute_flow_at_point/, "const uint8_t *src, int src_stride, const uint8_t *ref, int ref_stride, int x, int y, int width, int height, double *u, double *v"; specialize qw/aom_compute_flow_at_point sse4_1 avx2 neon sve/; }
diff --git a/aom_dsp/arm/aom_convolve_copy_neon.c b/aom_dsp/arm/aom_convolve_copy_neon.c index a60f37f..2cfb9f6 100644 --- a/aom_dsp/arm/aom_convolve_copy_neon.c +++ b/aom_dsp/arm/aom_convolve_copy_neon.c
@@ -59,11 +59,11 @@ int h) { if (w < 4) { // copy2 do { - memmove(dst, src, 2 * sizeof(*src)); + memcpy(dst, src, 2 * sizeof(*src)); src += src_stride; dst += dst_stride; - memmove(dst, src, 2 * sizeof(*src)); + memcpy(dst, src, 2 * sizeof(*src)); src += src_stride; dst += dst_stride; h -= 2;
diff --git a/aom_dsp/flow_estimation/arm/disflow_neon.c b/aom_dsp/flow_estimation/arm/disflow_neon.c index d6c0e0b..5d927cd 100644 --- a/aom_dsp/flow_estimation/arm/disflow_neon.c +++ b/aom_dsp/flow_estimation/arm/disflow_neon.c
@@ -24,9 +24,10 @@ // (x, y) in src and the other at (x + u, y + v) in ref. // This function returns the sum of squared pixel differences between // the two regions. -static inline void compute_flow_error(const uint8_t *src, const uint8_t *ref, - int width, int height, int stride, int x, - int y, double u, double v, int16_t *dt) { +static inline void compute_flow_error(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, + int width, int height, int x, int y, + double u, double v, int16_t *dt) { // Split offset into integer and fractional parts, and compute cubic // interpolation kernels const int u_int = (int)floor(u); @@ -56,11 +57,11 @@ const int y0 = clamp(y + v_int, -9, height); // Horizontal convolution. - const uint8_t *ref_start = ref + (y0 - 1) * stride + (x0 - 1); + const uint8_t *ref_start = ref + (y0 - 1) * ref_stride + (x0 - 1); int16x4_t h_filter = vmovn_s32(vld1q_s32(h_kernel)); for (int i = 0; i < DISFLOW_PATCH_SIZE + 3; ++i) { - uint8x16_t r = vld1q_u8(ref_start + i * stride); + uint8x16_t r = vld1q_u8(ref_start + i * ref_stride); uint16x8_t r0 = vmovl_u8(vget_low_u8(r)); uint16x8_t r1 = vmovl_u8(vget_high_u8(r)); @@ -114,7 +115,7 @@ sum_hi = vmlal_lane_s16(sum_hi, vget_high_s16(t2), v_filter, 2); sum_hi = vmlal_lane_s16(sum_hi, vget_high_s16(t3), v_filter, 3); - uint8x8_t s = vld1_u8(src + (i + y) * stride + x); + uint8x8_t s = vld1_u8(src + (i + y) * src_stride + x); int16x8_t s_s16 = vreinterpretq_s16_u16(vshll_n_u8(s, 3)); // This time, we have to round off the 6 extra bits which were kept @@ -230,9 +231,10 @@ vst1_s32(b, add_pairwise_s32x4(b_red)); } -void aom_compute_flow_at_point_neon(const uint8_t *src, const uint8_t *ref, - int x, int y, int width, int height, - int stride, double *u, double *v) { +void aom_compute_flow_at_point_neon(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, int x, + int y, int width, int height, double *u, + double *v) { double M_inv[4]; int b[2]; int16_t dt[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]; @@ -240,14 +242,15 @@ int16_t dy[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]; // Compute gradients within this patch - const uint8_t *src_patch = &src[y * stride + x]; - sobel_filter_x(src_patch, stride, dx, DISFLOW_PATCH_SIZE); - sobel_filter_y(src_patch, stride, dy, DISFLOW_PATCH_SIZE); + const uint8_t *src_patch = &src[y * src_stride + x]; + sobel_filter_x(src_patch, src_stride, dx, DISFLOW_PATCH_SIZE); + sobel_filter_y(src_patch, src_stride, dy, DISFLOW_PATCH_SIZE); compute_flow_matrix(dx, DISFLOW_PATCH_SIZE, dy, DISFLOW_PATCH_SIZE, M_inv); for (int itr = 0; itr < DISFLOW_MAX_ITR; itr++) { - compute_flow_error(src, ref, width, height, stride, x, y, *u, *v, dt); + compute_flow_error(src, src_stride, ref, ref_stride, width, height, x, y, + *u, *v, dt); compute_flow_vector(dx, DISFLOW_PATCH_SIZE, dy, DISFLOW_PATCH_SIZE, dt, DISFLOW_PATCH_SIZE, b);
diff --git a/aom_dsp/flow_estimation/arm/disflow_sve.c b/aom_dsp/flow_estimation/arm/disflow_sve.c index c2160a0..a8a912d 100644 --- a/aom_dsp/flow_estimation/arm/disflow_sve.c +++ b/aom_dsp/flow_estimation/arm/disflow_sve.c
@@ -30,9 +30,10 @@ // (x, y) in src and the other at (x + u, y + v) in ref. // This function returns the sum of squared pixel differences between // the two regions. -static inline void compute_flow_error(const uint8_t *src, const uint8_t *ref, - int width, int height, int stride, int x, - int y, double u, double v, int16_t *dt) { +static inline void compute_flow_error(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, + int width, int height, int x, int y, + double u, double v, int16_t *dt) { // Split offset into integer and fractional parts, and compute cubic // interpolation kernels const int u_int = (int)floor(u); @@ -62,16 +63,16 @@ const int y0 = clamp(y + v_int, -9, height); // Horizontal convolution. - const uint8_t *ref_start = ref + (y0 - 1) * stride + (x0 - 1); + const uint8_t *ref_start = ref + (y0 - 1) * ref_stride + (x0 - 1); const int16x4_t h_kernel_s16 = vmovn_s32(vld1q_s32(h_kernel)); const int16x8_t h_filter = vcombine_s16(h_kernel_s16, vdup_n_s16(0)); const uint16x8_t idx = vld1q_u16(kDeinterleaveTbl); for (int i = 0; i < DISFLOW_PATCH_SIZE + 3; ++i) { - svuint16_t r0 = svld1ub_u16(svptrue_b16(), ref_start + i * stride + 0); - svuint16_t r1 = svld1ub_u16(svptrue_b16(), ref_start + i * stride + 1); - svuint16_t r2 = svld1ub_u16(svptrue_b16(), ref_start + i * stride + 2); - svuint16_t r3 = svld1ub_u16(svptrue_b16(), ref_start + i * stride + 3); + svuint16_t r0 = svld1ub_u16(svptrue_b16(), ref_start + i * ref_stride + 0); + svuint16_t r1 = svld1ub_u16(svptrue_b16(), ref_start + i * ref_stride + 1); + svuint16_t r2 = svld1ub_u16(svptrue_b16(), ref_start + i * ref_stride + 2); + svuint16_t r3 = svld1ub_u16(svptrue_b16(), ref_start + i * ref_stride + 3); int16x8_t s0 = vreinterpretq_s16_u16(svget_neonq_u16(r0)); int16x8_t s1 = vreinterpretq_s16_u16(svget_neonq_u16(r1)); @@ -123,7 +124,7 @@ sum_hi = vmlal_lane_s16(sum_hi, vget_high_s16(t2), v_filter, 2); sum_hi = vmlal_lane_s16(sum_hi, vget_high_s16(t3), v_filter, 3); - uint8x8_t s = vld1_u8(src + (i + y) * stride + x); + uint8x8_t s = vld1_u8(src + (i + y) * src_stride + x); int16x8_t s_s16 = vreinterpretq_s16_u16(vshll_n_u8(s, 3)); // This time, we have to round off the 6 extra bits which were kept @@ -232,9 +233,10 @@ vst1_s32(b, vmovn_s64(b_s64[0])); } -void aom_compute_flow_at_point_sve(const uint8_t *src, const uint8_t *ref, - int x, int y, int width, int height, - int stride, double *u, double *v) { +void aom_compute_flow_at_point_sve(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, int x, + int y, int width, int height, double *u, + double *v) { double M_inv[4]; int b[2]; int16_t dt[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]; @@ -242,14 +244,15 @@ int16_t dy[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]; // Compute gradients within this patch - const uint8_t *src_patch = &src[y * stride + x]; - sobel_filter_x(src_patch, stride, dx, DISFLOW_PATCH_SIZE); - sobel_filter_y(src_patch, stride, dy, DISFLOW_PATCH_SIZE); + const uint8_t *src_patch = &src[y * src_stride + x]; + sobel_filter_x(src_patch, src_stride, dx, DISFLOW_PATCH_SIZE); + sobel_filter_y(src_patch, src_stride, dy, DISFLOW_PATCH_SIZE); compute_flow_matrix(dx, DISFLOW_PATCH_SIZE, dy, DISFLOW_PATCH_SIZE, M_inv); for (int itr = 0; itr < DISFLOW_MAX_ITR; itr++) { - compute_flow_error(src, ref, width, height, stride, x, y, *u, *v, dt); + compute_flow_error(src, src_stride, ref, ref_stride, width, height, x, y, + *u, *v, dt); compute_flow_vector(dx, DISFLOW_PATCH_SIZE, dy, DISFLOW_PATCH_SIZE, dt, DISFLOW_PATCH_SIZE, b);
diff --git a/aom_dsp/flow_estimation/corner_match.c b/aom_dsp/flow_estimation/corner_match.c index 07ea924..3679736 100644 --- a/aom_dsp/flow_estimation/corner_match.c +++ b/aom_dsp/flow_estimation/corner_match.c
@@ -118,12 +118,11 @@ double best_match_corr; } PointInfo; -static int determine_correspondence(const unsigned char *src, +static int determine_correspondence(const unsigned char *src, int src_stride, const int *src_corners, int num_src_corners, - const unsigned char *ref, + const unsigned char *ref, int ref_stride, const int *ref_corners, int num_ref_corners, - int width, int height, int src_stride, - int ref_stride, + int width, int height, Correspondence *correspondences) { PointInfo *src_point_info = NULL; PointInfo *ref_point_info = NULL; @@ -233,8 +232,8 @@ const int patch_tl_x = sx - DISFLOW_PATCH_CENTER; const int patch_tl_y = sy - DISFLOW_PATCH_CENTER; - aom_compute_flow_at_point(src, ref, patch_tl_x, patch_tl_y, width, height, - src_stride, &u, &v); + aom_compute_flow_at_point(src, src_stride, ref, ref_stride, patch_tl_x, + patch_tl_y, width, height, &u, &v); Correspondence *correspondence = &correspondences[num_correspondences]; correspondence->x = (double)sx; @@ -290,6 +289,10 @@ assert(ref_pyramid->layers[0].height == src_height); const int ref_stride = ref_pyramid->layers[0].stride; + if (src_corners->num_corners == 0 || ref_corners->num_corners == 0) { + return false; + } + // find correspondences between the two images correspondences = (Correspondence *)aom_malloc(src_corners->num_corners * sizeof(*correspondences)); @@ -298,9 +301,9 @@ return false; } num_correspondences = determine_correspondence( - src_buffer, src_corners->corners, src_corners->num_corners, ref_buffer, - ref_corners->corners, ref_corners->num_corners, src_width, src_height, - src_stride, ref_stride, correspondences); + src_buffer, src_stride, src_corners->corners, src_corners->num_corners, + ref_buffer, ref_stride, ref_corners->corners, ref_corners->num_corners, + src_width, src_height, correspondences); bool result = ransac(correspondences, num_correspondences, type, motion_models, num_motion_models, mem_alloc_failed);
diff --git a/aom_dsp/flow_estimation/disflow.c b/aom_dsp/flow_estimation/disflow.c index a1d7491..a275673 100644 --- a/aom_dsp/flow_estimation/disflow.c +++ b/aom_dsp/flow_estimation/disflow.c
@@ -172,9 +172,10 @@ const int patch_tl_x = x0 - DISFLOW_PATCH_CENTER; const int patch_tl_y = y0 - DISFLOW_PATCH_CENTER; aom_compute_flow_at_point( - src_pyr->layers[0].buffer, ref_pyr->layers[0].buffer, patch_tl_x, + src_pyr->layers[0].buffer, src_pyr->layers[0].stride, + ref_pyr->layers[0].buffer, ref_pyr->layers[0].stride, patch_tl_x, patch_tl_y, src_pyr->layers[0].width, src_pyr->layers[0].height, - src_pyr->layers[0].stride, &flow_u, &flow_v); + &flow_u, &flow_v); // Use original points (without offsets) when filling in correspondence // array @@ -191,11 +192,11 @@ // (x, y) in src and the other at (x + u, y + v) in ref. // This function returns the sum of squared pixel differences between // the two regions. -static inline void compute_flow_vector(const uint8_t *src, const uint8_t *ref, - int width, int height, int stride, int x, - int y, double u, double v, - const int16_t *dx, const int16_t *dy, - int *b) { +static inline void compute_flow_vector(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, + int width, int height, int x, int y, + double u, double v, const int16_t *dx, + const int16_t *dy, int *b) { memset(b, 0, 2 * sizeof(*b)); // Split offset into integer and fractional parts, and compute cubic @@ -235,10 +236,10 @@ const int x_w = x0 + j; int arr[4]; - arr[0] = (int)ref[y_w * stride + (x_w - 1)]; - arr[1] = (int)ref[y_w * stride + (x_w + 0)]; - arr[2] = (int)ref[y_w * stride + (x_w + 1)]; - arr[3] = (int)ref[y_w * stride + (x_w + 2)]; + arr[0] = (int)ref[y_w * ref_stride + (x_w - 1)]; + arr[1] = (int)ref[y_w * ref_stride + (x_w + 0)]; + arr[2] = (int)ref[y_w * ref_stride + (x_w + 1)]; + arr[3] = (int)ref[y_w * ref_stride + (x_w + 2)]; // Apply kernel and round, keeping 6 extra bits of precision. // @@ -270,7 +271,7 @@ // of precision to match the scale of the dx and dy arrays. const int round_bits = DISFLOW_INTERP_BITS + 6 - DISFLOW_DERIV_SCALE_LOG2; const int warped = ROUND_POWER_OF_TWO(result, round_bits); - const int src_px = src[(x + j) + (y + i) * stride] << 3; + const int src_px = src[(x + j) + (y + i) * src_stride] << 3; const int dt = warped - src_px; b[0] += dx[i * DISFLOW_PATCH_SIZE + j] * dt; b[1] += dy[i * DISFLOW_PATCH_SIZE + j] * dt; @@ -410,9 +411,10 @@ M_inv[3] = M[0] * det_inv; } -void aom_compute_flow_at_point_c(const uint8_t *src, const uint8_t *ref, int x, - int y, int width, int height, int stride, - double *u, double *v) { +void aom_compute_flow_at_point_c(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, int x, + int y, int width, int height, double *u, + double *v) { double M[4]; double M_inv[4]; int b[2]; @@ -420,16 +422,16 @@ int16_t dy[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]; // Compute gradients within this patch - const uint8_t *src_patch = &src[y * stride + x]; - sobel_filter(src_patch, stride, dx, DISFLOW_PATCH_SIZE, 1); - sobel_filter(src_patch, stride, dy, DISFLOW_PATCH_SIZE, 0); + const uint8_t *src_patch = &src[y * src_stride + x]; + sobel_filter(src_patch, src_stride, dx, DISFLOW_PATCH_SIZE, 1); + sobel_filter(src_patch, src_stride, dy, DISFLOW_PATCH_SIZE, 0); compute_flow_matrix(dx, DISFLOW_PATCH_SIZE, dy, DISFLOW_PATCH_SIZE, M); invert_2x2(M, M_inv); for (int itr = 0; itr < DISFLOW_MAX_ITR; itr++) { - compute_flow_vector(src, ref, width, height, stride, x, y, *u, *v, dx, dy, - b); + compute_flow_vector(src, src_stride, ref, ref_stride, width, height, x, y, + *u, *v, dx, dy, b); // Solve flow equations to find a better estimate for the flow vector // at this point @@ -447,6 +449,9 @@ static void fill_flow_field_borders(double *flow, int width, int height, int stride) { + if (width <= 2 * FLOW_BORDER_INNER || height <= 2 * FLOW_BORDER_INNER) return; + assert(width + 2 * FLOW_BORDER_OUTER <= stride); + // Calculate the bounds of the rectangle which was filled in by // compute_flow_field() before calling this function. // These indices are inclusive on both ends. @@ -478,7 +483,7 @@ for (int i = -FLOW_BORDER_OUTER; i < top_index; i++) { double *row = flow + i * stride - FLOW_BORDER_OUTER; size_t length = width + 2 * FLOW_BORDER_OUTER; - memcpy(row, top_row, length * sizeof(*row)); + memmove(row, top_row, length * sizeof(*row)); } // Bottom area @@ -486,7 +491,7 @@ for (int i = bottom_index + 1; i < height + FLOW_BORDER_OUTER; i++) { double *row = flow + i * stride - FLOW_BORDER_OUTER; size_t length = width + 2 * FLOW_BORDER_OUTER; - memcpy(row, bottom_row, length * sizeof(*row)); + memmove(row, bottom_row, length * sizeof(*row)); } } @@ -541,6 +546,10 @@ // vector, even though these must be interpolated using different source points. static void upscale_flow_component(double *flow, int cur_width, int cur_height, int stride, double *tmpbuf) { + assert(cur_width > 0); + assert(cur_height > 0); + assert(2 * cur_width <= stride); + const int half_len = FLOW_UPSCALE_TAPS / 2; // Check that the outer border is large enough to avoid needing to clamp @@ -571,13 +580,13 @@ const double *top_row = &tmpbuf[0]; for (int i = -FLOW_BORDER_OUTER; i < 0; i++) { double *row = &tmpbuf[i * stride]; - memcpy(row, top_row, 2 * cur_width * sizeof(*row)); + memmove(row, top_row, 2 * cur_width * sizeof(*row)); } const double *bottom_row = &tmpbuf[(cur_height - 1) * stride]; for (int i = cur_height; i < cur_height + FLOW_BORDER_OUTER; i++) { double *row = &tmpbuf[i * stride]; - memcpy(row, bottom_row, 2 * cur_width * sizeof(*row)); + memmove(row, bottom_row, 2 * cur_width * sizeof(*row)); } // Vertical upscale @@ -643,10 +652,11 @@ const PyramidLayer *cur_layer = &src_pyr->layers[level]; const int cur_width = cur_layer->width; const int cur_height = cur_layer->height; - const int cur_stride = cur_layer->stride; const uint8_t *src_buffer = cur_layer->buffer; + const int src_stride = cur_layer->stride; const uint8_t *ref_buffer = ref_pyr->layers[level].buffer; + const int ref_stride = ref_pyr->layers[level].stride; const int cur_flow_width = cur_width >> DOWNSAMPLE_SHIFT; const int cur_flow_height = cur_height >> DOWNSAMPLE_SHIFT; @@ -669,9 +679,9 @@ assert(patch_tl_x >= 0); assert(patch_tl_y >= 0); - aom_compute_flow_at_point(src_buffer, ref_buffer, patch_tl_x, - patch_tl_y, cur_width, cur_height, cur_stride, - &flow_u[flow_field_idx], + aom_compute_flow_at_point(src_buffer, src_stride, ref_buffer, + ref_stride, patch_tl_x, patch_tl_y, cur_width, + cur_height, &flow_u[flow_field_idx], &flow_v[flow_field_idx]); } } @@ -728,12 +738,17 @@ } static FlowField *alloc_flow_field(int frame_width, int frame_height) { + const int flow_width = frame_width >> DOWNSAMPLE_SHIFT; + const int flow_height = frame_height >> DOWNSAMPLE_SHIFT; + assert(flow_width > 0); + assert(flow_height > 0); + FlowField *flow = (FlowField *)aom_malloc(sizeof(FlowField)); if (flow == NULL) return NULL; // Calculate the size of the bottom (largest) layer of the flow pyramid - flow->width = frame_width >> DOWNSAMPLE_SHIFT; - flow->height = frame_height >> DOWNSAMPLE_SHIFT; + flow->width = flow_width; + flow->height = flow_height; flow->stride = flow->width + 2 * FLOW_BORDER_OUTER; const size_t flow_size = @@ -780,17 +795,30 @@ *mem_alloc_failed = true; return false; } + + if (src_layers != ref_layers) { + return false; + } + if (!av1_compute_corner_list(src, bit_depth, downsample_level, src_corners)) { *mem_alloc_failed = true; return false; } - assert(src_layers == ref_layers); - const int src_width = src_pyramid->layers[0].width; const int src_height = src_pyramid->layers[0].height; - assert(ref_pyramid->layers[0].width == src_width); - assert(ref_pyramid->layers[0].height == src_height); + if (ref_pyramid->layers[0].width != src_width || + ref_pyramid->layers[0].height != src_height) { + return false; + } + + if (src_width < (1 << DOWNSAMPLE_SHIFT) || + src_height < (1 << DOWNSAMPLE_SHIFT)) { + return false; + } + if (src_corners->num_corners == 0) { + return false; + } FlowField *flow = alloc_flow_field(src_width, src_height); if (!flow) {
diff --git a/aom_dsp/flow_estimation/x86/disflow_avx2.c b/aom_dsp/flow_estimation/x86/disflow_avx2.c index b93acdd..d1d1969 100644 --- a/aom_dsp/flow_estimation/x86/disflow_avx2.c +++ b/aom_dsp/flow_estimation/x86/disflow_avx2.c
@@ -81,11 +81,11 @@ // // TODO(rachelbarker): Test speed/quality impact of using bilinear interpolation // instad of bicubic interpolation -static inline void compute_flow_vector(const uint8_t *src, const uint8_t *ref, - int width, int height, int stride, int x, - int y, double u, double v, - const int16_t *dx, const int16_t *dy, - int *b) { +static inline void compute_flow_vector(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, + int width, int height, int x, int y, + double u, double v, const int16_t *dx, + const int16_t *dy, int *b) { const __m256i zero = _mm256_setzero_si256(); // Accumulate 8 32-bit partial sums for each element of b @@ -137,7 +137,7 @@ for (int i = -1; i < DISFLOW_PATCH_SIZE + 2; i += 2) { const int y_w = y0 + i; - const uint8_t *ref_row = &ref[y_w * stride + (x0 - 1)]; + const uint8_t *ref_row = &ref[y_w * ref_stride + (x0 - 1)]; int16_t *tmp_row = &tmp[i * DISFLOW_PATCH_SIZE]; // Load this row of pixels. @@ -145,7 +145,7 @@ // for a total of 11 pixels. Here we load 16 pixels, but only use // the first 11. __m256i row = - yy_loadu2_128((__m128i *)(ref_row + stride), (__m128i *)ref_row); + yy_loadu2_128((__m128i *)(ref_row + ref_stride), (__m128i *)ref_row); // Expand pixels to int16s // We must use unpacks here, as we have one row in each 128-bit lane @@ -234,8 +234,8 @@ _mm256_srai_epi32(_mm256_add_epi32(sum1, round_const_v), round_bits); __m256i warped = _mm256_packs_epi32(sum0_rounded, sum1_rounded); - __m128i src_pixels_u8 = xx_loadu_2x64(&src[(y + i + 1) * stride + x], - &src[(y + i) * stride + x]); + __m128i src_pixels_u8 = xx_loadu_2x64(&src[(y + i + 1) * src_stride + x], + &src[(y + i) * src_stride + x]); __m256i src_pixels = _mm256_slli_epi16(_mm256_cvtepu8_epi16(src_pixels_u8), 3); @@ -382,9 +382,10 @@ M_inv[3] = M[0] * det_inv; } -void aom_compute_flow_at_point_avx2(const uint8_t *src, const uint8_t *ref, - int x, int y, int width, int height, - int stride, double *u, double *v) { +void aom_compute_flow_at_point_avx2(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, int x, + int y, int width, int height, double *u, + double *v) { DECLARE_ALIGNED(32, double, M[4]); DECLARE_ALIGNED(32, double, M_inv[4]); DECLARE_ALIGNED(32, int16_t, dx[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]); @@ -392,15 +393,15 @@ int b[2]; // Compute gradients within this patch - const uint8_t *src_patch = &src[y * stride + x]; - sobel_filter(src_patch, stride, dx, dy); + const uint8_t *src_patch = &src[y * src_stride + x]; + sobel_filter(src_patch, src_stride, dx, dy); compute_flow_matrix(dx, DISFLOW_PATCH_SIZE, dy, DISFLOW_PATCH_SIZE, M); invert_2x2(M, M_inv); for (int itr = 0; itr < DISFLOW_MAX_ITR; itr++) { - compute_flow_vector(src, ref, width, height, stride, x, y, *u, *v, dx, dy, - b); + compute_flow_vector(src, src_stride, ref, ref_stride, width, height, x, y, + *u, *v, dx, dy, b); // Solve flow equations to find a better estimate for the flow vector // at this point
diff --git a/aom_dsp/flow_estimation/x86/disflow_sse4.c b/aom_dsp/flow_estimation/x86/disflow_sse4.c index ffbe485..2d01315 100644 --- a/aom_dsp/flow_estimation/x86/disflow_sse4.c +++ b/aom_dsp/flow_estimation/x86/disflow_sse4.c
@@ -80,11 +80,11 @@ // // TODO(rachelbarker): Test speed/quality impact of using bilinear interpolation // instad of bicubic interpolation -static inline void compute_flow_vector(const uint8_t *src, const uint8_t *ref, - int width, int height, int stride, int x, - int y, double u, double v, - const int16_t *dx, const int16_t *dy, - int *b) { +static inline void compute_flow_vector(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, + int width, int height, int x, int y, + double u, double v, const int16_t *dx, + const int16_t *dy, int *b) { // This function is written to do 8x8 convolutions only assert(DISFLOW_PATCH_SIZE == 8); @@ -134,7 +134,7 @@ for (int i = -1; i < DISFLOW_PATCH_SIZE + 2; ++i) { const int y_w = y0 + i; - const uint8_t *ref_row = &ref[y_w * stride + (x0 - 1)]; + const uint8_t *ref_row = &ref[y_w * ref_stride + (x0 - 1)]; int16_t *tmp_row = &tmp[i * DISFLOW_PATCH_SIZE]; // Load this row of pixels. @@ -218,7 +218,7 @@ __m128i warped = _mm_packs_epi32(sum0_rounded, sum1_rounded); __m128i src_pixels_u8 = - _mm_loadl_epi64((__m128i *)&src[(y + i) * stride + x]); + _mm_loadl_epi64((__m128i *)&src[(y + i) * src_stride + x]); __m128i src_pixels = _mm_slli_epi16(_mm_cvtepu8_epi16(src_pixels_u8), 3); // Calculate delta from the target patch @@ -357,9 +357,10 @@ M_inv[3] = M[0] * det_inv; } -void aom_compute_flow_at_point_sse4_1(const uint8_t *src, const uint8_t *ref, - int x, int y, int width, int height, - int stride, double *u, double *v) { +void aom_compute_flow_at_point_sse4_1(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, int x, + int y, int width, int height, double *u, + double *v) { DECLARE_ALIGNED(16, double, M[4]); DECLARE_ALIGNED(16, double, M_inv[4]); DECLARE_ALIGNED(16, int16_t, dx[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]); @@ -367,15 +368,15 @@ int b[2]; // Compute gradients within this patch - const uint8_t *src_patch = &src[y * stride + x]; - sobel_filter(src_patch, stride, dx, dy); + const uint8_t *src_patch = &src[y * src_stride + x]; + sobel_filter(src_patch, src_stride, dx, dy); compute_flow_matrix(dx, DISFLOW_PATCH_SIZE, dy, DISFLOW_PATCH_SIZE, M); invert_2x2(M, M_inv); for (int itr = 0; itr < DISFLOW_MAX_ITR; itr++) { - compute_flow_vector(src, ref, width, height, stride, x, y, *u, *v, dx, dy, - b); + compute_flow_vector(src, src_stride, ref, ref_stride, width, height, x, y, + *u, *v, dx, dy, b); // Solve flow equations to find a better estimate for the flow vector // at this point
diff --git a/aom_dsp/x86/aom_convolve_copy_avx2.c b/aom_dsp/x86/aom_convolve_copy_avx2.c index efce154..86d396c 100644 --- a/aom_dsp/x86/aom_convolve_copy_avx2.c +++ b/aom_dsp/x86/aom_convolve_copy_avx2.c
@@ -10,6 +10,7 @@ */ #include <immintrin.h> +#include <string.h> #include "config/aom_dsp_rtcd.h" @@ -36,20 +37,20 @@ if (w == 2) { do { - memmove(dst, src, 2 * sizeof(*src)); + memcpy(dst, src, 2 * sizeof(*src)); src += src_stride; dst += dst_stride; - memmove(dst, src, 2 * sizeof(*src)); + memcpy(dst, src, 2 * sizeof(*src)); src += src_stride; dst += dst_stride; h -= 2; } while (h); } else if (w == 4) { do { - memmove(dst, src, 4 * sizeof(*src)); + memcpy(dst, src, 4 * sizeof(*src)); src += src_stride; dst += dst_stride; - memmove(dst, src, 4 * sizeof(*src)); + memcpy(dst, src, 4 * sizeof(*src)); src += src_stride; dst += dst_stride; h -= 2; @@ -170,10 +171,10 @@ if (w == 2) { do { - memmove(dst, src, 2 * sizeof(*src)); + memcpy(dst, src, 2 * sizeof(*src)); src += src_stride; dst += dst_stride; - memmove(dst, src, 2 * sizeof(*src)); + memcpy(dst, src, 2 * sizeof(*src)); src += src_stride; dst += dst_stride; h -= 2;
diff --git a/aom_dsp/x86/aom_convolve_copy_sse2.c b/aom_dsp/x86/aom_convolve_copy_sse2.c index 223b404..871f53e 100644 --- a/aom_dsp/x86/aom_convolve_copy_sse2.c +++ b/aom_dsp/x86/aom_convolve_copy_sse2.c
@@ -10,6 +10,7 @@ */ #include <immintrin.h> +#include <string.h> #include "config/aom_dsp_rtcd.h" @@ -44,20 +45,20 @@ if (w == 2) { do { - memmove(dst, src, 2 * sizeof(*src)); + memcpy(dst, src, 2 * sizeof(*src)); src += src_stride; dst += dst_stride; - memmove(dst, src, 2 * sizeof(*src)); + memcpy(dst, src, 2 * sizeof(*src)); src += src_stride; dst += dst_stride; h -= 2; } while (h); } else if (w == 4) { do { - memmove(dst, src, 4 * sizeof(*src)); + memcpy(dst, src, 4 * sizeof(*src)); src += src_stride; dst += dst_stride; - memmove(dst, src, 4 * sizeof(*src)); + memcpy(dst, src, 4 * sizeof(*src)); src += src_stride; dst += dst_stride; h -= 2;
diff --git a/av1/arg_defs.c b/av1/arg_defs.c index 9a3352c..d9627cd 100644 --- a/av1/arg_defs.c +++ b/av1/arg_defs.c
@@ -42,8 +42,6 @@ static const struct arg_enum_list tuning_enum[] = { { "psnr", AOM_TUNE_PSNR }, { "ssim", AOM_TUNE_SSIM }, - { "vmaf_with_preprocessing", AOM_TUNE_VMAF_WITH_PREPROCESSING }, - { "vmaf_without_preprocessing", AOM_TUNE_VMAF_WITHOUT_PREPROCESSING }, { "vmaf", AOM_TUNE_VMAF_MAX_GAIN }, { "vmaf_neg", AOM_TUNE_VMAF_NEG_MAX_GAIN }, { "butteraugli", AOM_TUNE_BUTTERAUGLI },
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c index b43c96d..030da6e 100644 --- a/av1/av1_cx_iface.c +++ b/av1/av1_cx_iface.c
@@ -887,8 +887,7 @@ #endif #if !CONFIG_TUNE_VMAF - if (extra_cfg->tuning >= AOM_TUNE_VMAF_WITH_PREPROCESSING && - extra_cfg->tuning <= AOM_TUNE_VMAF_NEG_MAX_GAIN) { + if (is_vmaf_tuning_mode(extra_cfg->tuning)) { ERROR( "This error may be related to the wrong configuration options: try to " "set -DCONFIG_TUNE_VMAF=1 at the time CMake is run."); @@ -1673,21 +1672,32 @@ if (cfg->g_lag_in_frames > 1 || cfg->g_pass != AOM_RC_ONE_PASS) ERROR("Cannot change width or height after initialization"); // Note: function encoder_set_config() is allowed to be called multiple - // times. However, when the original frame width or height is less than two - // times of the new frame width or height, a forced key frame should be - // used (for the case of single spatial layer, since otherwise a previous + // times. In single-pass realtime mode without lookahead (g_lag_in_frames == + // 0), and with the maximum frame size declared up front via + // g_forced_max_frame_width/height, reference frame scaling allows upscaling + // up to 16x and downscaling by up to 2x without forcing a keyframe. The + // forced maximum frame size is required because the internal buffers are + // then allocated for that maximum; without it they are only sized for the + // current frame size and a larger frame would overflow them. Outside of + // this mode, or if reference frame scaling constraints are violated, a + // keyframe is forced (for single spatial layer, since otherwise a previous // encoded frame at a lower layer may be the desired reference). To make - // sure the correct detection of a forced key frame, we need - // to update the frame width and height only when the actual encoding is - // performed. cpi->last_coded_width and cpi->last_coded_height are used to - // track the actual coded frame size. + // sure the correct detection of a forced key frame, we need to update the + // frame width and height only when the actual encoding is performed. + // cpi->last_coded_width and cpi->last_coded_height are used to track the + // actual coded frame size. + const bool allow_ref_scaled_upscale = + cfg->g_forced_max_frame_width && cfg->g_forced_max_frame_height && + ctx->oxcf.mode == REALTIME && cfg->g_pass == AOM_RC_ONE_PASS && + cfg->g_lag_in_frames == 0; if (ctx->ppi->cpi->svc.number_spatial_layers == 1 && ctx->ppi->cpi->last_coded_width && ctx->ppi->cpi->last_coded_height && (!valid_ref_frame_size(ctx->ppi->cpi->last_coded_width, ctx->ppi->cpi->last_coded_height, cfg->g_w, cfg->g_h) || - ((int)cfg->g_w > ctx->ppi->cpi->last_coded_width) || - ((int)cfg->g_h > ctx->ppi->cpi->last_coded_height))) { + (!allow_ref_scaled_upscale && + (((int)cfg->g_w > ctx->ppi->cpi->last_coded_width) || + ((int)cfg->g_h > ctx->ppi->cpi->last_coded_height))))) { force_key = 1; } } @@ -3408,8 +3418,7 @@ } #if CONFIG_TUNE_VMAF - if (ctx->extra_cfg.tuning >= AOM_TUNE_VMAF_WITH_PREPROCESSING && - ctx->extra_cfg.tuning <= AOM_TUNE_VMAF_NEG_MAX_GAIN) { + if (is_vmaf_tuning_mode(ctx->extra_cfg.tuning)) { aom_init_vmaf_model(&ppi->cpi->vmaf_info.vmaf_model, ppi->cpi->oxcf.tune_cfg.vmaf_model_path); } @@ -4901,6 +4910,9 @@ } else if (arg_match_helper(&arg, &g_av1_codec_arg_defs.force_max_q, argv, err_string)) { extra_cfg.force_max_q = arg_parse_uint_helper(&arg, err_string); + } else if (arg_match_helper(&arg, &g_av1_codec_arg_defs.resize_mode, argv, + err_string)) { + ctx->cfg.rc_resize_mode = arg_parse_uint_helper(&arg, err_string); } else { match = 0; snprintf(err_string, ARG_ERR_MSG_MAX_LEN, "Cannot find aom option %s",
diff --git a/av1/common/reconintra.c b/av1/common/reconintra.c index 33016a2..4e94c00 100644 --- a/av1/common/reconintra.c +++ b/av1/common/reconintra.c
@@ -1750,8 +1750,8 @@ // separate function build_non_directional_intra_predictors() is introduced // for these modes to avoid redundant computations while generating pred data. - const int n_top_px = have_top ? AOMMIN(txwpx, xr + txwpx) : 0; - const int n_left_px = have_left ? AOMMIN(txhpx, yd + txhpx) : 0; + const int n_top_px = have_top ? clamp(xr + txwpx, 0, txwpx) : 0; + const int n_left_px = have_left ? clamp(yd + txhpx, 0, txhpx) : 0; if (!use_filter_intra && !is_dr_mode) { #if CONFIG_AV1_HIGHBITDEPTH if (is_hbd) { @@ -1815,9 +1815,9 @@ const int disable_edge_filter = !enable_intra_edge_filter; const int intra_edge_filter_type = get_intra_edge_filter_type(xd, plane); const int n_topright_px = - have_top_right > 0 ? AOMMIN(txwpx, xr) : have_top_right; + have_top_right > 0 ? clamp(xr, 0, txwpx) : have_top_right; const int n_bottomleft_px = - have_bottom_left > 0 ? AOMMIN(txhpx, yd) : have_bottom_left; + have_bottom_left > 0 ? clamp(yd, 0, txhpx) : have_bottom_left; #if CONFIG_AV1_HIGHBITDEPTH if (is_hbd) { highbd_build_directional_and_filter_intra_predictors(
diff --git a/av1/decoder/decodeframe.c b/av1/decoder/decodeframe.c index b6ee3ac..fcb328d 100644 --- a/av1/decoder/decodeframe.c +++ b/av1/decoder/decodeframe.c
@@ -5370,6 +5370,11 @@ xd->error_info = cm->error; if (initialize_flag) setup_frame_info(pbi); + if (pbi->dec_tile_row < -1 || pbi->dec_tile_row >= tiles->rows || + pbi->dec_tile_col < -1 || pbi->dec_tile_col >= tiles->cols) { + aom_internal_error(&pbi->error, AOM_CODEC_INVALID_PARAM, + "Invalid tile row or column"); + } const int num_planes = av1_num_planes(cm); #if CONFIG_COLLECT_COMPONENT_TIMING
diff --git a/av1/encoder/allintra_vis.c b/av1/encoder/allintra_vis.c index ec8ec26..91b5147 100644 --- a/av1/encoder/allintra_vis.c +++ b/av1/encoder/allintra_vis.c
@@ -59,34 +59,41 @@ void av1_init_mb_wiener_var_buffer(AV1_COMP *cpi) { AV1_COMMON *cm = &cpi->common; + const int current_size = cpi->frame_info.mi_rows * cpi->frame_info.mi_cols; // This block size is also used to determine number of workers in // multi-threading. If it is changed, one needs to change it accordingly in // "compute_num_ai_workers()". cpi->weber_bsize = BLOCK_8X8; - if (cpi->oxcf.enable_rate_guide_deltaq) { - if (cpi->mb_weber_stats && cpi->prep_rate_estimates && - cpi->ext_rate_distribution) - return; - } else { - if (cpi->mb_weber_stats) return; + if (cpi->mb_weber_stats && cpi->mb_weber_stats_alloc_size < current_size) { + aom_free(cpi->mb_weber_stats); + cpi->mb_weber_stats = NULL; + aom_free(cpi->prep_rate_estimates); + cpi->prep_rate_estimates = NULL; + aom_free(cpi->ext_rate_distribution); + cpi->ext_rate_distribution = NULL; + cpi->mb_weber_stats_alloc_size = 0; } - CHECK_MEM_ERROR(cm, cpi->mb_weber_stats, - aom_calloc(cpi->frame_info.mi_rows * cpi->frame_info.mi_cols, - sizeof(*cpi->mb_weber_stats))); + if (!cpi->mb_weber_stats) { + CHECK_MEM_ERROR(cm, cpi->mb_weber_stats, + aom_calloc(current_size, sizeof(*cpi->mb_weber_stats))); + cpi->mb_weber_stats_alloc_size = current_size; + } if (cpi->oxcf.enable_rate_guide_deltaq) { - CHECK_MEM_ERROR( - cm, cpi->prep_rate_estimates, - aom_calloc(cpi->frame_info.mi_rows * cpi->frame_info.mi_cols, - sizeof(*cpi->prep_rate_estimates))); + if (!cpi->prep_rate_estimates) { + CHECK_MEM_ERROR(cm, cpi->prep_rate_estimates, + aom_calloc(cpi->mb_weber_stats_alloc_size, + sizeof(*cpi->prep_rate_estimates))); + } - CHECK_MEM_ERROR( - cm, cpi->ext_rate_distribution, - aom_calloc(cpi->frame_info.mi_rows * cpi->frame_info.mi_cols, - sizeof(*cpi->ext_rate_distribution))); + if (!cpi->ext_rate_distribution) { + CHECK_MEM_ERROR(cm, cpi->ext_rate_distribution, + aom_calloc(cpi->mb_weber_stats_alloc_size, + sizeof(*cpi->ext_rate_distribution))); + } } } @@ -103,7 +110,8 @@ for (int row = mi_row; row < mi_row + mi_high; row += mi_step) { for (int col = mi_col; col < mi_col + mi_wide; col += mi_step) { - if (row >= cm->mi_params.mi_rows || col >= cm->mi_params.mi_cols) + if (row < 0 || col < 0 || row >= cm->mi_params.mi_rows || + col >= cm->mi_params.mi_cols) continue; satd += cpi->mb_weber_stats[(row / mi_step) * mb_stride + (col / mi_step)] @@ -112,10 +120,10 @@ } } - if (mb_count) satd = (int)(satd / mb_count); + if (mb_count) satd = satd / mb_count; satd = AOMMAX(1, satd); - return (int)satd; + return satd; } static int64_t get_sse(AV1_COMP *const cpi, BLOCK_SIZE bsize, int mi_row, @@ -131,7 +139,8 @@ for (int row = mi_row; row < mi_row + mi_high; row += mi_step) { for (int col = mi_col; col < mi_col + mi_wide; col += mi_step) { - if (row >= cm->mi_params.mi_rows || col >= cm->mi_params.mi_cols) + if (row < 0 || col < 0 || row >= cm->mi_params.mi_rows || + col >= cm->mi_params.mi_cols) continue; distortion += @@ -141,10 +150,10 @@ } } - if (mb_count) distortion = (int)(distortion / mb_count); + if (mb_count) distortion = distortion / mb_count; distortion = AOMMAX(1, distortion); - return (int)distortion; + return distortion; } static double get_max_scale(const AV1_COMP *const cpi, BLOCK_SIZE bsize, @@ -158,7 +167,8 @@ for (int row = mi_row; row < mi_row + mi_high; row += mi_step) { for (int col = mi_col; col < mi_col + mi_wide; col += mi_step) { - if (row >= cm->mi_params.mi_rows || col >= cm->mi_params.mi_cols) + if (row < 0 || col < 0 || row >= cm->mi_params.mi_rows || + col >= cm->mi_params.mi_cols) continue; const WeberStats *weber_stats = &cpi->mb_weber_stats[(row / mi_step) * mb_stride + (col / mi_step)]; @@ -186,7 +196,8 @@ for (int row = mi_row; row < mi_row + mi_high; row += mi_step) { for (int col = mi_col; col < mi_col + mi_wide; col += mi_step) { - if (row >= cm->mi_params.mi_rows || col >= cm->mi_params.mi_cols) + if (row < 0 || col < 0 || row >= cm->mi_params.mi_rows || + col >= cm->mi_params.mi_cols) continue; const WeberStats *weber_stats = @@ -206,6 +217,7 @@ } } + if (mb_count == 0) return 1; sb_wiener_var = (int)(((base_num + base_reg) / (base_den + base_reg)) / mb_count); sb_wiener_var = AOMMAX(1, sb_wiener_var); @@ -690,16 +702,20 @@ static int get_rate_guided_quantizer(const AV1_COMP *const cpi, BLOCK_SIZE bsize, int mi_row, int mi_col) { + const AV1_COMMON *const cm = &cpi->common; // Calculation uses 8x8. const int mb_step = mi_size_wide[cpi->weber_bsize]; // Accumulate to 16x16 const int block_step = mi_size_wide[BLOCK_16X16]; double sb_rate_hific = 0.0; double sb_rate_uniform = 0.0; - for (int row = mi_row; row < mi_row + mi_size_wide[bsize]; + for (int row = mi_row; row < mi_row + mi_size_high[bsize]; row += block_step) { - for (int col = mi_col; col < mi_col + mi_size_high[bsize]; + for (int col = mi_col; col < mi_col + mi_size_wide[bsize]; col += block_step) { + if (row < 0 || col < 0 || row >= cm->mi_params.mi_rows || + col >= cm->mi_params.mi_cols) + continue; sb_rate_hific += cpi->ext_rate_distribution[(row / mb_step) * cpi->frame_info.mi_cols + (col / mb_step)]; @@ -708,6 +724,10 @@ for (int c = 0; c < block_step; c += mb_step) { const int this_row = row + r; const int this_col = col + c; + if (this_row < 0 || this_col < 0 || + this_row >= cm->mi_params.mi_rows || + this_col >= cm->mi_params.mi_cols) + continue; sb_rate_uniform += cpi->prep_rate_estimates[(this_row / mb_step) * cpi->frame_info.mi_cols + @@ -727,7 +747,6 @@ double min_max_scale = AOMMAX(1.0, get_max_scale(cpi, bsize, mi_row, mi_col)); scale = 1.0 / AOMMIN(1.0 / scale, min_max_scale); - const AV1_COMMON *const cm = &cpi->common; const int base_qindex = cm->quant_params.base_qindex; int offset = av1_get_deltaq_offset(cm->seq_params->bit_depth, base_qindex, scale); @@ -774,12 +793,19 @@ void av1_init_mb_ur_var_buffer(AV1_COMP *cpi) { AV1_COMMON *cm = &cpi->common; + const int current_size = cpi->frame_info.mb_rows * cpi->frame_info.mb_cols; - if (cpi->mb_delta_q) return; + if (cpi->mb_delta_q && cpi->mb_delta_q_alloc_size < current_size) { + aom_free(cpi->mb_delta_q); + cpi->mb_delta_q = NULL; + cpi->mb_delta_q_alloc_size = 0; + } - CHECK_MEM_ERROR(cm, cpi->mb_delta_q, - aom_calloc(cpi->frame_info.mb_rows * cpi->frame_info.mb_cols, - sizeof(*cpi->mb_delta_q))); + if (!cpi->mb_delta_q) { + CHECK_MEM_ERROR(cm, cpi->mb_delta_q, + aom_calloc(current_size, sizeof(*cpi->mb_delta_q))); + cpi->mb_delta_q_alloc_size = current_size; + } } #if CONFIG_TFLITE
diff --git a/av1/encoder/allintra_vis.h b/av1/encoder/allintra_vis.h index ee0b691..492defa 100644 --- a/av1/encoder/allintra_vis.h +++ b/av1/encoder/allintra_vis.h
@@ -20,6 +20,10 @@ #include "av1/encoder/block.h" #include "av1/encoder/encoder.h" +#ifdef __cplusplus +extern "C" { +#endif + #define MB_WIENER_MT_UNIT_SIZE BLOCK_64X64 void av1_init_mb_wiener_var_buffer(AV1_COMP *cpi); @@ -48,4 +52,8 @@ int av1_get_sbq_variance_boost(const AV1_COMP *const cpi, const MACROBLOCK *x); #endif +#ifdef __cplusplus +} // extern "C" +#endif + #endif // AOM_AV1_ENCODER_ALLINTRA_VIS_H_
diff --git a/av1/encoder/aq_complexity.c b/av1/encoder/aq_complexity.c index 090463b..a497523 100644 --- a/av1/encoder/aq_complexity.c +++ b/av1/encoder/aq_complexity.c
@@ -141,10 +141,10 @@ // Rate depends on fraction of a SB64 in frame (xmis * ymis / bw * bh). // It is converted to bits << AV1_PROB_COST_SHIFT units. - const int64_t num = (int64_t)(cpi->rc.sb64_target_rate * xmis * ymis) + const int64_t num = ((int64_t)cpi->rc.sb64_target_rate * xmis * ymis) << AV1_PROB_COST_SHIFT; const int denom = cm->seq_params->mib_size * cm->seq_params->mib_size; - const int target_rate = (int)(num / denom); + const int64_t target_rate = num / denom; double logvar; double low_var_thresh; const int aq_strength = get_aq_c_strength(cm->quant_params.base_qindex,
diff --git a/av1/encoder/arm/reconinter_enc_neon.c b/av1/encoder/arm/reconinter_enc_neon.c index ac6501f..5c12301 100644 --- a/av1/encoder/arm/reconinter_enc_neon.c +++ b/av1/encoder/arm/reconinter_enc_neon.c
@@ -88,7 +88,7 @@ const int ref_vert_offset = ref_stride * ((SUBPEL_TAPS >> 1) - 1); const int im_vert_offset = im_stride * ((filter_params->taps >> 1) - 1); - assert(im_height <= (MAX_SB_SIZE * 2 + 16) + 16); + assert(im_height < (MAX_SB_SIZE + SUBPEL_TAPS)); aom_convolve8_horiz(ref - ref_vert_offset, ref_stride, im_block, MAX_SB_SIZE, filter_x, 16, NULL, -1, width, im_height); aom_convolve8_vert(im_block + im_vert_offset, MAX_SB_SIZE, comp_pred, width, @@ -178,7 +178,7 @@ av1_get_interp_filter_subpel_kernel(filter, subpel_y_q3 << 1); const int intermediate_height = (((height - 1) * 8 + subpel_y_q3) >> 3) + filter->taps; - assert(intermediate_height <= (MAX_SB_SIZE * 2 + 16) + 16); + assert(intermediate_height < (MAX_SB_SIZE + SUBPEL_TAPS)); aom_highbd_convolve8_horiz_neon( CONVERT_TO_BYTEPTR(ref - ref_stride * ((filter->taps >> 1) - 1)), ref_stride, CONVERT_TO_BYTEPTR(temp), MAX_SB_SIZE, kernel_x, 16, NULL,
diff --git a/av1/encoder/bitstream.c b/av1/encoder/bitstream.c index 53ca4ad..6975195 100644 --- a/av1/encoder/bitstream.c +++ b/av1/encoder/bitstream.c
@@ -2367,13 +2367,20 @@ struct aom_write_bit_buffer *wb) { const int coded_width = cm->superres_upscaled_width - 1; const int coded_height = cm->superres_upscaled_height - 1; + const SequenceHeader *seq_params = cm->seq_params; if (frame_size_override) { - const SequenceHeader *seq_params = cm->seq_params; int num_bits_width = seq_params->num_bits_width; int num_bits_height = seq_params->num_bits_height; aom_wb_write_literal(wb, coded_width, num_bits_width); aom_wb_write_literal(wb, coded_height, num_bits_height); + } else { + if (cm->superres_upscaled_width != seq_params->max_frame_width || + cm->superres_upscaled_height != seq_params->max_frame_height) { + aom_internal_error(cm->error, AOM_CODEC_UNSUP_BITSTREAM, + "Frame dimensions are different from the maximum " + "values when frame_size_override_flag is 0"); + } } write_superres_scale(cm, wb); @@ -2977,10 +2984,7 @@ int frame_size_override_flag = 0; - if (seq_params->reduced_still_picture_hdr) { - assert(cm->superres_upscaled_width == seq_params->max_frame_width && - cm->superres_upscaled_height == seq_params->max_frame_height); - } else { + if (!seq_params->reduced_still_picture_hdr) { if (seq_params->frame_id_numbers_present_flag) { int frame_id_len = seq_params->frame_id_length; aom_wb_write_literal(wb, cm->current_frame_id, frame_id_len); @@ -3055,118 +3059,110 @@ } } - if (current_frame->frame_type == KEY_FRAME) { + if (frame_is_intra_only(cm)) { write_frame_size(cm, frame_size_override_flag, wb); assert(!av1_superres_scaled(cm) || !features->allow_intrabc); if (features->allow_screen_content_tools && !av1_superres_scaled(cm)) aom_wb_write_bit(wb, features->allow_intrabc); - } else { - if (current_frame->frame_type == INTRA_ONLY_FRAME) { - write_frame_size(cm, frame_size_override_flag, wb); - assert(!av1_superres_scaled(cm) || !features->allow_intrabc); - if (features->allow_screen_content_tools && !av1_superres_scaled(cm)) - aom_wb_write_bit(wb, features->allow_intrabc); - } else if (current_frame->frame_type == INTER_FRAME || - frame_is_sframe(cm)) { - MV_REFERENCE_FRAME ref_frame; + } else if (current_frame->frame_type == INTER_FRAME || frame_is_sframe(cm)) { + MV_REFERENCE_FRAME ref_frame; - // NOTE: Error resilient mode turns off frame_refs_short_signaling - // automatically. + // NOTE: Error resilient mode turns off frame_refs_short_signaling + // automatically. #define FRAME_REFS_SHORT_SIGNALING 0 #if FRAME_REFS_SHORT_SIGNALING - current_frame->frame_refs_short_signaling = - seq_params->order_hint_info.enable_order_hint; + current_frame->frame_refs_short_signaling = + seq_params->order_hint_info.enable_order_hint; #endif // FRAME_REFS_SHORT_SIGNALING - if (current_frame->frame_refs_short_signaling) { - // In rtc case when cpi->sf.rt_sf.enable_ref_short_signaling is true, - // we turn on frame_refs_short_signaling when the current frame and - // golden frame are in the same order_hint group, and their relative - // distance is <= 64 (in order to be decodable). + if (current_frame->frame_refs_short_signaling) { + // In rtc case when cpi->sf.rt_sf.enable_ref_short_signaling is true, + // we turn on frame_refs_short_signaling when the current frame and + // golden frame are in the same order_hint group, and their relative + // distance is <= 64 (in order to be decodable). - // For other cases, an example solution for encoder-side - // implementation on frame_refs_short_signaling is also provided in - // this function, where frame_refs_short_signaling is only turned on - // when the encoder side decision on ref frames is identical to that - // at the decoder side. + // For other cases, an example solution for encoder-side + // implementation on frame_refs_short_signaling is also provided in + // this function, where frame_refs_short_signaling is only turned on + // when the encoder side decision on ref frames is identical to that + // at the decoder side. - current_frame->frame_refs_short_signaling = - check_frame_refs_short_signaling( - cm, cpi->sf.rt_sf.enable_ref_short_signaling); - } + current_frame->frame_refs_short_signaling = + check_frame_refs_short_signaling( + cm, cpi->sf.rt_sf.enable_ref_short_signaling); + } - if (seq_params->order_hint_info.enable_order_hint) - aom_wb_write_bit(wb, current_frame->frame_refs_short_signaling); + if (seq_params->order_hint_info.enable_order_hint) + aom_wb_write_bit(wb, current_frame->frame_refs_short_signaling); - if (current_frame->frame_refs_short_signaling) { - const int lst_ref = get_ref_frame_map_idx(cm, LAST_FRAME); - aom_wb_write_literal(wb, lst_ref, REF_FRAMES_LOG2); + if (current_frame->frame_refs_short_signaling) { + const int lst_ref = get_ref_frame_map_idx(cm, LAST_FRAME); + aom_wb_write_literal(wb, lst_ref, REF_FRAMES_LOG2); - const int gld_ref = get_ref_frame_map_idx(cm, GOLDEN_FRAME); - aom_wb_write_literal(wb, gld_ref, REF_FRAMES_LOG2); - } - int first_ref_map_idx = INVALID_IDX; - if (cpi->ppi->rtc_ref.set_ref_frame_config) { - for (ref_frame = LAST_FRAME; ref_frame <= ALTREF_FRAME; ++ref_frame) { - if (cpi->ppi->rtc_ref.reference[ref_frame - 1] == 1) { - first_ref_map_idx = cpi->ppi->rtc_ref.ref_idx[ref_frame - 1]; - break; - } - } - } + const int gld_ref = get_ref_frame_map_idx(cm, GOLDEN_FRAME); + aom_wb_write_literal(wb, gld_ref, REF_FRAMES_LOG2); + } + int first_ref_map_idx = INVALID_IDX; + if (cpi->ppi->rtc_ref.set_ref_frame_config) { for (ref_frame = LAST_FRAME; ref_frame <= ALTREF_FRAME; ++ref_frame) { - assert(get_ref_frame_map_idx(cm, ref_frame) != INVALID_IDX); - if (!current_frame->frame_refs_short_signaling) { - if (cpi->ppi->rtc_ref.set_ref_frame_config && - first_ref_map_idx != INVALID_IDX && - cpi->svc.number_spatial_layers == 1 && - !seq_params->order_hint_info.enable_order_hint) { - // For the usage of set_ref_frame_config: - // for any reference not used set their ref_map_idx - // to the first used reference. - const int map_idx = cpi->ppi->rtc_ref.reference[ref_frame - 1] - ? get_ref_frame_map_idx(cm, ref_frame) - : first_ref_map_idx; - aom_wb_write_literal(wb, map_idx, REF_FRAMES_LOG2); - } else { - aom_wb_write_literal(wb, get_ref_frame_map_idx(cm, ref_frame), - REF_FRAMES_LOG2); - } - } - if (seq_params->frame_id_numbers_present_flag) { - int i = get_ref_frame_map_idx(cm, ref_frame); - int frame_id_len = seq_params->frame_id_length; - int diff_len = seq_params->delta_frame_id_length; - int delta_frame_id_minus_1 = - ((cm->current_frame_id - cm->ref_frame_id[i] + - (1 << frame_id_len)) % - (1 << frame_id_len)) - - 1; - if (delta_frame_id_minus_1 < 0 || - delta_frame_id_minus_1 >= (1 << diff_len)) { - aom_internal_error(cm->error, AOM_CODEC_ERROR, - "Invalid delta_frame_id_minus_1"); - } - aom_wb_write_literal(wb, delta_frame_id_minus_1, diff_len); + if (cpi->ppi->rtc_ref.reference[ref_frame - 1] == 1) { + first_ref_map_idx = cpi->ppi->rtc_ref.ref_idx[ref_frame - 1]; + break; } } - - if (!features->error_resilient_mode && frame_size_override_flag) { - write_frame_size_with_refs(cm, wb); - } else { - write_frame_size(cm, frame_size_override_flag, wb); + } + for (ref_frame = LAST_FRAME; ref_frame <= ALTREF_FRAME; ++ref_frame) { + assert(get_ref_frame_map_idx(cm, ref_frame) != INVALID_IDX); + if (!current_frame->frame_refs_short_signaling) { + if (cpi->ppi->rtc_ref.set_ref_frame_config && + first_ref_map_idx != INVALID_IDX && + cpi->svc.number_spatial_layers == 1 && + !seq_params->order_hint_info.enable_order_hint) { + // For the usage of set_ref_frame_config: + // for any reference not used set their ref_map_idx + // to the first used reference. + const int map_idx = cpi->ppi->rtc_ref.reference[ref_frame - 1] + ? get_ref_frame_map_idx(cm, ref_frame) + : first_ref_map_idx; + aom_wb_write_literal(wb, map_idx, REF_FRAMES_LOG2); + } else { + aom_wb_write_literal(wb, get_ref_frame_map_idx(cm, ref_frame), + REF_FRAMES_LOG2); + } } - - if (!features->cur_frame_force_integer_mv) - aom_wb_write_bit(wb, features->allow_high_precision_mv); - write_frame_interp_filter(features->interp_filter, wb); - aom_wb_write_bit(wb, features->switchable_motion_mode); - if (frame_might_allow_ref_frame_mvs(cm)) { - aom_wb_write_bit(wb, features->allow_ref_frame_mvs); - } else { - assert(features->allow_ref_frame_mvs == 0); + if (seq_params->frame_id_numbers_present_flag) { + int i = get_ref_frame_map_idx(cm, ref_frame); + int frame_id_len = seq_params->frame_id_length; + int diff_len = seq_params->delta_frame_id_length; + int delta_frame_id_minus_1 = + ((cm->current_frame_id - cm->ref_frame_id[i] + + (1 << frame_id_len)) % + (1 << frame_id_len)) - + 1; + if (delta_frame_id_minus_1 < 0 || + delta_frame_id_minus_1 >= (1 << diff_len)) { + aom_internal_error(cm->error, AOM_CODEC_ERROR, + "Invalid delta_frame_id_minus_1"); + } + aom_wb_write_literal(wb, delta_frame_id_minus_1, diff_len); } } + + if (!features->error_resilient_mode && frame_size_override_flag) { + write_frame_size_with_refs(cm, wb); + } else { + write_frame_size(cm, frame_size_override_flag, wb); + } + + if (!features->cur_frame_force_integer_mv) + aom_wb_write_bit(wb, features->allow_high_precision_mv); + write_frame_interp_filter(features->interp_filter, wb); + aom_wb_write_bit(wb, features->switchable_motion_mode); + if (frame_might_allow_ref_frame_mvs(cm)) { + aom_wb_write_bit(wb, features->allow_ref_frame_mvs); + } else { + assert(features->allow_ref_frame_mvs == 0); + } } const int might_bwd_adapt = !(seq_params->reduced_still_picture_hdr) &&
diff --git a/av1/encoder/encode_strategy.c b/av1/encoder/encode_strategy.c index 85931ea..6f9715c 100644 --- a/av1/encoder/encode_strategy.c +++ b/av1/encoder/encode_strategy.c
@@ -1771,8 +1771,7 @@ #if CONFIG_TUNE_VMAF if (!is_stat_generation_stage(cpi) && - (oxcf->tune_cfg.tuning >= AOM_TUNE_VMAF_WITH_PREPROCESSING && - oxcf->tune_cfg.tuning <= AOM_TUNE_VMAF_NEG_MAX_GAIN)) { + is_vmaf_tuning_mode(oxcf->tune_cfg.tuning)) { av1_update_vmaf_curve(cpi); } #endif
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c index e3ef6eb..da81788 100644 --- a/av1/encoder/encodeframe.c +++ b/av1/encoder/encodeframe.c
@@ -2446,13 +2446,15 @@ mt_info->pack_bs_mt_enabled = AOMMIN(mt_info->num_mod_workers[MOD_PACK_BS], cm->tiles.cols * cm->tiles.rows) > 1; - if (oxcf->row_mt && (mt_info->num_workers > 1)) { + if (oxcf->row_mt && (mt_info->num_mod_workers[MOD_ENC] > 1)) { mt_info->row_mt_enabled = 1; enc_row_mt->sync_read_ptr = av1_row_mt_sync_read; enc_row_mt->sync_write_ptr = av1_row_mt_sync_write; av1_encode_tiles_row_mt(cpi); } else { - if (AOMMIN(mt_info->num_workers, cm->tiles.cols * cm->tiles.rows) > 1) { + const int num_workers = AOMMIN(mt_info->num_mod_workers[MOD_ENC], + cm->tiles.cols * cm->tiles.rows); + if (num_workers > 1) { av1_encode_tiles_mt(cpi); } else { // Preallocate the pc_tree for realtime coding to reduce the cost of
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c index 3842190..6ee258a 100644 --- a/av1/encoder/encoder.c +++ b/av1/encoder/encoder.c
@@ -429,6 +429,22 @@ av1_calculate_tile_rows(seq_params, mi_params->mi_rows, tiles); } +static inline void init_frame_info(FRAME_INFO *frame_info, + const AV1_COMMON *const cm) { + const CommonModeInfoParams *const mi_params = &cm->mi_params; + const SequenceHeader *const seq_params = cm->seq_params; + frame_info->frame_width = cm->width; + frame_info->frame_height = cm->height; + frame_info->mi_cols = mi_params->mi_cols; + frame_info->mi_rows = mi_params->mi_rows; + frame_info->mb_cols = mi_params->mb_cols; + frame_info->mb_rows = mi_params->mb_rows; + frame_info->num_mbs = mi_params->MBs; + frame_info->bit_depth = seq_params->bit_depth; + frame_info->subsampling_x = seq_params->subsampling_x; + frame_info->subsampling_y = seq_params->subsampling_y; +} + void av1_update_frame_size(AV1_COMP *cpi) { AV1_COMMON *const cm = &cpi->common; MACROBLOCKD *const xd = &cpi->td.mb.e_mbd; @@ -446,6 +462,7 @@ cpi->ppi->number_spatial_layers)); set_tile_info(cm, &cpi->oxcf.tile_cfg); + init_frame_info(&cpi->frame_info, cm); } static inline int does_level_match(int width, int height, double fps, @@ -646,7 +663,7 @@ seq->enable_intra_edge_filter = oxcf->intra_mode_cfg.enable_intra_edge_filter; seq->enable_filter_intra = oxcf->intra_mode_cfg.enable_filter_intra; - set_bitstream_level_tier(ppi, frm_dim_cfg->width, frm_dim_cfg->height, + set_bitstream_level_tier(ppi, seq->max_frame_width, seq->max_frame_height, oxcf->input_cfg.init_framerate); av1_set_svc_seq_params(ppi); } @@ -741,8 +758,12 @@ alloc_compressor_data(cpi); - cpi->data_alloc_width = cm->width; - cpi->data_alloc_height = cm->height; + cpi->data_alloc_width = oxcf->frm_dim_cfg.forced_max_frame_width + ? oxcf->frm_dim_cfg.forced_max_frame_width + : cm->width; + cpi->data_alloc_height = oxcf->frm_dim_cfg.forced_max_frame_height + ? oxcf->frm_dim_cfg.forced_max_frame_height + : cm->height; cpi->frame_size_related_setup_done = false; // Single thread case: use counts in common. @@ -979,9 +1000,13 @@ } if (x->upsample_pred == NULL) { + // The buffer 'upsampled_pred' is used to store the output of horizontal + // filtering in aom_(highbd_)upsampled_pred() function. As the length of the + // interpolation filter used is SUBPEL_TAPS a buffer size of (MAX_SB_SIZE + + // SUBPEL_TAPS) * MAX_SB_SIZE is allocated. CHECK_MEM_ERROR( cm, x->upsample_pred, - aom_memalign(16, (1 + is_highbitdepth) * ((MAX_SB_SIZE + 16) + 16) * + aom_memalign(16, (1 + is_highbitdepth) * (MAX_SB_SIZE + SUBPEL_TAPS) * MAX_SB_SIZE * sizeof(*x->upsample_pred))); x->e_mbd.tmp_upsample_pred = x->upsample_pred; } @@ -1038,8 +1063,12 @@ cpi->td.firstpass_ctx = NULL; alloc_compressor_data(cpi); realloc_segmentation_maps(cpi); - cpi->data_alloc_width = cm->width; - cpi->data_alloc_height = cm->height; + cpi->data_alloc_width = oxcf->frm_dim_cfg.forced_max_frame_width + ? oxcf->frm_dim_cfg.forced_max_frame_width + : cm->width; + cpi->data_alloc_height = oxcf->frm_dim_cfg.forced_max_frame_height + ? oxcf->frm_dim_cfg.forced_max_frame_height + : cm->height; cpi->frame_size_related_setup_done = false; } av1_update_frame_size(cpi); @@ -1048,6 +1077,12 @@ if (cpi->oxcf.q_cfg.aq_mode == CYCLIC_REFRESH_AQ) { int mi_rows = cpi->common.mi_params.mi_rows; int mi_cols = cpi->common.mi_params.mi_cols; + if (cpi->oxcf.frm_dim_cfg.forced_max_frame_width) { + mi_cols = size_in_mi(cpi->oxcf.frm_dim_cfg.forced_max_frame_width); + } + if (cpi->oxcf.frm_dim_cfg.forced_max_frame_height) { + mi_rows = size_in_mi(cpi->oxcf.frm_dim_cfg.forced_max_frame_height); + } aom_free(cpi->cyclic_refresh->map); CHECK_MEM_ERROR( cm, cpi->cyclic_refresh->map, @@ -1093,22 +1128,6 @@ #endif // CONFIG_REALTIME_ONLY } -static inline void init_frame_info(FRAME_INFO *frame_info, - const AV1_COMMON *const cm) { - const CommonModeInfoParams *const mi_params = &cm->mi_params; - const SequenceHeader *const seq_params = cm->seq_params; - frame_info->frame_width = cm->width; - frame_info->frame_height = cm->height; - frame_info->mi_cols = mi_params->mi_cols; - frame_info->mi_rows = mi_params->mi_rows; - frame_info->mb_cols = mi_params->mb_cols; - frame_info->mb_rows = mi_params->mb_rows; - frame_info->num_mbs = mi_params->MBs; - frame_info->bit_depth = seq_params->bit_depth; - frame_info->subsampling_x = seq_params->subsampling_x; - frame_info->subsampling_y = seq_params->subsampling_y; -} - static inline void init_frame_index_set(FRAME_INDEX_SET *frame_index_set) { frame_index_set->show_frame_count = 0; } @@ -1616,7 +1635,9 @@ cpi->consec_zero_mv_alloc_size = consec_zero_mv_alloc_size; cpi->mb_weber_stats = NULL; + cpi->mb_weber_stats_alloc_size = 0; cpi->mb_delta_q = NULL; + cpi->mb_delta_q_alloc_size = 0; cpi->palette_pixel_num = 0; cpi->scaled_last_source_available = 0; @@ -2625,8 +2646,12 @@ cpi->td.firstpass_ctx = NULL; alloc_compressor_data(cpi); realloc_segmentation_maps(cpi); - cpi->data_alloc_width = cm->width; - cpi->data_alloc_height = cm->height; + cpi->data_alloc_width = cpi->oxcf.frm_dim_cfg.forced_max_frame_width + ? cpi->oxcf.frm_dim_cfg.forced_max_frame_width + : cm->width; + cpi->data_alloc_height = cpi->oxcf.frm_dim_cfg.forced_max_frame_height + ? cpi->oxcf.frm_dim_cfg.forced_max_frame_height + : cm->height; cpi->frame_size_related_setup_done = false; } alloc_mb_mode_info_buffers(cpi); @@ -3392,8 +3417,7 @@ } #if CONFIG_TUNE_VMAF - if (oxcf->tune_cfg.tuning >= AOM_TUNE_VMAF_WITH_PREPROCESSING && - oxcf->tune_cfg.tuning <= AOM_TUNE_VMAF_NEG_MAX_GAIN) { + if (is_vmaf_tuning_mode(oxcf->tune_cfg.tuning)) { cpi->vmaf_info.original_qindex = q; q = av1_get_vmaf_base_qindex(cpi, q); } @@ -3597,8 +3621,7 @@ } #if CONFIG_TUNE_VMAF - if (oxcf->tune_cfg.tuning >= AOM_TUNE_VMAF_WITH_PREPROCESSING && - oxcf->tune_cfg.tuning <= AOM_TUNE_VMAF_NEG_MAX_GAIN) { + if (is_vmaf_tuning_mode(oxcf->tune_cfg.tuning)) { q = cpi->vmaf_info.original_qindex; } #endif @@ -4326,8 +4349,7 @@ } #endif #if CONFIG_TUNE_VMAF - else if (oxcf->tune_cfg.tuning == AOM_TUNE_VMAF_WITHOUT_PREPROCESSING || - oxcf->tune_cfg.tuning == AOM_TUNE_VMAF_MAX_GAIN || + else if (oxcf->tune_cfg.tuning == AOM_TUNE_VMAF_MAX_GAIN || oxcf->tune_cfg.tuning == AOM_TUNE_VMAF_NEG_MAX_GAIN) { av1_set_mb_vmaf_rdmult_scaling(cpi); } @@ -4770,11 +4792,9 @@ #if CONFIG_TUNE_VMAF if (!is_stat_generation_stage(cpi) && - cpi->oxcf.tune_cfg.tuning == AOM_TUNE_VMAF_WITH_PREPROCESSING) { - av1_vmaf_frame_preprocessing(cpi, sd); - } - if (!is_stat_generation_stage(cpi) && cpi->oxcf.tune_cfg.tuning == AOM_TUNE_VMAF_MAX_GAIN) { + // Future work: frame-level preprocessing can be used here if the + // performance is similar. av1_vmaf_blk_preprocessing(cpi, sd); } #endif
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h index 82e9cab..1b13fc9 100644 --- a/av1/encoder/encoder.h +++ b/av1/encoder/encoder.h
@@ -3586,6 +3586,12 @@ WeberStats *mb_weber_stats; /*! + * Allocated memory size (number of elements) for |mb_weber_stats| (and + * |prep_rate_estimates|, |ext_rate_distribution|). + */ + int mb_weber_stats_alloc_size; + + /*! * Buffer to store rate cost estimates for each macro block (8x8) in the * preprocessing stage used in allintra mode. */ @@ -3618,6 +3624,11 @@ int *mb_delta_q; /*! + * Allocated memory size (number of elements) for |mb_delta_q|. + */ + int mb_delta_q_alloc_size; + + /*! * Flag to indicate that current frame is dropped. */ bool is_dropped_frame; @@ -4444,6 +4455,12 @@ cm->show_frame && !cpi->is_dropped_frame; } +// Check if VMAF tuning is enabled. +static inline int is_vmaf_tuning_mode(const aom_tune_metric tuning) { + return tuning == AOM_TUNE_VMAF_MAX_GAIN || + tuning == AOM_TUNE_VMAF_NEG_MAX_GAIN; +} + static inline int is_frame_resize_pending(const AV1_COMP *const cpi) { const ResizePendingParams *const resize_pending_params = &cpi->resize_pending_params;
diff --git a/av1/encoder/encoder_alloc.h b/av1/encoder/encoder_alloc.h index 97b5273..ac4a873 100644 --- a/av1/encoder/encoder_alloc.h +++ b/av1/encoder/encoder_alloc.h
@@ -15,6 +15,7 @@ #include "av1/encoder/block.h" #include "av1/encoder/encodeframe_utils.h" #include "av1/encoder/encoder.h" +#include "av1/encoder/encoder_utils.h" #include "av1/encoder/encodetxb.h" #include "av1/encoder/ethread.h" #include "av1/encoder/global_motion_facade.h" @@ -105,22 +106,29 @@ static inline void realloc_segmentation_maps(AV1_COMP *cpi) { AV1_COMMON *const cm = &cpi->common; CommonModeInfoParams *const mi_params = &cm->mi_params; + int max_mi_cols = mi_params->mi_cols; + int max_mi_rows = mi_params->mi_rows; + if (cpi->oxcf.frm_dim_cfg.forced_max_frame_width) { + max_mi_cols = size_in_mi(cpi->oxcf.frm_dim_cfg.forced_max_frame_width); + } + if (cpi->oxcf.frm_dim_cfg.forced_max_frame_height) { + max_mi_rows = size_in_mi(cpi->oxcf.frm_dim_cfg.forced_max_frame_height); + } // Create the encoder segmentation map and set all entries to 0 aom_free(cpi->enc_seg.map); CHECK_MEM_ERROR(cm, cpi->enc_seg.map, - aom_calloc(mi_params->mi_rows * mi_params->mi_cols, 1)); + aom_calloc(max_mi_rows * max_mi_cols, 1)); // Create a map used for cyclic background refresh. if (cpi->cyclic_refresh) av1_cyclic_refresh_free(cpi->cyclic_refresh); - CHECK_MEM_ERROR( - cm, cpi->cyclic_refresh, - av1_cyclic_refresh_alloc(mi_params->mi_rows, mi_params->mi_cols)); + CHECK_MEM_ERROR(cm, cpi->cyclic_refresh, + av1_cyclic_refresh_alloc(max_mi_rows, max_mi_cols)); // Create a map used to mark inactive areas. aom_free(cpi->active_map.map); CHECK_MEM_ERROR(cm, cpi->active_map.map, - aom_calloc(mi_params->mi_rows * mi_params->mi_cols, 1)); + aom_calloc(max_mi_rows * max_mi_cols, 1)); } static inline void alloc_obmc_buffers(OBMCBuffer *obmc_buffer, @@ -365,6 +373,7 @@ aom_free(cpi->mb_weber_stats); cpi->mb_weber_stats = NULL; + cpi->mb_weber_stats_alloc_size = 0; if (cpi->oxcf.enable_rate_guide_deltaq) { aom_free(cpi->prep_rate_estimates); @@ -376,6 +385,7 @@ aom_free(cpi->mb_delta_q); cpi->mb_delta_q = NULL; + cpi->mb_delta_q_alloc_size = 0; #if !CONFIG_REALTIME_ONLY av1_free_tpl_gop_stats(&cpi->extrc_tpl_gop_stats);
diff --git a/av1/encoder/encodetxb.c b/av1/encoder/encodetxb.c index f0607b6..6066f9e 100644 --- a/av1/encoder/encodetxb.c +++ b/av1/encoder/encodetxb.c
@@ -21,6 +21,7 @@ #include "av1/encoder/bitstream.h" #include "av1/encoder/cost.h" #include "av1/encoder/encodeframe.h" +#include "av1/encoder/encoder_utils.h" #include "av1/encoder/hash.h" #include "av1/encoder/rdopt.h" #include "av1/encoder/tokenize.h" @@ -28,10 +29,18 @@ void av1_alloc_txb_buf(AV1_COMP *cpi) { AV1_COMMON *cm = &cpi->common; CoeffBufferPool *coeff_buf_pool = &cpi->coeff_buffer_pool; + int mi_rows = cm->mi_params.mi_rows; + int mi_cols = cm->mi_params.mi_cols; + if (cpi->oxcf.frm_dim_cfg.forced_max_frame_width) { + mi_cols = size_in_mi(cpi->oxcf.frm_dim_cfg.forced_max_frame_width); + } + if (cpi->oxcf.frm_dim_cfg.forced_max_frame_height) { + mi_rows = size_in_mi(cpi->oxcf.frm_dim_cfg.forced_max_frame_height); + } const int num_sb_rows = - CEIL_POWER_OF_TWO(cm->mi_params.mi_rows, cm->seq_params->mib_size_log2); + CEIL_POWER_OF_TWO(mi_rows, cm->seq_params->mib_size_log2); const int num_sb_cols = - CEIL_POWER_OF_TWO(cm->mi_params.mi_cols, cm->seq_params->mib_size_log2); + CEIL_POWER_OF_TWO(mi_cols, cm->seq_params->mib_size_log2); const int size = num_sb_rows * num_sb_cols; const int num_planes = av1_num_planes(cm); const int subsampling_x = cm->seq_params->subsampling_x; @@ -899,8 +908,11 @@ int mi_col) { const AV1_COMMON *const cm = &cpi->common; const int mib_size_log2 = cm->seq_params->mib_size_log2; - const int stride = - CEIL_POWER_OF_TWO(cm->mi_params.mi_cols, cm->seq_params->mib_size_log2); + int mi_cols = cm->mi_params.mi_cols; + if (cpi->oxcf.frm_dim_cfg.forced_max_frame_width) { + mi_cols = size_in_mi(cpi->oxcf.frm_dim_cfg.forced_max_frame_width); + } + const int stride = CEIL_POWER_OF_TWO(mi_cols, cm->seq_params->mib_size_log2); const int offset = (mi_row >> mib_size_log2) * stride + (mi_col >> mib_size_log2); return cpi->coeff_buffer_base + offset;
diff --git a/av1/encoder/ethread.c b/av1/encoder/ethread.c index d46b3ee..581294c 100644 --- a/av1/encoder/ethread.c +++ b/av1/encoder/ethread.c
@@ -750,8 +750,9 @@ return 1; } -static int enc_worker_hook(void *arg1, void *unused) { +static int enc_worker_hook(void *arg1, void *arg2) { EncWorkerData *const thread_data = (EncWorkerData *)arg1; + const int num_workers = (int)(intptr_t)arg2; AV1_COMP *const cpi = thread_data->cpi; MACROBLOCKD *const xd = &thread_data->td->mb.e_mbd; struct aom_internal_error_info *const error_info = &thread_data->error_info; @@ -760,8 +761,6 @@ const int tile_rows = cm->tiles.rows; int t; - (void)unused; - xd->error_info = error_info; // The jmp_buf is valid only for the duration of the function that calls @@ -784,8 +783,7 @@ thread_data->td->pc_root = NULL; } - for (t = thread_data->start; t < tile_rows * tile_cols; - t += cpi->mt_info.num_workers) { + for (t = thread_data->start; t < tile_rows * tile_cols; t += num_workers) { int tile_row = t / tile_cols; int tile_col = t % tile_cols; @@ -996,7 +994,7 @@ AOM_CHECK_MEM_ERROR( &ppi->error, td->upsample_pred, - aom_memalign(16, (1 + is_highbitdepth) * ((MAX_SB_SIZE + 16) + 16) * + aom_memalign(16, (1 + is_highbitdepth) * (MAX_SB_SIZE + SUBPEL_TAPS) * MAX_SB_SIZE * sizeof(*td->upsample_pred))); if (!is_first_pass && i < num_enc_workers) { @@ -1039,12 +1037,10 @@ } } - if (is_gradient_caching_for_hog_enabled(ppi->cpi)) { - const int plane_types = PLANE_TYPES >> ppi->seq_params.monochrome; - AOM_CHECK_MEM_ERROR(&ppi->error, td->pixel_gradient_info, - aom_malloc(sizeof(*td->pixel_gradient_info) * - plane_types * MAX_SB_SQUARE)); - } + const int plane_types = PLANE_TYPES >> ppi->seq_params.monochrome; + AOM_CHECK_MEM_ERROR(&ppi->error, td->pixel_gradient_info, + aom_malloc(sizeof(*td->pixel_gradient_info) * + plane_types * MAX_SB_SQUARE)); if (is_src_var_for_4x4_sub_blocks_caching_enabled(ppi->cpi)) { const BLOCK_SIZE sb_size = ppi->cpi->common.seq_params->sb_size; @@ -1588,7 +1584,7 @@ worker->hook = hook; worker->data1 = thread_data; - worker->data2 = NULL; + worker->data2 = (void *)(intptr_t)num_workers; thread_data->thread_id = i; // Set the starting tile for each thread.
diff --git a/av1/encoder/intra_mode_search.c b/av1/encoder/intra_mode_search.c index db8d9c8..cad0cdc 100644 --- a/av1/encoder/intra_mode_search.c +++ b/av1/encoder/intra_mode_search.c
@@ -998,6 +998,19 @@ intra_mode_info_cost_uv(cpi, x, mbmi, bsize, mode_cost); this_rd = RDCOST(x->rdmult, this_rate, tokenonly_rd_stats.dist); +#if CONFIG_AV1_HIGHBITDEPTH + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + const int is_smooth_uv_mode = + (uv_mode == UV_SMOOTH_PRED || uv_mode == UV_SMOOTH_V_PRED || + uv_mode == UV_SMOOTH_H_PRED); + if (is_smooth_uv_mode) { + if (this_rd < INT64_MAX) { + this_rd += (this_rd >> 2); + } + } + } +#endif + if (this_rd < best_rd) { best_mbmi = *mbmi; best_rd = this_rd;
diff --git a/av1/encoder/lookahead.c b/av1/encoder/lookahead.c index ee2e065..7dcabf9 100644 --- a/av1/encoder/lookahead.c +++ b/av1/encoder/lookahead.c
@@ -105,7 +105,7 @@ int uv_height = src->uv_crop_height; int subsampling_x = src->subsampling_x; int subsampling_y = src->subsampling_y; - int larger_dimensions, new_dimensions; + int new_dimensions; assert(ctx->read_ctxs[ENCODE_STAGE].valid == 1); if (ctx->read_ctxs[ENCODE_STAGE].sz + ctx->max_pre_frames > ctx->max_sz) @@ -122,31 +122,13 @@ height != buf->img.y_crop_height || uv_width != buf->img.uv_crop_width || uv_height != buf->img.uv_crop_height; - larger_dimensions = - width > buf->img.y_crop_width || height > buf->img.y_crop_height || - uv_width > buf->img.uv_crop_width || uv_height > buf->img.uv_crop_height; - assert(!larger_dimensions || new_dimensions); - if (larger_dimensions) { - YV12_BUFFER_CONFIG new_img; - memset(&new_img, 0, sizeof(new_img)); - if (aom_alloc_frame_buffer(&new_img, width, height, subsampling_x, - subsampling_y, use_highbitdepth, - AOM_BORDER_IN_PIXELS, 0, alloc_pyramid, 0)) + if (new_dimensions) { + if (aom_realloc_frame_buffer(&buf->img, width, height, subsampling_x, + subsampling_y, use_highbitdepth, + AOM_BORDER_IN_PIXELS, 0, NULL, NULL, NULL, + alloc_pyramid, 0)) return 1; - aom_free_frame_buffer(&buf->img); - buf->img = new_img; - } else if (new_dimensions) { - buf->img.y_width = src->y_width; - buf->img.y_height = src->y_height; - buf->img.uv_width = src->uv_width; - buf->img.uv_height = src->uv_height; - buf->img.y_crop_width = src->y_crop_width; - buf->img.y_crop_height = src->y_crop_height; - buf->img.uv_crop_width = src->uv_crop_width; - buf->img.uv_crop_height = src->uv_crop_height; - buf->img.subsampling_x = src->subsampling_x; - buf->img.subsampling_y = src->subsampling_y; } av1_copy_and_extend_frame(src, &buf->img);
diff --git a/av1/encoder/partition_search.c b/av1/encoder/partition_search.c index b5f1d6a..c038838 100644 --- a/av1/encoder/partition_search.c +++ b/av1/encoder/partition_search.c
@@ -637,8 +637,7 @@ } #endif #if CONFIG_TUNE_VMAF - else if (cpi->oxcf.tune_cfg.tuning == AOM_TUNE_VMAF_WITHOUT_PREPROCESSING || - cpi->oxcf.tune_cfg.tuning == AOM_TUNE_VMAF_MAX_GAIN || + else if (cpi->oxcf.tune_cfg.tuning == AOM_TUNE_VMAF_MAX_GAIN || cpi->oxcf.tune_cfg.tuning == AOM_TUNE_VMAF_NEG_MAX_GAIN) { av1_set_vmaf_rdmult(cpi, x, bsize, mi_row, mi_col, &x->rdmult); }
diff --git a/av1/encoder/pass2_strategy.c b/av1/encoder/pass2_strategy.c index 7af5bcf..42e9186 100644 --- a/av1/encoder/pass2_strategy.c +++ b/av1/encoder/pass2_strategy.c
@@ -4541,8 +4541,7 @@ p_rc->active_best_quality[i] = cpi->common.quant_params.base_qindex; #if CONFIG_TUNE_VMAF if (cpi->vmaf_info.original_qindex != -1 && - (cpi->oxcf.tune_cfg.tuning >= AOM_TUNE_VMAF_WITH_PREPROCESSING && - cpi->oxcf.tune_cfg.tuning <= AOM_TUNE_VMAF_NEG_MAX_GAIN)) { + is_vmaf_tuning_mode(cpi->oxcf.tune_cfg.tuning)) { p_rc->active_best_quality[i] = cpi->vmaf_info.original_qindex; } #endif @@ -4601,13 +4600,12 @@ } twopass->extend_maxq -= 1; // Overshoot - } else if ((rc_cfg->over_shoot_pct < 100) && + } else if ((rc_cfg->over_shoot_pct <= 100) && (p_rc->rolling_actual_bits > p_rc->rolling_target_bits)) { int pct_error = ((p_rc->rolling_actual_bits - p_rc->rolling_target_bits) * 100) / p_rc->rolling_target_bits; - pct_error = clamp(pct_error, 0, 100); if ((pct_error >= rc_cfg->over_shoot_pct) && (p_rc->rate_error_estimate < 0)) { twopass->extend_maxq += 1; @@ -4633,13 +4631,21 @@ // frame is unexpectedly almost perfectly predicted by the ARF or GF // but not very well predcited by the previous frame. if (!frame_is_kf_gf_arf(cpi) && !cpi->rc.is_src_frame_alt_ref) { - int fast_extra_thresh = rc->base_frame_target / HIGH_UNDERSHOOT_RATIO; - if (rc->projected_frame_size < fast_extra_thresh) { - p_rc->vbr_bits_off_target_fast += - fast_extra_thresh - rc->projected_frame_size; - p_rc->vbr_bits_off_target_fast = - AOMMIN(p_rc->vbr_bits_off_target_fast, - (4 * (int64_t)rc->avg_frame_bandwidth)); +#if CONFIG_AV1_HIGHBITDEPTH + if (cpi->common.seq_params->bit_depth > 8 && + cpi->oxcf.algo_cfg.sharpness == 3 && p_rc->vbr_bits_off_target < 0) { + p_rc->vbr_bits_off_target_fast = 0; + } else +#endif + { + int fast_extra_thresh = rc->base_frame_target / HIGH_UNDERSHOOT_RATIO; + if (rc->projected_frame_size < fast_extra_thresh) { + p_rc->vbr_bits_off_target_fast += + fast_extra_thresh - rc->projected_frame_size; + p_rc->vbr_bits_off_target_fast = + AOMMIN(p_rc->vbr_bits_off_target_fast, + (4 * (int64_t)rc->avg_frame_bandwidth)); + } } }
diff --git a/av1/encoder/pickcdef.c b/av1/encoder/pickcdef.c index 8ece9331..ed6db25 100644 --- a/av1/encoder/pickcdef.c +++ b/av1/encoder/pickcdef.c
@@ -899,7 +899,7 @@ // Allocate CDEF search context buffers. cdef_alloc_data(cm, cdef_search_ctx); // Frame level mse calculation. - if (cpi->mt_info.num_workers > 1) { + if (cpi->mt_info.num_mod_workers[MOD_CDEF_SEARCH] > 1) { av1_cdef_mse_calc_frame_mt(cpi); } else { cdef_mse_calc_frame(cdef_search_ctx, cm->error, adaptive_cdef_mode);
diff --git a/av1/encoder/ratectrl.c b/av1/encoder/ratectrl.c index f68c78f..03cb5c9 100644 --- a/av1/encoder/ratectrl.c +++ b/av1/encoder/ratectrl.c
@@ -365,8 +365,9 @@ LAYER_IDS_TO_IDX(svc->spatial_layer_id, i, svc->number_temporal_layers); LAYER_CONTEXT *lc = &svc->layer_context[layer]; PRIMARY_RATE_CONTROL *lp_rc = &lc->p_rc; - lp_rc->bits_off_target += - (int)round(lc->target_bandwidth / lc->framerate) - encoded_frame_size; + lp_rc->bits_off_target += (int64_t)saturate_cast_double_to_int( + round(lc->target_bandwidth / lc->framerate)) - + encoded_frame_size; // Clip buffer level to maximum buffer size for the layer. lp_rc->bits_off_target = AOMMIN(lp_rc->bits_off_target, lp_rc->maximum_buffer_size); @@ -2788,14 +2789,18 @@ simulate_parallel_frame ? cpi->ppi->p_rc.temp_vbr_bits_off_target_fast : p_rc->vbr_bits_off_target_fast; #endif - // Fast redistribution of bits arising from massive local undershoot. - // Don't do it for kf,arf,gf or overlay frames. if (!frame_is_kf_gf_arf(cpi) && #if CONFIG_FPMT_TEST vbr_bits_off_target_fast && #else p_rc->vbr_bits_off_target_fast && #endif +#if CONFIG_AV1_HIGHBITDEPTH + (cpi->common.seq_params->bit_depth > 8 && + cpi->oxcf.algo_cfg.sharpness == 3 + ? vbr_bits_off_target >= 0 + : 1) && +#endif !rc->is_src_frame_alt_ref) { int64_t one_frame_bits = AOMMAX(rc->avg_frame_bandwidth, frame_target); int64_t fast_extra_bits;
diff --git a/av1/encoder/rdopt.c b/av1/encoder/rdopt.c index b0ea574..053edda 100644 --- a/av1/encoder/rdopt.c +++ b/av1/encoder/rdopt.c
@@ -644,8 +644,8 @@ } #endif // CONFIG_AV1_HIGHBITDEPTH -static void get_variance_stats(const MACROBLOCK *x, int64_t *src_var, - int64_t *rec_var) { +void av1_get_variance_stats(const MACROBLOCK *x, int64_t *src_var, + int64_t *rec_var) { #if CONFIG_AV1_HIGHBITDEPTH if (is_cur_buf_hbd(&x->e_mbd)) { get_variance_stats_hbd(x, src_var, rec_var); @@ -665,6 +665,54 @@ *src_var = aom_calc_variance_stat(p->src.buf, p->src.stride, bw, bh); } +int av1_is_skip_txfm_penalized(const AV1_COMP *cpi, const MACROBLOCK *x, + BLOCK_SIZE bsize) { +#if CONFIG_AV1_HIGHBITDEPTH + const MACROBLOCKD *xd = &x->e_mbd; + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3 && + !frame_is_kf_gf_arf(cpi)) { + int64_t src_var, rec_var; + av1_get_variance_stats(x, &src_var, &rec_var); + if (src_var > rec_var) { + const int num_pixels = block_size_wide[bsize] * block_size_high[bsize]; + const int64_t src_var_per_px = src_var / num_pixels; + if (src_var_per_px >= 0) return 1; + } + } +#else + (void)cpi; + (void)x; + (void)bsize; +#endif + return 0; +} + +void av1_get_tx_skip_dist(const AV1_COMP *cpi, const MACROBLOCK *x, + BLOCK_SIZE bsize, int64_t dist, int64_t sse, + int64_t *no_skip_dist, int64_t *skip_dist) { + *no_skip_dist = dist; + *skip_dist = sse; +#if CONFIG_AV1_HIGHBITDEPTH + const MACROBLOCKD *xd = &x->e_mbd; + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3 && + !frame_is_kf_gf_arf(cpi)) { + int64_t src_var, rec_var; + av1_get_variance_stats(x, &src_var, &rec_var); + if (src_var > rec_var) { + int64_t var_offset = src_var - rec_var; + const int num_pixels = block_size_wide[bsize] * block_size_high[bsize]; + const int64_t src_var_per_px = src_var / num_pixels; + *no_skip_dist += var_offset; + *skip_dist += (src_var_per_px >= 0) ? (var_offset * 4) : var_offset; + } + } +#else + (void)cpi; + (void)x; + (void)bsize; +#endif +} + static void adjust_rdcost(const AV1_COMP *cpi, const MACROBLOCK *x, RD_STATS *rd_cost, bool is_inter_pred) { if ((cpi->oxcf.tune_cfg.tuning == AOM_TUNE_IQ || @@ -688,12 +736,56 @@ return; } +#if CONFIG_AV1_HIGHBITDEPTH + const MACROBLOCKD *xd = &x->e_mbd; + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + if (frame_is_intra_only(&cpi->common)) return; + + int64_t src_var, rec_var; + av1_get_variance_stats(x, &src_var, &rec_var); + + int64_t var_offset = (src_var > rec_var) ? (src_var - rec_var) : 0; + + const MB_MODE_INFO *mbmi = xd->mi[0]; + const int is_smooth_intra_mode = + (mbmi->mode == DC_PRED || mbmi->mode == SMOOTH_PRED || + mbmi->mode == SMOOTH_V_PRED || mbmi->mode == SMOOTH_H_PRED || + mbmi->mode == PAETH_PRED); + + const int is_skip_txfm = mbmi->skip_txfm || rd_cost->skip_txfm; + const int num_pixels = + block_size_wide[mbmi->bsize] * block_size_high[mbmi->bsize]; + const int64_t src_var_per_px = src_var / num_pixels; + + if (var_offset > 0 && + ((is_skip_txfm && src_var_per_px >= 0) || is_smooth_intra_mode || + is_interintra_mode(mbmi) || has_second_ref(mbmi))) { + var_offset *= 4; + } + + if (is_inter_pred && !has_second_ref(mbmi)) { + const int mv_mag = + abs(mbmi->mv[0].as_mv.row) + abs(mbmi->mv[0].as_mv.col); + if (mv_mag > 0 && src_var_per_px < 64) { + var_offset += (int64_t)mv_mag * (64 - src_var_per_px); + } + } + + if (var_offset <= 0) return; + + rd_cost->dist += var_offset; + + rd_cost->rdcost = RDCOST(x->rdmult, rd_cost->rate, rd_cost->dist); + return; + } +#endif + if (cpi->oxcf.algo_cfg.sharpness != 3) return; if (frame_is_kf_gf_arf(cpi)) return; int64_t src_var, rec_var; - get_variance_stats(x, &src_var, &rec_var); + av1_get_variance_stats(x, &src_var, &rec_var); if (src_var <= rec_var) return; @@ -713,12 +805,54 @@ return; } +#if CONFIG_AV1_HIGHBITDEPTH + const MACROBLOCKD *xd = &x->e_mbd; + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + if (frame_is_intra_only(&cpi->common)) return; + + int64_t src_var, rec_var; + av1_get_variance_stats(x, &src_var, &rec_var); + + int64_t var_offset = (src_var > rec_var) ? (src_var - rec_var) : 0; + + const MB_MODE_INFO *mbmi = xd->mi[0]; + const int is_smooth_intra_mode = + (mbmi->mode == DC_PRED || mbmi->mode == SMOOTH_PRED || + mbmi->mode == SMOOTH_V_PRED || mbmi->mode == SMOOTH_H_PRED || + mbmi->mode == PAETH_PRED); + + const int is_skip_txfm = mbmi->skip_txfm; + const int num_pixels = + block_size_wide[mbmi->bsize] * block_size_high[mbmi->bsize]; + const int64_t src_var_per_px = src_var / num_pixels; + + if (var_offset > 0 && + ((is_skip_txfm && src_var_per_px >= 0) || is_smooth_intra_mode || + is_interintra_mode(mbmi) || has_second_ref(mbmi))) { + var_offset *= 4; + } + + if (is_inter_pred && !has_second_ref(mbmi)) { + const int mv_mag = + abs(mbmi->mv[0].as_mv.row) + abs(mbmi->mv[0].as_mv.col); + if (mv_mag > 0 && src_var_per_px < 64) { + var_offset += (int64_t)mv_mag * (64 - src_var_per_px); + } + } + + if (var_offset <= 0) return; + + *rd_cost += RDCOST(x->rdmult, 0, var_offset); + return; + } +#endif + if (cpi->oxcf.algo_cfg.sharpness != 3) return; if (frame_is_kf_gf_arf(cpi)) return; int64_t src_var, rec_var; - get_variance_stats(x, &src_var, &rec_var); + av1_get_variance_stats(x, &src_var, &rec_var); if (src_var <= rec_var) return; @@ -1712,6 +1846,8 @@ rd_stats_uv, mbmi); } mbmi->skip_txfm = 0; + rd_stats->skip_txfm = 0; + if (rd_stats_y) rd_stats_y->skip_txfm = 0; increase_motion_mode_rdstats(cpi, mbmi, rd_stats, NULL, NULL); } else { @@ -3853,14 +3989,18 @@ this_sse = rd_stats_y.sse + rd_stats_uv.sse * 15 / 16; } + int64_t no_skip_dist, skip_sse; + av1_get_tx_skip_dist(cpi, x, mbmi->bsize, this_dist, this_sse, + &no_skip_dist, &skip_sse); + if (is_inter_mode(mbmi->mode) && (!cpi->oxcf.algo_cfg.sharpness || !comp_pred) && RDCOST(x->rdmult, mode_costs->skip_txfm_cost[skip_ctx][0] + rd_stats_y.rate + rd_stats_uv.rate, - this_dist) > RDCOST(x->rdmult, - mode_costs->skip_txfm_cost[skip_ctx][1], - this_sse)) { + no_skip_dist) > RDCOST(x->rdmult, + mode_costs->skip_txfm_cost[skip_ctx][1], + skip_sse)) { skip_blk = 1; rd_stats_y.rate = mode_costs->skip_txfm_cost[skip_ctx][1]; rd_stats_uv.rate = 0; @@ -3875,7 +4015,23 @@ &rd_stats_uv); int this_rate = rd_stats.rate + rd_stats_y.rate + rd_stats_uv.rate - winner_rate_y - winner_rate_uv; - int64_t this_rd = RDCOST(x->rdmult, this_rate, this_dist); + int64_t this_rd; +#if CONFIG_AV1_HIGHBITDEPTH + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + RD_STATS tmp_rd_stats; + av1_init_rd_stats(&tmp_rd_stats); + tmp_rd_stats.rate = this_rate; + tmp_rd_stats.dist = this_dist; + tmp_rd_stats.sse = this_sse; + tmp_rd_stats.skip_txfm = skip_blk; + adjust_rdcost(cpi, x, &tmp_rd_stats, is_inter_mode(mbmi->mode)); + this_rd = tmp_rd_stats.rdcost; + this_dist = tmp_rd_stats.dist; + } else +#endif + { + this_rd = RDCOST(x->rdmult, this_rate, this_dist); + } if (best_rd > this_rd) { *best_mbmode = *mbmi; *best_mode_index = winner_mode_index; @@ -4704,6 +4860,7 @@ mbmi->motion_mode = SIMPLE_TRANSLATION; mbmi->interintra_mode = (INTERINTRA_MODE)(II_DC_PRED - 1); set_default_interp_filters(mbmi, cm->features.interp_filter); + mbmi->skip_txfm = 0; } static inline void collect_single_states(MACROBLOCK *x, @@ -5663,7 +5820,13 @@ top_intra_model_rd[i] = INT64_MAX; } - if (cpi->oxcf.algo_cfg.sharpness) { +#if CONFIG_AV1_HIGHBITDEPTH + const int allow_larger_intra = + xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3; +#else + const int allow_larger_intra = 0; +#endif + if (cpi->oxcf.algo_cfg.sharpness && !allow_larger_intra) { int bh = mi_size_high[bsize]; int bw = mi_size_wide[bsize]; if (bh > 4 || bw > 4) return; @@ -6215,6 +6378,7 @@ const int comp_pred = second_ref_frame > INTRA_FRAME; txfm_info->skip_txfm = 0; + mbmi->skip_txfm = 0; sf_args.num_single_modes_processed += is_single_pred; #if CONFIG_COLLECT_COMPONENT_TIMING start_timing(cpi, skip_inter_mode_time); @@ -6300,9 +6464,6 @@ ref_frame_rd[ref_frame] = this_rd; } - adjust_cost(cpi, x, &this_rd, /*is_inter_pred=*/true); - adjust_rdcost(cpi, x, &rd_stats, /*is_inter_pred=*/true); - // Did this mode help, i.e., is it the new best mode if (this_rd < search_state.best_rd) { assert(IMPLIES(comp_pred,
diff --git a/av1/encoder/rdopt_utils.h b/av1/encoder/rdopt_utils.h index 04d46d0..2082163 100644 --- a/av1/encoder/rdopt_utils.h +++ b/av1/encoder/rdopt_utils.h
@@ -901,6 +901,16 @@ return txfm_rd_gate_level[TX_SEARCH_DEFAULT]; } +void av1_get_variance_stats(const MACROBLOCK *x, int64_t *src_var, + int64_t *rec_var); + +void av1_get_tx_skip_dist(const struct AV1_COMP *cpi, const MACROBLOCK *x, + BLOCK_SIZE bsize, int64_t dist, int64_t sse, + int64_t *no_skip_dist, int64_t *skip_dist); + +int av1_is_skip_txfm_penalized(const struct AV1_COMP *cpi, const MACROBLOCK *x, + BLOCK_SIZE bsize); + #ifdef __cplusplus } // extern "C" #endif
diff --git a/av1/encoder/reconinter_enc.c b/av1/encoder/reconinter_enc.c index f169721..825e17c 100644 --- a/av1/encoder/reconinter_enc.c +++ b/av1/encoder/reconinter_enc.c
@@ -491,7 +491,7 @@ av1_get_interp_filter_subpel_kernel(filter, subpel_y_q3 << 1); const int intermediate_height = (((height - 1) * 8 + subpel_y_q3) >> 3) + filter->taps; - assert(intermediate_height <= (MAX_SB_SIZE * 2 + 16) + 16); + assert(intermediate_height < (MAX_SB_SIZE + SUBPEL_TAPS)); aom_convolve8_horiz_c(ref - ref_stride * ((filter->taps >> 1) - 1), ref_stride, temp, MAX_SB_SIZE, kernel_x, 16, NULL, -1, width, intermediate_height); @@ -574,7 +574,7 @@ av1_get_interp_filter_subpel_kernel(filter, subpel_y_q3 << 1); const int intermediate_height = (((height - 1) * 8 + subpel_y_q3) >> 3) + filter->taps; - assert(intermediate_height <= (MAX_SB_SIZE * 2 + 16) + 16); + assert(intermediate_height < (MAX_SB_SIZE + SUBPEL_TAPS)); aom_highbd_convolve8_horiz_c( CONVERT_TO_BYTEPTR(ref - ref_stride * ((filter->taps >> 1) - 1)), ref_stride, CONVERT_TO_BYTEPTR(temp), MAX_SB_SIZE, kernel_x, 16, NULL,
diff --git a/av1/encoder/svc_layercontext.c b/av1/encoder/svc_layercontext.c index 8b8d4c5..af22f8f 100644 --- a/av1/encoder/svc_layercontext.c +++ b/av1/encoder/svc_layercontext.c
@@ -141,8 +141,8 @@ lp_rc->buffer_level = AOMMIN(lp_rc->buffer_level, lp_rc->maximum_buffer_size); lc->framerate = cpi->framerate / lc->framerate_factor; - lrc->avg_frame_bandwidth = - (int)round(lc->target_bandwidth / lc->framerate); + lrc->avg_frame_bandwidth = saturate_cast_double_to_int( + round(lc->target_bandwidth / lc->framerate)); lrc->max_frame_bandwidth = rc->max_frame_bandwidth; lrc->rtc_external_ratectrl = rc->rtc_external_ratectrl; lrc->worst_quality = av1_quantizer_to_qindex(lc->max_q); @@ -203,11 +203,12 @@ cpi->framerate / lcprev->framerate_factor; const int64_t prev_layer_target_bandwidth = lcprev->layer_target_bitrate; if (lc->framerate > prev_layer_framerate) { - lc->avg_frame_size = - (int)round((lc->target_bandwidth - prev_layer_target_bandwidth) / - (lc->framerate - prev_layer_framerate)); + lc->avg_frame_size = saturate_cast_double_to_int( + round((lc->target_bandwidth - prev_layer_target_bandwidth) / + (lc->framerate - prev_layer_framerate))); } else { - lc->avg_frame_size = (int)round(lc->target_bandwidth / lc->framerate); + lc->avg_frame_size = saturate_cast_double_to_int( + round(lc->target_bandwidth / lc->framerate)); } } }
diff --git a/av1/encoder/temporal_filter.c b/av1/encoder/temporal_filter.c index be7ff38..97be8fc 100644 --- a/av1/encoder/temporal_filter.c +++ b/av1/encoder/temporal_filter.c
@@ -382,6 +382,27 @@ *is_dc_diff_large = 50 * error < sse; if (src_var <= 2 * (int64_t)distortion) *is_low_cntras = 1; +#if CONFIG_AV1_HIGHBITDEPTH + const MACROBLOCKD *xd = &mb->e_mbd; + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + // Favor zero motion vector (0,0) for stationary/near-stationary regions + // when zero-motion error is close to the best found error or sufficiently + // small. + unsigned int zero_sse; + unsigned int zero_error = cpi->ppi->fn_ptr[block_size].vf( + ref_frame->y_buffer + y_offset, y_stride, + frame_to_filter->y_buffer + y_offset, y_stride, &zero_sse); + if (zero_error <= error + (error >> 4) || (zero_error / mb_pels) < 16) { + best_mv.as_mv = kZeroMv; + block_mv = kZeroMv; + *ref_mv = kZeroMv; + error = zero_error; + distortion = (int)zero_error; + block_mse = DIVIDE_AND_ROUND(error, mb_pels); + } + } +#endif + // On 4 mid-blocks in the 64x64 tf block. if (allow_me_for_sub_blks) { // midblock_size is 32x32, which corresponds to each 32x32 block in the @@ -1092,7 +1113,12 @@ // partition only if the difference between max and min source variance of // 4x4 blocks is greater than a threshold (which is derived empirically). bool allow_me_for_sub_blks = true; - if (cpi->sf.hl_sf.allow_sub_blk_me_in_tf) { + // In high bit-depth sharpness mode, disable sub-block motion estimation to + // avoid inconsistent filtering across sub-partitions and preserve + // sharpness. + if (mbd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + allow_me_for_sub_blks = false; + } else if (cpi->sf.hl_sf.allow_sub_blk_me_in_tf) { const int is_hbd = is_frame_high_bitdepth(frame_to_filter); // Initialize minimum variance to a large value and maximum variance to 0. double blk_4x4_var_min = DBL_MAX; @@ -1139,7 +1165,7 @@ filter_strength = AOMMIN(filter_strength, 1); if (cpi->oxcf.algo_cfg.sharpness == 3 && is_low_cntras) - filter_strength = AOMMIN(filter_strength, 3); + filter_strength = AOMMIN(filter_strength, mbd->bd > 8 ? 0 : 3); // Perform weighted averaging. if (frame == filter_frame_idx) { // Frame to be filtered.
diff --git a/av1/encoder/tpl_model.c b/av1/encoder/tpl_model.c index ba54d55..34cf2bf 100644 --- a/av1/encoder/tpl_model.c +++ b/av1/encoder/tpl_model.c
@@ -854,6 +854,20 @@ cpi, x, src_mb_buffer, ref_mb, src_stride, ref_stride, src_width, ref_width, bsize, center_mvs[idx].mv.as_mv, &this_mv); +#if CONFIG_AV1_HIGHBITDEPTH + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + // Bias towards (0,0) / short motion vectors in stationary background. + // Require a noticeable SAD improvement before replacing (0,0) with a + // non-zero MV. + if (bestsme != UINT32_MAX && best_rfidx_mv.as_int == 0 && + this_mv.as_int != 0) { + if (thissme + (thissme >> 4) >= bestsme) { + continue; + } + } + } +#endif + if (thissme < bestsme) { bestsme = thissme; best_rfidx_mv = this_mv; @@ -1724,7 +1738,7 @@ if (frame_update_type == LF_UPDATE) { *pframe_qindex = gf_group->q_val[gf_index]; leaf_frame_qindex = *pframe_qindex; - if (cpi->oxcf.rc_cfg.mode == AOM_VBR) { + if (cpi->oxcf.rc_cfg.mode == AOM_VBR || cpi->oxcf.rc_cfg.mode == AOM_Q) { const int bit_depth = cm->seq_params->bit_depth; const double q_val = av1_convert_qindex_to_q(*pframe_qindex, bit_depth); const double qindex_ratio = (double)(*pframe_qindex) / MAXQ;
diff --git a/av1/encoder/tune_vmaf.c b/av1/encoder/tune_vmaf.c index 7784202..981033d 100644 --- a/av1/encoder/tune_vmaf.c +++ b/av1/encoder/tune_vmaf.c
@@ -402,45 +402,6 @@ aom_free_frame_buffer(&blurred); } -void av1_vmaf_frame_preprocessing(AV1_COMP *const cpi, - const YV12_BUFFER_CONFIG *const source) { - const AV1_COMMON *const cm = &cpi->common; - const int bit_depth = cpi->td.mb.e_mbd.bd; - const int width = source->y_width; - const int height = source->y_height; - - YV12_BUFFER_CONFIG source_extended, blurred; - memset(&source_extended, 0, sizeof(source_extended)); - memset(&blurred, 0, sizeof(blurred)); - aom_alloc_frame_buffer( - &source_extended, width, height, source->subsampling_x, - source->subsampling_y, cm->seq_params->use_highbitdepth, - cpi->oxcf.border_in_pixels, cm->features.byte_alignment, false, 0); - aom_alloc_frame_buffer( - &blurred, width, height, source->subsampling_x, source->subsampling_y, - cm->seq_params->use_highbitdepth, cpi->oxcf.border_in_pixels, - cm->features.byte_alignment, false, 0); - - av1_copy_and_extend_frame(source, &source_extended); - gaussian_blur(bit_depth, &source_extended, &blurred); - aom_free_frame_buffer(&source_extended); - - const GF_GROUP *const gf_group = &cpi->ppi->gf_group; - const int layer_depth = - AOMMIN(gf_group->layer_depth[cpi->gf_frame_index], MAX_ARF_LAYERS - 1); - const double last_frame_unsharp_amount = - get_layer_value(cpi->vmaf_info.last_frame_unsharp_amount, layer_depth); - - const double best_frame_unsharp_amount = find_best_frame_unsharp_amount( - cpi, source, &blurred, last_frame_unsharp_amount, 0.05, 20, 1.01); - - cpi->vmaf_info.last_frame_unsharp_amount[layer_depth] = - best_frame_unsharp_amount; - - unsharp(cpi, source, &blurred, source, best_frame_unsharp_amount); - aom_free_frame_buffer(&blurred); -} - void av1_vmaf_blk_preprocessing(AV1_COMP *const cpi, const YV12_BUFFER_CONFIG *const source) { const AV1_COMMON *const cm = &cpi->common;
diff --git a/av1/encoder/tune_vmaf.h b/av1/encoder/tune_vmaf.h index 7e01435..a79f90f 100644 --- a/av1/encoder/tune_vmaf.h +++ b/av1/encoder/tune_vmaf.h
@@ -45,9 +45,6 @@ void av1_vmaf_blk_preprocessing(struct AV1_COMP *cpi, const YV12_BUFFER_CONFIG *source); -void av1_vmaf_frame_preprocessing(struct AV1_COMP *cpi, - const YV12_BUFFER_CONFIG *source); - void av1_vmaf_neg_preprocessing(struct AV1_COMP *cpi, const YV12_BUFFER_CONFIG *source);
diff --git a/av1/encoder/tx_search.c b/av1/encoder/tx_search.c index 70e862c..6281191 100644 --- a/av1/encoder/tx_search.c +++ b/av1/encoder/tx_search.c
@@ -190,12 +190,20 @@ // Uses simple features on top of DCT coefficients to quickly predict // whether optimal RD decision is to skip encoding the residual. // The sse value is stored in dist. -static int predict_skip_txfm(MACROBLOCK *x, BLOCK_SIZE bsize, int64_t *dist, +static int predict_skip_txfm(const AV1_COMP *cpi, MACROBLOCK *x, + BLOCK_SIZE bsize, int64_t *dist, int reduced_tx_set) { + const MACROBLOCKD *xd = &x->e_mbd; +#if CONFIG_AV1_HIGHBITDEPTH + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + if (av1_is_skip_txfm_penalized(cpi, x, bsize)) return 0; + } +#else + (void)cpi; +#endif const TxfmSearchParams *txfm_params = &x->txfm_search_params; const int bw = block_size_wide[bsize]; const int bh = block_size_high[bsize]; - const MACROBLOCKD *xd = &x->e_mbd; const int16_t dc_q = av1_dc_quant_QTX(x->qindex, 0, xd->bd); *dist = av1_pixel_diff_dist(x, 0, 0, 0, bsize, bsize, NULL); @@ -2953,13 +2961,33 @@ } // Check if forcing the block to skip transform leads to smaller RD cost. if (is_inter && !rd_stats->skip_txfm && !xd->lossless[mbmi->segment_id]) { - int64_t temp_skip_txfm_rd = - RDCOST(x->rdmult, skip_txfm_rate, rd_stats->sse); - if (temp_skip_txfm_rd <= rd) { - rd = temp_skip_txfm_rd; - rd_stats->rate = 0; - rd_stats->dist = rd_stats->sse; - rd_stats->skip_txfm = 1; +#if CONFIG_AV1_HIGHBITDEPTH + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + int64_t no_skip_dist, skip_dist; + av1_get_tx_skip_dist(cpi, x, bs, rd_stats->dist, rd_stats->sse, + &no_skip_dist, &skip_dist); + const int64_t temp_skip_txfm_rd = + RDCOST(x->rdmult, skip_txfm_rate, skip_dist); + const int64_t temp_no_skip_txfm_rd = + RDCOST(x->rdmult, rd_stats->rate + no_skip_txfm_rate + tx_size_rate, + no_skip_dist); + if (temp_skip_txfm_rd <= temp_no_skip_txfm_rd) { + rd = temp_skip_txfm_rd; + rd_stats->rate = 0; + rd_stats->dist = rd_stats->sse; + rd_stats->skip_txfm = 1; + } + } else +#endif + { + const int64_t temp_skip_txfm_rd = + RDCOST(x->rdmult, skip_txfm_rate, rd_stats->sse); + if (temp_skip_txfm_rd <= rd) { + rd = temp_skip_txfm_rd; + rd_stats->rate = 0; + rd_stats->dist = rd_stats->sse; + rd_stats->skip_txfm = 1; + } } } @@ -3600,7 +3628,7 @@ // context and terminate early. int64_t dist; if (txfm_params->skip_txfm_level && - predict_skip_txfm(x, bsize, &dist, + predict_skip_txfm(cpi, x, bsize, &dist, cpi->common.features.reduced_tx_set_used)) { set_skip_txfm(x, rd_stats, bsize, dist); // Save the RD search results into mb_rd_record. @@ -3673,7 +3701,7 @@ int64_t dist; if (tx_params->skip_txfm_level && is_inter && !xd->lossless[mbmi->segment_id] && - predict_skip_txfm(x, bs, &dist, + predict_skip_txfm(cpi, x, bs, &dist, cpi->common.features.reduced_tx_set_used)) { // Populate rdstats as per skip decision set_skip_txfm(x, rd_stats, bs, dist); @@ -3874,14 +3902,23 @@ int choose_skip_txfm = rd_stats->skip_txfm; if (!choose_skip_txfm && !xd->lossless[mbmi->segment_id]) { + int64_t no_skip_dist = rd_stats->dist; + int64_t skip_dist = rd_stats->sse; +#if CONFIG_AV1_HIGHBITDEPTH + if (xd->bd > 8 && cpi->oxcf.algo_cfg.sharpness == 3) { + av1_get_tx_skip_dist(cpi, x, bsize, rd_stats->dist, rd_stats->sse, + &no_skip_dist, &skip_dist); + } +#endif const int64_t rdcost_no_skip_txfm = RDCOST( x->rdmult, rd_stats_y->rate + rd_stats_uv->rate + skip_txfm_cost[0], - rd_stats->dist); + no_skip_dist); const int64_t rdcost_skip_txfm = - RDCOST(x->rdmult, skip_txfm_cost[1], rd_stats->sse); + RDCOST(x->rdmult, skip_txfm_cost[1], skip_dist); if (rdcost_no_skip_txfm >= rdcost_skip_txfm) choose_skip_txfm = 1; } if (choose_skip_txfm) { + const int naturally_skip = rd_stats->skip_txfm; rd_stats_y->rate = 0; rd_stats_uv->rate = 0; rd_stats->rate = mode_rate + skip_txfm_cost[1]; @@ -3889,13 +3926,15 @@ rd_stats_y->dist = rd_stats_y->sse; rd_stats_uv->dist = rd_stats_uv->sse; mbmi->skip_txfm = 1; - if (rd_stats->skip_txfm) { + rd_stats->skip_txfm = 1; + if (naturally_skip) { const int64_t tmprd = RDCOST(x->rdmult, rd_stats->rate, rd_stats->dist); if (tmprd > ref_best_rd) return 0; } } else { rd_stats->rate += skip_txfm_cost[0]; mbmi->skip_txfm = 0; + rd_stats->skip_txfm = 0; } return 1;
diff --git a/av1/encoder/x86/reconinter_enc_sse2.c b/av1/encoder/x86/reconinter_enc_sse2.c index e67ac1b..55a3ee9 100644 --- a/av1/encoder/x86/reconinter_enc_sse2.c +++ b/av1/encoder/x86/reconinter_enc_sse2.c
@@ -111,7 +111,7 @@ uint8_t *temp_start_vert = temp + MAX_SB_SIZE * ((filter->taps >> 1) - 1); int intermediate_height = (((height - 1) * 8 + subpel_y_q3) >> 3) + filter_taps; - assert(intermediate_height <= (MAX_SB_SIZE * 2 + 16) + 16); + assert(intermediate_height < (MAX_SB_SIZE + SUBPEL_TAPS)); aom_convolve8_horiz(ref_start, ref_stride, temp_start_horiz, MAX_SB_SIZE, kernel_x, 16, NULL, -1, width, intermediate_height); aom_convolve8_vert(temp_start_vert, MAX_SB_SIZE, comp_pred, width, NULL, -1, @@ -175,6 +175,17 @@ aom_highbd_convolve8_vert(ref8, ref_stride, comp_pred8, width, NULL, -1, kernel, 16, width, height, bd); } else { + // The src and dst parameters of 'aom_highbd_convolve8_vert()' call are the + // same buffer ('comp_pred8') although they start from different offsets in + // the buffer. For any given row 'y', the 8-tap vertical filter reads a + // window of input rows from 'y-3' to 'y+4' (stride = MAX_SB_SIZE) and the + // result is written to row 'y-3' (stride = width) of the `comp_pred8` + // buffer. Since 'width <= MAX_SB_SIZE', output is written only after its + // corresponding input has already been read, eliminating any risk of + // overwriting data required for future iterations. The function + // 'aom_highbd_convolve8_vert()' must process the rows from top to bottom in + // increading order of row index, otherwise it will break the in-place + // processing of 'comp_pred8'. uint16_t *temp = CONVERT_TO_SHORTPTR(comp_pred8); const uint16_t *ref = CONVERT_TO_SHORTPTR(ref8); const int16_t *const kernel_x = @@ -188,7 +199,7 @@ uint16_t *temp_start_vert = temp + MAX_SB_SIZE * ((filter->taps >> 1) - 1); const int intermediate_height = (((height - 1) * 8 + subpel_y_q3) >> 3) + filter_taps; - assert(intermediate_height <= (MAX_SB_SIZE * 2 + 16) + 16); + assert(intermediate_height < (MAX_SB_SIZE + SUBPEL_TAPS)); aom_highbd_convolve8_horiz(CONVERT_TO_BYTEPTR(ref_start), ref_stride, CONVERT_TO_BYTEPTR(temp_start_horiz), MAX_SB_SIZE, kernel_x, 16, NULL, -1, width,
diff --git a/av1/ratectrl_rtc.cc b/av1/ratectrl_rtc.cc index 238484f..966f569 100644 --- a/av1/ratectrl_rtc.cc +++ b/av1/ratectrl_rtc.cc
@@ -63,6 +63,7 @@ config->min_quantizers[0] = config->min_quantizer; config->scaling_factor_num[0] = 1; config->scaling_factor_den[0] = 1; + config->bit_depth = 8; } } // namespace @@ -156,8 +157,10 @@ AV1_COMMON *cm = &cpi_->common; AV1EncoderConfig *oxcf = &cpi_->oxcf; RATE_CONTROL *const rc = &cpi_->rc; - cm->seq_params->profile = PROFILE_0; - cm->seq_params->bit_depth = AOM_BITS_8; + const int bit_depth = rc_cfg.bit_depth == 0 ? 8 : rc_cfg.bit_depth; + if (bit_depth != 8 && bit_depth != 10 && bit_depth != 12) return false; + cm->seq_params->profile = (bit_depth == 12) ? PROFILE_2 : PROFILE_0; + cm->seq_params->bit_depth = static_cast<aom_bit_depth_t>(bit_depth); cm->show_frame = 1; oxcf->profile = cm->seq_params->profile; oxcf->mode = REALTIME; @@ -171,7 +174,7 @@ ceil(cpi_->framerate * rc_cfg.max_consec_drop_ms / 1000)); } cpi_->svc.framedrop_mode = AOM_FULL_SUPERFRAME_DROP; - oxcf->tool_cfg.bit_depth = AOM_BITS_8; + oxcf->tool_cfg.bit_depth = cm->seq_params->bit_depth; oxcf->tool_cfg.superblock_size = AOM_SUPERBLOCK_SIZE_DYNAMIC; oxcf->algo_cfg.loopfilter_control = LOOPFILTER_ALL; cm->current_frame.frame_number = 0; @@ -207,6 +210,12 @@ rc_is_valid_ = false; return false; } + if (rc_cfg.bit_depth != 0 && + rc_cfg.bit_depth != + static_cast<int>(cpi_->common.seq_params->bit_depth)) { + rc_is_valid_ = false; + return false; + } if (cpi_->svc.number_spatial_layers != rc_cfg.ss_number_layers || cpi_->svc.number_temporal_layers != rc_cfg.ts_number_layers) av1_free_svc_cyclic_refresh(cpi_);
diff --git a/av1/ratectrl_rtc.h b/av1/ratectrl_rtc.h index 0eeaa68..f3bbe3b 100644 --- a/av1/ratectrl_rtc.h +++ b/av1/ratectrl_rtc.h
@@ -97,6 +97,8 @@ int min_quantizers[kAomAV1MaxLayers]; int scaling_factor_num[kAomAV1MaxSpatialLayers]; int scaling_factor_den[kAomAV1MaxSpatialLayers]; + // Bit-depth (8, 10, or 12). + int bit_depth; } AomAV1RateControlRtcConfig; struct AomAV1RateControlRTC;
diff --git a/examples/av1_enc_fuzzer.cc b/examples/av1_enc_fuzzer.cc new file mode 100644 index 0000000..6868190 --- /dev/null +++ b/examples/av1_enc_fuzzer.cc
@@ -0,0 +1,329 @@ +/* + * Copyright (c) 2026, Alliance for Open Media. All rights reserved. + * + * This source code is subject to the terms of the BSD 2 Clause License and + * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License + * was not distributed with this source code in the LICENSE file, you can + * obtain it at www.aomedia.org/license/software. If the Alliance for Open + * Media Patent License 1.0 was not distributed with this source code in the + * PATENTS file, you can obtain it at www.aomedia.org/license/patent. + */ + +/* + * See build_av1_enc_fuzzer.sh for building instructions. + */ + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "aom/aom_encoder.h" +#include "aom/aom_image.h" +#include "aom/aomcx.h" + +namespace { + +constexpr unsigned int kMaxDimension = 1024; +constexpr size_t kMinHeaderSize = 16; + +// Bit masks derived from the mode_flags byte. The low two bits are used to +// select the encoding usage (see PickUsage). +enum ModeFlag { + kLossless = 1u << 2, + kRowMt = 1u << 3, + kUseSecondResolution = 1u << 4, + kKeyFrameSecond = 1u << 5, + kErrorResilient = 1u << 6, +}; + +// Number of frames to encode for a given cpu_used value. Lower cpu_used +// values encode more slowly, so fewer frames are used to stay within the +// fuzzer's execution time budget (libFuzzer defaults to one second per +// input). This can be refined based on measured fuzzer performance. +constexpr unsigned int kNumFramesForCpuUsed[12] = { + 50, // cpu_used 0 + 50, // cpu_used 1 + 50, // cpu_used 2 + 60, // cpu_used 3 + 60, // cpu_used 4 + 70, // cpu_used 5 + 70, // cpu_used 6 + 80, // cpu_used 7 + 80, // cpu_used 8 + 90, // cpu_used 9 + 90, // cpu_used 10 + 100 // cpu_used 11 +}; + +struct FuzzReader { + const uint8_t *data; + size_t size; +}; + +uint8_t ReadU8(FuzzReader *reader) { + if (reader->size == 0) return 0; + const uint8_t value = *reader->data++; + --reader->size; + return value; +} + +uint16_t ReadU16(FuzzReader *reader) { + if (reader->size < 2) return 0; + const uint16_t value = reader->data[0] | (reader->data[1] << 8); + reader->data += 2; + reader->size -= 2; + return value; +} + +unsigned int UsageToIndex(unsigned int usage) { + switch (usage) { + case AOM_USAGE_GOOD_QUALITY: return 0; + case AOM_USAGE_REALTIME: return 1; + case AOM_USAGE_ALL_INTRA: return 2; + default: return 0; + } +} + +unsigned int PickUsage(uint8_t raw) { + switch (raw % 3) { + case 0: return AOM_USAGE_GOOD_QUALITY; + case 1: return AOM_USAGE_REALTIME; + default: return AOM_USAGE_ALL_INTRA; + } +} + +aom_rc_mode PickRcMode(uint8_t raw) { + switch (raw % 4) { + case 0: return AOM_VBR; + case 1: return AOM_CBR; + case 2: return AOM_CQ; + default: return AOM_Q; + } +} + +unsigned int PickDimension(uint16_t raw) { return 1u + (raw % kMaxDimension); } + +int PickCpuUsed(unsigned int usage, uint8_t raw) { + const int max_cpu_used = usage == AOM_USAGE_REALTIME ? 11 : 9; + return raw % (max_cpu_used + 1); +} + +bool DrainPackets(aom_codec_ctx_t *codec) { + bool got_data = false; + aom_codec_iter_t iter = nullptr; + while (aom_codec_get_cx_data(codec, &iter) != nullptr) { + got_data = true; + } + return got_data; +} + +bool InitDefaultConfig(aom_codec_iface_t *iface, unsigned int usage, + aom_codec_enc_cfg_t *cfg) { + const unsigned int usages[3] = { AOM_USAGE_GOOD_QUALITY, AOM_USAGE_REALTIME, + AOM_USAGE_ALL_INTRA }; + const unsigned int requested_index = UsageToIndex(usage); + for (unsigned int i = 0; i < 3; ++i) { + const unsigned int index = (requested_index + i) % 3; + if (aom_codec_enc_config_default(iface, cfg, usages[index]) == + AOM_CODEC_OK) { + return true; + } + } + return false; +} + +// Deterministic PRNG so that the frame content is derived from the fuzzer +// input without requiring the reader to supply a full plane-sized chunk of +// data for every frame. +uint32_t Rand(uint32_t *state) { + *state = *state * 1664525u + 1013904223u; + return *state; +} + +void FillPlane(uint8_t *plane, int stride, unsigned int width, + unsigned int height, uint32_t *state) { + for (unsigned int row = 0; row < height; ++row) { + for (unsigned int col = 0; col < width; ++col) { + plane[static_cast<size_t>(row) * stride + col] = + static_cast<uint8_t>(Rand(state) >> 24); + } + } +} + +bool BuildImage(FuzzReader *reader, unsigned int width, unsigned int height, + aom_image_t *image, unsigned int frame_index) { + if (aom_img_alloc(image, AOM_IMG_FMT_I420, width, height, 1) == nullptr) { + return false; + } + + // Seed a deterministic PRNG from the fuzzer input and the frame index so + // that each encoded frame differs without consuming a plane-sized chunk of + // input per frame. + uint32_t state = 0; + for (unsigned int i = 0; i < 4; ++i) { + state = (state << 8) | ReadU8(reader); + } + const uint32_t golden_ratio = 0x9e3779b9u; + state ^= frame_index * golden_ratio; + + const unsigned int uv_width = (width + 1) / 2; + const unsigned int uv_height = (height + 1) / 2; + FillPlane(image->planes[AOM_PLANE_Y], image->stride[AOM_PLANE_Y], width, + height, &state); + FillPlane(image->planes[AOM_PLANE_U], image->stride[AOM_PLANE_U], uv_width, + uv_height, &state); + FillPlane(image->planes[AOM_PLANE_V], image->stride[AOM_PLANE_V], uv_width, + uv_height, &state); + return true; +} + +void ApplyControls(aom_codec_ctx_t *codec, unsigned int usage, + uint8_t mode_flags, uint8_t cpu_used_raw, uint8_t ctl0, + uint8_t ctl1) { + const int cpu_used = PickCpuUsed(usage, cpu_used_raw); + const unsigned int lossless = (mode_flags & kLossless) != 0; + const unsigned int row_mt = (mode_flags & kRowMt) != 0; + const unsigned int aq_mode = ctl0 % 4; + const unsigned int deltaq_mode = ctl1 % 4; + const unsigned int tile_columns = (ctl0 >> 4) & 0x3; + const unsigned int tile_rows = (ctl1 >> 4) & 0x3; + const unsigned int enable_cdef = (ctl0 >> 2) & 1; + const unsigned int enable_restoration = (ctl1 >> 2) & 1; + + (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AOME_SET_CPUUSED, cpu_used); + (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_LOSSLESS, lossless); + (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ROW_MT, row_mt); + (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_AQ_MODE, aq_mode); + (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_DELTAQ_MODE, deltaq_mode); + (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_TILE_COLUMNS, + tile_columns); + (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_TILE_ROWS, tile_rows); + (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ENABLE_CDEF, enable_cdef); + (void)AOM_CODEC_CONTROL_TYPECHECKED(codec, AV1E_SET_ENABLE_RESTORATION, + enable_restoration); +} + +bool EncodeFrame(aom_codec_ctx_t *codec, FuzzReader *reader, unsigned int width, + unsigned int height, aom_codec_pts_t pts, + aom_enc_frame_flags_t flags) { + aom_image_t image; + memset(&image, 0, sizeof(image)); + + if (!BuildImage(reader, width, height, &image, pts)) { + return false; + } + + const aom_codec_err_t enc_ret = + aom_codec_encode(codec, &image, pts, 1, flags); + if (enc_ret == AOM_CODEC_OK) { + (void)DrainPackets(codec); + } + + aom_img_free(&image); + return true; +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + aom_codec_iface_t *iface = aom_codec_av1_cx(); + if (iface == nullptr || size < kMinHeaderSize) return 0; + + FuzzReader reader = { data, size }; + const uint8_t mode_flags = ReadU8(&reader); + const uint8_t cpu_used_raw = ReadU8(&reader); + const uint8_t ctl0 = ReadU8(&reader); + const uint8_t ctl1 = ReadU8(&reader); + const unsigned int usage = PickUsage(mode_flags); + const unsigned int width0 = PickDimension(ReadU16(&reader)); + const unsigned int height0 = PickDimension(ReadU16(&reader)); + const unsigned int bitrate = 1u + (ReadU16(&reader) % 4000u); + const unsigned int width1 = PickDimension(ReadU16(&reader)); + const unsigned int height1 = PickDimension(ReadU16(&reader)); + const unsigned int threads0 = ReadU8(&reader) % 9; + const unsigned int threads1 = ReadU8(&reader) % 9; + + aom_codec_enc_cfg_t cfg; + if (!InitDefaultConfig(iface, usage, &cfg)) return 0; + + cfg.g_usage = usage; + cfg.g_w = width0; + cfg.g_h = height0; + cfg.g_threads = threads0; + cfg.g_forced_max_frame_width = kMaxDimension; + cfg.g_forced_max_frame_height = kMaxDimension; + cfg.g_timebase.num = 1; + cfg.g_timebase.den = 1000000; + cfg.g_pass = AOM_RC_ONE_PASS; + cfg.g_lag_in_frames = 0; + cfg.g_error_resilient = + (mode_flags & kErrorResilient) ? AOM_ERROR_RESILIENT_DEFAULT : 0; + cfg.rc_end_usage = PickRcMode(ctl0); + cfg.rc_target_bitrate = bitrate; + + aom_codec_ctx_t codec; + memset(&codec, 0, sizeof(codec)); + if (aom_codec_enc_init(&codec, iface, &cfg, 0) != AOM_CODEC_OK) return 0; + + const int cpu_used = PickCpuUsed(usage, cpu_used_raw); + ApplyControls(&codec, usage, mode_flags, cpu_used_raw, ctl0, ctl1); + + const unsigned int num_frames = kNumFramesForCpuUsed[cpu_used]; + bool config_failed = false; + for (unsigned int frame = 0; frame < num_frames; ++frame) { + aom_enc_frame_flags_t flags = 0; + if (frame == 1 && (mode_flags & kKeyFrameSecond)) { + flags |= AOM_EFLAG_FORCE_KF; + } + + unsigned int width = width0; + unsigned int height = height0; + if (mode_flags & kUseSecondResolution) { + // Vary the switch frame based on the input so different encodings + // exercise the resolution change at different points in the stream. + // num_frames >= 50 for every cpu_used, so switch_frame and + // restore_frame stay within [1, num_frames - 1]. + const unsigned int switch_frame = 1u + (ctl0 % (num_frames / 4u)); + const unsigned int restore_frame = + switch_frame + 1u + (ctl1 % (num_frames / 4u)); + if (frame == switch_frame) { + cfg.g_w = width1; + cfg.g_h = height1; + cfg.g_threads = threads1; + cfg.rc_end_usage = PickRcMode(ctl1); + if (aom_codec_enc_config_set(&codec, &cfg) != AOM_CODEC_OK) { + config_failed = true; + break; + } + width = width1; + height = height1; + } else if (frame == restore_frame) { + cfg.g_w = width0; + cfg.g_h = height0; + cfg.g_threads = threads0; + cfg.rc_end_usage = PickRcMode(ctl0); + if (aom_codec_enc_config_set(&codec, &cfg) != AOM_CODEC_OK) { + config_failed = true; + break; + } + width = width0; + height = height0; + } + } + + if (!EncodeFrame(&codec, &reader, width, height, frame, flags)) { + config_failed = true; + break; + } + } + + if (!config_failed) { + while (aom_codec_encode(&codec, nullptr, 0, 0, 0) == AOM_CODEC_OK && + DrainPackets(&codec)) { + } + } + + (void)aom_codec_destroy(&codec); + return 0; +}
diff --git a/examples/build_av1_enc_fuzzer.sh b/examples/build_av1_enc_fuzzer.sh new file mode 100755 index 0000000..a11de16 --- /dev/null +++ b/examples/build_av1_enc_fuzzer.sh
@@ -0,0 +1,69 @@ +#!/bin/bash +# +# Copyright (c) 2026, Alliance for Open Media. All rights reserved. +# +# This source code is subject to the terms of the BSD 2 Clause License and +# the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License +# was not distributed with this source code in the LICENSE file, you can +# obtain it at www.aomedia.org/license/software. If the Alliance for Open +# Media Patent License 1.0 was not distributed with this source code in the +# PATENTS file, you can obtain it at www.aomedia.org/license/patent. +# +############################################################################### +# Fuzzer for libaom AV1 encoder public API. +# ========================================= +# Requirements +# --------------------- +# Clang6.0 or above (must support -fsanitize=fuzzer -fsanitize=fuzzer-no-link) +# +# References: +# --------------------- +# http://llvm.org/docs/LibFuzzer.html +# https://github.com/google/oss-fuzz +# +# Steps to build / run +# --------------------- + +set -eu + +# Have a copy of AOM and a build directory ready. +if [[ $# -ne 2 ]]; then + echo "Pass in the AOM source tree as first argument, and a build directory " + echo "as the second argument. The AOM source tree can be obtained via: " + echo " git clone https://aomedia.googlesource.com/aom" + exit 2 +fi +if [[ -z "${CC:-}" ]]; then + echo "Set the CC environment variable to point to your C compiler." + exit 2 +fi +if [[ -z "${CXX:-}" ]]; then + echo "Set the CXX environment variable to point to your C++ compiler." + exit 2 +fi + +AOM_DIR=$1 +BUILD_DIR=$2 +# Run CMake with address sanitizer enabled and build the codec. +# Enable DO_RANGE_CHECK_CLAMP to suppress the noise of integer overflows +# in the transform functions. Also set memory limits. +EXTRA_C_FLAGS='-UNDEBUG -DDO_RANGE_CHECK_CLAMP=1 -DAOM_MAX_ALLOCABLE_MEMORY=1073741824' +cd "${BUILD_DIR}" +cmake "${AOM_DIR}" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCONFIG_PIC=1 \ + -DCONFIG_AV1_ENCODER=1 \ + -DENABLE_APPS=0 -DENABLE_EXAMPLES=0 -DENABLE_DOCS=0 -DENABLE_TESTS=0 \ + -DAOM_EXTRA_C_FLAGS="${EXTRA_C_FLAGS}" \ + -DAOM_EXTRA_CXX_FLAGS="${EXTRA_C_FLAGS}" -DSANITIZE=fuzzer-no-link,address + +# Build the codec. +make -j$(nproc) + +# Build the av1 encoder fuzzer +$CXX -std=c++17 -I${AOM_DIR} -I${BUILD_DIR} \ + -g -fsanitize=fuzzer,address \ + ${AOM_DIR}/examples/av1_enc_fuzzer.cc -o ${BUILD_DIR}/av1_enc_fuzzer \ + ${BUILD_DIR}/libaom.a + +echo "Fuzzer built at ${BUILD_DIR}/av1_enc_fuzzer." +echo "Create a corpus directory, and run:" +echo " av1_enc_fuzzer CORPUS_DIR"
diff --git a/test/av1_convolve_test.cc b/test/av1_convolve_test.cc index 4b444bd..7d1bef4 100644 --- a/test/av1_convolve_test.cc +++ b/test/av1_convolve_test.cc
@@ -1376,7 +1376,7 @@ private: void TestConvolveVmaf() { - // 8-tap Gaussian blur filter used by tune=vmaf_with_preprocessing + // 8-tap Gaussian blur filter used by tune=vmaf // in av1/encoder/tune_vmaf.c. // The array is of size 9 to allow passing kGaussFilter + 1 to // _mm_loadu_si128() in prepare_coeffs_6t().
diff --git a/test/av1_ext_tile_test.cc b/test/av1_ext_tile_test.cc index 1eb6ced..51a1d32 100644 --- a/test/av1_ext_tile_test.cc +++ b/test/av1_ext_tile_test.cc
@@ -29,6 +29,8 @@ // Fake width and height so that they can be multiples of the tile size. const int kImgWidth = 704; const int kImgHeight = 576; +const int kInvalidTileImgSize = 256; +const int kOutOfRangeTileCol = 2; // This test tests large scale tile coding case. Non-large-scale tile coding // is tested by the tile_independence test. @@ -78,7 +80,8 @@ ::libaom_test::Encoder *encoder) override { if (video->frame() == 0) { // Encode setting - encoder->Control(AOME_SET_CPUUSED, set_cpu_used_); + encoder->Control(AOME_SET_CPUUSED, + test_invalid_tile_ ? 8 : set_cpu_used_); encoder->Control(AOME_SET_ENABLEAUTOALTREF, 0); encoder->Control(AV1E_SET_FRAME_PARALLEL_DECODING, 1); @@ -88,8 +91,8 @@ encoder->Control(AV1E_SET_SUPERBLOCK_SIZE, AOM_SUPERBLOCK_SIZE_64X64); // Set tile_columns and tile_rows to MAX values, which guarantees the tile // size of 64 x 64 pixels(i.e. 1 SB) for <= 4k resolution. - encoder->Control(AV1E_SET_TILE_COLUMNS, 6); - encoder->Control(AV1E_SET_TILE_ROWS, 6); + encoder->Control(AV1E_SET_TILE_COLUMNS, test_invalid_tile_ ? 1 : 6); + encoder->Control(AV1E_SET_TILE_ROWS, test_invalid_tile_ ? 1 : 6); } else if (video->frame() == 1) { frame_flags_ = AOM_EFLAG_NO_UPD_LAST | AOM_EFLAG_NO_UPD_GF | AOM_EFLAG_NO_UPD_ARF; @@ -109,6 +112,16 @@ } void FramePktHook(const aom_codec_cx_pkt_t *pkt) override { + if (test_invalid_tile_) { + decoder_->Control(AV1_SET_DECODE_TILE_ROW, 0); + decoder_->Control(AV1_SET_DECODE_TILE_COL, kOutOfRangeTileCol); + EXPECT_EQ(AOM_CODEC_INVALID_PARAM, + decoder_->DecodeFrame( + reinterpret_cast<uint8_t *>(pkt->data.frame.buf), + pkt->data.frame.sz)); + return; + } + // Skip decoding 1 frame. if (pkt->data.frame.pts == (aom_codec_pts_t)kSkip) return; @@ -186,12 +199,28 @@ ASSERT_EQ(md5_, tile_md5_); } + void TestInvalidTile() { + ::libaom_test::I420VideoSource video("hantro_collage_w352h288.yuv", + kInvalidTileImgSize, + kInvalidTileImgSize, 30, 1, 0, 1); + cfg_.rc_target_bitrate = 500; + cfg_.g_error_resilient = AOM_ERROR_RESILIENT_DEFAULT; + cfg_.large_scale_tile = 1; + cfg_.g_lag_in_frames = 0; + cfg_.g_threads = 1; + test_invalid_tile_ = true; + + init_flags_ = AOM_CODEC_USE_PSNR; + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); + } + ::libaom_test::TestMode encoding_mode_; int set_cpu_used_; ::libaom_test::Decoder *decoder_; aom_image_t tile_img_; std::vector<std::string> md5_; std::vector<std::string> tile_md5_; + bool test_invalid_tile_ = false; }; TEST_P(AV1ExtTileTest, DecoderResultTest) { TestRoundTrip(); } @@ -205,6 +234,8 @@ TEST_P(AV1ExtTileTestLarge, DecoderResultTest) { TestRoundTrip(); } +TEST_P(AV1ExtTileTestLarge, RejectsInvalidDecodeTile) { TestInvalidTile(); } + AV1_INSTANTIATE_TEST_SUITE( // Now only test 2-pass mode. AV1ExtTileTestLarge, ::testing::Values(::libaom_test::kTwoPassGood),
diff --git a/test/av1_key_value_api_test.cc b/test/av1_key_value_api_test.cc index d3e9dc1..8444907 100644 --- a/test/av1_key_value_api_test.cc +++ b/test/av1_key_value_api_test.cc
@@ -104,6 +104,7 @@ std::make_tuple("auto-intra-tools-off", "1"), // uint std::make_tuple("min-gf-interval", "10"), // uint std::make_tuple("min-partition-size", "4"), // int + std::make_tuple("resize-mode", "3"), // uint std::make_tuple("tune", "psnr"), // enum }; @@ -121,6 +122,7 @@ std::make_tuple("tune", "PsnR1"), // out of range std::make_tuple("cq-level", "1000"), + std::make_tuple("resize-mode", "10"), }; INSTANTIATE_TEST_SUITE_P(KeyValAPI, EncValidTest,
diff --git a/test/comp_mask_pred_test.cc b/test/comp_mask_pred_test.cc index f57160c..be6a1fb 100644 --- a/test/comp_mask_pred_test.cc +++ b/test/comp_mask_pred_test.cc
@@ -82,10 +82,10 @@ rnd_.Reset(libaom_test::ACMRandom::DeterministicSeed()); av1_init_wedge_masks(); comp_pred1_ = - (uint8_t *)aom_memalign(16, ((MAX_SB_SIZE + 16) + 16) * MAX_SB_SIZE); + (uint8_t *)aom_memalign(16, (MAX_SB_SIZE + SUBPEL_TAPS) * MAX_SB_SIZE); ASSERT_NE(comp_pred1_, nullptr); comp_pred2_ = - (uint8_t *)aom_memalign(16, ((MAX_SB_SIZE + 16) + 16) * MAX_SB_SIZE); + (uint8_t *)aom_memalign(16, (MAX_SB_SIZE + SUBPEL_TAPS) * MAX_SB_SIZE); ASSERT_NE(comp_pred2_, nullptr); pred_ = (uint8_t *)aom_memalign(16, MAX_SB_SQUARE); ASSERT_NE(pred_, nullptr); @@ -458,10 +458,10 @@ av1_init_wedge_masks(); comp_pred1_ = (uint16_t *)aom_memalign( - 16, ((MAX_SB_SIZE + 16) + 16) * MAX_SB_SIZE * sizeof(*comp_pred1_)); + 16, (MAX_SB_SIZE + SUBPEL_TAPS) * MAX_SB_SIZE * sizeof(*comp_pred1_)); ASSERT_NE(comp_pred1_, nullptr); comp_pred2_ = (uint16_t *)aom_memalign( - 16, ((MAX_SB_SIZE + 16) + 16) * MAX_SB_SIZE * sizeof(*comp_pred2_)); + 16, (MAX_SB_SIZE + SUBPEL_TAPS) * MAX_SB_SIZE * sizeof(*comp_pred2_)); ASSERT_NE(comp_pred2_, nullptr); pred_ = (uint16_t *)aom_memalign(16, MAX_SB_SQUARE * sizeof(*pred_)); ASSERT_NE(pred_, nullptr);
diff --git a/test/disflow_test.cc b/test/disflow_test.cc index d881f7b..f852608 100644 --- a/test/disflow_test.cc +++ b/test/disflow_test.cc
@@ -9,6 +9,7 @@ * PATENTS file, you can obtain it at www.aomedia.org/license/patent. */ +#include "aom_dsp/flow_estimation/corner_match.h" #include "aom_dsp/flow_estimation/disflow.h" #include "gtest/gtest.h" @@ -21,9 +22,10 @@ namespace { -using ComputeFlowAtPointFunc = void (*)(const uint8_t *src, const uint8_t *ref, +using ComputeFlowAtPointFunc = void (*)(const uint8_t *src, int src_stride, + const uint8_t *ref, int ref_stride, int x, int y, int width, int height, - int stride, double *u, double *v); + double *u, double *v); class ComputeFlowTest : public ::testing::TestWithParam<ComputeFlowAtPointFunc> { @@ -74,16 +76,17 @@ aom_usec_timer ref_timer, test_timer; - aom_compute_flow_at_point_c(src, ref, x, y, kWidth, kHeight, kWidth, &u_ref, - &v_ref); + aom_compute_flow_at_point_c(src, kWidth, ref, kWidth, x, y, kWidth, kHeight, + &u_ref, &v_ref); - target_func_(src, ref, x, y, kWidth, kHeight, kWidth, &u_test, &v_test); + target_func_(src, kWidth, ref, kWidth, x, y, kWidth, kHeight, &u_test, + &v_test); if (run_times > 1) { aom_usec_timer_start(&ref_timer); for (int i = 0; i < run_times; ++i) { - aom_compute_flow_at_point_c(src, ref, x, y, kWidth, kHeight, kWidth, - &u_ref, &v_ref); + aom_compute_flow_at_point_c(src, kWidth, ref, kWidth, x, y, kWidth, + kHeight, &u_ref, &v_ref); } aom_usec_timer_mark(&ref_timer); const double elapsed_time_c = @@ -91,7 +94,8 @@ aom_usec_timer_start(&test_timer); for (int i = 0; i < run_times; ++i) { - target_func_(src, ref, x, y, kWidth, kHeight, kWidth, &u_test, &v_test); + target_func_(src, kWidth, ref, kWidth, x, y, kWidth, kHeight, &u_test, + &v_test); } aom_usec_timer_mark(&test_timer); const double elapsed_time_simd = @@ -129,4 +133,52 @@ ::testing::Values(aom_compute_flow_at_point_sve)); #endif +#if CONFIG_AV1_ENCODER && !CONFIG_REALTIME_ONLY +TEST(DisflowTest, NarrowDimensions) { + YV12_BUFFER_CONFIG src = {}; + YV12_BUFFER_CONFIG ref = {}; + + constexpr int kWidth = 17; + constexpr int kHeight = 1; + ASSERT_EQ(aom_alloc_frame_buffer(&src, kWidth, kHeight, 1, 1, 0, + AOM_BORDER_IN_PIXELS, 0, true, 0), + 0); + ASSERT_EQ(aom_alloc_frame_buffer(&ref, kWidth, kHeight, 1, 1, 0, + AOM_BORDER_IN_PIXELS, 0, true, 0), + 0); + + MotionModel motion_models[1]; + bool mem_alloc_failed = false; + bool ret = av1_compute_global_motion_disflow( + TRANSLATION, &src, &ref, 8, 0, motion_models, 1, &mem_alloc_failed); + EXPECT_FALSE(ret); + EXPECT_FALSE(mem_alloc_failed); + + aom_free_frame_buffer(&src); + aom_free_frame_buffer(&ref); +} + +TEST(DisflowTest, MismatchedDimensions) { + YV12_BUFFER_CONFIG src = {}; + YV12_BUFFER_CONFIG ref = {}; + + ASSERT_EQ(aom_alloc_frame_buffer(&src, 128, 96, 1, 1, 0, AOM_BORDER_IN_PIXELS, + 0, true, 0), + 0); + ASSERT_EQ(aom_alloc_frame_buffer(&ref, 320, 240, 1, 1, 0, + AOM_BORDER_IN_PIXELS, 0, true, 0), + 0); + + MotionModel motion_models[1]; + bool mem_alloc_failed = false; + bool ret = av1_compute_global_motion_disflow( + TRANSLATION, &src, &ref, 8, 0, motion_models, 1, &mem_alloc_failed); + EXPECT_FALSE(ret); + EXPECT_FALSE(mem_alloc_failed); + + aom_free_frame_buffer(&src); + aom_free_frame_buffer(&ref); +} +#endif // CONFIG_AV1_ENCODER && !CONFIG_REALTIME_ONLY + } // namespace
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc index e672822..5621933 100644 --- a/test/encode_api_test.cc +++ b/test/encode_api_test.cc
@@ -29,6 +29,9 @@ #include "aom/aom_image.h" #include "aom_mem/aom_mem.h" +#include "av1/common/blockd.h" +#include "av1/common/reconintra.h" +#include "av1/encoder/allintra_vis.h" #include "test/codec_factory.h" #include "test/encode_test_driver.h" #include "test/util.h" @@ -2684,4 +2687,422 @@ ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK); } +#if !CONFIG_REALTIME_ONLY +TEST(EncodeAPI, Buganizer558434716) { + aom_codec_iface_t *iface = aom_codec_av1_cx(); + aom_codec_enc_cfg_t cfg; + ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY), + AOM_CODEC_OK); + + cfg.g_w = 128; + cfg.g_h = 96; + cfg.g_forced_max_frame_width = 1920; + cfg.g_forced_max_frame_height = 1080; + cfg.g_pass = AOM_RC_ONE_PASS; + cfg.g_lag_in_frames = 0; + cfg.g_threads = 1; + + aom_codec_ctx_t enc; + ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK); + + aom_image_t *img_small = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 128, 96, 1); + ASSERT_NE(img_small, nullptr); + FillImage(img_small, 128); + + aom_image_t *img_large = + aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 320, 240, 1); + ASSERT_NE(img_large, nullptr); + FillImage(img_large, 128); + + // Frame 0 at 128x96 + ASSERT_EQ(aom_codec_encode(&enc, img_small, 0, 1, 0), AOM_CODEC_OK); + + // Switch to larger resolution + cfg.g_w = 320; + cfg.g_h = 240; + ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK); + + // Frame 1 at 320x240 + ASSERT_EQ(aom_codec_encode(&enc, img_large, 1, 1, AOM_EFLAG_FORCE_KF), + AOM_CODEC_OK); + + // Switch back to smaller resolution + cfg.g_w = 128; + cfg.g_h = 96; + ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK); + + // Frame 2 at 128x96 (keyframe) + ASSERT_EQ(aom_codec_encode(&enc, img_small, 2, 1, AOM_EFLAG_FORCE_KF), + AOM_CODEC_OK); + + // Frames 3+ at 128x96 (interframes referencing frame 2) + for (int i = 3; i < 8; ++i) { + ASSERT_EQ(aom_codec_encode(&enc, img_small, i, 1, 0), AOM_CODEC_OK); + } + + aom_img_free(img_small); + aom_img_free(img_large); + ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK); +} + +TEST(EncodeAPI, PerceptualAIDynamicResolutionChange) { + aom_codec_iface_t *iface = aom_codec_av1_cx(); + aom_codec_enc_cfg_t cfg; + ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA), + AOM_CODEC_OK); + + cfg.g_w = 17; + cfg.g_h = 1; + cfg.g_forced_max_frame_width = 320; + cfg.g_forced_max_frame_height = 240; + cfg.g_pass = AOM_RC_ONE_PASS; + cfg.g_lag_in_frames = 0; + cfg.g_threads = 1; + + aom_codec_ctx_t enc; + ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_DELTAQ_MODE, 3), AOM_CODEC_OK); + + aom_image_t *img_small = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 17, 1, 1); + ASSERT_NE(img_small, nullptr); + FillImage(img_small, 128); + + aom_image_t *img_large = + aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 320, 240, 1); + ASSERT_NE(img_large, nullptr); + FillImage(img_large, 128); + + // Frame 0 at 17x1 + ASSERT_EQ(aom_codec_encode(&enc, img_small, 0, 1, 0), AOM_CODEC_OK); + + // Switch to larger resolution + cfg.g_w = 320; + cfg.g_h = 240; + ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK); + + // Frame 1 at 320x240 + ASSERT_EQ(aom_codec_encode(&enc, img_large, 1, 1, 0), AOM_CODEC_OK); + + // Switch back to 17x1 + cfg.g_w = 17; + cfg.g_h = 1; + ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK); + + // Frame 2 at 17x1 + ASSERT_EQ(aom_codec_encode(&enc, img_small, 2, 1, 0), AOM_CODEC_OK); + + aom_img_free(img_small); + aom_img_free(img_large); + ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK); +} + +// Test for OSS-Fuzz Issue 559079132: Integer-overflow in +// av1_caq_select_segment. When COMPLEXITY_AQ (aq_mode 2) is enabled with a high +// target bitrate, sb64_target_rate is large, causing signed integer overflow +// when multiplying sb64_target_rate * xmis * ymis in 32-bit arithmetic and when +// casting target_rate to int. +TEST(EncodeAPI, Issue559079132) { + aom_codec_iface_t *const iface = aom_codec_av1_cx(); + aom_codec_ctx_t enc; + aom_codec_enc_cfg_t cfg; + + ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY), + AOM_CODEC_OK); + + cfg.g_w = 64; + cfg.g_h = 64; + cfg.g_timebase.num = 1; + cfg.g_timebase.den = 1; + cfg.rc_target_bitrate = 2000000; + cfg.rc_end_usage = AOM_CBR; + cfg.g_lag_in_frames = 0; + + ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_AQ_MODE, 2), AOM_CODEC_OK); + + aom_image_t *img = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 64, 64, 1); + ASSERT_NE(img, nullptr); + FillImageRandom(img); + + EncodeOne(&enc, img, 0); + + // Flush encoder. + ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK); + aom_codec_iter_t iter = nullptr; + while (aom_codec_get_cx_data(&enc, &iter) != nullptr) { + } + + aom_img_free(img); + ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK); +} +#endif // !CONFIG_REALTIME_ONLY + +// Tests for OSS-Fuzz Issues 558463888, 559075253, 559225640: +// When dynamically reducing threads with multiple tiles and row_mt=0, the +// primary worker pool size (mt_info->num_workers) remains at the higher +// capacity allocated previously while the module workers for the encode stage +// (mt_info->num_mod_workers[MOD_ENC]) is reduced. If encode_frame_internal() +// uses mt_info->num_workers instead of mt_info->num_mod_workers[MOD_ENC], it +// can erroneously call av1_encode_tiles_mt() or stride with the wrong worker +// count, causing tiles to be skipped and leading to a crash in bitstream +// packing. +void TestDynamicThreadReductionWithTiles(int tile_rows, int tile_columns, + int init_threads, int max_threads) { + aom_codec_iface_t *const iface = aom_codec_av1_cx(); + aom_codec_ctx_t enc; + aom_codec_enc_cfg_t cfg; + + ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME), + AOM_CODEC_OK); + + cfg.g_w = 256; + cfg.g_h = 256; + // Allow dynamic resolution changes up to 512x512. + cfg.g_forced_max_frame_width = 512; + cfg.g_forced_max_frame_height = 512; + cfg.g_threads = init_threads; + cfg.g_lag_in_frames = 0; + cfg.g_error_resilient = 1; + + ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK); + + // Disable row-mt and configure tiling. + ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_ROW_MT, 0), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, 7), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TILE_ROWS, tile_rows), + AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_TILE_COLUMNS, tile_columns), + AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&enc, AOME_SET_ENABLEAUTOALTREF, 0), + AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&enc, AV1E_SET_AQ_MODE, 3), AOM_CODEC_OK); + + aom_image_t *img_256 = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 256, 256, 1); + ASSERT_NE(img_256, nullptr); + FillImageRandom(img_256); + + aom_image_t *img_512 = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 512, 512, 1); + ASSERT_NE(img_512, nullptr); + FillImageRandom(img_512); + + // Frame 0: Encode at 256x256 with init_threads. + EncodeOne(&enc, img_256, 0); + + // Frame 1: Switch to 512x512 with max_threads. This allocates max_threads + // workers in the primary worker pool (mt_info->num_workers = max_threads). + cfg.g_w = 512; + cfg.g_h = 512; + cfg.g_threads = max_threads; + ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK); + EncodeOne(&enc, img_512, 1); + + // Frame 2: Switch back to 256x256 with init_threads. The worker pool retains + // mt_info->num_workers = max_threads, but mt_info->num_mod_workers[MOD_ENC] + // is init_threads. This verifies that tile encoding properly handles the + // reduced worker count without skipping tiles. + cfg.g_w = 256; + cfg.g_h = 256; + cfg.g_threads = init_threads; + ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK); + EncodeOne(&enc, img_256, 2); + + // Flush encoder. + ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK); + aom_codec_iter_t iter = nullptr; + while (aom_codec_get_cx_data(&enc, &iter) != nullptr) { + } + + aom_img_free(img_256); + aom_img_free(img_512); + ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK); +} + +TEST(EncodeAPI, Issue558463888) { + TestDynamicThreadReductionWithTiles(/*tile_rows=*/1, /*tile_columns=*/0, + /*init_threads=*/1, /*max_threads=*/2); +} + +TEST(EncodeAPI, Issue559075253) { + TestDynamicThreadReductionWithTiles(/*tile_rows=*/1, /*tile_columns=*/1, + /*init_threads=*/2, /*max_threads=*/4); +} + +TEST(EncodeAPI, Issue559225640) { + TestDynamicThreadReductionWithTiles(/*tile_rows=*/0, /*tile_columns=*/1, + /*init_threads=*/1, /*max_threads=*/4); +} + +#if !CONFIG_REALTIME_ONLY +TEST(EncodeAPI, Buganizer558446054) { +#if !CONFIG_SHARED + av1_init_intra_predictors(); + + MACROBLOCKD xd = {}; + YV12_BUFFER_CONFIG cur_buf = {}; + xd.cur_buf = &cur_buf; + MB_MODE_INFO mbmi = {}; + mbmi.bsize = BLOCK_8X8; + mbmi.partition = PARTITION_NONE; + mbmi.mode = D45_PRED; + MB_MODE_INFO *mbmi_ptr = &mbmi; + xd.mi = &mbmi_ptr; + xd.bd = 8; + xd.left_available = 1; + xd.up_available = 1; + xd.tile.mi_row_end = 100; + xd.tile.mi_col_end = 100; + // Set mb_to_bottom_edge and mb_to_right_edge such that + // yd + txhpx < 0 and xr < 0: + // yd + txhpx = (mb_to_bottom_edge >> 3) + hpx - y = -16 + 8 - 0 = -8 < 0 + // xr = (mb_to_right_edge >> 3) + wpx - x - txwpx = -5 + 8 - 0 - 4 = -1 < 0 + // xr + txwpx = 3 > 0 (so n_top_px = 3). + xd.mb_to_bottom_edge = -16 * 8; + xd.mb_to_right_edge = -5 * 8; + + uint8_t ref_buf[64 * 64]; + memset(ref_buf, 200, sizeof(ref_buf)); + uint8_t dst_buf[64 * 64] = { 0 }; + av1_predict_intra_block(&xd, BLOCK_64X64, /*enable_intra_edge_filter=*/0, + /*wpx=*/8, /*hpx=*/8, TX_4X4, D45_PRED, + /*angle_delta=*/0, /*use_palette=*/0, + FILTER_INTRA_MODES, ref_buf + 64 * 8 + 8, 64, dst_buf, + 64, /*col_off=*/0, /*row_off=*/0, /*plane=*/0); + // In Debug builds (-UNDEBUG), reverting clamp() triggers: + // Assertion `n_left_px >= 0' failed. + // In Release builds (-DNDEBUG), reverting clamp() causes n_topright_px < 0 + // (-4 < 0), skipping top-right extension of above_row[3]=200 into + // above_row[4..7] and leaving dst_buf[3] == 127 instead of 200. + ASSERT_EQ(dst_buf[3], 200); +#endif // !CONFIG_SHARED + + aom_codec_iface_t *iface = aom_codec_av1_cx(); + aom_codec_enc_cfg_t cfg; + ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA), + AOM_CODEC_OK); + + cfg.g_w = 65; + cfg.g_h = 33; + cfg.g_threads = 0; + cfg.g_lag_in_frames = 0; + cfg.g_pass = AOM_RC_ONE_PASS; + cfg.rc_end_usage = AOM_Q; + + aom_codec_ctx_t codec; + ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&codec, AOME_SET_CPUUSED, 1), AOM_CODEC_OK); + + aom_image_t raw; + ASSERT_NE(aom_img_alloc(&raw, AOM_IMG_FMT_I420, 65, 33, 1), nullptr); + FillImageRandom(&raw); + + ASSERT_EQ(aom_codec_encode(&codec, &raw, 0, 1, 0), AOM_CODEC_OK); + + aom_img_free(&raw); + ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK); +} +#endif // !CONFIG_REALTIME_ONLY + +#if !CONFIG_REALTIME_ONLY +TEST(EncodeAPI, Buganizer558463892_558589747) { +#if !CONFIG_SHARED + std::unique_ptr<AV1_COMP> cpi_test(new AV1_COMP()); + struct aom_internal_error_info error = {}; + if (setjmp(error.jmp)) FAIL(); + error.setjmp = 1; + cpi_test->common.error = &error; + cpi_test->frame_info.mi_rows = 16; + cpi_test->frame_info.mi_cols = 16; + av1_init_mb_wiener_var_buffer(cpi_test.get()); + ASSERT_NE(cpi_test->mb_weber_stats, nullptr); + cpi_test->mb_weber_stats[0].satd = 12345; + + // Increase dimensions to 64x64 MI units: mb_weber_stats must be reallocated. + cpi_test->frame_info.mi_rows = 64; + cpi_test->frame_info.mi_cols = 64; + av1_init_mb_wiener_var_buffer(cpi_test.get()); + EXPECT_EQ(cpi_test->mb_weber_stats[0].satd, 0); + aom_free(cpi_test->mb_weber_stats); +#endif // !CONFIG_SHARED + + aom_codec_iface_t *iface = aom_codec_av1_cx(); + aom_codec_enc_cfg_t cfg; + ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_ALL_INTRA), + AOM_CODEC_OK); + + cfg.g_w = 64; + cfg.g_h = 64; + cfg.g_forced_max_frame_width = 1024; + cfg.g_forced_max_frame_height = 1024; + cfg.g_threads = 0; + cfg.g_lag_in_frames = 0; + cfg.g_pass = AOM_RC_ONE_PASS; + cfg.rc_end_usage = AOM_Q; + + aom_codec_ctx_t codec; + ASSERT_EQ(aom_codec_enc_init(&codec, iface, &cfg, 0), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&codec, AV1E_SET_DELTAQ_MODE, 3), AOM_CODEC_OK); + ASSERT_EQ(aom_codec_control(&codec, AOME_SET_CPUUSED, 0), AOM_CODEC_OK); + + // Encode frame 0 at 64x64 + aom_image_t raw; + ASSERT_NE(aom_img_alloc(&raw, AOM_IMG_FMT_I420, 64, 64, 1), nullptr); + FillImageRandom(&raw); + ASSERT_EQ(aom_codec_encode(&codec, &raw, 0, 1, 0), AOM_CODEC_OK); + aom_img_free(&raw); + + // Change resolution to 256x256 mid-stream + cfg.g_w = 256; + cfg.g_h = 256; + ASSERT_EQ(aom_codec_enc_config_set(&codec, &cfg), AOM_CODEC_OK); + + // Encode frame 1 at 256x256 + ASSERT_NE(aom_img_alloc(&raw, AOM_IMG_FMT_I420, 256, 256, 1), nullptr); + FillImageRandom(&raw); + ASSERT_EQ(aom_codec_encode(&codec, &raw, 1, 1, 0), AOM_CODEC_OK); + aom_img_free(&raw); + + ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK); +} +#endif // !CONFIG_REALTIME_ONLY + +#if !CONFIG_REALTIME_ONLY +TEST(EncodeAPI, Buganizer558417547) { +#if !CONFIG_SHARED + std::unique_ptr<AV1_COMP> cpi_test(new AV1_COMP()); + SequenceHeader seq_params = {}; + seq_params.bit_depth = AOM_BITS_8; + seq_params.sb_size = BLOCK_64X64; + cpi_test->common.seq_params = &seq_params; + cpi_test->common.mi_params.mi_rows = 16; + cpi_test->common.mi_params.mi_cols = 16; + cpi_test->common.quant_params.base_qindex = 128; + cpi_test->common.delta_q_info.delta_q_res = 4; + cpi_test->frame_info.mi_rows = 16; + cpi_test->frame_info.mi_cols = 16; + cpi_test->norm_wiener_variance = 100; + struct aom_internal_error_info error = {}; + if (setjmp(error.jmp)) FAIL(); + error.setjmp = 1; + cpi_test->common.error = &error; + av1_init_mb_wiener_var_buffer(cpi_test.get()); + ASSERT_NE(cpi_test->mb_weber_stats, nullptr); + + // Set large SATD and distortion values exceeding INT32_MAX. + for (int i = 0; i < 16 * 16; ++i) { + cpi_test->mb_weber_stats[i].satd = 3000000000LL; + cpi_test->mb_weber_stats[i].distortion = 3000000000LL; + cpi_test->mb_weber_stats[i].rec_pix_max = 255; + } + + // Test out-of-bounds / negative mi_row and mi_col without crashing or SIGFPE. + int q_neg = av1_get_sbq_perceptual_ai(cpi_test.get(), BLOCK_64X64, -16, -16); + EXPECT_GE(q_neg, 0); + int q_oob = av1_get_sbq_perceptual_ai(cpi_test.get(), BLOCK_64X64, 100, 100); + EXPECT_GE(q_oob, 0); + + aom_free(cpi_test->mb_weber_stats); +#endif // !CONFIG_SHARED +} +#endif // !CONFIG_REALTIME_ONLY + } // namespace
diff --git a/test/end_to_end_psnr_test.cc b/test/end_to_end_psnr_test.cc index b006509..26c286b 100644 --- a/test/end_to_end_psnr_test.cc +++ b/test/end_to_end_psnr_test.cc
@@ -31,11 +31,11 @@ const unsigned int kCqLevel = 18; // List of psnr thresholds for speed settings 0-8 and 4 encoding modes const double kPsnrThreshold[][4] = { - { 34.9, 44.4, 39.5, 41.9 }, { 34.9, 44.4, 39.5, 41.9 }, - { 34.9, 44.4, 39.4, 41.9 }, { 34.9, 44.4, 39.1, 41.8 }, - { 34.9, 44.4, 39.1, 41.8 }, { 34.9, 44.29, 38.5, 41.8 }, - { 34.9, 44.3, 38.5, 41.3 }, { 34.9, 44.3, 38.5, 40.8 }, - { 34.9, 44.3, 38.5, 40.8 } + { 34.1, 44.4, 39.5, 41.9 }, { 34.1, 44.4, 39.5, 41.9 }, + { 34.1, 44.4, 39.4, 41.9 }, { 34.1, 44.4, 39.1, 41.8 }, + { 34.1, 44.4, 39.1, 41.8 }, { 34.1, 44.29, 38.5, 41.8 }, + { 34.1, 44.3, 38.5, 41.3 }, { 34.1, 44.3, 38.5, 40.8 }, + { 34.1, 44.3, 38.5, 40.8 } }; struct TestVideoParam {
diff --git a/test/forced_max_frame_width_height_test.cc b/test/forced_max_frame_width_height_test.cc index 5211d66..762de80 100644 --- a/test/forced_max_frame_width_height_test.cc +++ b/test/forced_max_frame_width_height_test.cc
@@ -22,6 +22,10 @@ #include "aom/aomcx.h" #include "aom/aom_encoder.h" #include "config/aom_config.h" +#if CONFIG_AV1_DECODER +#include "aom/aom_decoder.h" +#include "aom/aomdx.h" +#endif #include "gtest/gtest.h" namespace { @@ -277,4 +281,292 @@ EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc)); } +// A regression test for bug 555350218. +TEST(EncodeForcedMaxFrameWidthHeight, ReducedStillPictureHeader) { + constexpr size_t kImageDataSize = 128 * 128 + 2 * 64 * 64; + std::unique_ptr<unsigned char[]> img_data(new unsigned char[kImageDataSize]); + ASSERT_NE(img_data, nullptr); + memset(img_data.get(), 128, kImageDataSize); + aom_image_t img; + EXPECT_EQ(&img, + aom_img_wrap(&img, AOM_IMG_FMT_I420, 128, 128, 1, img_data.get())); + + aom_codec_iface_t *iface = aom_codec_av1_cx(); + aom_codec_enc_cfg_t cfg; + EXPECT_EQ(AOM_CODEC_OK, + aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME)); + cfg.g_w = 128; + cfg.g_h = 128; + cfg.g_limit = 1; + + aom_codec_ctx_t enc; + + cfg.g_forced_max_frame_width = 128; + cfg.g_forced_max_frame_height = 128; + EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 0, 1, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc)); + + cfg.g_forced_max_frame_width = 256; + cfg.g_forced_max_frame_height = 256; + EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0)); + EXPECT_NE(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 0, 1, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc)); +} + +TEST(EncodeForcedMaxFrameWidthHeight, DynamicUpscaleDoesNotForceKeyframe) { + constexpr size_t kImageDataSize = 256 * 256 + 2 * 128 * 128; + std::unique_ptr<unsigned char[]> img_data(new unsigned char[kImageDataSize]); + ASSERT_NE(img_data, nullptr); + memset(img_data.get(), 128, kImageDataSize); + + aom_codec_iface_t *iface = aom_codec_av1_cx(); + aom_codec_enc_cfg_t cfg; + EXPECT_EQ(AOM_CODEC_OK, + aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME)); + cfg.g_w = 128; + cfg.g_h = 128; + cfg.g_forced_max_frame_width = 256; + cfg.g_forced_max_frame_height = 256; + cfg.g_lag_in_frames = 0; + cfg.kf_mode = AOM_KF_DISABLED; + aom_codec_ctx_t enc; + EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AOME_SET_CPUUSED, 7)); + +#if CONFIG_AV1_DECODER + aom_codec_ctx_t dec; + EXPECT_EQ(AOM_CODEC_OK, + aom_codec_dec_init(&dec, aom_codec_av1_dx(), nullptr, 0)); +#endif + + // Frame 0: 128x128 keyframe. + aom_image_t img; + EXPECT_EQ(&img, + aom_img_wrap(&img, AOM_IMG_FMT_I420, 128, 128, 1, img_data.get())); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 0, 1, 0)); + + aom_codec_iter_t iter = nullptr; + const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter); + ASSERT_NE(pkt, nullptr); + EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT); + EXPECT_TRUE(pkt->data.frame.flags & AOM_FRAME_IS_KEY); + +#if CONFIG_AV1_DECODER + EXPECT_EQ( + AOM_CODEC_OK, + aom_codec_decode(&dec, static_cast<const uint8_t *>(pkt->data.frame.buf), + pkt->data.frame.sz, nullptr)); + aom_codec_iter_t dec_iter = nullptr; + aom_image_t *dec_img = aom_codec_get_frame(&dec, &dec_iter); + ASSERT_NE(dec_img, nullptr); + EXPECT_EQ(dec_img->d_w, 128u); + EXPECT_EQ(dec_img->d_h, 128u); +#endif + + // Frame 1: Upscale to 256x256. + cfg.g_w = 256; + cfg.g_h = 256; + EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_config_set(&enc, &cfg)); + + EXPECT_EQ(&img, + aom_img_wrap(&img, AOM_IMG_FMT_I420, 256, 256, 1, img_data.get())); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 1, 1, 0)); + + iter = nullptr; + pkt = aom_codec_get_cx_data(&enc, &iter); + ASSERT_NE(pkt, nullptr); + EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT); + EXPECT_FALSE(pkt->data.frame.flags & AOM_FRAME_IS_KEY); + +#if CONFIG_AV1_DECODER + EXPECT_EQ( + AOM_CODEC_OK, + aom_codec_decode(&dec, static_cast<const uint8_t *>(pkt->data.frame.buf), + pkt->data.frame.sz, nullptr)); + dec_iter = nullptr; + dec_img = aom_codec_get_frame(&dec, &dec_iter); + ASSERT_NE(dec_img, nullptr); + EXPECT_EQ(dec_img->d_w, 256u); + EXPECT_EQ(dec_img->d_h, 256u); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&dec)); +#endif + + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, nullptr, 0, 0, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc)); +} + +TEST(EncodeForcedMaxFrameWidthHeight, DynamicUpscaleUpTo16x) { + constexpr size_t kImageDataSize = 2048 * 2048 + 2 * 1024 * 1024; + std::unique_ptr<unsigned char[]> img_data(new unsigned char[kImageDataSize]); + ASSERT_NE(img_data, nullptr); + memset(img_data.get(), 128, kImageDataSize); + + aom_codec_iface_t *iface = aom_codec_av1_cx(); + aom_codec_enc_cfg_t cfg; + EXPECT_EQ(AOM_CODEC_OK, + aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME)); + cfg.g_w = 128; + cfg.g_h = 128; + cfg.g_forced_max_frame_width = 2048; + cfg.g_forced_max_frame_height = 2048; + cfg.g_lag_in_frames = 0; + cfg.kf_mode = AOM_KF_DISABLED; + aom_codec_ctx_t enc; + EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AOME_SET_CPUUSED, 7)); + +#if CONFIG_AV1_DECODER + aom_codec_ctx_t dec; + EXPECT_EQ(AOM_CODEC_OK, + aom_codec_dec_init(&dec, aom_codec_av1_dx(), nullptr, 0)); +#endif + + // Frame 0: 128x128 keyframe. + aom_image_t img; + EXPECT_EQ(&img, + aom_img_wrap(&img, AOM_IMG_FMT_I420, 128, 128, 1, img_data.get())); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 0, 1, 0)); + + aom_codec_iter_t iter = nullptr; + const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter); + ASSERT_NE(pkt, nullptr); + EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT); + EXPECT_TRUE(pkt->data.frame.flags & AOM_FRAME_IS_KEY); + +#if CONFIG_AV1_DECODER + EXPECT_EQ( + AOM_CODEC_OK, + aom_codec_decode(&dec, static_cast<const uint8_t *>(pkt->data.frame.buf), + pkt->data.frame.sz, nullptr)); +#endif + + // Frame 1: Upscale 16x to 2048x2048. + cfg.g_w = 2048; + cfg.g_h = 2048; + EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_config_set(&enc, &cfg)); + + EXPECT_EQ(&img, aom_img_wrap(&img, AOM_IMG_FMT_I420, 2048, 2048, 1, + img_data.get())); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 1, 1, 0)); + + iter = nullptr; + pkt = aom_codec_get_cx_data(&enc, &iter); + ASSERT_NE(pkt, nullptr); + EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT); + EXPECT_FALSE(pkt->data.frame.flags & AOM_FRAME_IS_KEY); + +#if CONFIG_AV1_DECODER + EXPECT_EQ( + AOM_CODEC_OK, + aom_codec_decode(&dec, static_cast<const uint8_t *>(pkt->data.frame.buf), + pkt->data.frame.sz, nullptr)); + aom_codec_iter_t dec_iter = nullptr; + aom_image_t *dec_img = aom_codec_get_frame(&dec, &dec_iter); + ASSERT_NE(dec_img, nullptr); + EXPECT_EQ(dec_img->d_w, 2048u); + EXPECT_EQ(dec_img->d_h, 2048u); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&dec)); +#endif + + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, nullptr, 0, 0, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc)); +} + +// Values of the AV1_LEVEL enum in av1/common/enums.h, which is not part of the +// public API. +// +// Passing kLevelKeepStats as the target level only switches on level statistics +// collection. Unlike a real target level it does not constrain the encoder, so +// it does not change what the rest of the test measures. +constexpr int kSeqLevelIdx5_0 = 12; +constexpr int kLevelKeepStats = 32; +constexpr int kMaxOperatingPoints = 32; + +TEST(EncodeForcedMaxFrameWidthHeight, + SequenceLevelIndexUsesForcedMaxDimensions) { + constexpr size_t kImageDataSize = 2560 * 1536 + 2 * 1280 * 768; + std::unique_ptr<unsigned char[]> img_data(new unsigned char[kImageDataSize]); + ASSERT_NE(img_data, nullptr); + memset(img_data.get(), 128, kImageDataSize); + + aom_codec_iface_t *iface = aom_codec_av1_cx(); + aom_codec_enc_cfg_t cfg; + EXPECT_EQ(AOM_CODEC_OK, + aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME)); + cfg.g_w = 640; + cfg.g_h = 384; + cfg.g_forced_max_frame_width = 2560; + cfg.g_forced_max_frame_height = 1536; + cfg.g_lag_in_frames = 0; + cfg.kf_mode = AOM_KF_DISABLED; + aom_codec_ctx_t enc; + EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_init(&enc, iface, &cfg, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AOME_SET_CPUUSED, 7)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AV1E_SET_TARGET_SEQ_LEVEL_IDX, + kLevelKeepStats)); + +#if CONFIG_AV1_DECODER + aom_codec_ctx_t dec; + EXPECT_EQ(AOM_CODEC_OK, + aom_codec_dec_init(&dec, aom_codec_av1_dx(), nullptr, 0)); +#endif + + // Frame 0: 640x384 keyframe. + aom_image_t img; + EXPECT_EQ(&img, + aom_img_wrap(&img, AOM_IMG_FMT_I420, 640, 384, 1, img_data.get())); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 0, 1, 0)); + + aom_codec_iter_t iter = nullptr; + const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter); + ASSERT_NE(pkt, nullptr); + EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT); + +#if CONFIG_AV1_DECODER + EXPECT_EQ( + AOM_CODEC_OK, + aom_codec_decode(&dec, static_cast<const uint8_t *>(pkt->data.frame.buf), + pkt->data.frame.sz, nullptr)); +#endif + + // Frame 1: Upscale 4x to 2560x1536. + cfg.g_w = 2560; + cfg.g_h = 1536; + EXPECT_EQ(AOM_CODEC_OK, aom_codec_enc_config_set(&enc, &cfg)); + + EXPECT_EQ(&img, aom_img_wrap(&img, AOM_IMG_FMT_I420, 2560, 1536, 1, + img_data.get())); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 1, 1, 0)); + + iter = nullptr; + pkt = aom_codec_get_cx_data(&enc, &iter); + ASSERT_NE(pkt, nullptr); + EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT); + EXPECT_FALSE(pkt->data.frame.flags & AOM_FRAME_IS_KEY); + +#if CONFIG_AV1_DECODER + EXPECT_EQ( + AOM_CODEC_OK, + aom_codec_decode(&dec, static_cast<const uint8_t *>(pkt->data.frame.buf), + pkt->data.frame.sz, nullptr)); + aom_codec_iter_t dec_iter = nullptr; + aom_image_t *dec_img = aom_codec_get_frame(&dec, &dec_iter); + ASSERT_NE(dec_img, nullptr); + EXPECT_EQ(dec_img->d_w, 2560u); + EXPECT_EQ(dec_img->d_h, 1536u); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&dec)); +#endif + + // The level the encoder derives from the statistics it collected has to + // account for the upscaled frame, not just the initial 640x384 one. + int seq_level_idx[kMaxOperatingPoints]; + EXPECT_EQ(AOM_CODEC_OK, + aom_codec_control(&enc, AV1E_GET_SEQ_LEVEL_IDX, seq_level_idx)); + EXPECT_EQ(seq_level_idx[0], kSeqLevelIdx5_0); + + EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, nullptr, 0, 0, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc)); +} + } // namespace
diff --git a/test/frame_size_tests.cc b/test/frame_size_tests.cc index 4ed8354..96d5448 100644 --- a/test/frame_size_tests.cc +++ b/test/frame_size_tests.cc
@@ -222,11 +222,25 @@ iter = nullptr; while ((pkt = aom_codec_get_cx_data(enc.get(), &iter)) != nullptr) { ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT); - // The frame following a resolution change should be a keyframe as the - // change is too extreme to allow previous references to be used. - if (i == 0 || usage_ == AOM_USAGE_ALL_INTRA) { + // All the resolution changes above are within the reference frame + // scaling limits (up to 16x up and 2x down). In single pass realtime + // mode without lookahead, and with the maximum frame size declared up + // front via g_forced_max_frame_width/height, such changes are coded as + // inter frames that scale their references, so only the very first + // frame is a keyframe. Other modes force a keyframe on every + // resolution change. + const bool scales_references = usage_ == AOM_USAGE_REALTIME; + if (usage_ == AOM_USAGE_ALL_INTRA || frame_count == 0) { EXPECT_NE(pkt->data.frame.flags & AOM_FRAME_IS_KEY, 0u) << "frame " << frame_count; + } else if (i == 0) { + if (scales_references) { + EXPECT_EQ(pkt->data.frame.flags & AOM_FRAME_IS_KEY, 0u) + << "frame " << frame_count; + } else { + EXPECT_NE(pkt->data.frame.flags & AOM_FRAME_IS_KEY, 0u) + << "frame " << frame_count; + } } frame_count++; }
diff --git a/test/ratectrl_rtc_test.cc b/test/ratectrl_rtc_test.cc index 4760d39..9120122 100644 --- a/test/ratectrl_rtc_test.cc +++ b/test/ratectrl_rtc_test.cc
@@ -551,6 +551,8 @@ void TestGetCdefInfoRateControl(); void TestCreateRateControlConfig(); void TestDestroyRateControlRTC(); + void TestBitDepthRateControl(); + void TestLoopFilterLevelBitDepth(); void SetConfig(); private: @@ -694,7 +696,7 @@ av1_ratecontrol_rtc_init_ratecontrol_config(&config); ASSERT_EQ(config.width, 1280); ASSERT_EQ(config.height, 720); - // only width and height is checked. can be extended + ASSERT_EQ(config.bit_depth, 8); av1_ratecontrol_rtc_destroy(controller); } @@ -706,6 +708,72 @@ av1_ratecontrol_rtc_destroy(controller); } +void RcExternMethodsInterfaceTest::TestBitDepthRateControl() { + // Invalid bit-depth values should fail. + rc_cfg_.bit_depth = 9; + AomAV1RateControlRTC *invalid_controller = + av1_ratecontrol_rtc_create(&rc_cfg_); + ASSERT_EQ(invalid_controller, nullptr); + + rc_cfg_.bit_depth = -1; + invalid_controller = av1_ratecontrol_rtc_create(&rc_cfg_); + ASSERT_EQ(invalid_controller, nullptr); + + rc_cfg_.bit_depth = 14; + invalid_controller = av1_ratecontrol_rtc_create(&rc_cfg_); + ASSERT_EQ(invalid_controller, nullptr); + + // 8-bit depth is always supported. + rc_cfg_.bit_depth = 8; + AomAV1RateControlRTC *controller_8 = av1_ratecontrol_rtc_create(&rc_cfg_); + ASSERT_NE(controller_8, nullptr); + // Attempting to change bit-depth dynamically should fail. + rc_cfg_.bit_depth = 10; + ASSERT_FALSE(av1_ratecontrol_rtc_update(controller_8, &rc_cfg_)); + av1_ratecontrol_rtc_destroy(controller_8); + + rc_cfg_.bit_depth = 10; + AomAV1RateControlRTC *controller_10 = av1_ratecontrol_rtc_create(&rc_cfg_); + ASSERT_NE(controller_10, nullptr); + rc_cfg_.bit_depth = 8; + ASSERT_FALSE(av1_ratecontrol_rtc_update(controller_10, &rc_cfg_)); + av1_ratecontrol_rtc_destroy(controller_10); + + rc_cfg_.bit_depth = 12; + AomAV1RateControlRTC *controller_12 = av1_ratecontrol_rtc_create(&rc_cfg_); + ASSERT_NE(controller_12, nullptr); + av1_ratecontrol_rtc_destroy(controller_12); +} + +void RcExternMethodsInterfaceTest::TestLoopFilterLevelBitDepth() { + frame_params_.spatial_layer_id = 0; + frame_params_.temporal_layer_id = 0; + frame_params_.frame_type = kAomKeyFrame; + + rc_cfg_.bit_depth = 8; + AomAV1RateControlRTC *controller_8 = av1_ratecontrol_rtc_create(&rc_cfg_); + ASSERT_NE(controller_8, nullptr); + ASSERT_EQ(av1_ratecontrol_rtc_compute_qp(controller_8, &frame_params_), + kAomFrameDropDecisionOk); + const AomAV1LoopfilterLevel lpf_8 = + av1_ratecontrol_rtc_get_loop_filter_level(controller_8); + av1_ratecontrol_rtc_destroy(controller_8); + + rc_cfg_.bit_depth = 10; + AomAV1RateControlRTC *controller_10 = av1_ratecontrol_rtc_create(&rc_cfg_); + ASSERT_NE(controller_10, nullptr); + ASSERT_EQ(av1_ratecontrol_rtc_compute_qp(controller_10, &frame_params_), + kAomFrameDropDecisionOk); + const AomAV1LoopfilterLevel lpf_10 = + av1_ratecontrol_rtc_get_loop_filter_level(controller_10); + av1_ratecontrol_rtc_destroy(controller_10); + + // For 10-bit AV1 encoding, the estimated loopfilter level should be stronger + // than for 8-bit at the same configuration. + EXPECT_GT(lpf_10.filter_level[0], lpf_8.filter_level[0]); + EXPECT_GT(lpf_10.filter_level[1], lpf_8.filter_level[1]); +} + TEST_P(RcInterfaceTest, OneLayer) { RunOneLayer(); } TEST_P(RcInterfaceTest, OneLayerDropFramesCBR) { RunOneLayerDropFramesCBR(); } @@ -764,6 +832,14 @@ TestDestroyRateControlRTC(); } +TEST_P(RcExternMethodsInterfaceTest, BitDepthRateControllerTest) { + TestBitDepthRateControl(); +} + +TEST_P(RcExternMethodsInterfaceTest, LoopFilterLevelBitDepthTest) { + TestLoopFilterLevelBitDepth(); +} + AV1_INSTANTIATE_TEST_SUITE(RcInterfaceTest, ::testing::Values(0, 3)); AV1_INSTANTIATE_TEST_SUITE(RcExternMethodsInterfaceTest, ::testing::Values(0, 3));