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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions libvmaf/src/feature/float_motion.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ static int init(VmafFeatureExtractor *fex, enum VmafPixelFormat pix_fmt,

MotionState *s = fex->priv;

if (s->motion_add_scale1) {
int scaled_width = (int)(w * 0.5 + 0.5);
if (scaled_width > VIF_BILINEAR_MAX_WIDTH) return -EINVAL;
}

s->float_stride = ALIGN_CEIL(w * sizeof(float));
s->ref = aligned_malloc(s->float_stride * h, 32);
s->tmp = aligned_malloc(s->float_stride * h, 32);
Expand Down
5 changes: 5 additions & 0 deletions libvmaf/src/feature/float_vif.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ static int init(VmafFeatureExtractor *fex, enum VmafPixelFormat pix_fmt,

s->scaled_w = (int)(w * s->vif_prescale + 0.5);
s->scaled_h = (int)(h * s->vif_prescale + 0.5);
if (scaling_method == vif_scale_bilinear &&
s->scaled_w > VIF_BILINEAR_MAX_WIDTH)
{
return -EINVAL;
}
s->float_stride = ALIGN_CEIL(w * sizeof(float));
s->scaled_float_stride = ALIGN_CEIL(s->scaled_w * sizeof(float));
s->ref = aligned_malloc(s->float_stride * h, 32);
Expand Down
61 changes: 55 additions & 6 deletions libvmaf/src/feature/speed.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ typedef struct SpeedBuffers {
float *cov_mat;
float *eigenvalues;
float *tmp_buffer;
// Bilinear column table for the (fixed, resolution-derived) prescale of
// this feature extractor instance. Populated once in speed_init() and
// reused for every frame instead of being recomputed per call. NULL
// when the configured scaling method isn't bilinear.
int *bilinear_x1a;
int *bilinear_x2a;
float *bilinear_dxa;
} SpeedBuffers;

// Everything that is passed in as a feature option and is needed for
Expand Down Expand Up @@ -938,12 +945,13 @@ static void subtract_image(float *im1, float *im2, int w, int h, size_t stride)
// Filters the image with a Gaussian filter and then performs local
// mean subtraction
static void filter_and_downscale(SpeedDimensions dim, SpeedOptions *opt,
float *frame_buffer, float *tmp_buffer,
float *frame_buffer, SpeedBuffers *bufs,
size_t float_stride)
{
size_t stride_px = float_stride / sizeof(float);

size_t frame_size = stride_px * dim.alloc_height;
float *tmp_buffer = bufs->tmp_buffer;
float *curr_scale = tmp_buffer; tmp_buffer += frame_size;
float *tmpbuf = tmp_buffer; tmp_buffer += frame_size;

Expand All @@ -954,9 +962,19 @@ static void filter_and_downscale(SpeedDimensions dim, SpeedOptions *opt,
if (!ALMOST_EQUAL(opt->speed_prescale, 1.0)) {
memcpy(tmpbuf, frame_buffer,
stride_px * dim.alloc_height * sizeof(float));
vif_scale_frame_s(scaling_method, tmpbuf, frame_buffer,
dim.original_width, dim.original_height, stride_px,
dim.scaled_width, dim.scaled_height, stride_px);
if (scaling_method == vif_scale_bilinear && bufs->bilinear_x1a) {
// Column table was precomputed once in speed_init() for this
// instance's fixed src_w/dst_w, instead of being recomputed on
// every call as vif_scale_frame_s(vif_scale_bilinear, ...) does.
vif_scale_frame_bilinear_precomputed_s(tmpbuf, frame_buffer,
dim.original_width, dim.original_height, stride_px,
dim.scaled_width, dim.scaled_height, stride_px,
bufs->bilinear_x1a, bufs->bilinear_x2a, bufs->bilinear_dxa);
} else {
vif_scale_frame_s(scaling_method, tmpbuf, frame_buffer,
dim.original_width, dim.original_height, stride_px,
dim.scaled_width, dim.scaled_height, stride_px);
}
}

// The kernelscale has been checked for validity in the init callback
Expand Down Expand Up @@ -986,11 +1004,11 @@ static void filter_and_downscale(SpeedDimensions dim, SpeedOptions *opt,
int speed_extract_score(SpeedState *s, SpeedOptions *opt, float *ref,
float *dis, float *score)
{
filter_and_downscale(s->dimensions, opt, ref, s->buffers.tmp_buffer,
filter_and_downscale(s->dimensions, opt, ref, &s->buffers,
s->float_stride);
int err_ref = est_params(s, ref, opt->speed_sigma_nn, &(s->ref_results));

filter_and_downscale(s->dimensions, opt, dis, s->buffers.tmp_buffer,
filter_and_downscale(s->dimensions, opt, dis, &s->buffers,
s->float_stride);

int err_dis = est_params(s, dis, opt->speed_sigma_nn, &(s->dis_results));
Expand Down Expand Up @@ -1084,6 +1102,31 @@ int speed_init(SpeedState *s, SpeedOptions *opt, int w, int h)
if (!s->buffers.tmp_buffer)
return -ENOMEM;

s->buffers.bilinear_x1a = NULL;
s->buffers.bilinear_x2a = NULL;
s->buffers.bilinear_dxa = NULL;
if (scaling_method == vif_scale_bilinear &&
!ALMOST_EQUAL(opt->speed_prescale, 1.0)) {
// The column table only depends on original_width/scaled_width, both
// fixed for the lifetime of this instance, so it's computed once here
// instead of on every filter_and_downscale() call.
s->buffers.bilinear_x1a =
aligned_malloc(sizeof(int) * dim->scaled_width, 32);
if (!s->buffers.bilinear_x1a)
return -ENOMEM;
s->buffers.bilinear_x2a =
aligned_malloc(sizeof(int) * dim->scaled_width, 32);
if (!s->buffers.bilinear_x2a)
return -ENOMEM;
s->buffers.bilinear_dxa =
aligned_malloc(sizeof(float) * dim->scaled_width, 32);
if (!s->buffers.bilinear_dxa)
return -ENOMEM;
vif_scale_frame_bilinear_precompute_columns_s(dim->original_width,
dim->scaled_width, s->buffers.bilinear_x1a,
s->buffers.bilinear_x2a, s->buffers.bilinear_dxa);
}

s->ref_results.entropies = aligned_malloc(sizeof(float) * dim->num_blocks, 32);
if (!s->ref_results.entropies)
return -ENOMEM;
Expand Down Expand Up @@ -1124,6 +1167,12 @@ int speed_close(SpeedState *s) {
aligned_free(s->buffers.eigenvalues);
if (s->buffers.tmp_buffer)
aligned_free(s->buffers.tmp_buffer);
if (s->buffers.bilinear_x1a)
aligned_free(s->buffers.bilinear_x1a);
if (s->buffers.bilinear_x2a)
aligned_free(s->buffers.bilinear_x2a);
if (s->buffers.bilinear_dxa)
aligned_free(s->buffers.bilinear_dxa);

if (s->ref_results.entropies)
aligned_free(s->ref_results.entropies);
Expand Down
90 changes: 64 additions & 26 deletions libvmaf/src/feature/vif_tools.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <errno.h>
#include <float.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
Expand Down Expand Up @@ -656,42 +657,79 @@ static void vif_scale_frame_lanczos4_s(const float *src, float *dst,
}
}

static float bilinear_interpolation(const float *src, int width, int height, int src_stride, float x, float y) {
int x1 = mirror(floor(x), 0, width - 1);
int x2 = mirror(ceil(x), 0, width - 1);
int y1 = mirror(floor(y), 0, height - 1);
int y2 = mirror(ceil(y), 0, height - 1);

float dx = x - x1;
float dy = y - y1;

return (
(1 - dy) * (1 - dx) * src[y1 * src_stride + x1] +
(1 - dy) * dx * src[y1 * src_stride + x2] +
dy * (1 - dx) * src[y2 * src_stride + x1] +
dy * dx * src[y2 * src_stride + x2]
);
// The source indices and fractional weight for bilinear sampling depend
// only on the output column -- computing them once per column and reusing
// them across all rows avoids redoing floor/ceil/mirror once per pixel.
void vif_scale_frame_bilinear_precompute_columns_s(int src_w, int dst_w,
int *x1a, int *x2a, float *dxa) {
float ratio_x = (float)src_w / dst_w;
for (int x = 0; x < dst_w; x++) {
float xx = (x + 0.5) * ratio_x - 0.5;
x1a[x] = mirror(floor(xx), 0, src_w - 1);
x2a[x] = mirror(ceil(xx), 0, src_w - 1);
dxa[x] = xx - x1a[x];
}
}

static void vif_scale_frame_bilinear_s(const float *src, float *dst,
int src_w, int src_h, int src_stride,
int dst_w, int dst_h, int dst_stride) {
static void vif_scale_frame_bilinear_apply_s(const float *src, float *dst,
int src_h, int src_stride,
int dst_w, int dst_h, int dst_stride,
const int *x1a, const int *x2a, const float *dxa) {
float ratio_y = (float)src_h / dst_h;
for (int y = 0; y < dst_h; y++) {
float yy = (y + 0.5) * ratio_y - 0.5;
int y1 = mirror(floor(yy), 0, src_h - 1);
int y2 = mirror(ceil(yy), 0, src_h - 1);
float dy = yy - y1;
const float *r1 = src + (size_t)y1 * src_stride;
const float *r2 = src + (size_t)y2 * src_stride;
float *drow = dst + (size_t)y * dst_stride;
for (int x = 0; x < dst_w; x++) {
int x1 = x1a[x], x2 = x2a[x];
float dx = dxa[x];
drow[x] = (1 - dy) * (1 - dx) * r1[x1] +
(1 - dy) * dx * r1[x2] +
dy * (1 - dx) * r2[x1] +
dy * dx * r2[x2];
}
}
}

void vif_scale_frame_bilinear_precomputed_s(const float *src, float *dst,
int src_w, int src_h, int src_stride,
int dst_w, int dst_h, int dst_stride,
const int *x1a, const int *x2a, const float *dxa) {
// if the input and output sizes are the same
if (src_w == dst_w && src_h == dst_h) {
memcpy(dst, src, dst_stride * dst_h * sizeof(float));
return;
}

float ratio_x = (float)src_w / dst_w;
float ratio_y = (float)src_h / dst_h;
vif_scale_frame_bilinear_apply_s(src, dst, src_h, src_stride,
dst_w, dst_h, dst_stride, x1a, x2a, dxa);
}

for (int y = 0; y < dst_h; y++) {
float yy = (y + 0.5) * ratio_y - 0.5;
for (int x = 0; x < dst_w; x++) {
float xx = (x + 0.5) * ratio_x - 0.5;
dst[y * dst_stride + x] = bilinear_interpolation(src, src_w, src_h, src_stride, xx, yy);
}
static void vif_scale_frame_bilinear_s(const float *src, float *dst,
int src_w, int src_h, int src_stride,
int dst_w, int dst_h, int dst_stride) {
// if the input and output sizes are the same
if (src_w == dst_w && src_h == dst_h) {
memcpy(dst, src, dst_stride * dst_h * sizeof(float));
return;
}

// Column tables live on the stack, so the common case has no allocation
// at all. VLAs aren't used here since MSVC has no C99 VLA support.
// Callers must ensure dst_w <= VIF_BILINEAR_MAX_WIDTH before reaching
// here (checked once at init time; see vif_tools.h).
assert(dst_w <= VIF_BILINEAR_MAX_WIDTH);
int x1a[VIF_BILINEAR_MAX_WIDTH];
int x2a[VIF_BILINEAR_MAX_WIDTH];
float dxa[VIF_BILINEAR_MAX_WIDTH];

vif_scale_frame_bilinear_precompute_columns_s(src_w, dst_w, x1a, x2a, dxa);
vif_scale_frame_bilinear_apply_s(src, dst, src_h, src_stride,
dst_w, dst_h, dst_stride, x1a, x2a, dxa);
}

static void vif_scale_frame_nearest_s(const float *src, float *dst,
Expand Down
19 changes: 19 additions & 0 deletions libvmaf/src/feature/vif_tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,27 @@ void vif_filter1d_xy_s(const float *f, const float *src1, const float *src2, flo

int vif_get_scaling_method(char *scaling_method_str, enum vif_scaling_method *scale_method);

// Bilinear scaling (vif_scale_frame_s with vif_scale_bilinear, and
// vif_scale_frame_bilinear_precompute_columns_s/_precomputed_s) builds its
// column tables on the stack and does not support dst_w beyond this. Callers
// with a fixed prescale factor must check their dst_w against this limit at
// init time and fail rather than call into the scaler with a wider output.
#define VIF_BILINEAR_MAX_WIDTH 7680 // covers up to 8K frame width

void vif_scale_frame_s(enum vif_scaling_method scale_method, const float *src, float *dst, int src_w, int src_h, int src_stride, int dst_w, int dst_h, int dst_stride);

// Precomputes the per-output-column source indices/weight used by bilinear
// scaling. x1a, x2a and dxa must each have room for dst_w elements. The
// table only depends on src_w/dst_w, so callers that scale many frames at a
// fixed resolution (e.g. a feature extractor with a fixed prescale) can
// compute it once and reuse it via vif_scale_frame_bilinear_precomputed_s.
void vif_scale_frame_bilinear_precompute_columns_s(int src_w, int dst_w, int *x1a, int *x2a, float *dxa);

// Same as vif_scale_frame_s(vif_scale_bilinear, ...), but using a column
// table already filled in by vif_scale_frame_bilinear_precompute_columns_s
// for this src_w/dst_w instead of recomputing it.
void vif_scale_frame_bilinear_precomputed_s(const float *src, float *dst, int src_w, int src_h, int src_stride, int dst_w, int dst_h, int dst_stride, const int *x1a, const int *x2a, const float *dxa);

int vif_get_filter_size(int scale, float kernelscale);

void vif_get_filter(float *out, int scale, float kernelscale);
Expand Down
Loading