设备事件(核心音频 API)
设备事件通知客户端系统 音频终结点设备的状态发生更改。 下面是设备事件的示例:
- 用户从设备管理器或 Windows 多媒体控制面板启用或禁用音频终结点设备,Mmsys.cpl。
- 用户向系统添加音频适配器,或者从系统中删除音频适配器。
- 用户使用插孔检测将音频终结点设备插入音频插孔,或者从此类插孔中删除音频终结点设备。
- 用户更改分配给设备的 设备角色。
- 设备 属性的值 更改。
添加或删除音频适配器将为连接到适配器的所有音频终结点设备生成设备事件。 前面的列表中的前四项是设备状态更改的示例。 有关音频终结点设备的设备状态的详细信息,请参阅 DEVICE_STATE_XXX 常量。 有关插孔状态检测的详细信息,请参阅 音频终结点设备。
客户端可以注册,以在发生设备事件时收到通知。 为了响应这些通知,客户端可以动态更改其使用特定设备的方式,或选择要用于特定目的的其他设备。
例如,如果应用程序正在通过一组 USB 扬声器播放音频轨道,并且用户断开扬声器与 USB 连接器的连接,则应用程序会收到设备事件通知。 为了响应事件,如果应用程序检测到一组桌面扬声器已连接到系统主板上的集成音频适配器,则应用程序可以通过桌面扬声器恢复播放音频轨道。 在此示例中,从 USB 扬声器到桌面扬声器的转换会自动发生,无需用户通过显式重定向应用程序进行干预。
若要注册以接收设备通知,客户端调用 IMMDeviceEnumerator::RegisterEndpointNotificationCallback 方法。 当客户端不再需要通知时,它会通过调用 IMMDeviceEnumerator::UnregisterEndpointNotificationCallback 方法取消通知。 这两种方法采用名为 pClient 的输入参数,该参数是指向 IMMNotificationClient 接口实例的指针。
IMMNotificationClient 接口由客户端实现。 该接口包含多个方法,每个方法都用作特定类型的设备事件的回调例程。 当设备事件发生在音频终结点设备中时,MMDevice 模块会在 IMMNotificationClient 接口中调用当前注册以接收设备事件通知的每个客户端的相应方法。 这些调用将事件的说明传递给客户端。 有关详细信息,请参阅 IMMNotificationClient 接口。
注册以接收设备事件通知的客户端将接收系统中所有音频终结点设备中发生的所有类型的设备事件的通知。 如果客户端仅对某些事件类型或某些设备感兴趣,则其 IMMNotificationClient 实现中的方法应相应地筛选事件。
Windows SDK 提供的示例包括 IMMNotificationClient 接口的多个实现。 有关详细信息,请参阅 使用核心音频 API的 SDK 示例。
以下代码示例演示了 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,因此类定义包含 addRef 、Release和 QueryInterfaceIUnknown 方法 的实现。 类定义中的其余公共方法特定于 IMMNotificationClient 接口。 这些方法是:
- OnDefaultDeviceChanged,当用户更改音频终结点设备的设备角色时会调用该角色。
- OnDeviceAdded,当用户向系统添加音频终结点设备时会调用它。
- OnDeviceRemoved,当用户从系统中删除音频终结点设备时会调用该设备。
- OnDeviceStateChanged,当音频终结点设备的设备状态发生更改时调用。 (有关设备状态的详细信息,请参阅 DEVICE_STATE_ XXX 常量。)
- OnPropertyValueChanged,当音频终结点设备属性的值发生更改时调用。
其中每个方法都采用一个输入参数,pwstrDeviceId,该参数是指向终结点 ID 字符串的指针。 该字符串标识发生设备事件的音频终结点设备。
在前面的代码示例中,_PrintDeviceName是 CMMNotificationClient 类中输出设备的友好名称的私有方法。 _PrintDeviceName将终结点 ID 字符串作为输入参数。 它将字符串传递给 IMMDeviceEnumerator::GetDevice 方法。 GetDevice 创建一个终结点设备对象来表示设备,并向该对象提供 IMMDevice 接口。 接下来,_PrintDeviceName调用 IMMDevice::OpenPropertyStore 方法,以检索设备属性存储的 IPropertyStore 接口。 最后,_PrintDeviceName调用 IPropertyStore::GetItem 方法来获取设备的友好名称属性。 有关 IPropertyStore的详细信息,请参阅 Windows SDK 文档。
除了设备事件,客户端还可以注册以接收音频会话事件和终结点卷事件的通知。 有关详细信息,请参阅 IAudioSessionEvents 接口 和 IAudioEndpointVolumeCallback 接口。
相关主题