James Huk
2010-09-29 16:56:59 UTC
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 ;]
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 ;]