1
0
mirror of https://git.FreeBSD.org/ports.git synced 2024-12-13 03:03:15 +00:00
freebsd-ports/audio/vat/files/patch-ah
Bill Fenner 0bbbf2886a patch-ah: add Orion Hodson's full-duplex probing and AC97 fix
patch-a[klmn]: Split original patch-ah into one patch per file patched.

PR:		23618
Submitted by:	Orion Hodson <O.Hodson@cs.ucl.ac.uk>
2001-02-15 15:29:45 +00:00

578 lines
15 KiB
Plaintext

--- 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 ***/