次の方法で共有


デバイス トポロジ

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

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

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

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

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

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

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

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

接続されている 4 つのデバイス トポロジの例

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

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

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

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

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

  • ボリューム コントロール (Vol というラベル)
  • ミュート コントロール ("ミュート" というラベル)
  • マルチプレクサー (または入力セレクター、ラベル付き 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 を表す単純なトポロジを構築します。 エンドポイント デバイスのデバイス トポロジは、1 つのコネクタで構成されます。 このトポロジは単なるエンドポイント デバイスのプレースホルダーであり、オーディオ データを処理するためのサブユニットは含んでいません。 実際、アダプター デバイスには、クライアント アプリケーションがオーディオ処理を制御するために使用するすべてのサブユニットが含まれています。 エンドポイント デバイスのデバイス トポロジは、主にアダプター デバイスのデバイス トポロジを探索するための開始点として機能します。

デバイス トポロジ内の 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 つ以上のハードウェア コントロールがある場合があります。 ハードウェア コントロールは、次のインターフェイスのペアで表されます。

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

コネクタである部分は、次の関数固有のコントロール インターフェイスのいずれかをサポートする場合があります。

サブユニットである部分は、次の関数固有のコントロール インターフェイスの 1 つ以上をサポートする場合があります。

  • IAudioAutoGainControl
  • IAudioBass
  • IAudioChannelConfig
  • IAudioInputSelector
  • IAudioLoudness
  • IAudioMidrange
  • IAudioMuteする
  • IAudioOutputSelector
  • IAudioPeakMeter
  • IAudioTreble
  • IAudioVolumeLevel
  • IDeviceSpecificProperty

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

プログラミング ガイド