summaryrefslogtreecommitdiff
path: root/libavfilter/vf_yadif_cuda.c
diff options
context:
space:
mode:
authorPhilip Langdale <philipl@overt.org>2018-10-21 13:49:16 -0700
committerPhilip Langdale <philipl@overt.org>2018-11-02 11:26:30 -0700
commitd5272e94ab22bfc8f01fa3174e2c4664161ddf5a (patch)
tree00c8aecd37e3efa10dcf80ad2f3a17c870d96d54 /libavfilter/vf_yadif_cuda.c
parent598f0f39271d6033588b4d8ccc672c5bdc85fec7 (diff)
avfilter/vf_yadif_cuda: CUDA accelerated yadif deinterlacer
This is a cuda implementation of yadif, which gives us a way to do deinterlacing when using the nvdec hwaccel. In that scenario we don't have access to the nvidia deinterlacer.
Diffstat (limited to 'libavfilter/vf_yadif_cuda.c')
-rw-r--r--libavfilter/vf_yadif_cuda.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/libavfilter/vf_yadif_cuda.c b/libavfilter/vf_yadif_cuda.c
new file mode 100644
index 0000000000..be22344d9d
--- /dev/null
+++ b/libavfilter/vf_yadif_cuda.c
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2018 Philip Langdale <philipl@overt.org>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <cuda.h>
+#include "libavutil/avassert.h"
+#include "libavutil/hwcontext_cuda.h"
+#include "internal.h"
+#include "yadif.h"
+
+extern char vf_yadif_cuda_ptx[];
+
+typedef struct DeintCUDAContext {
+ YADIFContext yadif;
+
+ AVCUDADeviceContext *hwctx;
+ AVBufferRef *device_ref;
+ AVBufferRef *input_frames_ref;
+ AVHWFramesContext *input_frames;
+
+ CUcontext cu_ctx;
+ CUstream stream;
+ CUmodule cu_module;
+ CUfunction cu_func_uchar;
+ CUfunction cu_func_uchar2;
+ CUfunction cu_func_ushort;
+ CUfunction cu_func_ushort2;
+} DeintCUDAContext;
+
+#define DIV_UP(a, b) ( ((a) + (b) - 1) / (b) )
+#define ALIGN_UP(a, b) (((a) + (b) - 1) & ~((b) - 1))
+#define BLOCKX 32
+#define BLOCKY 16
+
+static int check_cu(AVFilterContext *avctx, CUresult err, const char *func)
+{
+ const char *err_name;
+ const char *err_string;
+
+ av_log(avctx, AV_LOG_TRACE, "Calling %s\n", func);
+
+ if (err == CUDA_SUCCESS)
+ return 0;
+
+ cuGetErrorName(err, &err_name);
+ cuGetErrorString(err, &err_string);
+
+ av_log(avctx, AV_LOG_ERROR, "%s failed", func);
+ if (err_name && err_string)
+ av_log(avctx, AV_LOG_ERROR, " -> %s: %s", err_name, err_string);
+ av_log(avctx, AV_LOG_ERROR, "\n");
+
+ return AVERROR_EXTERNAL;
+}
+
+#define CHECK_CU(x) check_cu(ctx, (x), #x)
+
+static CUresult call_kernel(AVFilterContext *ctx, CUfunction func,
+ CUdeviceptr prev, CUdeviceptr cur, CUdeviceptr next,
+ CUarray_format format, int channels,
+ int src_width, // Width is pixels per channel
+ int src_height, // Height is pixels per channel
+ int src_pitch, // Pitch is bytes
+ CUdeviceptr dst,
+ int dst_width, // Width is pixels per channel
+ int dst_height, // Height is pixels per channel
+ int dst_pitch, // Pitch is pixels per channel
+ int parity, int tff)
+{
+ DeintCUDAContext *s = ctx->priv;
+ CUtexObject tex_prev = 0, tex_cur = 0, tex_next = 0;
+ CUresult err;
+ int skip_spatial_check = s->yadif.mode&2;
+
+ void *args[] = { &dst, &tex_prev, &tex_cur, &tex_next,
+ &dst_width, &dst_height, &dst_pitch,
+ &src_width, &src_height, &parity, &tff,
+ &skip_spatial_check };
+
+ CUDA_TEXTURE_DESC tex_desc = {
+ .filterMode = CU_TR_FILTER_MODE_POINT,
+ .flags = CU_TRSF_READ_AS_INTEGER,
+ };
+
+ CUDA_RESOURCE_DESC res_desc = {
+ .resType = CU_RESOURCE_TYPE_PITCH2D,
+ .res.pitch2D.format = format,
+ .res.pitch2D.numChannels = channels,
+ .res.pitch2D.width = src_width,
+ .res.pitch2D.height = src_height,
+ .res.pitch2D.pitchInBytes = src_pitch,
+ };
+
+ res_desc.res.pitch2D.devPtr = (CUdeviceptr)prev;
+ err = CHECK_CU(cuTexObjectCreate(&tex_prev, &res_desc, &tex_desc, NULL));
+ if (err != CUDA_SUCCESS) {
+ goto exit;
+ }
+
+ res_desc.res.pitch2D.devPtr = (CUdeviceptr)cur;
+ err = CHECK_CU(cuTexObjectCreate(&tex_cur, &res_desc, &tex_desc, NULL));
+ if (err != CUDA_SUCCESS) {
+ goto exit;
+ }
+
+ res_desc.res.pitch2D.devPtr = (CUdeviceptr)next;
+ err = CHECK_CU(cuTexObjectCreate(&tex_next, &res_desc, &tex_desc, NULL));
+ if (err != CUDA_SUCCESS) {
+ goto exit;
+ }
+
+ err = CHECK_CU(cuLaunchKernel(func,
+ DIV_UP(dst_width, BLOCKX), DIV_UP(dst_height, BLOCKY), 1,
+ BLOCKX, BLOCKY, 1,
+ 0, s->stream, args, NULL));
+
+exit:
+ if (tex_prev)
+ CHECK_CU(cuTexObjectDestroy(tex_prev));
+ if (tex_cur)
+ CHECK_CU(cuTexObjectDestroy(tex_cur));
+ if (tex_next)
+ CHECK_CU(cuTexObjectDestroy(tex_next));
+
+ return err;
+}
+
+static void filter(AVFilterContext *ctx, AVFrame *dst,
+ int parity, int tff)
+{
+ DeintCUDAContext *s = ctx->priv;
+ YADIFContext *y = &s->yadif;
+ CUcontext dummy;
+ CUresult err;
+ int i;
+
+ err = CHECK_CU(cuCtxPushCurrent(s->cu_ctx));
+ if (err != CUDA_SUCCESS) {
+ goto exit;
+ }
+
+ for (i = 0; i < y->csp->nb_components; i++) {
+ CUfunction func;
+ CUarray_format format;
+ int pixel_size, channels;
+ const AVComponentDescriptor *comp = &y->csp->comp[i];
+
+ if (comp->plane < i) {
+ // We process planes as a whole, so don't reprocess
+ // them for additional components
+ continue;
+ }
+
+ pixel_size = (comp->depth + comp->shift) / 8;
+ channels = comp->step / pixel_size;
+ if (pixel_size > 2 || channels > 2) {
+ av_log(ctx, AV_LOG_ERROR, "Unsupported pixel format: %s\n", y->csp->name);
+ goto exit;
+ }
+ switch (pixel_size) {
+ case 1:
+ func = channels == 1 ? s->cu_func_uchar : s->cu_func_uchar2;
+ format = CU_AD_FORMAT_UNSIGNED_INT8;
+ break;
+ case 2:
+ func = channels == 1 ? s->cu_func_ushort : s->cu_func_ushort2;
+ format = CU_AD_FORMAT_UNSIGNED_INT16;
+ break;
+ default:
+ av_log(ctx, AV_LOG_ERROR, "Unsupported pixel format: %s\n", y->csp->name);
+ goto exit;
+ }
+ av_log(ctx, AV_LOG_TRACE,
+ "Deinterlacing plane %d: pixel_size: %d channels: %d\n",
+ comp->plane, pixel_size, channels);
+ call_kernel(ctx, func,
+ (CUdeviceptr)y->prev->data[i],
+ (CUdeviceptr)y->cur->data[i],
+ (CUdeviceptr)y->next->data[i],
+ format, channels,
+ AV_CEIL_RSHIFT(y->cur->width, i ? y->csp->log2_chroma_w : 0),
+ AV_CEIL_RSHIFT(y->cur->height, i ? y->csp->log2_chroma_h : 0),
+ y->cur->linesize[i],
+ (CUdeviceptr)dst->data[i],
+ AV_CEIL_RSHIFT(dst->width, i ? y->csp->log2_chroma_w : 0),
+ AV_CEIL_RSHIFT(dst->height, i ? y->csp->log2_chroma_h : 0),
+ dst->linesize[i] / comp->step,
+ parity, tff);
+ }
+
+ err = CHECK_CU(cuStreamSynchronize(s->stream));
+ if (err != CUDA_SUCCESS) {
+ goto exit;
+ }
+
+exit:
+ CHECK_CU(cuCtxPopCurrent(&dummy));
+ return;
+}
+
+static av_cold void deint_cuda_uninit(AVFilterContext *ctx)
+{
+ CUcontext dummy;
+ DeintCUDAContext *s = ctx->priv;
+ YADIFContext *y = &s->yadif;
+
+ if (s->cu_module) {
+ CHECK_CU(cuCtxPushCurrent(s->cu_ctx));
+ CHECK_CU(cuModuleUnload(s->cu_module));
+ CHECK_CU(cuCtxPopCurrent(&dummy));
+ }
+
+ av_frame_free(&y->prev);
+ av_frame_free(&y->cur);
+ av_frame_free(&y->next);
+
+ av_buffer_unref(&s->device_ref);
+ s->hwctx = NULL;
+ av_buffer_unref(&s->input_frames_ref);
+ s->input_frames = NULL;
+}
+
+static int deint_cuda_query_formats(AVFilterContext *ctx)
+{
+ enum AVPixelFormat pix_fmts[] = {
+ AV_PIX_FMT_CUDA, AV_PIX_FMT_NONE,
+ };
+ int ret;
+
+ if ((ret = ff_formats_ref(ff_make_format_list(pix_fmts),
+ &ctx->inputs[0]->out_formats)) < 0)
+ return ret;
+ if ((ret = ff_formats_ref(ff_make_format_list(pix_fmts),
+ &ctx->outputs[0]->in_formats)) < 0)
+ return ret;
+
+ return 0;
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+ AVFilterContext *ctx = inlink->dst;
+ DeintCUDAContext *s = ctx->priv;
+
+ if (!inlink->hw_frames_ctx) {
+ av_log(ctx, AV_LOG_ERROR, "A hardware frames reference is "
+ "required to associate the processing device.\n");
+ return AVERROR(EINVAL);
+ }
+
+ s->input_frames_ref = av_buffer_ref(inlink->hw_frames_ctx);
+ if (!s->input_frames_ref) {
+ av_log(ctx, AV_LOG_ERROR, "A input frames reference create "
+ "failed.\n");
+ return AVERROR(ENOMEM);
+ }
+ s->input_frames = (AVHWFramesContext*)s->input_frames_ref->data;
+
+ return 0;
+}
+
+static int config_output(AVFilterLink *link)
+{
+ AVHWFramesContext *output_frames;
+ AVFilterContext *ctx = link->src;
+ DeintCUDAContext *s = ctx->priv;
+ YADIFContext *y = &s->yadif;
+ int ret = 0;
+ CUcontext dummy;
+ CUresult err;
+
+ av_assert0(s->input_frames);
+ s->device_ref = av_buffer_ref(s->input_frames->device_ref);
+ if (!s->device_ref) {
+ av_log(ctx, AV_LOG_ERROR, "A device reference create "
+ "failed.\n");
+ return AVERROR(ENOMEM);
+ }
+ s->hwctx = ((AVHWDeviceContext*)s->device_ref->data)->hwctx;
+ s->cu_ctx = s->hwctx->cuda_ctx;
+ s->stream = s->hwctx->stream;
+
+ link->hw_frames_ctx = av_hwframe_ctx_alloc(s->device_ref);
+ if (!link->hw_frames_ctx) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to create HW frame context "
+ "for output.\n");
+ ret = AVERROR(ENOMEM);
+ goto exit;
+ }
+
+ output_frames = (AVHWFramesContext*)link->hw_frames_ctx->data;
+
+ output_frames->format = AV_PIX_FMT_CUDA;
+ output_frames->sw_format = s->input_frames->sw_format;
+ output_frames->width = ctx->inputs[0]->w;
+ output_frames->height = ctx->inputs[0]->h;
+
+ output_frames->initial_pool_size = 4;
+
+ ret = ff_filter_init_hw_frames(ctx, link, 10);
+ if (ret < 0)
+ goto exit;
+
+ ret = av_hwframe_ctx_init(link->hw_frames_ctx);
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR, "Failed to initialise CUDA frame "
+ "context for output: %d\n", ret);
+ goto exit;
+ }
+
+ link->time_base.num = ctx->inputs[0]->time_base.num;
+ link->time_base.den = ctx->inputs[0]->time_base.den * 2;
+ link->w = ctx->inputs[0]->w;
+ link->h = ctx->inputs[0]->h;
+
+ if(y->mode & 1)
+ link->frame_rate = av_mul_q(ctx->inputs[0]->frame_rate,
+ (AVRational){2, 1});
+
+ if (link->w < 3 || link->h < 3) {
+ av_log(ctx, AV_LOG_ERROR, "Video of less than 3 columns or lines is not supported\n");
+ ret = AVERROR(EINVAL);
+ goto exit;
+ }
+
+ y->csp = av_pix_fmt_desc_get(output_frames->sw_format);
+ y->filter = filter;
+
+ err = CHECK_CU(cuCtxPushCurrent(s->cu_ctx));
+ if (err != CUDA_SUCCESS) {
+ ret = AVERROR_EXTERNAL;
+ goto exit;
+ }
+
+ err = CHECK_CU(cuModuleLoadData(&s->cu_module, vf_yadif_cuda_ptx));
+ if (err != CUDA_SUCCESS) {
+ ret = AVERROR_INVALIDDATA;
+ goto exit;
+ }
+
+ err = CHECK_CU(cuModuleGetFunction(&s->cu_func_uchar, s->cu_module, "yadif_uchar"));
+ if (err != CUDA_SUCCESS) {
+ ret = AVERROR_INVALIDDATA;
+ goto exit;
+ }
+
+ err = CHECK_CU(cuModuleGetFunction(&s->cu_func_uchar2, s->cu_module, "yadif_uchar2"));
+ if (err != CUDA_SUCCESS) {
+ ret = AVERROR_INVALIDDATA;
+ goto exit;
+ }
+
+ err= CHECK_CU(cuModuleGetFunction(&s->cu_func_ushort, s->cu_module, "yadif_ushort"));
+ if (err != CUDA_SUCCESS) {
+ ret = AVERROR_INVALIDDATA;
+ goto exit;
+ }
+
+ err = CHECK_CU(cuModuleGetFunction(&s->cu_func_ushort2, s->cu_module, "yadif_ushort2"));
+ if (err != CUDA_SUCCESS) {
+ ret = AVERROR_INVALIDDATA;
+ goto exit;
+ }
+
+exit:
+ CHECK_CU(cuCtxPopCurrent(&dummy));
+
+ return ret;
+}
+
+static const AVClass yadif_cuda_class = {
+ .class_name = "yadif_cuda",
+ .item_name = av_default_item_name,
+ .option = ff_yadif_options,
+ .version = LIBAVUTIL_VERSION_INT,
+ .category = AV_CLASS_CATEGORY_FILTER,
+};
+
+static const AVFilterPad deint_cuda_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .filter_frame = ff_yadif_filter_frame,
+ .config_props = config_input,
+ },
+ { NULL }
+};
+
+static const AVFilterPad deint_cuda_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .request_frame = ff_yadif_request_frame,
+ .config_props = config_output,
+ },
+ { NULL }
+};
+
+AVFilter ff_vf_yadif_cuda = {
+ .name = "yadif_cuda",
+ .description = NULL_IF_CONFIG_SMALL("Deinterlace CUDA frames"),
+ .priv_size = sizeof(DeintCUDAContext),
+ .priv_class = &yadif_cuda_class,
+ .uninit = deint_cuda_uninit,
+ .query_formats = deint_cuda_query_formats,
+ .inputs = deint_cuda_inputs,
+ .outputs = deint_cuda_outputs,
+ .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
+ .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
+};