[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