/* * Copyright (c) 1993 Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the Computer Systems * Engineering Group at Lawrence Berkeley Laboratory. * 4. Neither the name of the University nor of the Laboratory may be used * to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char rcsid[] = "@(#) $Header: audio-hp.cc,v 1.9 96/03/18 02:54:57 van Exp $ (LBL)"; #endif #include "config.h" #include #include #include #include "audio.h" #define ABUFSIZE 4096 #define ABUFLEN (ABUFSIZE/sizeof(short)) extern const unsigned char lintomulawX[]; extern const short mulawtolin[]; class HPAudio : public Audio { public: HPAudio(); virtual int FrameReady(); virtual u_char* Read(); virtual void Write(u_char *); virtual void SetRGain(int); virtual void SetPGain(int); virtual void InputPort(int); virtual void OutputPort(int); virtual void Obtain(); protected: friend class HPAudioMatcher; int lastmean[4]; short* readptr; short* readbufend; short* readbuf; u_char* ubufptr; u_char* ubufend; u_char* ubuf; short* writeptr; short* writebufend; short* writebuf; float rgain_scale; float rgain_min; float pgain_scale; float pgain_min; }; class HP2160Audio : public HPAudio { public: HP2160Audio(); virtual int FrameReady(); virtual void Write(u_char *); virtual void Obtain(); }; static class HPAudioMatcher : public Matcher { public: HPAudioMatcher() : Matcher("audio") {} TclObject* match(const char* fmt); } hpaudio_matcher; TclObject* HPAudioMatcher::match(const char* fmt) { if (strcasecmp(fmt, "hp") != 0) return (0); int cfd = open("/dev/audioCtl", O_RDONLY, 0); if (cfd < 0) { perror("/dev/audioCtl"); exit(1); } /* get gain & channel config */ audio_describe ad; if (ioctl(cfd, AUDIO_DESCRIBE, &ad) < 0) { perror("AUDIO_DESCRIBE"); exit(1); } close(cfd); HPAudio* aud; if (ad.audio_id == AUDIO_ID_PSB2160) aud = new HP2160Audio; else aud = new HPAudio; aud->rgain_scale = float(ad.max_receive_gain-ad.min_receive_gain)/255.; aud->rgain_min = ad.min_receive_gain; aud->pgain_scale = float(ad.max_transmit_gain-ad.min_transmit_gain)/255.; aud->pgain_min = ad.min_transmit_gain; return (aud); } HPAudio::HPAudio() { iports = 2; oports = 3; lastmean[0] = 0; lastmean[1] = 0; lastmean[2] = 0; lastmean[3] = 0; readbuf = new short[ABUFLEN]; readptr = readbufend = readbuf + ABUFLEN; writeptr = writebuf = new short[ABUFLEN]; writebufend = writebuf + ABUFLEN; ubufptr = ubuf = new u_char[blksize]; ubufend = ubuf + blksize; } void HPAudio::Obtain() { if (HaveAudio()) abort(); fd = open("/dev/audio", O_RDWR, 0); if (fd >= 0) { /* * To get around the * problem that the hardware can only do 4K read/writes * (half a second for 8KHz ulaw), we run in 16bit linear * stereo mode to get the latency down to `only' 125ms. * After setting up the format (which has to be done * before the driver buffers are allocated), * make sure we do non-blocking reads then allocate * about a second of buffer space. */ if (ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_LINEAR16BIT) < 0) { perror("AUDIO_SET_DATA_FORMAT"); exit(1); } if (ioctl(fd, AUDIO_SET_CHANNELS, 2) < 0) { perror("AUDIO_SET_CHANNELS"); exit(1); } /* * the default sample rate is supposed to be 8khz but * there's a kernel bug that keeps it from being set so * we inherit whatever the last used. so set it to what * we need. */ if (ioctl(fd, AUDIO_SET_SAMPLE_RATE, 8000) < 0) { perror("AUDIO_SET_SAMPLE_RATE"); exit(1); } readptr = readbufend; writeptr = writebuf; SetRGain(rgain); SetPGain(pgain); OutputPort(oport); InputPort(iport); if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { perror("audio O_NONBLOCK"); exit(1); } if (ioctl(fd, AUDIO_SET_RXBUFSIZE, 32768) < 0) { perror("AUDIO_SET_RXBUFSIZE"); exit(1); } if (ioctl(fd, AUDIO_SET_TXBUFSIZE, 32768) < 0) { perror("AUDIO_SET_TXBUFSIZE"); exit(1); } Audio::Obtain(); } } void HPAudio::Write(u_char *cp) { if (HaveAudio()) { register u_char *cpend = cp + blksize; register short *wbuf = writeptr; register short *wend = writebufend; for ( ; cp < cpend; cp += 4) { wbuf[1] = wbuf[0] = mulawtolin[cp[0]]; wbuf[3] = wbuf[2] = mulawtolin[cp[1]]; wbuf[5] = wbuf[4] = mulawtolin[cp[2]]; wbuf[7] = wbuf[6] = mulawtolin[cp[3]]; wbuf += 8; if (wbuf >= wend) { wbuf = writebuf; if (write(fd, (char*)wbuf, ABUFSIZE) != ABUFSIZE) perror("aud write"); } } writeptr = wbuf; } } int HPAudio::FrameReady() { register u_char* cp = ubufptr; register u_char* cpend = ubufend; register short* rbuf = readptr; register short* rend = readbufend; register int smean = lastmean[iport]; if (!HaveAudio()) return (0); for ( ; cp < cpend; cp += 4) { if (rbuf >= rend) { rbuf = readbuf; int cc = read(fd, (char*)rbuf, ABUFSIZE); if (cc <= 0) { /* * The audio seems to occasionally * lock up. My guess is that * scheduling delays occasionally * cause us to get behind & the * kernel audio input buffer * overflows so the driver * goes into 'pause' (a really * stupid design decision on hp's * part) and we have to manually * unpause it or we get continuous * EIO errors. */ ubufptr = cp; readbufend = rbuf; if (cc == -1 && errno != EAGAIN) { if (errno == EIO) { struct audio_status as; ioctl(fd, AUDIO_GET_STATUS, &as); if (as.receive_status == AUDIO_PAUSE) { ioctl(fd, AUDIO_RESUME, AUDIO_RECEIVE); } } else { Release(); Obtain(); } } return (0); } readbufend = rend = (short*)((u_char*)rbuf + cc); } /* * there's probably a dc offset due to the phantom * power for the mike. this has to be filtered out * so we can do power calculations so we estimate the * dc bias via an ~1HZ lowpass filter & subtract it out. */ register int mean, dif, res; register int r0 = (int(rbuf[0]) + int(rbuf[1])) >> 1; mean = smean >> 13; dif = r0 - mean; smean += dif; res = lintomulawX[dif & 0x1ffff] << 24; register int r1 = (int(rbuf[2]) + int(rbuf[3])) >> 1; mean = smean >> 13; dif = r1 - mean; smean += dif; res |= lintomulawX[dif & 0x1ffff] << 16; register int r2 = (int(rbuf[4]) + int(rbuf[5])) >> 1; mean = smean >> 13; dif = r2 - mean; smean += dif; res |= lintomulawX[dif & 0x1ffff] << 8; register int r3 = (int(rbuf[6]) + int(rbuf[7])) >> 1; mean = smean >> 13; dif = r3 - mean; smean += dif; res |= lintomulawX[dif & 0x1ffff]; *(int*)cp = res; rbuf += 8; } lastmean[iport] = smean; readptr = rbuf; return (1); } u_char* HPAudio::Read() { return (ubufptr = ubuf); } void HPAudio::SetRGain(int level) { rgain = level; if (HaveAudio()) { struct audio_gain gain; if (ioctl(fd, AUDIO_GET_GAINS, &gain) < 0) { perror("AUDIO_GET_GAINS"); exit(1); } int g = int(float(rgain) * rgain_scale + rgain_min); gain.cgain[0].receive_gain = g; gain.cgain[1].receive_gain = g; if (ioctl(fd, AUDIO_SET_GAINS, &gain) < 0) { perror("AUDIO_SET_GAINS"); exit(1); } } } void HPAudio::SetPGain(int level) { pgain = level; if (HaveAudio()) { struct audio_gain gain; if (ioctl(fd, AUDIO_GET_GAINS, &gain) < 0) { perror("AUDIO_GET_GAINS"); exit(1); } int g = int(float(pgain) * pgain_scale + pgain_min); gain.cgain[0].transmit_gain = g; gain.cgain[1].transmit_gain = g; if (ioctl(fd, AUDIO_SET_GAINS, &gain) < 0) { perror("AUDIO_SET_GAINS"); exit(1); } } } void HPAudio::InputPort(int p) { iport = p; if (HaveAudio()) { ioctl(fd, AUDIO_SET_INPUT, iport? AUDIO_IN_LINE : AUDIO_IN_MIKE); } } void HPAudio::OutputPort(int p) { oport = p; if (HaveAudio()) { ioctl(fd, AUDIO_SET_OUTPUT, oport == 0? AUDIO_OUT_SPEAKER : oport == 1? AUDIO_OUT_EXTERNAL : AUDIO_OUT_LINE); } } HP2160Audio::HP2160Audio() { iports = 1; oports = 2; } void HP2160Audio::Obtain() { if (HaveAudio()) return; fd = open("/dev/audio", O_RDWR, 0); if (fd >= 0) { struct audio_select_thresholds selt; selt.write_threshold = selt.read_threshold = blksize * 4; if (ioctl(fd, AUDIO_SET_SEL_THRESHOLD, &selt) < 0) { perror("AUDIO_SET_SEL_THRESHOLD"); exit(1); } if (ioctl(fd, AUDIO_SET_DATA_FORMAT, AUDIO_FORMAT_LINEAR16BIT) < 0) { perror("AUDIO_SET_DATA_FORMAT"); exit(1); } if (ioctl(fd, AUDIO_SET_CHANNELS, 1) < 0) { perror("AUDIO_SET_CHANNELS"); exit(1); } /* * the default sample rate is supposed to be 8khz but * there's a kernel bug that keeps it from being set so * we inherit whatever the last used. so set it to what * we need. */ if (ioctl(fd, AUDIO_SET_SAMPLE_RATE, 8000) < 0) { perror("AUDIO_SET_SAMPLE_RATE"); exit(1); } readptr = readbufend; writeptr = writebuf; SetRGain(rgain); SetPGain(pgain); OutputPort(oport); InputPort(iport); if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { perror("audio O_NONBLOCK"); exit(1); } if (ioctl(fd, AUDIO_SET_RXBUFSIZE, 32768) < 0) { perror("AUDIO_SET_RXBUFSIZE"); exit(1); } if (ioctl(fd, AUDIO_SET_TXBUFSIZE, 32768) < 0) { perror("AUDIO_SET_TXBUFSIZE"); exit(1); } notify(); } } void HP2160Audio::Write(u_char *cp) { if (HaveAudio()) { register u_char *cpend = cp + blksize; register short *wbuf = writebuf; for ( ; cp < cpend; cp += 4) { wbuf[0] = mulawtolin[cp[0]]; wbuf[1] = mulawtolin[cp[1]]; wbuf[2] = mulawtolin[cp[2]]; wbuf[3] = mulawtolin[cp[3]]; wbuf += 4; } int cc = write(fd, (char *)writebuf, wbuf - writebuf); if (cc < 0 && errno != EPERM) perror("audio write"); } } int HP2160Audio::FrameReady() { register u_char* cp = ubufptr; register u_char* cpend = ubufend; register short* rbuf = readptr; register short* rend = readbufend; register int smean = lastmean[iport]; for ( ; cp < cpend; cp += 4) { if (rbuf >= rend) { rbuf = readbuf; int cc = read(fd, (char*)rbuf, ABUFSIZE); if (cc < 0) { if (errno == EINVAL) /* probably wrapped file pos. */ lseek(fd, 0, SEEK_SET); ubufptr = cp; readbufend = rbuf; return (0); } readbufend = rend = (short*)((u_char*)rbuf + cc); } /* * there's probably a dc offset due to the phantom * power for the mike. this has to be filtered out * so we can do power calculations so we estimate the * dc bias via an ~1HZ lowpass filter & subtract it out. */ register int mean, dif, res; register int r0 = int(rbuf[0]); mean = smean >> 13; dif = r0 - mean; smean += dif; res = lintomulawX[dif & 0x1ffff] << 24; register int r1 = int(rbuf[1]); mean = smean >> 13; dif = r1 - mean; smean += dif; res |= lintomulawX[dif & 0x1ffff] << 16; register int r2 = int(rbuf[2]); mean = smean >> 13; dif = r2 - mean; smean += dif; res |= lintomulawX[dif & 0x1ffff] << 8; register int r3 = int(rbuf[3]); mean = smean >> 13; dif = r3 - mean; smean += dif; res |= lintomulawX[dif & 0x1ffff]; *(int*)cp = res; rbuf += 4; } lastmean[iport] = smean; readptr = rbuf; return (1); }