summaryrefslogblamecommitdiff
path: root/audio/vat/files/patch-ah
blob: 38c4ef3adf4c8dd7168e42d8f32f5e6ac39a99f6 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                        
                                                






                                                                    


                                                                               










                                      
                               















                                                                        
                                               
                   
      
                           


                               




















                                     
                     



































































































                                                                       
























































                                                                             



                                                         




































































                                                                               














                                                  


                                                  

              


                               

                        

















                                                                          





















































































































































                                                                           


                                                                    

















































































                                                                    
--- audio-voxware.cc.dist	Fri Apr 26 05:22:37 1996
+++ audio-voxware.cc	Mon Dec 18 18:18:31 2000
@@ -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 <string.h>
-#include <sys/fcntl.h>
-#include <errno.h>
-#if defined(sco) || defined(__bsdi__)
-#include <sys/socket.h>
-#endif
-#if defined(__FreeBSD__)
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <unistd.h>
-#include <machine/soundcard.h>
+
+/*
+ * 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 <osfcn.h>
 #else
-#include <sys/soundcard.h>
+#include <fcntl.h>
 #endif
+#include <machine/soundcard.h>
 #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 ***/