/*************************************************************************** * Copyright (C) 2004 by Johan Maes ON4QZ * * on4qz@telenet.be * * * * http://users.telenet.be/on4qz * * * * 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 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 "soundcard.h" #include #include #include #include #include #include "utils.h" //#define DEBUGSOUNDCARD #define MODE AFMT_S16_LE /** create an instance of the soundcrd interface \param samplingrate nominal sampling rate (i.e. 8000, 11025, ...) */ soundcard::soundcard(int samplingrate) { status=CLOSED; dspInfo.speed=samplingrate; timer1=new QTimer(this); timer2=new QTimer(this); started=FALSE; #ifdef DEBUGSOUNDCARD logfile.add("soundcard created"); #endif connect(timer1,SIGNAL(timeout()),SLOT(writeNext())); connect(timer2,SIGNAL(timeout()),SLOT(slotStop())); audioBufferLen=AUDIOBUFFERSIZE; //just give it a default } /** deletes instance of the soundcard \n stop() is called before the delete of the instance \sa stop */ soundcard::~soundcard() { #ifdef DEBUGSOUNDCARD logfile.add("soundcard: deleted=%ux",this); #endif stop(); } /** sets/changes the sampligrate of the soundcrd \param samplingrate nominal sampling rate (i.e. 8000, 11025, ...) */ void soundcard::setSamplingrate(int samplingrate) { dspInfo.speed=samplingrate; } /** stops all soundcard activity and closes the interface */ void soundcard::stop() { if (status!=CLOSED) { close(dspInfo.fdDSP); started=FALSE; emit signalStopped(); timer1->stop(); // timer2->stop(); //#ifdef DEBUGSOUNDCARD logfile.add("soundcard: signal send, stop=%x,status=%d",this,status); status=CLOSED; //#endif } busy=FALSE; #ifdef DEBUGSOUNDCARD logfile.add("soundcard: busy=FALSE, stop=%x",this); #endif } /** function to be called before stopping the soundcard interface while transmitting \return The number of samples in the buffers still to be transmitted */ int soundcard::waitEnd() { audio_buf_info info; ioctl(dspInfo.fdDSP, SNDCTL_DSP_GETOSPACE, &info); return((info.fragstotal-info.fragments)*audioBufferLen/sizeof(int)); } bool soundcard::setParam(int format, int fragsize,int channels,QString &errorString) { uint fs; int temp; dspInfo.channels=channels; dspInfo.format=format; for (fs=0;fragsize>0;fs++) { fragsize>>=1; } dspInfo.fragSize=0xffff0000+fs; // encoded as MMMMSSSS where SSSS is the power of 2 // set number of channels if (ioctl (dspInfo.fdDSP, SNDCTL_DSP_CHANNELS,&(dspInfo.channels))) { errorString="Error on ioctl SNDCTL_DSP_CHANNELS"; return FALSE; } /* set the format */ int reqFormat=dspInfo.format; if (ioctl (dspInfo.fdDSP, SNDCTL_DSP_SETFMT, &(dspInfo.format))) { errorString="Error on ioctl SNDCTL_DSP_SETFMT"; return FALSE; } if (dspInfo.format !=reqFormat ) { errorString="Requested Format not supported"; return FALSE; } /* set the sampling rate */ if (ioctl (dspInfo.fdDSP, SNDCTL_DSP_SPEED, &(dspInfo.speed))) { errorString="Error on ioctl SNDCTL_DSP_SPEED"; return FALSE; } ioctl(dspInfo.fdDSP,SOUND_PCM_READ_RATE,&temp) ; if (ioctl (dspInfo.fdDSP, SNDCTL_DSP_SETFRAGMENT,&(dspInfo.fragSize))) { #ifdef DEBUGSOUNDCARD logfile.add("soundcard: setfragment failed"); #endif // no action -we'll do our best... } // and read it back if(ioctl(dspInfo.fdDSP,SNDCTL_DSP_GETBLKSIZE,&(dspInfo.fragSize))) { errorString="Error on reading fragsize"; return FALSE; } audioBufferLen=dspInfo.fragSize; return TRUE; } /** starts to receive from soundcard or file \param s name of the device to be opened (e.g. /dev/dsp) \param errorString verbose error message \return TRUE if successful, FALSE otherwise \sa stop, startTransmit, startFullDuplex */ bool soundcard::startReceive(const char *s,QString &errorString) { stop(); #ifdef DEBUGSOUNDCARD logfile.add("soundcard: startReceive=%x",this); #endif if ((dspInfo.fdDSP = open (s, O_RDONLY)) == -1) { errorString="Cannot open sounddevice"; return (FALSE); } if(setParam(MODE, AUDIOBUFFERSIZE,1,errorString)==FALSE) { close(dspInfo.fdDSP); return FALSE; } status=OPENFORREAD; return TRUE; } /** starts to transmit to soundcard or file \param s name of the device to be opened (e.g. /dev/dsp) \param errorString verbose error message \return TRUE if successful, FALSE otherwise \sa stop, startReceive, startFullDuplex */ bool soundcard::startTransmit(const char *s,QString &errorString) { stop(); #ifdef DEBUGSOUNDCARD logfile.add("soundcard: startTransmit=%x",this); #endif if ((dspInfo.fdDSP = open (s, O_WRONLY,0)) == -1) { errorString="Cannot open sounddevice"; return FALSE; } if(setParam(MODE, AUDIOBUFFERSIZE,1,errorString)==FALSE) { close(dspInfo.fdDSP); return FALSE; } status=OPENFORWRITE; return TRUE; } /** starts to receive and transmit in full duplex (if the soundcard supports it) \param s name of the device to be opened (e.g. /dev/dsp) \param errorString verbose error message \return TRUE if successful, FALSE otherwise \sa stop, startReceive, startTransmit */ bool soundcard::startFullDuplex(const char *s,QString &errorString) { stop(); errorString=""; if ((dspInfo.fdDSP = open (s, O_RDWR,0)) == -1) { errorString="Cannot open sounddevice"; return FALSE; } if (ioctl(dspInfo.fdDSP, SNDCTL_DSP_SETDUPLEX, 0)) { errorString="Cannot set to duplex"; return FALSE; } if (ioctl(dspInfo.fdDSP, SNDCTL_DSP_GETCAPS, &dspInfo.caps)) { errorString="Cannot get caps"; return FALSE; } if ((dspInfo.caps&DSP_CAP_DUPLEX) != DSP_CAP_DUPLEX) /* we have to have a full duplex audio */ { errorString="Cannot set to fullduplex"; return FALSE; } if(setParam(MODE , AUDIOBUFFERSIZE,1,errorString)==FALSE) { close(dspInfo.fdDSP); return FALSE; } status=OPENFORREADWRITE; return TRUE; } /** retrieves data from the souncard @param audioBuffer array of characters of \p audioBufferLen length. \n #audioBufferLen is set by the soundcard init routine. \return TRUE if audioBufferLen characters are read \note #audioBufferLen is the size of the array of characters, not the number of samples.\n e.g.For mono 16 bits samples, divide audioBufferLen by 2. */ bool soundcard::read(char * audioBuffer) { audio_buf_info info; int len; if (started) { ioctl(dspInfo.fdDSP, SNDCTL_DSP_GETISPACE, &info); if(info.fragments==0) { return 0; } fragmentLatency=info.fragments; } else { started=TRUE; } len = ::read(dspInfo.fdDSP,audioBuffer,audioBufferLen); #ifdef DEBUGSOUNDCARD logfile.add("latency %d %d",fragmentLatency,len); #endif return len==audioBufferLen; } /** Sends a complete buffer to the audio interface. \n This function will normally called from inside a loop until it returns success\n The #signalTransmitBufferAvailable will be sent if there are buffers available @param iLongBuffer pointer to data to be transmitted @param length number of samples to be transmitted \n If called with length=0, the transmission is stopped \return TRUE if successful, FALSE if busy */ bool soundcard::write(short int *iLongBuffer,uint length) { if(busy) return FALSE; if (length==0) { delayedStop(); } else { longBuffer=(char *)iLongBuffer; writeLength=length*sizeof(short int); writeIndex=0; timer1->start(0,TRUE); busy=TRUE; } return TRUE; } /** Sends a buffer with audioBufferLen characters to the audio interface. \n @param audioBuffer array of characters of \p audioBufferLen length\n no #signalTransmitBufferAvailable will be sent \return TRUE if successful, FALSE if busy */ bool soundcard::write(char * audioBuffer) { audio_buf_info info; #ifdef DEBUGSOUNDCARD if (status==CLOSED) { logfile.add("soundcard: status closed during write "); } #endif ioctl(dspInfo.fdDSP, SNDCTL_DSP_GETOSPACE, &info); if(info.fragments<=(info.fragstotal/2)) { // debug("frag=%d totfrag=%d, samples=%d",info.fragments,info.fragstotal,audioBufferLen/2); return FALSE; } int len; len = ::write(dspInfo.fdDSP,audioBuffer,audioBufferLen); return len==audioBufferLen; } void soundcard::writeNext() { if(write(&longBuffer[writeIndex])==0) { timer1->start(100,TRUE); //buffers are full -> wait } else { writeIndex+=audioBufferLen; if(writeIndex>writeLength) { busy=FALSE; emit signalTransmitBufferAvailable(); } timer1->start(0,TRUE); } } void soundcard::delayedStop() { int delay=(waitEnd()*1000)/dspInfo.speed; //#ifdef DEBUGSOUNDCARD logfile.add("soundard: delayedstop %d",delay); //#endif timer2->start(5*(delay+1),TRUE); } void soundcard::slotStop() { #ifdef DEBUGSOUNDCARD logfile.add("soundcard: slotStop=%lx",(long unsigned int)this); #endif stop(); }