Partager via


Topologies d’appareil

L’API DeviceTopology permet aux clients de contrôler diverses fonctions internes des adaptateurs audio auxquels ils ne peuvent pas accéder via l’API MMDevice, WASAPI ou l’API EndpointVolume.

Comme expliqué précédemment, l’API MMDevice, WASAPI et l’API EndpointVolume présentent des microphones, des haut-parleurs, des casques et d’autres périphériques d’entrée et de sortie audio aux clients en tant que périphériques de point de terminaison audio. Le modèle d’appareil de point de terminaison fournit aux clients un accès pratique aux contrôles de volume et de désactivation du son dans les appareils audio. Les clients qui nécessitent uniquement ces contrôles simples peuvent éviter de parcourir les topologies internes des périphériques matériels dans les cartes audio.

Dans Windows Vista, le moteur audio configure automatiquement les topologies des périphériques audio pour une utilisation par les applications audio. Ainsi, les applications ont rarement, voire jamais, besoin d’utiliser l’API DeviceTopology à cet effet. Par exemple, supposons qu’une carte audio contient un multiplexeur d’entrée qui peut capturer un flux à partir d’une entrée de ligne ou d’un microphone, mais qui ne peut pas capturer les flux des deux appareils de point de terminaison en même temps. Supposons que l’utilisateur ait activé les applications en mode exclusif pour préempter l’utilisation d’un appareil de point de terminaison audio par les applications en mode partagé, comme décrit dans Flux en mode exclusif. Si une application en mode partagé enregistre un flux à partir de l’entrée de ligne au moment où une application en mode exclusif commence à enregistrer un flux à partir du microphone, le moteur audio bascule automatiquement le multiplexeur de l’entrée de ligne vers le microphone. En revanche, dans les versions antérieures de Windows, y compris Windows XP, l’application en mode exclusif dans cet exemple utilise les fonctions mixerXxx de l’API multimédia Windows pour parcourir les topologies des périphériques adaptateurs, découvrir le multiplexeur et configurer le multiplexeur pour sélectionner l’entrée du microphone. Dans Windows Vista, ces étapes ne sont plus nécessaires.

Toutefois, certains clients peuvent exiger un contrôle explicite sur les types de contrôles matériels audio qui ne sont pas accessibles via l’API MMDevice, WASAPI ou l’API EndpointVolume. Pour ces clients, l’API DeviceTopology permet de parcourir les topologies des périphériques adaptateurs pour découvrir et gérer les contrôles audio dans les appareils. Les applications qui utilisent l’API DeviceTopology doivent être conçues avec précaution pour éviter d’interférer avec la stratégie audio Windows et de perturber les configurations internes des périphériques audio partagés avec d’autres applications. Pour plus d’informations sur la stratégie audio Windows, consultez Composants audio en mode utilisateur.

L’API DeviceTopology fournit des interfaces pour découvrir et gérer les types de contrôles audio suivants dans une topologie d’appareil :

  • Contrôle automatique des gains
  • Contrôle des basses
  • Sélecteur d’entrée (multiplexeur)
  • Contrôle de l’intensité sonore
  • Contrôle midrange
  • Désactiver le contrôle
  • Sélecteur de sortie (démultiplexeur)
  • Compteur de pointe
  • Contrôle des aigus
  • Contrôle du volume

En outre, l’API DeviceTopology permet aux clients d’interroger les périphériques adaptateurs pour obtenir des informations sur les formats de flux qu’ils prennent en charge. Le fichier d’en-tête Devicetopology.h définit les interfaces dans l’API DeviceTopology.

Le diagramme suivant montre un exemple de plusieurs topologies d’appareils connectés pour la partie d’un adaptateur PCI qui capture l’audio à partir d’un microphone, d’une entrée de ligne et d’un lecteur CD.

exemple de quatre topologies d’appareils connectés

Le diagramme précédent montre les chemins de données qui mènent des entrées analogiques au bus système. Chacun des appareils suivants est représenté sous la forme d’un objet device-topology avec une interface IDeviceTopology :

  • Appareil de capture d’ondes
  • Appareil multiplexeur d’entrée
  • Appareil de point de terminaison A
  • Appareil de point de terminaison B

