Discussion:
[PATCH] Per Application Volume Control plugin for ALSA
James Huk
2010-09-29 16:56:59 UTC
Permalink
Hello everybody.

This is my first "post" here, so please be "gentle".

I have created PAVC plugin for ALSA, it is based on COPY plugin, and
use SOFTVOL plugin for sound output. As far as I can tell from current
tests - It works perfectly, and it doesn't break compatibility with
any apps I use (and PulseAudio does). So I was wondering - is there
any chance this plugin would be added to the next official ALSA
release?

Here is the patch that enables it on current alsa-lib-1.0.23
(hopefully this mailing list is a good place to "post" this):


--- a/configure.in 2010-09-23 17:03:00.000000000 +0200
+++ b/configure.in 2010-09-23 17:09:11.029900400 +0200
@@ -445,7 +445,7 @@
pcm_plugins=""
fi

-PCM_PLUGIN_LIST="copy linear route mulaw alaw adpcm rate plug multi
shm file null empty share meter hooks lfloat ladspa dmix dshare dsnoop
asym iec958 softvol extplug ioplug mmap_emul"
+PCM_PLUGIN_LIST="copy linear route mulaw alaw adpcm rate plug multi
shm file null empty share meter hooks lfloat ladspa dmix dshare dsnoop
asym iec958 softvol extplug ioplug mmap_emul pavc"

build_pcm_plugin="no"
for t in $PCM_PLUGIN_LIST; do
@@ -516,6 +516,7 @@
AM_CONDITIONAL(BUILD_PCM_PLUGIN_EXTPLUG, test x$build_pcm_extplug = xyes)
AM_CONDITIONAL(BUILD_PCM_PLUGIN_IOPLUG, test x$build_pcm_ioplug = xyes)
AM_CONDITIONAL(BUILD_PCM_PLUGIN_MMAP_EMUL, test x$build_pcm_mmap_emul = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_PAVC, test x$build_pcm_pavc = xyes)

dnl Defines for plug plugin
if test "$build_pcm_rate" = "yes"; then

--- a/src/pcm/Makefile.am 2010-04-16 13:11:05.000000000 +0200
+++ b/src/pcm/Makefile.am 2010-09-23 17:20:10.601899695 +0200
@@ -102,6 +102,9 @@
if BUILD_PCM_PLUGIN_MMAP_EMUL
libpcm_la_SOURCES += pcm_mmap_emul.c
endif
+if BUILD_PCM_PLUGIN_PAVC
+libpcm_la_SOURCES += pcm_pavc.c
+endif

EXTRA_DIST = pcm_dmix_i386.c pcm_dmix_x86_64.c pcm_dmix_generic.c



--- a/src/pcm/pcm.c 2010-04-16 13:11:05.000000000 +0200
+++ b/src/pcm/pcm.c 2010-09-23 17:10:43.813900406 +0200
@@ -2047,7 +2047,7 @@
static const char *const build_in_pcms[] = {
"adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat",
"linear", "meter", "mulaw", "multi", "null", "empty", "plug",
"rate", "route", "share",
- "shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",
+ "shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul","pavc",
NULL
};

--- a/include/pcm.h 2010-04-16 13:11:05.000000000 +0200
+++ b/include/pcm.h 2010-09-23 17:11:58.389899158 +0200
@@ -376,7 +376,9 @@
SND_PCM_TYPE_EXTPLUG,
/** Mmap-emulation plugin */
SND_PCM_TYPE_MMAP_EMUL,
- SND_PCM_TYPE_LAST = SND_PCM_TYPE_MMAP_EMUL
+ /** PAVC plugin */
+ SND_PCM_TYPE_PAVC,
+ SND_PCM_TYPE_LAST = SND_PCM_TYPE_PAVC
};

/** PCM type */

--- a/src/pcm/pcm_pavc.c
+++ b/src/pcm/pcm_pavc.c
@@ -0,0 +1,846 @@
+/*
+ * PCM - PAVC plugin
+ * Based on PCM - Copy conversion plugin by Abramo Bagnara
<***@alsa-project.org>
+ *
+ * Copyright (c) 2010 by Adrian KobyliƄski <***@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_pavc = "";
+#endif
+
+#ifndef DOC_HIDDEN
+typedef struct {
+ /* This field need to be the first */
+ snd_pcm_plugin_t plug;
+} snd_pcm_pavc_t;
+#endif
+
+static int snd_pcm_pavc_hw_refine_cprepare(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+ int err;
+ snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+ err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+ &access_mask);
+ if (err < 0)
+ return err;
+ params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine_sprepare(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *sparams)
+{
+ snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+ _snd_pcm_hw_params_any(sparams);
+ _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+ &saccess_mask);
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine_schange(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_params_t *sparams)
+{
+ int err;
+ unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+ err = _snd_pcm_hw_params_refine(sparams, links, params);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine_cchange(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_params_t *sparams)
+{
+ int err;
+ unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+ err = _snd_pcm_hw_params_refine(params, links, sparams);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+ return snd_pcm_hw_refine_slave(pcm, params,
+ snd_pcm_pavc_hw_refine_cprepare,
+ snd_pcm_pavc_hw_refine_cchange,
+ snd_pcm_pavc_hw_refine_sprepare,
+ snd_pcm_pavc_hw_refine_schange,
+ snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_pavc_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+ return snd_pcm_hw_params_slave(pcm, params,
+ snd_pcm_pavc_hw_refine_cchange,
+ snd_pcm_pavc_hw_refine_sprepare,
+ snd_pcm_pavc_hw_refine_schange,
+ snd_pcm_generic_hw_params);
+}
+
+static snd_pcm_uframes_t
+ snd_pcm_pavc_write_areas(snd_pcm_t *pcm,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size,
+ const snd_pcm_channel_area_t *slave_areas,
+ snd_pcm_uframes_t slave_offset,
+ snd_pcm_uframes_t *slave_sizep)
+{
+ if (size > *slave_sizep)
+ size = *slave_sizep;
+ snd_pcm_areas_copy(slave_areas, slave_offset,
+ areas, offset,
+ pcm->channels, size, pcm->format);
+ *slave_sizep = size;
+ return size;
+}
+
+static snd_pcm_uframes_t
+ snd_pcm_pavc_read_areas(snd_pcm_t *pcm,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size,
+ const snd_pcm_channel_area_t *slave_areas,
+ snd_pcm_uframes_t slave_offset,
+ snd_pcm_uframes_t *slave_sizep)
+{
+ if (size > *slave_sizep)
+ size = *slave_sizep;
+ snd_pcm_areas_copy(areas, offset,
+ slave_areas, slave_offset,
+ pcm->channels, size, pcm->format);
+ *slave_sizep = size;
+ return size;
+}
+
+static void snd_pcm_pavc_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+ snd_pcm_pavc_t *pavc = pcm->private_data;
+ snd_output_printf(out, "Copy conversion PCM\n");
+ if (pcm->setup) {
+ snd_output_printf(out, "Its setup is:\n");
+ snd_pcm_dump_setup(pcm, out);
+ }
+ snd_output_printf(out, "Slave: ");
+ snd_pcm_dump(pavc->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_pavc_ops = {
+ .close = snd_pcm_generic_close,
+ .info = snd_pcm_generic_info,
+ .hw_refine = snd_pcm_pavc_hw_refine,
+ .hw_params = snd_pcm_pavc_hw_params,
+ .hw_free = snd_pcm_generic_hw_free,
+ .sw_params = snd_pcm_generic_sw_params,
+ .channel_info = snd_pcm_generic_channel_info,
+ .dump = snd_pcm_pavc_dump,
+ .nonblock = snd_pcm_generic_nonblock,
+ .async = snd_pcm_generic_async,
+ .mmap = snd_pcm_generic_mmap,
+ .munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new copy PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \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
+ * changed in future.
+ */
+int snd_pcm_pavc_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t
*slave, int close_slave)
+{
+ snd_pcm_t *pcm;
+ snd_pcm_pavc_t *pavc;
+ int err;
+ assert(pcmp && slave);
+ pavc = calloc(1, sizeof(snd_pcm_pavc_t));
+ if (!pavc) {
+ return -ENOMEM;
+ }
+ snd_pcm_plugin_init(&pavc->plug);
+ pavc->plug.read = snd_pcm_pavc_read_areas;
+ pavc->plug.write = snd_pcm_pavc_write_areas;
+ pavc->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+ pavc->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+ pavc->plug.gen.slave = slave;
+ pavc->plug.gen.close_slave = close_slave;
+
+ err = snd_pcm_new(&pcm, SND_PCM_TYPE_PAVC, name, slave->stream,
slave->mode);
+ if (err < 0) {
+ free(pavc);
+ return err;
+ }
+ pcm->ops = &snd_pcm_pavc_ops;
+ pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+ pcm->private_data = pavc;
+ pcm->poll_fd = slave->poll_fd;
+ pcm->poll_events = slave->poll_events;
+ pcm->monotonic = slave->monotonic;
+ snd_pcm_set_hw_ptr(pcm, &pavc->plug.hw_ptr, -1, 0);
+ snd_pcm_set_appl_ptr(pcm, &pavc->plug.appl_ptr, -1, 0);
+ *pcmp = pcm;
+
+ return 0;
+}
+
+int countString(char * x,char x2,int count)
+{
+ int i;
+ int z=0;
+ for(i=0;i<count;++i)
+ {
+ if(x[i]==x2)
+ {
+ ++z;
+ }
+ }
+ return z;
+}
+
+char *deleteChar(char *x, char x2, int count)
+{
+ char *ret;
+ ret=(char*) malloc(count+1);
+ int i;
+ for(i=0;i<count;++i)
+ {
+ ret[i]=0;
+ }
+ for(i=0;i<count;++i)
+ {
+ if(x[i]!=x2)
+ {
+ ret[i]=x[i];
+ }
+ else
+ {
+ ret[i]=' ';
+ }
+ }
+ return ret;
+}
+
+char *getProceses()
+{
+ FILE *pipe;
+ if ( !(pipe = (FILE*)popen("lsof -F p /dev/snd/pcmC0D0p","r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ return NULL;
+ }
+
+ int i=0,j=0;
+ int c;
+
+ do {
+ c = fgetc (pipe);
+ ++i;
+ } while (c != EOF);
+ fclose (pipe);
+
+ char *buf=(char*)malloc(sizeof(char)*i);
+
+ for(j=0;j<i;++j)
+ {
+ buf[j]=0;
+ }
+
+ i=0;
+
+ if ( !(pipe = (FILE*)popen("lsof -F p /dev/snd/pcmC0D0p","r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ return NULL;
+ }
+
+ do {
+ c = fgetc (pipe);
+ if(c!=EOF && c!='p' && c!='\n')
+ {
+ buf[i]=c;
+ }
+ else if(c=='p')
+ {
+ buf[i]=' ';
+ }
+ else if(c=='\n')
+ {
+ buf[i]=',';
+ }
+ ++i;
+ } while (c != EOF);
+ fclose (pipe);
+
+ return buf;
+}
+
+char *loadPid(char *name)
+{
+ FILE *file;
+ int c,i=0,j=0;
+
+ file=fopen(name,"r");
+ if(file!=NULL)
+ {
+ do{
+ c=fgetc(file);
+ ++i;
+ }while(c!=EOF);
+ fclose(file);
+
+ char *buf=malloc((sizeof(char))*i);
+ for(j=0;j<i;++j)
+ {
+ buf[j]=0;
+ }
+
+ i=0;
+
+ file=fopen(name,"r");
+ if(file!=NULL)
+ {
+ do{
+ c=fgetc(file);
+ if(c!=EOF && c!='\n')
+ buf[i]=c;
+ ++i;
+ }while(c!=EOF);
+ fclose(file);
+
+ return buf;
+ }
+ else
+ {
+ return NULL;
+ }
+
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+void savePid(char *pid,char *file)
+{
+ int i;
+ char command3[256];
+
+ for(i=0;i<256;++i)
+ {
+ command3[i]=0;
+ }
+
+ strcat(command3,"echo ");
+ strcat(command3,pid);
+ strcat(command3," > ");
+ strcat(command3,file);
+
+ for(i=5;i<256;++i)
+ {
+ if(command3[i]!=EOF && command3[i]!='\n' && command3[i]!='*')
+ {
+ command3[i]=command3[i];
+ }
+ else
+ {
+ command3[i]=' ';
+ }
+ }
+
+ system(command3);
+}
+
+int checkDir(char *name)
+{
+ char command[128];
+ memset(command,0,128);
+
+ strcat(command,"ls -aF ");
+ strcat(command,getenv("HOME"));
+ strcat(command," | grep \\./$ | grep -w ");
+ strcat(command,name);
+
+ FILE *pipe;
+ if ( !(pipe = (FILE*)popen(command,"r")) )
+ {
+ perror("Problems with pipe");
+ return -1;
+ }
+
+ int i=0;
+ int c;
+
+ do {
+ c = fgetc (pipe);
+ if(c!=EOF && c!='\n')
+ ++i;
+ } while (c != EOF);
+ fclose (pipe);
+
+ if(i>0)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+int createDir(char *name)
+{
+ pid_t PID;
+ int status;
+
+ PID=fork();
+
+ if(PID>0)
+ {
+ char command[128];
+ memset(command,0,128);
+
+ strcat(command,getenv("HOME"));
+ strcat(command,"/");
+ strcat(command,".softvol");
+
+ execlp("mkdir","mkdir",command,NULL);
+ }
+ else
+ {
+ pid_t wpid = waitpid(PID, &status, WUNTRACED);
+ }
+
+ return WEXITSTATUS(status);
+}
+
+char *getDir(char *name)
+{
+ static char command[128];
+ memset(command,0,128);
+
+ strcat(command,getenv("HOME"));
+ strcat(command,"/");
+ strcat(command,name);
+
+ return command;
+}
+
+char *getSoftvolNumber(int channelCount,int current)
+{
+ size_t i;
+ static char ret[4];
+ memset(ret,0,5);
+ char buf1[4],buf2[4];
+ sprintf(buf1,"%i",channelCount);
+ sprintf(buf2,"%i",current);
+
+ if(strlen(buf2)<strlen(buf1))
+ {
+ for(i=0;i<(strlen(buf1)-1);++i)
+ {
+ ret[i]='0';
+ }
+
+ strcat(ret,buf2);
+
+ return ret;
+ }
+ else if(strlen(buf2)==strlen(buf1))
+ {
+ strcat(ret,buf2);
+ return ret;
+ }
+ else
+ {
+ //this should never happen
+ return NULL;
+ }
+}
+
+int checkSoftvolProcess(int channelcount)
+{
+ int i=0,c=0,ret=channelcount-1,j=0;
+ char nameBuf[1024];
+ char pidBuf[32];
+ char numBuf[4];
+ char command[128];
+ char fileName[64];
+
+ FILE *fpipe;
+
+ for(i=0;i<channelcount;++i)
+ {
+ memset(nameBuf,0,1024);
+ memset(numBuf,0,4);
+ memset(command,0,128);
+ memset(fileName,0,64);
+
+ strcat(command,"ps -A -o pid= | grep -w -f ");
+ strcat(command,getDir(".softvol"));
+ strcat(command,"/SOFTVOL");
+ strcat(command,getSoftvolNumber(channelcount,i));
+ strcat(command,".pid");
+
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+ strcat(fileName,getSoftvolNumber(channelcount,i));
+ strcat(fileName,".pid");
+
+ //Here we check if current process is already conected to something
+ FILE *temp=fopen(fileName,"r");
+ if(temp!=NULL)
+ {
+ j=0;
+ do{
+ c=fgetc(temp);
+ if(c!=EOF && c!='\n')
+ {
+ nameBuf[j]=c;
+ }
+ else
+ {
+ nameBuf[j]='\0';
+ }
+
+ ++j;
+ }while(c!=EOF);
+ }
+ else
+ {
+ memset(nameBuf,0,1024);
+ }
+
+ sprintf(pidBuf,"%d",getpid());
+
+ if(strcmp(nameBuf,pidBuf)==0)
+ {
+ //Current process wants to re-initialize the sound - so
we connect it
+ //to the output it is currently using
+ ret=i;
+ break;
+ }
+ else
+ {
+ //Current process is not connected to anything - so we connect it
+ //to the firs unused softvol output
+ memset(fileName,0,64);
+ }
+ }
+
+ if(fileName[0]!=0)
+ {
+ //Current process is already using some softvol output - so we
+ //reconnect it to this output
+ for(i=0;i<channelcount;++i)
+ {
+ memset(numBuf,0,4);
+ memset(command,0,128);
+ memset(fileName,0,64);
+
+ strcat(command,"ps -A -o pid= | grep -w -f ");
+ strcat(command,getDir(".softvol"));
+ strcat(command,"/SOFTVOL");
+ strcat(command,getSoftvolNumber(channelcount,i));
+ strcat(command,".pid 2> /dev/null");
+
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+ strcat(fileName,getSoftvolNumber(channelcount,i));
+ strcat(fileName,".pid");
+
+ //Here we check if process is alive...
+ if ( !(fpipe = (FILE*)popen(command,"r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ }
+
+ j=0;
+
+ do {
+ c = fgetc (fpipe);
+ if(c!=EOF)
+ ++j;
+ } while (c != EOF);
+ fclose (fpipe);
+
+ //...and if it is the current process
+ if(j>0)
+ {
+ FILE *temp=fopen(fileName,"r");
+ j=0;
+ do{
+ c=fgetc(temp);
+ if(c!=EOF && c!='\n')
+ {
+ nameBuf[j]=c;
+ }
+ else
+ {
+ nameBuf[j]='\0';
+ }
+
+ ++j;
+ }while(c!=EOF);
+
+ char pidBuf[32];
+ sprintf(pidBuf,"%d",getpid());
+
+ if(strcmp(nameBuf,pidBuf)==0)
+ {
+ //Current process wants to re-initialize the
sound - so we connect it
+ //to the output it is currently using
+ ret=i;
+ break;
+ }
+ else
+ {
+ //It is some other process - so we connect it
+ //to the first unused softvol output
+ ret=i+1;
+ }
+ }
+ else
+ {
+ ret=i;
+ }
+ }
+ }
+ else
+ {
+ //Process isn't using any softvol output - so we connect it
+ //to the first unused on the list
+
+ for(i=0;i<channelcount;++i)
+ {
+ memset(numBuf,0,4);
+ memset(command,0,128);
+ memset(fileName,0,64);
+
+ strcat(command,"ps -A -o pid= | grep -w -f ");
+ strcat(command,getDir(".softvol"));
+ strcat(command,"/SOFTVOL");
+ strcat(command,getSoftvolNumber(channelcount,i));
+ strcat(command,".pid 2> /dev/null");
+
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+ strcat(fileName,getSoftvolNumber(channelcount,i));
+ strcat(fileName,".pid");
+
+ //Here we check if process is alive...
+ if ( !(fpipe = (FILE*)popen(command,"r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ }
+
+ j=0;
+
+ do {
+ c = fgetc (fpipe);
+ if(c!=EOF)
+ ++j;
+ } while (c != EOF);
+ fclose (fpipe);
+
+ //...and if it is the current process
+ if(j>0)
+ {
+
+ }
+ else
+ {
+ ret=i;
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_copy Plugin: copy
+
+This plugin copies samples from master copy PCM to given slave PCM.
+The channel count, format and rate must match for both of them.
+
+\code
+pcm.name {
+ type copy # Copy PCM
+ slave STR # Slave name
+ # or
+ slave { # Slave definition
+ pcm STR # Slave PCM name
+ # or
+ pcm { } # Slave PCM definition
+ }
+}
+\endcode
+
+\subsection pcm_plugins_copy_funcref Function reference
+
+<UL>
+ <LI>snd_pcm_copy_open()
+ <LI>_snd_pcm_copy_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new copy PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \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
+ * changed in future.
+ */
+int _snd_pcm_pavc_open(snd_pcm_t **pcmp, const char *name,
+ snd_config_t *root, snd_config_t *conf,
+ snd_pcm_stream_t stream, int mode)
+{
+ long channelcount=-1;
+ int ret=0;
+ char softvolName[64];
+ char pidBuf[32];
+ char fileName[64];
+ char helpBuf[32];
+
+ memset(softvolName,0,64);
+
+ snd_config_iterator_t i, next;
+ int err;
+ snd_pcm_t *spcm;
+ snd_config_t *slave = NULL, *sconf;
+
+ snd_config_t *tempConfig;
+ err=snd_config_search(conf,"channelcount",&tempConfig);
+ if(err<0)
+ {
+ SNDERR("channelcount field not found");
+ return -ENOENT;
+ }
+ err=snd_config_get_integer(tempConfig,&channelcount);
+ if(err<0)
+ {
+ SNDERR("channelcount vaule must be integer");
+ return -EINVAL;
+ }
+
+ snd_config_t *softvol[channelcount];
+
+ int counter=0;
+
+ sprintf(softvolName,"softvol%i",counter);
+
+ snd_config_for_each(i, next, conf)
+ {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (snd_pcm_conf_generic_id(id))
+ continue;
+ if (strcmp(id, "slave") == 0)
+ {
+ slave = n;
+ continue;
+ }
+ if (strcmp(id, "channelcount") == 0)
+ {
+ continue;
+ }
+ if((counter<channelcount) && strcmp(id,softvolName)==0)
+ {
+ softvol[counter]=n;
+ ++counter;
+ sprintf(softvolName,"softvol%i",counter);
+ continue;
+ }
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ if (!slave) {
+ SNDERR("slave is not defined");
+ return -EINVAL;
+ }
+
+ if(checkDir(".softvol"))
+ {
+ //Directory exists - we do nothing
+ }
+ else
+ {
+ //Directory doesn't exists we need to create it
+ if((err=createDir(".softvol"))!=0)
+ {
+ SNDERR("Unable to create ~/.softvol directory - unknown
error %i",err);
+ return err;
+ }
+ }
+
+ ret=checkSoftvolProcess(channelcount);
+
+ err = snd_pcm_slave_conf(root, softvol[ret], &sconf, 0);
+
+ if (err < 0)
+ return err;
+ err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+ snd_config_delete(sconf);
+ if (err < 0)
+ return err;
+ err = snd_pcm_pavc_open(pcmp, name, spcm, 1);
+ if (err < 0)
+ snd_pcm_close(spcm);
+
+ sprintf(pidBuf,"%d",getpid());
+ memset(fileName,0,64);
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+
+ strcat(fileName,getSoftvolNumber(channelcount,ret));
+ strcat(fileName,".pid");
+
+ savePid(pidBuf,fileName);
+
+ return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_pavc_open, SND_PCM_DLSYM_VERSION);
+#endif




To test PAVC, we have to rebuild alsa-lib, and create .asoundrc or
/etc/asound.conf that looks like this:


pcm.!default {
type plug
slave.pcm "asymed"
}

pcm.asymed
{
type asym
playback.pcm "pavcp"
capture.pcm "dsnooped"
}

pcm.dmixer {
type dmix
ipc_key 1025
slave {
pcm "hw:0"
period_time 0
period_size 256
#buffer_size 4096
periods 128
rate 44100
}
}

pcm.dsnooped {
type dsnoop
ipc_key 1026
slave
{
pcm "hw:0"
channels 2
period_size 256
#buffer_size 4096
rate 44100
periods 0
period_time 0
}
}

pcm.softvol00 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol00"
card 0
}
}

pcm.softvol01 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol01"
card 0
}
}

pcm.softvol02 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol02"
card 0
}
}

pcm.softvol03 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol03"
card 0
}
}

pcm.softvol04 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol04"
card 0
}
}

pcm.softvol05 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol05"
card 0
}
}

pcm.softvol06 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol06"
card 0
}
}

pcm.softvol07 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol07"
card 0
}
}

pcm.softvol08 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol08"
card 0
}
}

pcm.softvol09 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol09"
card 0
}
}

pcm.softvol10 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol10"
card 0
}
}

