デバイス トポロジ

DeviceTopology API を使用すると、MMDevice APIWASAPI、または EndpointVolume API を介してアクセスすることができないオーディオ アダプターのさまざまな内部機能を、クライアントが制御可能になります。

前述のように、MMDevice APIWASAPIEndpointVolume API は、マイク、スピーカー、ヘッドフォン、他のオーディオ入出力デバイスをオーディオ エンドポイント デバイスとしてクライアントに表示します。 エンドポイント デバイス モデルを使用すると、クライアントはオーディオ デバイスのボリューム コントロールとミュート コントロールに簡単にアクセスすることができます。 これらのシンプルなコントロールのみを必要とするクライアントは、オーディオ アダプターにおけるハードウェア デバイスの内部トポロジの走査を回避できます。

Windows Vista では、オーディオ アプリケーションにより使用されるオーディオ デバイスのトポロジが、オーディオ エンジンによって自動的に構成されます。 したがって、この目的のためにアプリケーションで DeviceTopology API を使用する必要はほとんどありません。 たとえば、オーディオ アダプターに、ライン入力またはマイクからストリームをキャプチャできるが、両方のエンドポイント デバイスからのストリームを同時にキャプチャできない入力マルチプレクサーが含まれているとします。 「排他モード ストリーム」で説明されているように、共有モード アプリケーションによるオーディオ エンドポイント デバイスの使用を優先するようユーザーが排他モード アプリケーションを有効にしたとします。 排他モード アプリケーションがマイクからのストリームの記録を開始した時点で、共有モード アプリケーションがライン入力からのストリームを記録している場合、オーディオ エンジンは自動的にマルチプレクサーをライン入力からマイクに切り替えます。 これに対し、Windows XP を含む以前のバージョンの Windows では、この例の排他モード アプリケーションが、Windows マルチメディア API の mixerXxx 関数を使用することにより、アダプター デバイスのトポロジを走査して、マルチプレクサーを検出し、マルチプレクサーを構成してマイク入力を選択します。 Windows Vista では、これらの手順は不要になりました。

ただし、一部のクライアントでは、MMDevice API、WASAPI、または EndpointVolume API を介してアクセスすることができないオーディオ ハードウェア コントロールの種類を明示的に制御する必要があります。 これらのクライアントの場合、DeviceTopology API に、アダプター デバイスのトポロジを走査して、デバイス内のオーディオ コントロールを検出および管理する機能が用意されています。 DeviceTopology API を使用するアプリケーションは、Windows オーディオ ポリシーに干渉したり、他のアプリケーションと共有されているオーディオ デバイスの内部構成を妨げないように、注意して設計する必要があります。 Windows オーディオ ポリシーについて詳しくは、「ユーザー モード オーディオ コンポーネント」をご覧ください。

DeviceTopology API には、デバイス トポロジで次の種類のオーディオ コントロールを検出および管理するためのインターフェイスが用意されています。

  • 自動ゲイン制御
  • 低音コントロール
  • 入力セレクター (マルチプレクサー)
  • ラウドネス コントロール
  • ミッドレンジ コントロール
  • ミュート コントロール
  • 出力セレクター (デマルチプレクサー)
  • ピーク メーター
  • 高音コントロール
  • ボリューム コントロール

さらに、DeviceTopology API を使用すると、クライアントはアダプター デバイスに対して、サポートされているストリーム形式に関する情報を照会できます。 ヘッダー ファイル Devicetopology.h は、DeviceTopology API のインターフェイスを定義します。

次の図は、マイク、ライン入力、CD プレーヤーからオーディオをキャプチャする PCI アダプターの部分に対する、いくつかの接続されたデバイス トポロジの例を示しています。

example of four connected device topologies

上の図は、アナログ入力からシステム バスまでのデータ パスを示しています。 次の各デバイスは、IDeviceTopology インターフェイスを持つデバイス トポロジ オブジェクトとして表されます。

  • ウェーブ キャプチャ デバイス
  • 入力マルチプレクサー デバイス
  • エンドポイント デバイス A
  • エンドポイント デバイス B

