/* * Copyright (c) 1991-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. */ static const char rcsid[] = "@(#) $Header: audio-sun.cc,v 1.24 96/05/03 01:23:30 van Exp $ (LBL)"; #undef resource_h #ifdef __svr4__ #include #include #else #include #endif #include #include #include #include #include "audio-sun.h" class SUNDBRIAudio : public SUNAudio { public: SUNDBRIAudio(const char* device); virtual u_char* Read(); protected: int lastmean_[4]; }; class SUNXAudio : public SUNDBRIAudio { public: SUNXAudio(const char* device); virtual u_char* Read(); virtual void Write(u_char*); protected: virtual void setup_device(); u_char* ubuf; }; static class SunAudioMatcher : public Matcher { public: SunAudioMatcher() : Matcher("audio") {} TclObject* match(const char* id) { if (strcasecmp(id, "sun") == 0) return create(); return (0); } Audio* create(); } sunaudio_matcher; Audio* SunAudioMatcher::create() { Tcl& tcl = Tcl::instance(); const char* device = tcl.attr("audioFileName"); int i = 0; if (strcmp(device, "/dev/audio") == 0 && (i = open("/dev/audioctl", O_RDONLY, 0)) >= 0) { audio_info_t info; AUDIO_INITINFO(&info); int j = ioctl(i, AUDIO_GETINFO, &info); close(i); if (j != -1 && info.play.avail_ports > 3 && info.play.avail_ports < 32) { /* * if the driver is advanced enough to let us set * the blocksize, use the 'sunx' driver which reads * in linear to avoid the ulaw quantization noise * from the dc offset on the mike & line inputs. * If we can't set the blocksize, use the 'dbri' * driver which just reads in ulaw (if we can't * set the blocksize & switch to linear, sun's * braindead driver will supply a weird, huge * blocksize). */ #ifndef __svr4__ int bs = info.record._xxx[2]; #else int bs = info.record.buffer_size; #endif return ((bs != -1 && bs != 0)? new SUNXAudio(device) : new SUNDBRIAudio(device)); } } return (new SUNAudio(device)); } SUNAudio::SUNAudio(const char* device) { iports = 1; oports = 2; audio_info_t* i = new audio_info_t; state = i; AUDIO_INITINFO(i); rgain = 0; rbalance = 32; pgain = 0; pbalance = 32; mgain = 0; bufcur = buf = new u_char[blksize]; bufend = buf + blksize; device_ = new char[strlen(device) + 1]; strcpy(device_, device); } int SUNAudio::command(int argc, const char*const* argv) { Tcl& tcl = Tcl::instance(); if (argc == 3) { if (strcmp(argv[1], "input") == 0) { if (strcmp(argv[2], "balance") == 0) { sprintf(tcl.result(), "%s %d", IPortToStr(iport), rbalance); return (TCL_OK); } } else if (strcmp(argv[1], "output") == 0) { if (strcmp(argv[2], "balance") == 0) { sprintf(tcl.result(), "%s %d", OPortToStr(oport), pbalance); return (TCL_OK); } } else if (strcmp(argv[1], "monitor") == 0) { if (strcmp(argv[2], "gain") == 0) { sprintf(tcl.result(), "monitor %d", mgain); return (TCL_OK); } } } else if (argc == 4) { if (strcmp(argv[1], "input") == 0) { if (strcmp(argv[2], "balance") == 0) { SetRBalance(atoi(argv[3])); return (TCL_OK); } } else if (strcmp(argv[1], "output") == 0) { if (strcmp(argv[2], "balance") == 0) { SetPBalance(atoi(argv[3])); return (TCL_OK); } } else if (strcmp(argv[1], "monitor") == 0) { if (strcmp(argv[2], "gain") == 0) { SetMGain(atoi(argv[3])); return (TCL_OK); } } } return (Audio::command(argc, argv)); } int SUNAudio::OPort(int p) { switch(p) { default: case output_speaker: return (AUDIO_SPEAKER); case output_phones: return (AUDIO_HEADPHONE); case output_line: return (AUDIO_LINE_OUT); } } int SUNAudio::IPort(int p) { switch(p) { default: case input_mike: return (AUDIO_MICROPHONE); case input_line: return (AUDIO_LINE_IN); } } void SUNAudio::Obtain() { if (!HaveAudio()) { /* * try to save the current audio state (we have to * do this before opening /dev/audio because the open * will wipe out the sample rate & encoding). */ int ctl = open("/dev/audioctl", O_RDONLY, 0); if (ctl >= 0) { audio_info_t* i = (audio_info_t*)state; AUDIO_INITINFO(i); ioctl(ctl, AUDIO_GETINFO, i); close(ctl); } fd = open(device_, O_RDWR|O_NDELAY); if (fd >= 0) { setup_device(); Audio::Obtain(); } } } void SUNAudio::Release() { if (HaveAudio()) { /* restore the audio state to what it was on entry */ setinfo((audio_info_t*)state); Audio::Release(); } } void SUNAudio::Write(u_char *cp) { register int len = blksize; int cc = write(fd, (char *)cp, len); if ((len -= cc) != 0) { do { if (cc < 0) { if (errno != EPERM) perror("audio write"); return; } cp += cc; cc = write(fd, (char *)cp, len); len -= cc; } while (len > 0); } } int SUNAudio::FrameReady() { register int len = bufend - bufcur; while (len > 0) { int cc = read(fd, (char *)bufcur, len); if (cc < 0) { switch (errno) { case EINVAL: /* probably wrapped file pos. */ lseek(fd, 0, SEEK_SET); cc = 0; break; case EPERM: /* probably lost audio */ Release(); Obtain(); goto out; #ifndef __svr4__ case EWOULDBLOCK: #endif case EAGAIN: /* should warn about audio_bsize here */ goto out; default: perror("audio read"); Release(); Obtain(); return (0); } } bufcur += cc; len -= cc; } out: return (bufcur >= bufend); } u_char* SUNAudio::Read() { bufcur = buf; return (buf); } void SUNAudio::setup_device() { audio_info_t info; if (fcntl(fd, F_SETFL, O_NONBLOCK|O_NDELAY) < 0) perror("audio O_NONBLOCK"); int on = 1; ioctl(fd, FIONBIO, &on); #ifndef __svr4__ int bsddrvr = 0; AUDIO_INITINFO(&info); getinfo(&info); if (info._yyy[0] == 0) bsddrvr = 1; #endif AUDIO_INITINFO(&info); info.play.sample_rate = 8000; /*XXX*/ info.play.channels = 1; /*XXX*/ info.play.precision = 8; /*XXX*/ info.play.encoding = AUDIO_ENCODING_ULAW; /*XXX*/ info.play.gain = pgain; info.play.port = OPort(oport); info.play.balance = pbalance; info.record.sample_rate = 8000; /*XXX*/ info.record.channels = 1; /*XXX*/ info.record.precision = 8; /*XXX*/ info.record.encoding = AUDIO_ENCODING_ULAW; /*XXX*/ info.record.gain = rgain; info.record.port = IPort(iport); info.record.balance = rbalance; #ifndef __svr4__ /* solaris 2.2 hack: set buffer size. * NB- this changes to _xxx[3] for os<4.1.3 */ info.record._xxx[2] = blksize; if (bsddrvr) { /* bsd driver hack - set buffer size */ info._yyy[0] = blksize; } else { /* 4.1.3 bug workaround: pause then resume */ info.record.pause = 1; setinfo(&info); info.record.pause = 0; } #else info.record.buffer_size = blksize; #endif info.monitor_gain = mgain; setinfo(&info); /* flush input to get rid of any data fragments */ ioctl(fd, I_FLUSH, FLUSHR); bufcur = buf; } int SUNAudio::GainClip(int level) { if (level < AUDIO_MIN_GAIN) return AUDIO_MIN_GAIN; else if (level > AUDIO_MAX_GAIN) return AUDIO_MAX_GAIN; else return level; } int SUNAudio::getinfo(audio_info_t* info) { int sts; if (fd < 0) sts = 0; else sts = ioctl(fd, AUDIO_GETINFO, info); return (sts); } int SUNAudio::setinfo(audio_info_t* info) { int sts; if (fd < 0) sts = 0; else sts = ioctl(fd, AUDIO_SETINFO, info); return (sts); } void SUNAudio::SetRGain(int level) { audio_info_t info; rgain = GainClip(level); AUDIO_INITINFO(&info); info.record.gain = rgain; setinfo(&info); } void SUNAudio::SetPGain(int level) { audio_info_t info; pgain = GainClip(level); AUDIO_INITINFO(&info); info.play.gain = pgain; setinfo(&info); } void SUNAudio::SetRBalance(int level) { audio_info_t info; rbalance = GainClip(level); AUDIO_INITINFO(&info); info.record.balance = rbalance; setinfo(&info); } void SUNAudio::SetMGain(int level) { audio_info_t info; mgain = GainClip(level); AUDIO_INITINFO(&info); info.monitor_gain = mgain; setinfo(&info); } void SUNAudio::SetPBalance(int level) { audio_info_t info; pbalance = GainClip(level); AUDIO_INITINFO(&info); info.play.balance = pbalance; setinfo(&info); } void SUNAudio::OutputPort(int p) { audio_info_t info; oport = p; AUDIO_INITINFO(&info); info.play.port = OPort(p); setinfo(&info); } void SUNAudio::InputPort(int p) { audio_info_t info; iport = p; AUDIO_INITINFO(&info); info.record.port = IPort(p); setinfo(&info); } extern const short mulawtolin[]; extern const unsigned char lintomulaw[]; extern const unsigned char lintomulawX[]; SUNDBRIAudio::SUNDBRIAudio(const char* device) : SUNAudio(device) { /* XXX - hardwired for dbri speakerbox */ iports = 2; oports = 3; lastmean_[0] = 0; lastmean_[1] = 0; lastmean_[2] = 0; lastmean_[3] = 0; } u_char* SUNDBRIAudio::Read() { register u_char* cp = buf; /* * for some reason, SUN didn't bother to filter out the * mike 'phantom power' DC signal so we end up with a * large DC offset that screws up the lin-to-mu conversion * and the speakerphone power calculations. So, all the * hair in the following loop is a low-pass filter with ~1Hz * passband to estimate the DC offset. */ register u_int* ip = (u_int*)cp; register u_int* ep = (u_int*)(cp + blksize); register int smean = lastmean_[iport]; register const short* u2l = mulawtolin; register const u_char* l2u = lintomulaw; if (iport != 0) { while (ip < ep) { register int mean, dif; register u_int res; register u_int dat = *ip; register int s0 = u2l[dat >> 24]; register int s1 = u2l[(dat >> 16) & 0xff]; register int s2 = u2l[(dat >> 8) & 0xff]; register int s3 = u2l[dat & 0xff]; mean = smean >> 13; dif = s0 - mean; smean += dif; res = l2u[dif & 0xffff] << 24; mean = smean >> 13; dif = s1 - mean; smean += dif; res |= l2u[dif & 0xffff] << 16; mean = smean >> 13; dif = s2 - mean; smean += dif; res |= l2u[dif & 0xffff] << 8; mean = smean >> 13; dif = s3 - mean; smean += dif; res |= l2u[dif & 0xffff]; *ip++ = res; } } else { while (ip < ep) { register int mean, dif; register u_int res; register u_int dat = *ip; register int s0 = u2l[dat >> 24]; register int s1 = u2l[(dat >> 16) & 0xff]; register int s2 = u2l[(dat >> 8) & 0xff]; register int s3 = u2l[dat & 0xff]; mean = smean >> 13; dif = s0 - mean; smean += dif; res = l2u[(dif >> 1) & 0xffff] << 24; mean = smean >> 13; dif = s1 - mean; smean += dif; res |= l2u[(dif >> 1) & 0xffff] << 16; mean = smean >> 13; dif = s2 - mean; smean += dif; res |= l2u[(dif >> 1) & 0xffff] << 8; mean = smean >> 13; dif = s3 - mean; smean += dif; res |= l2u[(dif >> 1) & 0xffff]; *ip++ = res; } } lastmean_[iport] = smean; bufcur = cp; return (cp); } SUNXAudio::SUNXAudio(const char* device) : SUNDBRIAudio(device) { ubuf = bufcur; bufcur = buf = new u_char[blksize << 1]; bufend = buf + (blksize << 1); } u_char* SUNXAudio::Read() { /* * for some reason, SUN didn't bother to filter out the * mike 'phantom power' DC signal so we end up with a * large DC offset that screws up the lin-to-mu conversion * and the speakerphone power calculations. So, all the * hair in the following loop is a low-pass filter with ~1Hz * passband to estimate the DC offset. To avoid the * quantization noise from unbiasing a mulaw signal, we * have to read & write 8KHz linear. */ register short* ip = (short*)buf; register short* ep = ip + blksize; register u_int* op = (u_int*)ubuf; register int smean = lastmean_[iport]; register const u_char* l2u = lintomulawX; while (ip < ep) { register int mean, dif; register u_int res; register int s0 = ip[0]; register int s1 = ip[1]; register int s2 = ip[2]; register int s3 = ip[3]; mean = smean >> 13; dif = s0 - mean; smean += dif; res = l2u[dif & 0x1ffff] << 24; mean = smean >> 13; dif = s1 - mean; smean += dif; res |= l2u[dif & 0x1ffff] << 16; mean = smean >> 13; dif = s2 - mean; smean += dif; res |= l2u[dif & 0x1ffff] << 8; mean = smean >> 13; dif = s3 - mean; smean += dif; res |= l2u[dif & 0x1ffff]; *op++ = res; ip += 4; } lastmean_[iport] = smean; bufcur = buf; return (ubuf); } void SUNXAudio::Write(u_char *cp) { register u_int len = blksize; u_int samps[MAXAUDIOSIZE/2]; register u_int* sp = samps; register u_int* ep = sp + len / 2; register const u_short* u2l = (u_short*)mulawtolin; register u_int* ip = (u_int*)cp; for ( ; sp < ep; sp += 4) { register u_int s = *ip++; sp[0] = (u2l[(s >> 24) & 0xff] << 16) | u2l[(s >> 16) & 0xff]; sp[1] = (u2l[(s >> 8) & 0xff] << 16) | u2l[s & 0xff]; s = *ip++; sp[2] = (u2l[(s >> 24) & 0xff] << 16) | u2l[(s >> 16) & 0xff]; sp[3] = (u2l[(s >> 8) & 0xff] << 16) | u2l[s & 0xff]; } write(fd, (char *)samps, len << 1); } void SUNXAudio::setup_device() { SUNAudio::setup_device(); audio_info_t info; AUDIO_INITINFO(&info); info.record.precision = 16; info.record.encoding = AUDIO_ENCODING_LINEAR; #ifndef __svr4__ info.record._xxx[2] = blksize << 1; #else info.record.buffer_size = blksize << 1; #endif info.play.precision = 16; info.play.encoding = AUDIO_ENCODING_LINEAR; setinfo(&info); /* flush input again to get rid of any data with wrong encoding */ ioctl(fd, I_FLUSH, FLUSHR); bufcur = buf; }