[alsa-devel] [PATCH] Per Application Volume Control plugin for ALSA

Takashi Iwai tiwai at suse.de
Sun Oct 17 10:55:57 CEST 2010


At Wed, 29 Sep 2010 18:56:59 +0200,
James Huk wrote:
> 
> 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):

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
> 
> 
> --- 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
> <abramo at alsa-project.org>
> + *
> + *   Copyright (c) 2010 by Adrian Kobyliński <huk256 at 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 ;]
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel


More information about the Alsa-devel mailing list