pcm.softvol11 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol11"
card 0
}
}

ctl.dmixer {
type hw
card 0
}

ctl.dsnooped {
type hw
card 0
}


pcm.pavcp
{
type pavc
slave.pcm "dmixer"
channelcount 12
softvol0.pcm "softvol00"
softvol1.pcm "softvol01"
softvol2.pcm "softvol02"
softvol3.pcm "softvol03"
softvol4.pcm "softvol04"
softvol5.pcm "softvol05"
softvol6.pcm "softvol06"
softvol7.pcm "softvol07"
softvol8.pcm "softvol08"
softvol9.pcm "softvol09"
softvol10.pcm "softvol10"
softvol11.pcm "softvol11"
}


With this, anything connected to "default" will be automatically
redirected to the first unused softvol channel. PIDs of apps that
currently use softvol channels are saved to ~/.softvol/SOFTVOLXX.pid,
so this can be checked at any time. If some application will try to
restart only its sound subsystem, PAVC plugin will check those files
to determine if it should be connected to the new softvol channel, or
the one it currently uses. I tried to mimic the way this works with
OSS4.

Current limitations:

-Since there is no "mute and slider at the same time" support in
SOFTVOL plugin - there is no mute switch for software channels (or
maybe there is a way I don't know about?)

-Software channels must be named SOFTVOL00,01,02 and so on

-SOFTVOL channels have both PLAYBACK and CAPTURE capabilities by
default - this makes some mixers go haywire, and display double
sliders - maybe there is a way around this I don't know about (like
setting PLAYBACK only capability)?

