Softvol: Allow per process volume control.
diff mbox

Message ID 20150709032406.12952.5374@spalge.com
State New
Headers show

Commit Message

Jimmy July 9, 2015, 2:18 a.m. UTC
This change uses ephemeral pcm controls to provide per process volume
control in conjunction with dmix. It basically generates a new control
every time a softvol PCM is opened and deletes it afterwards. It also
needs to generate a unique name for the control and I have included one
method of doing that. This patch is totally non-breaking and backwards
compatible, apart from one call to snd_ctl_elem_unlock (see below).

Changes to asoundrc:

* pcm.softvol.per_process BOOL has been added to enable these changes,
  defaults to false.
* when per_process is true then bits of text in pcm.softvol.control.name
  surrounded by curly braces ('{' and '}') are treated as substitution
  variables (eg "softvol.{pid}") so that uniquely named controls can be
  generated.

Changes to pcm_softvol.c

* new int (really a bool) in snd_pcm_softvol_t that is read in
  _...open() and passed through to the internal open method because that
  seemed to be the thing to do and then used in softvol_load_control().
* a big ugly per_proc_format_ctl_name() function has been added that
  generates the new hopefully unique names. It talks to /proc/ and does
  a bunch string manipulation. There probably are no off-by-one errors
  left in there.
* per process controls are closed along with the pcm.
* I added a call to snd_ctl_elem_unlock() when a new control is created
  and *didn't* put a guard on it because I don't understand why anyone
  would want the control locked anyway ... It seems that the first time
  that softvol is opened you can't use the control so you have to close
  and open it again. Presumably there is a reason for this because
  unlock isn't called anywhere else, but I don't know what it is. Nothing
  else calls unlock either, maybe nothing else makes new on-demand ctls.

I'm open to suggestions like "get rid of the templating" and "use better
config variable names" but as far as I can tell the basic idea is useful
and simple.
---
 src/pcm/pcm_softvol.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 144 insertions(+), 4 deletions(-)

Comments

Takashi Iwai July 15, 2015, 11:03 a.m. UTC | #1
On Thu, 09 Jul 2015 04:18:48 +0200,
Jimmy wrote:
> 
> This change uses ephemeral pcm controls to provide per process volume
> control in conjunction with dmix. It basically generates a new control
> every time a softvol PCM is opened and deletes it afterwards. It also
> needs to generate a unique name for the control and I have included one
> method of doing that. This patch is totally non-breaking and backwards
> compatible, apart from one call to snd_ctl_elem_unlock (see below).
> 
> Changes to asoundrc:
> 
> * pcm.softvol.per_process BOOL has been added to enable these changes,
>   defaults to false.
> * when per_process is true then bits of text in pcm.softvol.control.name
>   surrounded by curly braces ('{' and '}') are treated as substitution
>   variables (eg "softvol.{pid}") so that uniquely named controls can be
>   generated.
> 
> Changes to pcm_softvol.c
> 
> * new int (really a bool) in snd_pcm_softvol_t that is read in
>   _...open() and passed through to the internal open method because that
>   seemed to be the thing to do and then used in softvol_load_control().
> * a big ugly per_proc_format_ctl_name() function has been added that
>   generates the new hopefully unique names. It talks to /proc/ and does
>   a bunch string manipulation. There probably are no off-by-one errors
>   left in there.
> * per process controls are closed along with the pcm.
> * I added a call to snd_ctl_elem_unlock() when a new control is created
>   and *didn't* put a guard on it because I don't understand why anyone
>   would want the control locked anyway ... It seems that the first time
>   that softvol is opened you can't use the control so you have to close
>   and open it again. Presumably there is a reason for this because
>   unlock isn't called anywhere else, but I don't know what it is. Nothing
>   else calls unlock either, maybe nothing else makes new on-demand ctls.
> 
> I'm open to suggestions like "get rid of the templating" and "use better
> config variable names" but as far as I can tell the basic idea is useful
> and simple.

Thanks for the patch.  The idea is pretty interesting, but I'm
concerned by the implementation details, particularly because the
number of user-space ctl elements is limited.  That is, we a user
starts looping a playback, the resource will exhaust immediately.

