Logo Search packages:      
Sourcecode: linphone version File versions  Download package

alsacard.c

/***************************************************************************
 *            alsacard.c
 *
 *  Fri Oct 15 16:13:08 2004
 *  Copyright  2004  Simon Morlat 
 *  Email <simon.morlat@linphone.org>
 ****************************************************************************/
/* The mediastreamer library creates and run multimedia streams in a modular
      way.
*/
/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "alsacard.h"

#ifdef HAVE_ALSA_ASOUNDLIB_H

static gchar *over_pcmdev=NULL;

#include "mssndread.h"
#include "mssndwrite.h"

#include <signal.h>

int __alsa_card_write(AlsaCard *obj,char *buf,int size);

int alsa_set_params(AlsaCard *obj, int rw, int bits, int stereo, int rate)
{
      snd_pcm_hw_params_t *hwparams=NULL;
      snd_pcm_sw_params_t *swparams=NULL;
      snd_pcm_t *pcm_handle;
      gint dir,exact_value;
      gint channels;
      gint fsize=0;
      gint periods=8;
      gint periodsize=256;
      gint err;
      int format;
      
      if (rw) {
            pcm_handle=obj->write_handle;
      }
      else pcm_handle=obj->read_handle;
      
      /* Allocate the snd_pcm_hw_params_t structure on the stack. */
    snd_pcm_hw_params_alloca(&hwparams);
      
      /* Init hwparams with full configuration space */
    if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
            g_warning("alsa_set_params: Cannot configure this PCM device.\n");
            return(-1);
    }
      
      if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
      g_warning("alsa_set_params: Error setting access.\n");
      return(-1);
    }
      /* Set sample format */
#ifdef WORDS_BIGENDIAN
      format=SND_PCM_FORMAT_S16_BE;
#else
      format=SND_PCM_FORMAT_S16_LE;
#endif
    if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) < 0) {
      g_warning("alsa_set_params: Error setting format.\n");
      return(-1);
    }
      /* Set number of channels */
      if (stereo) channels=2;
      else channels=1;
    if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, channels) < 0) {
      g_warning("alsa_set_params: Error setting channels.\n");
      return(-1);
    }
      /* Set sample rate. If the exact rate is not supported */
    /* by the hardware, use nearest possible rate.         */ 
      exact_value=rate;
      dir=0;
    if ((err=snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_value, &dir))<0){
            g_warning("alsa_set_params: Error setting rate to %i:%s",rate,snd_strerror(err));
            return -1;
      }
    if (dir != 0) {
      g_warning("alsa_set_params: The rate %d Hz is not supported by your hardware.\n "
            "==> Using %d Hz instead.\n", rate, exact_value);
    }
      /* choose greater period size when rate is high */
      periodsize=periodsize*(rate/8000);  
      
      /* Set buffer size (in frames). The resulting latency is given by */
    /* latency = periodsize * periods / (rate * bytes_per_frame)     */
      /*
      fsize=periodsize * periods;
      exact_value=fsize;
    if ((err=snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams,&exact_value)) < 0) {
      g_warning("alsa_set_params: Error setting buffer size:%s",snd_strerror(err));
      return(-1);
    }
      if (fsize!= exact_value) {
      g_warning("alsa_set_params: The buffer size %d is not supported by your hardware.\n "
            "==> Using %d instead.\n", fsize, exact_value);
    }
      */
      /* set period size */
      exact_value=periodsize;
      dir=0;
    if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &exact_value, &dir) < 0) {
      g_warning("alsa_set_params: Error setting period size.\n");
      return(-1);
    }
      if (dir != 0) {
      g_warning("alsa_set_params: The period size %d is not supported by your hardware.\n "
            "==> Using %d instead.\n", periodsize, exact_value);
    }
      periodsize=exact_value;
      /* Set number of periods. Periods used to be called fragments. */ 
      exact_value=periods;
      dir=0;
    if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &exact_value, &dir) < 0) {
      g_warning("alsa_set_params: Error setting periods.\n");
      return(-1);
    }
      if (dir != 0) {
      g_warning("alsa_set_params: The number of periods %d is not supported by your hardware.\n "
            "==> Using %d instead.\n", periods, exact_value);
    }
      /* Apply HW parameter settings to */
    /* PCM device and prepare device  */
    if ((err=snd_pcm_hw_params(pcm_handle, hwparams)) < 0) {
      g_warning("alsa_set_params: Error setting HW params:%s",snd_strerror(err));
      return(-1);
    }
      /*prepare sw params */
      if (rw){
      snd_pcm_sw_params_alloca(&swparams);
      snd_pcm_sw_params_current(pcm_handle, swparams);
      if ((err=snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams,periodsize*2 ))<0){
            g_warning("alsa_set_params: Error setting start threshold:%s",snd_strerror(err));
            return -1;
      }
      if ((err=snd_pcm_sw_params(pcm_handle, swparams))<0){
            g_warning("alsa_set_params: Error setting SW params:%s",snd_strerror(err));
            return(-1);
      }
      }
      obj->frame_size=channels*(bits/8);
      SND_CARD(obj)->bsize=periodsize*obj->frame_size;
      //SND_CARD(obj)->bsize=4096;
      obj->frames=periodsize;
      g_message("alsa_set_params:  blocksize=%i.",SND_CARD(obj)->bsize);
      return SND_CARD(obj)->bsize;  
}

int alsa_card_open_r(AlsaCard *obj,int bits,int stereo,int rate)
{
      int bsize;
      int err;
      snd_pcm_t *pcm_handle;
      gchar *pcmdev;
      if (over_pcmdev!=NULL) pcmdev=over_pcmdev;
      else pcmdev=obj->pcmdev;
      
      if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_CAPTURE,SND_PCM_NONBLOCK) < 0) {
            g_warning("alsa_card_open_r: Error opening PCM device %s\n",obj->pcmdev );
            return -1;
      }
      g_return_val_if_fail(pcm_handle!=NULL,-1);
      obj->read_handle=pcm_handle;
      if ((bsize=alsa_set_params(obj,0,bits,stereo,rate))<0){
            snd_pcm_close(pcm_handle);
            obj->read_handle=NULL;
            return -1;
      }
      obj->readbuf=g_malloc0(bsize);
      
      err=snd_pcm_start(obj->read_handle);
      if (err<0){
            g_warning("Cannot start read pcm: %s", snd_strerror(err));
      }
      obj->readpos=0;
      SND_CARD(obj)->bsize=bsize;
      SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED;
      return 0;
}

int alsa_card_open_w(AlsaCard *obj,int bits,int stereo,int rate)
{
      int err,bsize;
      snd_pcm_t *pcm_handle;
      gchar *pcmdev;
      if (over_pcmdev!=NULL) pcmdev=over_pcmdev;
      else pcmdev=obj->pcmdev;
      
      if (snd_pcm_open(&pcm_handle, pcmdev,SND_PCM_STREAM_PLAYBACK,SND_PCM_NONBLOCK) < 0) {
      g_warning("alsa_card_open_w: Error opening PCM device %s\n", obj->pcmdev);
      return -1;
    }
      obj->write_handle=pcm_handle;
      if ((bsize=alsa_set_params(obj,1,bits,stereo,rate))<0){
            snd_pcm_close(pcm_handle);
            obj->write_handle=NULL;
            return -1;
      }
      obj->writebuf=g_malloc0(bsize);
      
      obj->writepos=0;
      SND_CARD(obj)->bsize=bsize;
      SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED;
      return 0;
}


void alsa_card_set_blocking_mode(AlsaCard *obj, gboolean yesno){
      if (obj->read_handle!=NULL) snd_pcm_nonblock(obj->read_handle,!yesno);
      if (obj->write_handle!=NULL) snd_pcm_nonblock(obj->write_handle,!yesno);
}

void alsa_card_close_r(AlsaCard *obj)
{
      if (obj->read_handle!=NULL){
            snd_pcm_close(obj->read_handle);
            obj->read_handle=NULL;
            g_free(obj->readbuf);
            obj->readbuf=NULL;
      }
}

void alsa_card_close_w(AlsaCard *obj)
{
      if (obj->write_handle!=NULL){
            snd_pcm_close(obj->write_handle);
            obj->write_handle=NULL;
            g_free(obj->writebuf);
            obj->writebuf=NULL;
      }
}

int alsa_card_probe(AlsaCard *obj,int bits,int stereo,int rate)
{
      int ret;
      ret=alsa_card_open_w(obj,bits,stereo,rate);
      if (ret<0) return -1;
      ret=SND_CARD(obj)->bsize;
      alsa_card_close_w(obj);
      return ret;
}


void alsa_card_destroy(AlsaCard *obj)
{
      snd_card_uninit(SND_CARD(obj));
      g_free(obj->pcmdev);
      if (obj->readbuf!=0) g_free(obj->readbuf);
      if (obj->writebuf!=0) g_free(obj->writebuf);    
}

