diff options
Diffstat (limited to 'filters/f_lavfi.c')
-rw-r--r-- | filters/f_lavfi.c | 952 |
1 files changed, 952 insertions, 0 deletions
diff --git a/filters/f_lavfi.c b/filters/f_lavfi.c new file mode 100644 index 0000000000..a97f126efb --- /dev/null +++ b/filters/f_lavfi.c @@ -0,0 +1,952 @@ +/* + * This file is part of mpv. + * + * mpv 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. + * + * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <inttypes.h> +#include <stdarg.h> +#include <assert.h> + +#include <libavutil/avstring.h> +#include <libavutil/mem.h> +#include <libavutil/mathematics.h> +#include <libavutil/rational.h> +#include <libavutil/error.h> +#include <libavutil/opt.h> +#include <libavfilter/avfilter.h> +#include <libavfilter/buffersink.h> +#include <libavfilter/buffersrc.h> + +#include "common/common.h" +#include "common/av_common.h" +#include "common/tags.h" +#include "common/msg.h" + +#include "audio/format.h" +#include "audio/aframe.h" +#include "video/mp_image.h" +#include "audio/fmt-conversion.h" +#include "video/fmt-conversion.h" +#include "video/hwdec.h" + +#include "f_lavfi.h" +#include "filter.h" +#include "filter_internal.h" +#include "user_filters.h" + +#if LIBAVFILTER_VERSION_MICRO < 100 +#define av_buffersink_get_frame_flags(a, b, c) av_buffersink_get_frame(a, b) +#define AV_BUFFERSINK_FLAG_NO_REQUEST 0 +#endif + +struct lavfi { + struct mp_log *log; + struct mp_filter *f; + + char *graph_string; + char **graph_opts; + bool force_bidir; + enum mp_frame_type force_type; + bool direct_filter; + char **direct_filter_opts; + + AVFilterGraph *graph; + // Set to true once all inputs have been initialized, and the graph is + // linked. + bool initialized; + + // Graph is draining to either handle format changes (if input format + // changes for one pad, recreate the graph after draining all buffered + // frames), or undo previously sent EOF (libavfilter does not accept + // input anymore after sending EOF, so recreate the graph to "unstuck" it). + bool draining_recover; + + // Filter can't be put into a working state. + bool failed; + + struct lavfi_pad **in_pads; + int num_in_pads; + + struct lavfi_pad **out_pads; + int num_out_pads; + + struct lavfi_pad **all_pads; + int num_all_pads; + + AVFrame *tmp_frame; + + struct mp_lavfi public; +}; + +struct lavfi_pad { + struct lavfi *main; + enum mp_frame_type type; + enum mp_pin_dir dir; + char *name; // user-given pad name + + struct mp_pin *pin; // internal pin for this (never NULL once initialized) + int pin_index; + + AVFilterContext *filter; + int filter_pad; + // buffersrc or buffersink connected to filter/filter_pad + AVFilterContext *buffer; + AVRational timebase; + bool buffer_is_eof; // received/sent EOF to the buffer + + struct mp_tags *metadata; + + // 1-frame queue for input. + struct mp_frame pending; + + // Used to check input format changes. + struct mp_frame in_fmt; +}; + +// Free the libavfilter graph (not c), reset all state. +// Does not free pending data intentionally. +static void free_graph(struct lavfi *c) +{ + avfilter_graph_free(&c->graph); + for (int n = 0; n < c->num_all_pads; n++) { + struct lavfi_pad *pad = c->all_pads[n]; + + pad->filter = NULL; + pad->filter_pad = -1; + pad->buffer = NULL; + mp_frame_unref(&pad->in_fmt); + pad->buffer_is_eof = false; + } + c->initialized = false; + c->draining_recover = false; +} + +static void add_pad(struct lavfi *c, int dir, int index, AVFilterContext *filter, + int filter_pad, const char *name, bool first_init) +{ + if (c->failed) + return; + + enum AVMediaType avmt; + if (dir == MP_PIN_IN) { + avmt = avfilter_pad_get_type(filter->input_pads, filter_pad); + } else { + avmt = avfilter_pad_get_type(filter->output_pads, filter_pad); + } + int type; + switch (avmt) { + case AVMEDIA_TYPE_VIDEO: type = MP_FRAME_VIDEO; break; + case AVMEDIA_TYPE_AUDIO: type = MP_FRAME_AUDIO; break; + default: + MP_FATAL(c, "unknown media type\n"); + c->failed = true; + return; + } + + // For anonymous pads, just make something up. libavfilter allows duplicate + // pad names (while we don't), so we check for collisions along with normal + // duplicate pads below. + char tmp[80]; + const char *dir_string = dir == MP_PIN_IN ? "in" : "out"; + if (name) { + if (c->direct_filter) { + // libavfilter has this very unpleasant thing that filter labels + // don't have to be unique - in particular, both input and output + // are usually named "default". With direct filters, the user has + // no chance to provide better names, so do something to resolve it. + snprintf(tmp, sizeof(tmp), "%s_%s", name, dir_string); + name = tmp; + } + } else { + snprintf(tmp, sizeof(tmp), "%s%d", dir_string, index); + name = tmp; + } + + struct lavfi_pad *p = NULL; + for (int n = 0; n < c->num_all_pads; n++) { + if (strcmp(c->all_pads[n]->name, name) == 0) { + p = c->all_pads[n]; + break; + } + } + + if (p) { + // Graph recreation case: reassociate an existing pad. + if (p->filter) { + // Collision due to duplicate names. + MP_FATAL(c, "more than one pad with label '%s'\n", name); + c->failed = true; + return; + } + if (p->dir != dir || p->type != type) { + // libavfilter graph parser behavior not deterministic. + MP_FATAL(c, "pad '%s' changed type or direction\n", name); + c->failed = true; + return; + } + } else { + if (!first_init) { + MP_FATAL(c, "filter pad '%s' got added later?\n", name); + c->failed = true; + return; + } + p = talloc_zero(c, struct lavfi_pad); + p->main = c; + p->dir = dir; + p->name = talloc_strdup(p, name); + p->type = type; + p->pin_index = -1; + p->metadata = talloc_zero(p, struct mp_tags); + if (p->dir == MP_PIN_IN) + MP_TARRAY_APPEND(c, c->in_pads, c->num_in_pads, p); + if (p->dir == MP_PIN_OUT) + MP_TARRAY_APPEND(c, c->out_pads, c->num_out_pads, p); + MP_TARRAY_APPEND(c, c->all_pads, c->num_all_pads, p); + } + p->filter = filter; + p->filter_pad = filter_pad; +} + +static void add_pads(struct lavfi *c, int dir, AVFilterInOut *l, bool first_init) +{ + int index = 0; + for (; l; l = l->next) + add_pad(c, dir, index++, l->filter_ctx, l->pad_idx, l->name, first_init); +} + +static void add_pads_direct(struct lavfi *c, int dir, AVFilterContext *f, + AVFilterPad *pads, int num_pads, bool first_init) +{ + for (int n = 0; n < num_pads; n++) + add_pad(c, dir, n, f, n, avfilter_pad_get_name(pads, n), first_init); +} + +// Parse the user-provided filter graph, and populate the unlinked filter pads. +static void precreate_graph(struct lavfi *c, bool first_init) +{ + assert(!c->graph); + + c->failed = false; + + c->graph = avfilter_graph_alloc(); + if (!c->graph) + abort(); + + if (mp_set_avopts(c->log, c->graph, c->graph_opts) < 0) + goto error; + + if (c->direct_filter) { + AVFilterContext *filter = avfilter_graph_alloc_filter(c->graph, + avfilter_get_by_name(c->graph_string), "filter"); + if (!filter) { + MP_FATAL(c, "filter '%s' not found or failed to allocate\n", + c->graph_string); + goto error; + } + + if (mp_set_avopts(c->log, filter->priv, c->direct_filter_opts) < 0) + goto error; + + if (avfilter_init_str(filter, NULL) < 0) { + MP_FATAL(c, "filter failed to initialize\n"); + goto error; + } + + add_pads_direct(c, MP_PIN_IN, filter, filter->input_pads, + filter->nb_inputs, first_init); + add_pads_direct(c, MP_PIN_OUT, filter, filter->output_pads, + filter->nb_outputs, first_init); + } else { + AVFilterInOut *in = NULL, *out = NULL; + if (avfilter_graph_parse2(c->graph, c->graph_string, &in, &out) < 0) { + MP_FATAL(c, "parsing the filter graph failed\n"); + goto error; + } + add_pads(c, MP_PIN_IN, in, first_init); + add_pads(c, MP_PIN_OUT, out, first_init); + avfilter_inout_free(&in); + avfilter_inout_free(&out); + } + + for (int n = 0; n < c->num_all_pads; n++) + c->failed |= !c->all_pads[n]->filter; + + if (c->failed) + goto error; + + return; + +error: + free_graph(c); + c->failed = true; + mp_filter_internal_mark_failed(c->f); + return; +} + +// Ensure to send EOF to each input pad, so the graph can be drained properly. +static void send_global_eof(struct lavfi *c) +{ + for (int n = 0; n < c->num_in_pads; n++) { + struct lavfi_pad *pad = c->in_pads[n]; + if (!pad->buffer || pad->buffer_is_eof) + continue; + + if (av_buffersrc_add_frame(pad->buffer, NULL) < 0) + MP_FATAL(c, "could not send EOF to filter\n"); + + pad->buffer_is_eof = true; + } +} + +// libavfilter allows changing some parameters on the fly, but not +// others. +static bool is_aformat_ok(struct mp_aframe *a, struct mp_aframe *b) +{ + struct mp_chmap ca = {0}, cb = {0}; + mp_aframe_get_chmap(a, &ca); + mp_aframe_get_chmap(b, &cb); + return mp_chmap_equals(&ca, &cb) && + mp_aframe_get_rate(a) == mp_aframe_get_rate(b) && + mp_aframe_get_format(a) == mp_aframe_get_format(b); +} +static bool is_vformat_ok(struct mp_image *a, struct mp_image *b) +{ + return a->imgfmt == b->imgfmt && + a->w == b->w && a->h && b->h && + a->params.p_w == b->params.p_w && a->params.p_h == b->params.p_h; +} +static bool is_format_ok(struct mp_frame a, struct mp_frame b) +{ + if (a.type == b.type && a.type == MP_FRAME_VIDEO) + return is_vformat_ok(a.data, b.data); + if (a.type == b.type && a.type == MP_FRAME_AUDIO) + return is_aformat_ok(a.data, b.data); + return false; +} + +static void read_pad_input(struct lavfi *c, struct lavfi_pad *pad) +{ + assert(pad->dir == MP_PIN_IN); + + if (pad->pending.type || c->draining_recover) + return; + + pad->pending = mp_pin_out_read(pad->pin); + + if (pad->pending.type && pad->pending.type != MP_FRAME_EOF && + pad->pending.type != pad->type) + { + MP_FATAL(c, "unknown frame %s\n", mp_frame_type_str(pad->pending.type)); + mp_frame_unref(&pad->pending); + } + + if (mp_frame_is_data(pad->pending) && pad->in_fmt.type && + !is_format_ok(pad->pending, pad->in_fmt)) + { + if (!c->draining_recover) + MP_VERBOSE(c, "format change on %s\n", pad->name); + c->draining_recover = true; + if (c->initialized) + send_global_eof(c); + } +} + +// Attempt to initialize all pads. Return true if all are initialized, or +// false if more data is needed (or on error). +static bool init_pads(struct lavfi *c) +{ + if (!c->graph) + goto error; + + for (int n = 0; n < c->num_out_pads; n++) { + struct lavfi_pad *pad = c->out_pads[n]; + if (pad->buffer) + continue; + + const AVFilter *dst_filter = NULL; + if (pad->type == MP_FRAME_AUDIO) { + dst_filter = avfilter_get_by_name("abuffersink"); + } else if (pad->type == MP_FRAME_VIDEO) { + dst_filter = avfilter_get_by_name("buffersink"); + } else { + assert(0); + } + + if (!dst_filter) + goto error; + + char name[256]; + snprintf(name, sizeof(name), "mpv_sink_%s", pad->name); + + if (avfilter_graph_create_filter(&pad->buffer, dst_filter, + name, NULL, NULL, c->graph) < 0) + goto error; + + if (avfilter_link(pad->filter, pad->filter_pad, pad->buffer, 0) < 0) + goto error; + } + + for (int n = 0; n < c->num_in_pads; n++) { + struct lavfi_pad *pad = c->in_pads[n]; + if (pad->buffer) + continue; + + mp_frame_unref(&pad->in_fmt); + + read_pad_input(c, pad); + // no input data, format unknown, can't init, wait longer. + if (!pad->pending.type) + return false; + + if (mp_frame_is_data(pad->pending)) { + assert(pad->pending.type == pad->type); + + pad->in_fmt = mp_frame_ref(pad->pending); + if (!pad->in_fmt.type) + goto error; + + if (pad->in_fmt.type == MP_FRAME_VIDEO) + mp_image_unref_data(pad->in_fmt.data); + if (pad->in_fmt.type == MP_FRAME_AUDIO) + mp_aframe_unref_data(pad->in_fmt.data); + } + + if (pad->pending.type == MP_FRAME_EOF && !pad->in_fmt.type) { + // libavfilter makes this painful. Init it with a dummy config, + // just so we can tell it the stream is EOF. + if (pad->type == MP_FRAME_AUDIO) { + struct mp_aframe *fmt = mp_aframe_create(); + mp_aframe_set_format(fmt, AF_FORMAT_FLOAT); + mp_aframe_set_chmap(fmt, &(struct mp_chmap)MP_CHMAP_INIT_STEREO); + mp_aframe_set_rate(fmt, 48000); + pad->in_fmt = (struct mp_frame){MP_FRAME_AUDIO, fmt}; + } + if (pad->type == MP_FRAME_VIDEO) { + struct mp_image *fmt = talloc_zero(NULL, struct mp_image); + mp_image_setfmt(fmt, IMGFMT_420P); + mp_image_set_size(fmt, 64, 64); + pad->in_fmt = (struct mp_frame){MP_FRAME_VIDEO, fmt}; + } + } + + if (pad->in_fmt.type != pad->type) + goto error; + + AVBufferSrcParameters *params = av_buffersrc_parameters_alloc(); + if (!params) + goto error; + + pad->timebase = AV_TIME_BASE_Q; + + char *filter_name = NULL; + if (pad->type == MP_FRAME_AUDIO) { + struct mp_aframe *fmt = pad->in_fmt.data; + params->format = af_to_avformat(mp_aframe_get_format(fmt)); + params->sample_rate = mp_aframe_get_rate(fmt); + struct mp_chmap chmap = {0}; + mp_aframe_get_chmap(fmt, &chmap); + params->channel_layout = mp_chmap_to_lavc(&chmap); + pad->timebase = (AVRational){1, mp_aframe_get_rate(fmt)}; + filter_name = "abuffer"; + } else if (pad->type == MP_FRAME_VIDEO) { + struct mp_image *fmt = pad->in_fmt.data; + params->format = imgfmt2pixfmt(fmt->imgfmt); + params->width = fmt->w; + params->height = fmt->h; + params->sample_aspect_ratio.num = fmt->params.p_w; + params->sample_aspect_ratio.den = fmt->params.p_h; + params->hw_frames_ctx = fmt->hwctx; + filter_name = "buffer"; + } else { + assert(0); + } + + params->time_base = pad->timebase; + + const AVFilter *filter = avfilter_get_by_name(filter_name); + if (filter) { + char name[256]; + snprintf(name, sizeof(name), "mpv_src_%s", pad->name); + + pad->buffer = avfilter_graph_alloc_filter(c->graph, filter, name); + } + if (!pad->buffer) { + av_free(params); + goto error; + } + + int ret = av_buffersrc_parameters_set(pad->buffer, params); + av_free(params); + if (ret < 0) + goto error; + + if (avfilter_init_str(pad->buffer, NULL) < 0) + goto error; + + if (avfilter_link(pad->buffer, 0, pad->filter, pad->filter_pad) < 0) + goto error; + } + + return true; +error: + MP_FATAL(c, "could not initialize filter pads\n"); + c->failed = true; + mp_filter_internal_mark_failed(c->f); + return false; +} + +static void dump_graph(struct lavfi *c) +{ +#if LIBAVFILTER_VERSION_MICRO >= 100 + MP_DBG(c, "Filter graph:\n"); + char *s = avfilter_graph_dump(c->graph, NULL); + if (s) + MP_DBG(c, "%s\n", s); + av_free(s); +#endif +} + +// Initialize the graph if all inputs have formats set. If it's already +// initialized, or can't be initialized yet, do nothing. +static void init_graph(struct lavfi *c) +{ + assert(!c->initialized); + + if (!c->graph) + precreate_graph(c, false); + + if (init_pads(c)) { + struct mp_stream_info *info = mp_filter_find_stream_info(c->f); + if (info && info->hwdec_devs) { + struct mp_hwdec_ctx *hwdec = hwdec_devices_get_first(info->hwdec_devs); + for (int n = 0; n < c->graph->nb_filters; n++) { + AVFilterContext *filter = c->graph->filters[n]; + if (hwdec && hwdec->av_device_ref) + filter->hw_device_ctx = av_buffer_ref(hwdec->av_device_ref); + } + } + + // And here the actual libavfilter initialization happens. + if (avfilter_graph_config(c->graph, NULL) < 0) { + MP_FATAL(c, "failed to configure the filter graph\n"); + free_graph(c); + c->failed = true; + mp_filter_internal_mark_failed(c->f); + return; + } + + // The timebase is available after configuring. + for (int n = 0; n < c->num_out_pads; n++) { + struct lavfi_pad *pad = c->out_pads[n]; + + pad->timebase = pad->buffer->inputs[0]->time_base; + } + + c->initialized = true; + + if (!c->direct_filter) // (output uninteresting for direct filters) + dump_graph(c); + } +} + +static bool feed_input_pads(struct lavfi *c) +{ + bool progress = false; + bool was_draining = c->draining_recover; + + assert(c->initialized); + + for (int n = 0; n < c->num_in_pads; n++) { + struct lavfi_pad *pad = c->in_pads[n]; + + bool requested = true; +#if LIBAVFILTER_VERSION_MICRO >= 100 + requested = av_buffersrc_get_nb_failed_requests(pad->buffer) > 0; +#endif + + // Always request a frame after EOF so that we can know if the EOF state + // changes (e.g. for sparse streams with midstream EOF). + requested |= pad->buffer_is_eof; + + if (requested) + read_pad_input(c, pad); + + if (!pad->pending.type || c->draining_recover) + continue; + + if (pad->buffer_is_eof) { + MP_WARN(c, "eof state changed on %s\n", pad->name); + c->draining_recover = true; + send_global_eof(c); + continue; + } + + AVFrame *frame = mp_frame_to_av(pad->pending, &pad->timebase); + bool eof = pad->pending.type == MP_FRAME_EOF; + + mp_frame_unref(&pad->pending); + + if (!frame && !eof) { + MP_FATAL(c, "out of memory or unsupported format\n"); + continue; + } + + pad->buffer_is_eof = !frame; + + if (av_buffersrc_add_frame(pad->buffer, frame) < 0) + MP_FATAL(c, "could not pass frame to filter\n"); + av_frame_free(&frame); + + progress = true; + } + + if (!was_draining && c->draining_recover) + progress = true; + + return progress; +} + +static bool read_output_pads(struct lavfi *c) +{ + bool progress = false; + + assert(c->initialized); + + for (int n = 0; n < c->num_out_pads; n++) { + struct lavfi_pad *pad = c->out_pads[n]; + + if (!mp_pin_in_needs_data(pad->pin)) + continue; + + assert(pad->buffer); + + int r = AVERROR_EOF; + if (!pad->buffer_is_eof) + r = av_buffersink_get_frame_flags(pad->buffer, c->tmp_frame, 0); + if (r >= 0) { +#if LIBAVUTIL_VERSION_MICRO >= 100 + mp_tags_copy_from_av_dictionary(pad->metadata, c->tmp_frame->metadata); +#endif + struct mp_frame frame = + mp_frame_from_av(pad->type, c->tmp_frame, &pad->timebase); + av_frame_unref(c->tmp_frame); + if (frame.type) { + mp_pin_in_write(pad->pin, frame); + } else { + MP_ERR(c, "could not use filter output\n"); + mp_frame_unref(&frame); + } + progress = true; + } else if (r == AVERROR(EAGAIN)) { + // We expect that libavfilter will request input on one of the + // input pads (via av_buffersrc_get_nb_failed_requests()). + } else if (r == AVERROR_EOF) { + if (!c->draining_recover && !pad->buffer_is_eof) + mp_pin_in_write(pad->pin, MP_EOF_FRAME); + if (!pad->buffer_is_eof) + progress = true; + pad->buffer_is_eof = true; + } else { + // Real error - ignore it. + MP_ERR(c, "error on filtering (%d)\n", r); + } + } + + return progress; +} + +static void lavfi_process(struct mp_filter *f) +{ + struct lavfi *c = f->priv; + + if (!c->initialized) + init_graph(c); + + while (c->initialized) { + bool a = read_output_pads(c); + bool b = feed_input_pads(c); + if (!a && !b) + break; + } + + // Start over on format changes or EOF draining. + if (c->draining_recover) { + // Wait until all outputs got EOF. + bool all_eof = true; + for (int n = 0; n < c->num_out_pads; n++) + all_eof &= c->out_pads[n]->buffer_is_eof; + + if (all_eof) { + MP_VERBOSE(c, "recovering all eof\n"); + free_graph(c); + mp_filter_internal_mark_progress(c->f); + } + } + + if (c->failed) + mp_filter_internal_mark_failed(c->f); +} + +static void lavfi_reset(struct mp_filter *f) +{ + struct lavfi *c = f->priv; + + free_graph(c); + + for (int n = 0; n < c->num_in_pads; n++) + mp_frame_unref(&c->in_pads[n]->pending); +} + +static void lavfi_destroy(struct mp_filter *f) +{ + struct lavfi *c = f->priv; + + lavfi_reset(f); + av_frame_free(&c->tmp_frame); +} + +static bool lavfi_command(struct mp_filter *f, struct mp_filter_command *cmd) +{ + struct lavfi *c = f->priv; + + if (!c->initialized) + return false; + + switch (cmd->type) { +#if LIBAVFILTER_VERSION_MICRO >= 100 + case MP_FILTER_COMMAND_TEXT: { + return avfilter_graph_send_command(c->graph, "all", cmd->cmd, cmd->arg, + &(char){0}, 0, 0) >= 0; + } +#endif + case MP_FILTER_COMMAND_GET_META: { + // We can worry later about what it should do to multi output filters. + if (c->num_out_pads < 1) + return false; + struct mp_tags **ptags = cmd->res; + *ptags = mp_tags_dup(NULL, c->out_pads[0]->metadata); + return true; + } + default: + return false; + } +} + +static const struct mp_filter_info lavfi_filter = { + .name = "lavfi", + .priv_size = sizeof(struct lavfi), + .process = lavfi_process, + .reset = lavfi_reset, + .destroy = lavfi_destroy, + .command = lavfi_command, +}; + +static struct lavfi *lavfi_alloc(struct mp_filter *parent) +{ + struct mp_filter *f = mp_filter_create(parent, &lavfi_filter); + if (!f) + return NULL; + + struct lavfi *c = f->priv; + + c->f = f; + c->log = f->log; + c->public.f = f; + c->tmp_frame = av_frame_alloc(); + if (!c->tmp_frame) + abort(); + + return c; +} + +static struct mp_lavfi *do_init(struct lavfi *c) +{ + precreate_graph(c, true); + + if (c->failed) + goto error; + + for (int n = 0; n < c->num_in_pads + c->num_out_pads; n++) { + // First add input pins to satisfy order for bidir graph types. + struct lavfi_pad *pad = + n < c->num_in_pads ? c->in_pads[n] : c->out_pads[n - c->num_in_pads]; + + pad->pin_index = c->f->num_pins; + pad->pin = mp_filter_add_pin(c->f, pad->dir, pad->name); + + if (c->force_type && c->force_type != pad->type) { + MP_FATAL(c, "mismatching media type\n"); + goto error; + } + } + + if (c->force_bidir) { + if (c->f->num_pins != 2) { + MP_FATAL(c, "exactly 2 pads required\n"); + goto error; + } + if (mp_pin_get_dir(c->f->ppins[0]) != MP_PIN_OUT || + mp_pin_get_dir(c->f->ppins[1]) != MP_PIN_IN) + { + MP_FATAL(c, "1 input and 1 output pad required\n"); + goto error; + } + } + + return &c->public; + +error: + talloc_free(c->f); + return NULL; +} + +struct mp_lavfi *mp_lavfi_create_graph(struct mp_filter *parent, + enum mp_frame_type type, bool bidir, + char **graph_opts, const char *graph) +{ + struct lavfi *c = lavfi_alloc(parent); + if (!c) + return NULL; + + c->force_type = type; + c->force_bidir = bidir; + c->graph_opts = mp_dup_str_array(c, graph_opts); + c->graph_string = talloc_strdup(c, graph); + + return do_init(c); +} + +struct mp_lavfi *mp_lavfi_create_filter(struct mp_filter *parent, + enum mp_frame_type type, bool bidir, + char **graph_opts, + const char *filter, char **filter_opts) +{ + struct lavfi *c = lavfi_alloc(parent); + if (!c) + return NULL; + + c->force_type = type; + c->force_bidir = bidir; + c->graph_opts = mp_dup_str_array(c, graph_opts); + c->graph_string = talloc_strdup(c, filter); + c->direct_filter_opts = mp_dup_str_array(c, filter_opts); + c->direct_filter = true; + + return do_init(c); +} + +struct lavfi_user_opts { + bool is_bridge; + + char *graph; + char **avopts; + + char *filter_name; + char **filter_opts; +}; + +static struct mp_filter *vf_lavfi_create(struct mp_filter *parent, void *options) +{ + struct lavfi_user_opts *opts = options; + struct mp_lavfi *l; + if (opts->is_bridge) { + l = mp_lavfi_create_filter(parent, MP_FRAME_VIDEO, true, + opts->avopts, opts->filter_name, + opts->filter_opts); + } else { + l = mp_lavfi_create_graph(parent, MP_FRAME_VIDEO, true, + opts->avopts, opts->graph); + } + talloc_free(opts); + return l ? l->f : NULL; +} + +static bool is_single_video_only(const AVFilterPad *pads) +{ + int count = avfilter_pad_count(pads); + if (count != 1) + return false; + return avfilter_pad_get_type(pads, 0) == AVMEDIA_TYPE_VIDEO; +} + +// Does it have exactly one video input and one video output? +static bool is_usable(const AVFilter *filter) +{ + return is_single_video_only(filter->inputs) && + is_single_video_only(filter->outputs); +} + +static void print_help(struct mp_log *log) +{ + mp_info(log, "List of libavfilter filters:\n"); + for (const AVFilter *filter = avfilter_next(NULL); filter; + filter = avfilter_next(filter)) + { + if (is_usable(filter)) + mp_info(log, " %-16s %s\n", filter->name, filter->description); + } + mp_info(log, "\n" + "This lists video->video filters only. Refer to\n" + "\n" + " https://ffmpeg.org/ffmpeg-filters.html\n" + "\n" + "to see how to use each filter and what arguments each filter takes.\n" + "Also, be sure to quote the FFmpeg filter string properly, e.g.:\n" + "\n" + " \"--vf=lavfi=[gradfun=20:30]\"\n" + "\n" + "Otherwise, mpv and libavfilter syntax will conflict.\n" + "\n"); +} + +#define OPT_BASE_STRUCT struct lavfi_user_opts + +const struct mp_user_filter_entry vf_lavfi = { + .desc = { + .description = "libavfilter bridge", + .name = "lavfi", + .priv_size = sizeof(OPT_BASE_STRUCT), + .options = (const m_option_t[]){ + OPT_STRING("graph", graph, M_OPT_MIN, .min = 1), + OPT_KEYVALUELIST("o", avopts, 0), + {0} + }, + .print_help = print_help, + }, + .create = vf_lavfi_create, +}; + +const struct mp_user_filter_entry vf_lavfi_bridge = { + .desc = { + .description = "libavfilter bridge (explicit options)", + .name = "lavfi-bridge", + .priv_size = sizeof(OPT_BASE_STRUCT), + .options = (const m_option_t[]){ + OPT_STRING("name", filter_name, M_OPT_MIN, .min = 1), + OPT_KEYVALUELIST("opts", filter_opts, 0), + OPT_KEYVALUELIST("o", avopts, 0), + {0} + }, + .priv_defaults = &(const OPT_BASE_STRUCT){ + .is_bridge = true, + }, + .print_help = print_help, + }, + .create = vf_lavfi_create, +}; |