/* The mediastreamer library aims at providing modular media processing and I/O for linphone, but also for any telephony application. Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org 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 library 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "alsacard.h" #ifdef HAVE_ALSA_ASOUNDLIB_H static gchar *over_pcmdev=NULL; #include "msossread.h" #include "msosswrite.h" #include 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*obj->frame_size; } 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 (sizereadpos,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 (sizewritepos,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; long sndMixerPMin; long sndMixerPMax; long newvol; 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_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); err=snd_mixer_selem_get_capture_volume(elem,SND_MIXER_SCHN_UNKNOWN,&newvol); newvol-=sndMixerPMin; value=(100*newvol)/(sndMixerPMax-sndMixerPMin); 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)){ snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); err=snd_mixer_selem_get_playback_volume(elem,SND_MIXER_SCHN_FRONT_LEFT,&newvol); newvol-=sndMixerPMin; value=(100*newvol)/(sndMixerPMax-sndMixerPMin); 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; } 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; long sndMixerPMin; long sndMixerPMax; long newvol; 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_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); newvol=(((sndMixerPMax-sndMixerPMin)*level)/100)+sndMixerPMin; snd_mixer_selem_set_capture_volume_all(elem,newvol); /* //g_message("Succesfully set capture level for %s.",elemname); */ return; } break; case PLAYBACK: if (snd_mixer_selem_has_playback_volume(elem)){ snd_mixer_selem_get_playback_volume_range(elem, &sndMixerPMin, &sndMixerPMax); newvol=(((sndMixerPMax-sndMixerPMin)*level)/100)+sndMixerPMin; snd_mixer_selem_set_playback_volume_all(elem,newvol); /* //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("oss_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("oss_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_oss_read_new(); ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); return f; } MSFilter *alsa_card_create_write_filter(AlsaCard *card) { MSFilter *f=ms_oss_write_new(); ms_oss_write_set_device(MS_OSS_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;indexcards[index]=alsa_card_new(devindex); m->cards[index]->index=index; found++; index++; } } return found; } void alsa_card_manager_set_default_pcm_device(const gchar *pcmdev){ if (over_pcmdev!=NULL){ g_free(over_pcmdev); } over_pcmdev=g_strdup(pcmdev); } #endif