mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-05 13:30:02 -05:00
module-ladspa-filter: change to graph syntax
Expand the syntax to allow a graph of nodes and links later.
This commit is contained in:
parent
22373de5b4
commit
3f5ebdedca
1 changed files with 447 additions and 148 deletions
|
|
@ -81,7 +81,89 @@ static const struct spa_dict_item module_props[] = {
|
|||
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
#define MAX_NODES 1
|
||||
#define MAX_LINKS 0
|
||||
#define MAX_PORTS 64
|
||||
#define MAX_CONTROLS 256
|
||||
#define MAX_SAMPLES 8192
|
||||
|
||||
struct ladspa_handle {
|
||||
struct spa_list link;
|
||||
int ref;
|
||||
char path[PATH_MAX];
|
||||
void *handle;
|
||||
LADSPA_Descriptor_Function desc_func;
|
||||
struct spa_list descriptor_list;
|
||||
};
|
||||
|
||||
struct ladspa_descriptor {
|
||||
struct spa_list link;
|
||||
int ref;
|
||||
struct ladspa_handle *handle;
|
||||
char label[256];
|
||||
const LADSPA_Descriptor *desc;
|
||||
struct spa_list node_list;
|
||||
|
||||
uint32_t n_input;
|
||||
uint32_t n_output;
|
||||
uint32_t n_control;
|
||||
uint32_t n_notify;
|
||||
unsigned long input[MAX_PORTS];
|
||||
unsigned long output[MAX_PORTS];
|
||||
unsigned long control[MAX_PORTS];
|
||||
unsigned long notify[MAX_PORTS];
|
||||
LADSPA_Data default_control[MAX_PORTS];
|
||||
};
|
||||
|
||||
struct node {
|
||||
struct spa_list link;
|
||||
struct ladspa_descriptor *desc;
|
||||
|
||||
uint32_t pending;
|
||||
uint32_t required;
|
||||
|
||||
char name[256];
|
||||
LADSPA_Data control_data[MAX_PORTS];
|
||||
LADSPA_Data notify_data[MAX_PORTS];
|
||||
|
||||
uint32_t n_hndl;
|
||||
LADSPA_Handle hndl[MAX_PORTS];
|
||||
};
|
||||
|
||||
struct link {
|
||||
struct spa_list link;
|
||||
uint32_t output_node;
|
||||
uint32_t output_port;
|
||||
uint32_t input_node;
|
||||
uint32_t input_port;
|
||||
LADSPA_Data control_data;
|
||||
LADSPA_Data audio_data[MAX_SAMPLES];
|
||||
};
|
||||
|
||||
struct graph {
|
||||
struct impl *impl;
|
||||
|
||||
struct spa_list node_list;
|
||||
struct spa_list link_list;
|
||||
|
||||
uint32_t n_input;
|
||||
const LADSPA_Descriptor *in_desc[MAX_PORTS];
|
||||
LADSPA_Handle *in_hndl[MAX_PORTS];
|
||||
uint32_t in_port[MAX_PORTS];
|
||||
|
||||
uint32_t n_output;
|
||||
const LADSPA_Descriptor *out_desc[MAX_PORTS];
|
||||
LADSPA_Handle *out_hndl[MAX_PORTS];
|
||||
uint32_t out_port[MAX_PORTS];
|
||||
|
||||
uint32_t n_hndl;
|
||||
const LADSPA_Descriptor *desc[MAX_PORTS];
|
||||
LADSPA_Handle *hndl[MAX_PORTS];
|
||||
|
||||
uint32_t n_control;
|
||||
struct node *control_node[MAX_CONTROLS];
|
||||
uint32_t control_index[MAX_CONTROLS];
|
||||
};
|
||||
|
||||
struct impl {
|
||||
struct pw_context *context;
|
||||
|
|
@ -95,6 +177,8 @@ struct impl {
|
|||
struct spa_hook core_proxy_listener;
|
||||
struct spa_hook core_listener;
|
||||
|
||||
struct spa_list ladspa_handle_list;
|
||||
|
||||
struct pw_properties *capture_props;
|
||||
struct pw_stream *capture;
|
||||
struct spa_hook capture_listener;
|
||||
|
|
@ -110,21 +194,7 @@ struct impl {
|
|||
|
||||
uint32_t rate;
|
||||
|
||||
void *handle;
|
||||
uint32_t n_input;
|
||||
uint32_t n_output;
|
||||
uint32_t n_control;
|
||||
uint32_t n_notify;
|
||||
unsigned long input[MAX_PORTS];
|
||||
unsigned long output[MAX_PORTS];
|
||||
unsigned long control[MAX_PORTS];
|
||||
unsigned long notify[MAX_PORTS];
|
||||
const LADSPA_Descriptor *desc;
|
||||
uint32_t n_hndl;
|
||||
LADSPA_Handle hndl[MAX_PORTS];
|
||||
LADSPA_Data control_data[MAX_PORTS];
|
||||
LADSPA_Data notify_data[MAX_PORTS];
|
||||
LADSPA_Data default_control[MAX_PORTS];
|
||||
struct graph graph;
|
||||
};
|
||||
|
||||
static void do_unload_module(void *obj, void *data, int res, uint32_t id)
|
||||
|
|
@ -151,9 +221,9 @@ static void capture_process(void *d)
|
|||
{
|
||||
struct impl *impl = d;
|
||||
struct pw_buffer *in, *out;
|
||||
uint32_t i, size = 0, n_hndl = impl->n_hndl;
|
||||
struct graph *graph = &impl->graph;
|
||||
uint32_t i, size = 0, n_hndl = graph->n_hndl;
|
||||
int32_t stride = 0;
|
||||
const LADSPA_Descriptor *desc = impl->desc;
|
||||
|
||||
if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL)
|
||||
pw_log_warn("out of capture buffers: %m");
|
||||
|
|
@ -166,22 +236,22 @@ static void capture_process(void *d)
|
|||
|
||||
for (i = 0; i < in->buffer->n_datas; i++) {
|
||||
struct spa_data *ds = &in->buffer->datas[i];
|
||||
desc->connect_port(impl->hndl[i % n_hndl],
|
||||
impl->input[i % impl->n_input],
|
||||
graph->in_desc[i]->connect_port(graph->in_hndl[i],
|
||||
graph->in_port[i],
|
||||
SPA_MEMBER(ds->data, ds->chunk->offset, void));
|
||||
size = SPA_MAX(size, ds->chunk->size);
|
||||
stride = SPA_MAX(stride, ds->chunk->stride);
|
||||
}
|
||||
for (i = 0; i < out->buffer->n_datas; i++) {
|
||||
struct spa_data *dd = &out->buffer->datas[i];
|
||||
desc->connect_port(impl->hndl[i % n_hndl],
|
||||
impl->output[i % impl->n_output], dd->data);
|
||||
graph->out_desc[i]->connect_port(graph->out_hndl[i],
|
||||
graph->out_port[i], dd->data);
|
||||
dd->chunk->offset = 0;
|
||||
dd->chunk->size = size;
|
||||
dd->chunk->stride = stride;
|
||||
}
|
||||
for (i = 0; i < n_hndl; i++)
|
||||
desc->run(impl->hndl[i], size / sizeof(float));
|
||||
graph->desc[i]->run(graph->hndl[i], size / sizeof(float));
|
||||
|
||||
done:
|
||||
if (in != NULL)
|
||||
|
|
@ -190,9 +260,9 @@ done:
|
|||
pw_stream_queue_buffer(impl->playback, out);
|
||||
}
|
||||
|
||||
static float get_default(struct impl *impl, uint32_t p)
|
||||
static float get_default(struct impl *impl, struct ladspa_descriptor *desc, uint32_t p)
|
||||
{
|
||||
const LADSPA_Descriptor *d = impl->desc;
|
||||
const LADSPA_Descriptor *d = desc->desc;
|
||||
LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor;
|
||||
LADSPA_Data lower, upper, def;
|
||||
|
||||
|
|
@ -253,15 +323,19 @@ static float get_default(struct impl *impl, uint32_t p)
|
|||
return def;
|
||||
}
|
||||
|
||||
static struct spa_pod *get_prop_info(struct impl *impl, struct spa_pod_builder *b, uint32_t idx)
|
||||
static struct spa_pod *get_prop_info(struct graph *graph, struct spa_pod_builder *b, uint32_t idx)
|
||||
{
|
||||
struct spa_pod_frame f[2];
|
||||
const LADSPA_Descriptor *d = impl->desc;
|
||||
uint32_t p = impl->control[idx];
|
||||
struct impl *impl = graph->impl;
|
||||
struct node *node = graph->control_node[idx];
|
||||
struct ladspa_descriptor *desc = node->desc;
|
||||
uint32_t i = graph->control_index[idx];
|
||||
uint32_t p = desc->control[i];
|
||||
const LADSPA_Descriptor *d = desc->desc;
|
||||
LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor;
|
||||
float def, upper, lower;
|
||||
|
||||
def = get_default(impl, p);
|
||||
def = get_default(impl, desc, p);
|
||||
lower = d->PortRangeHints[p].LowerBound;
|
||||
upper = d->PortRangeHints[p].UpperBound;
|
||||
|
||||
|
|
@ -289,30 +363,34 @@ static struct spa_pod *get_prop_info(struct impl *impl, struct spa_pod_builder *
|
|||
return spa_pod_builder_pop(b, &f[0]);
|
||||
}
|
||||
|
||||
static struct spa_pod *get_props_param(struct impl *impl, struct spa_pod_builder *b)
|
||||
static struct spa_pod *get_props_param(struct graph *graph, struct spa_pod_builder *b)
|
||||
{
|
||||
struct spa_pod_frame f[2];
|
||||
uint32_t i;
|
||||
|
||||
spa_pod_builder_push_object(b, &f[0],
|
||||
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
|
||||
for (i = 0; i < impl->n_control; i++) {
|
||||
for (i = 0; i < graph->n_control; i++) {
|
||||
struct node *node = graph->control_node[i];
|
||||
uint32_t idx = graph->control_index[i];
|
||||
spa_pod_builder_prop(b, SPA_PROP_START_CUSTOM + i, 0);
|
||||
spa_pod_builder_float(b, impl->control_data[i]);
|
||||
spa_pod_builder_float(b, node->control_data[idx]);
|
||||
}
|
||||
return spa_pod_builder_pop(b, &f[0]);
|
||||
}
|
||||
|
||||
static int set_control_value(struct impl *impl, const char *name, float *value)
|
||||
static int set_control_value(struct node *node, const char *name, float *value)
|
||||
{
|
||||
uint32_t i;
|
||||
for (i = 0; i < impl->n_control; i++) {
|
||||
uint32_t p = impl->control[i];
|
||||
if (strcmp(impl->desc->PortNames[p], name) == 0) {
|
||||
float old = impl->control_data[i];
|
||||
impl->control_data[i] = value ? *value : impl->default_control[i];
|
||||
pw_log_info("control %d ('%s') to %f", i, name, impl->control_data[i]);
|
||||
return old == impl->control_data[i] ? 0 : 1;
|
||||
struct ladspa_descriptor *desc = node->desc;
|
||||
|
||||
for (i = 0; i < desc->n_control; i++) {
|
||||
uint32_t p = desc->control[i];
|
||||
if (strcmp(desc->desc->PortNames[p], name) == 0) {
|
||||
float old = node->control_data[i];
|
||||
node->control_data[i] = value ? *value : desc->default_control[i];
|
||||
pw_log_info("control %d ('%s') to %f", i, name, node->control_data[i]);
|
||||
return old == node->control_data[i] ? 0 : 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -323,6 +401,7 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param)
|
|||
struct impl *impl = data;
|
||||
const struct spa_pod_prop *prop;
|
||||
struct spa_pod_object *obj = (struct spa_pod_object *) param;
|
||||
struct graph *graph = &impl->graph;
|
||||
int changed = 0;
|
||||
|
||||
if (id != SPA_PARAM_Props)
|
||||
|
|
@ -331,20 +410,24 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param)
|
|||
SPA_POD_OBJECT_FOREACH(obj, prop) {
|
||||
uint32_t idx;
|
||||
float value;
|
||||
struct node *node;
|
||||
|
||||
if (prop->key < SPA_PROP_START_CUSTOM)
|
||||
continue;
|
||||
idx = prop->key - SPA_PROP_START_CUSTOM;
|
||||
if (idx >= impl->n_control)
|
||||
if (idx >= graph->n_control)
|
||||
continue;
|
||||
|
||||
if (spa_pod_get_float(&prop->value, &value) < 0)
|
||||
continue;
|
||||
|
||||
if (impl->control_data[idx] != value) {
|
||||
impl->control_data[idx] = value;
|
||||
node = graph->control_node[idx];
|
||||
idx = graph->control_index[idx];
|
||||
|
||||
if (node->control_data[idx] != value) {
|
||||
node->control_data[idx] = value;
|
||||
changed++;
|
||||
pw_log_info("control %d to %f", idx, impl->control_data[idx]);
|
||||
pw_log_info("control %d to %f", idx, node->control_data[idx]);
|
||||
}
|
||||
}
|
||||
if (changed > 0) {
|
||||
|
|
@ -353,7 +436,7 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param)
|
|||
const struct spa_pod *params[1];
|
||||
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
params[0] = get_props_param(impl, &b);
|
||||
params[0] = get_props_param(graph, &b);
|
||||
|
||||
pw_stream_update_params(impl->playback, params, 1);
|
||||
}
|
||||
|
|
@ -399,6 +482,7 @@ static int setup_streams(struct impl *impl)
|
|||
uint32_t i, n_params;
|
||||
const struct spa_pod *params[256];
|
||||
struct spa_pod_builder b;
|
||||
struct graph *graph = &impl->graph;
|
||||
|
||||
impl->capture = pw_stream_new(impl->core,
|
||||
"ladspa capture", impl->capture_props);
|
||||
|
|
@ -427,10 +511,10 @@ static int setup_streams(struct impl *impl)
|
|||
params[n_params++] = spa_format_audio_raw_build(&b,
|
||||
SPA_PARAM_EnumFormat, &impl->capture_info);
|
||||
|
||||
for (i = 0; i < impl->n_control; i++)
|
||||
params[n_params++] = get_prop_info(impl, &b, i);
|
||||
for (i = 0; i < graph->n_control; i++)
|
||||
params[n_params++] = get_prop_info(graph, &b, i);
|
||||
|
||||
params[n_params++] = get_props_param(impl, &b);
|
||||
params[n_params++] = get_props_param(graph, &b);
|
||||
|
||||
res = pw_stream_connect(impl->capture,
|
||||
PW_DIRECTION_INPUT,
|
||||
|
|
@ -480,6 +564,7 @@ static const LADSPA_Descriptor *find_descriptor(LADSPA_Descriptor_Function desc_
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static uint32_t find_port(struct impl *impl, const char *name, int mask)
|
||||
{
|
||||
uint32_t p;
|
||||
|
|
@ -516,166 +601,377 @@ static uint32_t collect_ports(struct impl *impl, const char *str, unsigned long
|
|||
}
|
||||
return n_ports;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int parse_control(struct impl *impl, const char *control)
|
||||
static void ladspa_handle_unref(struct ladspa_handle *hndl)
|
||||
{
|
||||
struct spa_json it[2];
|
||||
char v[256];
|
||||
if (--hndl->ref > 0)
|
||||
return;
|
||||
|
||||
spa_json_init(&it[0], control, strlen(control));
|
||||
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
|
||||
spa_json_init(&it[1], control, strlen(control));
|
||||
|
||||
while (spa_json_get_string(&it[1], v, sizeof(v)) > 0) {
|
||||
float fl;
|
||||
if (spa_json_get_float(&it[1], &fl) <= 0)
|
||||
break;
|
||||
set_control_value(impl, v, &fl);
|
||||
}
|
||||
return 0;
|
||||
dlclose(hndl->handle);
|
||||
free(hndl);
|
||||
}
|
||||
|
||||
static int load_ladspa(struct impl *impl, struct pw_properties *props)
|
||||
static struct ladspa_handle *ladspa_handle_load(struct impl *impl, const char *plugin)
|
||||
{
|
||||
struct ladspa_handle *hndl;
|
||||
char path[PATH_MAX];
|
||||
const char *e, *plugin, *label, *inputs, *outputs, *control;
|
||||
LADSPA_Descriptor_Function desc_func;
|
||||
const LADSPA_Descriptor *d;
|
||||
uint32_t i, j, p;
|
||||
const char *e;
|
||||
int res;
|
||||
|
||||
if ((e = getenv("LADSPA_PATH")) == NULL)
|
||||
e = "/usr/lib64/ladspa";
|
||||
|
||||
if ((plugin = pw_properties_get(props, "ladspa.plugin")) == NULL)
|
||||
return -EINVAL;
|
||||
if ((label = pw_properties_get(props, "ladspa.label")) == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s.so", e, plugin);
|
||||
|
||||
impl->handle = dlopen(path, RTLD_NOW);
|
||||
if (impl->handle == NULL) {
|
||||
spa_list_for_each(hndl, &impl->ladspa_handle_list, link) {
|
||||
if (strcmp(hndl->path, path) == 0) {
|
||||
hndl->ref++;
|
||||
return hndl;
|
||||
}
|
||||
}
|
||||
|
||||
hndl = calloc(1, sizeof(*hndl));
|
||||
hndl->ref = 1;
|
||||
snprintf(hndl->path, sizeof(hndl->path), "%s", path);
|
||||
|
||||
hndl->handle = dlopen(path, RTLD_NOW);
|
||||
if (hndl->handle == NULL) {
|
||||
pw_log_error("plugin dlopen failed %s: %s", path, dlerror());
|
||||
res = -ENOENT;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
desc_func = (LADSPA_Descriptor_Function)dlsym(impl->handle,
|
||||
hndl->desc_func = (LADSPA_Descriptor_Function)dlsym(hndl->handle,
|
||||
"ladspa_descriptor");
|
||||
|
||||
if (desc_func == NULL) {
|
||||
if (hndl->desc_func == NULL) {
|
||||
pw_log_error("cannot find descriptor function from %s: %s",
|
||||
path, dlerror());
|
||||
res = -ENOSYS;
|
||||
goto exit;
|
||||
}
|
||||
if ((d = find_descriptor(desc_func, label)) == NULL) {
|
||||
spa_list_init(&hndl->descriptor_list);
|
||||
|
||||
return hndl;
|
||||
|
||||
exit:
|
||||
if (hndl->handle != NULL)
|
||||
dlclose(hndl->handle);
|
||||
free(hndl);
|
||||
errno = -res;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void ladspa_descriptor_unref(struct impl *impl, struct ladspa_descriptor *desc)
|
||||
{
|
||||
if (--desc->ref > 0)
|
||||
return;
|
||||
|
||||
spa_list_remove(&desc->link);
|
||||
ladspa_handle_unref(desc->handle);
|
||||
free(desc);
|
||||
}
|
||||
|
||||
static struct ladspa_descriptor *ladspa_descriptor_load(struct impl *impl,
|
||||
const char *plugin, const char *label)
|
||||
{
|
||||
struct ladspa_handle *hndl;
|
||||
struct ladspa_descriptor *desc;
|
||||
const LADSPA_Descriptor *d;
|
||||
uint32_t i;
|
||||
unsigned long p;
|
||||
int res;
|
||||
|
||||
if ((hndl = ladspa_handle_load(impl, plugin)) == NULL)
|
||||
return NULL;
|
||||
|
||||
spa_list_for_each(desc, &hndl->descriptor_list, link) {
|
||||
if (strcmp(desc->label, label) == 0) {
|
||||
desc->ref++;
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
desc = calloc(1, sizeof(*desc));
|
||||
desc->ref = 1;
|
||||
desc->handle = hndl;
|
||||
|
||||
if ((d = find_descriptor(hndl->desc_func, label)) == NULL) {
|
||||
pw_log_error("cannot find label %s", label);
|
||||
res = -ENOENT;
|
||||
goto exit;
|
||||
}
|
||||
impl->desc = d;
|
||||
desc->desc = d;
|
||||
snprintf(desc->label, sizeof(desc->label), "%s", label);
|
||||
spa_list_init(&desc->node_list);
|
||||
|
||||
pw_properties_setf(props, "ladspa.unique-id", "%lu", impl->desc->UniqueID);
|
||||
pw_properties_setf(props, "ladspa.name", "%s", impl->desc->Name);
|
||||
pw_properties_setf(props, "ladspa.maker", "%s", impl->desc->Maker);
|
||||
pw_properties_setf(props, "ladspa.copyright", "%s", impl->desc->Copyright);
|
||||
|
||||
if ((inputs = pw_properties_get(props, "ladspa.inputs")) != NULL) {
|
||||
if ((impl->n_input = collect_ports(impl, inputs, impl->input,
|
||||
LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO)) == SPA_ID_INVALID) {
|
||||
res = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
if ((outputs = pw_properties_get(props, "ladspa.outputs")) != NULL) {
|
||||
if ((impl->n_output = collect_ports(impl, outputs, impl->output,
|
||||
LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO)) == SPA_ID_INVALID) {
|
||||
res = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
for (p = 0; p < d->PortCount; p++) {
|
||||
if (LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) {
|
||||
if (inputs == NULL && LADSPA_IS_PORT_INPUT(d->PortDescriptors[p])) {
|
||||
pw_log_info("using port %d ('%s') as input %d", p,
|
||||
d->PortNames[p], impl->n_input);
|
||||
impl->input[impl->n_input++] = p;
|
||||
if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p])) {
|
||||
pw_log_info("using port %lu ('%s') as input %d", p,
|
||||
d->PortNames[p], desc->n_input);
|
||||
desc->input[desc->n_input++] = p;
|
||||
}
|
||||
else if (outputs == NULL && LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) {
|
||||
pw_log_info("using port %d ('%s') as output %d", p,
|
||||
d->PortNames[p], impl->n_output);
|
||||
impl->output[impl->n_output++] = p;
|
||||
else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) {
|
||||
pw_log_info("using port %lu ('%s') as output %d", p,
|
||||
d->PortNames[p], desc->n_output);
|
||||
desc->output[desc->n_output++] = p;
|
||||
}
|
||||
} else if (LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])) {
|
||||
if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p])) {
|
||||
pw_log_info("using port %d ('%s') as control %d", p,
|
||||
d->PortNames[p], impl->n_control);
|
||||
impl->control[impl->n_control++] = p;
|
||||
pw_log_info("using port %lu ('%s') as control %d", p,
|
||||
d->PortNames[p], desc->n_control);
|
||||
desc->control[desc->n_control++] = p;
|
||||
}
|
||||
else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) {
|
||||
pw_log_info("using port %d ('%s') as notify %d", p,
|
||||
d->PortNames[p], impl->n_notify);
|
||||
impl->notify[impl->n_notify++] = p;
|
||||
pw_log_info("using port %lu ('%s') as notify %d", p,
|
||||
d->PortNames[p], desc->n_notify);
|
||||
desc->notify[desc->n_notify++] = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (impl->n_input == 0 || impl->n_output == 0) {
|
||||
if (desc->n_input == 0 || desc->n_output == 0) {
|
||||
pw_log_error("plugin has no input or no output ports");
|
||||
res = -ENOTSUP;
|
||||
goto exit;
|
||||
}
|
||||
for (j = 0; j < impl->n_control; j++) {
|
||||
p = impl->control[j];
|
||||
impl->default_control[j] = get_default(impl, p);
|
||||
impl->control_data[j] = impl->default_control[j];
|
||||
pw_log_info("control %d ('%s') set to %f", j, d->PortNames[p], impl->control_data[j]);
|
||||
for (i = 0; i < desc->n_control; i++) {
|
||||
p = desc->control[i];
|
||||
desc->default_control[i] = get_default(impl, desc, p);
|
||||
pw_log_info("control %d ('%s') default to %f", i,
|
||||
d->PortNames[p], desc->default_control[i]);
|
||||
}
|
||||
spa_list_append(&hndl->descriptor_list, &desc->link);
|
||||
|
||||
return desc;
|
||||
|
||||
exit:
|
||||
if (hndl != NULL)
|
||||
ladspa_handle_unref(hndl);
|
||||
free(desc);
|
||||
errno = -res;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {
|
||||
* "Reverb tail" = 2.0
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
static int parse_control(struct node *node, struct spa_json *control)
|
||||
{
|
||||
struct spa_json it[1];
|
||||
char key[256];
|
||||
|
||||
if (spa_json_enter_object(control, &it[0]) <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) {
|
||||
float fl;
|
||||
if (spa_json_get_float(&it[0], &fl) <= 0)
|
||||
break;
|
||||
set_control_value(node, key, &fl);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* name = rev
|
||||
* plugin = g2reverb
|
||||
* label = G2reverb
|
||||
* control = [
|
||||
* ...
|
||||
* ]
|
||||
*/
|
||||
static int load_node(struct graph *graph, struct spa_json *json)
|
||||
{
|
||||
struct spa_json it[1];
|
||||
struct ladspa_descriptor *desc;
|
||||
struct node *node;
|
||||
const char *val;
|
||||
char key[256];
|
||||
char name[256] = "";
|
||||
char plugin[256] = "";
|
||||
char label[256] = "";
|
||||
bool have_control = false;
|
||||
uint32_t i;
|
||||
|
||||
while (spa_json_get_string(json, key, sizeof(key)) > 0) {
|
||||
if (strcmp("name", key) == 0) {
|
||||
if (spa_json_get_string(json, name, sizeof(name)) <= 0)
|
||||
return -EINVAL;
|
||||
} else if (strcmp("plugin", key) == 0) {
|
||||
if (spa_json_get_string(json, plugin, sizeof(plugin)) <= 0)
|
||||
return -EINVAL;
|
||||
} else if (strcmp("label", key) == 0) {
|
||||
if (spa_json_get_string(json, label, sizeof(label)) <= 0)
|
||||
return -EINVAL;
|
||||
} else if (strcmp("control", key) == 0) {
|
||||
if (spa_json_enter_array(json, &it[0]) <= 0)
|
||||
return -EINVAL;
|
||||
have_control = true;
|
||||
} else if (spa_json_next(json, &val) < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
pw_log_info("loading %s %s", plugin, label);
|
||||
|
||||
if ((desc = ladspa_descriptor_load(graph->impl, plugin, label)) == NULL)
|
||||
return -errno;
|
||||
|
||||
node = calloc(1, sizeof(*node));
|
||||
if (node == NULL)
|
||||
return -errno;
|
||||
|
||||
node->desc = desc;
|
||||
snprintf(node->name, sizeof(node->name), "%s", name);
|
||||
|
||||
for (i = 0; i < desc->n_control; i++)
|
||||
node->control_data[i] = desc->default_control[i];
|
||||
|
||||
if (have_control)
|
||||
parse_control(node, &it[0]);
|
||||
|
||||
spa_list_append(&graph->node_list, &node->link);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_graph(struct graph *graph)
|
||||
{
|
||||
struct impl *impl = graph->impl;
|
||||
struct node *node;
|
||||
uint32_t i, j, n_input, n_output, n_hndl;
|
||||
int res;
|
||||
unsigned long p;
|
||||
|
||||
graph->n_input = n_input = 0;
|
||||
graph->n_output = n_output = 0;
|
||||
graph->n_control = 0;
|
||||
|
||||
spa_list_for_each(node, &graph->node_list, link) {
|
||||
struct ladspa_descriptor *desc = node->desc;
|
||||
n_input += desc->n_input;
|
||||
n_output += desc->n_output;
|
||||
}
|
||||
|
||||
if (impl->capture_info.channels == 0)
|
||||
impl->capture_info.channels = impl->n_input;
|
||||
impl->capture_info.channels = n_input;
|
||||
if (impl->playback_info.channels == 0)
|
||||
impl->playback_info.channels = impl->n_output;
|
||||
impl->playback_info.channels = n_output;
|
||||
|
||||
impl->n_hndl = impl->capture_info.channels / impl->n_input;
|
||||
if (impl->n_hndl != impl->playback_info.channels / impl->n_output) {
|
||||
n_hndl = impl->capture_info.channels / n_input;
|
||||
if (n_hndl != impl->playback_info.channels / n_output) {
|
||||
pw_log_error("invalid channels");
|
||||
res = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
pw_log_info("using %d instances", impl->n_hndl);
|
||||
pw_log_info("using %d instances", n_hndl);
|
||||
|
||||
if ((control = pw_properties_get(props, "ladspa.control")) != NULL)
|
||||
parse_control(impl, control);
|
||||
spa_list_for_each(node, &graph->node_list, link) {
|
||||
struct ladspa_descriptor *desc = node->desc;
|
||||
const LADSPA_Descriptor *d = desc->desc;
|
||||
for (i = 0; i < n_hndl; i++) {
|
||||
if ((node->hndl[i] = d->instantiate(d, impl->rate)) == NULL) {
|
||||
pw_log_error("cannot create plugin instance");
|
||||
res = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
node->n_hndl = i;
|
||||
|
||||
for (i = 0; i < impl->n_hndl;i++) {
|
||||
if ((impl->hndl[i] = d->instantiate(d, impl->rate)) == NULL) {
|
||||
pw_log_error("cannot create plugin instance");
|
||||
res = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
for (j = 0; j < desc->n_input; j++) {
|
||||
p = desc->input[j];
|
||||
graph->in_desc[graph->n_input] = d;
|
||||
graph->in_hndl[graph->n_input] = node->hndl[i];
|
||||
graph->in_port[graph->n_input] = p;
|
||||
graph->n_input++;
|
||||
}
|
||||
for (j = 0; j < desc->n_output; j++) {
|
||||
p = desc->output[j];
|
||||
graph->out_desc[graph->n_output] = d;
|
||||
graph->out_hndl[graph->n_output] = node->hndl[i];
|
||||
graph->out_port[graph->n_output] = p;
|
||||
graph->n_output++;
|
||||
}
|
||||
|
||||
for (j = 0; j < impl->n_control; j++) {
|
||||
p = impl->control[j];
|
||||
d->connect_port(impl->hndl[i], p, &impl->control_data[j]);
|
||||
graph->hndl[graph->n_hndl] = node->hndl[i];
|
||||
graph->desc[graph->n_hndl] = d;
|
||||
graph->n_hndl++;
|
||||
|
||||
for (j = 0; j < desc->n_control; j++) {
|
||||
p = desc->control[j];
|
||||
d->connect_port(node->hndl[i], p, &node->control_data[j]);
|
||||
}
|
||||
for (j = 0; j < desc->n_notify; j++) {
|
||||
p = desc->notify[j];
|
||||
d->connect_port(node->hndl[i], p, &node->notify_data[j]);
|
||||
}
|
||||
if (d->activate)
|
||||
d->activate(node->hndl[i]);
|
||||
}
|
||||
for (j = 0; j < impl->n_notify; j++) {
|
||||
p = impl->notify[j];
|
||||
d->connect_port(impl->hndl[i], p, &impl->notify_data[j]);
|
||||
}
|
||||
if (d->activate)
|
||||
d->activate(impl->hndl[i]);
|
||||
}
|
||||
return 0;
|
||||
|
||||
exit:
|
||||
if (impl->handle != NULL)
|
||||
dlclose(impl->handle);
|
||||
impl->handle = NULL;
|
||||
for (i = 0; i < n_hndl; i++) {
|
||||
if (node->hndl[i] != NULL)
|
||||
node->desc->desc->cleanup(node->hndl[i]);
|
||||
node->hndl[i] = NULL;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* ladspa.graph = {
|
||||
* nodes = [
|
||||
* { ... } ...
|
||||
* ]
|
||||
* links = [
|
||||
* { ... } ...
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
static int load_graph(struct graph *graph, struct pw_properties *props)
|
||||
{
|
||||
struct spa_json it[4];
|
||||
const char *json, *val;
|
||||
char key[256];
|
||||
int res;
|
||||
|
||||
spa_list_init(&graph->node_list);
|
||||
spa_list_init(&graph->link_list);
|
||||
|
||||
if ((json = pw_properties_get(props, "ladspa.graph")) == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
spa_json_init(&it[0], json, strlen(json));
|
||||
if (spa_json_enter_object(&it[0], &it[1]) <= 0)
|
||||
spa_json_init(&it[1], json, strlen(json));
|
||||
|
||||
while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) {
|
||||
if (strcmp("nodes", key) == 0) {
|
||||
if (spa_json_enter_array(&it[1], &it[2]) <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
while (spa_json_enter_object(&it[2], &it[3]) > 0) {
|
||||
if ((res = load_node(graph, &it[3])) < 0)
|
||||
return res;
|
||||
}
|
||||
}
|
||||
else if (strcmp("links", key) == 0) {
|
||||
if (spa_json_enter_array(&it[1], &it[2]) <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
while (spa_json_enter_object(&it[2], &it[3]) > 0) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
} else if (spa_json_next(&it[1], &val) < 0)
|
||||
break;
|
||||
}
|
||||
return setup_graph(graph);;
|
||||
}
|
||||
|
||||
static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
|
||||
{
|
||||
struct impl *impl = data;
|
||||
|
|
@ -823,6 +1119,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
|||
impl->context = context;
|
||||
impl->work = pw_context_get_work_queue(context);
|
||||
impl->rate = 48000;
|
||||
impl->graph.impl = impl;
|
||||
spa_list_init(&impl->ladspa_handle_list);
|
||||
|
||||
|
||||
if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL)
|
||||
pw_properties_setf(props, PW_KEY_NODE_GROUP, "ladspa-filter-%u", id);
|
||||
|
|
@ -846,8 +1145,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
|||
parse_audio_info(impl->capture_props, &impl->capture_info);
|
||||
parse_audio_info(impl->playback_props, &impl->playback_info);
|
||||
|
||||
if ((res = load_ladspa(impl, props)) < 0) {
|
||||
pw_log_error("can't load ladspa: %s", spa_strerror(res));
|
||||
if ((res = load_graph(&impl->graph, props)) < 0) {
|
||||
pw_log_error("can't load graph: %s", spa_strerror(res));
|
||||
goto error;
|
||||
}
|
||||
copy_props(impl, props, "ladspa.unique-id");
|
||||
|
|
@ -856,11 +1155,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
|||
copy_props(impl, props, "ladspa.copyright");
|
||||
|
||||
if (pw_properties_get(impl->capture_props, PW_KEY_MEDIA_NAME) == NULL)
|
||||
pw_properties_setf(impl->capture_props, PW_KEY_MEDIA_NAME, "%s input",
|
||||
impl->desc->Name);
|
||||
pw_properties_setf(impl->capture_props, PW_KEY_MEDIA_NAME, "ladspa input %u",
|
||||
id);
|
||||
if (pw_properties_get(impl->playback_props, PW_KEY_MEDIA_NAME) == NULL)
|
||||
pw_properties_setf(impl->playback_props, PW_KEY_MEDIA_NAME, "%s output",
|
||||
impl->desc->Name);
|
||||
pw_properties_setf(impl->playback_props, PW_KEY_MEDIA_NAME, "ladspa output %u",
|
||||
id);
|
||||
|
||||
impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core);
|
||||
if (impl->core == NULL) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue