From patchwork Thu Nov 23 21:16:46 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 6298 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.2.161.94 with SMTP id m30csp1284278jah; Thu, 23 Nov 2017 13:17:49 -0800 (PST) X-Google-Smtp-Source: AGs4zMbabDUnl6WVlQ7ICnQUKvFUKq/OBuJKu7fPRR812RJ+7XBcNNkNWP8Bu67QBpdokb4a1Ou4 X-Received: by 10.28.104.6 with SMTP id d6mr7948429wmc.101.1511471869892; Thu, 23 Nov 2017 13:17:49 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1511471869; cv=none; d=google.com; s=arc-20160816; b=k5EoKqUv1aF5kySxHljuHX/C1BQcXT4T1z8Y+1SJ7ywqlDrI8lAOCR45bWjIWU9Yj9 SAN3RiTGiJEpu5S/5BSbZGfqrZ4+UWWvXnFRDlsbobBmlAKG+UBjCwrH+IGE17TZl8xA u7vG5SqAhw+sL0GqG03pl9JfxFPoIHO+HeucBYk0qLWafDAW57gNEqlmt73HMayWRXsw vVqsk4MrdXdExQi104ZhH358Et+voTHl7UYmTu23BFXWIc9SM1rXX3aOjGOzVCE5Uzlh QDZ4wi4LBLSgfEXLvJ+3tVw/LWvnNqTtepIGOr9AcacOLh6iL5bexes9Om6URjNwOAKL 7q/Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:message-id:date:to:from:dkim-signature :delivered-to:arc-authentication-results; bh=Rme8Dz3Vjj2wLCew9+yKjrXxGTP90d8enVMfNW/IJk0=; b=ih8xgsfRASwddJd+NGNmel7JfxxFhHmHuXeElQCdu6imEE7v7hY/xb4zkIff4YJqwI 7POC1ueZqteLOvAUfD1QqEBEclIaGqbYEso5fu+u8vady4ZLCUIXUpMVs1mQNC6osFC6 vR53OBJKnAZno5b26u4j/jdG0nPqUhVVVuC7WLRhfOLykh1GM6y6LUigQZT0wXsGVkRL x5m47vHHuS28hfYP/+gXw7oyao28iod4VwieMYa/d3Q5ZIu9AHtfgZYq3a3dK08W09zx 0yHr9bOoSwDzEIzy6jmxRnBVhK7lfgHdPa8TspnkD0VmTNg6YhAgHIcQaG2Iyzijmc2M kBwg== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=R1HQBrpZ; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id m5si15406446wrb.75.2017.11.23.13.17.47; Thu, 23 Nov 2017 13:17:49 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=R1HQBrpZ; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 1BF5C68A17C; Thu, 23 Nov 2017 23:17:46 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wm0-f47.google.com (mail-wm0-f47.google.com [74.125.82.47]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id B2F4168A08F for ; Thu, 23 Nov 2017 23:17:39 +0200 (EET) Received: by mail-wm0-f47.google.com with SMTP id b189so18939358wmd.5 for ; Thu, 23 Nov 2017 13:17:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=+fSatcBPtG7Dp++ttKKT8D0pSeZYIq3tJR0RyBjwuus=; b=R1HQBrpZ60aXyRYhLlob+L0nk/zk+gW1FuZo2d0L+il3RS2OwW8uNQ2j7yB1hHpD4k msSfQ7Gi9u4zHESfcOdgOEz08+4JmgIDvJlaPfWriwhKj+k4MGLYdKaMZsrT02gvSxyL eSyKyM1Dmj5af3P0Nt1GsdAKVhyd3zi1zU6XDjOBxYI9DIZC8BBi9+NNjvCilCEa+KK+ BhU9hXle3HZFGnYOUe0B2SvOoikGLZsq1ZWmUVPt2dhUpnEDW1aUJpan+PrcdhWhrf8y ZNwLXIGvF69DD6sM2GFudiUSpDpbUVj79V6iMTvaVEfsOVKbtUHQTNL4ORgz9ATQ+JVU WmzA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=+fSatcBPtG7Dp++ttKKT8D0pSeZYIq3tJR0RyBjwuus=; b=HIrPSp/INBtaQ2zu4RdJ6d0A2gERXUxmaLWs99OyDjYCRgFeBp7D1NQFSQAROxCCgI jfP/I6jUNLj1mZsvg6cFXCEovvMEUy5qeTRYeGromgwOHiOMDSbXcfXySdwZcW9TgRdT HV0Prsx4exV4ZFW5i5u0VbBxQ64qSSh7NlF/YT4lno7dJmJSfkfA9SWvYjHna2OAZ0Kn RnAXC/GVdE+JgimvPkFaidoVQlRpNXBkICMi5IKyqs0M7PzD+laf+6021o2IV5c520Ay YpuHsDF1rPu8tqND8YCMF5LSDzDTsbnJiO1oflvhAUF2wee2gWrqNu1qKQwucfJy/9Bx 9+Jw== X-Gm-Message-State: AJaThX6TxD/a8BS2j+7Y6LCBtDRC7bz3q8B6TRF87X66Gi2I8CObzFbS GAJ3oPsLPFeHWpeb0DlH1owRuw== X-Received: by 10.28.7.133 with SMTP id 127mr7902264wmh.31.1511471859496; Thu, 23 Nov 2017 13:17:39 -0800 (PST) Received: from localhost.localdomain ([94.250.174.60]) by smtp.gmail.com with ESMTPSA id z192sm3122397wmc.32.2017.11.23.13.17.37 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 23 Nov 2017 13:17:38 -0800 (PST) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Thu, 23 Nov 2017 22:16:46 +0100 Message-Id: <20171123211646.7323-1-onemda@gmail.com> X-Mailer: git-send-email 2.11.0 Subject: [FFmpeg-devel] [PATCH] avfilter: add lv2 wrapper filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Paul B Mahol --- configure | 4 + doc/filters.texi | 37 ++++ libavfilter/Makefile | 1 + libavfilter/af_lv2.c | 552 +++++++++++++++++++++++++++++++++++++++++++++++ libavfilter/allfilters.c | 1 + 5 files changed, 595 insertions(+) create mode 100644 libavfilter/af_lv2.c diff --git a/configure b/configure index 3ec6407fb2..84c965ce4a 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="lilv" mcdeint_filter_deps="avcodec gpl" movie_filter_deps="avcodec avformat" mpdecimate_filter_deps="gpl" @@ -5829,6 +5832,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 lilv 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 04a8139c6d..d0b053a266 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -3280,6 +3280,43 @@ lowpass=c=LFE @end example @end itemize +@section LV2 + +Load a LV2 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. + +@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 + @section mcompand Multiband Compress or expand the audio's dynamic range. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 89737b5ad0..d1924ebfdd 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..bf6c51b07d --- /dev/null +++ b/libavfilter/af_lv2.c @@ -0,0 +1,552 @@ +/* + * 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 "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 long nb_inputs; + unsigned long nb_inputcontrols; + unsigned long 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_out; + const LV2_Feature* features[3]; + + 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; +} 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; + + for (i = 0; i < table->n_uris; i++) { + if (!strcmp(table->uris[i], uri)) { + return i + 1; + } + } + + table->uris = av_realloc(table->uris, ++table->n_uris * sizeof(char*)); + table->uris[table->n_uris - 1] = av_malloc(len + 1); + memcpy(table->uris[table->n_uris - 1], uri, len + 1); + 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 int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + LV2Context *s = ctx->priv; + LV2_Atom_Sequence seq_in = { + { sizeof(LV2_Atom_Sequence_Body), + uri_table_map(&s->uri_table, LV2_ATOM__Sequence) }, + { 0, 0 } }; + int ich = 0, och = 0, i; + 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); + } + + 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, &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]); + } + } + + seq_in.atom.size = sizeof(LV2_Atom_Sequence_Body); + seq_in.atom.type = uri_table_map(&s->uri_table, LV2_ATOM__Sequence); + s->seq_out->atom.size = 1024; + s->seq_out->atom.type = uri_table_map(&s->uri_table, LV2_ATOM__Chunk); + + 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; + int i; + + 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); + + for (i = 0; i < s->nb_outputs; i++) { + } + + out->sample_rate = s->sample_rate; + out->pts = s->pts; + s->pts += s->nb_samples; + + return ff_filter_frame(outlink, out); +} + +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; + + 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)); + + lilv_plugin_get_port_ranges_float(s->plugin, s->mins, s->maxes, s->controls); + s->seq_out = av_malloc(sizeof(LV2_Atom_Sequence) + 1024); + + 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; + } + } + + 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); + + 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:%lu", 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->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 50e9d9a0cb..a858202ad7 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);