|
|
|
@ -24,6 +24,8 @@ |
|
|
|
|
* Calculate the VMAF between two input videos. |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
#include "config_components.h" |
|
|
|
|
|
|
|
|
|
#include <libvmaf.h> |
|
|
|
|
|
|
|
|
|
#include "libavutil/avstring.h" |
|
|
|
@ -36,6 +38,13 @@ |
|
|
|
|
#include "internal.h" |
|
|
|
|
#include "video.h" |
|
|
|
|
|
|
|
|
|
#if CONFIG_LIBVMAF_CUDA_FILTER |
|
|
|
|
#include <libvmaf_cuda.h> |
|
|
|
|
|
|
|
|
|
#include "libavutil/hwcontext.h" |
|
|
|
|
#include "libavutil/hwcontext_cuda_internal.h" |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
typedef struct LIBVMAFContext { |
|
|
|
|
const AVClass *class; |
|
|
|
|
FFFrameSync fs; |
|
|
|
@ -58,6 +67,9 @@ typedef struct LIBVMAFContext { |
|
|
|
|
unsigned model_cnt; |
|
|
|
|
unsigned frame_cnt; |
|
|
|
|
unsigned bpc; |
|
|
|
|
#if CONFIG_LIBVMAF_CUDA_FILTER |
|
|
|
|
VmafCudaState *cu_state; |
|
|
|
|
#endif |
|
|
|
|
} LIBVMAFContext; |
|
|
|
|
|
|
|
|
|
#define OFFSET(x) offsetof(LIBVMAFContext, x) |
|
|
|
@ -717,3 +729,201 @@ const AVFilter ff_vf_libvmaf = { |
|
|
|
|
FILTER_OUTPUTS(libvmaf_outputs), |
|
|
|
|
FILTER_PIXFMTS_ARRAY(pix_fmts), |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
#if CONFIG_LIBVMAF_CUDA_FILTER |
|
|
|
|
static const enum AVPixelFormat supported_formats[] = { |
|
|
|
|
AV_PIX_FMT_YUV420P, |
|
|
|
|
AV_PIX_FMT_YUV444P16, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
static int format_is_supported(enum AVPixelFormat fmt) |
|
|
|
|
{ |
|
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
for (i = 0; i < FF_ARRAY_ELEMS(supported_formats); i++) |
|
|
|
|
if (supported_formats[i] == fmt) |
|
|
|
|
return 1; |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int config_props_cuda(AVFilterLink *outlink) |
|
|
|
|
{ |
|
|
|
|
int err; |
|
|
|
|
AVFilterContext *ctx = outlink->src; |
|
|
|
|
LIBVMAFContext *s = ctx->priv; |
|
|
|
|
AVFilterLink *inlink = ctx->inputs[0]; |
|
|
|
|
AVHWFramesContext *frames_ctx = (AVHWFramesContext*) inlink->hw_frames_ctx->data; |
|
|
|
|
AVCUDADeviceContext *device_hwctx = frames_ctx->device_ctx->hwctx; |
|
|
|
|
CUcontext cu_ctx = device_hwctx->cuda_ctx; |
|
|
|
|
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frames_ctx->sw_format); |
|
|
|
|
|
|
|
|
|
VmafConfiguration cfg = { |
|
|
|
|
.log_level = log_level_map(av_log_get_level()), |
|
|
|
|
.n_subsample = s->n_subsample, |
|
|
|
|
.n_threads = s->n_threads, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
VmafCudaPictureConfiguration cuda_pic_cfg = { |
|
|
|
|
.pic_params = { |
|
|
|
|
.bpc = desc->comp[0].depth, |
|
|
|
|
.w = inlink->w, |
|
|
|
|
.h = inlink->h, |
|
|
|
|
.pix_fmt = pix_fmt_map(frames_ctx->sw_format), |
|
|
|
|
}, |
|
|
|
|
.pic_prealloc_method = VMAF_CUDA_PICTURE_PREALLOCATION_METHOD_DEVICE, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
VmafCudaConfiguration cuda_cfg = { |
|
|
|
|
.cu_ctx = cu_ctx, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (!format_is_supported(frames_ctx->sw_format)) { |
|
|
|
|
av_log(s, AV_LOG_ERROR, |
|
|
|
|
"Unsupported input format: %s\n", desc->name); |
|
|
|
|
return AVERROR(EINVAL); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err = vmaf_init(&s->vmaf, cfg); |
|
|
|
|
if (err) |
|
|
|
|
return AVERROR(EINVAL); |
|
|
|
|
|
|
|
|
|
err = vmaf_cuda_state_init(&s->cu_state, cuda_cfg); |
|
|
|
|
if (err) |
|
|
|
|
return AVERROR(EINVAL); |
|
|
|
|
|
|
|
|
|
err = vmaf_cuda_import_state(s->vmaf, s->cu_state); |
|
|
|
|
if (err) |
|
|
|
|
return AVERROR(EINVAL); |
|
|
|
|
|
|
|
|
|
err = vmaf_cuda_preallocate_pictures(s->vmaf, cuda_pic_cfg); |
|
|
|
|
if (err < 0) |
|
|
|
|
return err; |
|
|
|
|
|
|
|
|
|
err = parse_deprecated_options(ctx); |
|
|
|
|
if (err) |
|
|
|
|
return err; |
|
|
|
|
|
|
|
|
|
err = parse_models(ctx); |
|
|
|
|
if (err) |
|
|
|
|
return err; |
|
|
|
|
|
|
|
|
|
err = parse_features(ctx); |
|
|
|
|
if (err) |
|
|
|
|
return err; |
|
|
|
|
|
|
|
|
|
return config_output(outlink); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int copy_picture_data_cuda(VmafContext* vmaf, |
|
|
|
|
AVCUDADeviceContext* device_hwctx, |
|
|
|
|
AVFrame* src, VmafPicture* dst, |
|
|
|
|
enum AVPixelFormat pix_fmt) |
|
|
|
|
{ |
|
|
|
|
const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(pix_fmt); |
|
|
|
|
CudaFunctions *cu = device_hwctx->internal->cuda_dl; |
|
|
|
|
|
|
|
|
|
CUDA_MEMCPY2D m = { |
|
|
|
|
.srcMemoryType = CU_MEMORYTYPE_DEVICE, |
|
|
|
|
.dstMemoryType = CU_MEMORYTYPE_DEVICE, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
int err = vmaf_cuda_fetch_preallocated_picture(vmaf, dst); |
|
|
|
|
if (err) |
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
|
|
|
|
|
err = cu->cuCtxPushCurrent(device_hwctx->cuda_ctx); |
|
|
|
|
if (err) |
|
|
|
|
return AVERROR_EXTERNAL; |
|
|
|
|
|
|
|
|
|
for (unsigned i = 0; i < pix_desc->nb_components; i++) { |
|
|
|
|
m.srcDevice = (CUdeviceptr) src->data[i]; |
|
|
|
|
m.srcPitch = src->linesize[i]; |
|
|
|
|
m.dstDevice = (CUdeviceptr) dst->data[i]; |
|
|
|
|
m.dstPitch = dst->stride[i]; |
|
|
|
|
m.WidthInBytes = dst->w[i] * ((dst->bpc + 7) / 8); |
|
|
|
|
m.Height = dst->h[i]; |
|
|
|
|
|
|
|
|
|
err = cu->cuMemcpy2D(&m); |
|
|
|
|
if (err) |
|
|
|
|
return AVERROR_EXTERNAL; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err = cu->cuCtxPopCurrent(NULL); |
|
|
|
|
if (err) |
|
|
|
|
return AVERROR_EXTERNAL; |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int do_vmaf_cuda(FFFrameSync* fs) |
|
|
|
|
{ |
|
|
|
|
AVFilterContext* ctx = fs->parent; |
|
|
|
|
LIBVMAFContext* s = ctx->priv; |
|
|
|
|
AVFilterLink *inlink = ctx->inputs[0]; |
|
|
|
|
AVHWFramesContext *frames_ctx = (AVHWFramesContext*) inlink->hw_frames_ctx->data; |
|
|
|
|
AVCUDADeviceContext *device_hwctx = frames_ctx->device_ctx->hwctx; |
|
|
|
|
VmafPicture pic_ref, pic_dist; |
|
|
|
|
AVFrame *ref, *dist; |
|
|
|
|
|
|
|
|
|
int err = 0; |
|
|
|
|
|
|
|
|
|
err = ff_framesync_dualinput_get(fs, &dist, &ref); |
|
|
|
|
if (err < 0) |
|
|
|
|
return err; |
|
|
|
|
if (ctx->is_disabled || !ref) |
|
|
|
|
return ff_filter_frame(ctx->outputs[0], dist); |
|
|
|
|
|
|
|
|
|
err = copy_picture_data_cuda(s->vmaf, device_hwctx, ref, &pic_ref, |
|
|
|
|
frames_ctx->sw_format); |
|
|
|
|
if (err) { |
|
|
|
|
av_log(s, AV_LOG_ERROR, "problem during copy_picture_data_cuda.\n"); |
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err = copy_picture_data_cuda(s->vmaf, device_hwctx, dist, &pic_dist, |
|
|
|
|
frames_ctx->sw_format); |
|
|
|
|
if (err) { |
|
|
|
|
av_log(s, AV_LOG_ERROR, "problem during copy_picture_data_cuda.\n"); |
|
|
|
|
return AVERROR(ENOMEM); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err = vmaf_read_pictures(s->vmaf, &pic_ref, &pic_dist, s->frame_cnt++); |
|
|
|
|
if (err) { |
|
|
|
|
av_log(s, AV_LOG_ERROR, "problem during vmaf_read_pictures.\n"); |
|
|
|
|
return AVERROR(EINVAL); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ff_filter_frame(ctx->outputs[0], dist); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static av_cold int init_cuda(AVFilterContext *ctx) |
|
|
|
|
{ |
|
|
|
|
LIBVMAFContext *s = ctx->priv; |
|
|
|
|
s->fs.on_event = do_vmaf_cuda; |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static const AVFilterPad libvmaf_outputs_cuda[] = { |
|
|
|
|
{ |
|
|
|
|
.name = "default", |
|
|
|
|
.type = AVMEDIA_TYPE_VIDEO, |
|
|
|
|
.config_props = config_props_cuda, |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const AVFilter ff_vf_libvmaf_cuda = { |
|
|
|
|
.name = "libvmaf_cuda", |
|
|
|
|
.description = NULL_IF_CONFIG_SMALL("Calculate the VMAF between two video streams."), |
|
|
|
|
.preinit = libvmaf_framesync_preinit, |
|
|
|
|
.init = init_cuda, |
|
|
|
|
.uninit = uninit, |
|
|
|
|
.activate = activate, |
|
|
|
|
.priv_size = sizeof(LIBVMAFContext), |
|
|
|
|
.priv_class = &libvmaf_class, |
|
|
|
|
FILTER_INPUTS(libvmaf_inputs), |
|
|
|
|
FILTER_OUTPUTS(libvmaf_outputs_cuda), |
|
|
|
|
FILTER_SINGLE_PIXFMT(AV_PIX_FMT_CUDA), |
|
|
|
|
.flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, |
|
|
|
|
}; |
|
|
|
|
#endif |
|
|
|
|