裝置事件 (核心音訊 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,因此類別定義包含 IUnknown 方法 AddRefReleaseQueryInterface實作。 類別定義中的其餘公用方法是 IMMNotificationClient 介面特有的。 這些方法是:

  • OnDefaultDeviceChanged,當用戶變更音訊端點裝置的裝置角色時,會呼叫它。
  • OnDeviceAdded,當使用者將音訊端點裝置新增至系統時呼叫。
  • OnDeviceRemoved,當使用者從系統移除音訊端點裝置時呼叫。
  • OnDeviceStateChanged,會在音訊端點裝置的裝置狀態變更時呼叫。 (如需裝置狀態的詳細資訊,請參閱 DEVICE_STATE_ XXX 常數。)
  • OnPropertyValueChanged,會在音訊端點裝置的 屬性值變更時呼叫。

每個方法都會採用輸入參數 pwstrDeviceId,也就是端點標識符字串的指標。 字串會識別發生裝置事件的音訊端點裝置。

在上述程式代碼範例中,_PrintDeviceName是 CMMNotificationClient 類別中的私用方法,可列印裝置的易記名稱。 _PrintDeviceName接受端點標識符字串作為輸入參數。 它會將字串傳遞至 IMMDeviceEnumerator::GetDevice 方法。 GetDevice 會建立端點裝置物件來代表裝置,並提供 該物件的 IMMDevice 介面。 接下來,_PrintDeviceName呼叫 IMMDevice::OpenPropertyStore 方法來擷取裝置屬性存放區的 IPropertyStore 介面。 最後,_PrintDeviceName呼叫 IPropertyStore::GetItem 方法來取得裝置的易記名稱屬性。 如需 IPropertyStore 的詳細資訊,請參閱 Windows SDK 檔。

除了裝置事件之外,用戶端還可以註冊以接收音訊會話事件和端點音量事件的通知。 如需詳細資訊,請參閱 IAudioSessionEvents 介面IAudioEndpointVolumeCallback 介面

音訊端點裝置