トポロジ図では、アダプター デバイス (ウェーブ キャプチャ デバイスと入力マルチプレクサー デバイス) とエンドポイント デバイスが組み合わせられます。 オーディオ データは、デバイス間の接続を介して、あるデバイスから次のデバイスに渡されます。 接続の両側に、データがデバイスへの出入り時に通過するコネクタ (図では Con というラベルが付いています) があります。

図の左端では、ライン入力とマイク ジャックからの信号がエンドポイント デバイスに入ります。

ウェーブ キャプチャ デバイスと入力マルチプレクサー デバイス内には、ストリーム処理関数があります。これは、DeviceTopology API の用語ではサブユニットと呼ばれます。 上の図には、次の種類のサブユニットが示されています。

  • ボリューム コントロール (ラベル Vol)
  • ミュート コントロール (ラベル Mute)
  • マルチプレクサー (または入力セレクター、ラベル MUX)
  • アナログ デジタル コンバーター (ラベル ADC)

ボリューム、ミュート、マルチプレクサーのサブユニットの設定はクライアントによって制御することができます。DeviceTopology API は、それらを制御するための制御インターフェイスをクライアントに提供します。 この例では、ADC サブユニットに制御設定がありません。 したがって、DeviceTopology API は ADC の制御インターフェイスを提供しません。

DeviceTopology API の用語では、コネクタとサブユニットは同じ一般カテゴリ (パーツ) に属しています。 すべてのパーツは、コネクタかサブユニットかに関係なく、共通の関数セットを提供します。 DeviceTopology API は、コネクタとサブユニットに共通する汎用関数を表す IPart インターフェイスを実装します。 この API は、コネクタとサブユニットの特定の側面を表す IConnector インターフェイスと ISubunit インターフェイスを実装します。

DeviceTopology API は、オーディオ ドライバーがオペレーティング システムに公開してこれらのデバイスを表すカーネル ストリーミング (KS) フィルターから、ウェーブ キャプチャ デバイスと入力マルチプレクサー デバイスのトポロジを構築します。 (オーディオ アダプター ドライバーは、これらのフィルターのハードウェア依存部分を表す IMiniportWaveXxx インターフェイスと IMiniportTopology インターフェイスを実装します。これらのインターフェイスと KS フィルターについて詳しくは、Windows DDK のドキュメントをご覧ください)。

DeviceTopology API は、上の図のエンドポイント デバイス A と B を表す単純なトポロジを構築します。 エンドポイント デバイスのデバイス トポロジは、単一のコネクタで構成されています。 このトポロジは、エンドポイント デバイスのプレースホルダーにすぎず、オーディオ データを処理するためのサブユニットは含まれていません。 事実、アダプター デバイスには、クライアント アプリケーションがオーディオ処理を制御するために使用するすべてのサブユニットが含まれています。 エンドポイント デバイスのデバイス トポロジは、主にアダプター デバイスのデバイス トポロジを探索するための開始点として機能します。

デバイス トポロジ内の 2 つの部分間の内部接続は、リンクと呼ばれます。 DeviceTopology API には、デバイス トポロジ内のある部分から次の部分へのリンクを走査するためのメソッドが用意されています。 この API には、デバイス トポロジ間の接続を走査するためのメソッドも用意されています。

接続されているデバイス トポロジのセットの探索を開始するため、クライアント アプリケーションによってオーディオ エンドポイント デバイスの IDeviceTopology インターフェイスがアクティブ化されます。 エンドポイント デバイスのコネクタは、オーディオ アダプター内のコネクタまたはネットワークに接続します。 エンドポイントがオーディオ アダプター上のデバイスに接続する場合、DeviceTopology API のメソッドを使用すると、接続の反対側にあるアダプター デバイスの IDeviceTopology インターフェイスへの参照を取得することにより、アプリケーションはエンドポイントからアダプターへの接続をステップ実行できます。 一方、ネットワークにはデバイス トポロジがありません。 ネットワーク接続は、システムにリモートでアクセスしているクライアントにオーディオ ストリームをパイプします。