gboolean alsa_card_can_read(AlsaCard *obj)
{
      int frames;
      g_return_val_if_fail(obj->read_handle!=NULL,0);
      if (obj->readpos!=0) return TRUE;
      if ( frames=snd_pcm_avail_update(obj->read_handle)>=obj->frames) return 1;
      //g_message("frames=%i",frames);
      return 0;
}



int __alsa_card_read(AlsaCard *obj,char *buf,int bsize)
{
      int err;
      sigset_t set;
      sigemptyset(&set);
      sigaddset(&set,SIGALRM);
      sigprocmask(SIG_BLOCK,&set,NULL);
      err=snd_pcm_readi(obj->read_handle,buf,bsize/obj->frame_size);
      if (err<0) {
            if (err!=-EPIPE){
                  g_warning("alsa_card_read: snd_pcm_readi() failed:%s.",snd_strerror(err));
            }
            snd_pcm_prepare(obj->read_handle);
            err=snd_pcm_readi(obj->read_handle,buf,bsize/obj->frame_size);
            if (err<0) g_warning("alsa_card_read: snd_pcm_readi() failed:%s.",snd_strerror(err));
      }
      sigprocmask(SIG_UNBLOCK,&set,NULL);
      return err;
}

int alsa_card_read(AlsaCard *obj,char *buf,int size)
{
      int err;
      gint bsize=SND_CARD(obj)->bsize;
      g_return_val_if_fail(obj->read_handle!=NULL,-1);
      if (size<bsize){
            gint canread=MIN(bsize-obj->readpos,size);
            
            if (obj->readpos==0){
                  err=__alsa_card_read(obj,obj->readbuf,bsize);
            }
                  
            memcpy(buf,&obj->readbuf[obj->readpos],canread);
            obj->readpos+=canread;
            if (obj->readpos>=bsize) obj->readpos=0;
            return canread;
      }else{
            err=__alsa_card_read(obj,buf,size);
            return err;
      }
      
}

int __alsa_card_write(AlsaCard *obj,char *buf,int size)
{
      int err;
      sigset_t set;
      sigemptyset(&set);
      sigaddset(&set,SIGALRM);
      sigprocmask(SIG_BLOCK,&set,NULL);
      if ((err=snd_pcm_writei(obj->write_handle,buf,size/obj->frame_size))<0){
            if (err!=-EPIPE){
                  g_warning("alsa_card_write: snd_pcm_writei() failed:%s.",snd_strerror(err));
            }
            snd_pcm_prepare(obj->write_handle);
            err=snd_pcm_writei(obj->write_handle,buf,size/obj->frame_size);
            if (err<0) g_warning("alsa_card_write: Error writing sound buffer (size=%i):%s",size,snd_strerror(err));
            
      }
      sigprocmask(SIG_UNBLOCK,&set,NULL);
      return err;
}

int alsa_card_write(AlsaCard *obj,char *buf,int size)
{
      int err;
      gint bsize=SND_CARD(obj)->bsize;
      g_return_val_if_fail(obj->write_handle!=NULL,-1);
      if (size<bsize){
            gint canwrite;
            
            canwrite=MIN(bsize-obj->writepos,size);
            memcpy(&obj->writebuf[obj->writepos],buf,canwrite);
            obj->writepos+=canwrite;
            if (obj->writepos>=bsize){
                  err=__alsa_card_write(obj,obj->writebuf,bsize);
                  obj->writepos=0;
            }
            return canwrite;
      }else{
            return __alsa_card_write(obj,buf,bsize);
      }
}

snd_mixer_t *alsa_mixer_open(AlsaCard *obj){
      snd_mixer_t *mixer=NULL;
      int err;
      err=snd_mixer_open(&mixer,0);
      if (err<0){
            g_warning("Could not open alsa mixer: %s",snd_strerror(err));
            return NULL;
      }
      if ((err = snd_mixer_attach (mixer, obj->mixdev)) < 0){
            g_warning("Could not attach mixer to card: %s",snd_strerror(err));
            snd_mixer_close(mixer);
            return NULL;
      }
      if ((err = snd_mixer_selem_register (mixer, NULL, NULL)) < 0){
            g_warning("snd_mixer_selem_register: %s",snd_strerror(err));
            snd_mixer_close(mixer);
            return NULL;
      }
      if ((err = snd_mixer_load (mixer)) < 0){
            g_warning("snd_mixer_load: %s",snd_strerror(err));
            snd_mixer_close(mixer);
            return NULL;
      }
      obj->mixer=mixer;
      return mixer;
}

