From ffc01280be6316ae625972a0976fef077a3a0b51 Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Thu, 23 Nov 2017 16:08:42 +0100 Subject: [PATCH] avfilter: add lv2 wrapper filter Signed-off-by: Paul B Mahol --- Changelog | 1 + configure | 4 + doc/filters.texi | 59 ++++ libavfilter/Makefile | 1 + libavfilter/af_lv2.c | 602 +++++++++++++++++++++++++++++++++++++++ libavfilter/allfilters.c | 1 + libavfilter/version.h | 2 +- 7 files changed, 669 insertions(+), 1 deletion(-) create mode 100644 libavfilter/af_lv2.c diff --git a/Changelog b/Changelog index 0b7e02392d..e3092e211f 100644 --- a/Changelog +++ b/Changelog @@ -20,6 +20,7 @@ version : - OpenCL overlay filter - video mix filter - video normalize filter +- audio lv2 wrapper filter version 3.4: diff --git a/configure b/configure index 7769427ffb..0cc97ebbbb 100755 --- a/configure +++ b/configure @@ -283,6 +283,7 @@ External library support: --enable-libzimg enable z.lib, needed for zscale filter [no] --enable-libzmq enable message passing via libzmq [no] --enable-libzvbi enable teletext support via libzvbi [no] + --enable-lv2 enable LV2 audio filtering [no] --disable-lzma disable lzma [autodetect] --enable-decklink enable Blackmagic DeckLink I/O support [no] --enable-libndi_newtek enable Newteck NDI I/O support [no] @@ -1631,6 +1632,7 @@ EXTERNAL_LIBRARY_LIST=" libzimg libzmq libzvbi + lv2 mediacodec openal opengl @@ -3229,6 +3231,7 @@ hqdn3d_filter_deps="gpl" interlace_filter_deps="gpl" kerndeint_filter_deps="gpl" ladspa_filter_deps="ladspa libdl" +lv2_filter_deps="lv2" mcdeint_filter_deps="avcodec gpl" movie_filter_deps="avcodec avformat" mpdecimate_filter_deps="gpl" @@ -5825,6 +5828,7 @@ enabled gmp && require gmp gmp.h mpz_export -lgmp enabled gnutls && require_pkg_config gnutls gnutls gnutls/gnutls.h gnutls_global_init enabled jni && { [ $target_os = "android" ] && check_header jni.h && enabled pthreads || die "ERROR: jni not found"; } enabled ladspa && require_header ladspa.h +enabled lv2 && require_pkg_config lv2 lilv-0 "lilv-0/lilv/lilv.h" lilv_world_new enabled libiec61883 && require libiec61883 libiec61883/iec61883.h iec61883_cmp_connect -lraw1394 -lavc1394 -lrom1394 -liec61883 enabled libass && require_pkg_config libass libass ass/ass.h ass_library_init enabled libbluray && require_pkg_config libbluray libbluray libbluray/bluray.h bd_open diff --git a/doc/filters.texi b/doc/filters.texi index fda789630b..476f014ac8 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -3281,6 +3281,65 @@ lowpass=c=LFE @end example @end itemize +@section lv2 + +Load a LV2 (LADSPA Version 2) plugin. + +To enable compilation of this filter you need to configure FFmpeg with +@code{--enable-lv2}. + +@table @option +@item plugin, p +Specifies the plugin URI. You may need to escape ':'. + +@item controls, c +Set the '|' separated list of controls which are zero or more floating point +values that determine the behavior of the loaded plugin (for example delay, +threshold or gain). +If @option{controls} is set to @code{help}, all available controls and +their valid ranges are printed. + +@item sample_rate, s +Specify the sample rate, default to 44100. Only used if plugin have +zero inputs. + +@item nb_samples, n +Set the number of samples per channel per each output frame, default +is 1024. Only used if plugin have zero inputs. + +@item duration, d +Set the minimum duration of the sourced audio. See +@ref{time duration syntax,,the Time duration section in the ffmpeg-utils(1) manual,ffmpeg-utils} +for the accepted syntax. +Note that the resulting duration may be greater than the specified duration, +as the generated audio is always cut at the end of a complete frame. +If not specified, or the expressed duration is negative, the audio is +supposed to be generated forever. +Only used if plugin have zero inputs. +@end table + +@subsection Examples + +@itemize +@item +Apply bass enhancer plugin from Calf: +@example +lv2=p=http\\\\://calf.sourceforge.net/plugins/BassEnhancer:c=amount=2 +@end example + +@item +Apply bass vinyl plugin from Calf: +@example +lv2=p=http\\\\://calf.sourceforge.net/plugins/Vinyl:c=drone=0.2|aging=0.5 +@end example + +@item +Apply bit crusher plugin from ArtyFX: +@example +lv2=p=http\\\\://www.openavproductions.com/artyfx#bitta:c=crush=0.3 +@end example +@end itemize + @section mcompand Multiband Compress or expand the audio's dynamic range. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index dd195d2538..0b77d7a01f 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -101,6 +101,7 @@ OBJS-$(CONFIG_JOIN_FILTER) += af_join.o OBJS-$(CONFIG_LADSPA_FILTER) += af_ladspa.o OBJS-$(CONFIG_LOUDNORM_FILTER) += af_loudnorm.o ebur128.o OBJS-$(CONFIG_LOWPASS_FILTER) += af_biquads.o +OBJS-$(CONFIG_LV2_FILTER) += af_lv2.o OBJS-$(CONFIG_MCOMPAND_FILTER) += af_mcompand.o OBJS-$(CONFIG_PAN_FILTER) += af_pan.o OBJS-$(CONFIG_REPLAYGAIN_FILTER) += af_replaygain.o diff --git a/libavfilter/af_lv2.c b/libavfilter/af_lv2.c new file mode 100644 index 0000000000..8a0a6fd888 --- /dev/null +++ b/libavfilter/af_lv2.c @@ -0,0 +1,602 @@ +/* + * Copyright (c) 2017 Paul B Mahol + * Copyright (c) 2007-2016 David Robillard + * + * 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 + */ + +/** + * @file + * LV2 wrapper + */ + +#include +#include +#include + +#include "libavutil/avassert.h" +#include "libavutil/avstring.h" +#include "libavutil/channel_layout.h" +#include "libavutil/opt.h" +#include "audio.h" +#include "avfilter.h" +#include "internal.h" + +typedef struct URITable { + char **uris; + size_t n_uris; +} URITable; + +typedef struct LV2Context { + const AVClass *class; + char *plugin_uri; + char *options; + + unsigned nb_inputs; + unsigned nb_inputcontrols; + unsigned nb_outputs; + + int sample_rate; + int nb_samples; + int64_t pts; + int64_t duration; + + LilvWorld *world; + const LilvPlugin *plugin; + uint32_t nb_ports; + float *values; + URITable uri_table; + LV2_URID_Map map; + LV2_Feature map_feature; + LV2_URID_Unmap unmap; + LV2_Feature unmap_feature; + LV2_Atom_Sequence seq_in[2]; + LV2_Atom_Sequence *seq_out; + const LV2_Feature *features[5]; + + float *mins; + float *maxes; + float *controls; + + LilvInstance *instance; + + LilvNode *atom_AtomPort; + LilvNode *atom_Sequence; + LilvNode *lv2_AudioPort; + LilvNode *lv2_CVPort; + LilvNode *lv2_ControlPort; + LilvNode *lv2_Optional; + LilvNode *lv2_InputPort; + LilvNode *lv2_OutputPort; + LilvNode *urid_map; + LilvNode *powerOf2BlockLength; + LilvNode *fixedBlockLength; + LilvNode *boundedBlockLength; +} LV2Context; + +#define OFFSET(x) offsetof(LV2Context, x) +#define FLAGS AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM + +static const AVOption lv2_options[] = { + { "plugin", "set plugin uri", OFFSET(plugin_uri), AV_OPT_TYPE_STRING, .flags = FLAGS }, + { "p", "set plugin uri", OFFSET(plugin_uri), AV_OPT_TYPE_STRING, .flags = FLAGS }, + { "controls", "set plugin options", OFFSET(options), AV_OPT_TYPE_STRING, .flags = FLAGS }, + { "c", "set plugin options", OFFSET(options), AV_OPT_TYPE_STRING, .flags = FLAGS }, + { "sample_rate", "set sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64=44100}, 1, INT32_MAX, FLAGS }, + { "s", "set sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64=44100}, 1, INT32_MAX, FLAGS }, + { "nb_samples", "set the number of samples per requested frame", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64=1024}, 1, INT_MAX, FLAGS }, + { "n", "set the number of samples per requested frame", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64=1024}, 1, INT_MAX, FLAGS }, + { "duration", "set audio duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=-1}, -1, INT64_MAX, FLAGS }, + { "d", "set audio duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=-1}, -1, INT64_MAX, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(lv2); + +static void uri_table_init(URITable *table) +{ + table->uris = NULL; + table->n_uris = 0; +} + +static void uri_table_destroy(URITable *table) +{ + int i; + + for (i = 0; i < table->n_uris; i++) { + av_freep(&table->uris[i]); + } + + av_freep(&table->uris); +} + +static LV2_URID uri_table_map(LV2_URID_Map_Handle handle, const char *uri) +{ + URITable *table = (URITable*)handle; + const size_t len = strlen(uri); + size_t i; + char **tmp; + + for (i = 0; i < table->n_uris; i++) { + if (!strcmp(table->uris[i], uri)) { + return i + 1; + } + } + + tmp = av_calloc(table->n_uris + 1, sizeof(char*)); + if (!tmp) + return table->n_uris; + memcpy(tmp, table->uris, table->n_uris * sizeof(char**)); + + av_free(table->uris); + table->uris = tmp; + table->uris[table->n_uris] = av_malloc(len + 1); + if (!table->uris[table->n_uris]) + return table->n_uris; + + memcpy(table->uris[table->n_uris], uri, len + 1); + table->n_uris++; + + return table->n_uris; +} + +static const char *uri_table_unmap(LV2_URID_Map_Handle handle, LV2_URID urid) +{ + URITable *table = (URITable*)handle; + + if (urid > 0 && urid <= table->n_uris) { + return table->uris[urid - 1]; + } + + return NULL; +} + +static void connect_ports(LV2Context *s, AVFrame *in, AVFrame *out) +{ + int ich = 0, och = 0, i; + + for (i = 0; i < s->nb_ports; i++) { + const LilvPort *port = lilv_plugin_get_port_by_index(s->plugin, i); + + if (lilv_port_is_a(s->plugin, port, s->lv2_AudioPort) || + lilv_port_is_a(s->plugin, port, s->lv2_CVPort)) { + if (lilv_port_is_a(s->plugin, port, s->lv2_InputPort)) { + lilv_instance_connect_port(s->instance, i, in->extended_data[ich++]); + } else if (lilv_port_is_a(s->plugin, port, s->lv2_OutputPort)) { + lilv_instance_connect_port(s->instance, i, out->extended_data[och++]); + } else { + av_log(s, AV_LOG_WARNING, "port %d neither input nor output, skipping\n", i); + } + } else if (lilv_port_is_a(s->plugin, port, s->atom_AtomPort)) { + if (lilv_port_is_a(s->plugin, port, s->lv2_InputPort)) { + lilv_instance_connect_port(s->instance, i, &s->seq_in); + } else { + lilv_instance_connect_port(s->instance, i, s->seq_out); + } + } else if (lilv_port_is_a(s->plugin, port, s->lv2_ControlPort)) { + lilv_instance_connect_port(s->instance, i, &s->controls[i]); + } + } + + s->seq_in[0].atom.size = sizeof(LV2_Atom_Sequence_Body); + s->seq_in[0].atom.type = uri_table_map(&s->uri_table, LV2_ATOM__Sequence); + s->seq_out->atom.size = 9624; + s->seq_out->atom.type = uri_table_map(&s->uri_table, LV2_ATOM__Chunk); +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + LV2Context *s = ctx->priv; + AVFrame *out; + + if (!s->nb_outputs || + (av_frame_is_writable(in) && s->nb_inputs == s->nb_outputs)) { + out = in; + } else { + out = ff_get_audio_buffer(ctx->outputs[0], in->nb_samples); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + } + + connect_ports(s, in, out); + + lilv_instance_run(s->instance, in->nb_samples); + + if (out != in) + av_frame_free(&in); + + return ff_filter_frame(ctx->outputs[0], out); +} + +static int request_frame(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + LV2Context *s = ctx->priv; + AVFrame *out; + int64_t t; + + if (ctx->nb_inputs) + return ff_request_frame(ctx->inputs[0]); + + t = av_rescale(s->pts, AV_TIME_BASE, s->sample_rate); + if (s->duration >= 0 && t >= s->duration) + return AVERROR_EOF; + + out = ff_get_audio_buffer(outlink, s->nb_samples); + if (!out) + return AVERROR(ENOMEM); + + connect_ports(s, out, out); + + lilv_instance_run(s->instance, out->nb_samples); + + out->sample_rate = s->sample_rate; + out->pts = s->pts; + s->pts += s->nb_samples; + + return ff_filter_frame(outlink, out); +} + +static const LV2_Feature buf_size_features[3] = { + { LV2_BUF_SIZE__powerOf2BlockLength, NULL }, + { LV2_BUF_SIZE__fixedBlockLength, NULL }, + { LV2_BUF_SIZE__boundedBlockLength, NULL }, +}; + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + LV2Context *s = ctx->priv; + char *p, *arg, *saveptr = NULL; + int i, sample_rate; + + uri_table_init(&s->uri_table); + s->map.handle = &s->uri_table; + s->map.map = uri_table_map; + s->map_feature.URI = LV2_URID_MAP_URI; + s->map_feature.data = &s->map; + s->unmap.handle = &s->uri_table; + s->unmap.unmap = uri_table_unmap; + s->unmap_feature.URI = LV2_URID_UNMAP_URI; + s->unmap_feature.data = &s->unmap; + s->features[0] = &s->map_feature; + s->features[1] = &s->unmap_feature; + s->features[2] = &buf_size_features[0]; + s->features[3] = &buf_size_features[1]; + s->features[4] = &buf_size_features[2]; + + if (ctx->nb_inputs) { + AVFilterLink *inlink = ctx->inputs[0]; + + outlink->format = inlink->format; + outlink->sample_rate = sample_rate = inlink->sample_rate; + if (s->nb_inputs == s->nb_outputs) { + outlink->channel_layout = inlink->channel_layout; + outlink->channels = inlink->channels; + } + + } else { + outlink->sample_rate = sample_rate = s->sample_rate; + outlink->time_base = (AVRational){1, s->sample_rate}; + } + + s->instance = lilv_plugin_instantiate(s->plugin, sample_rate, s->features); + if (!s->instance) { + av_log(s, AV_LOG_ERROR, "Failed to instantiate <%s>\n", lilv_node_as_uri(lilv_plugin_get_uri(s->plugin))); + return AVERROR(EINVAL); + } + + s->mins = av_calloc(s->nb_ports, sizeof(float)); + s->maxes = av_calloc(s->nb_ports, sizeof(float)); + s->controls = av_calloc(s->nb_ports, sizeof(float)); + + if (!s->mins || !s->maxes || !s->controls) + return AVERROR(ENOMEM); + + lilv_plugin_get_port_ranges_float(s->plugin, s->mins, s->maxes, s->controls); + s->seq_out = av_malloc(sizeof(LV2_Atom_Sequence) + 9624); + if (!s->seq_out) + return AVERROR(ENOMEM); + + if (s->options && !strcmp(s->options, "help")) { + if (!s->nb_inputcontrols) { + av_log(ctx, AV_LOG_INFO, + "The '%s' plugin does not have any input controls.\n", + s->plugin_uri); + } else { + av_log(ctx, AV_LOG_INFO, + "The '%s' plugin has the following input controls:\n", + s->plugin_uri); + for (i = 0; i < s->nb_ports; i++) { + const LilvPort *port = lilv_plugin_get_port_by_index(s->plugin, i); + const LilvNode *symbol = lilv_port_get_symbol(s->plugin, port); + LilvNode *name = lilv_port_get_name(s->plugin, port); + + if (lilv_port_is_a(s->plugin, port, s->lv2_InputPort) && + lilv_port_is_a(s->plugin, port, s->lv2_ControlPort)) { + av_log(ctx, AV_LOG_INFO, "%s\t\t (from %f to %f) (default %f)\t\t%s\n", + lilv_node_as_string(symbol), s->mins[i], s->maxes[i], s->controls[i], + lilv_node_as_string(name)); + } + + lilv_node_free(name); + } + } + return AVERROR_EXIT; + } + + p = s->options; + while (s->options) { + const LilvPort *port; + LilvNode *sym; + float val; + char *str, *vstr; + int index; + + if (!(arg = av_strtok(p, " |", &saveptr))) + break; + p = NULL; + + vstr = strstr(arg, "="); + if (vstr == NULL) { + av_log(ctx, AV_LOG_ERROR, "Invalid syntax.\n"); + return AVERROR(EINVAL); + } + + vstr[0] = 0; + str = arg; + val = atof(vstr+1); + sym = lilv_new_string(s->world, str); + port = lilv_plugin_get_port_by_symbol(s->plugin, sym); + lilv_node_free(sym); + if (!port) { + av_log(s, AV_LOG_WARNING, "Unknown option: <%s>\n", str); + } else { + index = lilv_port_get_index(s->plugin, port); + s->controls[index] = val; + } + } + + if (s->nb_inputs && + (lilv_plugin_has_feature(s->plugin, s->powerOf2BlockLength) || + lilv_plugin_has_feature(s->plugin, s->fixedBlockLength) || + lilv_plugin_has_feature(s->plugin, s->boundedBlockLength))) { + AVFilterLink *inlink = ctx->inputs[0]; + + inlink->partial_buf_size = inlink->min_samples = inlink->max_samples = 4096; + } + + return 0; +} + +static av_cold int init(AVFilterContext *ctx) +{ + LV2Context *s = ctx->priv; + const LilvPlugins *plugins; + const LilvPlugin *plugin; + AVFilterPad pad = { NULL }; + LilvNode *uri; + int i; + + s->world = lilv_world_new(); + if (!s->world) + return AVERROR(ENOMEM); + + uri = lilv_new_uri(s->world, s->plugin_uri); + if (!uri) { + av_log(s, AV_LOG_ERROR, "Invalid plugin URI <%s>\n", s->plugin_uri); + return AVERROR(EINVAL); + } + + lilv_world_load_all(s->world); + plugins = lilv_world_get_all_plugins(s->world); + plugin = lilv_plugins_get_by_uri(plugins, uri); + lilv_node_free(uri); + + if (!plugin) { + av_log(s, AV_LOG_ERROR, "Plugin <%s> not found\n", s->plugin_uri); + return AVERROR(EINVAL); + } + + s->plugin = plugin; + s->nb_ports = lilv_plugin_get_num_ports(s->plugin); + + s->lv2_InputPort = lilv_new_uri(s->world, LV2_CORE__InputPort); + s->lv2_OutputPort = lilv_new_uri(s->world, LV2_CORE__OutputPort); + s->lv2_AudioPort = lilv_new_uri(s->world, LV2_CORE__AudioPort); + s->lv2_ControlPort = lilv_new_uri(s->world, LV2_CORE__ControlPort); + s->lv2_Optional = lilv_new_uri(s->world, LV2_CORE__connectionOptional); + s->atom_AtomPort = lilv_new_uri(s->world, LV2_ATOM__AtomPort); + s->atom_Sequence = lilv_new_uri(s->world, LV2_ATOM__Sequence); + s->urid_map = lilv_new_uri(s->world, LV2_URID__map); + s->powerOf2BlockLength = lilv_new_uri(s->world, LV2_BUF_SIZE__powerOf2BlockLength); + s->fixedBlockLength = lilv_new_uri(s->world, LV2_BUF_SIZE__fixedBlockLength); + s->boundedBlockLength = lilv_new_uri(s->world, LV2_BUF_SIZE__boundedBlockLength); + + for (i = 0; i < s->nb_ports; i++) { + const LilvPort *lport = lilv_plugin_get_port_by_index(s->plugin, i); + int is_input = 0; + int is_optional = 0; + + is_optional = lilv_port_has_property(s->plugin, lport, s->lv2_Optional); + + if (lilv_port_is_a(s->plugin, lport, s->lv2_InputPort)) { + is_input = 1; + } else if (!lilv_port_is_a(s->plugin, lport, s->lv2_OutputPort) && !is_optional) { + return AVERROR(EINVAL); + } + + if (lilv_port_is_a(s->plugin, lport, s->lv2_ControlPort)) { + if (is_input) { + s->nb_inputcontrols++; + } + } else if (lilv_port_is_a(s->plugin, lport, s->lv2_AudioPort)) { + if (is_input) { + s->nb_inputs++; + } else { + s->nb_outputs++; + } + } + } + + pad.type = AVMEDIA_TYPE_AUDIO; + + if (s->nb_inputs) { + pad.name = av_asprintf("in0:%s:%u", s->plugin_uri, s->nb_inputs); + if (!pad.name) + return AVERROR(ENOMEM); + + pad.filter_frame = filter_frame; + if (ff_insert_inpad(ctx, ctx->nb_inputs, &pad) < 0) { + av_freep(&pad.name); + return AVERROR(ENOMEM); + } + } + + return 0; +} + +static int query_formats(AVFilterContext *ctx) +{ + LV2Context *s = ctx->priv; + AVFilterFormats *formats; + AVFilterChannelLayouts *layouts; + AVFilterLink *outlink = ctx->outputs[0]; + static const enum AVSampleFormat sample_fmts[] = { + AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE }; + int ret; + + formats = ff_make_format_list(sample_fmts); + if (!formats) + return AVERROR(ENOMEM); + ret = ff_set_common_formats(ctx, formats); + if (ret < 0) + return ret; + + if (s->nb_inputs) { + formats = ff_all_samplerates(); + if (!formats) + return AVERROR(ENOMEM); + + ret = ff_set_common_samplerates(ctx, formats); + if (ret < 0) + return ret; + } else { + int sample_rates[] = { s->sample_rate, -1 }; + + ret = ff_set_common_samplerates(ctx, ff_make_format_list(sample_rates)); + if (ret < 0) + return ret; + } + + if (s->nb_inputs == 2 && s->nb_outputs == 2) { + layouts = NULL; + ret = ff_add_channel_layout(&layouts, AV_CH_LAYOUT_STEREO); + if (ret < 0) + return ret; + ret = ff_set_common_channel_layouts(ctx, layouts); + if (ret < 0) + return ret; + } else { + if (s->nb_inputs >= 1) { + AVFilterLink *inlink = ctx->inputs[0]; + uint64_t inlayout = FF_COUNT2LAYOUT(s->nb_inputs); + + layouts = NULL; + ret = ff_add_channel_layout(&layouts, inlayout); + if (ret < 0) + return ret; + ret = ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts); + if (ret < 0) + return ret; + + if (!s->nb_outputs) { + ret = ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts); + if (ret < 0) + return ret; + } + } + + if (s->nb_outputs >= 1) { + uint64_t outlayout = FF_COUNT2LAYOUT(s->nb_outputs); + + layouts = NULL; + ret = ff_add_channel_layout(&layouts, outlayout); + if (ret < 0) + return ret; + ret = ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts); + if (ret < 0) + return ret; + } + } + + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + LV2Context *s = ctx->priv; + + lilv_node_free(s->powerOf2BlockLength); + lilv_node_free(s->fixedBlockLength); + lilv_node_free(s->boundedBlockLength); + lilv_node_free(s->urid_map); + lilv_node_free(s->atom_Sequence); + lilv_node_free(s->atom_AtomPort); + lilv_node_free(s->lv2_Optional); + lilv_node_free(s->lv2_ControlPort); + lilv_node_free(s->lv2_AudioPort); + lilv_node_free(s->lv2_OutputPort); + lilv_node_free(s->lv2_InputPort); + uri_table_destroy(&s->uri_table); + lilv_instance_free(s->instance); + lilv_world_free(s->world); + av_freep(&s->mins); + av_freep(&s->maxes); + av_freep(&s->controls); + av_freep(&s->seq_out); + + if (ctx->nb_inputs) + av_freep(&ctx->input_pads[0].name); +} + +static const AVFilterPad lv2_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .config_props = config_output, + .request_frame = request_frame, + }, + { NULL } +}; + +AVFilter ff_af_lv2 = { + .name = "lv2", + .description = NULL_IF_CONFIG_SMALL("Apply LV2 effect."), + .priv_size = sizeof(LV2Context), + .priv_class = &lv2_class, + .init = init, + .uninit = uninit, + .query_formats = query_formats, + .inputs = 0, + .outputs = lv2_outputs, + .flags = AVFILTER_FLAG_DYNAMIC_INPUTS, +}; diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index e09d841387..4c834f7381 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -112,6 +112,7 @@ static void register_all(void) REGISTER_FILTER(LADSPA, ladspa, af); REGISTER_FILTER(LOUDNORM, loudnorm, af); REGISTER_FILTER(LOWPASS, lowpass, af); + REGISTER_FILTER(LV2, lv2, af); REGISTER_FILTER(MCOMPAND, mcompand, af); REGISTER_FILTER(PAN, pan, af); REGISTER_FILTER(REPLAYGAIN, replaygain, af); diff --git a/libavfilter/version.h b/libavfilter/version.h index fdcf76befe..1d356a9a5a 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -30,7 +30,7 @@ #include "libavutil/version.h" #define LIBAVFILTER_VERSION_MAJOR 7 -#define LIBAVFILTER_VERSION_MINOR 4 +#define LIBAVFILTER_VERSION_MINOR 5 #define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \