filter-chain: add builtin plugins

Add a simple mixer builtin plugin.
Free memory on module unload.
This commit is contained in:
Wim Taymans 2021-05-09 12:00:30 +02:00
parent 3c7222d84b
commit 00e85a252c
2 changed files with 224 additions and 58 deletions

View file

@ -114,7 +114,6 @@ struct ladspa_descriptor {
struct ladspa_handle *handle; struct ladspa_handle *handle;
char label[256]; char label[256];
const LADSPA_Descriptor *desc; const LADSPA_Descriptor *desc;
struct spa_list node_list;
uint32_t n_input; uint32_t n_input;
uint32_t n_output; uint32_t n_output;
@ -171,6 +170,17 @@ struct link {
struct port *input; struct port *input;
}; };
struct graph_port {
const LADSPA_Descriptor *desc;
LADSPA_Handle hndl;
uint32_t port;
};
struct graph_hndl {
const LADSPA_Descriptor *desc;
LADSPA_Handle hndl;
};
struct graph { struct graph {
struct impl *impl; struct impl *impl;
@ -178,18 +188,13 @@ struct graph {
struct spa_list link_list; struct spa_list link_list;
uint32_t n_input; uint32_t n_input;
const LADSPA_Descriptor *in_desc[MAX_PORTS]; struct graph_port input[MAX_PORTS];
LADSPA_Handle in_hndl[MAX_PORTS];
uint32_t in_port[MAX_PORTS];
uint32_t n_output; uint32_t n_output;
const LADSPA_Descriptor *out_desc[MAX_PORTS]; struct graph_port output[MAX_PORTS];
LADSPA_Handle out_hndl[MAX_PORTS];
uint32_t out_port[MAX_PORTS];
uint32_t n_hndl; uint32_t n_hndl;
const LADSPA_Descriptor *desc[MAX_PORTS]; struct graph_hndl hndl[MAX_HNDL];
LADSPA_Handle hndl[MAX_PORTS];
uint32_t n_control; uint32_t n_control;
struct port *control_port[MAX_CONTROLS]; struct port *control_port[MAX_CONTROLS];
@ -230,6 +235,8 @@ struct impl {
struct graph graph; struct graph graph;
}; };
#include "module-filter-chain/builtin.h"
static void do_unload_module(void *obj, void *data, int res, uint32_t id) static void do_unload_module(void *obj, void *data, int res, uint32_t id)
{ {
struct impl *impl = data; struct impl *impl = data;
@ -269,22 +276,24 @@ static void capture_process(void *d)
for (i = 0; i < in->buffer->n_datas; i++) { for (i = 0; i < in->buffer->n_datas; i++) {
struct spa_data *ds = &in->buffer->datas[i]; struct spa_data *ds = &in->buffer->datas[i];
graph->in_desc[i]->connect_port(graph->in_hndl[i], struct graph_port *port = &graph->input[i];
graph->in_port[i], port->desc->connect_port(port->hndl, port->port,
SPA_MEMBER(ds->data, ds->chunk->offset, void)); SPA_MEMBER(ds->data, ds->chunk->offset, void));
size = SPA_MAX(size, ds->chunk->size); size = SPA_MAX(size, ds->chunk->size);
stride = SPA_MAX(stride, ds->chunk->stride); stride = SPA_MAX(stride, ds->chunk->stride);
} }
for (i = 0; i < out->buffer->n_datas; i++) { for (i = 0; i < out->buffer->n_datas; i++) {
struct spa_data *dd = &out->buffer->datas[i]; struct spa_data *dd = &out->buffer->datas[i];
graph->out_desc[i]->connect_port(graph->out_hndl[i], struct graph_port *port = &graph->output[i];
graph->out_port[i], dd->data); port->desc->connect_port(port->hndl, port->port, dd->data);
dd->chunk->offset = 0; dd->chunk->offset = 0;
dd->chunk->size = size; dd->chunk->size = size;
dd->chunk->stride = stride; dd->chunk->stride = stride;
} }
for (i = 0; i < n_hndl; i++) for (i = 0; i < n_hndl; i++) {
graph->desc[i]->run(graph->hndl[i], size / sizeof(float)); struct graph_hndl *hndl = &graph->hndl[i];
hndl->desc->run(hndl->hndl, size / sizeof(float));
}
done: done:
if (in != NULL) if (in != NULL)
@ -670,8 +679,8 @@ static void ladspa_handle_unref(struct ladspa_handle *hndl)
{ {
if (--hndl->ref > 0) if (--hndl->ref > 0)
return; return;
if (hndl->handle)
dlclose(hndl->handle); dlclose(hndl->handle);
free(hndl); free(hndl);
} }
@ -698,16 +707,19 @@ static struct ladspa_handle *ladspa_handle_load(struct impl *impl, const char *p
hndl->ref = 1; hndl->ref = 1;
snprintf(hndl->path, sizeof(hndl->path), "%s", path); snprintf(hndl->path, sizeof(hndl->path), "%s", path);
hndl->handle = dlopen(path, RTLD_NOW); if (strcmp(plugin, "builtin") == 0) {
if (hndl->handle == NULL) { hndl->desc_func = builtin_ladspa_descriptor;
pw_log_error("plugin dlopen failed %s: %s", path, dlerror()); } else {
res = -ENOENT; hndl->handle = dlopen(path, RTLD_NOW);
goto exit; if (hndl->handle == NULL) {
pw_log_error("plugin dlopen failed %s: %s", path, dlerror());
res = -ENOENT;
goto exit;
}
hndl->desc_func = (LADSPA_Descriptor_Function)dlsym(hndl->handle,
"ladspa_descriptor");
} }
hndl->desc_func = (LADSPA_Descriptor_Function)dlsym(hndl->handle,
"ladspa_descriptor");
if (hndl->desc_func == NULL) { if (hndl->desc_func == NULL) {
pw_log_error("cannot find descriptor function from %s: %s", pw_log_error("cannot find descriptor function from %s: %s",
path, dlerror()); path, dlerror());
@ -726,7 +738,7 @@ exit:
return NULL; return NULL;
} }
static void ladspa_descriptor_unref(struct impl *impl, struct ladspa_descriptor *desc) static void ladspa_descriptor_unref(struct ladspa_descriptor *desc)
{ {
if (--desc->ref > 0) if (--desc->ref > 0)
return; return;
@ -767,7 +779,6 @@ static struct ladspa_descriptor *ladspa_descriptor_load(struct impl *impl,
} }
desc->desc = d; desc->desc = d;
snprintf(desc->label, sizeof(desc->label), "%s", label); snprintf(desc->label, sizeof(desc->label), "%s", label);
spa_list_init(&desc->node_list);
for (p = 0; p < d->PortCount; p++) { for (p = 0; p < d->PortCount; p++) {
if (LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) { if (LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) {
@ -888,6 +899,11 @@ static int parse_link(struct graph *graph, struct spa_json *json)
link->output = out_port; link->output = out_port;
link->input = in_port; link->input = in_port;
if (in_port->n_links > 0) {
pw_log_info("Can't have more than 1 link to %s, use a mixer", input);
return -ENOTSUP;
}
pw_log_info("linking %s:%s -> %s:%s", pw_log_info("linking %s:%s -> %s:%s",
out_port->node->name, out_port->node->name,
out_port->node->desc->desc->PortNames[out_port->p], out_port->node->desc->desc->PortNames[out_port->p],
@ -906,6 +922,17 @@ static int parse_link(struct graph *graph, struct spa_json *json)
return 0; return 0;
} }
static void link_free(struct link *link)
{
spa_list_remove(&link->input_link);
link->input->n_links--;
link->input->node->n_deps--;
spa_list_remove(&link->output_link);
link->output->n_links--;
spa_list_remove(&link->link);
free(link);
}
/** /**
* type = ladspa * type = ladspa
* name = rev * name = rev
@ -935,8 +962,6 @@ static int load_node(struct graph *graph, struct spa_json *json)
pw_log_error("type expects a string"); pw_log_error("type expects a string");
return -EINVAL; return -EINVAL;
} }
if (strcmp(type, "ladspa") != 0)
return -ENOTSUP;
} else if (strcmp("name", key) == 0) { } else if (strcmp("name", key) == 0) {
if (spa_json_get_string(json, name, sizeof(name)) <= 0) { if (spa_json_get_string(json, name, sizeof(name)) <= 0) {
pw_log_error("name expects a string"); pw_log_error("name expects a string");
@ -959,8 +984,12 @@ static int load_node(struct graph *graph, struct spa_json *json)
break; break;
} }
pw_log_info("loading %s %s", plugin, label); if (strcmp(type, "builtin") == 0) {
snprintf(plugin, sizeof(plugin), "%s", "builtin");
} else if (strcmp(type, "ladspa") != 0)
return -ENOTSUP;
pw_log_info("loading %s %s", plugin, label);
if ((desc = ladspa_descriptor_load(graph->impl, plugin, label)) == NULL) if ((desc = ladspa_descriptor_load(graph->impl, plugin, label)) == NULL)
return -errno; return -errno;
@ -1009,6 +1038,23 @@ static int load_node(struct graph *graph, struct spa_json *json)
return 0; return 0;
} }
static void node_free(struct node *node)
{
uint32_t i, j;
const LADSPA_Descriptor *d = node->desc->desc;
spa_list_remove(&node->link);
for (i = 0; i < node->n_hndl; i++) {
for (j = 0; j < node->desc->n_output; j++)
free(node->output_port[j].audio_data[i]);
if (d->activate)
d->activate(node->hndl[i]);
d->cleanup(node->hndl[i]);
}
ladspa_descriptor_unref(node->desc);
free(node);
}
static struct node *find_next_node(struct graph *graph) static struct node *find_next_node(struct graph *graph)
{ {
struct node *node; struct node *node;
@ -1028,13 +1074,6 @@ static int setup_input_port(struct graph *graph, struct port *port)
struct link *link; struct link *link;
uint32_t i, n_hndl = port->node->n_hndl; uint32_t i, n_hndl = port->node->n_hndl;
if (port->n_links > 1) {
pw_log_warn("mixing not implemented yet");
/* FIXME, add a mixer node here, connect all linked peer
* data to the input, make output data, connect to the input
* port */
}
spa_list_for_each(link, &port->link_list, input_link) { spa_list_for_each(link, &port->link_list, input_link) {
struct port *peer = link->output; struct port *peer = link->output;
for (i = 0; i < n_hndl; i++) { for (i = 0; i < n_hndl; i++) {
@ -1078,6 +1117,8 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_
struct impl *impl = graph->impl; struct impl *impl = graph->impl;
struct node *node, *first, *last; struct node *node, *first, *last;
struct port *port; struct port *port;
struct graph_port *gp;
struct graph_hndl *gh;
uint32_t i, j, n_input, n_output, n_hndl = 0; uint32_t i, j, n_input, n_output, n_hndl = 0;
int res; int res;
unsigned long p; unsigned long p;
@ -1177,12 +1218,12 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_
desc = first->desc; desc = first->desc;
d = desc->desc; d = desc->desc;
for (j = 0; j < desc->n_input; j++) { for (j = 0; j < desc->n_input; j++) {
gp = &graph->input[graph->n_input++];
pw_log_info("input port %s[%d]:%s", pw_log_info("input port %s[%d]:%s",
first->name, i, d->PortNames[desc->input[j]]); first->name, i, d->PortNames[desc->input[j]]);
graph->in_desc[graph->n_input] = d; gp->desc = d;
graph->in_hndl[graph->n_input] = first->hndl[i]; gp->hndl = first->hndl[i];
graph->in_port[graph->n_input] = desc->input[j]; gp->port = desc->input[j];
graph->n_input++;
} }
} else { } else {
struct spa_json it = *inputs; struct spa_json it = *inputs;
@ -1192,26 +1233,26 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_
pw_log_error("input port %s not found", v); pw_log_error("input port %s not found", v);
goto error; goto error;
} }
gp = &graph->input[graph->n_input++];
desc = port->node->desc; desc = port->node->desc;
d = desc->desc; d = desc->desc;
pw_log_info("input port %s[%d]:%s", pw_log_info("input port %s[%d]:%s",
port->node->name, i, d->PortNames[port->p]); port->node->name, i, d->PortNames[port->p]);
graph->in_desc[graph->n_input] = d; gp->desc = d;
graph->in_hndl[graph->n_input] = port->node->hndl[i]; gp->hndl = port->node->hndl[i];
graph->in_port[graph->n_input] = port->p; gp->port = port->p;
graph->n_input++;
} }
} }
if (outputs == NULL) { if (outputs == NULL) {
desc = last->desc; desc = last->desc;
d = desc->desc; d = desc->desc;
for (j = 0; j < desc->n_output; j++) { for (j = 0; j < desc->n_output; j++) {
gp = &graph->output[graph->n_output++];
pw_log_info("output port %s[%d]:%s", pw_log_info("output port %s[%d]:%s",
last->name, i, d->PortNames[desc->output[j]]); last->name, i, d->PortNames[desc->output[j]]);
graph->out_desc[graph->n_output] = d; gp->desc = d;
graph->out_hndl[graph->n_output] = last->hndl[i]; gp->hndl = last->hndl[i];
graph->out_port[graph->n_output] = desc->output[j]; gp->port = desc->output[j];
graph->n_output++;
} }
} else { } else {
struct spa_json it = *outputs; struct spa_json it = *outputs;
@ -1221,15 +1262,14 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_
pw_log_error("output port %s not found", v); pw_log_error("output port %s not found", v);
goto error; goto error;
} }
gp = &graph->output[graph->n_output++];
desc = port->node->desc; desc = port->node->desc;
d = desc->desc; d = desc->desc;
pw_log_info("output port %s[%d]:%s", pw_log_info("output port %s[%d]:%s",
port->node->name, i, d->PortNames[port->p]); port->node->name, i, d->PortNames[port->p]);
graph->out_desc[graph->n_output] = d; gp->desc = d;
graph->out_hndl[graph->n_output] = port->node->hndl[i]; gp->hndl = port->node->hndl[i];
graph->out_port[graph->n_output] = port->p; gp->port = port->p;
graph->n_output++;
} }
} }
} }
@ -1247,9 +1287,9 @@ static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_
setup_input_port(graph, &node->input_port[i]); setup_input_port(graph, &node->input_port[i]);
for (i = 0; i < n_hndl; i++) { for (i = 0; i < n_hndl; i++) {
graph->hndl[graph->n_hndl] = node->hndl[i]; gh = &graph->hndl[graph->n_hndl++];
graph->desc[graph->n_hndl] = d; gh->hndl = node->hndl[i];
graph->n_hndl++; gh->desc = d;
} }
for (i = 0; i < desc->n_output; i++) for (i = 0; i < desc->n_output; i++)
@ -1338,6 +1378,16 @@ static int load_graph(struct graph *graph, struct pw_properties *props)
return setup_graph(graph, pinputs, poutputs); return setup_graph(graph, pinputs, poutputs);
} }
static void graph_free(struct graph *graph)
{
struct link *link;
struct node *node;
spa_list_consume(link, &graph->link_list, link)
link_free(link);
spa_list_consume(node, &graph->node_list, link)
node_free(node);
}
static void core_error(void *data, uint32_t id, int seq, int res, const char *message) static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
{ {
struct impl *impl = data; struct impl *impl = data;
@ -1379,6 +1429,7 @@ static void impl_destroy(struct impl *impl)
if (impl->playback_props) if (impl->playback_props)
pw_properties_free(impl->playback_props); pw_properties_free(impl->playback_props);
pw_work_queue_cancel(impl->work, impl, SPA_ID_INVALID); pw_work_queue_cancel(impl->work, impl, SPA_ID_INVALID);
graph_free(&impl->graph);
free(impl); free(impl);
} }

View file

@ -0,0 +1,115 @@
/* 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.
*/
struct builtin {
LADSPA_Data *port[64];
};
static LADSPA_Handle builtin_instantiate(const struct _LADSPA_Descriptor * Descriptor,
unsigned long SampleRate)
{
struct builtin *impl;
impl = calloc(1, sizeof(*impl));
if (impl == NULL)
return NULL;
return impl;
}
static void builtin_connect_port(LADSPA_Handle Instance, unsigned long Port,
LADSPA_Data * DataLocation)
{
struct builtin *impl = Instance;
impl->port[Port] = DataLocation;
}
static void builtin_cleanup(LADSPA_Handle Instance)
{
struct builtin *impl = Instance;
free(impl);
}
/** mixer */
static void mixer_run(LADSPA_Handle Instance, unsigned long SampleCount)
{
struct builtin *impl = Instance;
unsigned long i;
float gain1 = impl->port[3][0];
float gain2 = impl->port[4][0];
float *in1 = impl->port[1], *in2 = impl->port[2], *out = impl->port[0];
if (gain1 == 0.0f && gain2 == 0.0f) {
memset(out, 0, SampleCount * sizeof(float));
} else if (gain1 == 1.0f && gain2 == 1.0f) {
for (i = 0; i < SampleCount; i++)
out[i] = in1[i] + in2[i];
} else {
for (i = 0; i < SampleCount; i++)
out[i] = in1[i] * gain1 + in2[i] * gain2;
}
}
static const LADSPA_PortDescriptor mixer_port_desc[] = {
LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO,
LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO,
LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO,
LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL,
LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL,
};
static const char * const mixer_port_names[] = {
"Out", "In 1", "In 2", "Gain 1", "Gain 2"
};
static const LADSPA_PortRangeHint mixer_range_hints[] = {
{ 0, }, { 0, }, { 0, },
{ LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_1, 0.0, 10.0 },
{ LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_1, 0.0, 10.0 }
};
static const LADSPA_Descriptor mixer_desc = {
.Label = "mixer",
.Name = "Mix 2 inputs",
.Maker = "PipeWire",
.Copyright = "MIT",
.PortCount = 5,
.PortDescriptors = mixer_port_desc,
.PortNames = mixer_port_names,
.PortRangeHints = mixer_range_hints,
.instantiate = builtin_instantiate,
.connect_port = builtin_connect_port,
.run = mixer_run,
.cleanup = builtin_cleanup,
};
static const LADSPA_Descriptor * builtin_ladspa_descriptor(unsigned long Index)
{
switch(Index) {
case 0:
return &mixer_desc;
}
return NULL;
}