| /* |
| * 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/mem.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_opencl_source_xfade, 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, "transition" }, |
| { "custom", "custom transition", 0, AV_OPT_TYPE_CONST, {.i64=CUSTOM}, 0, 0, FLAGS, "transition" }, |
| { "fade", "fade transition", 0, AV_OPT_TYPE_CONST, {.i64=FADE}, 0, 0, FLAGS, "transition" }, |
| { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, "transition" }, |
| { "wiperight", "wipe right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPERIGHT}, 0, 0, FLAGS, "transition" }, |
| { "wipeup", "wipe up transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEUP}, 0, 0, FLAGS, "transition" }, |
| { "wipedown", "wipe down transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEDOWN}, 0, 0, FLAGS, "transition" }, |
| { "slideleft", "slide left transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDELEFT}, 0, 0, FLAGS, "transition" }, |
| { "slideright", "slide right transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDERIGHT}, 0, 0, FLAGS, "transition" }, |
| { "slideup", "slide up transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEUP}, 0, 0, FLAGS, "transition" }, |
| { "slidedown", "slide down transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEDOWN}, 0, 0, FLAGS, "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_video_buffer = get_video_buffer, |
| .config_props = &ff_opencl_filter_config_input, |
| }, |
| { |
| .name = "xfade", |
| .type = AVMEDIA_TYPE_VIDEO, |
| .get_video_buffer = get_video_buffer, |
| .config_props = &ff_opencl_filter_config_input, |
| }, |
| { NULL } |
| }; |
| |
| static const AVFilterPad xfade_opencl_outputs[] = { |
| { |
| .name = "default", |
| .type = AVMEDIA_TYPE_VIDEO, |
| .config_props = &xfade_opencl_config_output, |
| }, |
| { NULL } |
| }; |
| |
| 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, |
| .query_formats = &ff_opencl_filter_query_formats, |
| .activate = &xfade_opencl_activate, |
| .inputs = xfade_opencl_inputs, |
| .outputs = xfade_opencl_outputs, |
| .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, |
| }; |