| /* |
| 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include "osscard.h" |
| |
| #include "msossread.h" |
| #include "msosswrite.h" |
| |
| #ifdef HAVE_SYS_SOUNDCARD_H |
| #include <sys/soundcard.h> |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/time.h> |
| |
| #if 0 |
| void * oss_thread(OssCard *obj) |
| { |
| gint i; |
| gint err; |
| g_message("oss_thread: starting **********"); |
| while(1){ |
| for(i=0;i<OSS_CARD_BUFFERS;i++){ |
| g_mutex_lock(obj->lock); |
| if (obj->ref==0){ |
| g_cond_signal(obj->cond); |
| g_mutex_unlock(obj->lock); |
| g_thread_exit(NULL); |
| } |
| g_mutex_unlock(obj->lock); |
| obj->readindex=i; |
| |
| err=read(obj->fd,obj->readbuf[i],SND_CARD(obj)->bsize); |
| if (err<0) g_warning("oss_thread: read() error:%s.",strerror(errno)); |
| obj->writeindex=i; |
| write(obj->fd,obj->writebuf[i],SND_CARD(obj)->bsize); |
| memset(obj->writebuf[i],0,SND_CARD(obj)->bsize); |
| } |
| } |
| } |
| #endif |
| int oss_open(OssCard *obj, int bits,int stereo, int rate) |
| { |
| int fd; |
| int p=0,cond=0; |
| int i=0; |
| int min_size=0,blocksize=512; |
| int err; |
| |
| //g_message("opening sound device"); |
| fd=open(obj->dev_name,O_RDWR|O_NONBLOCK); |
| if (fd<0) return -EWOULDBLOCK; |
| /* unset nonblocking mode */ |
| /* We wanted non blocking open but now put it back to normal ; thanks Xine !*/ |
| fcntl(fd, F_SETFL, fcntl(fd, F_GETFL)&~O_NONBLOCK); |
| |
| /* reset is maybe not needed but takes time*/ |
| /*ioctl(fd, SNDCTL_DSP_RESET, 0); */ |
| |
| |
| #ifdef WORDS_BIGENDIAN |
| p=AFMT_U16_BE; |
| #else |
| p=AFMT_U16_LE; |
| #endif |
| |
| err=ioctl(fd,SNDCTL_DSP_SETFMT,&p); |
| if (err<0){ |
| g_warning("oss_open: can't set sample format:%s.",strerror(errno)); |
| } |
| |
| |
| p = bits; /* 16 bits */ |
| err=ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &p); |
| if (err<0){ |
| g_warning("oss_open: can't set sample size to %i:%s.",bits,strerror(errno)); |
| } |
| |
| p = rate; /* rate in khz*/ |
| err=ioctl(fd, SNDCTL_DSP_SPEED, &p); |
| if (err<0){ |
| g_warning("oss_open: can't set sample rate to %i:%s.",rate,strerror(errno)); |
| } |
| |
| p = stereo; /* stereo or not */ |
| err=ioctl(fd, SNDCTL_DSP_STEREO, &p); |
| if (err<0){ |
| g_warning("oss_open: can't set mono/stereo mode:%s.",strerror(errno)); |
| } |
| |
| if (rate==16000) blocksize=4096; /* oss emulation is not very good at 16khz */ |
| else blocksize=blocksize*(rate/8000); |
| ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); |
| |
| /* try to subdivide BLKSIZE to reach blocksize if necessary */ |
| if (min_size>blocksize) |
| { |
| cond=1; |
| p=min_size/blocksize; |
| while(cond) |
| { |
| i=ioctl(fd, SNDCTL_DSP_SUBDIVIDE, &p); |
| //printf("SUB_DIVIDE said error=%i,errno=%i\n",i,errno); |
| if ((i==0) || (p==1)) cond=0; |
| else p=p/2; |
| } |
| } |
| ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); |
| if (min_size>blocksize) |
| { |
| g_warning("dsp block size set to %i.",min_size); |
| }else{ |
| /* no need to access the card with less latency than needed*/ |
| min_size=blocksize; |
| } |
| |
| g_message("dsp blocksize is %i.",min_size); |
| |
| /* start recording !!! Alex */ |
| { |
| int fl,res; |
| |
| fl=PCM_ENABLE_OUTPUT|PCM_ENABLE_INPUT; |
| res=ioctl(fd, SNDCTL_DSP_SETTRIGGER, &fl); |
| if (res<0) g_warning("OSS_TRIGGER: %s",strerror(errno)); |
| } |
| |
| obj->fd=fd; |
| obj->readpos=0; |
| obj->writepos=0; |
| SND_CARD(obj)->bits=bits; |
| SND_CARD(obj)->stereo=stereo; |
| SND_CARD(obj)->rate=rate; |
| SND_CARD(obj)->bsize=min_size; |
| return fd; |
| } |
| |
| int oss_card_probe(OssCard *obj,int bits,int stereo,int rate) |
| { |
| |
| int fd; |
| int p=0,cond=0; |
| int i=0; |
| int min_size=0,blocksize=512; |
| |
| if (obj->fd>0) return SND_CARD(obj)->bsize; |
| fd=open(obj->dev_name,O_RDWR|O_NONBLOCK); |
| if (fd<0) { |
| g_warning("oss_card_probe: can't open %s: %s.",obj->dev_name,strerror(errno)); |
| return -1; |
| } |
| ioctl(fd, SNDCTL_DSP_RESET, 0); |
| |
| p = bits; /* 16 bits */ |
| ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &p); |
| |
| p = stereo; /* number of channels */ |
| ioctl(fd, SNDCTL_DSP_CHANNELS, &p); |
| |
| p = rate; /* rate in khz*/ |
| ioctl(fd, SNDCTL_DSP_SPEED, &p); |
| |
| ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); |
| |
| /* try to subdivide BLKSIZE to reach blocksize if necessary */ |
| if (min_size>blocksize) |
| { |
| cond=1; |
| p=min_size/blocksize; |
| while(cond) |
| { |
| i=ioctl(fd, SNDCTL_DSP_SUBDIVIDE, &p); |
| //printf("SUB_DIVIDE said error=%i,errno=%i\n",i,errno); |
| if ((i==0) || (p==1)) cond=0; |
| else p=p/2; |
| } |
| } |
| ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &min_size); |
| if (min_size>blocksize) |
| { |
| g_warning("dsp block size set to %i.",min_size); |
| }else{ |
| /* no need to access the card with less latency than needed*/ |
| min_size=blocksize; |
| } |
| close(fd); |
| return min_size; |
| } |
| |
| |
| int oss_card_open(OssCard *obj,int bits,int stereo,int rate) |
| { |
| int fd; |
| obj->ref++; |
| if (obj->fd==0){ |
| fd=oss_open(obj,bits,stereo,rate); |
| if (fd<0) { |
| obj->fd=0; |
| obj->ref--; |
| return -1; |
| } |
| } |
| |
| obj->readbuf=g_malloc0(SND_CARD(obj)->bsize); |
| obj->writebuf=g_malloc0(SND_CARD(obj)->bsize); |
| |
| SND_CARD(obj)->flags|=SND_CARD_FLAGS_OPENED; |
| return 0; |
| } |
| |
| void oss_card_close(OssCard *obj) |
| { |
| int i; |
| obj->ref--; |
| if (obj->ref==0) { |
| close(obj->fd); |
| obj->fd=0; |
| SND_CARD(obj)->flags&=~SND_CARD_FLAGS_OPENED; |
| g_free(obj->readbuf); |
| obj->readbuf=NULL; |
| g_free(obj->writebuf); |
| obj->writebuf=NULL; |
| |
| } |
| } |
| |
| void oss_card_destroy(OssCard *obj) |
| { |
| snd_card_uninit(SND_CARD(obj)); |
| g_free(obj->dev_name); |
| g_free(obj->mixdev_name); |
| if (obj->readbuf!=NULL) g_free(obj->readbuf); |
| if (obj->writebuf!=NULL) g_free(obj->writebuf); |
| } |
| |
| gboolean oss_card_can_read(OssCard *obj) |
| { |
| struct timeval tout={0,0}; |
| int err; |
| fd_set fdset; |
| if (obj->readpos!=0) return TRUE; |
| FD_ZERO(&fdset); |
| FD_SET(obj->fd,&fdset); |
| err=select(obj->fd+1,&fdset,NULL,NULL,&tout); |
| if (err>0) return TRUE; |
| else return FALSE; |
| } |
| |
| int oss_card_read(OssCard *obj,char *buf,int size) |
| { |
| int err; |
| gint bsize=SND_CARD(obj)->bsize; |
| if (size<bsize){ |
| gint canread=MIN(bsize-obj->readpos,size); |
| if (obj->readpos==0){ |
| err=read(obj->fd,obj->readbuf,bsize); |
| if (err<0) { |
| g_warning("oss_card_read: read() failed:%s.",strerror(errno)); |
| return -1; |
| } |
| } |
| |
| memcpy(buf,&obj->readbuf[obj->readpos],canread); |
| obj->readpos+=canread; |
| if (obj->readpos>=bsize) obj->readpos=0; |
| return canread; |
| }else{ |
| err=read(obj->fd,buf,size); |
| if (err<0) { |
| g_warning("oss_card_read: read-2() failed:%s.",strerror(errno)); |
| } |
| return err; |
| } |
| |
| } |
| |
| int oss_card_write(OssCard *obj,char *buf,int size) |
| { |
| int err; |
| gint bsize=SND_CARD(obj)->bsize; |
| |
| 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=write(obj->fd,obj->writebuf,bsize); |
| obj->writepos=0; |
| } |
| return canwrite; |
| }else{ |
| return write(obj->fd,buf,bsize); |
| } |
| } |
| |
| void oss_card_set_level(OssCard *obj,gint way,gint a) |
| { |
| int p,mix_fd; |
| int osscmd; |
| g_return_if_fail(obj->mixdev_name!=NULL); |
| #ifdef HAVE_SYS_SOUNDCARD_H |
| switch(way){ |
| case SND_CARD_LEVEL_GENERAL: |
| osscmd=SOUND_MIXER_VOLUME; |
| break; |
| case SND_CARD_LEVEL_INPUT: |
| osscmd=SOUND_MIXER_IGAIN; |
| break; |
| case SND_CARD_LEVEL_OUTPUT: |
| osscmd=SOUND_MIXER_PCM; |
| break; |
| default: |
| g_warning("oss_card_set_level: unsupported command."); |
| return; |
| } |
| p=(((int)a)<<8 | (int)a); |
| mix_fd = open(obj->mixdev_name, O_WRONLY); |
| ioctl(mix_fd,MIXER_WRITE(osscmd), &p); |
| close(mix_fd); |
| #endif |
| } |
| |
| gint oss_card_get_level(OssCard *obj,gint way) |
| { |
| int p=0,mix_fd; |
| int osscmd; |
| g_return_if_fail(obj->mixdev_name!=NULL); |
| #ifdef HAVE_SYS_SOUNDCARD_H |
| switch(way){ |
| case SND_CARD_LEVEL_GENERAL: |
| osscmd=SOUND_MIXER_VOLUME; |
| break; |
| case SND_CARD_LEVEL_INPUT: |
| osscmd=SOUND_MIXER_IGAIN; |
| break; |
| case SND_CARD_LEVEL_OUTPUT: |
| osscmd=SOUND_MIXER_PCM; |
| break; |
| default: |
| g_warning("oss_card_get_level: unsupported command."); |
| return -1; |
| } |
| mix_fd = open(obj->mixdev_name, O_RDONLY); |
| ioctl(mix_fd,MIXER_READ(SOUND_MIXER_VOLUME), &p); |
| close(mix_fd); |
| #endif |
| return p>>8; |
| } |
| |
| void oss_card_set_source(OssCard *obj,int source) |
| { |
| gint p=0; |
| gint mix_fd; |
| g_return_if_fail(obj->mixdev_name!=NULL); |
| #ifdef HAVE_SYS_SOUNDCARD_H |
| if (source == 'c') |
| p = 1 << SOUND_MIXER_CD; |
| if (source == 'l') |
| p = 1 << SOUND_MIXER_LINE; |
| if (source == 'm') |
| p = 1 << SOUND_MIXER_MIC; |
| |
| |
| mix_fd = open(obj->mixdev_name, O_WRONLY); |
| ioctl(mix_fd, SOUND_MIXER_WRITE_RECSRC, &p); |
| close(mix_fd); |
| #endif |
| } |
| |
| MSFilter *oss_card_create_read_filter(OssCard *card) |
| { |
| MSFilter *f=ms_oss_read_new(); |
| ms_oss_read_set_device(MS_OSS_READ(f),SND_CARD(card)->index); |
| return f; |
| } |
| |
| MSFilter *oss_card_create_write_filter(OssCard *card) |
| { |
| MSFilter *f=ms_oss_write_new(); |
| ms_oss_write_set_device(MS_OSS_WRITE(f),SND_CARD(card)->index); |
| return f; |
| } |
| |
| |
| SndCard * oss_card_new(char *devname, char *mixdev_name) |
| { |
| OssCard * obj= g_new0(OssCard,1); |
| SndCard *base= SND_CARD(obj); |
| snd_card_init(base); |
| obj->dev_name=g_strdup(devname); |
| obj->mixdev_name=g_strdup( mixdev_name); |
| #ifdef HAVE_GLIB |
| base->card_name=g_strdup_printf("%s (Open Sound System)",devname); |
| #else |
| base->card_name=malloc(100); |
| snprintf(base->card_name, 100, "%s (Open Sound System)",devname); |
| #endif |
| base->_probe=(SndCardOpenFunc)oss_card_probe; |
| base->_open_r=(SndCardOpenFunc)oss_card_open; |
| base->_open_w=(SndCardOpenFunc)oss_card_open; |
| base->_can_read=(SndCardPollFunc)oss_card_can_read; |
| base->_read=(SndCardIOFunc)oss_card_read; |
| base->_write=(SndCardIOFunc)oss_card_write; |
| base->_close_r=(SndCardCloseFunc)oss_card_close; |
| base->_close_w=(SndCardCloseFunc)oss_card_close; |
| base->_set_rec_source=(SndCardMixerSetRecSourceFunc)oss_card_set_source; |
| base->_set_level=(SndCardMixerSetLevelFunc)oss_card_set_level; |
| base->_get_level=(SndCardMixerGetLevelFunc)oss_card_get_level; |
| base->_destroy=(SndCardDestroyFunc)oss_card_destroy; |
| base->_create_read_filter=(SndCardCreateFilterFunc)oss_card_create_read_filter; |
| base->_create_write_filter=(SndCardCreateFilterFunc)oss_card_create_write_filter; |
| return base; |
| } |
| |
| #define DSP_NAME "/dev/dsp" |
| #define MIXER_NAME "/dev/mixer" |
| |
| gint oss_card_manager_init(SndCardManager *manager, gint tabindex) |
| { |
| gchar *devname; |
| gchar *mixername; |
| gint devindex=0; |
| gint found=0; |
| |
| /* search for /dev/dsp and /dev/mixer */ |
| #ifdef HAVE_GLIB |
| if (g_file_test(DSP_NAME,G_FILE_TEST_EXISTS)){ |
| tabindex++; |
| devindex++; |
| manager->cards[0]=oss_card_new(DSP_NAME,MIXER_NAME); |
| manager->cards[0]->index=0; |
| found++; |
| g_message("Found /dev/dsp."); |
| } |
| for (;tabindex<MAX_SND_CARDS && devindex<MAX_SND_CARDS ;devindex++){ |
| devname=g_strdup_printf("%s%i",DSP_NAME,devindex); |
| mixername=g_strdup_printf("%s%i",MIXER_NAME,devindex); |
| if (g_file_test(devname,G_FILE_TEST_EXISTS)){ |
| manager->cards[tabindex]=oss_card_new(devname,mixername); |
| manager->cards[tabindex]->index=tabindex; |
| tabindex++; |
| found++; |
| } |
| g_free(devname); |
| g_free(mixername); |
| } |
| #else |
| if (access(DSP_NAME,F_OK)==0){ |
| tabindex++; |
| devindex++; |
| manager->cards[0]=oss_card_new(DSP_NAME,MIXER_NAME); |
| manager->cards[0]->index=0; |
| found++; |
| g_message("Found /dev/dsp."); |
| } |
| for (;tabindex<MAX_SND_CARDS && devindex<MAX_SND_CARDS ;devindex++){ |
| devname=malloc(100); |
| snprintf(devname, 100, "%s%i",DSP_NAME,devindex); |
| mixername=malloc(100); |
| snprintf(mixername, 100, "%s%i",MIXER_NAME,devindex); |
| |
| if (access(devname,F_OK)==0){ |
| manager->cards[tabindex]=oss_card_new(devname,mixername); |
| manager->cards[tabindex]->index=tabindex; |
| tabindex++; |
| found++; |
| } |
| g_free(devname); |
| g_free(mixername); |
| } |
| #endif |
| if (tabindex==0) g_warning("No sound cards found !"); |
| return found; |
| } |
| |
| |
| #endif |