Also, removal of the ctl element is voluntarily done, that is, if a
program aborts in the middle, it leaves the ctl element.  If we want
the implementation with the user-space ctl element, it'd be better to
have a support in kernel side to manage the life cycle of ctl element
more safely.


Takashi

> ---
>  src/pcm/pcm_softvol.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 144 insertions(+), 4 deletions(-)
> 
> diff --git a/src/pcm/pcm_softvol.c b/src/pcm/pcm_softvol.c
> index c6cfd8896b26..a03b672a6f42 100644
> --- a/src/pcm/pcm_softvol.c
> +++ b/src/pcm/pcm_softvol.c
> @@ -31,6 +31,11 @@
>  #include "pcm_local.h"
>  #include "pcm_plugin.h"
>  
> +#include <sys/types.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <libgen.h> //basename
> +
>  #ifndef PIC
>  /* entry for static linking */
>  const char *_snd_module_pcm_softvol = "";
> @@ -51,6 +56,7 @@ typedef struct {
>  	double min_dB;
>  	double max_dB;
>  	unsigned int *dB_value;
> +	int per_process;
>  } snd_pcm_softvol_t;
>  
>  #define VOL_SCALE_SHIFT		16
> @@ -408,6 +414,17 @@ static void softvol_free(snd_pcm_softvol_t *svol)
>  static int snd_pcm_softvol_close(snd_pcm_t *pcm)
>  {
>  	snd_pcm_softvol_t *svol = pcm->private_data;
> +	snd_ctl_elem_info_t *cinfo;
> +
> +	if (svol->per_process) {
> +		snd_ctl_elem_info_alloca(&cinfo);
> +		snd_ctl_elem_info_set_id(cinfo, &svol->elem.id);
> +		// Ignore non-existence at this point.
> +		if (!snd_ctl_elem_info(svol->ctl, cinfo)) {
> +			snd_ctl_elem_remove(svol->ctl, &cinfo->id);
> +		}
> +	}
> +
>  	softvol_free(svol);
>  	return 0;
>  }
> @@ -689,6 +706,92 @@ static int add_user_ctl(snd_pcm_softvol_t *svol, snd_ctl_elem_info_t *cinfo, int
>  	return snd_ctl_elem_write(svol->ctl, &svol->elem);
>  }
>  
> +/* Takes a template string of the form "softvol {cmd} ({pid})" and writes to
> + * outbuf a the same string with the text within curly braces replaced with
> + * relevant information. Returns the number of chars written to outbuf
> + * including a terminating '\0' or a negative number on error.
> + *
> + * Currently supported values for substition are:
> + * pid  The current processes pid
> + * cmd  The basename of the processes argv[0], from /proc/self/cmdline
> + * exe  The basename of the processes binary, from following /proc/self/exe
> + * {    The literal open curly brace '{'
> + * ob   The literal open curly brace '{'
> + * cb   The literal open curly brace '}'
> + */
> +static int per_proc_format_ctl_name(char *outbuf, size_t outbuf_len, char *template) {
> +	int template_len = strlen(template), n, proc_fd;
> +	char *outbuf_p = outbuf, *open_b, *close_b, *template_p = template;
> +	char proc_buf[50]; // max pid can be up to 22 bits ...
> +	char resolved_path[PATH_MAX];
> +
> +	// walk through template_p pushing onto outbuf_p
> +	while (outbuf_p < outbuf+outbuf_len) {
> +		open_b = strchr(template_p, '{');
> +		if (open_b == NULL) {
> +			// Point the end of the current literal to the end
> +			open_b = template + template_len;
> +		}
> +		memcpy(outbuf_p, template_p, open_b-template_p);
> +		outbuf_p += open_b-template_p;
> +		template_p = open_b+1;
> +
> +		if (template_p >= template + template_len - 1) {
> +			*outbuf_p = '\0';
> +			break;
> +		}
> +
> +		if ((close_b = strchr(open_b, '}')) == NULL) {
> +			SNDERR("Invalid control name template: Unmatched '{'");
> +			return -EINVAL;
> +		}
> +		*close_b = '\0';
> +		if (strcmp(template_p, "pid") == 0) {
> +			n = snprintf(outbuf_p, outbuf+outbuf_len-outbuf_p, "%d", getpid());
> +		} else if (strcmp(template_p, "cmd") == 0) {
> +			n = snprintf(proc_buf, 49, "/proc/%d/cmdline", getpid());
> +			proc_fd = open(proc_buf, O_RDONLY);
> +			if (proc_fd < 0) {
> +				// Maybe a short lived process?
> +				n = errno;
> +				SNDERR("Unable to complete control name template: open: %s: %s", strerror(errno), proc_buf);
> +				return -n;
> +			}
> +			n = read(proc_fd, proc_buf, 49);
> +			proc_buf[n] = '\0';
> +			n = basename(proc_buf)-proc_buf;
> +			open_b = strchr(proc_buf+n, ' ');
> +			if (open_b != NULL) *open_b = '\0';
> +			n = snprintf(outbuf_p, outbuf+outbuf_len-outbuf_p, "%s", proc_buf+n);
> +		} else if (strcmp(template_p, "exe") == 0) {
> +			n = snprintf(proc_buf, 49, "/proc/%d/exe", getpid());
> +			if (realpath(proc_buf, resolved_path) == NULL) {
> +				// Maybe a short lived process?
> +				n = errno;
> +				SNDERR("Unable to complete control name template: open: %s: %s", strerror(errno), proc_buf);
> +				return -n;
> +			}
> +			n = basename(resolved_path)-resolved_path;
> +			n = snprintf(outbuf_p, outbuf+outbuf_len-outbuf_p, "%s", basename(resolved_path));
> +		} else if (strcmp(template_p, "{") == 0 || strcmp(template_p, "ob") == 0) {
> +			n = 1;
> +			*outbuf_p = '{';
> +		} else if (strcmp(template_p, "cb") == 0) {
> +			n = 1;
> +			*outbuf_p = '}';
> +		} else if (strlen(template_p) == 0) {
> +			n = 0;
> +		} else {
> +			SNDERR("Invalid control name template: Unknown substitution: %s", template_p);
> +			return -EINVAL;
> +		}
> +
> +		outbuf_p = outbuf_p + n;
> +		template_p = close_b + 1;
> +	}
> +	return strlen(outbuf)+1;
> +}
> +
>  /*
>   * load and set up user-control
>   * returns 0 if the user-control is found or created,
> @@ -700,7 +803,7 @@ static int softvol_load_control(snd_pcm_t *pcm, snd_pcm_softvol_t *svol,
>  				int cchannels, double min_dB, double max_dB,
>  				int resolution)
>  {
> -	char tmp_name[32];
> +	char tmp_name[32], tmp_template[sizeof(ctl_id->name)];
>  	snd_pcm_info_t *info;
>  	snd_ctl_elem_info_t *cinfo;
>  	int err;
> @@ -724,6 +827,15 @@ static int softvol_load_control(snd_pcm_t *pcm, snd_pcm_softvol_t *svol,
>  		return err;
>  	}
>  
> +	if (svol->per_process) {
> +		err = per_proc_format_ctl_name(tmp_template, sizeof(ctl_id->name), ctl_id->name);
> +		if (err < 1) { // Shouldn't be zero length output...
> +			SNDERR("Cannot open CTL %s: Control name template error %d", tmp_name, err);
> +			return -EINVAL;
> +		}
> +		memcpy(&ctl_id->name, tmp_template, sizeof(ctl_id->name));
> +	}
> +
>  	svol->elem.id = *ctl_id;
>  	svol->max_val = resolution - 1;
>  	svol->min_dB = min_dB;
> @@ -747,6 +859,7 @@ static int softvol_load_control(snd_pcm_t *pcm, snd_pcm_softvol_t *svol,
>  			SNDERR("Cannot add a control");
>  			return err;
>  		}
> +		snd_ctl_elem_unlock(svol->ctl, &cinfo->id);
>  	} else {
>  		if (! (cinfo->access & SNDRV_CTL_ELEM_ACCESS_USER)) {
>  			/* hardware control exists */
> @@ -836,6 +949,10 @@ static const snd_pcm_ops_t snd_pcm_softvol_ops = {
>   * \param resolution resolution of control
>   * \param slave Slave PCM handle
>   * \param close_slave When set, the slave PCM handle is closed with copy PCM
> + * \param per_process When set, a new ephemeral control is created for each
> + *        softvol PCM. This allows per process volume control when used with
> + *        dmix. Make sure the control name has eg {pid}, {cmd}, {exe} in it so
> + *        the generated names are unique.
>   * \retval zero on success otherwise a negative error code
>   * \warning Using of this function might be dangerous in the sense
>   *          of compatibility reasons. The prototype might be freely
> @@ -846,7 +963,7 @@ int snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
>  			 int ctl_card, snd_ctl_elem_id_t *ctl_id,
>  			 int cchannels,
>  			 double min_dB, double max_dB, int resolution,
> -			 snd_pcm_t *slave, int close_slave)
> +			 snd_pcm_t *slave, int close_slave, int per_process)
>  {
>  	snd_pcm_t *pcm;
>  	snd_pcm_softvol_t *svol;
> @@ -862,6 +979,7 @@ int snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
>  	svol = calloc(1, sizeof(*svol));
>  	if (! svol)
>  		return -ENOMEM;
> +	svol->per_process = per_process;
>  	err = softvol_load_control(slave, svol, ctl_card, ctl_id, cchannels,
>  				   min_dB, max_dB, resolution);
>  	if (err < 0) {
> @@ -929,6 +1047,17 @@ If the control already exists and it's a system control (i.e. no
>  user-defined control), the plugin simply passes its slave without
>  any changes.
>  
> +If per_process is set you can (and should) user certain substition variables
> +in the control name so that uniquely named controls can be generated per-process.
> +Substition variables are within curly braces, everything else is used literaly, eg
> +{cmd} ({pid}). Currently supported variables are:
> +- pid  The current processes pid
> +- cmd  The basename of the processes argv[0], from /proc/self/cmdline
> +- exe  The basename of the processes binary, from following /proc/self/exe
> +- {    The literal open curly brace '{'
> +- ob   The literal open curly brace '{'
> +- cb   The literal open curly brace '}'
> +
>  \code
>  pcm.name {
>          type softvol            # Soft Volume conversion PCM
> @@ -953,6 +1082,8 @@ pcm.name {
>  	[max_dB REAL]           # maximal dB value (default:   0.0)
>  	[resolution INT]        # resolution (default: 256)
>  				# resolution = 2 means a mute switch
> +	[per_process BOOL]      # generate one control per process (default: false)
> +				# useful with dmix.
>  }
>  \endcode
>  
> @@ -992,7 +1123,7 @@ int _snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
>  	int resolution = PRESET_RESOLUTION;
>  	double min_dB = PRESET_MIN_DB;
>  	double max_dB = ZERO_DB;
> -	int card = -1, cchannels = 2;
> +	int card = -1, cchannels = 2, per_process = 0;
>  
>  	snd_config_for_each(i, next, conf) {
>  		snd_config_t *n = snd_config_iterator_entry(i);
> @@ -1035,6 +1166,15 @@ int _snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
>  			}
>  			continue;
>  		}
> +		if (strcmp(id, "per_process") == 0) {
> +			err = snd_config_get_bool(n);
> +			if (err < 0) {
> +				SNDERR("Invalid boolean value in per_process");
> +				return err;
> +			}
> +			per_process = err;
> +			continue;
> +		}
>  		SNDERR("Unknown field %s", id);
>  		return -EINVAL;
>  	}
> @@ -1092,7 +1232,7 @@ int _snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
>  			return err;
>  		}
>  		err = snd_pcm_softvol_open(pcmp, name, sformat, card, ctl_id, cchannels,
> -					   min_dB, max_dB, resolution, spcm, 1);
> +					   min_dB, max_dB, resolution, spcm, 1, per_process);
>  		if (err < 0)
>  			snd_pcm_close(spcm);
>  	}
> -- 
> 2.1.4
> 
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
Takashi Sakamoto July 15, 2015, 3:46 p.m. UTC | #2
Hi,

On Jul 15 2015 20:03, Takashi Iwai wrote:
> Also, removal of the ctl element is voluntarily done, that is, if a
> program aborts in the middle, it leaves the ctl element.  If we want
> the implementation with the user-space ctl element, it'd be better to
> have a support in kernel side to manage the life cycle of ctl element
> more safely.

I'm interested in this idea about the life cycle of ctl elements added
by userspace applications. (Of cource, interested in per-process volume
control for dmix PCM plugins, itself. Sorry to start another discussion
in this thread...)

My headache is 'struct snd_kcontrol.owner'. In kernel land, this member
is used to distinguish whether the control element is locked or not,
against its name.

This implementation cannot allows control elements to keep PID which has
added the control element. If a control element has lost the PID, snd
module cannot release it when a process with the PID closes ALSA ctl
character device.

Well, if adding members for owner's PID and lock state, and keep enough
backward-compatibility to userspace, we can achieve the idea, I think.


Regards

Takashi Sakamoto

Patch
diff mbox

diff --git a/src/pcm/pcm_softvol.c b/src/pcm/pcm_softvol.c
index c6cfd8896b26..a03b672a6f42 100644
--- a/src/pcm/pcm_softvol.c
+++ b/src/pcm/pcm_softvol.c
@@ -31,6 +31,11 @@ 
 #include "pcm_local.h"
 #include "pcm_plugin.h"
 
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <libgen.h> //basename
+
 #ifndef PIC
 /* entry for static linking */
 const char *_snd_module_pcm_softvol = "";