-Code of PAVC, could probably look nicer

Hopefully someone will take a look at this, of course I am open to
suggestions and critics.

Best regards.

P.S

Sorry for my English ;]
Takashi Iwai
2010-10-17 08:55:57 UTC
Permalink
At Wed, 29 Sep 2010 18:56:59 +0200,
Post by James Huk
Hello everybody.
This is my first "post" here, so please be "gentle".
I have created PAVC plugin for ALSA, it is based on COPY plugin, and
use SOFTVOL plugin for sound output. As far as I can tell from current
tests - It works perfectly, and it doesn't break compatibility with
any apps I use (and PulseAudio does). So I was wondering - is there
any chance this plugin would be added to the next official ALSA
release?
Here is the patch that enables it on current alsa-lib-1.0.23
Thanks for the patch. I looked over this post until now.

The idea is interesting, and I thought of a similar thing before PA
gets popularized, too.

Though, this implementation is a bit too hackish, especially the part
using pipe & co. If we have a more simpler and cleaner way for this,
it's worth including to the standard plugin, I think.


thanks,

Takashi
Post by James Huk
--- a/configure.in 2010-09-23 17:03:00.000000000 +0200
+++ b/configure.in 2010-09-23 17:09:11.029900400 +0200
@@ -445,7 +445,7 @@
pcm_plugins=""
fi
-PCM_PLUGIN_LIST="copy linear route mulaw alaw adpcm rate plug multi
shm file null empty share meter hooks lfloat ladspa dmix dshare dsnoop
asym iec958 softvol extplug ioplug mmap_emul"
+PCM_PLUGIN_LIST="copy linear route mulaw alaw adpcm rate plug multi
shm file null empty share meter hooks lfloat ladspa dmix dshare dsnoop
asym iec958 softvol extplug ioplug mmap_emul pavc"
build_pcm_plugin="no"
for t in $PCM_PLUGIN_LIST; do
@@ -516,6 +516,7 @@
AM_CONDITIONAL(BUILD_PCM_PLUGIN_EXTPLUG, test x$build_pcm_extplug = xyes)
AM_CONDITIONAL(BUILD_PCM_PLUGIN_IOPLUG, test x$build_pcm_ioplug = xyes)
AM_CONDITIONAL(BUILD_PCM_PLUGIN_MMAP_EMUL, test x$build_pcm_mmap_emul = xyes)
+AM_CONDITIONAL(BUILD_PCM_PLUGIN_PAVC, test x$build_pcm_pavc = xyes)
dnl Defines for plug plugin
if test "$build_pcm_rate" = "yes"; then
--- a/src/pcm/Makefile.am 2010-04-16 13:11:05.000000000 +0200
+++ b/src/pcm/Makefile.am 2010-09-23 17:20:10.601899695 +0200
@@ -102,6 +102,9 @@
if BUILD_PCM_PLUGIN_MMAP_EMUL
libpcm_la_SOURCES += pcm_mmap_emul.c
endif
+if BUILD_PCM_PLUGIN_PAVC
+libpcm_la_SOURCES += pcm_pavc.c
+endif
EXTRA_DIST = pcm_dmix_i386.c pcm_dmix_x86_64.c pcm_dmix_generic.c
--- a/src/pcm/pcm.c 2010-04-16 13:11:05.000000000 +0200
+++ b/src/pcm/pcm.c 2010-09-23 17:10:43.813900406 +0200
@@ -2047,7 +2047,7 @@
static const char *const build_in_pcms[] = {
"adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat",
"linear", "meter", "mulaw", "multi", "null", "empty", "plug",
"rate", "route", "share",
- "shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul",
+ "shm", "dsnoop", "dshare", "asym", "iec958", "softvol", "mmap_emul","pavc",
NULL
};
--- a/include/pcm.h 2010-04-16 13:11:05.000000000 +0200
+++ b/include/pcm.h 2010-09-23 17:11:58.389899158 +0200
@@ -376,7 +376,9 @@
SND_PCM_TYPE_EXTPLUG,
/** Mmap-emulation plugin */
SND_PCM_TYPE_MMAP_EMUL,
- SND_PCM_TYPE_LAST = SND_PCM_TYPE_MMAP_EMUL
+ /** PAVC plugin */
+ SND_PCM_TYPE_PAVC,
+ SND_PCM_TYPE_LAST = SND_PCM_TYPE_PAVC
};
/** PCM type */
--- a/src/pcm/pcm_pavc.c
+++ b/src/pcm/pcm_pavc.c
@@ -0,0 +1,846 @@
+/*
+ * PCM - PAVC plugin
+ * Based on PCM - Copy conversion plugin by Abramo Bagnara
+ *
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <byteswap.h>
+#include "pcm_local.h"
+#include "pcm_plugin.h"
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+
+#ifndef PIC
+/* entry for static linking */
+const char *_snd_module_pcm_pavc = "";
+#endif
+
+#ifndef DOC_HIDDEN
+typedef struct {
+ /* This field need to be the first */
+ snd_pcm_plugin_t plug;
+} snd_pcm_pavc_t;
+#endif
+
+static int snd_pcm_pavc_hw_refine_cprepare(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
+{
+ int err;
+ snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
+ err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
+ &access_mask);
+ if (err < 0)
+ return err;
+ params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine_sprepare(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *sparams)
+{
+ snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
+ _snd_pcm_hw_params_any(sparams);
+ _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
+ &saccess_mask);
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine_schange(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_params_t *sparams)
+{
+ int err;
+ unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+ err = _snd_pcm_hw_params_refine(sparams, links, params);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine_cchange(snd_pcm_t *pcm
ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params,
+ snd_pcm_hw_params_t *sparams)
+{
+ int err;
+ unsigned int links = ~SND_PCM_HW_PARBIT_ACCESS;
+ err = _snd_pcm_hw_params_refine(params, links, sparams);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static int snd_pcm_pavc_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+ return snd_pcm_hw_refine_slave(pcm, params,
+ snd_pcm_pavc_hw_refine_cprepare,
+ snd_pcm_pavc_hw_refine_cchange,
+ snd_pcm_pavc_hw_refine_sprepare,
+ snd_pcm_pavc_hw_refine_schange,
+ snd_pcm_generic_hw_refine);
+}
+
+static int snd_pcm_pavc_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+ return snd_pcm_hw_params_slave(pcm, params,
+ snd_pcm_pavc_hw_refine_cchange,
+ snd_pcm_pavc_hw_refine_sprepare,
+ snd_pcm_pavc_hw_refine_schange,
+ snd_pcm_generic_hw_params);
+}
+
+static snd_pcm_uframes_t
+ snd_pcm_pavc_write_areas(snd_pcm_t *pcm,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size,
+ const snd_pcm_channel_area_t *slave_areas,
+ snd_pcm_uframes_t slave_offset,
+ snd_pcm_uframes_t *slave_sizep)
+{
+ if (size > *slave_sizep)
+ size = *slave_sizep;
+ snd_pcm_areas_copy(slave_areas, slave_offset,
+ areas, offset,
+ pcm->channels, size, pcm->format);
+ *slave_sizep = size;
+ return size;
+}
+
+static snd_pcm_uframes_t
+ snd_pcm_pavc_read_areas(snd_pcm_t *pcm,
+ const snd_pcm_channel_area_t *areas,
+ snd_pcm_uframes_t offset,
+ snd_pcm_uframes_t size,
+ const snd_pcm_channel_area_t *slave_areas,
+ snd_pcm_uframes_t slave_offset,
+ snd_pcm_uframes_t *slave_sizep)
+{
+ if (size > *slave_sizep)
+ size = *slave_sizep;
+ snd_pcm_areas_copy(areas, offset,
+ slave_areas, slave_offset,
+ pcm->channels, size, pcm->format);
+ *slave_sizep = size;
+ return size;
+}
+
+static void snd_pcm_pavc_dump(snd_pcm_t *pcm, snd_output_t *out)
+{
+ snd_pcm_pavc_t *pavc = pcm->private_data;
+ snd_output_printf(out, "Copy conversion PCM\n");
+ if (pcm->setup) {
+ snd_output_printf(out, "Its setup is:\n");
+ snd_pcm_dump_setup(pcm, out);
+ }
+ snd_output_printf(out, "Slave: ");
+ snd_pcm_dump(pavc->plug.gen.slave, out);
+}
+
+static const snd_pcm_ops_t snd_pcm_pavc_ops = {
+ .close = snd_pcm_generic_close,
+ .info = snd_pcm_generic_info,
+ .hw_refine = snd_pcm_pavc_hw_refine,
+ .hw_params = snd_pcm_pavc_hw_params,
+ .hw_free = snd_pcm_generic_hw_free,
+ .sw_params = snd_pcm_generic_sw_params,
+ .channel_info = snd_pcm_generic_channel_info,
+ .dump = snd_pcm_pavc_dump,
+ .nonblock = snd_pcm_generic_nonblock,
+ .async = snd_pcm_generic_async,
+ .mmap = snd_pcm_generic_mmap,
+ .munmap = snd_pcm_generic_munmap,
+};
+
+/**
+ * \brief Creates a new copy PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param slave Slave PCM handle
+ * \param close_slave When set, the slave PCM handle is closed with copy PCM
+ * \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
+ * changed in future.
+ */
+int snd_pcm_pavc_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t
*slave, int close_slave)
+{
+ snd_pcm_t *pcm;
+ snd_pcm_pavc_t *pavc;
+ int err;
+ assert(pcmp && slave);
+ pavc = calloc(1, sizeof(snd_pcm_pavc_t));
+ if (!pavc) {
+ return -ENOMEM;
+ }
+ snd_pcm_plugin_init(&pavc->plug);
+ pavc->plug.read = snd_pcm_pavc_read_areas;
+ pavc->plug.write = snd_pcm_pavc_write_areas;
+ pavc->plug.undo_read = snd_pcm_plugin_undo_read_generic;
+ pavc->plug.undo_write = snd_pcm_plugin_undo_write_generic;
+ pavc->plug.gen.slave = slave;
+ pavc->plug.gen.close_slave = close_slave;
+
+ err = snd_pcm_new(&pcm, SND_PCM_TYPE_PAVC, name, slave->stream,
slave->mode);
+ if (err < 0) {
+ free(pavc);
+ return err;
+ }
+ pcm->ops = &snd_pcm_pavc_ops;
+ pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+ pcm->private_data = pavc;
+ pcm->poll_fd = slave->poll_fd;
+ pcm->poll_events = slave->poll_events;
+ pcm->monotonic = slave->monotonic;
+ snd_pcm_set_hw_ptr(pcm, &pavc->plug.hw_ptr, -1, 0);
+ snd_pcm_set_appl_ptr(pcm, &pavc->plug.appl_ptr, -1, 0);
+ *pcmp = pcm;
+
+ return 0;
+}
+
+int countString(char * x,char x2,int count)
+{
+ int i;
+ int z=0;
+ for(i=0;i<count;++i)
+ {
+ if(x[i]==x2)
+ {
+ ++z;
+ }
+ }
+ return z;
+}
+
+char *deleteChar(char *x, char x2, int count)
+{
+ char *ret;
+ ret=(char*) malloc(count+1);
+ int i;
+ for(i=0;i<count;++i)
+ {
+ ret[i]=0;
+ }
+ for(i=0;i<count;++i)
+ {
+ if(x[i]!=x2)
+ {
+ ret[i]=x[i];
+ }
+ else
+ {
+ ret[i]=' ';
+ }
+ }
+ return ret;
+}
+
+char *getProceses()
+{
+ FILE *pipe;
+ if ( !(pipe = (FILE*)popen("lsof -F p /dev/snd/pcmC0D0p","r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ return NULL;
+ }
+
+ int i=0,j=0;
+ int c;
+
+ do {
+ c = fgetc (pipe);
+ ++i;
+ } while (c != EOF);
+ fclose (pipe);
+
+ char *buf=(char*)malloc(sizeof(char)*i);
+
+ for(j=0;j<i;++j)
+ {
+ buf[j]=0;
+ }
+
+ i=0;
+
+ if ( !(pipe = (FILE*)popen("lsof -F p /dev/snd/pcmC0D0p","r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ return NULL;
+ }
+
+ do {
+ c = fgetc (pipe);
+ if(c!=EOF && c!='p' && c!='\n')
+ {
+ buf[i]=c;
+ }
+ else if(c=='p')
+ {
+ buf[i]=' ';
+ }
+ else if(c=='\n')
+ {
+ buf[i]=',';
+ }
+ ++i;
+ } while (c != EOF);
+ fclose (pipe);
+
+ return buf;
+}
+
+char *loadPid(char *name)
+{
+ FILE *file;
+ int c,i=0,j=0;
+
+ file=fopen(name,"r");
+ if(file!=NULL)
+ {
+ do{
+ c=fgetc(file);
+ ++i;
+ }while(c!=EOF);
+ fclose(file);
+
+ char *buf=malloc((sizeof(char))*i);
+ for(j=0;j<i;++j)
+ {
+ buf[j]=0;
+ }
+
+ i=0;
+
+ file=fopen(name,"r");
+ if(file!=NULL)
+ {
+ do{
+ c=fgetc(file);
+ if(c!=EOF && c!='\n')
+ buf[i]=c;
+ ++i;
+ }while(c!=EOF);
+ fclose(file);
+
+ return buf;
+ }
+ else
+ {
+ return NULL;
+ }
+
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+void savePid(char *pid,char *file)
+{
+ int i;
+ char command3[256];
+
+ for(i=0;i<256;++i)
+ {
+ command3[i]=0;
+ }
+
+ strcat(command3,"echo ");
+ strcat(command3,pid);
+ strcat(command3," > ");
+ strcat(command3,file);
+
+ for(i=5;i<256;++i)
+ {
+ if(command3[i]!=EOF && command3[i]!='\n' && command3[i]!='*')
+ {
+ command3[i]=command3[i];
+ }
+ else
+ {
+ command3[i]=' ';
+ }
+ }
+
+ system(command3);
+}
+
+int checkDir(char *name)
+{
+ char command[128];
+ memset(command,0,128);
+
+ strcat(command,"ls -aF ");
+ strcat(command,getenv("HOME"));
+ strcat(command," | grep \\./$ | grep -w ");
+ strcat(command,name);
+
+ FILE *pipe;
+ if ( !(pipe = (FILE*)popen(command,"r")) )
+ {
+ perror("Problems with pipe");
+ return -1;
+ }
+
+ int i=0;
+ int c;
+
+ do {
+ c = fgetc (pipe);
+ if(c!=EOF && c!='\n')
+ ++i;
+ } while (c != EOF);
+ fclose (pipe);
+
+ if(i>0)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+int createDir(char *name)
+{
+ pid_t PID;
+ int status;
+
+ PID=fork();
+
+ if(PID>0)
+ {
+ char command[128];
+ memset(command,0,128);
+
+ strcat(command,getenv("HOME"));
+ strcat(command,"/");
+ strcat(command,".softvol");
+
+ execlp("mkdir","mkdir",command,NULL);
+ }
+ else
+ {
+ pid_t wpid = waitpid(PID, &status, WUNTRACED);
+ }
+
+ return WEXITSTATUS(status);
+}
+
+char *getDir(char *name)
+{
+ static char command[128];
+ memset(command,0,128);
+
+ strcat(command,getenv("HOME"));
+ strcat(command,"/");
+ strcat(command,name);
+
+ return command;
+}
+
+char *getSoftvolNumber(int channelCount,int current)
+{
+ size_t i;
+ static char ret[4];
+ memset(ret,0,5);
+ char buf1[4],buf2[4];
+ sprintf(buf1,"%i",channelCount);
+ sprintf(buf2,"%i",current);
+
+ if(strlen(buf2)<strlen(buf1))
+ {
+ for(i=0;i<(strlen(buf1)-1);++i)
+ {
+ ret[i]='0';
+ }
+
+ strcat(ret,buf2);
+
+ return ret;
+ }
+ else if(strlen(buf2)==strlen(buf1))
+ {
+ strcat(ret,buf2);
+ return ret;
+ }
+ else
+ {
+ //this should never happen
+ return NULL;
+ }
+}
+
+int checkSoftvolProcess(int channelcount)
+{
+ int i=0,c=0,ret=channelcount-1,j=0;
+ char nameBuf[1024];
+ char pidBuf[32];
+ char numBuf[4];
+ char command[128];
+ char fileName[64];
+
+ FILE *fpipe;
+
+ for(i=0;i<channelcount;++i)
+ {
+ memset(nameBuf,0,1024);
+ memset(numBuf,0,4);
+ memset(command,0,128);
+ memset(fileName,0,64);
+
+ strcat(command,"ps -A -o pid= | grep -w -f ");
+ strcat(command,getDir(".softvol"));
+ strcat(command,"/SOFTVOL");
+ strcat(command,getSoftvolNumber(channelcount,i));
+ strcat(command,".pid");
+
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+ strcat(fileName,getSoftvolNumber(channelcount,i));
+ strcat(fileName,".pid");
+
+ //Here we check if current process is already conected to something
+ FILE *temp=fopen(fileName,"r");
+ if(temp!=NULL)
+ {
+ j=0;
+ do{
+ c=fgetc(temp);
+ if(c!=EOF && c!='\n')
+ {
+ nameBuf[j]=c;
+ }
+ else
+ {
+ nameBuf[j]='\0';
+ }
+
+ ++j;
+ }while(c!=EOF);
+ }
+ else
+ {
+ memset(nameBuf,0,1024);
+ }
+
+ sprintf(pidBuf,"%d",getpid());
+
+ if(strcmp(nameBuf,pidBuf)==0)
+ {
+ //Current process wants to re-initialize the sound - so
we connect it
+ //to the output it is currently using
+ ret=i;
+ break;
+ }
+ else
+ {
+ //Current process is not connected to anything - so we connect it
+ //to the firs unused softvol output
+ memset(fileName,0,64);
+ }
+ }
+
+ if(fileName[0]!=0)
+ {
+ //Current process is already using some softvol output - so we
+ //reconnect it to this output
+ for(i=0;i<channelcount;++i)
+ {
+ memset(numBuf,0,4);
+ memset(command,0,128);
+ memset(fileName,0,64);
+
+ strcat(command,"ps -A -o pid= | grep -w -f ");
+ strcat(command,getDir(".softvol"));
+ strcat(command,"/SOFTVOL");
+ strcat(command,getSoftvolNumber(channelcount,i));
+ strcat(command,".pid 2> /dev/null");
+
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+ strcat(fileName,getSoftvolNumber(channelcount,i));
+ strcat(fileName,".pid");
+
+ //Here we check if process is alive...
+ if ( !(fpipe = (FILE*)popen(command,"r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ }
+
+ j=0;
+
+ do {
+ c = fgetc (fpipe);
+ if(c!=EOF)
+ ++j;
+ } while (c != EOF);
+ fclose (fpipe);
+
+ //...and if it is the current process
+ if(j>0)
+ {
+ FILE *temp=fopen(fileName,"r");
+ j=0;
+ do{
+ c=fgetc(temp);
+ if(c!=EOF && c!='\n')
+ {
+ nameBuf[j]=c;
+ }
+ else
+ {
+ nameBuf[j]='\0';
+ }
+
+ ++j;
+ }while(c!=EOF);
+
+ char pidBuf[32];
+ sprintf(pidBuf,"%d",getpid());
+
+ if(strcmp(nameBuf,pidBuf)==0)
+ {
+ //Current process wants to re-initialize the
sound - so we connect it
+ //to the output it is currently using
+ ret=i;
+ break;
+ }
+ else
+ {
+ //It is some other process - so we connect it
+ //to the first unused softvol output
+ ret=i+1;
+ }
+ }
+ else
+ {
+ ret=i;
+ }
+ }
+ }
+ else
+ {
+ //Process isn't using any softvol output - so we connect it
+ //to the first unused on the list
+
+ for(i=0;i<channelcount;++i)
+ {
+ memset(numBuf,0,4);
+ memset(command,0,128);
+ memset(fileName,0,64);
+
+ strcat(command,"ps -A -o pid= | grep -w -f ");
+ strcat(command,getDir(".softvol"));
+ strcat(command,"/SOFTVOL");
+ strcat(command,getSoftvolNumber(channelcount,i));
+ strcat(command,".pid 2> /dev/null");
+
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+ strcat(fileName,getSoftvolNumber(channelcount,i));
+ strcat(fileName,".pid");
+
+ //Here we check if process is alive...
+ if ( !(fpipe = (FILE*)popen(command,"r")) )
+ { // If fpipe is NULL
+ perror("Problems with pipe");
+ }
+
+ j=0;
+
+ do {
+ c = fgetc (fpipe);
+ if(c!=EOF)
+ ++j;
+ } while (c != EOF);
+ fclose (fpipe);
+
+ //...and if it is the current process
+ if(j>0)
+ {
+
+ }
+ else
+ {
+ ret=i;
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+/*! \page pcm_plugins
+
+\section pcm_plugins_copy Plugin: copy
+
+This plugin copies samples from master copy PCM to given slave PCM.
+The channel count, format and rate must match for both of them.
+
+\code
+pcm.name {
+ type copy # Copy PCM
+ slave STR # Slave name
+ # or
+ slave { # Slave definition
+ pcm STR # Slave PCM name
+ # or
+ pcm { } # Slave PCM definition
+ }
+}
+\endcode
+
+\subsection pcm_plugins_copy_funcref Function reference
+
+<UL>
+ <LI>snd_pcm_copy_open()
+ <LI>_snd_pcm_copy_open()
+</UL>
+
+*/
+
+/**
+ * \brief Creates a new copy PCM
+ * \param pcmp Returns created PCM handle
+ * \param name Name of PCM
+ * \param root Root configuration node
+ * \param conf Configuration node with copy PCM description
+ * \param stream Stream type
+ * \param mode Stream mode
+ * \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
+ * changed in future.
+ */
+int _snd_pcm_pavc_open(snd_pcm_t **pcmp, const char *name,
+ snd_config_t *root, snd_config_t *conf,
+ snd_pcm_stream_t stream, int mode)
+{
+ long channelcount=-1;
+ int ret=0;
+ char softvolName[64];
+ char pidBuf[32];
+ char fileName[64];
+ char helpBuf[32];
+
+ memset(softvolName,0,64);
+
+ snd_config_iterator_t i, next;
+ int err;
+ snd_pcm_t *spcm;
+ snd_config_t *slave = NULL, *sconf;
+
+ snd_config_t *tempConfig;
+ err=snd_config_search(conf,"channelcount",&tempConfig);
+ if(err<0)
+ {
+ SNDERR("channelcount field not found");
+ return -ENOENT;
+ }
+ err=snd_config_get_integer(tempConfig,&channelcount);
+ if(err<0)
+ {
+ SNDERR("channelcount vaule must be integer");
+ return -EINVAL;
+ }
+
+ snd_config_t *softvol[channelcount];
+
+ int counter=0;
+
+ sprintf(softvolName,"softvol%i",counter);
+
+ snd_config_for_each(i, next, conf)
+ {
+ snd_config_t *n = snd_config_iterator_entry(i);
+ const char *id;
+ if (snd_config_get_id(n, &id) < 0)
+ continue;
+ if (snd_pcm_conf_generic_id(id))
+ continue;
+ if (strcmp(id, "slave") == 0)
+ {
+ slave = n;
+ continue;
+ }
+ if (strcmp(id, "channelcount") == 0)
+ {
+ continue;
+ }
+ if((counter<channelcount) && strcmp(id,softvolName)==0)
+ {
+ softvol[counter]=n;
+ ++counter;
+ sprintf(softvolName,"softvol%i",counter);
+ continue;
+ }
+ SNDERR("Unknown field %s", id);
+ return -EINVAL;
+ }
+
+ if (!slave) {
+ SNDERR("slave is not defined");
+ return -EINVAL;
+ }
+
+ if(checkDir(".softvol"))
+ {
+ //Directory exists - we do nothing
+ }
+ else
+ {
+ //Directory doesn't exists we need to create it
+ if((err=createDir(".softvol"))!=0)
+ {
+ SNDERR("Unable to create ~/.softvol directory - unknown
error %i",err);
+ return err;
+ }
+ }
+
+ ret=checkSoftvolProcess(channelcount);
+
+ err = snd_pcm_slave_conf(root, softvol[ret], &sconf, 0);
+
+ if (err < 0)
+ return err;
+ err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
+ snd_config_delete(sconf);
+ if (err < 0)
+ return err;
+ err = snd_pcm_pavc_open(pcmp, name, spcm, 1);
+ if (err < 0)
+ snd_pcm_close(spcm);
+
+ sprintf(pidBuf,"%d",getpid());
+ memset(fileName,0,64);
+ strcat(fileName,getDir(".softvol"));
+ strcat(fileName,"/SOFTVOL");
+
+ strcat(fileName,getSoftvolNumber(channelcount,ret));
+ strcat(fileName,".pid");
+
+ savePid(pidBuf,fileName);
+
+ return err;
+}
+#ifndef DOC_HIDDEN
+SND_DLSYM_BUILD_VERSION(_snd_pcm_pavc_open, SND_PCM_DLSYM_VERSION);
+#endif
To test PAVC, we have to rebuild alsa-lib, and create .asoundrc or
pcm.!default {
type plug
slave.pcm "asymed"
}
pcm.asymed
{
type asym
playback.pcm "pavcp"
capture.pcm "dsnooped"
}
pcm.dmixer {
type dmix
ipc_key 1025
slave {
pcm "hw:0"
period_time 0
period_size 256
#buffer_size 4096
periods 128
rate 44100
}
}
pcm.dsnooped {
type dsnoop
ipc_key 1026
slave
{
pcm "hw:0"
channels 2
period_size 256
#buffer_size 4096
rate 44100
periods 0
period_time 0
}
}
pcm.softvol00 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol00"
card 0
}
}
pcm.softvol01 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol01"
card 0
}
}
pcm.softvol02 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol02"
card 0
}
}
pcm.softvol03 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol03"
card 0
}
}
pcm.softvol04 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol04"
card 0
}
}
pcm.softvol05 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol05"
card 0
}
}
pcm.softvol06 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol06"
card 0
}
}
pcm.softvol07 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol07"
card 0
}
}
pcm.softvol08 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol08"
card 0
}
}
pcm.softvol09 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol09"
card 0
}
}
pcm.softvol10 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol10"
card 0
}
}
pcm.softvol11 {
type softvol
slave {
pcm "dmixer"
}
control {
name "Softvol11"
card 0
}
}
ctl.dmixer {
type hw
card 0
}
ctl.dsnooped {
type hw
card 0
}
pcm.pavcp
{
type pavc
slave.pcm "dmixer"
channelcount 12
softvol0.pcm "softvol00"
softvol1.pcm "softvol01"
softvol2.pcm "softvol02"
softvol3.pcm "softvol03"
softvol4.pcm "softvol04"
softvol5.pcm "softvol05"
softvol6.pcm "softvol06"
softvol7.pcm "softvol07"
softvol8.pcm "softvol08"
softvol9.pcm "softvol09"
softvol10.pcm "softvol10"
softvol11.pcm "softvol11"
}
With this, anything connected to "default" will be automatically
redirected to the first unused softvol channel. PIDs of apps that
currently use softvol channels are saved to ~/.softvol/SOFTVOLXX.pid,
so this can be checked at any time. If some application will try to
restart only its sound subsystem, PAVC plugin will check those files
to determine if it should be connected to the new softvol channel, or
the one it currently uses. I tried to mimic the way this works with
OSS4.
-Since there is no "mute and slider at the same time" support in
SOFTVOL plugin - there is no mute switch for software channels (or
maybe there is a way I don't know about?)
-Software channels must be named SOFTVOL00,01,02 and so on
-SOFTVOL channels have both PLAYBACK and CAPTURE capabilities by
default - this makes some mixers go haywire, and display double
sliders - maybe there is a way around this I don't know about (like
setting PLAYBACK only capability)?
-Code of PAVC, could probably look nicer
Hopefully someone will take a look at this, of course I am open to
suggestions and critics.
Best regards.
P.S
Sorry for my English ;]
_______________________________________________
Alsa-devel mailing list
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Raymond Yau
2010-10-17 11:20:03 UTC
Permalink
Post by Takashi Iwai
At Wed, 29 Sep 2010 18:56:59 +0200,
Post by James Huk
Hello everybody.
This is my first "post" here, so please be "gentle".
I have created PAVC plugin for ALSA, it is based on COPY plugin, and
use SOFTVOL plugin for sound output. As far as I can tell from current
tests - It works perfectly, and it doesn't break compatibility with
any apps I use (and PulseAudio does). So I was wondering - is there
any chance this plugin would be added to the next official ALSA
release?
Here is the patch that enables it on current alsa-lib-1.0.23
Thanks for the patch. I looked over this post until now.
The idea is interesting, and I thought of a similar thing before PA
gets popularized, too.
Though, this implementation is a bit too hackish, especially the part
using pipe & co. If we have a more simpler and cleaner way for this,
it's worth including to the standard plugin, I think.
thanks,
Takashi
Is it possible to implement per voice cvolume control for dmix similar to
those per voice volume control of those hardware mixing sound cards ?

