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; }
} else { if (! (cinfo->access & SNDRV_CTL_ELEM_ACCESS_USER)) { /* hardware control exists */snd_ctl_elem_unlock(svol->ctl, &cinfo->id);
@@ -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);
if (err < 0) snd_pcm_close(spcm); }min_dB, max_dB, resolution, spcm, 1, per_process);
-- 2.1.4
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel