/********** This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. (See .) This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA **********/ // Copyright (c) 2001-2004 Live Networks, Inc. All rights reserved. // Windows implementation of a generic audio input device // This version uses Windows' built-in software mixer. // Implementation #include ////////// Mixer and AudioInputPort definition ////////// class AudioInputPort { public: int tag; DWORD dwComponentType; char name[MIXER_LONG_NAME_CHARS]; }; class Mixer { public: Mixer(); virtual ~Mixer(); void open(unsigned numChannels, unsigned samplingFrequency, unsigned granularityInMS); void open(); // open with default parameters void getPortsInfo(); Boolean enableInputPort(unsigned portIndex, char const*& errReason, MMRESULT& errCode); void close(); unsigned index; HMIXER hMixer; // valid when open DWORD dwRecLineID; // valid when open unsigned numPorts; AudioInputPort* ports; char name[MAXPNAMELEN]; }; ////////// AudioInputDevice (remaining) implementation ////////// AudioInputDevice* AudioInputDevice::createNew(UsageEnvironment& env, int inputPortNumber, unsigned char bitsPerSample, unsigned char numChannels, unsigned samplingFrequency, unsigned granularityInMS) { Boolean success; WindowsAudioInputDevice* newSource = new WindowsAudioInputDevice(env, inputPortNumber, bitsPerSample, numChannels, samplingFrequency, granularityInMS, success); if (!success) {delete newSource; newSource = NULL;} return newSource; } AudioPortNames* AudioInputDevice::getPortNames() { WindowsAudioInputDevice::initializeIfNecessary(); AudioPortNames* portNames = new AudioPortNames; portNames->numPorts = WindowsAudioInputDevice::numInputPortsTotal; portNames->portName = new char*[WindowsAudioInputDevice::numInputPortsTotal]; // If there's more than one mixer, print only the port name. // If there's two or more mixers, also include the mixer name // (to disambiguate port names that may be the same name in different mixers) char portNameBuffer[2*MAXPNAMELEN+10/*slop*/]; char mixerNameBuffer[MAXPNAMELEN]; char const* portNameFmt; if (WindowsAudioInputDevice::numMixers <= 1) { portNameFmt = "%s"; } else { portNameFmt = "%s (%s)"; } unsigned curPortNum = 0; for (unsigned i = 0; i < WindowsAudioInputDevice::numMixers; ++i) { Mixer& mixer = WindowsAudioInputDevice::ourMixers[i]; if (WindowsAudioInputDevice::numMixers <= 1) { mixerNameBuffer[0] = '\0'; } else { strncpy(mixerNameBuffer, mixer.name, sizeof mixerNameBuffer); #if 0 // Hack: Simplify the mixer name, by truncating after the first space character: for (int k = 0; k < sizeof mixerNameBuffer && mixerNameBuffer[k] != '\0'; ++k) { if (mixerNameBuffer[k] == ' ') { mixerNameBuffer[k] = '\0'; break; } } #endif } for (unsigned j = 0; j < mixer.numPorts; ++j) { sprintf(portNameBuffer, portNameFmt, mixer.ports[j].name, mixerNameBuffer); portNames->portName[curPortNum++] = strDup(portNameBuffer); } } return portNames; } ////////// WindowsAudioInputDevice implementation ////////// WindowsAudioInputDevice ::WindowsAudioInputDevice(UsageEnvironment& env, int inputPortNumber, unsigned char bitsPerSample, unsigned char numChannels, unsigned samplingFrequency, unsigned granularityInMS, Boolean& success) : WindowsAudioInputDevice_common(env, inputPortNumber, bitsPerSample, numChannels, samplingFrequency, granularityInMS), fCurMixerId(-1) { success = initialSetInputPort(inputPortNumber); } WindowsAudioInputDevice::~WindowsAudioInputDevice() { if (fCurMixerId >= 0) ourMixers[fCurMixerId].close(); delete[] ourMixers; ourMixers = NULL; numMixers = numInputPortsTotal = 0; } void WindowsAudioInputDevice::initializeIfNecessary() { if (ourMixers != NULL) return; // we've already been initialized numMixers = mixerGetNumDevs(); ourMixers = new Mixer[numMixers]; // Initialize each mixer: numInputPortsTotal = 0; for (unsigned i = 0; i < numMixers; ++i) { Mixer& mixer = ourMixers[i]; mixer.index = i; mixer.open(); if (mixer.hMixer != NULL) { // This device has a valid mixer. Get information about its ports: mixer.getPortsInfo(); mixer.close(); if (mixer.numPorts == 0) continue; numInputPortsTotal += mixer.numPorts; } else { mixer.ports = NULL; mixer.numPorts = 0; } } } Boolean WindowsAudioInputDevice::setInputPort(int portIndex) { initializeIfNecessary(); if (portIndex < 0 || portIndex >= (int)numInputPortsTotal) { // bad index envir().setResultMsg("Bad input port index\n"); return False; } // Find the mixer and port that corresponds to "portIndex": int newMixerId, portWithinMixer, portIndexCount = 0; for (newMixerId = 0; newMixerId < (int)numMixers; ++newMixerId) { int prevPortIndexCount = portIndexCount; portIndexCount += ourMixers[newMixerId].numPorts; if (portIndexCount > portIndex) { // it's with this mixer portWithinMixer = portIndex - prevPortIndexCount; break; } } // Check that this mixer is allowed: if (allowedDeviceNames != NULL) { int i; for (i = 0; allowedDeviceNames[i] != NULL; ++i) { if (strncmp(ourMixers[newMixerId].name, allowedDeviceNames[i], strlen(allowedDeviceNames[i])) == 0) { // The allowed device name is a prefix of this mixer's name break; // this mixer is allowed } } if (allowedDeviceNames[i] == NULL) { // this mixer is not on the allowed list envir().setResultMsg("Access to this audio device is not allowed\n"); return False; } } if (newMixerId != fCurMixerId) { // The mixer has changed, so close the old one and open the new one: if (fCurMixerId >= 0) ourMixers[fCurMixerId].close(); fCurMixerId = newMixerId; ourMixers[fCurMixerId].open(fNumChannels, fSamplingFrequency, fGranularityInMS); } if (portIndex != fCurPortIndex) { // Change the input port: fCurPortIndex = portIndex; char const* errReason; MMRESULT errCode; if (!ourMixers[newMixerId].enableInputPort(portWithinMixer, errReason, errCode)) { char resultMsg[100]; sprintf(resultMsg, "Failed to enable input port: %s failed (0x%08x)\n", errReason, errCode); envir().setResultMsg(resultMsg); return False; } // Later, may also need to transfer 'gain' to new port ##### } return True; } unsigned WindowsAudioInputDevice::numMixers = 0; Mixer* WindowsAudioInputDevice::ourMixers = NULL; unsigned WindowsAudioInputDevice::numInputPortsTotal = 0; ////////// Mixer and AudioInputPort implementation ////////// Mixer::Mixer() : hMixer(NULL), dwRecLineID(0), numPorts(0), ports(NULL) { } Mixer::~Mixer() { delete[] ports; } void Mixer::open(unsigned numChannels, unsigned samplingFrequency, unsigned granularityInMS) { HMIXER newHMixer = NULL; do { MIXERCAPS mc; if (mixerGetDevCaps(index, &mc, sizeof mc) != MMSYSERR_NOERROR) break; // Copy the mixer name: strncpy(name, mc.szPname, MAXPNAMELEN); // Find the correct line for this mixer: unsigned i, uWavIn; unsigned nWavIn = waveInGetNumDevs(); for (i = 0; i < nWavIn; ++i) { WAVEINCAPS wic; if (waveInGetDevCaps(i, &wic, sizeof wic) != MMSYSERR_NOERROR) continue; MIXERLINE ml; ml.cbStruct = sizeof ml; ml.Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN; strncpy(ml.Target.szPname, wic.szPname, MAXPNAMELEN); ml.Target.vDriverVersion = wic.vDriverVersion; ml.Target.wMid = wic.wMid; ml.Target.wPid = wic.wPid; if (mixerGetLineInfo((HMIXEROBJ)index, &ml, MIXER_GETLINEINFOF_TARGETTYPE/*|MIXER_OBJECTF_MIXER*/) == MMSYSERR_NOERROR) { // this is the right line uWavIn = i; dwRecLineID = ml.dwLineID; break; } } if (i >= nWavIn) break; // error: we couldn't find the right line if (mixerOpen(&newHMixer, index, (unsigned long)NULL, (unsigned long)NULL, MIXER_OBJECTF_MIXER) != MMSYSERR_NOERROR) break; if (newHMixer == NULL) break; // Sanity check: re-call "mixerGetDevCaps()" using the mixer device handle: if (mixerGetDevCaps((UINT)newHMixer, &mc, sizeof mc) != MMSYSERR_NOERROR) break; if (mc.cDestinations < 1) break; // error: this mixer has no destinations if (!WindowsAudioInputDevice_common::openWavInPort(uWavIn, numChannels, samplingFrequency, granularityInMS)) break; hMixer = newHMixer; return; } while (0); // An error occurred: close(); } void Mixer::open() { open(1, 8000, 20); } void Mixer::getPortsInfo() { MIXERCAPS mc; mixerGetDevCaps((UINT)hMixer, &mc, sizeof mc); MIXERLINE mlt; unsigned i; for (i = 0; i < mc.cDestinations; ++i) { memset(&mlt, 0, sizeof mlt); mlt.cbStruct = sizeof mlt; mlt.dwDestination = i; if (mixerGetLineInfo((HMIXEROBJ)hMixer, &mlt, MIXER_GETLINEINFOF_DESTINATION) != MMSYSERR_NOERROR) continue; if (mlt.dwLineID == dwRecLineID) break; // this is the destination we're interested in } ports = new AudioInputPort[mlt.cConnections]; numPorts = mlt.cConnections; for (i = 0; i < numPorts; ++i) { MIXERLINE mlc; memcpy(&mlc, &mlt, sizeof mlc); mlc.dwSource = i; mixerGetLineInfo((HMIXEROBJ)hMixer, &mlc, MIXER_GETLINEINFOF_SOURCE/*|MIXER_OBJECTF_HMIXER*/); ports[i].tag = mlc.dwLineID; ports[i].dwComponentType = mlc.dwComponentType; strncpy(ports[i].name, mlc.szName, MIXER_LONG_NAME_CHARS); } // Make the microphone the first port in the list: for (i = 1; i < numPorts; ++i) { #ifdef OLD_MICROPHONE_TESTING_CODE if (_strnicmp("mic", ports[i].name, 3) == 0 || _strnicmp("mik", ports[i].name, 3) == 0) { #else if (ports[i].dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE) { #endif AudioInputPort tmp = ports[0]; ports[0] = ports[i]; ports[i] = tmp; } } } Boolean Mixer::enableInputPort(unsigned portIndex, char const*& errReason, MMRESULT& errCode) { errReason = NULL; // unless there's an error AudioInputPort& port = ports[portIndex]; MIXERCONTROL mc; mc.cMultipleItems = 1; // in case it doesn't get set below MIXERLINECONTROLS mlc; #if 0 // the following doesn't seem to be needed, and can fail: mlc.cbStruct = sizeof mlc; mlc.pamxctrl = &mc; mlc.cbmxctrl = sizeof (MIXERCONTROL); mlc.dwLineID = port.tag; mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; if ((errCode = mixerGetLineControls((HMIXEROBJ)hMixer, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE/*|MIXER_OBJECTF_HMIXER*/)) != MMSYSERR_NOERROR) { errReason = "mixerGetLineControls()"; return False; } #endif MIXERLINE ml; memset(&ml, 0, sizeof (MIXERLINE)); ml.cbStruct = sizeof (MIXERLINE); ml.dwLineID = port.tag; if ((errCode = mixerGetLineInfo((HMIXEROBJ)hMixer, &ml, MIXER_GETLINEINFOF_LINEID)) != MMSYSERR_NOERROR) { errReason = "mixerGetLineInfo()1"; return False; } char portname[MIXER_LONG_NAME_CHARS+1]; strncpy(portname, ml.szName, MIXER_LONG_NAME_CHARS); memset(&ml, 0, sizeof (MIXERLINE)); ml.cbStruct = sizeof (MIXERLINE); ml.dwLineID = dwRecLineID; if ((errCode = mixerGetLineInfo((HMIXEROBJ)hMixer, &ml, MIXER_GETLINEINFOF_LINEID/*|MIXER_OBJECTF_HMIXER*/)) != MMSYSERR_NOERROR) { errReason = "mixerGetLineInfo()2"; return False; } // Get Mixer/MUX control information (need control id to set and get control details) mlc.cbStruct = sizeof mlc; mlc.dwLineID = ml.dwLineID; mlc.cControls = 1; mc.cbStruct = sizeof mc; // Needed???##### mc.dwControlID = 0xDEADBEEF; // For testing ##### mlc.pamxctrl = &mc; mlc.cbmxctrl = sizeof mc; mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX; // Single Select if ((errCode = mixerGetLineControls((HMIXEROBJ)hMixer, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE/*|MIXER_OBJECTF_HMIXER*/)) != MMSYSERR_NOERROR) { mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_MIXER; // Multiple Select mixerGetLineControls((HMIXEROBJ)hMixer, &mlc, MIXER_GETLINECONTROLSF_ONEBYTYPE/*|MIXER_OBJECTF_HMIXER*/); } unsigned matchLine = 0; if (mc.cMultipleItems > 1) { // Before getting control, we need to know which line to grab. // We figure this out by listing the lines, and comparing names: MIXERCONTROLDETAILS mcd; mcd.cbStruct = sizeof mcd; mcd.cChannels = ml.cChannels; mcd.cMultipleItems = mc.cMultipleItems; MIXERCONTROLDETAILS_LISTTEXT* mcdlText = new MIXERCONTROLDETAILS_LISTTEXT[mc.cMultipleItems]; mcd.cbDetails = sizeof (MIXERCONTROLDETAILS_LISTTEXT); mcd.paDetails = mcdlText; if (mc.dwControlID != 0xDEADBEEF) { // we know the control id for real mcd.dwControlID = mc.dwControlID; if ((errCode = mixerGetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_GETCONTROLDETAILSF_LISTTEXT/*|MIXER_OBJECTF_HMIXER*/)) != MMSYSERR_NOERROR) { delete[] mcdlText; errReason = "mixerGetControlDetails()1"; return False; } } else { // Hack: We couldn't find a MUX or MIXER control, so try to guess the control id: for (mc.dwControlID = 0; mc.dwControlID < 32; ++mc.dwControlID) { mcd.dwControlID = mc.dwControlID; if ((errCode = mixerGetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_GETCONTROLDETAILSF_LISTTEXT/*|MIXER_OBJECTF_HMIXER*/)) == MMSYSERR_NOERROR) break; } if (mc.dwControlID == 32) { // unable to guess mux/mixer control id delete[] mcdlText; errReason = "mixerGetControlDetails()2"; return False; } } for (unsigned i = 0; i < mcd.cMultipleItems; ++i) { if (strcmp(mcdlText[i].szName, portname) == 0) { matchLine = i; break; } } delete[] mcdlText; } // Now get control itself: MIXERCONTROLDETAILS mcd; mcd.cbStruct = sizeof mcd; mcd.dwControlID = mc.dwControlID; mcd.cChannels = ml.cChannels; mcd.cMultipleItems = mc.cMultipleItems; MIXERCONTROLDETAILS_BOOLEAN* mcdbState = new MIXERCONTROLDETAILS_BOOLEAN[mc.cMultipleItems]; mcd.paDetails = mcdbState; mcd.cbDetails = sizeof (MIXERCONTROLDETAILS_BOOLEAN); if ((errCode = mixerGetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_GETCONTROLDETAILSF_VALUE/*|MIXER_OBJECTF_HMIXER*/)) != MMSYSERR_NOERROR) { delete[] mcdbState; errReason = "mixerGetControlDetails()3"; return False; } for (unsigned j = 0; j < mcd.cMultipleItems; ++j) { mcdbState[j].fValue = (j == matchLine); } if ((errCode = mixerSetControlDetails((HMIXEROBJ)hMixer, &mcd, MIXER_OBJECTF_HMIXER)) != MMSYSERR_NOERROR) { delete[] mcdbState; errReason = "mixerSetControlDetails()"; return False; } delete[] mcdbState; return True; } void Mixer::close() { WindowsAudioInputDevice_common::waveIn_close(); if (hMixer != NULL) mixerClose(hMixer); hMixer = NULL; dwRecLineID = 0; }