/* * 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 "libavutil/log.h" #include "libavutil/opt.h" #include "libavutil/pixdesc.h" #include "avfilter.h" #include "filters.h" #include "internal.h" #include "opencl.h" #include "opencl_source.h" #include "video.h" enum XFadeTransitions { CUSTOM, FADE, WIPELEFT, WIPERIGHT, WIPEUP, WIPEDOWN, SLIDELEFT, SLIDERIGHT, SLIDEUP, SLIDEDOWN, NB_TRANSITIONS, }; typedef struct XFadeOpenCLContext { OpenCLFilterContext ocf; int transition; const char *source_file; const char *kernel_name; int64_t duration; int64_t offset; int initialised; cl_kernel kernel; cl_command_queue command_queue; int nb_planes; int64_t duration_pts; int64_t offset_pts; int64_t first_pts; int64_t last_pts; int64_t pts; int xfade_is_over; int need_second; int eof[2]; AVFrame *xf[2]; } XFadeOpenCLContext; static int xfade_opencl_load(AVFilterContext *avctx, enum AVPixelFormat main_format, enum AVPixelFormat xfade_format) { XFadeOpenCLContext *ctx = avctx->priv; cl_int cle; const AVPixFmtDescriptor *main_desc; int err, main_planes; const char *kernel_name; main_desc = av_pix_fmt_desc_get(main_format); if (main_format != xfade_format) { av_log(avctx, AV_LOG_ERROR, "Input formats are not same.\n"); return AVERROR(EINVAL); } main_planes = 0; for (int i = 0; i < main_desc->nb_components; i++) main_planes = FFMAX(main_planes, main_desc->comp[i].plane + 1); ctx->nb_planes = main_planes; if (ctx->transition == CUSTOM) { err = ff_opencl_filter_load_program_from_file(avctx, ctx->source_file); } else { err = ff_opencl_filter_load_program(avctx, &ff_source_xfade_cl, 1); } if (err < 0) return err; ctx->command_queue = clCreateCommandQueue(ctx->ocf.hwctx->context, ctx->ocf.hwctx->device_id, 0, &cle); CL_FAIL_ON_ERROR(AVERROR(EIO), "Failed to create OpenCL " "command queue %d.\n", cle); switch (ctx->transition) { case CUSTOM: kernel_name = ctx->kernel_name; break; case FADE: kernel_name = "fade"; break; case WIPELEFT: kernel_name = "wipeleft"; break; case WIPERIGHT: kernel_name = "wiperight"; break; case WIPEUP: kernel_name = "wipeup"; break; case WIPEDOWN: kernel_name = "wipedown"; break; case SLIDELEFT: kernel_name = "slideleft"; break; case SLIDERIGHT: kernel_name = "slideright"; break; case SLIDEUP: kernel_name = "slideup"; break; case SLIDEDOWN: kernel_name = "slidedown"; break; default: err = AVERROR_BUG; goto fail; } ctx->kernel = clCreateKernel(ctx->ocf.program, kernel_name, &cle); CL_FAIL_ON_ERROR(AVERROR(EIO), "Failed to create kernel %d.\n", cle); ctx->initialised = 1; return 0; fail: if (ctx->command_queue) clReleaseCommandQueue(ctx->command_queue); if (ctx->kernel) clReleaseKernel(ctx->kernel); return err; } static int xfade_frame(AVFilterContext *avctx, AVFrame *a, AVFrame *b) { AVFilterLink *outlink = avctx->outputs[0]; XFadeOpenCLContext *ctx = avctx->priv; AVFrame *output; cl_int cle; cl_float progress = av_clipf(1.f - ((cl_float)(ctx->pts - ctx->first_pts - ctx->offset_pts) / ctx->duration_pts), 0.f, 1.f); size_t global_work[2]; int kernel_arg = 0; int err, plane; if (!ctx->initialised) { AVHWFramesContext *main_fc = (AVHWFramesContext*)a->hw_frames_ctx->data; AVHWFramesContext *xfade_fc = (AVHWFramesContext*)b->hw_frames_ctx->data; err = xfade_opencl_load(avctx, main_fc->sw_format, xfade_fc->sw_format); if (err < 0) return err; } output = ff_get_video_buffer(outlink, outlink->w, outlink->h); if (!output) { err = AVERROR(ENOMEM); goto fail; } for (plane = 0; plane < ctx->nb_planes; plane++) { cl_mem mem; kernel_arg = 0; mem = (cl_mem)output->data[plane]; CL_SET_KERNEL_ARG(ctx->kernel, kernel_arg, cl_mem, &mem); kernel_arg++; mem = (cl_mem)ctx->xf[0]->data[plane]; CL_SET_KERNEL_ARG(ctx->kernel, kernel_arg, cl_mem, &mem); kernel_arg++; mem = (cl_mem)ctx->xf[1]->data[plane]; CL_SET_KERNEL_ARG(ctx->kernel, kernel_arg, cl_mem, &mem); kernel_arg++; CL_SET_KERNEL_ARG(ctx->kernel, kernel_arg, cl_float, &progress); kernel_arg++; err = ff_opencl_filter_work_size_from_image(avctx, global_work, output, plane, 0); if (err < 0) goto fail; cle = clEnqueueNDRangeKernel(ctx->command_queue, ctx->kernel, 2, NULL, global_work, NULL, 0, NULL, NULL); CL_FAIL_ON_ERROR(AVERROR(EIO), "Failed to enqueue xfade kernel " "for plane %d: %d.\n", plane, cle); } cle = clFinish(ctx->command_queue); CL_FAIL_ON_ERROR(AVERROR(EIO), "Failed to finish command queue: %d.\n", cle); err = av_frame_copy_props(output, ctx->xf[0]); if (err < 0) goto fail; output->pts = ctx->pts; return ff_filter_frame(outlink, output); fail: av_frame_free(&output); return err; } static int xfade_opencl_config_output(AVFilterLink *outlink) { AVFilterContext *avctx = outlink->src; XFadeOpenCLContext *ctx = avctx->priv; AVFilterLink *inlink0 = avctx->inputs[0]; AVFilterLink *inlink1 = avctx->inputs[1]; int err; err = ff_opencl_filter_config_output(outlink); if (err < 0) return err; if (inlink0->w != inlink1->w || inlink0->h != inlink1->h) { av_log(avctx, AV_LOG_ERROR, "First input link %s parameters " "(size %dx%d) do not match the corresponding " "second input link %s parameters (size %dx%d)\n", avctx->input_pads[0].name, inlink0->w, inlink0->h, avctx->input_pads[1].name, inlink1->w, inlink1->h); return AVERROR(EINVAL); } if (inlink0->time_base.num != inlink1->time_base.num || inlink0->time_base.den != inlink1->time_base.den) { av_log(avctx, AV_LOG_ERROR, "First input link %s timebase " "(%d/%d) do not match the corresponding " "second input link %s timebase (%d/%d)\n", avctx->input_pads[0].name, inlink0->time_base.num, inlink0->time_base.den, avctx->input_pads[1].name, inlink1->time_base.num, inlink1->time_base.den); return AVERROR(EINVAL); } ctx->first_pts = ctx->last_pts = ctx->pts = AV_NOPTS_VALUE; outlink->time_base = inlink0->time_base; outlink->sample_aspect_ratio = inlink0->sample_aspect_ratio; outlink->frame_rate = inlink0->frame_rate; if (ctx->duration) ctx->duration_pts = av_rescale_q(ctx->duration, AV_TIME_BASE_Q, outlink->time_base); if (ctx->offset) ctx->offset_pts = av_rescale_q(ctx->offset, AV_TIME_BASE_Q, outlink->time_base); return 0; } static int xfade_opencl_activate(AVFilterContext *avctx) { XFadeOpenCLContext *ctx = avctx->priv; AVFilterLink *outlink = avctx->outputs[0]; AVFrame *in = NULL; int ret = 0, status; int64_t pts; FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, avctx); if (ctx->xfade_is_over) { ret = ff_inlink_consume_frame(avctx->inputs[1], &in); if (ret < 0) { return ret; } else if (ret > 0) { in->pts = (in->pts - ctx->last_pts) + ctx->pts; return ff_filter_frame(outlink, in); } else if (ff_inlink_acknowledge_status(avctx->inputs[1], &status, &pts)) { ff_outlink_set_status(outlink, status, ctx->pts); return 0; } else if (!ret) { if (ff_outlink_frame_wanted(outlink)) { ff_inlink_request_frame(avctx->inputs[1]); return 0; } } } if (ff_inlink_queued_frames(avctx->inputs[0]) > 0) { ctx->xf[0] = ff_inlink_peek_frame(avctx->inputs[0], 0); if (ctx->xf[0]) { if (ctx->first_pts == AV_NOPTS_VALUE) { ctx->first_pts = ctx->xf[0]->pts; } ctx->pts = ctx->xf[0]->pts; if (ctx->first_pts + ctx->offset_pts > ctx->xf[0]->pts) { ctx->xf[0] = NULL; ctx->need_second = 0; ff_inlink_consume_frame(avctx->inputs[0], &in); return ff_filter_frame(outlink, in); } ctx->need_second = 1; } } if (ctx->xf[0] && ff_inlink_queued_frames(avctx->inputs[1]) > 0) { ff_inlink_consume_frame(avctx->inputs[0], &ctx->xf[0]); ff_inlink_consume_frame(avctx->inputs[1], &ctx->xf[1]); ctx->last_pts = ctx->xf[1]->pts; ctx->pts = ctx->xf[0]->pts; if (ctx->xf[0]->pts - (ctx->first_pts + ctx->offset_pts) > ctx->duration_pts) ctx->xfade_is_over = 1; ret = xfade_frame(avctx, ctx->xf[0], ctx->xf[1]); av_frame_free(&ctx->xf[0]); av_frame_free(&ctx->xf[1]); return ret; } if (ff_inlink_queued_frames(avctx->inputs[0]) > 0 && ff_inlink_queued_frames(avctx->inputs[1]) > 0) { ff_filter_set_ready(avctx, 100); return 0; } if (ff_outlink_frame_wanted(outlink)) { if (!ctx->eof[0] && ff_outlink_get_status(avctx->inputs[0])) { ctx->eof[0] = 1; ctx->xfade_is_over = 1; } if (!ctx->eof[1] && ff_outlink_get_status(avctx->inputs[1])) { ctx->eof[1] = 1; } if (!ctx->eof[0] && !ctx->xf[0]) ff_inlink_request_frame(avctx->inputs[0]); if (!ctx->eof[1] && (ctx->need_second || ctx->eof[0])) ff_inlink_request_frame(avctx->inputs[1]); if (ctx->eof[0] && ctx->eof[1] && ( ff_inlink_queued_frames(avctx->inputs[0]) <= 0 || ff_inlink_queued_frames(avctx->inputs[1]) <= 0)) ff_outlink_set_status(outlink, AVERROR_EOF, AV_NOPTS_VALUE); return 0; } return FFERROR_NOT_READY; } static av_cold void xfade_opencl_uninit(AVFilterContext *avctx) { XFadeOpenCLContext *ctx = avctx->priv; cl_int cle; if (ctx->kernel) { cle = clReleaseKernel(ctx->kernel); if (cle != CL_SUCCESS) av_log(avctx, AV_LOG_ERROR, "Failed to release " "kernel: %d.\n", cle); } if (ctx->command_queue) { cle = clReleaseCommandQueue(ctx->command_queue); if (cle != CL_SUCCESS) av_log(avctx, AV_LOG_ERROR, "Failed to release " "command queue: %d.\n", cle); } ff_opencl_filter_uninit(avctx); } static AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h) { XFadeOpenCLContext *s = inlink->dst->priv; return s->xfade_is_over || !s->need_second ? ff_null_get_video_buffer (inlink, w, h) : ff_default_get_video_buffer(inlink, w, h); } #define OFFSET(x) offsetof(XFadeOpenCLContext, x) #define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM) static const AVOption xfade_opencl_options[] = { { "transition", "set cross fade transition", OFFSET(transition), AV_OPT_TYPE_INT, {.i64=1}, 0, NB_TRANSITIONS-1, FLAGS, .unit = "transition" }, { "custom", "custom transition", 0, AV_OPT_TYPE_CONST, {.i64=CUSTOM}, 0, 0, FLAGS, .unit = "transition" }, { "fade", "fade transition", 0, AV_OPT_TYPE_CONST, {.i64=FADE}, 0, 0, FLAGS, .unit = "transition" }, { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, .unit = "transition" }, { "wiperight", "wipe right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPERIGHT}, 0, 0, FLAGS, .unit = "transition" }, { "wipeup", "wipe up transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEUP}, 0, 0, FLAGS, .unit = "transition" }, { "wipedown", "wipe down transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEDOWN}, 0, 0, FLAGS, .unit = "transition" }, { "slideleft", "slide left transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDELEFT}, 0, 0, FLAGS, .unit = "transition" }, { "slideright", "slide right transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDERIGHT}, 0, 0, FLAGS, .unit = "transition" }, { "slideup", "slide up transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEUP}, 0, 0, FLAGS, .unit = "transition" }, { "slidedown", "slide down transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEDOWN}, 0, 0, FLAGS, .unit = "transition" }, { "source", "set OpenCL program source file for custom transition", OFFSET(source_file), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = FLAGS }, { "kernel", "set kernel name in program file for custom transition", OFFSET(kernel_name), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = FLAGS }, { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS }, { "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS }, { NULL } }; AVFILTER_DEFINE_CLASS(xfade_opencl); static const AVFilterPad xfade_opencl_inputs[] = { { .name = "main", .type = AVMEDIA_TYPE_VIDEO, .get_buffer.video = get_video_buffer, .config_props = &ff_opencl_filter_config_input, }, { .name = "xfade", .type = AVMEDIA_TYPE_VIDEO, .get_buffer.video = get_video_buffer, .config_props = &ff_opencl_filter_config_input, }, }; static const AVFilterPad xfade_opencl_outputs[] = { { .name = "default", .type = AVMEDIA_TYPE_VIDEO, .config_props = &xfade_opencl_config_output, }, }; const AVFilter ff_vf_xfade_opencl = { .name = "xfade_opencl", .description = NULL_IF_CONFIG_SMALL("Cross fade one video with another video."), .priv_size = sizeof(XFadeOpenCLContext), .priv_class = &xfade_opencl_class, .init = &ff_opencl_filter_init, .uninit = &xfade_opencl_uninit, .activate = &xfade_opencl_activate, FILTER_INPUTS(xfade_opencl_inputs), FILTER_OUTPUTS(xfade_opencl_outputs), FILTER_SINGLE_PIXFMT(AV_PIX_FMT_OPENCL), .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, .flags = AVFILTER_FLAG_HWDEVICE, };