does dmix allow an application open at least 16 voices ?
Colin Guthrie
2010-10-17 11:21:21 UTC
Permalink
Post by Takashi Iwai
At Wed, 29 Sep 2010 18:56:59 +0200,
Post by James Huk
Hello everybody.
This is my first "post" here, so please be "gentle".
I have created PAVC plugin for ALSA, it is based on COPY plugin, and
use SOFTVOL plugin for sound output. As far as I can tell from current
tests - It works perfectly, and it doesn't break compatibility with
any apps I use (and PulseAudio does). So I was wondering - is there
any chance this plugin would be added to the next official ALSA
release?
Here is the patch that enables it on current alsa-lib-1.0.23
Thanks for the patch. I looked over this post until now.
The idea is interesting, and I thought of a similar thing before PA
gets popularized, too.
Though, this implementation is a bit too hackish, especially the part
using pipe & co. If we have a more simpler and cleaner way for this,
it's worth including to the standard plugin, I think.
I was thinking much the same thing when I read over this patch a few
weeks ago.

While it could be interesting to include this in alsa, I struggled to
think of a use case where PA would not be more appropriate.

Is there a specific use case targeted with this patch or is it really
just a "proof of concept" demonstration that alsa can do this sans PA?

Col
--
Colin Guthrie
gmane(at)colin.guthr.ie
http://colin.guthr.ie/

