[PATCH] Add support to expose controls of ladspa plugin
From: Camel Guo camelg@axis.com
In order for external software components to adjust ladspa plugin dynamically, this commit adds an option to exposes the control array of input control ports of a ladspa plugin to a file, through which any applications with proper permission can control this plugin.
Signed-off-by: Camel Guo camelg@axis.com --- src/pcm/pcm_ladspa.c | 157 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 139 insertions(+), 18 deletions(-)
diff --git a/src/pcm/pcm_ladspa.c b/src/pcm/pcm_ladspa.c index ad73347d..40b5d38f 100644 --- a/src/pcm/pcm_ladspa.c +++ b/src/pcm/pcm_ladspa.c @@ -32,6 +32,9 @@ * http://www.medianet.ag */
+#include <stdbool.h> +#include <sys/mman.h> +#include <sys/stat.h> #include <dirent.h> #include <locale.h> #include <math.h> @@ -93,7 +96,8 @@ typedef struct { unsigned int port_bindings_size; /* size of array */ unsigned int *port_bindings; /* index = channel number, value = LADSPA port */ unsigned int controls_size; /* size of array */ - unsigned char *controls_initialized; /* initialized by ALSA user */ + bool controls_new; /* if controls is new, it can be overrided by alsa config */ + bool controls_shared; /* if controls is shared memory map */ LADSPA_Data *controls; /* index = LADSPA control port index */ } snd_pcm_ladspa_plugin_io_t;
@@ -101,6 +105,7 @@ typedef struct { struct list_head list; snd_pcm_ladspa_policy_t policy; char *filename; + char *controls_path; void *dl_handle; const LADSPA_Descriptor *desc; snd_pcm_ladspa_plugin_io_t input; @@ -110,6 +115,73 @@ typedef struct {
#endif /* DOC_HIDDEN */
+static LADSPA_Data* mmap_ladspa_controls(const char* filename, unsigned long length, bool *is_new) +{ + LADSPA_Data *ptr = NULL; + int fd = -1; + int ret = 0; + struct stat statbuf = { 0 }; + int prot = PROT_READ; + + if (filename == NULL || is_new == NULL) return NULL; + + *is_new = false; + + fd = open(filename, O_RDONLY); + if(fd < 0) { + if (errno == ENOENT) { + fd = open(filename, O_RDWR | O_CREAT, 0666); + if(fd < 0) { + SNDERR("Failed to open controls file: %s", filename); + return NULL; + } + + prot |= PROT_WRITE; + *is_new = true; + } else { + SNDERR("Failed to open %s due to '%s'", filename, strerror(errno)); + return NULL; + } + } + + ret = fstat(fd, &statbuf); + if (ret == -1) { + SNDERR("Failed to get status of '%s' due to '%s'", filename, strerror(errno)); + goto out; + } + + if (statbuf.st_size < (off_t) length) { + /* this file is invalid, must be truncated and re-initialized later on */ + close(fd); + + fd = open(filename, O_RDWR | O_CREAT, 0666); + if(fd < 0) { + SNDERR("Failed to open controls file: %s", filename); + /* no need to close(fd), so just return */ + return NULL; + } + + ret = ftruncate(fd, length); + if (ret == -1) { + SNDERR("Failed to increase the size of '%s' due to '%s'", filename, strerror(errno)); + goto out; + } + + prot |= PROT_WRITE; + *is_new = true; + } + + ptr = (LADSPA_Data*) mmap(NULL, length, prot, MAP_SHARED, fd, 0); + if(ptr == MAP_FAILED) { + SNDERR("Failed to mmap %s due to %s", filename, strerror(errno)); + ptr = NULL; + } + +out: + close (fd); + return ptr; +} + static unsigned int snd_pcm_ladspa_count_ports(snd_pcm_ladspa_plugin_t *lplug, LADSPA_PortDescriptor pdesc) { @@ -174,8 +246,11 @@ static int snd_pcm_ladspa_find_port_idx(unsigned int *res,
static void snd_pcm_ladspa_free_io(snd_pcm_ladspa_plugin_io_t *io) { - free(io->controls); - free(io->controls_initialized); + if (io->controls_shared) { + munmap(io->controls, io->controls_size * sizeof(LADSPA_Data)); + } else { + free(io->controls); + } }
static void snd_pcm_ladspa_free_plugins(struct list_head *plugins) @@ -186,6 +261,8 @@ static void snd_pcm_ladspa_free_plugins(struct list_head *plugins) snd_pcm_ladspa_free_io(&plugin->output); if (plugin->dl_handle) dlclose(plugin->dl_handle); + if (plugin->controls_path) + free(plugin->controls_path); free(plugin->filename); list_del(&plugin->list); free(plugin); @@ -574,8 +651,6 @@ static int snd_pcm_ladspa_connect_controls(snd_pcm_ladspa_plugin_t *plugin, for (idx = midx = 0; idx < plugin->desc->PortCount; idx++) if ((plugin->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_CONTROL)) == (io->pdesc | LADSPA_PORT_CONTROL)) { if (io->controls_size > midx) { - if (!io->controls_initialized[midx]) - snd_pcm_ladspa_get_default_cvalue(plugin->desc, idx, &io->controls[midx]); plugin->desc->connect_port(instance->handle, idx, &io->controls[midx]); } else { return -EINVAL; @@ -878,6 +953,10 @@ snd_pcm_ladspa_write_areas(snd_pcm_t *pcm, snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); list_for_each(pos1, &plugin->instances) { instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list); + if (plugin->input.controls_shared) { + (void) snd_pcm_ladspa_connect_controls(plugin, &plugin->input, instance); + } + /* Skip output controls since they can not be changed dynamically */ for (idx = 0; idx < instance->input.channels.size; idx++) { chn = instance->input.channels.array[idx]; data = instance->input.data[idx]; @@ -939,6 +1018,10 @@ snd_pcm_ladspa_read_areas(snd_pcm_t *pcm, snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); list_for_each(pos1, &plugin->instances) { instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list); + if (plugin->input.controls_shared) { + (void) snd_pcm_ladspa_connect_controls(plugin, &plugin->input, instance); + } + /* Skip output controls since they can not be changed dynamically */ for (idx = 0; idx < instance->input.channels.size; idx++) { chn = instance->input.channels.array[idx]; data = instance->input.data[idx]; @@ -1227,26 +1310,50 @@ static int snd_pcm_ladspa_add_default_controls(snd_pcm_ladspa_plugin_t *lplug, { unsigned int count = 0; LADSPA_Data *array; - unsigned char *initialized; unsigned long idx; + unsigned long midx; + bool controls_shared = false; + bool is_new = true;
for (idx = 0; idx < lplug->desc->PortCount; idx++) if ((lplug->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_CONTROL)) == (io->pdesc | LADSPA_PORT_CONTROL)) count++; - array = (LADSPA_Data *)calloc(count, sizeof(LADSPA_Data)); - if (!array) - return -ENOMEM; - initialized = (unsigned char *)calloc(count, sizeof(unsigned char)); - if (!initialized) { - free(array); - return -ENOMEM; + + /* + * Only support to expose ladspa control array for input control ports + */ + if ((io->pdesc == LADSPA_PORT_INPUT) && lplug->controls_path && count != 0) { + array = mmap_ladspa_controls(lplug->controls_path, count * sizeof(LADSPA_Data), &is_new); + if (!array) + return -ENOMEM; + controls_shared = true; + } else { + array = (LADSPA_Data *)calloc(count, sizeof(LADSPA_Data)); + if (!array) + return -ENOMEM; + } + + if (is_new) { + /* + * Initialize array of controls of this ladspa plugin with its + * default values defined in this plugin implementation. This + * array can be overrided by its alsa configuration files. + */ + for (idx = midx = 0; idx < lplug->desc->PortCount; idx++) { + if ((lplug->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_CONTROL)) == (io->pdesc | LADSPA_PORT_CONTROL)) { + snd_pcm_ladspa_get_default_cvalue(lplug->desc, idx, &array[midx]); + midx++; + } + } } + io->controls_size = count; - io->controls_initialized = initialized; + io->controls_new = is_new; + io->controls_shared = controls_shared; io->controls = array;
return 0; -} +}
static int snd_pcm_ladspa_parse_controls(snd_pcm_ladspa_plugin_t *lplug, snd_pcm_ladspa_plugin_io_t *io, @@ -1287,8 +1394,10 @@ static int snd_pcm_ladspa_parse_controls(snd_pcm_ladspa_plugin_t *lplug, SNDERR("internal error"); return err; } - io->controls_initialized[uval] = 1; - io->controls[uval] = (LADSPA_Data)dval; + + if (io->controls_new) { + io->controls[uval] = (LADSPA_Data)dval; + } }
return 0; @@ -1429,7 +1538,7 @@ static int snd_pcm_ladspa_add_plugin(struct list_head *list, int reverse) { snd_config_iterator_t i, next; - const char *label = NULL, *filename = NULL; + const char *label = NULL, *filename = NULL, *controls_path = NULL; long ladspa_id = 0; int err; snd_pcm_ladspa_plugin_t *lplug; @@ -1467,6 +1576,12 @@ static int snd_pcm_ladspa_add_plugin(struct list_head *list, output = n; continue; } + if (strcmp(id, "controls") == 0) { + err = snd_config_get_string(n, &controls_path); + if (err < 0) + return err; + continue; + } if (strcmp(id, "policy") == 0) { const char *str; err = snd_config_get_string(n, &str); @@ -1496,6 +1611,11 @@ static int snd_pcm_ladspa_add_plugin(struct list_head *list, lplug->input.pdesc = LADSPA_PORT_INPUT; lplug->output.pdesc = LADSPA_PORT_OUTPUT; INIT_LIST_HEAD(&lplug->instances); + if (controls_path) { + lplug->controls_path = strdup(controls_path); + } else { + lplug->controls_path = NULL; + } if (filename) { err = snd_pcm_ladspa_check_file(lplug, filename, label, ladspa_id); if (err < 0) { @@ -1692,6 +1812,7 @@ pcm.name { [label STR] # LADSPA plugin label (for example 'delay_5s') [filename STR] # Full filename of .so library with LADSPA plugin code [policy STR] # Policy can be 'none' or 'duplicate' + [controls STR] # Path (directory) with controls for this plugin input | output { bindings { C INT or STR # C - channel, INT - audio port index, STR - audio port name
Dne 01. 02. 21 v 16:03 Camel Guo napsal(a):
From: Camel Guo camelg@axis.com
In order for external software components to adjust ladspa plugin dynamically, this commit adds an option to exposes the control array of input control ports of a ladspa plugin to a file, through which any applications with proper permission can control this plugin.
It looks like a pure hack (although the implementation is interesting). The controls may be exposed via the ctl (control) API like we do in src/pcm/pcm_softvol.c for example. The floats can be mapped to integer64 or we may discuss to add the float type to the control API elements.
Jaroslav
On 2/1/21 4:32 PM, Jaroslav Kysela wrote:
Dne 01. 02. 21 v 16:03 Camel Guo napsal(a):
From: Camel Guo camelg@axis.com
In order for external software components to adjust ladspa plugin dynamically, this commit adds an option to exposes the control array of input control ports of a ladspa plugin to a file, through which any applications with proper permission can control this plugin.
It looks like a pure hack (although the implementation is interesting). The controls may be exposed via the ctl (control) API like we do in src/pcm/pcm_softvol.c for example. The floats can be mapped to integer64 or we may discuss to add the float type to the control API elements.
If there are not so many input controls of a ladspa plugin, I think it is okay to implement it like pcm_softvol.c. But the problem is that some plugins might have more than 100 input controls. For every input control, there will be a system call ioctl in order to get its value. This will make performance really bad. If a ladspa plugin like this needs to support per-channel control, that will make it even worst.
But with shared memory like this, it will become a pure memory read, this will make performance acceptable.
Another benefit of exposing ladspa control array to shared memory is that it makes it possible for any algorithms to connect with these ladspa plugins. The ladspa control array is basically a float-array, which is exactly the input, output of lots of machine learning, deep learning algorithm. Imagine an algorithm listening to the audio stream automatically applies privacy masks on audio stream to mask human voice via ladspa plugins.
Jaroslav
-- Jaroslav Kysela perex@perex.cz Linux Sound Maintainer; ALSA Project; Red Hat, Inc.
participants (3)
-
Camel Guo
-
Camel Guo
-
Jaroslav Kysela