События устройства (основные API аудио)

Событие устройства уведомляет клиентов об изменении состояния устройства аудио конечной точки в системе. Ниже приведены примеры событий устройства:

  • Пользователь включает или отключает устройство аудио конечной точки из диспетчер устройств или с панели управления мультимедиа Windows, Mmsys.cpl.
  • Пользователь добавляет аудиоадаптер в систему или удаляет аудиоадаптер из системы.
  • Пользователь подключает устройство аудио конечной точки к звуковому разъему с обнаружением джек-присутствия или удаляет устройство конечной точки звука из такого разъема.
  • Пользователь изменяет роль устройства, назначенную устройству.
  • Значение свойства устройства изменяется.

Добавление или удаление звукового адаптера создает события устройства для всех устройств аудиоконечной точки, которые подключаются к адаптеру. Первые четыре элемента в предыдущем списке являются примерами изменений состояния устройства. Дополнительные сведения об состояниях устройств аудиоконечной точки см. в разделе DEVICE_STATE_XXX Константы. Дополнительные сведения об обнаружении обнаружения джек-присутствия см. в разделе "Устройства аудио конечных точек".

Клиент может зарегистрировать уведомление при возникновении событий устройства. В ответ на эти уведомления клиент может динамически изменять способ использования определенного устройства или выбрать другое устройство для конкретной цели.

Например, если приложение воспроизводит звуковую дорожку через набор USB-динамиков, а пользователь отключает динамики от USB-соединителя, приложение получает уведомление об событии устройства. В ответ на событие, если приложение обнаруживает, что набор динамиков настольных компьютеров подключен к встроенному звуковому адаптеру на системной материнской плате, приложение может возобновить воспроизведение звуковой дорожки через динамики на рабочем столе. В этом примере переход с USB-динамиков на настольные динамики происходит автоматически, не требуя, чтобы пользователь вмешивался, явно перенаправляя приложение.

Чтобы зарегистрировать уведомления об устройстве, клиент вызывает метод IMMDeviceEnumerator::RegisterEndpointNotificationCallback. Если клиент больше не требует уведомлений, он отменяет их путем вызова метода IMMDeviceEnumerator::UnregisterEndpointNotificationCallback. Оба метода принимают входной параметр с именем pClient, то есть указатель на экземпляр интерфейса IMMNotificationClient.

Интерфейс IMMNotificationClient реализуется клиентом. Интерфейс содержит несколько методов, каждый из которых служит в качестве подпрограммы обратного вызова для определенного типа события устройства. Когда событие устройства происходит на устройстве аудио конечной точки, модуль MMDevice вызывает соответствующий метод в интерфейсе IMMNotificationClient каждого клиента, зарегистрированного в настоящее время для получения уведомлений об событиях устройства. Эти вызовы передают описание события клиентам. Дополнительные сведения см. в интерфейсе IMMNotificationClient.

Клиент, зарегистрированный для получения уведомлений об событиях устройства, получит уведомления обо всех типах событий устройства, происходящих во всех устройствах аудиоконечной точки в системе. Если клиент заинтересован только в определенных типах событий или на определенных устройствах, методы в реализации IMMNotificationClient должны фильтровать события соответствующим образом.

Пакет SDK для Windows содержит примеры, включающие несколько реализаций для интерфейса IMMNotificationClient. Дополнительные сведения см. в примерах пакета SDK, использующих ОСНОВНЫЕ API аудио.

В следующем примере кода показана возможная реализация интерфейса 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, который вызывается при добавлении устройства аудио конечной точки в систему.
  • OnDevice Remove, который вызывается при удалении устройства аудио конечной точки из системы.
  • OnDeviceStateChanged, который вызывается при изменении состояния устройства конечной точки звука. (Дополнительные сведения о состояниях устройства см. в разделе DEVICE_STATE_ константы XXX.)
  • OnPropertyValueChanged, который вызывается при изменении значения свойства устройства аудио конечной точки.

Каждый из этих методов принимает входной параметр pwstrDeviceId, то есть указатель на строку идентификатора конечной точки. Строка определяет устройство аудио конечной точки, в котором произошло событие устройства.

В предыдущем примере кода _PrintDeviceName является частным методом в классе CMMNotificationClient, который выводит понятное имя устройства. _PrintDeviceName принимает строку идентификатора конечной точки в качестве входного параметра. Он передает строку методу IMMDeviceEnumerator::GetDevice . GetDevice создает объект устройства конечной точки для представления устройства и предоставляет интерфейс IMMDevice для этого объекта. Затем _PrintDeviceName вызывает метод IMMDevice::OpenPropertyStore, чтобы получить интерфейс IPropertyStore в хранилище свойств устройства. Наконец, _PrintDeviceName вызывает метод IPropertyStore::GetItem для получения свойства понятного имени устройства. Дополнительные сведения об IPropertyStore см. в документации по пакету SDK для Windows.

Помимо событий устройства клиенты могут регистрировать уведомления о событиях аудио-сеанса и событиях тома конечной точки. Дополнительные сведения см. в разделе "Интерфейс IAudioSessionEvents" и интерфейс IAudioEndpointVolumeCallback.

Устройства аудиоконечной точки