在开发Windows应用程序时,经常需要对音频设备进行管理,例如确保在程序运行期间所有激活的音频输入设备(通常是麦克风)都被静音。为了实现这一功能,编写了两个软件组件:一个系统服务,它实现了一个定时器;以及一个控制台应用程序,定时器的回调函数会在设定的时间间隔到达时调用这个程序。
本文主要介绍的是控制台应用程序的开发,它是基于Vista和Win7操作系统的。在XP、Vista和Win7这三个目标平台上,有两种选择:一是使用XP/传统音频API(例如Mixer API),这些API在所有三个Windows版本上应该仍然可以工作,尽管效率不高,并且可能在即将到来的Win8及以后的版本中被淘汰;二是使用Vista引入的新的核心音频API。由于XP不支持这些API,后者的方法将需要为XP单独实现,这在本文中使用的是微软的音频混合器API(<mmsystem.h>
,是<Windows.h>
的一部分)。
控制台应用程序基于EndpointVolume
Microsoft示例,这个示例随SDK 7.1提供,在multimedia\audio
目录下。对这个示例进行了大量修改,主要是因为它被编写为一个通用的控制台应用程序,接收了许多与目的无关的命令行选项,而且代码只处理输出端点(如扬声器),而不是感兴趣的输入端点。
大部分工作是在控制台应用程序的wmain()
函数中完成的,可以在下面看到。代码中包含了解释性的内联注释。
int wmain(int argc, wchar_t *argv[]) {
// A GUI application should use COINIT_APARTMENTTHREADED instead of COINIT_MULTITHREADED.
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr)) {
printf("\nUnable to initialize COM: %x\n", hr);
goto Exit;
}
IMMDeviceEnumerator *deviceEnumerator = NULL;
//We initialize the device enumerator here
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
if (FAILED(hr)) {
printf("\nUnable to instantiate device enumerator: %x\n", hr);
goto Exit;
}
IMMDeviceCollection *deviceCollection = NULL;
//Here we enumerate the audio endpoints of interest (in this case audio capture endpoints)
//into our device collection. We use "eCapture"
//for audio capture endpoints, "eRender" for
//audio output endpoints and "eAll" for all audio endpoints
hr = deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
if (FAILED(hr)) {
printf("\nUnable to retrieve device collection: %x\n", hr);
goto Exit;
}
UINT deviceCount;
hr = deviceCollection->GetCount(&deviceCount);
if (FAILED(hr)) {
printf("\nUnable to get device collection length: %x\n", hr);
goto Exit;
}
IMMDevice *device = NULL;
//This loop goes over each audio endpoint in our device collection,
//gets and diplays its friendly name and then tries to mute it
for (UINT i = 0; i < deviceCount; i += 1) {
LPWSTR deviceName;
//Here we use the GetDeviceName() function provided with the sample
//(see source code zip)
deviceName = GetDeviceName(deviceCollection, i);
//Get device friendly name
if (deviceName == NULL)
goto Exit;
printf("\nDevice to be muted has index: %d and name: %S\n", i, deviceName);
//this needs to be done because name is stored in a heap allocated buffer
free(deviceName);
device = NULL;
//Put device ref into device var
hr = deviceCollection->Item(i, &device);
if (FAILED(hr)) {
printf("\nUnable to retrieve device %d: %x\n", i, hr);
goto Exit;
}
//This is the Core Audio interface of interest
IAudioEndpointVolume *endpointVolume = NULL;
//We activate it here
hr = device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast(&endpointVolume));
if (FAILED(hr)) {
printf("\nUnable to activate endpoint volume on output device: %x\n", hr);
goto Exit;
}
hr = endpointVolume->SetMute(TRUE, NULL);
//Try to mute endpoint here
if (FAILED(hr)) {
printf("\nUnable to set mute state on endpoint: %x\n", hr);
goto Exit;
} else
printf("\nEndpoint muted successfully!\n");
}
Exit:
//Core Audio and COM clean up here
SafeRelease(&deviceCollection);
SafeRelease(&deviceEnumerator);
SafeRelease(&device);
CoUninitialize();
return 0;
}
核心音频是一个功能强大的低级API,它提供了对音频子系统内核模式组件的直接访问。因此,对于希望对音频设备/端点有强大控制的应用程序来说,这是一个不错的选择。
与控制音频端点(无论是输出还是输入)的音量级别和静音状态相关的最相关的Core Audio接口是IAudioEndpointVolume
。这个接口提供了诸如SetMute()
和SetMasterVolumeLevel()
等方法。SetMute()
在静音系统麦克风方面工作得很好。
上面的代码将静音所有麦克风和任何其他激活/启用的音频捕获设备。它通过枚举所有可用的音频捕获端点来实现这一点:
hr = deviceEnumerator->EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE, &deviceCollection);
注意这里如何使用eCapture
和DEVICE_STATE_ACTIVE
常量调用EnumAudioEndpoints()
函数的。前者的可能值有:
后者的可能值有:
运行控制台应用程序
一旦构建了附带的Visual Studio项目(需要Visual Studio 2010),可以通过在构建目录中调用可执行文件来运行控制台应用程序。控制台应用程序将显示它尝试静音的每个音频捕获端点的名称,以及静音操作的结果(成功/失败)。