void alsa_mixer_close(AlsaCard *obj){
      snd_mixer_close(obj->mixer);
      obj->mixer=NULL;
}

typedef enum {CAPTURE, PLAYBACK, CAPTURE_SWITCH, PLAYBACK_SWITCH} MixerAction;

static gint get_mixer_element(snd_mixer_t *mixer,const char *name, MixerAction action){
      long value=0;
      const char *elemname;
      snd_mixer_elem_t *elem;
      int err;
      elem=snd_mixer_first_elem(mixer);
      while (elem!=NULL){
            elemname=snd_mixer_selem_get_name(elem);
            //g_message("Found alsa mixer element %s.",elemname);
            if (strcmp(elemname,name)==0){
                  switch (action){
                        case CAPTURE:
                        if (snd_mixer_selem_has_capture_volume(elem)){
                              err=snd_mixer_selem_get_capture_volume(elem,SND_MIXER_SCHN_UNKNOWN,&value);
                              if (err<0) g_warning("Could not get capture volume for %s:%s",name,snd_strerror(err));
                              //else g_message("Succesfully get capture level for %s.",elemname);
                              break;
                        }
                        break;
                        case PLAYBACK:
                        if (snd_mixer_selem_has_playback_volume(elem)){
                              err=snd_mixer_selem_get_playback_volume(elem,SND_MIXER_SCHN_FRONT_LEFT,&value);
                              if (err<0) g_warning("Could not get playback volume for %s:%s",name,snd_strerror(err));
                              //else g_message("Succesfully get playback level for %s.",elemname);
                              break;
                        }
                        break;
                        case CAPTURE_SWITCH:
                        
                        break;
                  }
            }
            elem=snd_mixer_elem_next(elem);
      }
      
      return (value*100)/30;
}


static void set_mixer_element(snd_mixer_t *mixer,const char *name, gint level,MixerAction action){
      const char *elemname;
      snd_mixer_elem_t *elem;
      int tmp;
      
      elem=snd_mixer_first_elem(mixer);
      
      while (elem!=NULL){
            elemname=snd_mixer_selem_get_name(elem);
            //g_message("Found alsa mixer element %s.",elemname);
            if (strcmp(elemname,name)==0){
                  switch(action){
                        case CAPTURE:
                        if (snd_mixer_selem_has_capture_volume(elem)){
                              snd_mixer_selem_set_capture_volume_all(elem,(level*30)/100);
                              //g_message("Succesfully set capture level for %s.",elemname);
                              return;
                        }
                        break;
                        case PLAYBACK:
                        if (snd_mixer_selem_has_playback_volume(elem)){
                              snd_mixer_selem_set_playback_volume_all(elem,(level*30)/100);
                              //g_message("Succesfully set playback level for %s.",elemname);
                              return;
                        }
                        break;
                        case CAPTURE_SWITCH:
                        if (snd_mixer_selem_has_capture_switch(elem)){
                              snd_mixer_selem_set_capture_switch_all(elem,level);
                              //g_message("Succesfully set capture switch for %s.",elemname);
                        }
                        break;
                        case PLAYBACK_SWITCH:
                        if (snd_mixer_selem_has_playback_switch(elem)){
                              snd_mixer_selem_set_playback_switch_all(elem,level);
                              //g_message("Succesfully set capture switch for %s.",elemname);
                        }
                        break;

                  }
            }
            elem=snd_mixer_elem_next(elem);
      }

      return ;
}


void alsa_card_set_level(AlsaCard *obj,gint way,gint a)
{     
      snd_mixer_t *mixer;
      mixer=alsa_mixer_open(obj);
      if (mixer==NULL) return ;
      switch(way){
            case SND_CARD_LEVEL_GENERAL:
                  set_mixer_element(mixer,"Master",a,PLAYBACK);
            break;
            case SND_CARD_LEVEL_INPUT:
                  set_mixer_element(mixer,"Capture",a,CAPTURE);
            break;
            case SND_CARD_LEVEL_OUTPUT:
                  set_mixer_element(mixer,"PCM",a,PLAYBACK);
            break;
            default:
                  g_warning("alsa_card_set_level: unsupported command.");
      }
      alsa_mixer_close(obj);
}

gint alsa_card_get_level(AlsaCard *obj,gint way)
{
      snd_mixer_t *mixer;
      gint value;
      mixer=alsa_mixer_open(obj);
      if (mixer==NULL) return 0;
      switch(way){
            case SND_CARD_LEVEL_GENERAL:
                  value=get_mixer_element(mixer,"Master",PLAYBACK);
            break;
            case SND_CARD_LEVEL_INPUT:
                  value=get_mixer_element(mixer,"Capture",CAPTURE);
            break;
            case SND_CARD_LEVEL_OUTPUT:
                  value=get_mixer_element(mixer,"PCM",PLAYBACK);
            break;
            default:
                  g_warning("alsa_card_set_level: unsupported command.");
      }
      alsa_mixer_close(obj);
      return value;
}

void alsa_card_set_source(AlsaCard *obj,int source)
{
      snd_mixer_t *mixer;
      mixer=alsa_mixer_open(obj);
      if (mixer==NULL) return;
      switch (source){
            case 'm':
                  set_mixer_element(mixer,"Mic",1,CAPTURE_SWITCH);
                  set_mixer_element(mixer,"Capture",1,CAPTURE_SWITCH);
                  break;
            case 'l':
                  set_mixer_element(mixer,"Line",1,CAPTURE_SWITCH);
                  set_mixer_element(mixer,"Capture",1,CAPTURE_SWITCH);
                  break;
      }
}

MSFilter *alsa_card_create_read_filter(AlsaCard *card)
{
      MSFilter *f=ms_snd_read_new();
      ms_snd_read_set_device(MS_SND_READ(f),SND_CARD(card)->index);
      return f;
}

MSFilter *alsa_card_create_write_filter(AlsaCard *card)
{
      MSFilter *f=ms_snd_write_new();
      ms_snd_write_set_device(MS_SND_WRITE(f),SND_CARD(card)->index);
      return f;
}


SndCard * alsa_card_new(gint devid)
{
      AlsaCard * obj;
      SndCard *base;
      int err;
      gchar *name=NULL;
      
      /* carefull: this is an alsalib call despite its name! */
      err=snd_card_get_name(devid,&name);
      if (err<0) {
            return NULL;
      }
      obj= g_new0(AlsaCard,1);
      base= SND_CARD(obj);
      snd_card_init(base);
      
      base->card_name=g_strdup_printf("%s (Advanced Linux Sound Architecture)",name);
      base->_probe=(SndCardOpenFunc)alsa_card_probe;
      base->_open_r=(SndCardOpenFunc)alsa_card_open_r;
      base->_open_w=(SndCardOpenFunc)alsa_card_open_w;
      base->_can_read=(SndCardPollFunc)alsa_card_can_read;
      base->_set_blocking_mode=(SndCardSetBlockingModeFunc)alsa_card_set_blocking_mode;
      base->_read=(SndCardIOFunc)alsa_card_read;
      base->_write=(SndCardIOFunc)alsa_card_write;
      base->_close_r=(SndCardCloseFunc)alsa_card_close_r;
      base->_close_w=(SndCardCloseFunc)alsa_card_close_w;
      base->_set_rec_source=(SndCardMixerSetRecSourceFunc)alsa_card_set_source;
      base->_set_level=(SndCardMixerSetLevelFunc)alsa_card_set_level;
      base->_get_level=(SndCardMixerGetLevelFunc)alsa_card_get_level;
      base->_destroy=(SndCardDestroyFunc)alsa_card_destroy;
      base->_create_read_filter=(SndCardCreateFilterFunc)alsa_card_create_read_filter;
      base->_create_write_filter=(SndCardCreateFilterFunc)alsa_card_create_write_filter;
      
      
      obj->pcmdev=g_strdup_printf("plughw:%i,0",devid);
      obj->mixdev=g_strdup_printf("hw:%i",devid);
      obj->readbuf=NULL;
      obj->writebuf=NULL;
      return base;
}


gint alsa_card_manager_init(SndCardManager *m, gint index)
{
      gint devindex;
      gint i;
      gint found=0;
      gchar *name=NULL;
      for(devindex=0;index<MAX_SND_CARDS && devindex<MAX_SND_CARDS ;devindex++){
            if (snd_card_get_name(devindex,&name)==0){
                  g_message("Found ALSA device: %s",name);
                  m->cards[index]=alsa_card_new(devindex);
                  m->cards[index]->index=index;
                  found++;
                  index++;
            }
      }
      return found;
}

void alsa_card_manager_set_default_pcm_device(gchar *pcmdev){
      if (over_pcmdev!=NULL){
            g_free(over_pcmdev);    
      }
      over_pcmdev=g_strdup(pcmdev);
}

#endif

Generated by  Doxygen 1.6.0   Back to index