DeviceTopology API は、オーディオ アダプター内のハードウェア デバイスのトポロジにのみアクセスできます。 図の左端にある外部デバイスと右端のソフトウェア コンポーネントは、API の範囲を超えています。 図の両側の破線は、DeviceTopology API の制限を表しています。 クライアントは API を使用し、入力ジャックからシステム バスに拡張されるデータ パスを探索することができますが、API はこれらの境界を越えて侵入することはできません。

前の図の各コネクタには、コネクタが行う接続の種類を示す接続の種類が関連付けられています。 したがって、接続の両側のコネクタは、常に同じ種類の接続になります。 接続の種類は、ConnectorType 列挙値 (Physical_External、Physical_Internal、Software_Fixed、Software_IO、Network) で示されます。 入力マルチプレクサー デバイスとエンドポイント デバイス A およびエンドポイント デバイス B の間の接続は、種類が Physical_External です。つまり、接続は外部デバイス (言い換えると、ユーザーがアクセス可能なオーディオ ジャック) への物理的な接続を表します。 内部 CD プレーヤーからのアナログ信号への接続は、種類 Physical_Internal です。これは、システム シャーシ内に設置されている補助デバイスへの物理的な接続を示しています。 ウェーブ キャプチャ デバイスと入力マルチプレクサー デバイスの間の接続は、種類 Software_Fixed です。これは、固定されており、ソフトウェア制御下で構成できない永続的な接続を示しています。 最後に、図の右側にあるシステム バスへの接続は種類 Software_IO です。これは、接続のデータ I/O がソフトウェア制御下の DMA エンジンによって実装されていることを示しています。 (この図には、ネットワーク接続の種類の例は含まれていません)。

クライアントは、エンドポイント デバイスでデータ パスの走査を開始します。 まず、「オーディオ デバイスの列挙」で説明されているように、エンドポイント デバイスを表す IMMDevice インターフェイスをクライアントが取得します。 エンドポイント デバイスの IDeviceTopology インターフェイスを取得するため、クライアントはパラメーター iid を REFIID IID_IDeviceTopology に設定して、IMMDevice::Activate メソッドを呼び出します。

前の図の例では、入力マルチプレクサー デバイスに、ライン入力およびマイク ジャックから取得されたキャプチャ ストリームのすべてのハードウェア コントロール (ボリューム、ミュート、マルチプレクサー) が含まれています。 次のコード例は、回線入力またはマイクのエンドポイント デバイスの IMMDevice インターフェイスから、入力マルチプレクサー デバイスの IDeviceTopology インターフェイスを取得する方法を示しています。

//-----------------------------------------------------------
// The input argument to this function is a pointer to the
// IMMDevice interface of an endpoint device. The function
// outputs a pointer (counted reference) to the
// IDeviceTopology interface of the adapter device that
// connects to the endpoint device.
//-----------------------------------------------------------
#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const IID IID_IDeviceTopology = __uuidof(IDeviceTopology);
const IID IID_IPart = __uuidof(IPart);

HRESULT GetHardwareDeviceTopology(
            IMMDevice *pEndptDev,
            IDeviceTopology **ppDevTopo)
{
    HRESULT hr = S_OK;
    IDeviceTopology *pDevTopoEndpt = NULL;
    IConnector *pConnEndpt = NULL;
    IConnector *pConnHWDev = NULL;
    IPart *pPartConn = NULL;

    // Get the endpoint device's IDeviceTopology interface.

    hr = pEndptDev->Activate(
                      IID_IDeviceTopology, CLSCTX_ALL,
                      NULL, (void**)&pDevTopoEndpt);
    EXIT_ON_ERROR(hr)

    // The device topology for an endpoint device always
    // contains just one connector (connector number 0).

    hr = pDevTopoEndpt->GetConnector(0, &pConnEndpt);
    EXIT_ON_ERROR(hr)

    // Use the connector in the endpoint device to get the
    // connector in the adapter device.

    hr = pConnEndpt->GetConnectedTo(&pConnHWDev);
    EXIT_ON_ERROR(hr)

    // Query the connector in the adapter device for
    // its IPart interface.

    hr = pConnHWDev->QueryInterface(
                         IID_IPart, (void**)&pPartConn);
    EXIT_ON_ERROR(hr)

    // Use the connector's IPart interface to get the
    // IDeviceTopology interface for the adapter device.

    hr = pPartConn->GetTopologyObject(ppDevTopo);

Exit:
    SAFE_RELEASE(pDevTopoEndpt)
    SAFE_RELEASE(pConnEndpt)
    SAFE_RELEASE(pConnHWDev)
    SAFE_RELEASE(pPartConn)

    return hr;
}

前のコード例の GetHardwareDeviceTopology 関数は、次の手順を実行して、入力マルチプレクサー デバイスの IDeviceTopology インターフェイスを取得します。

  1. IMMDevice::Activate メソッドを呼び出して、エンドポイント デバイスの IDeviceTopology インターフェイスを取得します。
  2. 前の手順で取得した IDeviceTopology インターフェイスを使用して、IDeviceTopology::GetConnector メソッドを呼び出し、エンドポイント デバイスの単一コネクタ (コネクタ番号 0) の IConnector インターフェイスを取得します。
  3. 前の手順で取得した IConnector インターフェイスを使用して、IConnector::GetConnectedTo メソッドを呼び出し、入力マルチプレクサー デバイスのコネクタの IConnector インターフェイスを取得します。
  4. 前の手順で取得した IConnector インターフェイスの IPart インターフェイスに対してクエリを実行します。
  5. 前の手順で取得した IPart インターフェイスを使用して、IPart::GetTopologyObject メソッドを呼び出し、入力マルチプレクサー デバイスの IDeviceTopology インターフェイスを取得します。

前の図のマイクからユーザーが録音可能にするには、クライアント アプリケーションでマルチプレクサーがマイク入力を選択していることを確認する必要があります。 次のコード例は、クライアントがマルチプレクサーを見つけるまで、マイクからデータ パスを走査する方法を示しています。その後、マイク入力を選択するようプログラミングします。

//-----------------------------------------------------------
// The input argument to this function is a pointer to the
// IMMDevice interface for a capture endpoint device. The
// function traverses the data path that extends from the
// endpoint device to the system bus (for example, PCI)
// or external bus (USB). If the function discovers a MUX
// (input selector) in the path, it selects the MUX input
// that connects to the stream from the endpoint device.
//-----------------------------------------------------------
#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const IID IID_IDeviceTopology = __uuidof(IDeviceTopology);
const IID IID_IPart = __uuidof(IPart);
const IID IID_IConnector = __uuidof(IConnector);
const IID IID_IAudioInputSelector = __uuidof(IAudioInputSelector);

