mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
filter-chain: add support for lv2 plugins
Support lv2 plugins and their control values.
This commit is contained in:
parent
d86008cf8f
commit
597b332666
6 changed files with 402 additions and 7 deletions
|
|
@ -430,6 +430,9 @@ summary({'pipewire-alsa': alsa_dep.found()}, bool_yn: true)
|
|||
openssl_lib = dependency('openssl', required: get_option('raop'))
|
||||
summary({'OpenSSL (for raop-sink)': openssl_lib.found()}, bool_yn: true)
|
||||
|
||||
lilv_lib = dependency('lilv-0', required: get_option('lv2'))
|
||||
summary({'lilv (for lv2 plugins)': lilv_lib.found()}, bool_yn: true)
|
||||
|
||||
installed_tests_metadir = pipewire_datadir / 'installed-tests' / pipewire_name
|
||||
installed_tests_execdir = pipewire_libexecdir / 'installed-tests' / pipewire_name
|
||||
installed_tests_enabled = not get_option('installed_tests').disabled()
|
||||
|
|
|
|||
|
|
@ -218,3 +218,7 @@ option('raop',
|
|||
description: 'Enable module for Remote Audio Output Protocol',
|
||||
type: 'feature',
|
||||
value: 'auto')
|
||||
option('lv2',
|
||||
description: 'Enable loading of LV2 plugins',
|
||||
type: 'feature',
|
||||
value: 'auto')
|
||||
|
|
|
|||
|
|
@ -78,19 +78,33 @@ pffft_c = static_library('pffft_c',
|
|||
)
|
||||
simd_dependencies += pffft_c
|
||||
|
||||
filter_chain_sources = [
|
||||
'module-filter-chain.c',
|
||||
'module-filter-chain/biquad.c',
|
||||
'module-filter-chain/ladspa_plugin.c',
|
||||
'module-filter-chain/builtin_plugin.c',
|
||||
'module-filter-chain/convolver.c'
|
||||
]
|
||||
filter_chain_dependencies = [
|
||||
mathlib, dl_lib, pipewire_dep, sndfile_dep
|
||||
]
|
||||
|
||||
if lilv_lib.found()
|
||||
filter_chain_sources += [
|
||||
'module-filter-chain/lv2_plugin.c'
|
||||
]
|
||||
filter_chain_dependencies += [ lilv_lib ]
|
||||
endif
|
||||
|
||||
|
||||
pipewire_module_filter_chain = shared_library('pipewire-module-filter-chain',
|
||||
[ 'module-filter-chain.c',
|
||||
'module-filter-chain/biquad.c',
|
||||
'module-filter-chain/ladspa_plugin.c',
|
||||
'module-filter-chain/builtin_plugin.c',
|
||||
'module-filter-chain/convolver.c' ],
|
||||
filter_chain_sources,
|
||||
include_directories : [configinc, spa_inc],
|
||||
install : true,
|
||||
install_dir : modules_install_dir,
|
||||
install_rpath: modules_install_dir,
|
||||
link_with : simd_dependencies,
|
||||
dependencies : [mathlib, dl_lib, pipewire_dep, sndfile_dep],
|
||||
dependencies : filter_chain_dependencies,
|
||||
)
|
||||
|
||||
pipewire_module_echo_cancel_sources = [
|
||||
|
|
|
|||
|
|
@ -754,6 +754,9 @@ static struct plugin *plugin_load(struct impl *impl, const char *type, const cha
|
|||
else if (spa_streq(type, "ladspa")) {
|
||||
pl = load_ladspa_plugin(path, NULL);
|
||||
}
|
||||
else if (spa_streq(type, "lv2")) {
|
||||
pl = load_lv2_plugin(path, NULL);
|
||||
}
|
||||
if (pl == NULL)
|
||||
goto exit;
|
||||
|
||||
|
|
@ -1068,7 +1071,7 @@ static int load_node(struct graph *graph, struct spa_json *json)
|
|||
|
||||
if (spa_streq(type, "builtin")) {
|
||||
snprintf(plugin, sizeof(plugin), "%s", "builtin");
|
||||
} else if (!spa_streq(type, "ladspa"))
|
||||
} else if (!spa_streq(type, "ladspa") && !spa_streq(type, "lv2"))
|
||||
return -ENOTSUP;
|
||||
|
||||
pw_log_info("loading type:%s plugin:%s label:%s", type, plugin, label);
|
||||
|
|
|
|||
369
src/modules/module-filter-chain/lv2_plugin.c
Normal file
369
src/modules/module-filter-chain/lv2_plugin.c
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
/* PipeWire
|
||||
*
|
||||
* Copyright © 2021 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <spa/utils/defs.h>
|
||||
#include <spa/utils/list.h>
|
||||
#include <spa/utils/string.h>
|
||||
|
||||
#include <pipewire/log.h>
|
||||
#include <pipewire/utils.h>
|
||||
#include <pipewire/array.h>
|
||||
|
||||
#include <lilv/lilv.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h>
|
||||
|
||||
#include "plugin.h"
|
||||
|
||||
static struct context *_context;
|
||||
|
||||
typedef struct URITable {
|
||||
struct pw_array array;
|
||||
} URITable;
|
||||
|
||||
static void uri_table_init(URITable *table)
|
||||
{
|
||||
pw_array_init(&table->array, 1024);
|
||||
}
|
||||
|
||||
static void uri_table_destroy(URITable *table)
|
||||
{
|
||||
char **p;
|
||||
pw_array_for_each(p, &table->array)
|
||||
free(*p);
|
||||
pw_array_clear(&table->array);
|
||||
}
|
||||
|
||||
static LV2_URID uri_table_map(LV2_URID_Map_Handle handle, const char *uri)
|
||||
{
|
||||
URITable *table = (URITable*)handle;
|
||||
char *p;
|
||||
size_t i = 0;
|
||||
|
||||
pw_array_for_each(p, &table->array) {
|
||||
if (spa_streq(p, uri))
|
||||
return i + 1;
|
||||
i++;
|
||||
}
|
||||
|
||||
pw_array_add_ptr(&table->array, strdup(uri));
|
||||
return pw_array_get_len(&table->array, char*);
|
||||
}
|
||||
|
||||
static const char *uri_table_unmap(LV2_URID_Map_Handle handle, LV2_URID urid)
|
||||
{
|
||||
URITable *table = (URITable*)handle;
|
||||
|
||||
if (urid > 0 && urid <= pw_array_get_len(&table->array, char*))
|
||||
return *pw_array_get_unchecked(&table->array, urid, char*);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct context {
|
||||
int ref;
|
||||
LilvWorld *world;
|
||||
|
||||
LilvNode *lv2_InputPort;
|
||||
LilvNode *lv2_OutputPort;
|
||||
LilvNode *lv2_AudioPort;
|
||||
LilvNode *lv2_ControlPort;
|
||||
LilvNode *lv2_Optional;
|
||||
LilvNode *atom_AtomPort;
|
||||
LilvNode *atom_Sequence;
|
||||
LilvNode *urid_map;
|
||||
LilvNode *powerOf2BlockLength;
|
||||
LilvNode *fixedBlockLength;
|
||||
LilvNode *boundedBlockLength;
|
||||
|
||||
URITable uri_table;
|
||||
LV2_URID_Map map;
|
||||
LV2_URID_Unmap unmap;
|
||||
LV2_Feature map_feature;
|
||||
LV2_Feature unmap_feature;
|
||||
const LV2_Feature *features[5];
|
||||
};
|
||||
|
||||
static void context_free(struct context *c)
|
||||
{
|
||||
if (c->world) {
|
||||
lilv_node_free(c->powerOf2BlockLength);
|
||||
lilv_node_free(c->fixedBlockLength);
|
||||
lilv_node_free(c->boundedBlockLength);
|
||||
lilv_node_free(c->urid_map);
|
||||
lilv_node_free(c->atom_Sequence);
|
||||
lilv_node_free(c->atom_AtomPort);
|
||||
lilv_node_free(c->lv2_Optional);
|
||||
lilv_node_free(c->lv2_ControlPort);
|
||||
lilv_node_free(c->lv2_AudioPort);
|
||||
lilv_node_free(c->lv2_OutputPort);
|
||||
lilv_node_free(c->lv2_InputPort);
|
||||
lilv_world_free(c->world);
|
||||
}
|
||||
uri_table_destroy(&c->uri_table);
|
||||
free(c);
|
||||
}
|
||||
|
||||
static const LV2_Feature buf_size_features[3] = {
|
||||
{ LV2_BUF_SIZE__powerOf2BlockLength, NULL },
|
||||
{ LV2_BUF_SIZE__fixedBlockLength, NULL },
|
||||
{ LV2_BUF_SIZE__boundedBlockLength, NULL },
|
||||
};
|
||||
|
||||
static struct context *context_new(void)
|
||||
{
|
||||
struct context *c;
|
||||
|
||||
c = calloc(1, sizeof(*c));
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
uri_table_init(&c->uri_table);
|
||||
c->world = lilv_world_new();
|
||||
if (c->world == NULL)
|
||||
goto error;
|
||||
|
||||
lilv_world_load_all(c->world);
|
||||
|
||||
c->lv2_InputPort = lilv_new_uri(c->world, LV2_CORE__InputPort);
|
||||
c->lv2_OutputPort = lilv_new_uri(c->world, LV2_CORE__OutputPort);
|
||||
c->lv2_AudioPort = lilv_new_uri(c->world, LV2_CORE__AudioPort);
|
||||
c->lv2_ControlPort = lilv_new_uri(c->world, LV2_CORE__ControlPort);
|
||||
c->lv2_Optional = lilv_new_uri(c->world, LV2_CORE__connectionOptional);
|
||||
c->atom_AtomPort = lilv_new_uri(c->world, LV2_ATOM__AtomPort);
|
||||
c->atom_Sequence = lilv_new_uri(c->world, LV2_ATOM__Sequence);
|
||||
c->urid_map = lilv_new_uri(c->world, LV2_URID__map);
|
||||
c->powerOf2BlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__powerOf2BlockLength);
|
||||
c->fixedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__fixedBlockLength);
|
||||
c->boundedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__boundedBlockLength);
|
||||
|
||||
c->map.handle = &c->uri_table;
|
||||
c->map.map = uri_table_map;
|
||||
c->map_feature.URI = LV2_URID_MAP_URI;
|
||||
c->map_feature.data = &c->map;
|
||||
c->unmap.handle = &c->uri_table;
|
||||
c->unmap.unmap = uri_table_unmap;
|
||||
c->unmap_feature.URI = LV2_URID_UNMAP_URI;
|
||||
c->unmap_feature.data = &c->unmap;
|
||||
c->features[0] = &c->map_feature;
|
||||
c->features[1] = &c->unmap_feature;
|
||||
c->features[2] = &buf_size_features[0];
|
||||
c->features[3] = &buf_size_features[1];
|
||||
c->features[4] = &buf_size_features[2];
|
||||
|
||||
return c;
|
||||
error:
|
||||
context_free(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct context *context_ref(void)
|
||||
{
|
||||
if (_context == NULL) {
|
||||
_context = context_new();
|
||||
if (_context == NULL)
|
||||
return NULL;
|
||||
}
|
||||
_context->ref++;
|
||||
return _context;
|
||||
}
|
||||
|
||||
static void context_unref(struct context *context)
|
||||
{
|
||||
if (--_context->ref == 0) {
|
||||
context_free(_context);
|
||||
_context = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
struct plugin {
|
||||
struct fc_plugin plugin;
|
||||
struct context *c;
|
||||
const LilvPlugin *p;
|
||||
};
|
||||
|
||||
struct descriptor {
|
||||
struct fc_descriptor desc;
|
||||
struct plugin *p;
|
||||
};
|
||||
|
||||
static void *lv2_instantiate(const struct fc_descriptor *desc,
|
||||
unsigned long *SampleRate, int index, const char *config)
|
||||
{
|
||||
struct descriptor *d = (struct descriptor*)desc;
|
||||
struct plugin *p = d->p;
|
||||
struct context *c = p->c;
|
||||
return lilv_plugin_instantiate(p->p, *SampleRate, c->features);
|
||||
}
|
||||
|
||||
static void lv2_cleanup(void *instance)
|
||||
{
|
||||
lilv_instance_free(instance);
|
||||
}
|
||||
|
||||
static void lv2_connect_port(void *instance, unsigned long port, float *data)
|
||||
{
|
||||
lilv_instance_connect_port(instance, port, data);
|
||||
}
|
||||
|
||||
static void lv2_activate(void *instance)
|
||||
{
|
||||
lilv_instance_activate(instance);
|
||||
}
|
||||
|
||||
static void lv2_deactivate(void *instance)
|
||||
{
|
||||
lilv_instance_deactivate(instance);
|
||||
}
|
||||
|
||||
static void lv2_run(void *instance, unsigned long SampleCount)
|
||||
{
|
||||
lilv_instance_run(instance, SampleCount);
|
||||
}
|
||||
|
||||
static void lv2_free(struct fc_descriptor *desc)
|
||||
{
|
||||
struct descriptor *d = (struct descriptor*)desc;
|
||||
free((char*)d->desc.name);
|
||||
free(d->desc.ports);
|
||||
free(d);
|
||||
}
|
||||
|
||||
static const struct fc_descriptor *lv2_make_desc(struct fc_plugin *plugin, const char *name)
|
||||
{
|
||||
struct plugin *p = (struct plugin *)plugin;
|
||||
struct context *c = p->c;
|
||||
struct descriptor *desc;
|
||||
uint32_t i;
|
||||
float *mins, *maxes, *controls;
|
||||
|
||||
desc = calloc(1, sizeof(*desc));
|
||||
if (desc == NULL)
|
||||
return NULL;
|
||||
|
||||
desc->p = p;
|
||||
desc->desc.instantiate = lv2_instantiate;
|
||||
desc->desc.cleanup = lv2_cleanup;
|
||||
desc->desc.connect_port = lv2_connect_port;
|
||||
desc->desc.activate = lv2_activate;
|
||||
desc->desc.deactivate = lv2_deactivate;
|
||||
desc->desc.run = lv2_run;
|
||||
|
||||
desc->desc.free = lv2_free;
|
||||
|
||||
desc->desc.name = strdup(name);
|
||||
desc->desc.flags = 0;
|
||||
|
||||
desc->desc.n_ports = lilv_plugin_get_num_ports(p->p);
|
||||
desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct fc_port));
|
||||
|
||||
mins = alloca(desc->desc.n_ports * sizeof(float));
|
||||
maxes = alloca(desc->desc.n_ports * sizeof(float));
|
||||
controls = alloca(desc->desc.n_ports * sizeof(float));
|
||||
|
||||
lilv_plugin_get_port_ranges_float(p->p, mins, maxes, controls);
|
||||
|
||||
for (i = 0; i < desc->desc.n_ports; i++) {
|
||||
const LilvPort *port = lilv_plugin_get_port_by_index(p->p, i);
|
||||
const LilvNode *symbol = lilv_port_get_symbol(p->p, port);
|
||||
struct fc_port *fp = &desc->desc.ports[i];
|
||||
|
||||
fp->index = i;
|
||||
fp->name = strdup(lilv_node_as_string(symbol));
|
||||
|
||||
fp->flags = 0;
|
||||
if (lilv_port_is_a(p->p, port, c->lv2_InputPort))
|
||||
fp->flags |= FC_PORT_INPUT;
|
||||
if (lilv_port_is_a(p->p, port, c->lv2_OutputPort))
|
||||
fp->flags |= FC_PORT_OUTPUT;
|
||||
if (lilv_port_is_a(p->p, port, c->lv2_ControlPort))
|
||||
fp->flags |= FC_PORT_CONTROL;
|
||||
if (lilv_port_is_a(p->p, port, c->lv2_AudioPort))
|
||||
fp->flags |= FC_PORT_AUDIO;
|
||||
|
||||
fp->hint = 0;
|
||||
fp->min = mins[i];
|
||||
fp->max = maxes[i];
|
||||
fp->def = controls[i];
|
||||
}
|
||||
return &desc->desc;
|
||||
}
|
||||
|
||||
static void lv2_unload(struct fc_plugin *plugin)
|
||||
{
|
||||
struct plugin *p = (struct plugin *)plugin;
|
||||
context_unref(p->c);
|
||||
free(p);
|
||||
}
|
||||
|
||||
struct fc_plugin *load_lv2_plugin(const char *plugin_uri, const char *config)
|
||||
{
|
||||
struct context *c;
|
||||
const LilvPlugins *plugins;
|
||||
const LilvPlugin *plugin;
|
||||
LilvNode *uri;
|
||||
int res;
|
||||
struct plugin *p;
|
||||
|
||||
c = context_ref();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
uri = lilv_new_uri(c->world, plugin_uri);
|
||||
if (uri == NULL) {
|
||||
pw_log_warn("invalid URI %s", plugin_uri);
|
||||
res = -EINVAL;
|
||||
goto error_unref;
|
||||
}
|
||||
|
||||
plugins = lilv_world_get_all_plugins(c->world);
|
||||
plugin = lilv_plugins_get_by_uri(plugins, uri);
|
||||
lilv_node_free(uri);
|
||||
|
||||
if (plugin == NULL) {
|
||||
pw_log_warn("can't load plugin %s", plugin_uri);
|
||||
res = -EINVAL;
|
||||
goto error_unref;
|
||||
}
|
||||
|
||||
p = calloc(1, sizeof(*p));
|
||||
if (!p) {
|
||||
res = -errno;
|
||||
goto error_unref;
|
||||
}
|
||||
p->p = plugin;
|
||||
p->c = c;
|
||||
|
||||
p->plugin.make_desc = lv2_make_desc;
|
||||
p->plugin.unload = lv2_unload;
|
||||
|
||||
return &p->plugin;
|
||||
|
||||
error_unref:
|
||||
context_unref(c);
|
||||
errno = -res;
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -94,5 +94,7 @@ static inline void fc_descriptor_free(struct fc_descriptor *desc)
|
|||
}
|
||||
|
||||
struct fc_plugin *load_ladspa_plugin(const char *path, const char *config);
|
||||
struct fc_plugin *load_lv2_plugin(const char *path, const char *config);
|
||||
struct fc_plugin *load_builtin_plugin(const char *path, const char *config);
|
||||
|
||||
void init_builtin_plugin(uint32_t cpu_flags);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue