Compartilhar via


Topologias de dispositivos

A API DeviceTopology oferece aos clientes controle sobre uma variedade de funções internas de adaptadores de áudio que eles não podem acessar por meio da API MMDevice, WASAPI ou da API EndpointVolume.

Conforme explicado anteriormente, a API MMDevice, a WASAPI e a API EndpointVolume apresentam microfones, alto-falantes, fones de ouvido e outros dispositivos de entrada e saída de áudio para clientes como dispositivos de ponto de extremidade de áudio. O modelo de dispositivo de ponto de extremidade fornece aos clientes acesso conveniente a controles de volume e mudo em dispositivos de áudio. Os clientes que exigem apenas esses controles simples podem evitar atravessar as topologias internas de dispositivos de hardware em adaptadores de áudio.

No Windows Vista, o mecanismo de áudio configura automaticamente as topologias de dispositivos de áudio para uso por aplicativos de áudio. Assim, os aplicativos raramente, ou nunca, precisam usar a API DeviceTopology para essa finalidade. Por exemplo, suponha que um adaptador de áudio contém um multiplexador de entrada que pode capturar um fluxo de uma entrada de linha ou um microfone, mas que não pode capturar fluxos de ambos os dispositivos de ponto de extremidade ao mesmo tempo. Suponha que o usuário tenha habilitado aplicativos de modo exclusivo para antecipar o uso de um dispositivo de ponto de extremidade de áudio por aplicativos de modo compartilhado, conforme descrito em Fluxos de modo exclusivo. Se um aplicativo de modo compartilhado estiver gravando um fluxo da entrada de linha no momento em que um aplicativo de modo exclusivo começar a gravar um fluxo do microfone, o mecanismo de áudio alternará automaticamente o multiplexador da entrada de linha para o microfone. Por outro lado, em versões anteriores do Windows, incluindo o Windows XP, o aplicativo de modo exclusivo neste exemplo usaria as funções mixerXxx na API multimídia do Windows para percorrer as topologias dos dispositivos adaptadores, descobrir o multiplexador e configurar o multiplexador para selecionar a entrada do microfone. No Windows Vista, essas etapas não são mais necessárias.

No entanto, alguns clientes podem exigir controle explícito sobre tipos de controles de hardware de áudio que não podem ser acessados por meio da API MMDevice, WASAPI ou a API EndpointVolume. Para esses clientes, a API DeviceTopology fornece a capacidade de percorrer as topologias de dispositivos adaptadores para descobrir e gerenciar os controles de áudio nos dispositivos. Os aplicativos que usam a API DeviceTopology devem ser projetados com cuidado para evitar interferir na política de áudio do Windows e perturbar as configurações internas de dispositivos de áudio compartilhados com outros aplicativos. Para obter mais informações sobre a política de áudio do Windows, consulte Componentes de áudio no modo de usuário.

A API DeviceTopology fornece interfaces para descobrir e gerenciar os seguintes tipos de controles de áudio em uma topologia de dispositivo:

  • Controle automático de ganho
  • Controle de graves
  • Seletor de entrada (multiplexador)
  • Controle de volume
  • Controle intermediário
  • Controle de mudo
  • Seletor de saída (demultiplexador)
  • Medidor de pico
  • Controle de agudos
  • Controle de volume

Além disso, a API DeviceTopology permite que os clientes consultem dispositivos adaptadores para obter informações sobre os formatos de fluxo suportados. O arquivo de cabeçalho Devicetopology.h define as interfaces na API DeviceTopology.

O diagrama a seguir mostra um exemplo de várias topologias de dispositivo conectado para a parte de um adaptador PCI que captura áudio de um microfone, entrada de linha e CD player.

example of four connected device topologies

O diagrama anterior mostra os caminhos de dados que levam das entradas analógicas para o barramento do sistema. Cada um dos seguintes dispositivos é representado como um objeto de topologia de dispositivo com uma interface IDeviceTopology:

  • Dispositivo de captura de ondas
  • Dispositivo multiplexador de entrada
  • Dispositivo de ponto de extremidade A
  • Dispositivo de ponto de extremidade B

Observe que o diagrama de topologia combina dispositivos adaptadores (os dispositivos multiplexadores de entrada e captura de onda) com dispositivos de ponto de extremidade. Através das conexões entre dispositivos, os dados de áudio passam de um dispositivo para o próximo. Em cada lado de uma conexão há um conector (rotulado Con no diagrama) através do qual os dados entram ou saem de um dispositivo.

Na borda esquerda do diagrama, os sinais das entradas de linha e dos conectores de microfone entram nos dispositivos de ponto de extremidade.

Dentro do dispositivo de captura de onda e do dispositivo multiplexador de entrada estão as funções de processamento de fluxo, que, na terminologia da API DeviceTopology, são chamadas de subunidades. Os seguintes tipos de subunidades aparecem no diagrama anterior:

  • Controle de volume (rotulado Vol)
  • Controle de mudo (rotulado Mudo)
  • Multiplexador (ou seletor de entrada; rotulado MUX)
  • Conversor analógico-digital (rotulado ADC)

As configurações nas subunidades volume, mudo e multiplexador podem ser controladas pelos clientes, e a API DeviceTopology fornece interfaces de controle aos clientes para controlá-los. Neste exemplo, a subunidade ADC não tem configurações de controle. Assim, a API DeviceTopology não fornece nenhuma interface de controle para o ADC.

Na terminologia da API DeviceTopology, conectores e subunidades pertencem à mesma categoria geral — partes. Todas as peças, independentemente de serem conectores ou subunidades, fornecem um conjunto comum de funções. A API DeviceTopology implementa uma interface IPart para representar as funções genéricas comuns a conectores e subunidades. A API implementa as interfaces IConnector e ISubunit para representar os aspectos específicos de conectores e subunidades.

A API DeviceTopology constrói as topologias do dispositivo de captura de onda e do dispositivo multiplexador de entrada a partir dos filtros de kernel streaming (KS) que o driver de áudio expõe ao sistema operacional para representar esses dispositivos. (O driver do adaptador de áudio implementa Interfaces IMiniportWaveXxx e IMiniportTopology para representar as partes dependentes de hardware desses filtros; para obter mais informações sobre essas interfaces e sobre filtros KS, consulte a documentação do Windows DDK.)

A API DeviceTopology constrói topologias triviais para representar os dispositivos de ponto de extremidade A e B no diagrama anterior. A topologia de dispositivo de um dispositivo de ponto de extremidade consiste em um único conector. Essa topologia é meramente um espaço reservado para o dispositivo de ponto de extremidade e não contém subunidades para processar dados de áudio. Na verdade, os dispositivos adaptadores contêm todas as subunidades que os aplicativos cliente usam para controlar o processamento de áudio. A topologia de dispositivo de um dispositivo de ponto de extremidade serve principalmente como um ponto de partida para explorar as topologias de dispositivo de dispositivos adaptadores.

As conexões internas entre duas partes em uma topologia de dispositivo são chamadas de links. A API DeviceTopology fornece métodos para atravessar links de uma parte para a próxima em uma topologia de dispositivo. A API também fornece métodos para percorrer as conexões entre topologias de dispositivo.

Para iniciar a exploração de um conjunto de topologias de dispositivo conectado, um aplicativo cliente ativa a interface IDeviceTopology de um dispositivo de ponto de extremidade de áudio. O conector em um dispositivo de ponto de extremidade se conecta a um conector em um adaptador de áudio ou a uma rede. Se o ponto de extremidade se conectar a um dispositivo em um adaptador de áudio, os métodos na API DeviceTopology permitirão que o aplicativo atravesse a conexão do ponto de extremidade para o adaptador obtendo uma referência à interface IDeviceTopology do dispositivo adaptador do outro lado da conexão. Uma rede, por outro lado, não tem topologia de dispositivo. Uma conexão de rede canaliza um fluxo de áudio para um cliente que está acessando o sistema remotamente.

A API DeviceTopology fornece acesso somente às topologias dos dispositivos de hardware em um adaptador de áudio. Os dispositivos externos na borda esquerda do diagrama e os componentes de software na borda direita estão além do escopo da API. As linhas tracejadas em ambos os lados do diagrama representam os limites da API DeviceTopology. O cliente pode usar a API para explorar um caminho de dados que se estende do conector de entrada até o barramento do sistema, mas a API não pode penetrar além desses limites.

Cada conector no diagrama anterior tem um tipo de conexão associado que indica o tipo de conexão que o conector faz. Assim, os conectores nos dois lados de uma conexão sempre têm tipos de conexão idênticos. O tipo de conexão é indicado por um valor de enumeração ConnectorType — Physical_External, Physical_Internal, Software_Fixed, Software_IO ou Network. As conexões entre o dispositivo multiplexador de entrada e os dispositivos de ponto de extremidade A e B são do tipo Physical_External, o que significa que a conexão representa uma conexão física com um dispositivo externo (em outras palavras, uma tomada de áudio acessível pelo usuário). A conexão com o sinal analógico do CD player interno é do tipo Physical_Internal, que indica uma conexão física com um dispositivo auxiliar instalado dentro do chassi do sistema. A conexão entre o dispositivo de captura de onda e o dispositivo multiplexador de entrada é do tipo Software_Fixed, que indica uma conexão permanente que é fixa e não pode ser configurada sob controle de software. Finalmente, a conexão com o barramento do sistema no lado direito do diagrama é do tipo Software_IO, o que indica que a E/S de dados para a conexão é implementada por um mecanismo DMA sob controle de software. (O diagrama não inclui um exemplo de um tipo de conexão de rede.)

O cliente começa a percorrer um caminho de dados no dispositivo de ponto de extremidade. Primeiro, o cliente obtém uma interface IMMDevice que representa o dispositivo de ponto de extremidade, conforme explicado em Enumerando dispositivos de áudio. Para obter a interface IDeviceTopology para o dispositivo de ponto de extremidade, o cliente chama o método IMMDevice::Activate com o parâmetro iid definido como REFIID IID_IDeviceTopology.

No exemplo do diagrama anterior, o dispositivo multiplexador de entrada contém todos os controles de hardware (volume, mudo e multiplexador) para os fluxos de captura das entradas de linha e dos conectores de microfone. O exemplo de código a seguir mostra como obter a interface IDeviceTopology para o dispositivo multiplexador de entrada da interface IMMDevice para o dispositivo de ponto de extremidade para a entrada de linha ou microfone:

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

A função GetHardwareDeviceTopology no exemplo de código anterior executa as seguintes etapas para obter a interface IDeviceTopology para o dispositivo multiplexador de entrada:

  1. Chame o método IMMDevice::Activate para obter a interface IDeviceTopology para o dispositivo de ponto de extremidade.
  2. Com a interface IDeviceTopology obtida na etapa anterior, chame o método IDeviceTopology::GetConnector para obter a interface IConnector do conector único (número do conector 0) no dispositivo de ponto de extremidade.
  3. Com a interface IConnector obtida na etapa anterior, chame o método IConnector::GetConnectedTo para obter a interface IConnector do conector no dispositivo multiplexador de entrada.
  4. Consulte a interface IConnector obtida na etapa anterior para sua interface IPart .
  5. Com a interface IPart obtida na etapa anterior, chame o método IPart::GetTopologyObject para obter a interface IDeviceTopology para o dispositivo multiplexador de entrada.

Antes que o usuário possa gravar a partir do microfone no diagrama anterior, o aplicativo cliente deve certificar-se de que o multiplexador seleciona a entrada do microfone. O exemplo de código a seguir mostra como um cliente pode percorrer o caminho de dados do microfone até encontrar o multiplexador, que ele programa para selecionar a entrada do microfone:

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