HRESULT SelectCaptureDevice(IMMDevice *pEndptDev)
{
    HRESULT hr = S_OK;
    DataFlow flow;
    IDeviceTopology *pDeviceTopology = NULL;
    IConnector *pConnFrom = NULL;
    IConnector *pConnTo = NULL;
    IPart *pPartPrev = NULL;
    IPart *pPartNext = NULL;
    IAudioInputSelector *pSelector = NULL;

    if (pEndptDev == NULL)
    {
        EXIT_ON_ERROR(hr = E_POINTER)
    }

    // Get the endpoint device's IDeviceTopology interface.
    hr = pEndptDev->Activate(
                      IID_IDeviceTopology, CLSCTX_ALL, NULL,
                      (void**)&pDeviceTopology);
    EXIT_ON_ERROR(hr)

    // The device topology for an endpoint device always
    // contains just one connector (connector number 0).
    hr = pDeviceTopology->GetConnector(0, &pConnFrom);
    SAFE_RELEASE(pDeviceTopology)
    EXIT_ON_ERROR(hr)

    // Make sure that this is a capture device.
    hr = pConnFrom->GetDataFlow(&flow);
    EXIT_ON_ERROR(hr)

    if (flow != Out)
    {
        // Error -- this is a rendering device.
        EXIT_ON_ERROR(hr = AUDCLNT_E_WRONG_ENDPOINT_TYPE)
    }

    // Outer loop: Each iteration traverses the data path
    // through a device topology starting at the input
    // connector and ending at the output connector.
    while (TRUE)
    {
        BOOL bConnected;
        hr = pConnFrom->IsConnected(&bConnected);
        EXIT_ON_ERROR(hr)

        // Does this connector connect to another device?
        if (bConnected == FALSE)
        {
            // This is the end of the data path that
            // stretches from the endpoint device to the
            // system bus or external bus. Verify that
            // the connection type is Software_IO.
            ConnectorType  connType;
            hr = pConnFrom->GetType(&connType);
            EXIT_ON_ERROR(hr)

            if (connType == Software_IO)
            {
                break;  // finished
            }
            EXIT_ON_ERROR(hr = E_FAIL)
        }

        // Get the connector in the next device topology,
        // which lies on the other side of the connection.
        hr = pConnFrom->GetConnectedTo(&pConnTo);
        EXIT_ON_ERROR(hr)
        SAFE_RELEASE(pConnFrom)

        // Get the connector's IPart interface.
        hr = pConnTo->QueryInterface(
                        IID_IPart, (void**)&pPartPrev);
        EXIT_ON_ERROR(hr)
        SAFE_RELEASE(pConnTo)

        // Inner loop: Each iteration traverses one link in a
        // device topology and looks for input multiplexers.
        while (TRUE)
        {
            PartType parttype;
            UINT localId;
            IPartsList *pParts;

            // Follow downstream link to next part.
            hr = pPartPrev->EnumPartsOutgoing(&pParts);
            EXIT_ON_ERROR(hr)

            hr = pParts->GetPart(0, &pPartNext);
            pParts->Release();
            EXIT_ON_ERROR(hr)

            hr = pPartNext->GetPartType(&parttype);
            EXIT_ON_ERROR(hr)

            if (parttype == Connector)
            {
                // We've reached the output connector that
                // lies at the end of this device topology.
                hr = pPartNext->QueryInterface(
                                  IID_IConnector,
                                  (void**)&pConnFrom);
                EXIT_ON_ERROR(hr)

                SAFE_RELEASE(pPartPrev)
                SAFE_RELEASE(pPartNext)
                break;
            }

            // Failure of the following call means only that
            // the part is not a MUX (input selector).
            hr = pPartNext->Activate(
                              CLSCTX_ALL,
                              IID_IAudioInputSelector,
                              (void**)&pSelector);
            if (hr == S_OK)
            {
                // We found a MUX (input selector), so select
                // the input from our endpoint device.
                hr = pPartPrev->GetLocalId(&localId);
                EXIT_ON_ERROR(hr)

                hr = pSelector->SetSelection(localId, NULL);
                EXIT_ON_ERROR(hr)

                SAFE_RELEASE(pSelector)
            }

            SAFE_RELEASE(pPartPrev)
            pPartPrev = pPartNext;
            pPartNext = NULL;
        }
    }

Exit:
    SAFE_RELEASE(pConnFrom)
    SAFE_RELEASE(pConnTo)
    SAFE_RELEASE(pPartPrev)
    SAFE_RELEASE(pPartNext)
    SAFE_RELEASE(pSelector)
    return hr;
}

DeviceTopology API は、前の図のようなマルチプレクサーをカプセル化するための IAudioInputSelector インターフェイスを実装します。 (IAudioOutputSelector インターフェイスは、デマルチプレクサーをカプセル化します)。前のコード例では、SelectCaptureDevice 関数の内部ループは、見つかった各サブユニットを照会して、サブユニットがマルチプレクサーであるかどうかを検出します。 サブユニットがマルチプレクサーの場合、関数は IAudioInputSelector::SetSelection メソッドを呼び出して、エンドポイント デバイスからストリームに接続する入力を選択します。

