閱讀英文

共用方式為


裝置拓撲

DeviceTopology API 可讓用戶端控制其無法透過MMDevice API、WASAPIEndpointVolume API 存取的各種音訊適配卡內部功能。

如先前所述,MMDevice API、WASAPIEndpointVolume API 會將麥克風、喇叭、耳機和其他音訊輸入和輸出裝置呈現給客戶端作為音訊端點裝置。 端點裝置模型可讓用戶端方便存取音訊裝置中的音量和靜音控制。 只有這些簡單控制件的用戶端可以避免周遊音訊配接器中硬體裝置的內部拓撲。

在 Windows Vista 中,音訊引擎會自動設定音訊裝置的拓撲,以供音訊應用程式使用。 因此,如果曾經,應用程式很少需要針對此目的使用DeviceTopology API。 例如,假設音訊配接器包含輸入多任務器,可以從線路輸入或麥克風擷取數據流,但無法同時從兩個端點裝置擷取數據流。 假設使用者已啟用獨佔模式應用程式,讓共用模式應用程式優先使用音訊端點裝置,如獨佔模式串流中所述。 如果共用模式應用程式在專用模式應用程式開始從麥克風錄製串流時,從線路輸入錄製數據流,音訊引擎會自動將多任務器從線路輸入切換至麥克風。 相反地,在舊版 Windows 中,包括 Windows XP,此範例中的獨佔模式應用程式會使用 Windows 多媒體 API 中的混合器Xxx 函式來周遊配接器裝置的拓撲、探索多任務器,以及設定多任務器以選取麥克風輸入。 在 Windows Vista 中,不再需要這些步驟。

不過,某些用戶端可能需要明確控制無法透過MMDevice API、WASAPI或EndpointVolume API 存取的音訊硬體控件類型。 針對這些用戶端,DeviceTopology API 可讓您周遊適配卡裝置的拓撲,以探索和管理裝置中的音頻控件。 使用 DeviceTopology API 的應用程式必須小心設計,以避免干擾 Windows 音訊原則,並干擾與其他應用程式共用之音訊裝置的內部設定。 如需 Windows 音訊原則的詳細資訊,請參閱 使用者模式音訊元件

DeviceTopology API 提供在裝置拓撲中探索及管理下列音訊控件類型的介面:

  • 自動增益控制
  • Bass 控制件
  • 輸入選取器 (多工作器)
  • 音量控制
  • Midrange 控制件
  • 靜音控件
  • 輸出選取器 (demultiplexer)
  • 尖峰計量
  • Treble 控件
  • 音量控制

此外,DeviceTopology API 可讓客戶端查詢配接器裝置,以取得其支持的數據流格式相關信息。 Devicetopology.h 頭文件定義 DeviceTopology API 中的介面。

下圖顯示數個連線裝置拓撲的範例,適用於從麥克風、線路輸入和 CD 播放程式擷取音訊的 PCI 適配卡部分。

example of four connected device topologies

上圖顯示從類比輸入到系統總線的數據路徑。 下列每一個裝置都會以具有 IDeviceTopology 介面的裝置拓撲物件表示:

  • 波浪擷取裝置
  • 輸入多任務器裝置
  • 端點裝置 A
  • 端點裝置 B

請注意,拓撲圖會將適配卡裝置(波浪擷取和輸入多任務器裝置)與端點裝置結合。 透過裝置之間的連線,音訊數據會從一部裝置傳遞至下一個裝置。 連接的每一端都是連接器(圖表中標示為 Con),數據會透過該連接器進入或離開裝置。

在圖表的左邊緣,來自線路輸入和麥克風插孔的訊號會進入端點裝置。

在波擷取裝置和輸入多任務器裝置內是串流處理函式,在DeviceTopology API術語中稱為子單位。 下列子單位類型會出現在上圖中:

  • 音量控制(標示為 Vol)
  • 靜音控制件(標示為靜音)
  • 多工作器(或輸入選取器;標示為 MUX)
  • 模擬數位轉換器(標示為 ADC)

磁碟區、靜音和多任務器子單位中的設定可由用戶端控制,DeviceTopology API 會提供控制介面給用戶端以控制它們。 在此範例中,ADC 子單位沒有控件設定。 因此,DeviceTopology API 不會提供 ADC 的控制介面。

在 DeviceTopology API 的術語中,連接器和子單位屬於相同的一般類別- 元件。 不論它們是否為連接器或子單位,所有元件都提供一組常見的函式。 DeviceTopology API 會實作 IPart 介面,代表連接器和子單位通用的泛型函式。 API 會實作 I 連線 or ISubunit 介面,以代表連接器和子單位的特定層面。

