Topologie di dispositivo

L'API DeviceTopology offre ai client il controllo su un'ampia gamma di funzioni interne di adattatori audio a cui non possono accedere tramite l'API MMDevice, WASAPI o l'API EndpointVolume.

Come spiegato in precedenza, l'API MMDevice, WASAPI e l'API EndpointVolume presentano microfoni, altoparlanti, cuffie e altri dispositivi di input e output audio ai client come dispositivi endpoint audio. Il modello di dispositivo endpoint consente ai client di accedere facilmente ai controlli del volume e disattivarli nei dispositivi audio. I client che richiedono solo questi semplici controlli possono evitare di attraversare le topologie interne dei dispositivi hardware nelle schede audio.

In Windows Vista, il motore audio configura automaticamente le topologie dei dispositivi audio per l'uso da parte di applicazioni audio. Di conseguenza, le applicazioni raramente, se mai, devono usare l'API DeviceTopology per questo scopo. Si supponga, ad esempio, che un adattatore audio contenga un multiplexer di input in grado di acquisire un flusso da un input di riga o da un microfono, ma che non può acquisire flussi da entrambi i dispositivi endpoint contemporaneamente. Si supponga che l'utente abbia abilitato applicazioni in modalità esclusiva per impedire l'uso di un dispositivo endpoint audio da applicazioni in modalità condivisa, come descritto in Modalità esclusiva Flussi. Se un'applicazione in modalità condivisa registra un flusso dall'input della riga al momento in cui un'applicazione in modalità esclusiva avvia la registrazione di un flusso dal microfono, il motore audio commuta automaticamente il multiplexer dall'input della riga al microfono. Al contrario, nelle versioni precedenti di Windows, tra cui Windows XP, l'applicazione in modalità esclusiva in questo esempio userebbe le funzioni mixerXxx nell'API multimediale Windows per attraversare le topologie dei dispositivi adattatori, individuare il multiplexer e configurare il multiplexer per selezionare l'input del microfono. In Windows Vista questi passaggi non sono più necessari.

Tuttavia, alcuni client potrebbero richiedere un controllo esplicito sui tipi di controlli hardware audio a cui non è possibile accedere tramite l'API MMDevice, WASAPI o l'API EndpointVolume. Per questi client, l'API DeviceTopology consente di attraversare le topologie dei dispositivi adattatori per individuare e gestire i controlli audio nei dispositivi. Le applicazioni che usano l'API DeviceTopology devono essere progettate con attenzione per evitare di interferire con i criteri audio di Windows e disturbare le configurazioni interne dei dispositivi audio condivisi con altre applicazioni. Per altre informazioni sui criteri audio di Windows, vedere Componenti audio in modalità utente.

L'API DeviceTopology fornisce interfacce per l'individuazione e la gestione dei tipi di controlli audio seguenti in una topologia del dispositivo:

  • Controllo del guadagno automatico
  • Controllo Basso
  • Selettore di input (multiplexer)
  • Controllo di rumorosità
  • Controllo Midrange
  • Disattiva controllo
  • Selettore di output (demultiplexer)
  • Misuratore di picco
  • Controllo treble
  • Controllo volume

Inoltre, l'API DeviceTopology consente ai client di eseguire query sui dispositivi adattatori per ottenere informazioni sui formati di flusso supportati. Il file di intestazione Devicetopology.h definisce le interfacce nell'API DeviceTopology.

Il diagramma seguente mostra un esempio di diverse topologie di dispositivi connessi per la parte di una scheda PCI che acquisisce l'audio da un microfono, un input di riga e un lettore CD.

example of four connected device topologies

Il diagramma precedente mostra i percorsi dei dati che portano dagli input analogici al bus di sistema. Ognuno dei dispositivi seguenti è rappresentato come oggetto topologia dispositivo con un'interfaccia IDeviceTopology :

  • Dispositivo di acquisizione wave
  • Dispositivo multiplexer di input
  • Dispositivo endpoint A
  • Dispositivo endpoint B