Notez que le diagramme de topologie combine des périphériques adaptateurs (les appareils de capture d’ondes et de multiplexeur d’entrée) avec des appareils de point de terminaison. Grâce aux connexions entre les appareils, les données audio passent d’un appareil à l’autre. De chaque côté d’une connexion se trouve un connecteur (intitulé Con dans le diagramme) par lequel les données entrent ou quittent un appareil.

Sur le bord gauche du diagramme, les signaux des prises d’entrée de ligne et de microphone entrent dans les appareils de point de terminaison.

À l’intérieur de l’appareil de capture d’ondes et du multiplexeur d’entrée se trouvent des fonctions de traitement de flux, qui, dans la terminologie de l’API DeviceTopology, sont appelées sous-unités. Les types de sous-unités suivants apparaissent dans le diagramme précédent :

  • Contrôle de volume (étiqueté Vol)
  • Contrôle Muet (étiqueté Muet)
  • Multiplexeur (ou sélecteur d’entrée ; étiqueté MUX)
  • Convertisseur analogique-numérique (étiqueté ADC)

Les paramètres des sous-unités de volume, de muet et de multiplexeur peuvent être contrôlés par les clients, et l’API DeviceTopology fournit des interfaces de contrôle aux clients pour les contrôler. Dans cet exemple, la sous-unité ADC n’a aucun paramètre de contrôle. Par conséquent, l’API DeviceTopology ne fournit aucune interface de contrôle pour aDC.

Dans la terminologie de l’API DeviceTopology, les connecteurs et les sous-unités appartiennent à la même catégorie générale( parties). Toutes les parties, qu’il s’agisse de connecteurs ou de sous-unités, fournissent un ensemble commun de fonctions. L’API DeviceTopology implémente une interface IPart pour représenter les fonctions génériques communes aux connecteurs et sous-unités. L’API implémente les interfaces IConnector et ISubunit pour représenter les aspects spécifiques des connecteurs et des sous-unités.

L’API DeviceTopology construit les topologies du périphérique de capture d’ondes et du multiplexeur d’entrée à partir des filtres KS (kernel-streaming) que le pilote audio expose au système d’exploitation pour représenter ces appareils. (Le pilote de carte audio implémente les interfaces IMiniportWaveXxx et IMiniportTopology pour représenter les parties dépendantes du matériel de ces filtres. Pour plus d’informations sur ces interfaces et sur les filtres KS, consultez la documentation windows DDK.)

L’API DeviceTopology construit des topologies triviales pour représenter les appareils de point de terminaison A et B dans le diagramme précédent. La topologie d’appareil d’un appareil de point de terminaison se compose d’un seul connecteur. Cette topologie est simplement un espace réservé pour l’appareil de point de terminaison et ne contient aucune sous-unité pour le traitement des données audio. En fait, les périphériques adaptateurs contiennent toutes les sous-unités que les applications clientes utilisent pour contrôler le traitement audio. La topologie d’appareil d’un appareil de point de terminaison sert principalement de point de départ à l’exploration des topologies d’appareils adaptateurs.

Les connexions internes entre deux parties d’une topologie d’appareil sont appelées liaisons. L’API DeviceTopology fournit des méthodes pour parcourir les liens d’une partie à l’autre dans une topologie d’appareil. L’API fournit également des méthodes pour parcourir les connexions entre les topologies d’appareils.

Pour commencer l’exploration d’un ensemble de topologies d’appareils connectés, une application cliente active l’interface IDeviceTopology d’un appareil de point de terminaison audio. Le connecteur d’un appareil de point de terminaison se connecte à un connecteur dans une carte audio ou à un réseau. Si le point de terminaison se connecte à un appareil sur une carte audio, les méthodes de l’API DeviceTopology permettent à l’application de passer du point de terminaison à l’adaptateur en obtenant une référence à l’interface IDeviceTopology de l’appareil adaptateur de l’autre côté de la connexion. En revanche, un réseau n’a pas de topologie d’appareil. Une connexion réseau canalise un flux audio vers un client qui accède au système à distance.

L’API DeviceTopology permet d’accéder uniquement aux topologies des périphériques matériels dans une carte audio. Les appareils externes sur le bord gauche du diagramme et les composants logiciels sur le bord droit dépassent le cadre de l’API. Les lignes en pointillés de chaque côté du diagramme représentent les limites de l’API DeviceTopology. Le client peut utiliser l’API pour explorer un chemin de données qui s’étend de la prise d’entrée au bus système, mais l’API ne peut pas pénétrer au-delà de ces limites.

Chaque connecteur du diagramme précédent a un type de connexion associé qui indique le type de connexion établi par le connecteur. Ainsi, les connecteurs des deux côtés d’une connexion ont toujours des types de connexion identiques. Le type de connexion est indiqué par une valeur d’énumération ConnectorType : Physical_External, Physical_Internal, Software_Fixed, Software_IO ou Réseau. Les connexions entre l’appareil de multiplexeur d’entrée et les périphériques de point de terminaison A et B sont de type Physical_External, ce qui signifie que la connexion représente une connexion physique à un appareil externe (en d’autres termes, une prise audio accessible par l’utilisateur). La connexion au signal analogique du lecteur CD interne est de type Physical_Internal, ce qui indique une connexion physique à un appareil auxiliaire installé à l’intérieur du châssis du système. La connexion entre l’appareil de capture d’ondes et le multiplexeur d’entrée est de type Software_Fixed, ce qui indique une connexion permanente qui est fixe et ne peut pas être configurée sous contrôle logiciel. Enfin, la connexion au bus système sur le côté droit du diagramme est de type Software_IO, ce qui indique que les E/S de données pour la connexion sont implémentées par un moteur DMA sous contrôle logiciel. (Le diagramme n’inclut pas d’exemple de type de connexion réseau.)

Le client commence à parcourir un chemin de données au niveau de l’appareil de point de terminaison. Tout d’abord, le client obtient une interface IMMDevice qui représente l’appareil de point de terminaison, comme expliqué dans Énumération des périphériques audio. Pour obtenir l’interface IDeviceTopology pour l’appareil de point de terminaison, le client appelle la méthode IMMDevice::Activate avec le paramètre iid défini sur REFIID IID_IDeviceTopology.

Dans l’exemple du diagramme précédent, le multiplexeur d’entrée contient tous les contrôles matériels (volume, muet et multiplexeur) pour les flux de capture à partir des prises d’entrée de ligne et de microphone. L’exemple de code suivant montre comment obtenir l’interface IDeviceTopology pour l’appareil multiplexeur d’entrée à partir de l’interface IMMDevice pour le périphérique de point de terminaison pour l’entrée de ligne ou le microphone :

//-----------------------------------------------------------
// 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 fonction GetHardwareDeviceTopology de l’exemple de code précédent effectue les étapes suivantes pour obtenir l’interface IDeviceTopology pour l’appareil multiplexeur d’entrée :

  1. Appelez la méthode IMMDevice::Activate pour obtenir l’interface IDeviceTopology pour l’appareil de point de terminaison.
  2. Avec l’interface IDeviceTopology obtenue à l’étape précédente, appelez la méthode IDeviceTopology::GetConnector pour obtenir l’interface IConnector du connecteur unique (numéro de connecteur 0) dans l’appareil de point de terminaison.
  3. Avec l’interface IConnector obtenue à l’étape précédente, appelez la méthode IConnector::GetConnectedTo pour obtenir l’interface IConnector du connecteur dans l’appareil multiplexeur d’entrée.
  4. Interrogez l’interface IConnector obtenue à l’étape précédente pour son interface IPart .
  5. Avec l’interface IPart obtenue à l’étape précédente, appelez la méthode IPart::GetTopologyObject pour obtenir l’interface IDeviceTopology pour l’appareil multiplexeur d’entrée.

Avant que l’utilisateur puisse enregistrer à partir du microphone dans le diagramme précédent, l’application cliente doit s’assurer que le multiplexeur sélectionne l’entrée du microphone. L’exemple de code suivant montre comment un client peut parcourir le chemin des données à partir du microphone jusqu’à ce qu’il trouve le multiplexeur, qu’il programme ensuite pour sélectionner l’entrée du microphone :

//-----------------------------------------------------------
// 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 implémente une interface IAudioInputSelector pour encapsuler un multiplexeur, tel que celui du diagramme précédent. (Une interface IAudioOutputSelector encapsule un démultiplexeur.) Dans l’exemple de code précédent, la boucle interne de la fonction SelectCaptureDevice interroge chaque sous-unité qu’elle trouve pour découvrir si la sous-unité est un multiplexeur. Si la sous-unité est un multiplexeur, la fonction appelle la méthode IAudioInputSelector::SetSelection pour sélectionner l’entrée qui se connecte au flux à partir de l’appareil de point de terminaison.

