Introduction
This article is the Windows XP counterpart of my other article on how to mute all microphones on Windows Vista/7 systems.
I recently had to write a application that among other things makes sure all active/enabled audio capture device endpoints (usually microphones), on Windows XP/Vista/7, are guaranteed muted while it runs. To accomplish this I wrote two pieces of software: a system service that implements a timer and a console application that the timer's callback invokes every time a set interval elapses.
The console application has two versions. The main version, presented in my previous article, uses the Core Audio API, introduced in Windows Vista, and works on all Windows OS versions supporting the Core Audio API (Windows Vista and later). The other version, presented in this article, works on Windows XP, and maybe earlier Windows versions (untested!), using the Windows Multimedia APIs (Mixer API).
Background
What is now referred to as "Legacy Audio and Video" APIs have a long history on the Windows platform starting off on some of the earliest version of Windows OS, e.g. an early implementation of DirectSound was available on Windows 95.
What I found most convenient to work with, in programmatically muting microphones on Windows XP, is the Audio Mixers API which is part of the Multimedia Audio API, itself part of the Windows Multimedia legacy APIs.
The Audio Mixer Reference page documents a number of functions (e.g. mixerOpen()
, mixerGetLineControls()
, mixerGetControlDetails()
, mixerSetControlDetails()
, mixerGetLineInfo()
) that provide ways for the programmer to access and manipulate the mixer controls representing the state of various sound system components like speakers and microphones.
One obvious conceptual problem that this API has (and this could be part of the reason it got superseded on Vista by the CoreAudio API) is that it is actually controlling the state of the sound system components (e.g. volume, mute state) by controlling the graphical widgets/controls (shown on audio mixer type windows) that represent them, instead of accessing structures at a lower level. However, it does seem to be doing its job reliably.
Please note that although the Audio Mixer API is still available on later Windows versions (e.g. Vista/7), it is not the recommended way to control audio components although it may, in some cases, actually work as expected if the executables accessing it are run in XP compatibility mode. The reason for this is explained on this thread by Microsoft's own Larry Osterman: the Audio Mixer API has been virtualized in post-XP Windows versions and therefore it may not work as expected even when run in compatibility mode.
I have actually run the executable produced by this code in XP compatibility mode on a Win7 x64 installation and it did work properly in some (but not all) variations of the code presented below (variations that all worked properly on XP).
Using the code
I did not start my work on this on a clean slate. My starting point was Audio Mixer API code posted by Microsoft on
this KB article. There are three functions listed there (
UnMute()
,
SetVolume()
and
SelectMic()
). The function most relevant to what I wanted to do is
UnMute()
.
The original version of the UnMute() function is listed below:
void UnMute()
{
HMIXER hmx;
mixerOpen(&hmx, 0, 0, 0, 0);
MIXERLINE mxl;
mxl.cbStruct = sizeof(mxl);
mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
mixerGetLineInfo((HMIXEROBJ)hmx, &mxl, MIXER_GETLINEINFOF_COMPONENTTYPE);
DWORD cConnections = mxl.cConnections;
for(DWORD j=0; j<cConnections; j++){
mxl.dwSource = j;
mixerGetLineInfo((HMIXEROBJ)hmx, &mxl, MIXER_GETLINEINFOF_SOURCE);
if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == mxl.dwComponentType)
break;
}
LPMIXERCONTROL pmxctrl = (LPMIXERCONTROL)malloc(sizeof MIXERCONTROL);
MIXERLINECONTROLS mxlctrl = {sizeof mxlctrl, mxl.dwLineID,
MIXERCONTROL_CONTROLTYPE_MUTE, 1, sizeof MIXERCONTROL, pmxctrl};
if(!mixerGetLineControls((HMIXEROBJ) hmx, &mxlctrl,
MIXER_GETLINECONTROLSF_ONEBYTYPE)){
DWORD cChannels = mxl.cChannels;
if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
cChannels = 1;
LPMIXERCONTROLDETAILS_BOOLEAN pbool =
(LPMIXERCONTROLDETAILS_BOOLEAN) malloc(cChannels * sizeof
MIXERCONTROLDETAILS_BOOLEAN);
MIXERCONTROLDETAILS mxcd = {sizeof(mxcd), pmxctrl->dwControlID,
cChannels, (HWND)0,
sizeof MIXERCONTROLDETAILS_BOOLEAN, (LPVOID) pbool};
mixerGetControlDetails((HMIXEROBJ)hmx, &mxcd,
MIXER_SETCONTROLDETAILSF_VALUE);
pbool[0].fValue = pbool[cChannels - 1].fValue = 0;
mixerSetControlDetails((HMIXEROBJ)hmx, &mxcd,
MIXER_SETCONTROLDETAILSF_VALUE);
free(pmxctrl);
free(pbool);
}
else
free(pmxctrl);
mixerClose(hmx);
}
I started off by reversing the functionality of this function so that it actually mutes the discovered microphone instead of un-muting it. This is pretty simple to do; just change a '0
' to a '1
'.
pbool[0].fValue = pbool[cChannels - 1].fValue = 1;
As you will notice, the Microsoft logic above only cares to discover the first microphone type component on the first mixer device. We can see this in the following two snippets:
if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == mxl.dwComponentType)
break;
In the code snippet right above they just break out of the loop once the first microphone has been found. Granted, this will suffice on most systems but what is there's more microphones?
mixerOpen(&hmx, 0, 0, 0, 0);
In the second code snippet right above they only care to open the mixer device at index 0. What if there's more soundcards or some other type of connected sound device?
To address these issues and make my code mute all microphones, whether on the same or different mixer devices, I modified the code as follows:
void MuteAllMixerMics()
{
HMIXER hmx;
UINT i = 0;
while (mixerOpen(&hmx, i++, 0, 0, 0) == MMSYSERR_NOERROR)
{
MIXERLINE mxl;
mxl.cbStruct = sizeof(mxl);
mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
mixerGetLineInfo((HMIXEROBJ)hmx, &mxl, MIXER_GETLINEINFOF_COMPONENTTYPE);
DWORD cConnections = mxl.cConnections;
for(DWORD j = 0; j < cConnections; j++)
{
mxl.dwSource = j;
mixerGetLineInfo((HMIXEROBJ)hmx, &mxl, MIXER_GETLINEINFOF_SOURCE);
if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == mxl.dwComponentType)
{
LPMIXERCONTROL pmxctrl = (LPMIXERCONTROL)malloc(sizeof MIXERCONTROL);
MIXERLINECONTROLS mxlctrl = {sizeof mxlctrl, mxl.dwLineID,
MIXERCONTROL_CONTROLTYPE_MUTE, 1, sizeof MIXERCONTROL, pmxctrl};
if(!mixerGetLineControls((HMIXEROBJ) hmx, &mxlctrl,
MIXER_GETLINECONTROLSF_ONEBYTYPE))
{
DWORD cChannels = mxl.cChannels;
if (MIXERCONTROL_CONTROLF_UNIFORM & pmxctrl->fdwControl)
cChannels = 1;
LPMIXERCONTROLDETAILS_BOOLEAN pbool = (LPMIXERCONTROLDETAILS_BOOLEAN)
malloc(cChannels *
sizeof MIXERCONTROLDETAILS_BOOLEAN);
MIXERCONTROLDETAILS mxcd = {sizeof(mxcd), pmxctrl->dwControlID, cChannels, (HWND)0,
sizeof MIXERCONTROLDETAILS_BOOLEAN, (LPVOID) pbool};
mixerGetControlDetails((HMIXEROBJ)hmx, &mxcd, MIXER_SETCONTROLDETAILSF_VALUE);
pbool[0].fValue = pbool[cChannels - 1].fValue = 1;
mixerSetControlDetails((HMIXEROBJ)hmx, &mxcd, MIXER_SETCONTROLDETAILSF_VALUE);
free(pmxctrl);
free(pbool);
}
else
free(pmxctrl);
}
}
mixerClose(hmx);
}
}
Points of Interest
The code above repeats the microphone discovery and muting operations for each mixer device known by the system. It iterates over available mixer devices like this:
while (mixerOpen(&hmx, i++, 0, 0, 0) == MMSYSERR_NOERROR)
According to its reference, mixerOpen()
will return MMSYSERR_NOERROR
everytime it finds a mixer device under the incremented UINT
index i
. So we loop until mixerOpen()
returns something else (i.e. an error) and search each found mixer for possibly more than one microphones like this:
DWORD cConnections = mxl.cConnections;
for(DWORD j = 0; j < cConnections; j++)
{
mxl.dwSource = j;
mixerGetLineInfo((HMIXEROBJ)hmx, &mxl, MIXER_GETLINEINFOF_SOURCE);
if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == mxl.dwComponentType)
{
...
}
...
}
How to compile and run
I created the attached full project using Visual Studio 2010; so you will need to either have VS 2010 installed on create a new project with your own version of Visual Studion using the provided source code files.
Just compile and run the executable produced in the build directory either on XP's command line or by double clicking it on Windows Explorer.
History
This is V 1.0 of the article.