前のコード例では、外側のループの各イテレーションが 1 つのデバイス トポロジを走査します。 前の図のデバイス トポロジを走査するとき、最初のイテレーションは入力マルチプレクサー デバイスを走査し、2 番目のイテレーションはウェーブ キャプチャ デバイスを走査します。 この関数は、図の右端にあるコネクタに到達すると終了します。 終了は、接続の種類 Software_IO を持つコネクタを関数が検出したときに発生します。 この接続の種類は、アダプター デバイスがシステム バスに接続するポイントを識別します。

前のコード例の IPart::GetPartType メソッドの呼び出しは、現在のパーツがコネクタかオーディオ処理サブユニットかを示す IPartType 列挙値を取得します。

前のコード例の内部ループは、IPart::EnumPartsOutgoing メソッドを呼び出すことにより、あるパーツから次のパーツへのリンクをステップ実行します。 (逆方向にステップ実行するための IPart::EnumPartsIncoming メソッドもあります)。このメソッドは、すべての発信パーツのリストを含む IPartsList オブジェクトを取得します。 ただし、SelectCaptureDevice 関数がキャプチャ デバイスで発生すると予想されるどのパーツにも、必ず送信パーツが 1 つだけ存在します。 したがって、IPartsList::GetPart の後続の呼び出しでは、必ずリストの最初のパーツ (パーツ番号 0) が要求されます。これは、この関数がリスト内の唯一のパーツであると想定しているためです。

その前提条件が無効なトポロジが SelectCaptureDevice 関数で検出された場合、この関数はデバイスを正しく構成できない可能性があります。 このようなエラーを回避するため、より汎用的なバージョンの関数で以下の処理が行われる場合があります。

  • IPartsList::GetCount メソッドを呼び出して、発信パーツの数を決定します。
  • 送信パーツごとに IPartsList::GetPart を呼び出し、パーツから続くデータ パスの走査を開始します。

一部のパーツには、クライアントが設定または取得できるハードウェア コントロールが関連付けられていますが、必ずしもすべてではありません。 特定のパーツに、0 個、1 個、または 1 個以上のハードウェア コントロールが存在する場合があります。 ハードウェア コントロールは、以下のインターフェイス ペアで表されます。

  • すべてのハードウェア コントロールに共通のメソッドを持つ汎用コントロール インターフェイス IControlInterface
  • 特定の種類のハードウェア コントロール (ボリューム コントロールなど) のコントロール パラメーターを公開する関数固有のインターフェイス (IAudioVolumeLevel など)。

パーツのハードウェア コントロールを列挙するため、クライアントは最初に IPart::GetControlInterfaceCount メソッドを呼び出して、パーツに関連付けられているハードウェア コントロールの数を決定します。 次に、クライアントは IPart::GetControlInterface メソッドに対して一連の呼び出しを行って、各ハードウェア コントロールの IControlInterface インターフェイスを取得します。 最後に、クライアントは IControlInterface::GetIID メソッドを呼び出してインターフェイス ID を取得することにより、各ハードウェア コントロールの関数固有のインターフェイスを取得します。 クライアントは、この ID で IPart::Activate メソッドを呼び出して、関数固有のインターフェイスを取得します。

コネクタであるパーツは、次の関数固有のコントロール インターフェイスのいずれかをサポートしている可能性があります。

サブユニットであるパーツは、次の関数固有のコントロール インターフェイスの 1 つ以上をサポートしている可能性があります。

基になるハードウェア コントロールにデバイス固有のコントロール値があり、前の一覧にある他の関数固有のインターフェイスでコントロールを適切に表すことができない場合のみ、パーツで IDeviceSpecificProperty インターフェイスがサポートされます。 通常、デバイス固有のプロパティは、パーツの種類、パーツのサブタイプ、パーツ名などの情報からプロパティ値の意味を推測できるクライアントにのみ役立ちます。 クライアントは、IPart::GetPartTypeIPart::GetSubType、および IPart::GetName メソッドを呼び出すことによって、この情報を取得できます。

プログラミング ガイド