--- audio-voxware.cc.orig Fri Apr 26 05:22:37 1996 +++ audio-voxware.cc Sat May 10 00:15:51 2003 @@ -1,4 +1,6 @@ /* + * Modifications (C) 1997-1998 by Luigi Rizzo and others. + * * Copyright (c) 1991-1993 Regents of the University of California. * All rights reserved. * @@ -33,31 +35,43 @@ static const char rcsid[] = "@(#) $Header: audio-voxware.cc,v 1.10 96/04/26 05:22:05 van Exp $ (LBL)"; -#include -#include -#include -#if defined(sco) || defined(__bsdi__) -#include -#endif -#if defined(__FreeBSD__) -#include -#include -#include -#include + +/* + * Full Duplex audio module for the new sound driver and full duplex + * cards. Luigi Rizzo, from original sources supplied by Amancio Hasty. + * + * This includes some enhancements: + * - the audio device to use can be in the AUDIODEV env. variable. + * It can be either a unit number or a full pathname; + * - use whatever format is available from the card (included split + * format e.g. for the sb16); + * - limit the maximum size of the playout queue to approx 4 frames; + * this is necessary if the write channel is slower than expected; + * the fix is based on two new ioctls, AIOGCAP and AIONWRITE, + * but the code should compile with the old driver as well. + */ + +#if !defined(__FreeBSD__) || (__FreeBSD__ < 4) +#include #else -#include +#include #endif +#include #include "audio.h" +#include "mulaw.h" #include "Tcl.h" #define ULAW_ZERO 0x7f + +/* for use in the Voxware driver */ #define ABUFLOG2 8 -#define ABUFLEN (1 << ABUFLOG2) #define NFRAG 5 -class VoxWareAudio : public Audio { +extern const u_char lintomulawX[]; + +class VoxWare : public Audio { public: - VoxWareAudio(); + VoxWare(); virtual int FrameReady(); virtual u_char* Read(); virtual void Write(u_char *); @@ -66,163 +80,415 @@ virtual void OutputPort(int); virtual void InputPort(int); virtual void Obtain(); + virtual void Release(); virtual void RMute(); virtual void RUnmute(); virtual int HalfDuplex() const; protected: + int ext_fd; /* source for external file */ - u_char* readptr; - u_char* readbufend; u_char* readbuf; + u_short *s16_buf; + + int play_fmt ; + int is_half_duplex ; + + // new sound driver + int rec_fmt ; /* the sb16 has split format... */ + snd_capabilities soundcaps; - u_char* ubufptr; - u_char* ubufend; - u_char* ubuf; - - u_char* writeptr; - u_char* writebufend; - u_char* writebuf; }; -static class VoxWareAudioMatcher : public Matcher { +static class VoxWareMatcher : public Matcher { public: - VoxWareAudioMatcher() : Matcher("audio") {} + VoxWareMatcher() : Matcher("audio") {} TclObject* match(const char* fmt) { if (strcmp(fmt, "voxware") == 0) - return (new VoxWareAudio); - else + return (new VoxWare); return (0); } -} voxware_audio_matcher; +} linux_audio_matcher; -VoxWareAudio::VoxWareAudio() +VoxWare::VoxWare() { - readbuf = new u_char[ABUFLEN]; - readptr = readbufend = readbuf + ABUFLEN; + readbuf = new u_char[blksize]; + s16_buf = new u_short[blksize]; - writeptr = writebuf = new u_char[ABUFLEN]; - writebufend = writebuf + ABUFLEN; + memset(readbuf, ULAW_ZERO, blksize); - ubufptr = ubuf = new u_char[blksize]; - ubufend = ubuf + blksize; - memset(ubuf, ULAW_ZERO, blksize); + ext_fd = -1 ; /* no external audio */ + iports = 4; /* number of input ports */ } -int VoxWareAudio::HalfDuplex() const +void +VoxWare::Obtain() { - /*XXX change this if full duplex audio device available*/ - return 1; -} + char *thedev; + char buf[64]; + int d = -1; -void VoxWareAudio::Obtain() -{ if (HaveAudio()) abort(); - - fd = open("/dev/audio", O_RDWR|O_NDELAY); + is_half_duplex = 0 ; + /* + * variable AUDIODEV has the name of the audio device. + * With the new audio driver, the main device can also control + * the mixer, so there is no need to carry two descriptors around. + */ + thedev=getenv("AUDIODEV"); + if (thedev==NULL) + thedev="/dev/audio"; + else if ( thedev[0] >= '0' && thedev[0] <= '9' ) { + d = atoi(thedev); + sprintf(buf,"/dev/audio%d", d); + thedev = buf ; + } + fd = open(thedev, O_RDWR ); if (fd >= 0) { - int on = 1; - ioctl(fd, FIONBIO, &on); + int i = -1 ; + u_long fmt = 0 ; + int rate = 8000 ; + + snd_chan_param pa; + struct snd_size sz; + i = ioctl(fd, AIOGCAP, &soundcaps); + fmt = soundcaps.formats ; /* can be invalid, check later */ + + play_fmt = AFMT_MU_LAW ; + rec_fmt = AFMT_MU_LAW ; + + if (i == -1 ) { /* setup code for old voxware driver */ + i = ioctl(fd, SNDCTL_DSP_GETFMTS, &fmt); + fmt &= AFMT_MU_LAW ; /* only use mu-law */ + fmt |= AFMT_FULLDUPLEX ; + if ( i < 0 ) { /* even voxware driver failed, try with pcaudio */ + fmt = AFMT_MU_LAW | AFMT_WEIRD ; + } + } + switch (soundcaps.formats & (AFMT_FULLDUPLEX | AFMT_WEIRD)) { + case AFMT_FULLDUPLEX : + /* + * this entry for cards with decent full duplex. Use s16 + * preferably (some are broken in ulaw) or ulaw or u8 otherwise. + */ + if (fmt & AFMT_S16_LE) + play_fmt = rec_fmt = AFMT_S16_LE ; + else if (soundcaps.formats & AFMT_MU_LAW) + play_fmt = rec_fmt = AFMT_MU_LAW ; + else if (soundcaps.formats & AFMT_U8) + play_fmt = rec_fmt = AFMT_U8 ; + else { + printf("sorry, no supported formats\n"); + close(fd); + fd = -1 ; + return; + } + break ; + case AFMT_FULLDUPLEX | AFMT_WEIRD : + /* this is the sb16... */ + if (fmt & AFMT_S16_LE) { + play_fmt = AFMT_U8 ; + rec_fmt = AFMT_S16_LE; + } else { + printf("sorry, no supported formats\n"); + close(fd); + fd = -1 ; + return; + } + break ; + default : + printf("sorry don't know how to deal with this card\n"); + close (fd); + fd = -1; + return; + } - int frag = (NFRAG << 16) | ABUFLOG2; - ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag); -#ifdef fullduplex + pa.play_format = play_fmt ; + pa.rec_format = rec_fmt ; + pa.play_rate = pa.rec_rate = rate ; + ioctl(fd, AIOSFMT, &pa); /* if this fails, also AIOSSIZE will.. */ + sz.play_size = (play_fmt == AFMT_S16_LE) ? 2*blksize : blksize; + sz.rec_size = (rec_fmt == AFMT_S16_LE) ? 2*blksize : blksize; + i = ioctl(fd, AIOSSIZE, &sz); + + /* + * Set the line input level to 0 to avoid loopback if the mic + * is connected to the line-in port (e.g. through an echo + * canceller). + */ + int v = 0; + (void)ioctl(fd, MIXER_WRITE(SOUND_MIXER_LINE), &v); + // restore hardware settings in case some other vat changed them + InputPort(iport); + SetRGain(rgain); + SetPGain(pgain); + + if ( i < 0 ) { // if AIOSSIZE fails, maybe this is a Voxware driver + ioctl(fd, SNDCTL_DSP_SPEED, &rate); + ioctl(fd, SNDCTL_DSP_SETFMT, &play_fmt); // same for play/rec + d = (play_fmt == AFMT_S16_LE) ? 2*blksize : blksize; + ioctl(fd, SNDCTL_DSP_SETBLKSIZE, &d); + read(fd, &i, 1); /* dummy read to start read engine */ + } Audio::Obtain(); -#else - notify(); -#endif + } else { + fprintf(stderr, "failed to open rw...\n"); + fd = open(thedev, O_WRONLY ); + fprintf(stderr, "open wronly returns %d\n", fd); + is_half_duplex = 1 ; + play_fmt = rec_fmt = AFMT_MU_LAW ; + notify(); /* XXX */ } } -void VoxWareAudio::Write(u_char *cp) +/* + * note: HalfDuplex() uses a modified function of the new driver, + * which will return AFMT_FULLDUPLEX set in SNDCTL_DSP_GETFMTS + * for full-duplex devices. In the old driver this was 0 so + * the default is to use half-duplex for them. Note also that I have + * not tested half-duplex operation. + */ +int +VoxWare::HalfDuplex() const { - if (HaveAudio() && (rmute & 1) != 0) { - register u_char *cpend = cp + blksize; - register u_char *wbuf = writeptr; - register u_char *wend = writebufend; - for ( ; cp < cpend; cp += 4) { - wbuf[0] = cp[0]; - wbuf[1] = cp[1]; - wbuf[2] = cp[2]; - wbuf[3] = cp[3]; - wbuf += 4; - if (wbuf >= wend) { - wbuf = writebuf; - if (write(fd, (char*)wbuf, ABUFLEN) != ABUFLEN) - perror("aud write"); - } - } - writeptr = wbuf; + int i, probed_duplex = 0; + + /* newpcm style */ +#ifdef SNDCTL_DSP_GETCAPS + ioctl(fd, SNDCTL_DSP_GETCAPS, &i); + probed_duplex |= (i & DSP_CAP_DUPLEX); +#endif /* SNDCTL_DSP_GETCAPS */ + + /* pcm style */ +#ifdef SNDCTL_DSP_GETFMTS + ioctl(fd, SNDCTL_DSP_GETFMTS, &i); + probed_duplex |= (i & AFMT_FULLDUPLEX); +#endif /* SNDCTL_DSP_GETFMTS */ + + if (is_half_duplex || (probed_duplex == 0)) { + fprintf(stderr, "HalfDuplex returns 1\n"); + return 1 ; } + + return 0; } -int VoxWareAudio::FrameReady() +void VoxWare::Release() { - if ((rmute & 1) == 0) { - register u_char* cp = ubufptr; - register u_char* cpend = ubufend; - register u_char* rbuf = readptr; - register u_char* rend = readbufend; - - for ( ; cp < cpend; cp += 4) { - if (rbuf >= rend) { - rbuf = readbuf; - int cc = read(fd, (char*)rbuf, ABUFLEN); - if (cc <= 0) { - ubufptr = cp; - readbufend = rbuf; - if (cc == -1 && errno != EAGAIN) { - Release(); - Obtain(); - } - return (0); + if (HaveAudio()) { + Audio::Release(); } - readbufend = rend = rbuf + cc; } - cp[0] = rbuf[0]; - cp[1] = rbuf[1]; - cp[2] = rbuf[2]; - cp[3] = rbuf[3]; - rbuf += 4; + +void VoxWare::Write(u_char *cp) +{ + int i = blksize, l; + if (play_fmt == AFMT_S16_LE) { + for (i=0; i< blksize; i++) + s16_buf[i] = mulawtolin[cp[i]] ; + cp = (u_char *)s16_buf; + i = 2 *blksize ; + } else if (play_fmt == AFMT_S8) { + for (i=0; i< blksize; i++) { + int x = mulawtolin[cp[i]] ; + x = (x >> 8 ) & 0xff; + cp[i] = (u_char)x ; + } + i = blksize ; + } else if (play_fmt == AFMT_U8) { + for (i=0; i< blksize; i++) { + int x = mulawtolin[cp[i]] ; + /* + * when translating to 8-bit formats, it would be useful to + * implement AGC to avoid loss of resolution in the conversion. + * This code is still incomplete... + */ +#if 0 /* AGC -- still not complete... */ + static int peak = 0; + if (x < 0) x = -x ; + if (x > peak) peak = ( peak*16 + x - peak ) / 16 ; + else peak = ( peak*8192 + x - peak ) / 8192 ; + if (peak < 128) peak = 128 ; + /* at this point peak is in the range 128..32k + * samples can be scaled and clipped consequently. + */ + x = x * 32768/peak ; + if (x > 32767) x = 32767; + else if (x < -32768) x = -32768; +#endif + x = (x >> 8 ) & 0xff; + x = (x ^ 0x80) & 0xff ; + cp[i] = (u_char)x ; + } + i = blksize ; + } +#if 0 + // this code is meant to keep the queue short. + int r, queued; + r = ioctl(fd, AIONWRITE, &queued); + queued = soundcaps.bufsize - queued ; + if (play_fmt == AFMT_S16_LE) { + if (queued > 8*blksize) + i -= 8 ; + } else { + if (queued > 4*blksize) + i -= 4 ; } - readptr = rbuf; +#endif + for ( ; i > 0 ; i -= l) { + l = write(fd, cp, i); + cp += l; } - return (1); } -u_char* VoxWareAudio::Read() +u_char* VoxWare::Read() { - u_char* cp = ubuf; - ubufptr = cp; - return (cp); + u_char* cp; + int l=0, l0 = blksize, i = blksize; + + cp = readbuf; + + if (rec_fmt == AFMT_S16_LE) { + cp = (u_char *)s16_buf; + l0 = i = 2 *blksize ; + } + for ( ; i > 0 ; i -= l ) { + l = read(fd, cp, i); + if (l<0) break; + cp += l ; + } + if (rec_fmt == AFMT_S16_LE) { + for (i=0; i< blksize; i++) { +#if 1 /* remove DC component... */ + static int smean = 0 ; /* smoothed mean to remove DC */ + int dif = ((short) s16_buf[i]) - (smean >> 13) ; + smean += dif ; + readbuf[i] = lintomulawX[ dif & 0x1ffff ] ; +#else + readbuf[i] = lintomulaw[ s16_buf[i] ] ; +#endif + } + } + else if (rec_fmt == AFMT_S8) { + for (i=0; i< blksize; i++) + readbuf[i] = lintomulaw[ readbuf[i]<<8 ] ; + } + else if (rec_fmt == AFMT_U8) { + for (i=0; i< blksize; i++) + readbuf[i] = lintomulaw[ (readbuf[i]<<8) ^ 0x8000 ] ; + } + if (iport == 3) { + l = read(ext_fd, readbuf, blksize); + if (l < blksize) { + lseek(ext_fd, (off_t) 0, 0); + read(ext_fd, readbuf+l, blksize - l); + } + } + return readbuf; } -void VoxWareAudio::SetRGain(int level) +/* + * should check that I HaveAudio() before trying to set gain. + * + * In most mixer devices, there is only a master volume control on + * the capture channel, so the following code does not really work + * as expected. The only (partial) exception is the MIC line, where + * there is generally a 20dB boost which can be enabled or not + * depending on the type of device. + */ +void VoxWare::SetRGain(int level) { + double x = level; + level = (int) (x/2.56); + int foo = (level<<8) | level; + if (!HaveAudio()) + Obtain(); + switch (iport) { + case 2: + case 1: + break; + case 0: + if (ioctl(fd, MIXER_WRITE(SOUND_MIXER_MIC), &foo) == -1) + printf("failed to set mic volume \n"); + break; + } + /* IGAIN tends to be found on SB-like mixers, RECLEV on AC97 */ + if ((ioctl(fd, MIXER_WRITE(SOUND_MIXER_IGAIN), &foo) == -1) && + (ioctl(fd, MIXER_WRITE(SOUND_MIXER_RECLEV), &foo) == -1)) + printf("failed set input line volume \n"); rgain = level; } -void VoxWareAudio::SetPGain(int level) +void VoxWare::SetPGain(int level) { + float x = level; + level = (int) (x/2.56); + int foo = (level<<8) | level; + if (ioctl(fd, MIXER_WRITE(SOUND_MIXER_PCM), &foo) == -1) { + printf("failed to output level %d \n", level); + } pgain = level; } -void VoxWareAudio::OutputPort(int p) +void VoxWare::OutputPort(int p) { oport = p; } -void VoxWareAudio::InputPort(int p) +void VoxWare::InputPort(int p) { + int src = 0; + + if (ext_fd >= 0 && p != 3) { + close(ext_fd); + ext_fd = -1 ; + } + + switch(p) { + case 3: + if (ext_fd == -1) + ext_fd = open(ext_fname, 0); + if (ext_fd != -1) + lseek(ext_fd, (off_t) 0, 0); + break; + case 2: + src = 1 << SOUND_MIXER_LINE; + break; + case 1: /* cd ... */ + src = 1 << SOUND_MIXER_CD; + break; + case 0 : + src = 1 << SOUND_MIXER_MIC; + break; + } + if ( ioctl(fd, SOUND_MIXER_WRITE_RECSRC, &src) == -1 ) { + printf("failed to select input \n"); + p = 0; + } iport = p; } -void VoxWareAudio::RMute() +void VoxWare::RMute() { rmute |= 1; } -void VoxWareAudio::RUnmute() +void VoxWare::RUnmute() { rmute &=~ 1; } + +/* + * FrameReady must return 0 every so often, or the system will keep + * processing mike data and not other events. + */ +int VoxWare::FrameReady() +{ + int i, l = 0; + int lim = blksize; + + i = ioctl(fd, FIONREAD, &l ); + if (rec_fmt == AFMT_S16_LE) lim = 2*blksize; + return (l >= lim) ? 1 : 0 ; +} +/*** end of file ***/