设备事件通知客户端系统中音频端点设备的状态更改。以下是设备事件的示例:
*用户从设备管理器或Windows多媒体控制面板mmsys.cpl启用或禁用音频端点设备。
*用户向系统添加音频适配器或从系统中删除音频适配器。
*用户将音频端点设备插入带有插孔存在检测功能的音频插孔,或从此类插孔中删除音频端点设备。
*用户更改设备角色分配给设备。
*设备更改的属性值。
添加或删除音频适配器会为连接到适配器的所有音频端点设备生成设备事件。前面列表中的前四项是设备状态更改的示例。有关音频终结点设备的设备状态的详细信息,请参阅设备状态xxx常量(DEVICE_STATE_XXX Constants)。有关插孔存在检测的更多信息,请参阅音频端点设备(Audio Endpoint Devices)。
客户端可以注册以在设备事件发生时得到通知。作为对这些通知的响应,客户机可以动态更改其使用特定设备的方式,或者选择其他设备用于特定目的。
例如,如果应用程序正在通过一组USB扬声器播放音频曲目,并且用户从USB接口断开扬声器连接,则应用程序将收到设备事件通知。作为对事件的响应,如果应用程序检测到一组桌面扬声器连接到系统主板上的集成音频适配器,则应用程序可以通过桌面扬声器继续播放音频曲目。在本例中,从USB扬声器到桌面扬声器的转换将自动进行,而无需用户通过显式重定向应用程序进行干预。
要注册以接收设备通知,客户端将调用IMMDeviceEnumerator::RegisterEndpointNotificationCallback方法 。当客户端不再需要通知时,它通过调用IMMDeviceEnumerator::UnregisterEndpointNotificationCallback来取消通知。这两个方法都采用输入参数namedpNotify,这是指向IMMNotificationClient接口实例的指针。
IMMNotificationClient接口由客户端实现。接口包含多个方法,每个方法都用作特定类型设备事件的回调例程。当音频终结点设备中发生设备事件时,MMDevice模块在当前注册接收设备事件通知的每个客户端的IMMNotificationClient接口调用适当的方法。这些调用将事件的描述传递给客户机。有关详细信息,请参阅IMMNotificationClient Interface。
注册接收设备事件通知的客户端将接收系统中所有音频终结点设备中发生的所有类型设备事件的通知。如果客户机只对某些事件类型或某些设备感兴趣,则其IMMNotificationClient 实现中的方法应适当地筛选事件。
Windows SDK提供的示例包括IMMNotificationClient接口的多个实现。有关更多信息,请参阅SDK Samples That Use the Core Audio APIs。
以下代码示例显示了immNotificationClient接口的可能实现:
//-----------------------------------------------------------
// Example implementation of IMMNotificationClient interface.
// When the status of audio endpoint devices change, the
// MMDevice module calls these methods to notify the client.
//-----------------------------------------------------------
#define SAFE_RELEASE(punk) \
if ((punk) != NULL) \
{ (punk)->Release(); (punk) = NULL; }
class CMMNotificationClient : public IMMNotificationClient
{
LONG _cRef;
IMMDeviceEnumerator *_pEnumerator;
// Private function to print device-friendly name
HRESULT _PrintDeviceName(LPCWSTR pwstrId);
public:
CMMNotificationClient() :
_cRef(1),
_pEnumerator(NULL)
{
}
~CMMNotificationClient()
{
SAFE_RELEASE(_pEnumerator)
}
// IUnknown methods -- AddRef, Release, and QueryInterface
ULONG STDMETHODCALLTYPE AddRef()
{
return InterlockedIncrement(&_cRef);
}
ULONG STDMETHODCALLTYPE Release()
{
ULONG ulRef = InterlockedDecrement(&_cRef);
if (0 == ulRef)
{
delete this;
}
return ulRef;
}
HRESULT STDMETHODCALLTYPE QueryInterface(
REFIID riid, VOID **ppvInterface)
{
if (IID_IUnknown == riid)
{
AddRef();
*ppvInterface = (IUnknown*)this;
}
else if (__uuidof(IMMNotificationClient) == riid)
{
AddRef();
*ppvInterface = (IMMNotificationClient*)this;
}
else
{
*ppvInterface = NULL;
return E_NOINTERFACE;
}
return S_OK;
}
// Callback methods for device-event notifications.
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(
EDataFlow flow, ERole role,
LPCWSTR pwstrDeviceId)
{
char *pszFlow = "?????";
char *pszRole = "?????";
_PrintDeviceName(pwstrDeviceId);
switch (flow)
{
case eRender:
pszFlow = "eRender";
break;
case eCapture:
pszFlow = "eCapture";
break;
}
switch (role)
{
case eConsole:
pszRole = "eConsole";
break;
case eMultimedia:
pszRole = "eMultimedia";
break;
case eCommunications:
pszRole = "eCommunications";
break;
}
printf(" -->New default device: flow = %s, role = %s\n",
pszFlow, pszRole);
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId)
{
_PrintDeviceName(pwstrDeviceId);
printf(" -->Added device\n");
return S_OK;
};
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId)
{
_PrintDeviceName(pwstrDeviceId);
printf(" -->Removed device\n");
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(
LPCWSTR pwstrDeviceId,
DWORD dwNewState)
{
char *pszState = "?????";
_PrintDeviceName(pwstrDeviceId);
switch (dwNewState)
{
case DEVICE_STATE_ACTIVE:
pszState = "ACTIVE";
break;
case DEVICE_STATE_DISABLED:
pszState = "DISABLED";
break;
case DEVICE_STATE_NOTPRESENT:
pszState = "NOTPRESENT";
break;
case DEVICE_STATE_UNPLUGGED:
pszState = "UNPLUGGED";
break;
}
printf(" -->New device state is DEVICE_STATE_%s (0x%8.8x)\n",
pszState, dwNewState);
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(
LPCWSTR pwstrDeviceId,
const PROPERTYKEY key)
{
_PrintDeviceName(pwstrDeviceId);
printf(" -->Changed device property "
"{%8.8x-%4.4x-%4.4x-%2.2x%2.2x-%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x}#%d\n",
key.fmtid.Data1, key.fmtid.Data2, key.fmtid.Data3,
key.fmtid.Data4[0], key.fmtid.Data4[1],
key.fmtid.Data4[2], key.fmtid.Data4[3],
key.fmtid.Data4[4], key.fmtid.Data4[5],
key.fmtid.Data4[6], key.fmtid.Data4[7],
key.pid);
return S_OK;
}
};
// Given an endpoint ID string, print the friendly device name.
HRESULT CMMNotificationClient::_PrintDeviceName(LPCWSTR pwstrId)
{
HRESULT hr = S_OK;
IMMDevice *pDevice = NULL;
IPropertyStore *pProps = NULL;
PROPVARIANT varString;
CoInitialize(NULL);
PropVariantInit(&varString);
if (_pEnumerator == NULL)
{
// Get enumerator for audio endpoint devices.
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
NULL, CLSCTX_INPROC_SERVER,
__uuidof(IMMDeviceEnumerator),
(void**)&_pEnumerator);
}
if (hr == S_OK)
{
hr = _pEnumerator->GetDevice(pwstrId, &pDevice);
}
if (hr == S_OK)
{
hr = pDevice->OpenPropertyStore(STGM_READ, &pProps);
}
if (hr == S_OK)
{
// Get the endpoint device's friendly-name property.
hr = pProps->GetValue(PKEY_Device_FriendlyName, &varString);
}
printf("----------------------\nDevice name: \"%S\"\n"
" Endpoint ID string: \"%S\"\n",
(hr == S_OK) ? varString.pwszVal : L"null device",
(pwstrId != NULL) ? pwstrId : L"null ID");
PropVariantClear(&varString);
SAFE_RELEASE(pProps)
SAFE_RELEASE(pDevice)
CoUninitialize();
return hr;
}
前面代码示例中的CMMNotificationClient类是IMMNotificationClient接口的实现。因为IMMNotificationClient继承自IUnknown,所以类定义包含IUnknown方法AddRef,Release, 和QueryInterface的实现。类定义中的其余公共方法特定于IMMNotificationClient接口。这些方法是:
OnDefaultDeviceChanged, 当用户更改音频终结点设备的设备角色时调用。
OnDeviceAdded, 当用户向系统添加音频端点设备时调用。
OnDeviceRemoved, 当用户从系统中删除音频终结点设备时调用。
OnDeviceStateChanged, 当音频端点设备的设备状态更改时调用。(有关设备状态的详细信息,请参阅DEVICE_STATE_ XXX Constants.)
OnPropertyValueChanged, 当音频终结点设备的属性值更改时调用。
每个方法都接受一个输入参数pwstrDeviceId,它是指向端点ID字符串的指针。字符串标识发生设备事件的音频终结点设备。
在前面的代码示例中,_PrintDeviceName是CMMNotificationClient类中打印设备友好名称的私有方法。_ _PrintDeviceName将端点ID字符串作为输入参数。它将字符串传递给IMMDeviceEnumerator::GetDevice,GetDevice创建一个端点设备对象来表示该设备,并向该对象提供IMMDevice接口。接下来,_PrintDeviceName调用IMMDevice::OpenPropertyStore方法来检索设备属性存储的IPropertyStore接口。最后,_PrintDeviceName调用IPropertyStore::GetItem方法以获取设备的友好名称属性。有关IPropertyStore的详细信息,请参阅Windows SDK文档。
除了设备事件外,客户端还可以注册以接收音频会话事件和终结音量事件的通知。更多信息,请参考IAudioSessionEvents Interface和IAudioEndpointVolumeCallback Interface.
原文链接: https://docs.microsoft.com/zh-cn/windows/desktop/CoreAudio/device-events