[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