[PATCH] Add support to expose controls of ladspa plugin
Camel Guo
camel.guo at axis.com
Mon Feb 1 16:03:52 CET 2021
From: Camel Guo <camelg at 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 at 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
--
2.20.1
More information about the Alsa-devel
mailing list