Topologías de dispositivos

La API DeviceTopology proporciona a los clientes control sobre una variedad de funciones internas de adaptadores de audio a las que no pueden acceder a través de la API MMDevice, WASAPI o endpointVolume API.

Como se explicó anteriormente, la API MMDevice, WASAPI y endpointVolume API presentan micrófonos, altavoces, auriculares y otros dispositivos de entrada y salida de audio a los clientes como dispositivos de punto de conexión de audio. El modelo de dispositivo de punto de conexión proporciona a los clientes un acceso cómodo a los controles de volumen y silenciar en dispositivos de audio. Los clientes que solo requieren estos controles simples pueden evitar atravesar las topologías internas de los dispositivos de hardware en adaptadores de audio.

En Windows Vista, el motor de audio configura automáticamente las topologías de los dispositivos de audio para que las usen las aplicaciones de audio. Por lo tanto, las aplicaciones rara vez, si alguna vez, necesitan usar la API DeviceTopology para este propósito. Por ejemplo, supongamos que un adaptador de audio contiene un multiplexador de entrada que puede capturar una secuencia de una entrada de línea o un micrófono, pero que no puede capturar secuencias de ambos dispositivos de punto de conexión al mismo tiempo. Supongamos que el usuario ha habilitado aplicaciones en modo exclusivo para evitar el uso de un dispositivo de punto de conexión de audio mediante aplicaciones en modo compartido, como se describe en Secuencias en modo exclusivo. Si una aplicación en modo compartido está grabando una secuencia de la entrada de línea en el momento en que una aplicación en modo exclusivo comienza a grabar una secuencia desde el micrófono, el motor de audio cambia automáticamente el multiplexador de la entrada de línea al micrófono. En cambio, en versiones anteriores de Windows, incluido Windows XP, la aplicación en modo exclusivo de este ejemplo usaría las funciones mixerXxx en la API multimedia de Windows para recorrer las topologías de los dispositivos del adaptador, detectar el multiplexador y configurar el multiplexador para seleccionar la entrada del micrófono. En Windows Vista, estos pasos ya no son necesarios.

Sin embargo, algunos clientes pueden requerir un control explícito sobre los tipos de controles de hardware de audio a los que no se puede acceder a través de la API MMDevice, WASAPI o endpointVolume API. Para estos clientes, la API DeviceTopology proporciona la capacidad de recorrer las topologías de los dispositivos adaptadores para detectar y administrar los controles de audio en los dispositivos. Las aplicaciones que usan la API DeviceTopology deben diseñarse con cuidado para evitar interferir con la directiva de audio de Windows e interrumpir las configuraciones internas de los dispositivos de audio que se comparten con otras aplicaciones. Para obtener más información sobre la directiva de audio de Windows, vea Componentes de audio en modo de usuario.

La API DeviceTopology proporciona interfaces para detectar y administrar los siguientes tipos de controles de audio en una topología de dispositivo:

  • Control de ganancia automática
  • Control Bass
  • Selector de entrada (multiplexador)
  • Control de ruido
  • Control de rango medio
  • Silenciar control
  • Selector de salida (demultiplexer)
  • Medidor máximo
  • Control Treble
  • Control de volumen

Además, la API DeviceTopology permite a los clientes consultar dispositivos de adaptador para obtener información sobre los formatos de secuencia que admiten. El archivo de encabezado Devicetopology.h define las interfaces de la API DeviceTopology.

En el diagrama siguiente se muestra un ejemplo de varias topologías de dispositivos conectados para la parte de un adaptador PCI que captura audio de un micrófono, entrada de línea y reproductor de CD.

ejemplo de cuatro topologías de dispositivos conectados

En el diagrama anterior se muestran las rutas de acceso de datos que conducen desde las entradas analógicas al bus del sistema. Cada uno de los siguientes dispositivos se representa como un objeto de topología de dispositivo con una interfaz IDeviceTopology :

  • Dispositivo de captura de onda
  • Dispositivo de multiplexador de entrada
  • Dispositivo de punto de conexión A
  • Dispositivo de punto de conexión B

Tenga en cuenta que el diagrama de topología combina dispositivos adaptadores (los dispositivos de captura de onda y de multiplexador de entrada) con dispositivos de punto de conexión. A través de las conexiones entre dispositivos, los datos de audio pasan de un dispositivo a otro. En cada lado de una conexión hay un conector (etiquetado con en el diagrama) a través del cual los datos entran o salen de un dispositivo.

En el borde izquierdo del diagrama, las señales de la entrada de línea y los conectores de micrófono entran en los dispositivos de punto de conexión.

Dentro del dispositivo de captura de onda y el dispositivo de multiplexador de entrada son funciones de procesamiento de flujos, que, en la terminología de la API DeviceTopology, se denominan subunits. Los siguientes tipos de subunidades aparecen en el diagrama anterior:

  • Control de volumen (con la etiqueta Vol)
  • Silenciar control (silenciado)
  • Multiplexador (o selector de entrada; MUX etiquetado)
  • Convertidor analógico a digital (etiquetado ADC)

Los clientes pueden controlar la configuración de las subuniciones de volumen, silenciar y multiplexer, y la API DeviceTopology proporciona interfaces de control a los clientes para controlarlas. En este ejemplo, la subunidad de ADC no tiene ninguna configuración de control. Por lo tanto, la API DeviceTopology no proporciona ninguna interfaz de control para el ADC.

En la terminología de la API DeviceTopology, los conectores y las subunidades pertenecen a la misma categoría general: partes. Todas las partes, independientemente de si son conectores o subunidades, proporcionan un conjunto común de funciones. La API DeviceTopology implementa una interfaz IPart para representar las funciones genéricas que son comunes a los conectores y subunits. La API implementa las interfaces IConnector e ISubunit para representar los aspectos específicos de los conectores y las subunits.

La API DeviceTopology construye las topologías del dispositivo de captura de ondas y el dispositivo de multiplexador de entrada a partir de los filtros de streaming de kernel (KS) que el controlador de audio expone al sistema operativo para representar estos dispositivos. (El controlador del adaptador de audio implementa interfaces IMiniportWaveXxx e IMiniportTopology para representar las partes dependientes del hardware de estos filtros; para obtener más información sobre estas interfaces y sobre los filtros KS, consulte la documentación de Windows DDK).

La API DeviceTopology construye topologías triviales para representar dispositivos de punto de conexión A y B en el diagrama anterior. La topología de dispositivo de un dispositivo de punto de conexión consta de un único conector. Esta topología es simplemente un marcador de posición para el dispositivo de punto de conexión y no contiene subunits para procesar datos de audio. De hecho, los dispositivos adaptadores contienen todas las subunidades que las aplicaciones cliente usan para controlar el procesamiento de audio. La topología de dispositivo de un dispositivo de punto de conexión sirve principalmente como punto de partida para explorar las topologías de dispositivo de los dispositivos adaptadores.

Las conexiones internas entre dos partes de una topología de dispositivo se denominan vínculos. La API DeviceTopology proporciona métodos para recorrer vínculos de una parte a la siguiente en una topología de dispositivo. La API también proporciona métodos para recorrer las conexiones entre topologías de dispositivo.

Para comenzar la exploración de un conjunto de topologías de dispositivos conectados, una aplicación cliente activa la interfaz IDeviceTopology de un dispositivo de punto de conexión de audio. El conector de un dispositivo de punto de conexión se conecta a un conector de un adaptador de audio o a una red. Si el punto de conexión se conecta a un dispositivo en un adaptador de audio, los métodos de la API DeviceTopology permiten a la aplicación recorrer la conexión desde el punto de conexión al adaptador obteniendo una referencia a la interfaz IDeviceTopology del dispositivo adaptador en el otro lado de la conexión. Por otro lado, una red no tiene topología de dispositivo. Una conexión de red canaliza una secuencia de audio a un cliente que accede al sistema de forma remota.

La API DeviceTopology proporciona acceso solo a las topologías de los dispositivos de hardware de un adaptador de audio. Los dispositivos externos en el borde izquierdo del diagrama y los componentes de software del borde derecho están fuera del ámbito de la API. Las líneas discontinuas de cualquier lado del diagrama representan los límites de la API DeviceTopology. El cliente puede usar la API para explorar una ruta de acceso de datos que se extiende desde el conector de entrada al bus del sistema, pero la API no puede penetrar más allá de estos límites.

Cada conector del diagrama anterior tiene un tipo de conexión asociado que indica el tipo de conexión que realiza el conector. Por lo tanto, los conectores de los dos lados de una conexión siempre tienen tipos de conexión idénticos. El tipo de conexión se indica mediante un valor de enumeración ConnectorType : Physical_External, Physical_Internal, Software_Fixed, Software_IO o Network. Las conexiones entre el dispositivo multiplexador de entrada y los dispositivos de punto de conexión A y B son de tipo Physical_External, lo que significa que la conexión representa una conexión física a un dispositivo externo (es decir, un conector de audio accesible para el usuario). La conexión a la señal analógica del reproductor de CD interno es de tipo Physical_Internal, que indica una conexión física a un dispositivo auxiliar instalado dentro del chasis del sistema. La conexión entre el dispositivo de captura de ondas y el dispositivo de multiplexador de entrada es de tipo Software_Fixed, que indica una conexión permanente que es fija y no se puede configurar bajo control de software. Por último, la conexión al bus del sistema en el lado derecho del diagrama es de tipo Software_IO, lo que indica que la E/S de datos de la conexión se implementa mediante un motor DMA bajo control de software. (El diagrama no incluye un ejemplo de un tipo de conexión de red).

El cliente comienza a recorrer una ruta de acceso de datos en el dispositivo de punto de conexión. En primer lugar, el cliente obtiene una interfaz IMMDevice que representa el dispositivo de punto de conexión, como se explica en Enumeración de dispositivos de audio. Para obtener la interfaz IDeviceTopology para el dispositivo de extremo, el cliente llama al método IMMDevice::Activate con el parámetro iid establecido en REFIID IID_IDeviceTopology.

En el ejemplo del diagrama anterior, el dispositivo de multiplexador de entrada contiene todos los controles de hardware (volumen, silenciador y multiplexador) para las secuencias de captura de las tomas de entrada de línea y micrófono. En el ejemplo de código siguiente se muestra cómo obtener la interfaz IDeviceTopology para el dispositivo multiplexador de entrada desde la interfaz IMMDevice para el dispositivo de punto de conexión para la entrada de línea o el micrófono:

//-----------------------------------------------------------
// 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 función GetHardwareDeviceTopology del ejemplo de código anterior realiza los pasos siguientes para obtener la interfaz IDeviceTopology del dispositivo multiplexador de entrada:

  1. Llame al método IMMDevice::Activate para obtener la interfaz IDeviceTopology del dispositivo de punto de conexión.
  2. Con la interfaz IDeviceTopology obtenida en el paso anterior, llame al método IDeviceTopology::GetConnector para obtener la interfaz IConnector del conector único (número de conector 0) en el dispositivo de punto de conexión.
  3. Con la interfaz IConnector obtenida en el paso anterior, llame al método IConnector::GetConnectedTo para obtener la interfaz IConnector del conector en el dispositivo multiplexador de entrada.
  4. Consulte la interfaz IConnector obtenida en el paso anterior para su interfaz IPart .
  5. Con la interfaz IPart obtenida en el paso anterior, llame al método IPart::GetTopologyObject para obtener la interfaz IDeviceTopology para el dispositivo multiplexador de entrada.

Para que el usuario pueda grabar desde el micrófono en el diagrama anterior, la aplicación cliente debe asegurarse de que el multiplexador selecciona la entrada del micrófono. En el ejemplo de código siguiente se muestra cómo un cliente puede atravesar la ruta de acceso de datos desde el micrófono hasta que encuentre el multiplexador, que luego programa para seleccionar la entrada del micrófono:

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

La API DeviceTopology implementa una interfaz IAudioInputSelector para encapsular un multiplexador, como el del diagrama anterior. (Una interfaz IAudioOutputSelector encapsula un demultiplexer). En el ejemplo de código anterior, el bucle interno de la función SelectCaptureDevice consulta cada subunidad que encuentra para detectar si la subunidad es un multiplexador. Si la subunidad es un multiplexador, la función llama al método IAudioInputSelector::SetSelection para seleccionar la entrada que se conecta a la secuencia desde el dispositivo de punto de conexión.

En el ejemplo de código anterior, cada iteración del bucle externo recorre una topología de dispositivo. Al recorrer las topologías del dispositivo en el diagrama anterior, la primera iteración recorre el dispositivo multiplexador de entrada y la segunda iteración atraviesa el dispositivo de captura de ondas. La función finalizará cuando llegue al conector en el borde derecho del diagrama. La finalización se produce cuando la función detecta un conector con un tipo de conexión Software_IO. Este tipo de conexión identifica el punto en el que el dispositivo adaptador se conecta al bus del sistema.

La llamada al método IPart::GetPartType del ejemplo de código anterior obtiene un valor de enumeración IPartType que indica si la parte actual es un conector o una subunidad de procesamiento de audio.

El bucle interno del ejemplo de código anterior recorre el vínculo de una parte a la siguiente llamando al método IPart::EnumPartsOutgoing . (También hay un método IPart::EnumPartsIncoming para ejecutar paso a paso en la dirección opuesta). Este método recupera un objeto IPartsList que contiene una lista de todos los elementos salientes. Sin embargo, cualquier parte que la función SelectCaptureDevice espera encontrar en un dispositivo de captura siempre tendrá exactamente una parte saliente. Por lo tanto, la llamada posterior a IPartsList::GetPart siempre solicita la primera parte de la lista, el número de parte 0, porque la función supone que esta es la única parte de la lista.

Si la función SelectCaptureDevice encuentra una topología para la que esa suposición no es válida, la función podría no configurar el dispositivo correctamente. Para evitar este error, una versión de uso general de la función podría hacer lo siguiente:

  • Llame al método IPartsList::GetCount para determinar el número de elementos salientes.
  • Para cada parte saliente, llame a IPartsList::GetPart para empezar a recorrer la ruta de acceso de datos que conduce desde la parte.

Algunos, pero no necesariamente todos, los elementos tienen controles de hardware asociados que los clientes pueden establecer u obtener. Una parte determinada puede tener cero, uno o varios controles de hardware. Un control de hardware se representa mediante el siguiente par de interfaces:

  • Una interfaz de control genérica, IControlInterface, que tiene métodos comunes a todos los controles de hardware.
  • Interfaz específica de función (por ejemplo, IAudioVolumeLevel) que expone los parámetros de control para un tipo determinado de control de hardware (por ejemplo, un control de volumen).

Para enumerar los controles de hardware de un elemento, el cliente llama primero al método IPart::GetControlInterfaceCount para determinar el número de controles de hardware asociados a la parte. A continuación, el cliente realiza una serie de llamadas al método IPart::GetControlInterface para obtener la interfaz IControlInterface para cada control de hardware. Por último, el cliente obtiene la interfaz específica de la función para cada control de hardware mediante una llamada al método IControlInterface::GetIID para obtener el identificador de interfaz. El cliente llama al método IPart::Activate con este identificador para obtener la interfaz específica de la función.

Una parte que es un conector puede admitir una de las siguientes interfaces de control específicas de función:

Una parte que es una subunidad puede admitir una o varias de las siguientes interfaces de control específicas de función:

Una parte admite la interfaz IDeviceSpecificProperty solo si el control de hardware subyacente tiene un valor de control específico del dispositivo y el control no se puede representar adecuadamente mediante ninguna otra interfaz específica de la función de la lista anterior. Normalmente, una propiedad específica del dispositivo solo es útil para un cliente que puede deducir el significado del valor de propiedad a partir de información como el tipo de elemento, el subtipo de elemento y el nombre del elemento. El cliente puede obtener esta información llamando a los métodos IPart::GetPartType, IPart::GetSubType e IPart::GetName .

Guía de programación