mirror of
https://github.com/rgaufman/live555.git
synced 2026-01-12 00:04:30 +08:00
497 lines
16 KiB
C++
497 lines
16 KiB
C++
/**********
|
|
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 3 of the License, or (at your
|
|
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
|
|
|
|
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.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 <WindowsAudioInputDevice_mixer.hh>
|
|
|
|
////////// 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;
|
|
|
|
#ifdef UNICODE
|
|
// Copy the mixer name:
|
|
wcstombs(name, mc.szPname, MAXPNAMELEN);
|
|
#else
|
|
strncpy(name, mc.szPname, MAXPNAMELEN);
|
|
#endif
|
|
|
|
// 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;
|
|
|
|
#ifdef UNICODE
|
|
wcsncpy(ml.Target.szPname, wic.szPname, MAXPNAMELEN);
|
|
#else
|
|
strncpy(ml.Target.szPname, wic.szPname, MAXPNAMELEN);
|
|
#endif
|
|
|
|
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;
|
|
#ifdef UNICODE
|
|
wcstombs(ports[i].name, mlc.szName, MIXER_LONG_NAME_CHARS);
|
|
#else
|
|
strncpy(ports[i].name, mlc.szName, MIXER_LONG_NAME_CHARS);
|
|
#endif
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
|
|
|
|
#ifdef UNICODE
|
|
wchar_t portname[MIXER_LONG_NAME_CHARS+1];
|
|
wcsncpy(portname, ml.szName, MIXER_LONG_NAME_CHARS);
|
|
#else
|
|
char portname[MIXER_LONG_NAME_CHARS+1];
|
|
strncpy(portname, ml.szName, MIXER_LONG_NAME_CHARS);
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
for (unsigned i = 0; i < mcd.cMultipleItems; ++i) {
|
|
if (wcscmp(mcdlText[i].szName, portname) == 0) {
|
|
matchLine = i;
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
for (unsigned i = 0; i < mcd.cMultipleItems; ++i) {
|
|
if (strcmp(mcdlText[i].szName, portname) == 0) {
|
|
matchLine = i;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|