Si noti che il diagramma della topologia combina i dispositivi adattatori (l'acquisizione delle onde e i dispositivi multiplexer di input) con i dispositivi endpoint. Attraverso le connessioni tra dispositivi, i dati audio passano da un dispositivo all'altro. Su ogni lato di una connessione è presente un connettore (con etichetta con nel diagramma) tramite il quale i dati vengono immessi o lasciano un dispositivo.

Sul bordo sinistro del diagramma, i segnali provenienti dai jack di input e microfono entrano nei dispositivi endpoint.

All'interno del dispositivo di acquisizione delle onde e del dispositivo multiplexer di input sono funzioni di elaborazione del flusso, che, nella terminologia dell'API DeviceTopology, vengono chiamate sottounite. I tipi di sottounite seguenti vengono visualizzati nel diagramma precedente:

  • Controllo volume (con etichetta Vol)
  • Disattivare il controllo (con etichetta Mute)
  • Multiplexer (o selettore di input; MUX con etichetta)
  • Convertitore analogico-digitale (ADC con etichetta)

Le impostazioni nelle sottounit di volume, disattivazione e multiplexer possono essere controllate dai client e l'API DeviceTopology fornisce interfacce di controllo ai client per controllarle. In questo esempio, la subunit di ADC non ha impostazioni di controllo. Di conseguenza, l'API DeviceTopology non fornisce alcuna interfaccia di controllo per ADC.

Nella terminologia dell'API DeviceTopology, i connettori e le sottounit appartengono alla stessa categoria generale, ovvero parti. Tutte le parti, indipendentemente dal fatto che siano connettori o sottouniti, forniscono un set comune di funzioni. L'API DeviceTopology implementa un'interfaccia IPart per rappresentare le funzioni generiche comuni ai connettori e alle sottounità. L'API implementa le interfacce I Connessione or e ISubunit per rappresentare gli aspetti specifici dei connettori e delle sottounite.

L'API DeviceTopology costruisce le topologie del dispositivo di acquisizione wave e il dispositivo multiplexer di input dal kernel streaming (KS) filtra che il driver audio espone al sistema operativo per rappresentare questi dispositivi. (Il driver dell'adattatore audio implementa Interfacce IMiniportWaveXxx e IMiniportTopology per rappresentare le parti dipendenti dall'hardware di questi filtri. Per altre informazioni su queste interfacce e sui filtri KS, vedere la documentazione di Windows DDK.

L'API DeviceTopology costruisce topologie semplici per rappresentare i dispositivi endpoint A e B nel diagramma precedente. La topologia del dispositivo di un endpoint è costituita da un singolo connettore. Questa topologia è semplicemente un segnaposto per il dispositivo endpoint e non contiene sottounità per l'elaborazione dei dati audio. Infatti, i dispositivi adattatori contengono tutte le sottounità usate dalle applicazioni client per controllare l'elaborazione audio. La topologia del dispositivo di un endpoint funge principalmente da punto di partenza per l'esplorazione delle topologie di dispositivo dei dispositivi adattatori.

Le connessioni interne tra due parti in una topologia del dispositivo sono denominate collegamenti. L'API DeviceTopology fornisce metodi per attraversare i collegamenti da una parte a quella successiva in una topologia del dispositivo. L'API fornisce anche metodi per attraversare le connessioni tra topologie di dispositivo.

Per iniziare l'esplorazione di un set di topologie di dispositivi connessi, un'applicazione client attiva l'interfaccia IDeviceTopology di un dispositivo endpoint audio. Il connettore in un dispositivo endpoint si connette a un connettore in una scheda audio o a una rete. Se l'endpoint si connette a un dispositivo in una scheda audio, i metodi nell'API DeviceTopology consentono all'applicazione di passare attraverso la connessione dall'endpoint all'adattatore ottenendo un riferimento all'interfaccia IDeviceTopology del dispositivo adattatore sull'altro lato della connessione. Una rete, d'altra parte, non ha topologia del dispositivo. Una connessione di rete invia un flusso audio a un client che accede al sistema in modalità remota.

L'API DeviceTopology fornisce l'accesso solo alle topologie dei dispositivi hardware in una scheda audio. I dispositivi esterni sul bordo sinistro del diagramma e i componenti software sul bordo destro non rientrano nell'ambito dell'API. Le linee tratteggiate su entrambi i lati del diagramma rappresentano i limiti dell'API DeviceTopology. Il client può usare l'API per esplorare un percorso di dati che si estende dal jack di input al bus di sistema, ma l'API non può penetrare oltre questi limiti.

Ogni connettore nel diagramma precedente ha un tipo di connessione associato che indica il tipo di connessione stabilita dal connettore. Pertanto, i connettori sui due lati di una connessione hanno sempre tipi di connessione identici. Il tipo di connessione è indicato da un valore di enumerazione Connessione orType, ovvero Physical_External, Physical_Internal, Software_Fixed, Software_IO o Rete. Le connessioni tra il dispositivo multiplexer di input e i dispositivi endpoint A e B sono di tipo Physical_External, il che significa che la connessione rappresenta una connessione fisica a un dispositivo esterno (in altre parole, un jack audio accessibile dall'utente). La connessione al segnale analogico dal lettore CD interno è di tipo Physical_Internal, che indica una connessione fisica a un dispositivo ausiliario installato all'interno dello chassis di sistema. La connessione tra il dispositivo di acquisizione delle onde e il dispositivo multiplexer di input è di tipo Software_Fixed, che indica una connessione permanente fissa e non può essere configurata sotto il controllo software. Infine, la connessione al bus di sistema sul lato destro del diagramma è di tipo Software_IO, che indica che i dati di I/O per la connessione vengono implementati da un motore DMA sotto controllo software. Il diagramma non include un esempio di tipo di connessione di rete.

Il client inizia a attraversare un percorso dati nel dispositivo endpoint. Prima di tutto, il client ottiene un'interfaccia IMMDevice che rappresenta il dispositivo endpoint, come illustrato in Enumerazione dei dispositivi audio. Per ottenere l'interfaccia IDeviceTopology per il dispositivo endpoint, il client chiama il metodo IMMDevice::Activate con il parametro iid impostato su REFIID IID_IDeviceTopology.

Nell'esempio del diagramma precedente, il dispositivo multiplexer di input contiene tutti i controlli hardware (volume, mute e multiplexer) per i flussi di acquisizione dai jack di input e microfono. L'esempio di codice seguente illustra come ottenere l'interfaccia IDeviceTopology per il dispositivo multiplexer di input dall'interfaccia IMMDevice per il dispositivo endpoint per l'input o il microfono della riga:

//-----------------------------------------------------------
// 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;
}

La funzione GetHardwareDeviceTopology nell'esempio di codice precedente esegue i passaggi seguenti per ottenere l'interfaccia IDeviceTopology per il dispositivo multiplexer di input:

  1. Chiamare il metodo IMMDevice::Activate per ottenere l'interfaccia IDeviceTopology per il dispositivo endpoint.
  2. Con l'interfaccia IDeviceTopology ottenuta nel passaggio precedente, chiamare il metodo IDeviceTopology::Get Connessione or per ottenere l'interfaccia I Connessione or del singolo connettore (numero di connettore 0) nel dispositivo endpoint.
  3. Con l'interfaccia I Connessione or ottenuta nel passaggio precedente, chiamare il metodo I Connessione or::Get Connessione edTo per ottenere l'interfaccia I Connessione or del connettore nel dispositivo multiplexer di input.
  4. Eseguire una query sull'interfaccia I Connessione or ottenuta nel passaggio precedente per l'interfaccia IPart.
  5. Con l'interfaccia IPart ottenuta nel passaggio precedente, chiamare il metodo IPart::GetTopologyObject per ottenere l'interfaccia IDeviceTopology per il dispositivo multiplexer di input.

Prima che l'utente possa registrare dal microfono nel diagramma precedente, l'applicazione client deve assicurarsi che il multiplexer selezioni l'input del microfono. L'esempio di codice seguente mostra come un client può attraversare il percorso dei dati dal microfono fino a trovare il multiplexer, che quindi programma per selezionare l'input del microfono:

//-----------------------------------------------------------
// 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;
}

L'API DeviceTopology implementa un'interfaccia IAudioInputSelector per incapsulare un multiplexer, ad esempio quello nel diagramma precedente. (An L'interfaccia IAudioOutputSelector incapsula un demultiplexer. Nell'esempio di codice precedente, il ciclo interno della funzione SelectCaptureDevice esegue una query su ogni subunit che trova per individuare se la subunit è un multiplexer. Se la subunit è un multiplexer, la funzione chiama il metodo IAudioInputSelector::SetSelection per selezionare l'input che si connette al flusso dal dispositivo endpoint.

Nell'esempio di codice precedente ogni iterazione del ciclo esterno attraversa una topologia del dispositivo. Quando si attraversano le topologie del dispositivo nel diagramma precedente, la prima iterazione attraversa il dispositivo multiplexer di input e la seconda iterazione attraversa il dispositivo di acquisizione delle onde. La funzione terminerà quando raggiunge il connettore al bordo destro del diagramma. La terminazione si verifica quando la funzione rileva un connettore con un tipo di connessione Software_IO. Questo tipo di connessione identifica il punto in cui il dispositivo adattatore si connette al bus di sistema.

La chiamata al metodo IPart::GetPartType nell'esempio di codice precedente ottiene un valore di enumerazione IPartType che indica se la parte corrente è un connettore o una subunit di elaborazione audio.

Il ciclo interno nell'esempio di codice precedente viene eseguito attraverso il collegamento da una parte alla successiva chiamando il metodo IPart::EnumPartsOutgoing. (C'è anche un Metodo IPart::EnumPartsIncoming per eseguire l'istruzione nella direzione opposta. Questo metodo recupera un oggetto IPartsList che contiene un elenco di tutte le parti in uscita. Tuttavia, qualsiasi parte che la funzione SelectCaptureDevice prevede di incontrare in un dispositivo di acquisizione avrà sempre esattamente una parte in uscita. Pertanto, la chiamata successiva a IPartsList::GetPart richiede sempre la prima parte dell'elenco, il numero di parte 0, perché la funzione presuppone che questa sia l'unica parte dell'elenco.

Se la funzione SelectCaptureDevice rileva una topologia per cui tale presupposto non è valido, la funzione potrebbe non riuscire a configurare correttamente il dispositivo. Per evitare un errore di questo tipo, una versione più generica della funzione potrebbe eseguire le operazioni seguenti:

  • Chiamare il metodo IPartsList::GetCount per determinare il numero di parti in uscita.
  • Per ogni parte in uscita, chiamare IPartsList::GetPart per iniziare a attraversare il percorso dei dati che conduce dalla parte.

Alcune parti, ma non necessariamente tutte, hanno controlli hardware associati che i client possono impostare o ottenere. Una parte specifica potrebbe avere zero, uno o più controlli hardware. Un controllo hardware è rappresentato dalla coppia di interfacce seguente:

  • Interfaccia di controllo generica, IControlInterface, che include metodi comuni a tutti i controlli hardware.
  • Interfaccia specifica della funzione (ad esempio, IAudioVolumeLevel) che espone i parametri di controllo per un particolare tipo di controllo hardware, ad esempio un controllo del volume.

Per enumerare i controlli hardware per una parte, il client chiama innanzitutto il metodo IPart::GetControlInterfaceCount per determinare il numero di controlli hardware associati alla parte. Successivamente, il client effettua una serie di chiamate al metodo IPart::GetControlInterface per ottenere l'interfaccia IControlInterface per ogni controllo hardware. Infine, il client ottiene l'interfaccia specifica della funzione per ogni controllo hardware chiamando il metodo IControlInterface::GetIID per ottenere l'ID interfaccia. Il client chiama il metodo IPart::Activate con questo ID per ottenere l'interfaccia specifica della funzione.

Una parte che è un connettore potrebbe supportare una delle interfacce di controllo specifiche della funzione seguenti:

Una parte che è una subunit potrebbe supportare una o più delle interfacce di controllo specifiche della funzione seguenti:

Una parte supporta l'interfaccia IDeviceSpecificProperty solo se il controllo hardware sottostante ha un valore di controllo specifico del dispositivo e il controllo non può essere adeguatamente rappresentato da qualsiasi altra interfaccia specifica della funzione nell'elenco precedente. In genere, una proprietà specifica del dispositivo è utile solo per un client che può dedurre il significato del valore della proprietà da informazioni quali il tipo di parte, il sottotipo di parte e il nome della parte. Il client può ottenere queste informazioni chiamando i metodi IPart::GetPartType, IPart::GetSubType e IPart::GetName.

Guida per programmatori