65.9K
CodeProject 正在变化。 阅读更多。
Home

静音 WinXP 上的所有麦克风

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (6投票s)

2012 年 8 月 12 日

CPOL

4分钟阅读

viewsIcon

27449

downloadIcon

919

如何使用音频混音器 API 枚举和静音所有混音器设备下的所有麦克风组件

介绍  

本文是我的 另一篇文章(关于如何在 Windows Vista/7 上静音所有麦克风)的 Windows XP 版本。

我最近不得不编写一个应用程序,该应用程序的功能包括确保在运行时所有活动的/启用的音频捕获设备端点(通常是麦克风)在 Windows XP/Vista/7 上都保证被静音。为了实现这一点,我编写了两款软件:一个实现计时器的系统服务和一个由计时器回调在每次设置间隔过去时调用的控制台应用程序。

控制台应用程序有两个版本。我 上一篇文章 中介绍的主要版本使用 Core Audio API(在 Windows Vista 中引入),适用于所有支持 Core Audio API 的 Windows 操作系统版本(Windows Vista 及更高版本)。本文介绍的另一个版本使用 Windows Multimedia API(Mixer API),适用于 Windows XP,可能还适用于更早的 Windows 版本(未测试!)。

背景  

现在被称为“旧版音频和视频”的 API 在 Windows 平台上有着悠久的历史,最早可以追溯到一些早期版本的 Windows 操作系统,例如,DirectSound 的早期实现可在 Windows 95 上使用。

在以编程方式静音 Windows XP 上的麦克风时,我认为最方便使用的是 音频混音器 API,它是 多媒体音频 API 的一部分,而多媒体音频 API 又属于 Windows 多媒体 旧版 API。 

音频混音器参考》页面记录了许多函数(例如 mixerOpen()mixerGetLineControls()mixerGetControlDetails()mixerSetControlDetails()mixerGetLineInfo()),这些函数为程序员提供了访问和操作代表各种声音系统组件(如扬声器和麦克风)状态的混音器控件的方法。 

这个 API 的一个明显概念性问题(这可能是它在 Vista 中被 CoreAudio API 取代的部分原因)是,它实际上是通过控制表示这些声音系统组件(例如音量、静音状态)的图形小部件/控件(显示在音频混音器类窗口上)来控制它们的状态,而不是在更低的级别访问结构。然而,它似乎确实可靠地完成了它的工作。

请注意,尽管音频混音器 API 在更高版本的 Windows(例如 Vista/7)中仍然可用,但它并不是控制音频组件的首选方法,尽管在某些情况下,如果访问它的可执行文件以 XP 兼容模式运行,它可能如预期般工作。其原因在微软 这个论坛帖子 中由微软的 Larry Osterman 解释了:音频混音器 API 在 XP 之后的 Windows 版本中已被虚拟化,因此即使在兼容模式下运行,也可能无法如预期般工作。

我实际上在 Win7 x64 安装上以 XP 兼容模式运行了由该代码生成的exe,并且在下面提供的代码的某些(但非全部)变体中,它都能正常工作(这些变体在 XP 上都能正常工作)。

使用代码

我并非从零开始进行这项工作。我的起点是微软在 此 KB 文章 中发布的音频混音器 API 代码。那里列出了三个函数(UnMute()SetVolume()SelectMic())。与我想做的事情最相关的函数是 UnMute()

下面列出了 UnMute() 函数的原始版本

void UnMute()
{
   // Open the mixer device
   HMIXER hmx;
   mixerOpen(&hmx, 0, 0, 0, 0);
 
   // Get the line info for the wave in destination line
   MIXERLINE mxl;
  mxl.cbStruct = sizeof(mxl);
  mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
  mixerGetLineInfo((HMIXEROBJ)hmx, &mxl, MIXER_GETLINEINFOF_COMPONENTTYPE);
 
   // Now find the microphone source line connected to this wave in
   // destination
   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;
   }
   // Find a mute control, if any, of the microphone line
   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)){
      // Found, so proceed
      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);
      // Unmute the microphone line (for both channels)
      pbool[0].fValue = pbool[cChannels - 1].fValue = 0;
      mixerSetControlDetails((HMIXEROBJ)hmx, &mxcd,
MIXER_SETCONTROLDETAILSF_VALUE);
 
    free(pmxctrl);
    free(pbool);
   }
  else
    free(pmxctrl);
 
   mixerClose(hmx);
}

我首先逆转了这个函数的功能,使其能够静音已发现的麦克风而不是取消静音。这很简单:只需将 '0' 改为 '1'。

// Unmute the microphone line (for both channels)
pbool[0].fValue = pbool[cChannels - 1].fValue = 1;

您会注意到,上面的微软逻辑只关心发现第一个混音器设备上的第一个麦克风类型组件。我们可以从以下两个代码片段中看到这一点

if (MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE == mxl.dwComponentType)
    break;

在上面的代码片段中,它们一旦找到第一个麦克风就退出循环。 诚然,这在大多数系统上都足够了,但如果有更多的麦克风呢?

mixerOpen(&hmx, 0, 0, 0, 0);

在上面第二个代码片段中,它们只关心打开索引为 0 的混音器设备。如果有更多的声卡或其他类型的连接声音设备呢? 

为了解决这些问题并使我的代码能够静音 **所有** 麦克风,无论它们是在同一个还是不同的混音器设备上,我修改了代码如下: 

void MuteAllMixerMics()
{
    HMIXER hmx;

    UINT i = 0;
    while (mixerOpen(&hmx, i++, 0, 0, 0) == MMSYSERR_NOERROR)
    {
        // Get the line info for the wave in destination line
        MIXERLINE mxl;
        mxl.cbStruct = sizeof(mxl);
        mxl.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
        mixerGetLineInfo((HMIXEROBJ)hmx, &mxl, MIXER_GETLINEINFOF_COMPONENTTYPE);

        // Now find the microphone source line connected to this wave in
        // destination
        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)
            {
                //Find a mute control, if any, of the microphone line
                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))
                {
                    //Found, so proceed
                    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);
                    
                    //Mute the microphone line (for both channels)
                    pbool[0].fValue = pbool[cChannels - 1].fValue = 1;
                    	
                    mixerSetControlDetails((HMIXEROBJ)hmx, &mxcd, MIXER_SETCONTROLDETAILSF_VALUE);
                    
                    free(pmxctrl);
                    free(pbool);
                }
                else
                    free(pmxctrl);
            }
		}
        
        mixerClose(hmx);
    }
}
 

关注点

上面的代码为系统已知的每个混音器设备重复麦克风发现和静音操作。它像这样遍历可用的混音器设备

while (mixerOpen(&hmx, i++, 0, 0, 0) == MMSYSERR_NOERROR)

根据 其参考mixerOpen() 在找到一个混音器设备时,在递增的 UINT 索引 i 下,每次都会返回 MMSYSERR_NOERROR。所以我们循环直到 mixerOpen() 返回其他内容(即错误),然后像这样搜索每个找到的混音器,以查找可能不止一个麦克风。

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)
    {
        ...
    }
    ...
}
 

如何编译和运行 

我使用 Visual Studio 2010 创建了附加的完整项目;所以您需要安装 VS 2010,或者使用您自己的 Visual Studio 版本创建新项目,并使用提供的源代码文件。

只需在 XP 的命令行中编译并在生成目录中运行生成的exe,或者在 Windows Explorer 中双击它运行。

历史 

这是本文的 V 1.0 版本。

© . All rights reserved.