/* * Copyright (c) 1996 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 Network Research * Group at Lawrence Berkeley National 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-win32.cc,v 1.10 96/05/14 06:44:53 van Exp $ (LBL)"; #include #include #include #include #include #include #include #include "audio.h" #include "ss.h" #include "Tcl.h" #define BLKS_PER_READ 4 #define READ_AHEAD 8 #define BLKS_PER_WRITE 4 #define WRITE_AHEAD 4 class audMux { public: MIXERCONTROLDETAILS select_[8]; MIXERCONTROLDETAILS vol_[8]; u_char mcnt_; u_char vcnt_; char mmap_[8]; char vmap_[8]; u_char isOut_; }; class Win32Audio : public Audio { public: Win32Audio(); ~Win32Audio(); void Write(u_char *); int FrameReady(); u_char* Read(); void SetRGain(int); void SetPGain(int); void OutputPort(int); void InputPort(int); void Obtain(); void Release(); void RMute(); void RUnmute(); int HalfDuplex() const; protected: int OpenOut(); void CloseOut(); int outErr(int) const; int OpenIn(); void CloseIn(); int inErr(int) const; int mapName(audMux& mux, const char* nm); int mapMixerPort(audMux& mux, const char* name); void setupMux(audMux& mux, DWORD ctype); void getMixerDetails(MIXERLINE&, MIXERCONTROL&, audMux&); void getMixerCtrls(MIXERLINE&, audMux&); HWAVEOUT out_; HWAVEIN in_; u_char* rbuf_; u_char* rbufStart_; u_char* rbufEnd_; u_char* zbuf_; u_int lastmean_; u_int ibindx_; u_char* ibufStart_; u_char* ibufEnd_; u_char* obuf_; u_char* obufStart_; u_char* obufEnd_; u_short iblen_; u_short oblen_; WAVEHDR iwhdr_[READ_AHEAD]; WAVEHDR owhdr_[BLKS_PER_WRITE * WRITE_AHEAD]; const WAVEFORMATEX* iformat_; const WAVEFORMATEX* oformat_; audMux omux_; audMux imux_; }; static class Win32AudioMatcher : public Matcher { public: Win32AudioMatcher() : Matcher("audio") {} TclObject* match(const char* fmt) { if (strcmp(fmt, "pc") == 0) return (new Win32Audio); return (0); } } win32_audio_matcher; extern void adios(); extern "C" const u_char lintomulawX[]; extern "C" const short mulawtolin[]; static const WAVEFORMATEX lin16fmt = { WAVE_FORMAT_PCM, 1, 8000, 2 * 8000, 2, 16, 0 }; static const WAVEFORMATEX lin8fmt = { WAVE_FORMAT_PCM, 1, 8000, 8000, 1, 8, 0 }; Win32Audio::Win32Audio() : out_(0), in_(0), lastmean_(0) { zbuf_ = new u_char[blksize]; memset(zbuf_, ULAW_ZERO, blksize); u_int len = blksize * BLKS_PER_READ; rbufStart_ = new u_char[len]; rbufEnd_ = rbufStart_ + len; rbuf_ = rbufEnd_; /* * figure out what input format is available. */ int sts = waveInOpen(0, WAVE_MAPPER, &lin16fmt, 0, 0, WAVE_FORMAT_QUERY); if (sts == WAVERR_BADFORMAT) { /* can't do 16 bit audio, try 8 bit */ sts = waveInOpen(0, WAVE_MAPPER, &lin8fmt, 0, 0, WAVE_FORMAT_QUERY); if (sts) { fprintf(stderr, "vat: soundcard supports neither 16 nor 8 bit 8KHz PCM audio input (%d)\n", sts); adios(); } iformat_ = &lin8fmt; iblen_ = len; len *= READ_AHEAD; ibufStart_ = new u_char[len]; ibufEnd_ = ibufStart_ + len; } else { iformat_ = &lin16fmt; iblen_ = len * sizeof(short); len *= READ_AHEAD; ibufStart_ = (u_char*)new short[len]; ibufEnd_ = ibufStart_ + len * sizeof(short); } /* * figure out what output format is available. */ sts = waveOutOpen(0, WAVE_MAPPER, &lin16fmt, 0, 0, WAVE_FORMAT_QUERY); if (sts == WAVERR_BADFORMAT) { /* can't do 16 bit audio, try 8 bit */ sts = waveOutOpen(0, WAVE_MAPPER, &lin8fmt, 0, 0, WAVE_FORMAT_QUERY); if (sts) { fprintf(stderr, "vat: soundcard supports neither 16 nor 8 bit 8KHz PCM audio output (%d)\n", sts); adios(); } oformat_ = &lin8fmt; len = blksize * BLKS_PER_WRITE; oblen_ = len; len *= WRITE_AHEAD; obufStart_ = new u_char[len]; obufEnd_ = obufStart_ + len; } else { oformat_ = &lin16fmt; len = blksize * BLKS_PER_WRITE; oblen_ = len * sizeof(short); len *= WRITE_AHEAD; obufStart_ = (u_char*)new short[len]; obufEnd_ = obufStart_ + len * sizeof(short); } if (mixerGetNumDevs()) { /* set up the mixer controls for input & out select & gain */ memset(&imux_, 0, sizeof(imux_)); memset(&imux_.mmap_, -1, sizeof(imux_.mmap_)); setupMux(imux_, MIXERLINE_COMPONENTTYPE_DST_WAVEIN); int i; for (i = 0; i < sizeof(imux_.mmap_); ++i) if (iports < imux_.mmap_[i]) iports = imux_.mmap_[i]; memset(&omux_, 0, sizeof(omux_)); memset(&omux_.mmap_, -1, sizeof(omux_.mmap_)); omux_.isOut_ = 1; setupMux(omux_, MIXERLINE_COMPONENTTYPE_DST_SPEAKERS); for (i = 0; i < sizeof(omux_.mmap_); ++i) if (oports < omux_.mmap_[i]) oports = omux_.mmap_[i]; } } Win32Audio::~Win32Audio() { CloseIn(); CloseOut(); delete zbuf_; delete rbufStart_; delete ibufStart_; delete obufStart_; } int Win32Audio::HalfDuplex() const { /*XXX*/ return 1; } int Win32Audio::inErr(int error) const { if (error) { char errorText[MAXERRORLENGTH]; waveInGetErrorText(error, errorText, sizeof(errorText)); fprintf(stderr, "vat - input error: %s\n", errorText); } return (error); } int Win32Audio::outErr(int error) const { if (error) { char errorText[MAXERRORLENGTH]; waveOutGetErrorText(error, errorText, sizeof(errorText)); fprintf(stderr, "vat - output error: %s\n", errorText); } return (error); } int Win32Audio::OpenOut() { int error = 0; if (out_ == 0) { error = waveOutOpen(&out_, WAVE_MAPPER, oformat_, NULL, NULL, CALLBACK_NULL); /* * Maybe we failed because someone is playing sound already. * Shut any sound off then try once more before giving up. */ if (error) { sndPlaySound(NULL, 0); if (outErr(waveOutOpen(&out_, WAVE_MAPPER, oformat_, NULL, NULL, CALLBACK_NULL))) { return 1; } } /* restore the gain to what the user set on this vat window */ SetPGain(pgain); /* (re-)initialize the output buffer descriptors */ memset(owhdr_, 0, sizeof(owhdr_)); u_char* bp = obufStart_; obuf_ = bp; u_int len = oblen_; int i; for (i = 0; i < WRITE_AHEAD; ++i) { /* * we mark the last hdr of the group of hdrs * associated with this write block as 'writeable' * (by setting its DONE bit) but make the address * in the hdr indicate the start of the group of * blocks. */ WAVEHDR* whp = &owhdr_[(i + 1) * BLKS_PER_WRITE - 1]; whp->dwFlags = 0; whp->dwBufferLength = oblen_; whp->lpData = (char*)bp; outErr(waveOutPrepareHeader(out_, whp, sizeof(*whp))); whp->dwFlags |= WHDR_DONE; bp += len; } /* * do initial write to generate a backlog to avoid * dropouts due to scheduling delays. */ for (i = BLKS_PER_WRITE; --i >= 0; ) Write(zbuf_); } return error; } int Win32Audio::OpenIn() { if (in_ == 0) { if (inErr(waveInOpen(&in_, WAVE_MAPPER, iformat_, NULL, NULL, CALLBACK_NULL))) return (1); /* restore the gain to what the user set on this vat window */ SetRGain(rgain); /* (re-)initialize the input buffer descriptors */ memset(iwhdr_, 0, sizeof(iwhdr_)); ibindx_ = 0; rbuf_ = rbufEnd_; u_char* bp = ibufStart_; u_int len = iblen_; memset(bp, 0, len * READ_AHEAD); for (int i = 0; i < READ_AHEAD; ++i) { WAVEHDR* whp = &iwhdr_[i]; whp->dwFlags = 0; whp->dwBufferLength = len; whp->lpData = (char*)bp; bp += len; waveInPrepareHeader(in_, whp, sizeof(*whp)); if (inErr(waveInAddBuffer(in_, whp, sizeof(*whp)))) { CloseIn(); return (1); } } waveInStart(in_); } return 0; } void Win32Audio::CloseOut() { if (out_) { waveOutReset(out_); for (int i = 1; i < WRITE_AHEAD + 1; ++i) { WAVEHDR* whp = &owhdr_[i * BLKS_PER_WRITE - 1]; if (whp->dwFlags & WHDR_PREPARED) waveOutUnprepareHeader(out_, whp, sizeof(*whp)); } waveOutClose(out_); out_ = 0; } } void Win32Audio::CloseIn() { if (in_) { waveInStop(in_); waveInReset(in_); for (int i = 0; i < READ_AHEAD; ++i) { WAVEHDR* whp = &iwhdr_[i]; if (whp->dwFlags & WHDR_PREPARED) waveInUnprepareHeader(in_, whp, sizeof(*whp)); } waveInClose(in_); in_ = 0; } } void Win32Audio::Obtain() { int error; if (HaveAudio()) abort(); if (rmute & 1) error = OpenOut(); else error = OpenIn(); if (error) fd = -1; else { fd = 0; SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); } notify(); } void Win32Audio::Release() { CloseOut(); CloseIn(); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); Audio::Release(); } void Win32Audio::Write(u_char *cp) { if (out_) { /* * copy the new data into our circular output buffer, * converting from ulaw to linear as we go. */ WAVEHDR* whp; if (oformat_ == &lin8fmt) { u_char* sp = obuf_; u_int bindx = (sp - obufStart_) / blksize; whp = &owhdr_[bindx]; u_char* ep = sp + blksize; const short* u2l = mulawtolin; while (sp < ep) *sp++ = (u2l[*cp++] >> 8) ^ 0x80; obuf_ = (ep >= obufEnd_)? obufStart_ : ep; } else { short* sp = (short*)obuf_; u_int bindx = (sp - (short*)obufStart_) / blksize; whp = &owhdr_[bindx]; short* ep = sp + blksize; const short* u2l = mulawtolin; while (sp < ep) *sp++ = u2l[*cp++]; obuf_ = ((u_char*)ep >= obufEnd_)? obufStart_ : (u_char*)ep; } /* * if the buffer descriptor associated with this block * is marked ready, ship it. */ if (whp->dwFlags & WHDR_DONE) { whp->dwFlags &=~ WHDR_DONE; outErr(waveOutWrite(out_, whp, sizeof(*whp))); } } } int Win32Audio::FrameReady() { if (in_ && rbuf_ >= rbufEnd_) { /* mulaw conversion buffer is empty - see if a read finished */ u_int i = ibindx_; WAVEHDR* whp = iwhdr_ + i; if ((whp->dwFlags & WHDR_DONE) == 0) return (0); /* read finished - move input to ulaw buffer */ rbuf_ = rbufStart_; ibindx_ = (i + 1) % READ_AHEAD; const u_char* l2u = lintomulawX; u_int* ip = (u_int*)rbuf_; int smean = lastmean_; if (iformat_ == &lin8fmt) { u_char* sp = (u_char*)whp->lpData; u_char* ep = sp + whp->dwBytesRecorded; for ( ; sp < ep; sp += 4) { register int mean, dif; register u_int res; register int s0 = int(sp[0]) - 0x80 << 8; register int s1 = int(sp[1]) - 0x80 << 8; register int s2 = int(sp[2]) - 0x80 << 8; register int s3 = int(sp[3]) - 0x80 << 8; mean = smean >> 13; dif = s0 - mean; smean += dif; res = l2u[dif & 0x1ffff] << 0; mean = smean >> 13; dif = s1 - mean; smean += dif; res |= l2u[dif & 0x1ffff] << 8; mean = smean >> 13; dif = s2 - mean; smean += dif; res |= l2u[dif & 0x1ffff] << 16; mean = smean >> 13; dif = s3 - mean; smean += dif; res |= l2u[dif & 0x1ffff] << 24; *ip++ = res; } } else { short* sp = (short*)whp->lpData; short* ep = (short*)((char*)sp + whp->dwBytesRecorded); for ( ; sp < ep; sp += 4) { register int mean, dif; register u_int res; register int s0 = sp[0]; register int s1 = sp[1]; register int s2 = sp[2]; register int s3 = sp[3]; mean = smean >> 13; dif = s0 - mean; smean += dif; res = l2u[dif & 0x1ffff] << 0; mean = smean >> 13; dif = s1 - mean; smean += dif; res |= l2u[dif & 0x1ffff] << 8; mean = smean >> 13; dif = s2 - mean; smean += dif; res |= l2u[dif & 0x1ffff] << 16; mean = smean >> 13; dif = s3 - mean; smean += dif; res |= l2u[dif & 0x1ffff] << 24; *ip++ = res; } } lastmean_ = smean; whp->dwFlags &=~ WHDR_DONE; inErr(waveInAddBuffer(in_, whp, sizeof(*whp))); } return (1); } u_char* Win32Audio::Read() { u_char* cp; if (in_) { cp = rbuf_; rbuf_ = cp + blksize; } else cp = zbuf_; return (cp); } void Win32Audio::SetRGain(int level) { rgain = level; if (in_) { if (level > 255) level = 255; level <<= 8; MIXERCONTROLDETAILS& mcd = imux_.vol_[imux_.vmap_[iport]]; for (u_int i = 0; i < mcd.cChannels; ++i) ((MIXERCONTROLDETAILS_UNSIGNED*)mcd.paDetails + i)->dwValue = level; mixerSetControlDetails(0, &mcd, MIXER_SETCONTROLDETAILSF_VALUE); } } void Win32Audio::SetPGain(int level) { pgain = level; if (out_) { if (level > 255) level = 255; level <<= 8; DWORD vol = level | (level << 16); outErr(waveOutSetVolume(out_, vol)); } } void Win32Audio::OutputPort(int p) { if (omux_.mmap_[p] >= 0) { oport = p; if (out_) mixerSetControlDetails(0, &omux_.select_[omux_.mmap_[p]], MIXER_SETCONTROLDETAILSF_VALUE); } } void Win32Audio::InputPort(int p) { if (imux_.mmap_[p] >= 0) { iport = p; if (in_) mixerSetControlDetails(0, &imux_.select_[imux_.mmap_[p]], MIXER_SETCONTROLDETAILSF_VALUE); } } void Win32Audio::RMute() { CloseIn(); if (OpenOut() == 0) rmute |= 1; } void Win32Audio::RUnmute() { CloseOut(); if (OpenIn() == 0) rmute &=~ 1; } int Win32Audio::mapName(audMux& mux, const char* name) { return (mux.isOut_? StrToOPort(name) : StrToIPort(name)); } int Win32Audio::mapMixerPort(audMux& mux, const char* name) { int i = mapName(mux, name); if (i < 0) { char nm[64]; strcpy(nm, name); char* cp; while ((cp = strrchr(nm, ' ')) != 0) { *cp = 0; if ((i = mapName(mux, nm)) >= 0) break; } } return (i); } void Win32Audio::getMixerDetails(MIXERLINE& ml, MIXERCONTROL& mc, audMux& mux) { MIXERCONTROLDETAILS mcd; mcd.cbStruct = sizeof(mcd); mcd.dwControlID = mc.dwControlID; mcd.cChannels = ml.cChannels; mcd.cMultipleItems = mc.cMultipleItems; if (mcd.cMultipleItems) { MIXERCONTROLDETAILS_LISTTEXT mcdt[16]; mcd.cbDetails = sizeof(mcdt[0]); mcd.paDetails = mcdt; u_int sts = mixerGetControlDetails(0, &mcd, MIXER_GETCONTROLDETAILSF_LISTTEXT); if (sts == 0) { for (u_int i = 0; i < mc.cMultipleItems; ++i) { int port = mapMixerPort(mux, mcdt[i].szName); if (port >= 0) mux.mmap_[port] = i; u_int n = mcd.cMultipleItems * mcd.cChannels; MIXERCONTROLDETAILS_BOOLEAN* mcdb = new MIXERCONTROLDETAILS_BOOLEAN[n]; memset(mcdb, 0, n * sizeof(*mcdb)); for (u_int j = 0; j < mcd.cChannels; ++j) mcdb[j*mc.cMultipleItems+i].fValue = 1; mux.select_[i] = mcd; mux.select_[i].cbDetails = sizeof(*mcdb); mux.select_[i].paDetails = mcdb; } mux.mcnt_ = (u_char)mcd.cMultipleItems; } } else { int i = mux.vcnt_++; int port = mapMixerPort(mux, ml.szName); if (port >= 0) mux.vmap_[port] = i; MIXERCONTROLDETAILS_UNSIGNED* mcdu = new MIXERCONTROLDETAILS_UNSIGNED[mcd.cChannels]; memset(mcdu, 0, mcd.cChannels * sizeof(*mcdu)); mux.vol_[i] = mcd; mux.vol_[i].cbDetails = sizeof(*mcdu); mux.vol_[i].paDetails = mcdu; } } void Win32Audio::getMixerCtrls(MIXERLINE& ml, audMux& mux) { MIXERLINECONTROLS mlc; MIXERCONTROL mc[16]; u_int i; memset(&mlc, 0, sizeof(mlc)); memset(mc, 0, sizeof(mc)); mlc.cbStruct = sizeof(mlc); mlc.cbmxctrl = sizeof(mc[0]); mlc.pamxctrl = &mc[0]; mlc.dwLineID = ml.dwLineID; mlc.cControls = ml.cControls; mixerGetLineControls(0, &mlc, MIXER_GETLINECONTROLSF_ALL); for (i = 0; i < mlc.cControls; ++i) { switch (mc[i].dwControlType) { case MIXERCONTROL_CONTROLTYPE_MUX: case MIXERCONTROL_CONTROLTYPE_MIXER: case MIXERCONTROL_CONTROLTYPE_VOLUME: getMixerDetails(ml, mc[i], mux); break; } } /* * if there are multiple source lines for this line, * get their controls */ for (i = 0; i < ml.cConnections; ++i) { MIXERLINE src; memset(&src, 0, sizeof(src)); src.cbStruct = sizeof(src); src.dwSource = i; src.dwDestination = ml.dwDestination; if (mixerGetLineInfo(0, &src, MIXER_GETLINEINFOF_SOURCE) == 0) getMixerCtrls(src, mux); } } void Win32Audio::setupMux(audMux& mux, DWORD ctype) { MIXERLINE l; memset(&l, 0, sizeof(l)); l.cbStruct = sizeof(l); l.dwComponentType = ctype; int s = mixerGetLineInfo(0, &l, MIXER_GETLINEINFOF_COMPONENTTYPE); if (s == 0) getMixerCtrls(l, mux); }