[alsa-devel] [PATCH] Softvol: Allow per process volume control.

Takashi Iwai tiwai at suse.de
Wed Jul 15 13:03:15 CEST 2015


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 at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
> 


More information about the Alsa-devel mailing list