Day Job:
Tribalogic Limited [http://www.tribalogic.net/]
Open Source:
Mageia Contributor [http://www.mageia.org/]
PulseAudio Hacker [http://www.pulseaudio.org/]
Trac Hacker [http://trac.edgewall.org/]
James Huk
2010-10-17 21:54:22 UTC
Permalink
Thanks for the answers, everyone.
Post by Takashi Iwai
Though, this implementation is a bit too hackish, especially the part
using pipe & co. If we have a more simpler and cleaner way for this,
it's worth including to the standard plugin, I think.
Yes, I agree. I know I should probably used fork() and exec() instead.
I must honestly say, I'm not that experienced with C and pipe simply
worked (actually, first implementation used "system()" instead, but it
didn't work well with some applications). I'm already thinking of
rewriting this a little (this may take a while though, because I have
no time right now) - any suggestions are welcome of course.
Post by Takashi Iwai
does dmix allow an application open at least 16 voices ?
I think dmix allows much more than that, if your PC can handle it.
Post by Takashi Iwai
Is there a specific use case targeted with this patch or is it really
just a "proof of concept" demonstration that alsa can do this sans PA?
Before I written this plugin I tried to use PulseAudio -
unfortunately, applications I use didn't worked to well with it so I
had to get rid of it, however I liked the idea of PAVC. Then I tried
OSS4 but again - unfortunately it doesn't support my ALC1200 too well
(interesting info for those out there who says "Get rid of ALSA, get
rid of PA, install OSS4 because it just works" well, not always guys -
this time ALSA is the winner - this is not intended as a flame-war
starter, just as information). Finally I found "copy" plugin for ALSA,
started to tamper with it a bit, and found out that writing PAVC
plugin wouldn't be, all that hard. I created this because I liked this
feature on PA and OSS4 and ALSA lacked it - nothing more to this, than
that.

P.S

Sorry for my English.
Raymond Yau
2010-10-18 01:48:12 UTC
Permalink
Post by James Huk
Thanks for the answers, everyone.
.
Post by Raymond Yau
does dmix allow an application open at least 16 voices ?
I think dmix allows much more than that, if your PC can handle it.
Those hardware mixing sound implement the per voice volume control ( 16-64
voices by hardware ) using IFACE_PCM instead of IFACE_MIXER

http://git.alsa-project.org/?p=alsa-tools.git;a=blob;f=hwmixvolume/README

How dmix prevent clipping occur when it mix several voices ?
Colin Guthrie
2010-10-18 12:14:44 UTC
Permalink
Post by James Huk
Thanks for the answers, everyone.
Post by Colin Guthrie
Is there a specific use case targeted with this patch or is it really
just a "proof of concept" demonstration that alsa can do this sans PA?
Before I written this plugin I tried to use PulseAudio -
unfortunately, applications I use didn't worked to well with it so I
had to get rid of it, however I liked the idea of PAVC. Then I tried
OSS4 but again - unfortunately it doesn't support my ALC1200 too well
(interesting info for those out there who says "Get rid of ALSA, get
rid of PA, install OSS4 because it just works" well, not always guys -
this time ALSA is the winner - this is not intended as a flame-war
starter, just as information). Finally I found "copy" plugin for ALSA,
started to tamper with it a bit, and found out that writing PAVC
plugin wouldn't be, all that hard. I created this because I liked this
feature on PA and OSS4 and ALSA lacked it - nothing more to this, than
that.
No worries, seems like a reasonable reason to have a go at this
implementation.

As a PA hacker, I'd be interested to know what apps were causing you
problems when you used PA so that we could help make them better for
other users who do want to use PA. If you could provide any details or
debug info, that would be great.
Post by James Huk
Sorry for my English.
No need to apologise for that, I think it was fine :)

Col
--
Colin Guthrie
gmane(at)colin.guthr.ie
http://colin.guthr.ie/

Day Job:
Tribalogic Limited [http://www.tribalogic.net/]
Open Source:
Mageia Contributor [http://www.mageia.org/]
PulseAudio Hacker [http://www.pulseaudio.org/]
Trac Hacker [http://trac.edgewall.org/]
James Huk
2010-10-18 21:21:13 UTC
Permalink
Post by Colin Guthrie
As a PA hacker, I'd be interested to know what apps were causing you
problems when you used PA so that we could help make them better for
other users who do want to use PA. If you could provide any details or
debug info, that would be great.
Well let me name a few:

-Quake4 - sound worked, but some latency was growing with time - after
about 30-60 minutes game restart was required because sound latency
was around 1000 ms
-SDL based apps (zsnes is the one I use a lot) - sound was lower
quality with PA, than with clean ALSA or OSS4, even with
SDL_AUDIODRIVER=pulse (but in that case - this may be zsnes fault as
well)
-wine - even with PA patch, I got poor latency in some apps, and more
crashes than with clean ALSA/OSS4
-KEGA Fusion (Megadrive emulator = freeware but closed-source) - well,
this is a special case - PA acted pretty weird with this one. It
started OK - I used PA in debug mode so I could see something like:
"Fusion requested latency of 32 ms - settings minimum latency to 32 ms
- or something similar), so far so good. After some time though, PA
informed me about underruns and started to increase latency to 64,
128...and so on, which caused the emulator to crash (or rather
freeze). Now the weird part is - if I restarted the emulator without
restarting PA, it would set the latency back to the one that crashed
the emu in the first place, and not for the 32 ms it started with -
this of course caused emulator to freeze again. Restarting PA helped
for this(I mean PA would set minimum latency for 32 ms again) but
after few minutes - freeze again (with the same latency problem).

I don't know if this is a bug or a feature of PA, but it caused
troubles for this app (and probably others as well). I did noticed
that with low-latency kernel it was much more difficult to trigger
this bug, I also tried with RT kernel and was never able to trigger it
there either - even when system was on full load, but I didn't test RT
kernel for very long so...
Post by Colin Guthrie
Post by James Huk
Sorry for my English.
No need to apologise for that, I think it was fine :)
Thanks - English is not my native language, so there probably are some
grammar errors in my posts - that's why I prefer to apologize up front
;]

Loading...