@@ -51,6 +56,7 @@  typedef struct {
 	double min_dB;
 	double max_dB;
 	unsigned int *dB_value;
+	int per_process;
 } snd_pcm_softvol_t;
 
 #define VOL_SCALE_SHIFT		16
@@ -408,6 +414,17 @@  static void softvol_free(snd_pcm_softvol_t *svol)
 static int snd_pcm_softvol_close(snd_pcm_t *pcm)
 {
 	snd_pcm_softvol_t *svol = pcm->private_data;
+	snd_ctl_elem_info_t *cinfo;
+
+	if (svol->per_process) {
+		snd_ctl_elem_info_alloca(&cinfo);
+		snd_ctl_elem_info_set_id(cinfo, &svol->elem.id);
+		// Ignore non-existence at this point.
+		if (!snd_ctl_elem_info(svol->ctl, cinfo)) {
+			snd_ctl_elem_remove(svol->ctl, &cinfo->id);
+		}
+	}
+
 	softvol_free(svol);
 	return 0;
 }
@@ -689,6 +706,92 @@  static int add_user_ctl(snd_pcm_softvol_t *svol, snd_ctl_elem_info_t *cinfo, int
 	return snd_ctl_elem_write(svol->ctl, &svol->elem);
 }
 
+/* Takes a template string of the form "softvol {cmd} ({pid})" and writes to
+ * outbuf a the same string with the text within curly braces replaced with
+ * relevant information. Returns the number of chars written to outbuf
+ * including a terminating '\0' or a negative number on error.
+ *
+ * Currently supported values for substition are:
+ * pid  The current processes pid
+ * cmd  The basename of the processes argv[0], from /proc/self/cmdline
+ * exe  The basename of the processes binary, from following /proc/self/exe
+ * {    The literal open curly brace '{'
+ * ob   The literal open curly brace '{'
+ * cb   The literal open curly brace '}'
+ */
+static int per_proc_format_ctl_name(char *outbuf, size_t outbuf_len, char *template) {
+	int template_len = strlen(template), n, proc_fd;
+	char *outbuf_p = outbuf, *open_b, *close_b, *template_p = template;
+	char proc_buf[50]; // max pid can be up to 22 bits ...
+	char resolved_path[PATH_MAX];
+
+	// walk through template_p pushing onto outbuf_p
+	while (outbuf_p < outbuf+outbuf_len) {
+		open_b = strchr(template_p, '{');
+		if (open_b == NULL) {
+			// Point the end of the current literal to the end
+			open_b = template + template_len;
+		}
+		memcpy(outbuf_p, template_p, open_b-template_p);
+		outbuf_p += open_b-template_p;
+		template_p = open_b+1;
+
+		if (template_p >= template + template_len - 1) {
+			*outbuf_p = '\0';
+			break;
+		}
+
+		if ((close_b = strchr(open_b, '}')) == NULL) {
+			SNDERR("Invalid control name template: Unmatched '{'");
+			return -EINVAL;
+		}
+		*close_b = '\0';
+		if (strcmp(template_p, "pid") == 0) {
+			n = snprintf(outbuf_p, outbuf+outbuf_len-outbuf_p, "%d", getpid());
+		} else if (strcmp(template_p, "cmd") == 0) {
+			n = snprintf(proc_buf, 49, "/proc/%d/cmdline", getpid());
+			proc_fd = open(proc_buf, O_RDONLY);
+			if (proc_fd < 0) {
+				// Maybe a short lived process?
+				n = errno;
+				SNDERR("Unable to complete control name template: open: %s: %s", strerror(errno), proc_buf);
+				return -n;
+			}
+			n = read(proc_fd, proc_buf, 49);
+			proc_buf[n] = '\0';
+			n = basename(proc_buf)-proc_buf;
+			open_b = strchr(proc_buf+n, ' ');
+			if (open_b != NULL) *open_b = '\0';
+			n = snprintf(outbuf_p, outbuf+outbuf_len-outbuf_p, "%s", proc_buf+n);
+		} else if (strcmp(template_p, "exe") == 0) {
+			n = snprintf(proc_buf, 49, "/proc/%d/exe", getpid());
+			if (realpath(proc_buf, resolved_path) == NULL) {
+				// Maybe a short lived process?
+				n = errno;
+				SNDERR("Unable to complete control name template: open: %s: %s", strerror(errno), proc_buf);
+				return -n;
+			}
+			n = basename(resolved_path)-resolved_path;
+			n = snprintf(outbuf_p, outbuf+outbuf_len-outbuf_p, "%s", basename(resolved_path));
+		} else if (strcmp(template_p, "{") == 0 || strcmp(template_p, "ob") == 0) {
+			n = 1;
+			*outbuf_p = '{';
+		} else if (strcmp(template_p, "cb") == 0) {
+			n = 1;
+			*outbuf_p = '}';
+		} else if (strlen(template_p) == 0) {
+			n = 0;
+		} else {
+			SNDERR("Invalid control name template: Unknown substitution: %s", template_p);
+			return -EINVAL;
+		}
+
+		outbuf_p = outbuf_p + n;
+		template_p = close_b + 1;
+	}
+	return strlen(outbuf)+1;
+}
+
 /*
  * load and set up user-control
  * returns 0 if the user-control is found or created,
@@ -700,7 +803,7 @@  static int softvol_load_control(snd_pcm_t *pcm, snd_pcm_softvol_t *svol,
 				int cchannels, double min_dB, double max_dB,
 				int resolution)
 {
-	char tmp_name[32];
+	char tmp_name[32], tmp_template[sizeof(ctl_id->name)];
 	snd_pcm_info_t *info;
 	snd_ctl_elem_info_t *cinfo;
 	int err;
@@ -724,6 +827,15 @@  static int softvol_load_control(snd_pcm_t *pcm, snd_pcm_softvol_t *svol,
 		return err;
 	}
 
+	if (svol->per_process) {
+		err = per_proc_format_ctl_name(tmp_template, sizeof(ctl_id->name), ctl_id->name);
+		if (err < 1) { // Shouldn't be zero length output...
+			SNDERR("Cannot open CTL %s: Control name template error %d", tmp_name, err);
+			return -EINVAL;
+		}
+		memcpy(&ctl_id->name, tmp_template, sizeof(ctl_id->name));
+	}
+
 	svol->elem.id = *ctl_id;
 	svol->max_val = resolution - 1;
 	svol->min_dB = min_dB;
@@ -747,6 +859,7 @@  static int softvol_load_control(snd_pcm_t *pcm, snd_pcm_softvol_t *svol,
 			SNDERR("Cannot add a control");
 			return err;
 		}
+		snd_ctl_elem_unlock(svol->ctl, &cinfo->id);
 	} else {
 		if (! (cinfo->access & SNDRV_CTL_ELEM_ACCESS_USER)) {
 			/* hardware control exists */