Dans l’exemple de code précédent, chaque itération de la boucle externe traverse une topologie d’appareil. Lors de la traversée des topologies d’appareil dans le diagramme précédent, la première itération traverse le multiplexeur d’entrée et la deuxième itération traverse l’appareil de capture d’ondes. La fonction se termine lorsqu’elle atteint le connecteur sur le bord droit du diagramme. L’arrêt se produit lorsque la fonction détecte un connecteur avec un type de connexion Software_IO. Ce type de connexion identifie le point auquel l’appareil adaptateur se connecte au bus système.

L’appel à la méthode IPart::GetPartType dans l’exemple de code précédent obtient une valeur d’énumération IPartType qui indique si la partie actuelle est un connecteur ou une sous-unité de traitement audio.

La boucle interne de l’exemple de code précédent traverse le lien d’une partie à l’autre en appelant la méthode IPart::EnumPartsOutgoing . (Il existe également une méthode IPart::EnumPartsIncoming pour effectuer un pas à pas dans la direction opposée.) Cette méthode récupère un objet IPartsList qui contient une liste de toutes les parties sortantes. Toutefois, toute partie que la fonction SelectCaptureDevice s’attend à rencontrer dans un appareil de capture aura toujours exactement une partie sortante. Ainsi, l’appel suivant à IPartsList::GetPart demande toujours la première partie de la liste, la partie numéro 0, car la fonction suppose qu’il s’agit de la seule partie de la liste.

Si la fonction SelectCaptureDevice rencontre une topologie pour laquelle cette hypothèse n’est pas valide, la fonction peut ne pas configurer correctement l’appareil. Pour éviter un tel échec, une version plus générale de la fonction peut effectuer les opérations suivantes :

  • Appelez la méthode IPartsList::GetCount pour déterminer le nombre de parties sortantes.
  • Pour chaque partie sortante, appelez IPartsList::GetPart pour commencer à parcourir le chemin d’accès aux données qui mène à partir de la partie.

Certaines parties, mais pas nécessairement toutes, ont des contrôles matériels associés que les clients peuvent définir ou obtenir. Un composant particulier peut avoir zéro, un ou plusieurs contrôles matériels. Un contrôle matériel est représenté par la paire d’interfaces suivante :

  • Une interface de contrôle générique, IControlInterface, qui a des méthodes communes à tous les contrôles matériels.
  • Interface spécifique à une fonction (par exemple, IAudioVolumeLevel) qui expose les paramètres de contrôle pour un type particulier de contrôle matériel (par exemple, un contrôle de volume).

Pour énumérer les contrôles matériels d’un composant, le client appelle d’abord la méthode IPart::GetControlInterfaceCount pour déterminer le nombre de contrôles matériels associés à la partie. Ensuite, le client effectue une série d’appels à la méthode IPart::GetControlInterface pour obtenir l’interface IControlInterface pour chaque contrôle matériel. Enfin, le client obtient l’interface spécifique à la fonction pour chaque contrôle matériel en appelant la méthode IControlInterface::GetIID pour obtenir l’ID d’interface. Le client appelle la méthode IPart::Activate avec cet ID pour obtenir l’interface spécifique à la fonction.

Une partie qui est un connecteur peut prendre en charge l’une des interfaces de contrôle spécifiques aux fonctions suivantes :

Une partie qui est une sous-unité peut prendre en charge une ou plusieurs des interfaces de contrôle spécifiques aux fonctions suivantes :

Un composant prend en charge l’interface IDeviceSpecificProperty uniquement si le contrôle matériel sous-jacent a une valeur de contrôle spécifique au périphérique et que le contrôle ne peut pas être correctement représenté par une autre interface spécifique à une fonction dans la liste précédente. En règle générale, une propriété spécifique à l’appareil est utile uniquement pour un client qui peut déduire la signification de la valeur de la propriété à partir d’informations telles que le type de composant, le sous-type de composant et le nom de la partie. Le client peut obtenir ces informations en appelant les méthodes IPart::GetPartType, IPart::GetSubType et IPart::GetName .

Guide de programmation