/* * Copyright (c) 1993,1994 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-af.cc,v 1.28 96/04/19 17:25:50 mccanne Locked $ (LBL)"; #include #include "audio.h" #include struct afstate { AC ac; int mingain; int maxgain; int gain; int soft; }; class AFAudio : public Audio { public: AFAudio(); virtual int FrameReady(); virtual u_char* Read(); virtual void Write(u_char *); virtual void SetRGain(int); virtual void SetPGain(int); virtual void OutputPort(int); virtual void Obtain(); virtual void Release(); protected: void SendReadRequest(); int FindDefaultDevice(AFAudioConn*); void noserver(); int slidergain(const afstate& af) const; void setgain(int level, afstate& af); void chksoftgain(afstate&, int softgain, int mask); AFAudioConn* raud; AFAudioConn* paud; u_char* readptr; u_char* readbufend; u_char* readbuf; u_char* replybuf; u_int afblksize; u_char* nextframe; u_char* firstframe; u_char* writebuf; int usingbuf; int lastmean_[4]; u_int aftime; /* time of last frame read */ u_int wrttime; /* time of last frame written */ u_int minusoff; /* max - diff. between server & writer */ u_int plusoff; /* avg + diff. between server & writer */ int plusvar; /* avg + variation between server & writer */ u_int poff; /* play offset relative to aftime */ int pmiss; /* number of consecutive missed frames */ afstate raf; /* record context */ afstate saf; /* speaker context */ afstate haf; /* headphone context */ afstate *paf; /* play context (points at saf/haf) */ enum { PHONE_CODEC = 0, LOCAL_CODEC = 1, HIFI_BOTH = 2, HIFI_LEFT = 3, HIFI_RIGHT = 4 }; }; static class AFAudioMatcher : public Matcher { public: AFAudioMatcher() : Matcher("audio") {} TclObject* match(const char* id) { if (strcasecmp(id, "af") == 0) return (new AFAudio); return (0); } } afaudio_matcher; /*XXX*/ #include "/usr/src/local/AudioFile/AF/lib/AF/Alibint.h" extern "C" void _AFlush(AFAudioConn* aud); extern "C" void _ARead(AFAudioConn* aud, char* data, long size); extern "C" void _AReadPad(AFAudioConn* aud, char* data, long size); extern "C" AStatus _AReply(AFAudioConn* aud, aReply* rep, int extra, ABool discard); extern "C" AStatus _AReplyAsync(AFAudioConn* aud, aReply* rep, int extra, ABool discard); #ifdef __osf__ extern "C" int flock(int, int); #endif void AFAudio::chksoftgain(afstate& af, int softgain, int mask) { if (af.mingain == af.maxgain) { af.mingain = -30; af.maxgain = 30; af.soft = 1; } else { af.soft = 0; if (softgain != 0) { AFSetACAttributes attr; attr.rec_gain = softgain; AFChangeACAttributes(af.ac, mask, &attr); } } } AFAudio::AFAudio() { Tcl& tcl = Tcl::instance(); int device = atoi(tcl.attr("afDevice")); int blocks = atoi(tcl.attr("afBlocks")); int rgain = atoi(tcl.attr("afSoftInputGain")); int pgain = atoi(tcl.attr("afSoftOuputGain")); /* * if the AUDIOFILE environment variable is set, use it as * the server name. Otherwise AFOpenAudioConn will try * to use DISPLAY which is probably wrong so force ":0". */ const char* sname = getenv("AUDIOFILE"); if (sname == 0) sname = ":0"; raud = AFOpenAudioConn((char*)sname); if (raud == 0) noserver(); paud = AFOpenAudioConn((char*)sname); if (paud == 0) noserver(); if (device >= ANumberOfAudioDevices(raud)) { fprintf(stderr, "vat: AF: bad device %d", device); exit(1); } if (device < 0) { device = FindDefaultDevice(raud); if (device < 0) { fprintf(stderr, "vat: AF: cannot find ulaw device"); exit(1); } } /* set up audio context, find sample size and sample rate */ AFSetACAttributes attr; attr.type = MU255; raf.ac = AFCreateAC(raud, device, ACEncodingType, &attr); saf.ac = AFCreateAC(paud, device, ACEncodingType, &attr); #ifdef notyet haf.ac = AFCreateAC(paud, HIFI_LEFT, 0, 0); #endif paf = &saf; raf.gain = AFQueryInputGain(raf.ac, &raf.mingain, &raf.maxgain); chksoftgain(raf, rgain, ACRecordGain); saf.gain = AFQueryOutputGain(saf.ac, &saf.mingain, &saf.maxgain); chksoftgain(saf, pgain, ACPlayGain); #ifdef notyet haf.gain = AFQueryOutputGain(haf.ac, &haf.mingain, &haf.maxgain); chksoftgain(haf); #endif /* * Set midscale initial values. Since the server won't tell us * what the real initial value is, and we want the slider position * to reflect the startup value, we have no other choice. */ SetRGain(128); SetPGain(128); afblksize = blksize * blocks; replybuf = new u_char[afblksize + sizeof(aReply)]; readbuf = replybuf + sizeof(aReply); readptr = readbufend = readbuf + afblksize; nextframe = 0; firstframe = 0; usingbuf = 0; if (afblksize != blksize) writebuf = new u_char[afblksize]; else writebuf = 0; poff = 3 * afblksize; plusoff = poff << 5; plusvar = 0; lastmean_[0] = 0; lastmean_[1] = 0; lastmean_[2] = 0; lastmean_[3] = 0; /* open (or create) the lock file */ openlock(); } void AFAudio::noserver() { if (getenv("AUDIOFILE") == 0) { fprintf(stderr, "vat: can't connect to AF server (AUDIOFILE not set)"); } else fprintf(stderr, "can't connect to AF server"); exit(1); } /* Find a suitable default device (the first device not connected to the phone) * Returns device number or -1 if no suitable device can be found. */ int AFAudio::FindDefaultDevice(AFAudioConn* aud) { char *s = (char *)getenv("AF_DEVICE"); if (s != NULL) return (atoi(s)); /* Find the first non-phone, 8kHz, mono device */ int n = ANumberOfAudioDevices(aud); for (int i = 0; i < n; ++i) { AFDeviceDescriptor* a = AAudioDeviceDescriptor(aud, i); if (a->inputsFromPhone == 0 && a->outputsToPhone == 0 && a->playSampleFreq == 8000 && a->playNchannels == 1) return (i); } return (-1); } void AFAudio::Release() { if (HaveAudio()) { /* gobble the result of the in-progress read */ aRecordSamplesReply reply; _AReply(raud, (aReply*)&reply, 0, aFalse); if (reply.length * 4 == afblksize) { char dummy[512]; _AReadPad(raud, dummy, afblksize); } unlock(); unlink(); fd = -1; notify(); } } void AFAudio::Obtain() { if (HaveAudio()) abort(); if (lock() == 0) { /* audio is ours - kick off first read */ fd = raud->fd; aftime = AFGetTime(raf.ac); wrttime = 0; minusoff = 0; readptr = readbufend; SendReadRequest(); Audio::Obtain(); } } void AFAudio::Write(u_char *cp) { if (HaveAudio()) { if (afblksize != blksize) { if (nextframe == 0) { firstframe = cp; nextframe = cp + blksize; usingbuf = 0; return; } if (nextframe != cp) { if (! usingbuf) { /* * frames wrapped in ss buffer -- * copy to writebuf to keep things * contiguous. */ int curlen = nextframe - firstframe; memcpy(writebuf, firstframe, curlen); firstframe = writebuf; nextframe = writebuf + curlen; usingbuf = 1; } memcpy(nextframe, cp, blksize); } nextframe += blksize; u_int len = nextframe - firstframe; if (len < afblksize) return; cp = firstframe; nextframe = 0; } u_int at = aftime + poff; if (at - wrttime > 3 * afblksize && wrttime) { /* * start of talk after silence -- see if we * should adjust offset. If AF missed any * frames in the last talkspurt, adjust the * offset to one that wouldn't have missed any * frames. Otherwise if we're more than a * frame time ahead of the recent average offset, * drop the current offset by half the difference * (or the max that wouldn't reorder AF playout, * whichever is smaller). */ u_int noff = poff; if (minusoff) { noff = minusoff >> 2; minusoff = 0; } else { /* * we went through the last talkspurt * with no drops & an average backlog * variation between us & AF of 'plusvar'. * To avoid drops we need 2*afblksize + * 2*plusvar of buffer between us & AF. * If we have more than that, reduce it. */ u_int doff = (plusvar >> 2) + (2 * afblksize); if (doff < noff) { int adj = (noff - doff) >> 2; noff -= adj; if (int(noff) < int(wrttime - aftime)) noff = wrttime - aftime; } } if (noff != poff) { poff = (noff + 3) & ~3; at = aftime + noff; } } wrttime = at; u_int now = AFPlaySamples(paf->ac, at, afblksize, cp); int dif = now - at; if (dif > 0) { u_int noff = now - aftime + 2 * afblksize; if (minusoff) minusoff += noff - (minusoff >> 1); else minusoff = noff << 1; if (++pmiss >= 3) { /* * losing bad - adapt now rather than * waiting for next talkspurt. */ poff = ((minusoff >> 1) + 3) & ~3; pmiss = 0; minusoff = 0; } } else { int delta = dif + (plusoff >> 5); plusoff -= delta; if (delta < 0) delta = -delta; plusvar += delta - (plusvar >> 3); pmiss = 0; } } } void AFAudio::SendReadRequest() { register aRecordSamplesReq *req; if (HaveAudio()) { #define aud raud GetReq(RecordSamples, req); #undef aud req->ac = raf.ac->acontext; req->startTime = aftime; req->nbytes = afblksize; req->sampleType = raf.ac->attributes.type; req->nchannels = raf.ac->attributes.channels; req->mask = ABlockMask; if (raf.ac->attributes.endian == ABigEndian) req->mask |= ABigEndianMask; _AFlush(raud); } } int AFAudio::FrameReady() { u_char* cp = readptr; if (cp >= readbufend) { aRecordSamplesReply* reply = (aRecordSamplesReply*)replybuf; if (_AReplyAsync(raud, (aReply*)reply, afblksize >> 2, aFalse) <= 0) /* no data available */ return (0); /* queue the next read */ SendReadRequest(); /* * If we get too far behind or get confused & * think we're ahead, jump forward. (This can * easily happen if the process is suspended.) */ u_int srvtime = reply->currentTime; u_int dif = srvtime - aftime; if (dif > 16000) if (int(dif) < -1600 || int(dif) > 0) { aftime = srvtime; } aftime += afblksize; readptr = readbuf; } return (1); } extern const unsigned char lintomulaw[]; extern const short mulawtolin[]; u_char* AFAudio::Read() { u_char* cp = readptr; readptr = cp + blksize; /* * remove any dc bias from the input signal. */ register u_char* ip = cp; register u_char* ep = readptr; register int smean = lastmean_[iport]; register const short* u2l = mulawtolin; register const u_char* l2u = lintomulaw; while (ip < ep) { register int mean, dif; register int s0 = u2l[ip[0]]; register int s1 = u2l[ip[1]]; register int s2 = u2l[ip[2]]; register int s3 = u2l[ip[3]]; mean = smean >> 13; dif = s0 - mean; smean += dif; ip[0] = l2u[dif & 0xffff]; mean = smean >> 13; dif = s1 - mean; smean += dif; ip[1] = l2u[dif & 0xffff]; mean = smean >> 13; dif = s2 - mean; smean += dif; ip[2] = l2u[dif & 0xffff]; mean = smean >> 13; dif = s3 - mean; smean += dif; ip[3] = l2u[dif & 0xffff]; ip += 4; } return (cp); } int AFAudio::slidergain(const afstate& af) const { if (af.mingain == 0 && af.maxgain == 0) /* Not adjustable. Just maintain mid-scale */ return (128); float range = af.maxgain - af.mingain; return (int(255. * float(af.gain - af.mingain) / range)); } void AFAudio::setgain(int level, afstate& af) { float range = af.maxgain - af.mingain; af.gain = int(af.mingain + range * float(level) / 255.); } void AFAudio::SetRGain(int level) { setgain(level, raf); if (raf.soft) { AFSetACAttributes attr; attr.rec_gain = raf.gain; AFChangeACAttributes(raf.ac, ACRecordGain, &attr); } else AFSetInputGain(raf.ac, raf.gain); rgain = slidergain(raf); } void AFAudio::SetPGain(int level) { setgain(level, *paf); if (paf->soft) { AFSetACAttributes attr; attr.play_gain = paf->gain; AFChangeACAttributes(paf->ac, ACPlayGain, &attr); } else AFSetOutputGain(paf->ac, paf->gain); pgain = slidergain(*paf); } void AFAudio::OutputPort(int p) { oport = p; #ifdef notyet paf = oport? &haf : &saf; #else paf = &saf; #endif }