@@ -836,6 +949,10 @@  static const snd_pcm_ops_t snd_pcm_softvol_ops = {
  * \param resolution resolution of control
  * \param slave Slave PCM handle
  * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \param per_process When set, a new ephemeral control is created for each
+ *        softvol PCM. This allows per process volume control when used with
+ *        dmix. Make sure the control name has eg {pid}, {cmd}, {exe} in it so
+ *        the generated names are unique.
  * \retval zero on success otherwise a negative error code
  * \warning Using of this function might be dangerous in the sense
  *          of compatibility reasons. The prototype might be freely
@@ -846,7 +963,7 @@  int snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
 			 int ctl_card, snd_ctl_elem_id_t *ctl_id,
 			 int cchannels,
 			 double min_dB, double max_dB, int resolution,
-			 snd_pcm_t *slave, int close_slave)
+			 snd_pcm_t *slave, int close_slave, int per_process)
 {
 	snd_pcm_t *pcm;
 	snd_pcm_softvol_t *svol;
@@ -862,6 +979,7 @@  int snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
 	svol = calloc(1, sizeof(*svol));
 	if (! svol)
 		return -ENOMEM;
+	svol->per_process = per_process;
 	err = softvol_load_control(slave, svol, ctl_card, ctl_id, cchannels,
 				   min_dB, max_dB, resolution);
 	if (err < 0) {
@@ -929,6 +1047,17 @@  If the control already exists and it's a system control (i.e. no
 user-defined control), the plugin simply passes its slave without
 any changes.
 
+If per_process is set you can (and should) user certain substition variables
+in the control name so that uniquely named controls can be generated per-process.
+Substition variables are within curly braces, everything else is used literaly, eg
+{cmd} ({pid}). Currently supported variables are:
+- pid  The current processes pid
+- cmd  The basename of the processes argv[0], from /proc/self/cmdline
+- exe  The basename of the processes binary, from following /proc/self/exe
+- {    The literal open curly brace '{'
+- ob   The literal open curly brace '{'
+- cb   The literal open curly brace '}'
+
 \code
 pcm.name {
         type softvol            # Soft Volume conversion PCM
@@ -953,6 +1082,8 @@  pcm.name {
 	[max_dB REAL]           # maximal dB value (default:   0.0)
 	[resolution INT]        # resolution (default: 256)
 				# resolution = 2 means a mute switch
+	[per_process BOOL]      # generate one control per process (default: false)
+				# useful with dmix.
 }
 \endcode
 
@@ -992,7 +1123,7 @@  int _snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
 	int resolution = PRESET_RESOLUTION;
 	double min_dB = PRESET_MIN_DB;
 	double max_dB = ZERO_DB;
-	int card = -1, cchannels = 2;
+	int card = -1, cchannels = 2, per_process = 0;
 
 	snd_config_for_each(i, next, conf) {
 		snd_config_t *n = snd_config_iterator_entry(i);
@@ -1035,6 +1166,15 @@  int _snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
 			}
 			continue;
 		}
+		if (strcmp(id, "per_process") == 0) {
+			err = snd_config_get_bool(n);
+			if (err < 0) {
+				SNDERR("Invalid boolean value in per_process");
+				return err;
+			}
+			per_process = err;
+			continue;
+		}
 		SNDERR("Unknown field %s", id);
 		return -EINVAL;
 	}
@@ -1092,7 +1232,7 @@  int _snd_pcm_softvol_open(snd_pcm_t **pcmp, const char *name,
 			return err;
 		}
 		err = snd_pcm_softvol_open(pcmp, name, sformat, card, ctl_id, cchannels,
-					   min_dB, max_dB, resolution, spcm, 1);
+					   min_dB, max_dB, resolution, spcm, 1, per_process);
 		if (err < 0)
 			snd_pcm_close(spcm);
 	}