디바이스 이벤트(핵심 오디오 API)

디바이스 이벤트는 시스템에서 오디오 엔드포인트 디바이스의 상태 변경 사항을 클라이언트에 알 수 있습니다. 다음은 디바이스 이벤트의 예입니다.

  • 사용자는 장치 관리자 또는 Windows 멀티미디어 제어판에서 오디오 엔드포인트 디바이스를 사용하거나 사용하지 않도록 설정하거나 Mmsys.cpl.
  • 사용자가 시스템에 오디오 어댑터를 추가하거나 시스템에서 오디오 어댑터를 제거합니다.
  • 사용자는 잭 프레즌스 감지를 사용하여 오디오 엔드포인트 디바이스를 오디오 잭에 연결하거나 이러한 잭에서 오디오 엔드포인트 디바이스를 제거합니다.
  • 사용자가 디바이스에 할당된 디바이스 역할을 변경합니다.
  • 디바이스의 속성 값 변경됩니다.

오디오 어댑터를 추가하거나 제거하면 어댑터에 연결하는 모든 오디오 엔드포인트 디바이스에 대한 디바이스 이벤트가 생성됩니다. 앞의 목록의 처음 4개 항목은 디바이스 상태 변경의 예입니다. 오디오 엔드포인트 디바이스의 디바이스 상태에 대한 자세한 내용은 DEVICE_STATE_XXX 상수를 참조하세요. 잭 프레즌스 검색에 대한 자세한 내용은 오디오 엔드포인트 디바이스를 참조하세요.

클라이언트는 디바이스 이벤트가 발생할 때 알림을 받도록 등록할 수 있습니다. 이러한 알림에 대한 응답으로 클라이언트는 특정 디바이스를 사용하는 방식을 동적으로 변경하거나 특정 용도로 사용할 다른 디바이스를 선택할 수 있습니다.

예를 들어 애플리케이션이 USB 스피커 집합을 통해 오디오 트랙을 재생 중이고 사용자가 USB 커넥터에서 스피커 연결을 끊는 경우 애플리케이션은 디바이스 이벤트 알림을 받습니다. 이벤트에 대한 응답으로 애플리케이션이 데스크톱 스피커 집합이 시스템 마더보드의 통합 오디오 어댑터에 연결되어 있음을 감지하면 애플리케이션이 데스크톱 스피커를 통해 오디오 트랙 재생을 다시 시작할 수 있습니다. 이 예제에서는 사용자가 애플리케이션을 명시적으로 리디렉션하여 개입할 필요 없이 USB 스피커에서 데스크톱 스피커로의 전환이 자동으로 수행됩니다.

디바이스 알림을 수신하도록 등록하기 위해 클라이언트는 IMMDeviceEnumerator::RegisterEndpointNotificationCallback 메서드를 호출합니다. 클라이언트에 알림이 더 이상 필요하지 않으면 IMMDeviceEnumerator::UnregisterEndpointNotificationCallback 메서드를 호출하여 알림을 취소합니다. 두 메서드 모두 iMMNotificationClient 인터페이스 instance 대한 포인터인 pClient라는 입력 매개 변수를 사용합니다.

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 인터페이스의 구현입니다. IMMNotificationClientIUnknown에서 상속되므로 클래스 정의에는 IUnknown 메서드 AddRef, ReleaseQueryInterface의 구현이 포함됩니다. 클래스 정의의 나머지 공용 메서드는 IMMNotificationClient 인터페이스와 관련이 있습니다. 이러한 메서드는 다음과 같습니다.

  • OnDefaultDeviceChanged는 사용자가 오디오 엔드포인트 디바이스의 디바이스 역할을 변경할 때 호출됩니다.
  • OnDeviceAdded는 사용자가 시스템에 오디오 엔드포인트 디바이스를 추가할 때 호출됩니다.
  • OnDeviceRemoved는 사용자가 시스템에서 오디오 엔드포인트 디바이스를 제거할 때 호출됩니다.
  • 오디오 엔드포인트 디바이스의 디바이스 상태가 변경되면 호출되는 OnDeviceStateChanged입니다. 디바이스 상태에 대한 자세한 내용은 DEVICE_STATE_ XXX 상수를 참조하세요.
  • OnPropertyValueChanged- 오디오 엔드포인트 디바이스의 속성 값이 변경되면 호출됩니다.

이러한 각 메서드는 엔드포인트 ID 문자열에 대한 포인터인 pwstrDeviceId라는 입력 매개 변수를 사용합니다. 문자열은 디바이스 이벤트가 발생한 오디오 엔드포인트 디바이스를 식별합니다.

앞의 코드 예제에서 _PrintDeviceName 디바이스의 이름을 인쇄하는 CMMNotificationClient 클래스의 프라이빗 메서드입니다. _PrintDeviceName 엔드포인트 ID 문자열을 입력 매개 변수로 사용합니다. 문자열을 IMMDeviceEnumerator::GetDevice 메서드에 전달합니다. GetDevice 는 디바이스를 나타내는 엔드포인트 디바이스 개체를 만들고 해당 개체에 IMMDevice 인터페이스를 제공합니다. 다음으로, _PrintDeviceName IMMDevice::OpenPropertyStore 메서드를 호출하여 디바이스의 속성 저장소에 대한 IPropertyStore 인터페이스를 검색합니다. 마지막으로 _PrintDeviceName IPropertyStore::GetItem 메서드를 호출하여 디바이스의 식별 이름 속성을 가져옵니다. IPropertyStore에 대한 자세한 내용은 Windows SDK 설명서를 참조하세요.

클라이언트는 디바이스 이벤트 외에도 등록하여 오디오 세션 이벤트 및 엔드포인트 볼륨 이벤트에 대한 알림을 받을 수 있습니다. 자세한 내용은 IAudioSessionEvents 인터페이스IAudioEndpointVolumeCallback 인터페이스를 참조하세요.

오디오 엔드포인트 디바이스