DeviceTopology API 會建構波擷取裝置的拓撲,並從核心串流 (KS) 篩選器輸入多任務器裝置,音訊驅動程式向操作系統公開來代表這些裝置。 (音訊配接器驅動程序會實作 IMiniportWaveXxxIMiniportTopology 介面來代表這些篩選器的硬體相依部分;如需這些介面和 KS 篩選器的詳細資訊,請參閱 Windows DDK 檔。

DeviceTopology API 會建構簡單的拓撲,以代表上圖中的端點裝置 A 和 B。 端點裝置的裝置拓撲是由單一連接器所組成。 此拓撲只是端點裝置的佔位元,而且沒有處理音訊數據的子單位。 事實上,配接器裝置包含用戶端應用程式用來控制音訊處理的所有子單位。 端點裝置的裝置拓撲主要作為探索適配卡裝置裝置裝置拓撲的起點。

裝置拓撲中兩個元件之間的內部連線稱為連結。 DeviceTopology API 提供在裝置拓撲中將連結從一個部分周游至下一個部分的方法。 API 也提供在裝置拓撲之間周遊連線的方法。

若要開始探索一組連線的裝置拓撲,用戶端應用程式會 啟動音訊端點裝置的IDeviceTopology 介面。 端點裝置中的連接器會連線到音訊配卡中的連接器或網路。 如果端點連線到音頻適配卡上的裝置,則 DeviceTopology API 中的方法可讓應用程式藉由取得連接另一端適配卡裝置之 IDeviceTopology 介面的參考,讓應用程式能夠逐步執行從端點到配接器的連線。 另一方面,網路沒有裝置拓撲。 網路聯機會將音訊串流傳送至遠端存取系統的用戶端。

DeviceTopology API 僅提供音訊適配卡中硬體裝置拓撲的存取權。 圖表左邊緣的外部裝置和右邊緣的軟體元件超出API的範圍。 圖表任一端的虛線代表 DeviceTopology API 的限制。 用戶端可以使用 API 來探索從輸入插孔延伸至系統總線的數據路徑,但 API 無法穿透超出這些界限。

上圖中的每個連接器都有相關聯的連接類型,指出連接器所建立的連接類型。 因此,連接兩端的連接器一律具有相同的連接類型。 連線類型是以 連線 orType 列舉值來表示,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 介面,用戶端會呼叫 IMMDevice::Activate 方法,並將參數 iid 設為 REFIID IID_IDeviceTopology。

在上圖的範例中,輸入多任務器裝置包含從線路輸入和麥克風插孔擷取串流的所有硬體控制(音量、靜音和多任務器)。 下列程式代碼範例示範如何端點裝置的 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::Get 連線 or 方法,以取得端點裝置中單一連接器 (連接器號碼 0) 的 I 連線 or 介面。
  3. 使用在上一個步驟中取得的 I 連線 or 介面,呼叫 I 連線 or::Get 連線 edTo 方法來取得輸入多任務器裝置中連接器的 I 連線 or 介面。
  4. 查詢在上一個步驟中取得的 I 連線 or 介面,以取得其 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 介面來封裝多任務器,例如上圖中的一個。 (An IAudioOutputSelector 介面會封裝 demultiplexer。)在上述程式代碼範例中,SelectCaptureDevice 函式的內部循環會查詢它找到的每個子單位,以探索子單位是否為多任務器。 如果子單位是多任務器,則函式會呼叫 IAudioInputSelector::SetSelection 方法,以選取從端點裝置連線到數據流的輸入。

在上述程式代碼範例中,外部迴圈的每個反覆項目都會周遊一個裝置拓撲。 在上圖中周遊裝置拓撲時,第一個反覆專案會周遊輸入多任務器裝置,而第二個反覆運算則會周遊波擷取裝置。 函式會在到達圖表右邊緣的連接器時終止。 當函式偵測到具有Software_IO連接類型的連接器時,就會終止。 此連線類型會識別配接器裝置連線到系統總線的點。

上述程式代碼範例中 IPart::GetPartType 方法的呼叫會取得 IPartType 列舉值,指出目前元件是連接器還是音訊處理子單位。

上述程式代碼範例中的內部迴圈會呼叫 IPart::EnumPartsOutgoing 方法,跨連結從一個部分到下一個部分。 (也有 用於逐步執行相反方向的 IPart::EnumPartsIncoming 方法。這個方法會 擷取包含所有傳出元件清單的 IPartsList 物件。 不過,SelectCaptureDevice 函式預期在擷取裝置中遇到的任何部分,一律只會有一個傳出部分。 因此,後續對 IPartsList::GetPart 的呼叫一律會要求清單中的第一個元件,第 0 部分,因為函式會假設這是清單中唯一的元件。

如果 SelectCaptureDevice 函式遇到該假設無效的拓撲,函式可能無法正確設定裝置。 若要避免這類失敗,函式的較一般用途版本可能會執行下列動作:

  • 呼叫 IPartsList::GetCount 方法來判斷傳出元件的數目。
  • 針對每個傳出元件,呼叫 IPartsList::GetPart 以開始周遊從元件導向的數據路徑。

有些元件不一定全都有客戶端可以設定或取得的相關硬體控制。 特定元件可能有零個、一或多個硬體控制件。 硬體控制項是由下列介面組表示:

  • 泛型控件介面 IControlInterface,其具有所有硬體控件通用的方法。
  • 函式特定介面(例如 IAudioVolumeLevel)會公開特定硬體控制項類型的控件參數(例如磁碟區控件)。

若要列舉元件的硬體控件,用戶端會先呼叫 IPart::GetControlInterfaceCount 方法,以判斷與元件相關聯的硬體控件數目。 接下來,用戶端會呼叫 IPart::GetControlInterface 方法,以取得每個硬體控制件的 IControlInterface 介面。 最後,用戶端會呼叫 IControlInterface::GetIID 方法來取得介面識別碼,以取得每個硬體控制件的函式特定介面。 用戶端會使用此標識符呼叫 IPart::Activate 方法,以取得函式特定的介面。

連接器的元件可能支援下列其中一個函式特定控制項介面:

屬於子單位的元件可能支援下列一或多個函式特定控制項介面:

只有當基礎硬體控制件具有裝置特定的控制項值,而且上述清單中的任何其他函式特定介面無法充分表示控制項時,元件才支援 IDeviceSpecificProperty 介面。 一般而言,裝置特定屬性僅適用於可從元件類型、部分子類型和元件名稱等資訊推斷屬性值意義的用戶端。 用戶端可以藉由呼叫 IPart::GetPartType、IPart::GetSubTypeIPart::GetName 方法來取得此資訊。

程式設計指南