A API DeviceTopology implementa uma interface IAudioInputSelector para encapsular um multiplexador, como o do diagrama anterior. (Um A interface IAudioOutputSelector encapsula um demultiplexador.) No exemplo de código anterior, o loop interno da função SelectCaptureDevice consulta cada subunidade que ele encontra para descobrir se a subunidade é um multiplexador. Se a subunidade for um multiplexador, a função chamará o método IAudioInputSelector::SetSelection para selecionar a entrada que se conecta ao fluxo a partir do dispositivo de ponto de extremidade.

No exemplo de código anterior, cada iteração do loop externo atravessa uma topologia de dispositivo. Ao atravessar as topologias do dispositivo no diagrama anterior, a primeira iteração atravessa o dispositivo multiplexador de entrada e a segunda iteração atravessa o dispositivo de captura de onda. A função terminará quando atingir o conector na borda direita do diagrama. A terminação ocorre quando a função detecta um conector com um tipo de conexão Software_IO. Esse tipo de conexão identifica o ponto no qual o dispositivo adaptador se conecta ao barramento do sistema.

A chamada para o método IPart::GetPartType no exemplo de código anterior obtém um valor de enumeração IPartType que indica se a parte atual é um conector ou uma subunidade de processamento de áudio.

O loop interno no exemplo de código anterior atravessa o link de uma parte para a próxima chamando o método IPart::EnumPartsOutgoing. (Há também um IPart::EnumPartsIncoming método para pisar na direção oposta.) Esse método recupera um objeto IPartsList que contém uma lista de todas as partes de saída. No entanto, qualquer parte que a função SelectCaptureDevice espera encontrar em um dispositivo de captura sempre terá exatamente uma parte de saída. Assim, a chamada subsequente para IPartsList::GetPart sempre solicita a primeira parte da lista, número de parte 0, porque a função assume que essa é a única parte na lista.

Se a função SelectCaptureDevice encontrar uma topologia para a qual essa suposição não é válida, a função poderá falhar ao configurar o dispositivo corretamente. Para evitar essa falha, uma versão de uso mais geral da função pode fazer o seguinte:

  • Chame o método IPartsList::GetCount para determinar o número de partes de saída.
  • Para cada parte de saída, chame IPartsList::GetPart para começar a percorrer o caminho de dados que leva da parte.

Algumas, mas não necessariamente todas, as peças têm controles de hardware associados que os clientes podem definir ou obter. Uma determinada peça pode ter zero, um ou mais controles de hardware. Um controle de hardware é representado pelo seguinte par de interfaces:

Para enumerar os controles de hardware de uma peça, o cliente primeiro chama o método IPart::GetControlInterfaceCount para determinar o número de controles de hardware associados à peça. Em seguida, o cliente faz uma série de chamadas para o método IPart::GetControlInterface para obter a interface IControlInterface para cada controle de hardware. Finalmente, o cliente obtém a interface específica da função para cada controle de hardware chamando o método IControlInterface::GetIID para obter a ID da interface. O cliente chama o método IPart::Activate com esse ID para obter a interface específica da função.

Uma peça que é um conector pode oferecer suporte a uma das seguintes interfaces de controle específicas da função:

Uma parte que é uma subunidade pode oferecer suporte a uma ou mais das seguintes interfaces de controle específicas da função:

Uma parte oferece suporte à interface IDeviceSpecificProperty somente se o controle de hardware subjacente tiver um valor de controle específico do dispositivo e o controle não puder ser representado adequadamente por qualquer outra interface específica da função na lista anterior. Normalmente, uma propriedade específica do dispositivo é útil apenas para um cliente que pode inferir o significado do valor da propriedade a partir de informações como o tipo de peça, o subtipo de peça e o nome da peça. O cliente pode obter essas informações chamando os métodos IPart::GetPartType, IPart::GetSubType e IPart::GetName.

Guia de programação