デバイス イベント (コア オーディオ API)

デバイス イベントは、システム内のオーディオ エンドポイント デバイスの状態の変化をクライアントに通知します。 デバイス イベントの例を次に示します。

  • ユーザーが、デバイス マネージャーまたは Windows マルチメディア コントロール パネル (Mmsys.cpl) からオーディオ エンドポイント デバイスを有効または無効にする。
  • ユーザーがシステムにオーディオ アダプターを追加するか、システムからオーディオ アダプターを削除する。
  • ユーザーが、オーディオ エンドポイント デバイスをジャック プレゼンス検出でオーディオ ジャックに接続するか、そのようなジャックからオーディオ エンドポイント デバイスを削除する。
  • ユーザーが、デバイスに割り当てられているデバイス ロールを変更する。
  • デバイスのプロパティの値が変化する。

オーディオ アダプターを追加または削除すると、アダプターに接続されているすべてのオーディオ エンドポイント デバイスのデバイス イベントが生成されます。 前の一覧の最初の 4 つの項目はデバイスの状態変更の例です。 オーディオ エンドポイント デバイスのデバイスの状態について詳しくは、「DEVICE_STATE_XXX 定数」をご覧ください。 ジャック プレゼンス検出について詳しくは、「オーディオ エンドポイント デバイス」をご覧ください。

クライアントは、デバイス イベントが発生したときに通知を受け取るよう登録できます。 これらの通知に応じて、クライアントは特定のデバイスの使用方法を動的に変更したり、特定の目的に使用する別のデバイスを選択したりすることができます。

たとえば、アプリケーションが一連の USB スピーカーを介してオーディオ トラックを再生していて、ユーザーが USB コネクタからそれらのスピーカーを切断した場合、アプリケーションはデバイス イベント通知を受け取ります。 イベントに応答して、一連のデスクトップ スピーカーがシステム マザーボード上の統合オーディオ アダプターに接続されていることがアプリケーションにより検出された場合、アプリケーションはデスクトップ スピーカーを通じてオーディオ トラックの再生を再開できます。 この例では、USB スピーカーからデスクトップ スピーカーへの切り替えは自動的に行われ、アプリケーションが明示的にリダイレクトされるためユーザーが介入する必要はありません。

デバイス通知の受信を登録するため、クライアントは IMMDeviceEnumerator::RegisterEndpointNotificationCallback メソッドを呼び出します。 クライアントが通知を必要としなくなったら、IMMDeviceEnumerator::UnregisterEndpointNotificationCallback メソッドを呼び出して、通知をキャンセルします。 どちらのメソッドも、iMMNotificationClient インターフェイス インスタンスへのポインターである 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 メソッドの AddRefReleaseQueryInterface の実装が含まれています。 クラス定義の残りのパブリック メソッドは、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 インターフェイス」をご覧ください。

オーディオ エンドポイント デバイス