Partager via


API Windows 11 pour les objets de traitement audio

Cette rubrique présente un ensemble de nouvelles API Windows 11 pour les objets de traitement audio (APOs) incluses avec un pilote audio.

Windows permet aux fabricants de matériel audio tiers d’inclure des effets de traitement numérique du signal basés sur l’hôte personnalisés. Ces effets sont emballés en tant qu’objets de traitement audio en mode utilisateur (APOs). Pour plus d’informations, veuillez consulter la section Objets de traitement audio Windows.

Certaines des API décrites ici permettent de nouveaux scénarios pour les fournisseurs de matériel indépendants (IHV) et les fournisseurs de logiciels indépendants (ISV), tandis que d’autres API visent à fournir des alternatives qui améliorent la fiabilité audio globale et les capacités de débogage.

  • Le cadre d’annulation d’écho acoustique (AEC) permet à un APO de s’identifier comme un AEC APO, accordant l’accès à un flux de référence et à des contrôles supplémentaires.
  • Le cadre de paramètres permettra aux APO d’exposer des méthodes pour interroger et modifier le magasin de propriétés pour les effets audio (« magasin de propriétés FX ») sur un point de terminaison audio. Lorsque ces méthodes sont implémentées par un APO, elles peuvent être invoquées par des applications de support matériel (HSA) associées à cet APO.
  • Le cadre de notifications permet aux effets audio (APOs) de demander des notifications pour gérer le volume, le point de terminaison et les changements du magasin de propriétés des effets audio.
  • Le framework de journalisation aide au développement et au débogage des APOs.
  • Le cadre de threading permet aux APOs d’être multi-thread en utilisant un pool de threads géré par le système d’exploitation, enregistré MMCSS.
  • Les API de découverte et de contrôle des effets audio permettent au système d’exploitation de détecter, d’activer et de désactiver les effets disponibles pour le traitement sur un flux.

Pour tirer parti de ces nouvelles API, les APO doivent utiliser la nouvelle interface IAudioSystemEffects3. Lorsqu’un APO implémente cette interface, le système d’exploitation l’interprète comme un signal implicite que l’APO prend en charge le cadre de paramètres APO et permet à l’APO de s’abonner aux notifications audio courantes du moteur audio.

Exigences de développement Windows 11 APO CAPX

Tous les nouveaux APO expédiés sur un appareil pour Windows 11 doivent être conformes aux API listées dans ce sujet, validées via HLK. De plus, tous les APO utilisant l’AEC doivent suivre la mise en œuvre décrite dans ce sujet, validée via HLK. Les implémentations personnalisées pour ces extensions de traitement audio de base (paramètres, journalisation, notifications, threading, AEC) doivent utiliser les API CAPX. Cela sera validé via les tests HLK de Windows 11. Par exemple, si un APO utilise des données de registre pour enregistrer des paramètres au lieu d’utiliser le cadre de paramètres, le test HLK associé échouera.

Exigences de version Windows

Les API décrites dans ce sujet sont disponibles à partir de la version 22000 du système d’exploitation Windows 11, WDK et SDK. Windows 10 ne prendra pas en charge ces API. Si un APO a l’intention de fonctionner à la fois sur Windows 10 et Windows 11, il peut examiner s’il est initialisé avec la structure APOInitSystemEffects2 ou APOInitSystemEffects3 pour déterminer s’il fonctionne sur un système d’exploitation qui prend en charge les API CAPX.

Les dernières versions de Windows, WDK et SDK peuvent être téléchargées ci-dessous via le programme Windows Insider. Les partenaires engagés avec Microsoft via Partner Center peuvent également accéder à ce contenu via Collaborate. Pour plus d’informations sur Collaborate, veuillez consulter la section Introduction à Microsoft Collaborate.

Le contenu de Windows 11 WHCP a été mis à jour pour fournir aux partenaires les moyens de valider ces API.

Le code source des exemples décrits dans ce sujet peut être trouvé ici : Audio/SYSVAD/APO - github

Annulation d’écho acoustique (AEC)

L’annulation d’écho acoustique (AEC) est un effet audio courant mis en œuvre par les fournisseurs de matériel indépendants (IHVs) et les fournisseurs de logiciels indépendants (ISVs) en tant qu’objet de traitement audio (APO) dans le pipeline de capture de microphone. Cet effet est différent des autres effets généralement mis en œuvre par les IHVs et les ISVs en ce qu’il nécessite 2 entrées : un flux audio du microphone et un flux audio d’un appareil de rendu qui agit comme le signal de référence.

Ce nouvel ensemble d’interfaces permet à un AEC APO de s’identifier comme tel au moteur audio. Ce faisant, le moteur audio configure l’APO de manière appropriée avec plusieurs entrées et une seule sortie.

Lorsque les nouvelles interfaces AEC sont implémentées par un APO, le moteur audio :

  • Configure l’APO avec une entrée supplémentaire qui fournit à l’APO le flux de référence d’un point de terminaison de rendu approprié.
  • Permute les flux de référence à mesure que l’appareil de rendu change.
  • Permet à un APO de contrôler le format du microphone d’entrée et du flux de référence.
  • Permet à un APO d’obtenir des horodatages sur les flux de microphone et de référence.

Approche précédente : Windows 10

Les APO sont des objets à entrée unique : sortie unique. Le moteur audio fournit un AEC APO l’audio du point de terminaison du microphone à son entrée. Pour obtenir le flux de référence, un APO peut soit interagir avec le pilote en utilisant des interfaces propriétaires pour récupérer l’audio de référence du point de terminaison de rendu, soit utiliser WASAPI pour ouvrir un flux de boucle sur le point de terminaison de rendu.

Les deux approches ci-dessus présentent des inconvénients :

  • Un AEC APO qui utilise des canaux privés pour obtenir un flux de référence du pilote peut généralement le faire uniquement à partir de l’appareil de rendu audio intégré. En conséquence, l’annulation de l’écho ne fonctionnera pas si l’utilisateur diffuse de l’audio à partir d’un appareil non intégré tel qu’un appareil audio USB ou Bluetooth. Seul le système d’exploitation est au courant des bons points de terminaison de rendu qui peuvent servir de points de terminaison de référence.

  • Un APO peut utiliser WASAPI pour choisir le point de terminaison de rendu par défaut pour effectuer l’annulation de l’écho. Cependant, il existe certains pièges à éviter lors de l’ouverture d’un flux de boucle à partir du processus audiodg.exe (qui est l’endroit où l’APO est hébergé).

    • Le flux de boucle ne peut pas être ouvert/détruit lorsque le moteur audio appelle les méthodes principales de l’APO, car cela peut entraîner un blocage.
    • Un APO de capture ne connaît pas l’état des flux de ses clients. C’est-à-dire qu’une application de capture pourrait avoir un flux de capture dans l’état « STOP », mais l’APO n’est pas au courant de cet état et garde donc le flux de boucle ouvert dans l’état « RUN », ce qui est inefficace en termes de consommation d’énergie.

Définition de l’API : AEC

Le cadre AEC fournit de nouvelles structures et interfaces que les APO peuvent utiliser. Ces nouvelles structures et interfaces sont décrites ci-dessous.

Structure APO_CONNECTION_PROPERTY_V2

Les APO qui implémentent l’interface IApoAcousticEchoCancellation recevront une structure APO_CONNECTION_PROPERTY_V2 dans son appel à IAudioProcessingObjectRT::APOProcess. En plus de tous les champs de la structure APO_CONNECTION_PROPERTY, la version 2 de la structure fournit également des informations sur les horodatages pour les tampons audio.

Un APO peut examiner le champ APO_CONNECTION_PROPERTY.u32Signature pour déterminer si la structure qu’il reçoit du moteur audio est de type APO_CONNECTION_PROPERTY ou APO_CONNECTION_PROPERTY_V2. Les structures APO_CONNECTION_PROPERTY ont une signature APO_CONNECTION_PROPERTY_SIGNATURE, tandis que les APO_CONNECTION_PROPERTY_V2 ont une signature égale à APO_CONNECTION_PROPERTY_V2_SIGNATURE. Si la signature a une valeur égale à APO_CONNECTION_PROPERTY_V2_SIGNATURE, le pointeur vers la structure APO_CONNECTION_PROPERTY peut être converti en toute sécurité en un pointeur APO_CONNECTION_PROPERTY_V2.

Le code suivant est extrait de l’exemple Aec APO MFX - AecApoMfx.cpp et montre le recast.

    if (ppInputConnections[0]->u32Signature == APO_CONNECTION_PROPERTY_V2_SIGNATURE)
    {
        const APO_CONNECTION_PROPERTY_V2* connectionV2 = reinterpret_cast<const APO_CONNECTION_PROPERTY_V2*>(ppInputConnections[0]);
    }

IApoAcousticEchoCancellation

L’interface IApoAcousticEchoCancellation n’a pas de méthodes explicites. Son objectif est d’identifier un AEC APO au moteur audio. Cette interface ne peut être implémentée que par des effets de mode (MFX) sur les points de terminaison de capture. Implémenter cette interface sur tout autre APO entraînera un échec de chargement de cet APO. Pour des informations générales sur les MFX, veuillez consulter la section Architecture des objets de traitement audio.

Si l’effet de mode sur un point de terminaison de capture est implémenté comme une série d’APOs enchaînés, seul l’APO le plus proche de l’appareil peut implémenter cette interface. Les APO qui implémentent cette interface se verront proposer la structure APO_CONNECTION_PROPERTY_V2 dans son appel à IAudioProcessingobjectRT::APOProcess. L’APO peut vérifier une signature APO_CONNECTION_PROPERTY_V2_SIGNATURE sur la propriété de connexion et convertir la structure APO_CONNECTION_PROPERTY entrante en une structure APO_CONNECTION_PROPERTY_V2.

En reconnaissance du fait que les AEC APO exécutent généralement leurs algorithmes à une fréquence d’échantillonnage spécifique et à un nombre de canaux spécifiques, le moteur audio fournit une prise en charge du rééchantillonnage aux APO qui implémentent l’interface IApoAcousticEchoCancellation.

Lorsqu’un AEC APO renvoie APOERR_FORMAT_NOT_SUPPORTED dans l’appel à IAudioProcessingObject::OutInputFormatSupported, le moteur audio appellera IAudioProcessingObject::IsInputFormatSupported sur l’APO à nouveau avec un format de sortie NULL et un format d’entrée non null, pour obtenir le format suggéré par l’APO. Le moteur audio rééchantillonnera ensuite l’audio du microphone au format suggéré avant de l’envoyer à l’AEC APO. Cela élimine le besoin pour l’AEC APO de mettre en œuvre la conversion de fréquence d’échantillonnage et de nombre de canaux.

IApoAuxiliaryInputConfiguration

L’interface IApoAuxiliaryInputConfiguration fournit des méthodes que les APO peuvent implémenter afin que le moteur audio puisse ajouter et supprimer des flux d’entrée auxiliaires.

Cette interface est implémentée par l’AEC APO et utilisée par le moteur audio pour initialiser l’entrée de référence. Dans Windows 11, l’AEC APO ne sera initialisé qu’avec une seule entrée auxiliaire - celle qui contient le flux audio de référence pour l’annulation d’écho. La méthode AddAuxiliaryInput sera utilisée pour ajouter l’entrée de référence à l’APO. Les paramètres d’initialisation contiendront une référence au point de terminaison de rendu à partir duquel le flux de boucle est obtenu.

La méthode IsInputFormatSupported est appelée par le moteur audio pour négocier les formats sur l’entrée auxiliaire. Si l’AEC APO préfère un format spécifique, il peut renvoyer S_FALSE dans l’appel à IsInputFormatSupported et spécifier un format suggéré. Le moteur audio rééchantillonnera l’audio de référence au format suggéré et le fournira à l’entrée auxiliaire de l’AEC APO.

IApoAuxiliaryInputRT

L’interface IApoAuxiliaryInputRT est l’interface sécurisée en temps réel utilisée pour piloter les entrées auxiliaires d’un APO.

Cette interface est utilisée pour fournir des données audio sur l’entrée auxiliaire à l’APO. Notez que les entrées audio auxiliaires ne sont pas synchronisées avec les appels à IAudioProcessingObjectRT::APOProcess. Lorsqu’il n’y a pas d’audio rendu au point de terminaison de rendu, les données de boucle ne seront pas disponibles à l’entrée auxiliaire. C’est-à-dire qu’il n’y aura pas d’appels à IApoAuxiliaryInputRT::AcceptInput.

Résumé des API AEC CAPX

Pour plus d’informations, consultez les pages suivantes.

Exemple de code : AEC

Consultez les exemples de code Sysvad Audio AecApo suivants.

Le code suivant est extrait de l’exemple d’en-tête Aec APO : AecAPO.h et montre les trois nouvelles méthodes publiques ajoutées.

 public IApoAcousticEchoCancellation,
 public IApoAuxiliaryInputConfiguration,
 public IApoAuxiliaryInputRT

...

 COM_INTERFACE_ENTRY(IApoAcousticEchoCancellation)
 COM_INTERFACE_ENTRY(IApoAuxiliaryInputConfiguration)
 COM_INTERFACE_ENTRY(IApoAuxiliaryInputRT)

...


    // IAPOAuxiliaryInputConfiguration
    STDMETHOD(AddAuxiliaryInput)(
        DWORD dwInputId,
        UINT32 cbDataSize,
        BYTE *pbyData,
        APO_CONNECTION_DESCRIPTOR *pInputConnection
        ) override;
    STDMETHOD(RemoveAuxiliaryInput)(
        DWORD dwInputId
        ) override;
    STDMETHOD(IsInputFormatSupported)(
        IAudioMediaType* pRequestedInputFormat,
        IAudioMediaType** ppSupportedInputFormat
        ) override;
...

    // IAPOAuxiliaryInputRT
    STDMETHOD_(void, AcceptInput)(
        DWORD dwInputId,
        const APO_CONNECTION_PROPERTY *pInputConnection
        ) override;

    // IAudioSystemEffects3
    STDMETHODIMP GetControllableSystemEffectsList(_Outptr_result_buffer_maybenull_(*numEffects) AUDIO_SYSTEMEFFECT** effects, _Out_ UINT* numEffects, _In_opt_ HANDLE event) override
    {
        UNREFERENCED_PARAMETER(effects);
        UNREFERENCED_PARAMETER(numEffects);
        UNREFERENCED_PARAMETER(event);
        return S_OK; 
    }

Le code suivant est extrait de l’exemple Aec APO MFX : AecApoMfx.cpp et montre l’implémentation de AddAuxiliaryInput, lorsque l’APO ne peut gérer qu’une seule entrée auxiliaire.

STDMETHODIMP
CAecApoMFX::AddAuxiliaryInput(
    DWORD dwInputId,
    UINT32 cbDataSize,
    BYTE *pbyData,
    APO_CONNECTION_DESCRIPTOR * pInputConnection
)
{
    HRESULT hResult = S_OK;

    CComPtr<IAudioMediaType> spSupportedType;
    ASSERT_NONREALTIME();

    IF_TRUE_ACTION_JUMP(m_bIsLocked, hResult = APOERR_APO_LOCKED, Exit);
    IF_TRUE_ACTION_JUMP(!m_bIsInitialized, hResult = APOERR_NOT_INITIALIZED, Exit);

    BOOL bSupported = FALSE;
    hResult = IsInputFormatSupportedForAec(pInputConnection->pFormat, &bSupported);
    IF_FAILED_JUMP(hResult, Exit);
    IF_TRUE_ACTION_JUMP(!bSupported, hResult = APOERR_FORMAT_NOT_SUPPORTED, Exit);

    // This APO can only handle 1 auxiliary input
    IF_TRUE_ACTION_JUMP(m_auxiliaryInputId != 0, hResult = APOERR_NUM_CONNECTIONS_INVALID, Exit);

    m_auxiliaryInputId = dwInputId;

Examinez également le code d’exemple qui montre l’implémentation de CAecApoMFX::IsInputFormatSupported et CAecApoMFX::AcceptInput ainsi que la gestion de APO_CONNECTION_PROPERTY_V2.

Séquence d’opérations : AEC

Lors de l’initialisation :

  1. IAudioProcessingObject::Initialize
  2. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  3. IAudioProcessingObjectConfiguration:: LockForProcess
  4. IAudioProcessingObjectConfiguration ::UnlockForProcess
  5. IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput

Lors du changement d’appareil de rendu :

  1. IAudioProcessingObject::Initialize
  2. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  3. IAudioProcessingObjectConfiguration::LockForProcess
  4. Changements de l’appareil par défaut
  5. IAudioProcessingObjectConfiguration::UnlockForProcess
  6. IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
  7. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  8. IAudioProcessingObjectConfiguration::LockForProcess

Voici le comportement de tampon recommandé pour l’AEC.

  • Les tampons obtenus lors de l’appel à IApoAuxiliaryInputRT::AcceptInput doivent être écrits dans un tampon circulaire sans verrouiller le thread principal.
  • Lors de l’appel à IAudioProcessingObjectRT::APOProcess, le tampon circulaire doit être lu pour obtenir le dernier paquet audio du flux de référence, et ce paquet doit être utilisé pour exécuter l’algorithme d’annulation d’écho.
  • Les horodatages sur les données de référence et de microphone peuvent être utilisés pour aligner les données du haut-parleur et du microphone.

Flux de boucle de référence

Par défaut, le flux de boucle « écoute » le flux audio avant que le volume ou la mise en sourdine ne soit appliqué. Un flux de boucle tapé avant que le volume ne soit appliqué est connu sous le nom de flux de boucle pré-volume. Un avantage d’avoir un flux de boucle pré-volume est un flux audio clair et uniforme, quel que soit le réglage actuel du volume.

Certains algorithmes AEC peuvent préférer obtenir un flux de boucle après tout traitement de volume (y compris la mise en sourdine). Cette configuration est connue sous le nom de flux de boucle post-volume.

Dans la prochaine version majeure de Windows, les AEC APO peuvent demander une boucle post-volume sur les points de terminaison pris en charge.

Limites

Contrairement aux flux de boucle pré-volume, qui sont disponibles pour tous les points de terminaison de rendu, les flux de boucle post-volume peuvent ne pas être disponibles sur tous les points de terminaison.

Demande de boucle post-volume

Les AEC APO souhaitant utiliser une boucle post-volume doivent implémenter l’interface IApoAcousticEchoCancellation2.

Un AEC APO peut demander une boucle post-volume en renvoyant le drapeau APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK via le paramètre Properties dans son implémentation de IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties.

En fonction du point de terminaison de rendu actuellement utilisé, la boucle post-volume peut ne pas être disponible. Un AEC APO est informé si une boucle post-volume est utilisée lorsque sa méthode IApoAuxiliaryInputConfiguration::AddAuxiliaryInput est appelée. Si le champ AcousticEchoCanceller_Reference_Input streamProperties contient APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK, la boucle post-volume est utilisée.

Le code suivant extrait de l’exemple d’en-tête AEC APO - AecAPO.h montre les trois nouvelles méthodes publiques ajoutées.

public:
  // IApoAcousticEchoCancellation2
  STDMETHOD(GetDesiredReferenceStreamProperties)(
    _Out_ APO_REFERENCE_STREAM_PROPERTIES * properties) override;

  // IApoAuxiliaryInputConfiguration
  STDMETHOD(AddAuxiliaryInput)(
    DWORD dwInputId,
    UINT32 cbDataSize,
    _In_ BYTE* pbyData,
    _In_ APO_CONNECTION_DESCRIPTOR *pInputConnection
    ) override;

L’extrait de code suivant est extrait de l’exemple AEC APO MFX - AecApoMfx.cpp et montre l’implémentation de GetDesiredReferenceStreamProperties, et la partie pertinente de AddAuxiliaryInput.

STDMETHODIMP SampleApo::GetDesiredReferenceStreamProperties(
  _Out_ APO_REFERENCE_STREAM_PROPERTIES * properties)
{
  RETURN_HR_IF_NULL(E_INVALIDARG, properties);

  // Always request that a post-volume loopback stream be used, if
  // available. We will find out which type of stream was actually
  // created when AddAuxiliaryInput is invoked.
  *properties = APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK;
  return S_OK;
}

STDMETHODIMP
CAecApoMFX::AddAuxiliaryInput(
    DWORD dwInputId,
    UINT32 cbDataSize,
    BYTE *pbyData,
    APO_CONNECTION_DESCRIPTOR * pInputConnection
)
{
   // Parameter checking skipped for brevity, please see sample for 
   // full implementation.

  AcousticEchoCanceller_Reference_Input* referenceInput = nullptr;
  APOInitSystemEffects3* papoSysFxInit3 = nullptr;

  if (cbDataSize == sizeof(AcousticEchoCanceller_Reference_Input))
  {
    referenceInput = 
      reinterpret_cast<AcousticEchoCanceller_Reference_Input*>(pbyData);

    if (WI_IsFlagSet(
          referenceInput->streamProperties,
          APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK))
    {
      // Post-volume loopback is being used.
      m_bUsingPostVolumeLoopback = TRUE;
        
      // Note that we can get to the APOInitSystemEffects3 from     
      // AcousticEchoCanceller_Reference_Input.
      papoSysFxInit3 = (APOInitSystemEffects3*)pbyData;
    }
    else  if (cbDataSize == sizeof(APOInitSystemEffects3))
    {
      // Post-volume loopback is not supported.
      papoSysFxInit3 = (APOInitSystemEffects3*)pbyData;
    }

    // Remainder of method skipped for brevity.

Cadre de paramètres

Le cadre de paramètres permet aux APO d’exposer des méthodes pour interroger et modifier le magasin de propriétés pour les effets audio (« magasin de propriétés FX ») sur un point de terminaison audio. Ce cadre peut être utilisé par les APO et par les applications de support matériel (HSA) souhaitant communiquer des paramètres à cet APO. Les HSA peuvent être des applications Universal Windows Platform (UWP) et nécessitent une capacité spéciale pour invoquer les API dans le cadre de paramètres. Pour plus d’informations sur les applications HSA, veuillez consulter la section applications UWP pour appareils.

Structure du magasin de propriétés Fx

Le nouveau magasin de propriétés Fx a trois sous-magasins : par défaut, utilisateur et volatile.

La sous-clé « Par défaut » contient les propriétés des effets personnalisés et est remplie à partir du fichier INF. Ces propriétés ne persistent pas à travers les mises à niveau de l’OS. Par exemple, les propriétés généralement définies dans un INF se trouveraient ici. Celles-ci seraient ensuite republiées à partir de l’INF.

La sous-clé « Utilisateur » contient les paramètres utilisateur concernant les propriétés des effets. Ces paramètres sont rendus persistants par l’OS à travers les mises à niveau et les migrations. Par exemple, tous les préréglages que l’utilisateur peut configurer et qui sont censés persister après la mise à niveau.

La sous-clé « Volatile » contient les propriétés des effets volatiles. Ces propriétés sont perdues lors du redémarrage de l’appareil et sont effacées chaque fois que le point de terminaison passe à l’état actif. Elles sont censées contenir des propriétés variant dans le temps (par exemple, en fonction des applications en cours d’exécution, de la posture de l’appareil, etc.). Par exemple, tous les paramètres dépendants de l’environnement actuel.

La façon de penser aux propriétés utilisateur par rapport aux propriétés par défaut est de savoir si vous souhaitez que les propriétés persistent à travers les mises à niveau de l’OS et du pilote. Les propriétés utilisateur seront rendues persistantes. Les propriétés par défaut seront republiées à partir de l’INF.

Contextes APO

Le cadre de paramètres CAPX permet à un auteur APO de regrouper les propriétés APO par contextes. Chaque APO peut définir son propre contexte et mettre à jour les propriétés relatives à son propre contexte. Le magasin de propriétés des effets pour un point de terminaison audio peut avoir zéro ou plusieurs contextes. Les fournisseurs sont libres de créer des contextes comme ils le souhaitent, que ce soit par SFX/MFX/EFX ou par mode. Un fournisseur peut également choisir d’avoir un seul contexte pour tous les APO fournis par ce fournisseur.

Capacité restreinte des paramètres

L’API des paramètres est destinée à prendre en charge tous les OEM et développeurs HSA intéressés par l’interrogation et la modification des paramètres des effets audio associés à un appareil audio. Cette API est exposée à une application HSA et aux applications Win32 pour fournir un accès au magasin de propriétés via la capacité restreinte « audioDeviceConfiguration » qui doit être déclarée dans le manifeste. De plus, un espace de noms correspondant doit être déclaré comme suit :

<Package
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  IgnorableNamespaces="uap mp rescap">
  ...
 
  <Capabilities>
    <rescap:Capability Name="audioDeviceConfiguration" />
  </Capabilities>
</Package>

L’interface IAudioSystemEffectsPropertyStore est lisible et modifiable par un service ISV/IHV, une application UWP store, des applications de bureau non administrateur et des APO. De plus, cela peut agir comme le mécanisme permettant aux APO de livrer des messages à un service ou une application UWP store.

Remarque

Ceci est une capacité restreinte : Si une application est soumise avec cette capacité au Microsoft Store, elle déclenchera un examen minutieux. L’application doit être une application de support matériel (HSA), et elle sera examinée pour évaluer qu’elle est effectivement une HSA avant l’approbation de la soumission.

Définition de l’API : Cadre de paramètres

La nouvelle interface IAudioSystemEffectsPropertyStore permet à une HSA d’accéder aux magasins de propriétés des effets audio système et de s’inscrire pour recevoir des notifications de changement de propriété.

La fonction ActiveAudioInterfaceAsync fournit une méthode pour obtenir l’interface IAudioSystemEffectsPropertyStore de manière asynchrone.

Une application peut recevoir des notifications lorsque le magasin de propriétés des effets système change, en utilisant la nouvelle interface de rappel IAudioSystemEffectsPropertyChangeNotificationClient.

Application essayant d’obtenir IAudioSystemEffectsPropertyStore en utilisant IMMDevice::Activate

L’exemple démontre comment une application de support matériel peut utiliser IMMDevice::Activate pour activer IAudioSystemEffectsPropertyStore. L’exemple montre comment utiliser IAudioSystemEffectsPropertyStore pour ouvrir un IPropertyStore qui contient des paramètres utilisateur.

#include <mmdeviceapi.h>

// This function opens an IPropertyStore with user settings on the specified IMMDevice.
// Input parameters:
// device - IMMDevice object that identifies the audio endpoint.
// propertyStoreContext - GUID that identifies the property store. Each APO can have its own GUID. These 
// GUIDs are chosen by the audio driver at installation time.
HRESULT GetPropertyStoreFromMMDevice(_In_ IMMDevice* device,
    REFGUID propertyStoreContext,
    _COM_Outptr_ IPropertyStore** userPropertyStore)
{
    *userPropertyStore = nullptr;

    wil::unique_prop_variant activationParam;
    RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));

    wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effectsPropertyStore;
    RETURN_IF_FAILED(device->Activate(__uuidof(effectsPropertyStore), CLSCTX_INPROC_SERVER, activationParam.addressof(), effectsPropertyStore.put_void()));

    RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, userPropertyStore));
    return S_OK;
}

Exemple utilisant ActivateAudioInterfaceAsync

Cet exemple fait la même chose que l’exemple précédent, mais au lieu d’utiliser IMMDevice, il utilise l’API ActivateAudioInterfaceAsync pour obtenir l’interface IAudioSystemEffectsPropertyStore de manière asynchrone.

include <mmdeviceapi.h>

class PropertyStoreHelper : 
    public winrt::implements<PropertyStoreHelper, IActivateAudioInterfaceCompletionHandler>
{
public:
    wil::unique_event_nothrow m_asyncOpCompletedEvent;

    HRESULT GetPropertyStoreAsync(
        _In_ PCWSTR deviceInterfacePath,
        REFGUID propertyStoreContext,
        _COM_Outptr_ IActivateAudioInterfaceAsyncOperation** operation);

    HRESULT GetPropertyStoreResult(_COM_Outptr_ IPropertyStore** userPropertyStore);

    // IActivateAudioInterfaceCompletionHandler
    STDMETHOD(ActivateCompleted)(_In_ IActivateAudioInterfaceAsyncOperation *activateOperation);

private:
    wil::com_ptr_nothrow<IPropertyStore> m_userPropertyStore;
    HRESULT m_hrAsyncOperationResult = E_FAIL;

    HRESULT GetUserPropertyStore(
        _In_ IActivateAudioInterfaceAsyncOperation* operation,
        _COM_Outptr_ IPropertyStore** userPropertyStore);
};

// This function opens an IPropertyStore with user settings asynchronously on the specified audio endpoint.
// Input parameters:
// deviceInterfacePath - the Device Interface Path string that identifies the audio endpoint. Can be 
// obtained from Windows.Devices.Enumeration.DeviceInformation.
// propertyStoreContext - GUID that identifies the property store. Each APO can have its own GUID. These 
// GUIDs are chosen by the audio driver at installation time.
//
// The function returns an IActivateAudioInterfaceAsyncOperation, which can be used to check the result of
// the asynchronous operation.
HRESULT PropertyStoreHelper::GetPropertyStoreAsync(
    _In_ PCWSTR deviceInterfacePath,
    REFGUID propertyStoreContext,
    _COM_Outptr_ IActivateAudioInterfaceAsyncOperation** operation)
{
    *operation = nullptr;

    wil::unique_prop_variant activationParam;
    RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));

    RETURN_IF_FAILED(ActivateAudioInterfaceAsync(deviceInterfacePath,
        __uuidof(IAudioSystemEffectsPropertyStore),
        activationParam.addressof(),
        this,
        operation));
    return S_OK;
}

// Once the IPropertyStore is available, the app can call this function to retrieve it.
// (The m_asyncOpCompletedEvent event is signaled when the asynchronous operation to retrieve
// the IPropertyStore has completed.)
HRESULT PropertyStoreHelper::GetPropertyStoreResult(_COM_Outptr_ IPropertyStore** userPropertyStore)
{
    *userPropertyStore = nullptr;

    // First check if the asynchronous operation completed. If it failed, the error code
    // is stored in the m_hrAsyncOperationResult variable.
    RETURN_IF_FAILED(m_hrAsyncOperationResult);

    RETURN_IF_FAILED(m_userPropertyStore.copy_to(userPropertyStore));
    return S_OK;
}

// Implementation of IActivateAudioInterfaceCompletionHandler::ActivateCompleted.
STDMETHODIMP PropertyStoreHelper::ActivateCompleted(_In_ IActivateAudioInterfaceAsyncOperation* operation)
{
    m_hrAsyncOperationResult = GetUserPropertyStore(operation, m_userPropertyStore.put());

    // Always signal the event that our caller might be waiting on before we exit,
    // even in case of failure.
    m_asyncOpCompletedEvent.SetEvent();
    return S_OK;
}

HRESULT PropertyStoreHelper::GetUserPropertyStore(
    _In_ IActivateAudioInterfaceAsyncOperation* operation,
    _COM_Outptr_ IPropertyStore** userPropertyStore)
{
    *userPropertyStore = nullptr;

    // Check if the asynchronous operation completed successfully, and retrieve an
    // IUnknown pointer to the result.
    HRESULT hrActivateResult;
    wil::com_ptr_nothrow<IUnknown> audioInterfaceUnknown;
    RETURN_IF_FAILED(operation->GetActivateResult(&hrActivateResult, audioInterfaceUnknown.put()));
    RETURN_IF_FAILED(hrActivateResult);

    // Convert the result to IAudioSystemEffectsPropertyStore
    wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effctsPropertyStore;
    RETURN_IF_FAILED(audioInterfaceUnknown.query_to(&effectsPropertyStore));

    // Open an IPropertyStore with the user settings.
    RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, userPropertyStore));
    return S_OK;
}

IAudioProcessingObject::Initialize code utilisant IAudioSystemEffectsPropertyStore

L’exemple démontre comment l’implémentation d’un APO peut utiliser la structure APOInitSystemEffects3 pour récupérer les interfaces IPropertyStore utilisateur, par défaut et volatiles pour l’APO, lors de l’initialisation de l’APO.

#include <audioenginebaseapo.h>

// Partial implementation of APO to show how an APO that implements IAudioSystemEffects3 can handle
// being initialized with the APOInitSystemEffects3 structure.
class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
    IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
public:
    // IAudioProcessingObject
    STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);

    // Implementation of IAudioSystemEffects2, IAudioSystemEffects3 has been omitted from this sample for brevity.  

private:

    wil::com_ptr_nothrow<IPropertyStore> m_defaultStore;
    wil::com_ptr_nothrow<IPropertyStore> m_userStore;
    wil::com_ptr_nothrow<IPropertyStore> m_volatileStore;

    // Each APO has its own private collection of properties. The collection is identified through a
    // a property store context GUID, which is defined below and in the audio driver INF file.
    const GUID m_propertyStoreContext = ...;
};

// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
    if (cbDataSize == sizeof(APOInitSystemEffects3))
    {
        // SampleApo supports the new IAudioSystemEffects3 interface so it will receive APOInitSystemEffects3
        // in pbyData if the audio driver has declared support for this.

        // Use IMMDevice to activate IAudioSystemEffectsPropertyStore that contains the default, user and
        // volatile settings.
        IMMDeviceCollection* deviceCollection = 
            reinterpret_cast<APOInitSystemEffects3*>(pbyData)->pDeviceCollection;
        if (deviceCollection != nullptr)
        {
            UINT32 numDevices;
            wil::com_ptr_nothrow<IMMDevice> endpoint;

            // Get the endpoint on which this APO has been created
            // (It is the last device in the device collection)
            if (SUCCEEDED(deviceCollection->GetCount(&numDevices)) &&
                numDevices > 0 &&
                SUCCEEDED(deviceCollection->Item(numDevices - 1, &endpoint)))
            {
                wil::unique_prop_variant activationParam;
                RETURN_IF_FAILED(InitPropVariantFromCLSID(m_propertyStoreContext, &activationParam));

                wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effectsPropertyStore;
                RETURN_IF_FAILED(endpoint->Activate(__uuidof(effectsPropertyStore), CLSCTX_ALL, &activationParam, effectsPropertyStore.put_void()));

                // Read default, user and volatile property values to set up initial operation of the APO
                RETURN_IF_FAILED(effectsPropertyStore->OpenDefaultPropertyStore(STGM_READWRITE, m_defaultStore.put()));
                RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, m_userStore.put()));
                RETURN_IF_FAILED(effectsPropertyStore->OpenVolatilePropertyStore(STGM_READWRITE, m_volatileStore.put()));

                // At this point the APO can read and write settings in the various property stores,
                // as appropriate. (Not shown.)
                // Note that APOInitSystemEffects3 contains all the members of APOInitSystemEffects2,
                // so an APO that knows how to initialize from APOInitSystemEffects2 can use the same
                // code to continue its initialization here.
            }
        }
    }
    else if (cbDataSize == sizeof(APOInitSystemEffects2))
    {
        // Use APOInitSystemEffects2 for the initialization of the APO.
        // If we get here, the audio driver did not declare support for IAudioSystemEffects3.
    }
    else if (cbDataSize == sizeof(APOInitSystemEffects))
    {
        // Use APOInitSystemEffects for the initialization of the APO.
    }

    return S_OK;
}

Application s’inscrivant pour des notifications de changement de propriété

L’exemple démontre l’utilisation de l’inscription pour les notifications de changement de propriété. Cela ne doit pas être utilisé depuis l’APO et doit être utilisé par les applications Win32.

class PropertyChangeNotificationClient : public 
    winrt::implements<PropertyChangeNotificationClient, IAudioSystemEffectsPropertyChangeNotificationClient>
{
private:
    wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> m_propertyStore;
    bool m_isListening = false;

public:
    HRESULT OpenPropertyStoreOnDefaultRenderEndpoint(REFGUID propertyStoreContext);
    HRESULT StartListeningForPropertyStoreChanges();
    HRESULT StopListeningForPropertyStoreChanges();

    // IAudioSystemEffectsPropertyChangeNotificationClient
    STDMETHOD(OnPropertyChanged)(AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE type, const PROPERTYKEY key);
};

// Open the IAudioSystemEffectsPropertyStore. This should be the first method invoked on this class.
HRESULT PropertyChangeNotificationClient::OpenPropertyStoreOnDefaultRenderEndpoint(
    REFGUID propertyStoreContext)
{
    wil::com_ptr_nothrow<IMMDeviceEnumerator> deviceEnumerator;
    RETURN_IF_FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator)));

    wil::com_ptr_nothrow<IMMDevice> device;
    RETURN_IF_FAILED(deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.put()));

    wil::unique_prop_variant activationParam;
    RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));

    RETURN_IF_FAILED(device->Activate(__uuidof(m_propertyStore), CLSCTX_INPROC_SERVER,
        &activationParam, m_propertyStore.put_void()));
    return S_OK;
}

// Start subscribing to callbacks that are invoked when there are changes to any of the IPropertyStores
// that are managed by IAudioSystemEffectsPropertyStore.
// The OpenPropertyStoreOnDefaultRenderEndpoint should have been invoked prior to invoking this function.
HRESULT PropertyChangeNotificationClient::StartListeningForPropertyStoreChanges()
{
    RETURN_HR_IF(E_FAIL, !m_propertyStore);
    RETURN_IF_FAILED(m_propertyStore->RegisterPropertyChangeNotification(this));
    m_isListening = true;
    return S_OK;
}

// Unsubscribe to event callbacks. Since IAudioSystemEffectsPropertyStore takes a reference on our
// PropertyChangeNotificationClient class, it is important that this method is invoked prior to cleanup,
// to break the circular reference.
HRESULT PropertyChangeNotificationClient::StopListeningForPropertyStoreChanges()
{
    if (m_propertyStore != nullptr && m_isListening)
    {
        RETURN_IF_FAILED(m_propertyStore->UnregisterPropertyChangeNotification(this));
        m_isListening = false;
    }
    return S_OK;
}

// Callback method that gets invoked when there have been changes to any of the IPropertyStores
// that are managed by IAudioSystemEffectsPropertyStore. Note that calls to 
// IAudioSystemEffectsPropertyChangeNotificationClient are not marshalled across COM apartments.
// Therefore, the OnPropertyChanged is most likely invoked on a different thread than the one used when
// invoking RegisterPropertyChangeNotification. If necessary, concurrent access to shared state should be
// protected with a critical section. 
STDMETHODIMP PropertyChangeNotificationClient::OnPropertyChanged(AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE type, const PROPERTYKEY key)
{
    if (type == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_USER)
    {
        // Handle changes to the User property store.

        wil::com_ptr_nothrow<IPropertyStore> userPropertyStore;
        RETURN_IF_FAILED(m_propertyStore->OpenUserPropertyStore(STGM_READ, userPropertyStore.put()));

        // Here we can call IPropertyStore::GetValue to read the current value of PROPERTYKEYs that we are
        // interested in.
    }
    else if (type == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_VOLATILE)
    {
        // Handle changes to the Volatile property store, if desired
    }

    return S_OK;
}

Exemple de code : Cadre de paramètres

Cet exemple de code provient de l’exemple SFX Swap APO : SwapAPOSFX.cpp.

// SampleApo supports the new IAudioSystemEffects3 interface so it will receive APOInitSystemEffects3
// in pbyData if the audio driver has declared support for this.

// Use IMMDevice to activate IAudioSystemEffectsPropertyStore that contains the default, user and
// volatile settings.
IMMDeviceCollection* deviceCollection = reinterpret_cast<APOInitSystemEffects3*>(pbyData)->pDeviceCollection;
if (deviceCollection != nullptr)
{
    UINT32 numDevices;
    wil::com_ptr_nothrow<IMMDevice> endpoint;

    // Get the endpoint on which this APO has been created
    // (It is the last device in the device collection)
    if (SUCCEEDED(deviceCollection->GetCount(&numDevices)) && numDevices > 0 &&
        SUCCEEDED(deviceCollection->Item(numDevices - 1, &endpoint)))
    {
        wil::unique_prop_variant activationParam;
        hr = InitPropVariantFromCLSID(SWAP_APO_SFX_CONTEXT, &activationParam);
        IF_FAILED_JUMP(hr, Exit);

        wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effectsPropertyStore;
        hr = endpoint->Activate(__uuidof(effectsPropertyStore), CLSCTX_ALL, &activationParam, effectsPropertyStore.put_void());
        IF_FAILED_JUMP(hr, Exit);

        // This is where an APO might want to open the volatile or default property stores as well 
        // Use STGM_READWRITE if IPropertyStore::SetValue is needed.
        hr = effectsPropertyStore->OpenUserPropertyStore(STGM_READ, m_userStore.put());
        IF_FAILED_JUMP(hr, Exit);
    }
}

Section INF : Cadre de paramètres

La syntaxe du fichier INF pour déclarer les propriétés des effets en utilisant le nouveau cadre de paramètres CAPX est la suivante :

HKR, FX\0\{ApoContext}\{Default|User}, %CUSTOM_PROPERTY_KEY%,,,

Cela remplace l’ancienne syntaxe pour déclarer les propriétés des effets comme suit :

# Old way of declaring FX properties
HKR, FX\0, %CUSTOM_PROPERTY_KEY_1%,,,

L’INF ne peut pas avoir à la fois l’entrée IAudioSystemEffectsPropertyStore et l’entrée IPropertyStore pour le même point de terminaison audio. Cela n’est pas pris en charge.

Exemple montrant l’utilisation du nouveau magasin de propriétés :

HKR,FX\0\%SWAP_APO_CONTEXT%,%PKEY_FX_Association%,,%KSNODETYPE_ANY%
; Enable the channel swap in the APO
HKR,FX\0\%SWAP_APO_CONTEXT%\User,%PKEY_Endpoint_Enable_Channel_Swap_SFX%,REG_DWORD,0x1

PKEY_Endpoint_Enable_Channel_Swap_SFX = "{A44531EF-5377-4944-AE15-53789A9629C7},2"
REG_DWORD = 0x00010001 ; FLG_ADDREG_TYPE_DWORD
SWAP_APO_CONTEXT = "{24E7F619-5B33-4084-9607-878DA8722417}"
PKEY_FX_Association  = "{D04E05A6-594B-4FB6-A80D-01AF5EED7D1D},0"
KSNODETYPE_ANY   = "{00000000-0000-0000-0000-000000000000}"

Cadre de notifications

Le cadre de notifications permet aux effets audio (APOs) de demander et de gérer les notifications de changement de volume, de point de terminaison et de magasin de propriétés des effets audio. Ce cadre est destiné à remplacer les API existantes utilisées par les APO pour s’inscrire et se désinscrire des notifications.

La nouvelle API introduit une interface que les APO peuvent utiliser pour déclarer le type de notifications qui intéressent l’APO. Windows interrogera l’APO sur les notifications qui l’intéressent et transmettra la notification aux APOs. Les APO n’ont plus besoin d’appeler explicitement les API d’inscription ou de désinscription.

Les notifications sont délivrées à un APO en utilisant une file d’attente en série. Le cas échéant, la première notification diffuse l’état initial de la valeur demandée (par exemple, le volume du point de terminaison audio). Les notifications cessent une fois qu’audiodg.exe cesse d’utiliser un APO pour le streaming. Les APO cesseront de recevoir des notifications après UnlockForProcess. Il est toujours nécessaire de synchroniser UnlockForProcess et les notifications embarquées.

Implémentation : Cadre de notifications

Pour tirer parti du cadre de notifications, un APO déclare les notifications qui l’intéressent. Il n’y a pas d’appels explicites d’inscription/désinscription. Toutes les notifications à l’APO sont sérialisées et il est important de ne pas bloquer le thread de rappel de notification trop longtemps.

Définition de l’API : Cadre de notifications

Le cadre de notifications implémente une nouvelle interface IAudioProcessingObjectNotifications que les clients peuvent implémenter pour s’inscrire et recevoir des notifications audio courantes pour les points de terminaison APO et les notifications d’effets système.

Pour plus d’informations, consultez les pages suivantes :

Exemple de code - Infrastructure de notifications

L’exemple montre comment un APO peut implémenter l’interface IAudioProcessingObjectNotifications. Dans la méthode GetApoNotificationRegistrationInfo, l’APO exemple s’inscrit pour les notifications de changements des magasins de propriétés des effets système.
La méthode HandleNotification est invoquée par le système d’exploitation pour notifier l’APO des changements correspondant à ce pour quoi l’APO s’était inscrit.

class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
    IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3,
    IAudioProcessingObjectNotifications>
{
public:
    // IAudioProcessingObjectNotifications
    STDMETHOD(GetApoNotificationRegistrationInfo)(
        _Out_writes_(count) APO_NOTIFICATION_DESCRIPTOR** apoNotifications, _Out_ DWORD* count);
    STDMETHOD_(void, HandleNotification)(_In_ APO_NOTIFICATION *apoNotification);

    // Implementation of IAudioSystemEffects2, IAudioSystemEffects3 has been omitted from this sample for brevity. 

private:
    wil::com_ptr_nothrow<IMMDevice> m_device;

    // Each APO has its own private collection of properties. The collection is identified through a
    // a property store context GUID, which is defined below and in the audio driver INF file.
    const GUID m_propertyStoreContext = ...;

    float m_masterVolume = 1.0f;
    BOOL m_isMuted = FALSE;
    BOOL m_allowOffloading = FALSE;

    // The rest of the implementation of IAudioProcessingObject is omitted for brevity
};

// The OS invokes this method on the APO to find out what notifications the APO is interested in.
STDMETHODIMP SampleApo::GetApoNotificationRegistrationInfo(
    _Out_writes_(count) APO_NOTIFICATION_DESCRIPTOR** apoNotificationDescriptorsReturned,
    _Out_ DWORD* count)
{
    *apoNotificationDescriptorsReturned = nullptr;
    *count = 0;

    // Before this function can be called, our m_device member variable should already have been initialized.
    // This would typically be done in our implementation of IAudioProcessingObject::Initialize, by using
    // APOInitSystemEffects3::pDeviceCollection to obtain the last IMMDevice in the collection.
    RETURN_HR_IF_NULL(E_FAIL, m_device);

    // Let the OS know what notifications we are interested in by returning an array of
    // APO_NOTIFICATION_DESCRIPTORs.
    constexpr DWORD numDescriptors = 3;
    wil::unique_cotaskmem_ptr<APO_NOTIFICATION_DESCRIPTOR[]> apoNotificationDescriptors;

    apoNotificationDescriptors.reset(static_cast<APO_NOTIFICATION_DESCRIPTOR*>(
        CoTaskMemAlloc(sizeof(APO_NOTIFICATION_DESCRIPTOR) * numDescriptors)));
    RETURN_IF_NULL_ALLOC(apoNotificationDescriptors);

    // Our APO wants to get notified when any change occurs on the user property store on the audio endpoint
    // identified by m_device.
    // The user property store is different for each APO. Ours is identified by m_propertyStoreContext.
    apoNotificationDescriptors[0].type = APO_NOTIFICATION_TYPE_AUDIO_SYSTEM_EFFECTS_PROPERTY_CHANGE;
    (void)m_device.query_to(&apoNotificationDescriptors[0].audioSystemEffectsPropertyChange.device);
    apoNotificationDescriptors[0].audioSystemEffectsPropertyChange.propertyStoreContext =   m_propertyStoreContext;

    // Our APO wants to get notified when an endpoint property changes on the audio endpoint.
    apoNotificationDescriptors[1].type = APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE;
    (void)m_device.query_to(&apoNotificationDescriptors[1].audioEndpointPropertyChange.device);


    // Our APO also wants to get notified when the volume level changes on the audio endpoint.
    apoNotificationDescriptors   [2].type = APO_NOTIFICATION_TYPE_ENDPOINT_VOLUME;
    (void)m_device.query_to(&apoNotificationDescriptors[2].audioEndpointVolume.device);

    *apoNotificationDescriptorsReturned = apoNotificationDescriptors.release();
    *count = numDescriptors;
    return S_OK;
}

static bool IsSameEndpointId(IMMDevice* device1, IMMDevice* device2)
{
    bool isSameEndpointId = false;

    wil::unique_cotaskmem_string deviceId1;
    if (SUCCEEDED(device1->GetId(&deviceId1)))
    {
        wil::unique_cotaskmem_string deviceId2;
        if (SUCCEEDED(device2->GetId(&deviceId2)))
        {
            isSameEndpointId = (CompareStringOrdinal(deviceId1.get(), -1, deviceId2.get(), -1, TRUE) == CSTR_EQUAL);
        }
    }
    return isSameEndpointId;
}

// HandleNotification is called whenever there is a change that matches any of the
// APO_NOTIFICATION_DESCRIPTOR elements in the array that was returned by GetApoNotificationRegistrationInfo.
// Note that the APO will have to query each property once to get its initial value because this method is
// only invoked when any of the properties have changed.
STDMETHODIMP_(void) SampleApo::HandleNotification(_In_ APO_NOTIFICATION* apoNotification)
{
    // Check if a property in the user property store has changed.
    if (apoNotification->type == APO_NOTIFICATION_TYPE_AUDIO_SYSTEM_EFFECTS_PROPERTY_CHANGE
        && IsSameEndpointId(apoNotification->audioSystemEffectsPropertyChange.endpoint, m_device.get())
        && apoNotification->audioSystemEffectsPropertyChange.propertyStoreContext == m_propertyStoreContext
        && apoNotification->audioSystemEffectsPropertyChange.propertyStoreType == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_USER)
    {
        // Check if one of the properties that we are interested in has changed.
        // As an example, we check for "PKEY_Endpoint_Enable_Channel_Swap_SFX" which is a fictitious
        // PROPERTYKEY that could be set on our user property store.
        if (apoNotification->audioSystemEffectsPropertyChange.propertyKey ==
            PKEY_Endpoint_Enable_Channel_Swap_SFX)
        {
            wil::unique_prop_variant var;
            if (SUCCEEDED(apoNotification->audioSystemEffectsPropertyChange.propertyStore->GetValue(
                    PKEY_Endpoint_Enable_Channel_Swap_SFX, &var)) &&
                var.vt != VT_EMPTY)
            {
                // We have retrieved the property value. Now we can do something interesting with it.
            }
        }
    }
    else if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE
        
        && IsSameEndpointId(apoNotification->audioEndpointPropertyChange.endpoint, m_device.get())
    {
        // Handle changes to PROPERTYKEYs in the audio endpoint's own property store.
        // In this example, we are interested in a property called "PKEY_Endpoint_AllowOffloading" that the
        // user might change in the audio control panel, and we update our member variable if this
        // property changes.
        if (apoNotification->audioEndpointPropertyChange.propertyKey == PKEY_Endpoint_AllowOffloading)
        {
            wil::unique_prop_variant var;
            if (SUCCEEDED(propertyStore->GetValue(PKEY_Endpoint_AllowOffloading, &var)) && var.vt == VT_BOOL)
            {
                m_allowOffloading = var.boolVal;
            }
        }    
    }
    else if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_VOLUME
        
        && IsSameEndpointId(apoNotification->audioEndpointVolumeChange.endpoint, m_device.get())
    {
        // Handle endpoint volume change
        m_masterVolume = apoNotification->audioEndpointVolumeChange.volume->fMasterVolume;
        m_isMuted = apoNotification->audioEndpointVolumeChange.volume->bMuted;
    }
}

Le code suivant est extrait de l’exemple Swap APO MFX : swapapomfx.cpp et montre l’inscription aux événements en renvoyant un tableau d’APO_NOTIFICATION_DESCRIPTOR.

HRESULT CSwapAPOMFX::GetApoNotificationRegistrationInfo(_Out_writes_(*count) APO_NOTIFICATION_DESCRIPTOR **apoNotifications, _Out_ DWORD *count)
{
    *apoNotifications = nullptr;
    *count = 0;

    RETURN_HR_IF_NULL(E_FAIL, m_device);

    // Let the OS know what notifications we are interested in by returning an array of
    // APO_NOTIFICATION_DESCRIPTORs.
    constexpr DWORD numDescriptors = 1;
    wil::unique_cotaskmem_ptr<APO_NOTIFICATION_DESCRIPTOR[]> apoNotificationDescriptors;

    apoNotificationDescriptors.reset(static_cast<APO_NOTIFICATION_DESCRIPTOR*>(
        CoTaskMemAlloc(sizeof(APO_NOTIFICATION_DESCRIPTOR) * numDescriptors)));
    RETURN_IF_NULL_ALLOC(apoNotificationDescriptors);

    // Our APO wants to get notified when an endpoint property changes on the audio endpoint.
    apoNotificationDescriptors[0].type = APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE;
    (void)m_device.query_to(&apoNotificationDescriptors[0].audioEndpointPropertyChange.device);

    *apoNotifications = apoNotificationDescriptors.release();
    *count = numDescriptors;

    return S_OK;
}

Le code suivant est extrait de l’exemple SwapAPO MFX HandleNotifications : swapapomfx.cpp et montre comment gérer les notifications.

void CSwapAPOMFX::HandleNotification(APO_NOTIFICATION *apoNotification)
{
    if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE)
    {
        // If either the master disable or our APO's enable properties changed...
        if (PK_EQUAL(apoNotification->audioEndpointPropertyChange.propertyKey, PKEY_Endpoint_Enable_Channel_Swap_MFX) ||
            PK_EQUAL(apoNotification->audioEndpointPropertyChange.propertyKey, PKEY_AudioEndpoint_Disable_SysFx))
        {
            struct KeyControl
            {
                PROPERTYKEY key;
                LONG* value;
            };

            KeyControl controls[] = {
                {PKEY_Endpoint_Enable_Channel_Swap_MFX, &m_fEnableSwapMFX},
            };

            m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"HandleNotification - pkey: " GUID_FORMAT_STRING L" %d", GUID_FORMAT_ARGS(apoNotification->audioEndpointPropertyChange.propertyKey.fmtid), apoNotification->audioEndpointPropertyChange.propertyKey.pid);

            for (int i = 0; i < ARRAYSIZE(controls); i++)
            {
                LONG fNewValue = true;

                // Get the state of whether channel swap MFX is enabled or not
                fNewValue = GetCurrentEffectsSetting(m_userStore.get(), controls[i].key, m_AudioProcessingMode);

                SetAudioSystemEffectState(m_effectInfos[i].id, fNewValue ? AUDIO_SYSTEMEFFECT_STATE_ON : AUDIO_SYSTEMEFFECT_STATE_OFF);
            }
        }
    }
}

Cadre de journalisation

Le cadre de journalisation fournit aux développeurs APO des moyens supplémentaires de collecte de données pour améliorer le développement et le débogage. Ce cadre unifie les différentes méthodes de journalisation utilisées par différents fournisseurs et les lie aux fournisseurs de journalisation des traces audio pour créer une journalisation plus significative. Le nouveau cadre fournit une API de journalisation, laissant le reste du travail au système d’exploitation.

Le fournisseur est défini comme suit :

IMPLEMENT_TRACELOGGING_CLASS(ApoTelemetryProvider, "Microsoft.Windows.Audio.ApoTrace",
    // {8b4a0b51-5dcf-5a9c-2817-95d0ec876a87}
    (0x8b4a0b51, 0x5dcf, 0x5a9c, 0x28, 0x17, 0x95, 0xd0, 0xec, 0x87, 0x6a, 0x87));

Chaque APO a son propre ID d’activité. Comme cela utilise le mécanisme de journalisation des traces existant, les outils de console existants peuvent être utilisés pour filtrer ces événements et les afficher en temps réel. Vous pouvez utiliser des outils existants comme tracelog et tracefmt, comme décrit dans Outils de traçage logiciel : Pilotes Windows. Pour plus d’informations sur les sessions de trace, veuillez consulter la section Création d’une session de trace avec un GUID de contrôle.

Les événements de journalisation de traces ne sont pas marqués comme télémétrie et ne seront pas affichés comme fournisseur de télémétrie dans des outils tels que xperf.

Implémentation : Cadre de journalisation

Le cadre de journalisation est basé sur les mécanismes de journalisation fournis par le traçage ETW. Pour plus d’informations sur ETW, veuillez consulter la section Traçage d’événements. Ce n’est pas destiné à la journalisation des données audio, mais plutôt à la journalisation des événements qui sont généralement journalisés en production. Les API de journalisation ne doivent pas être utilisées depuis le thread de streaming en temps réel, car elles peuvent potentiellement provoquer la préemption du thread de la pompe par le planificateur de CPU du système d’exploitation. La journalisation doit être principalement utilisée pour les événements qui aideront à déboguer les problèmes souvent rencontrés sur le terrain.

Définition de l’API : Cadre de journalisation

Le cadre de journalisation introduit l’interface IAudioProcessingObjectLoggingService qui fournit un nouveau service de journalisation pour les APOs.

Pour plus d’informations, veuillez consulter la section IAudioProcessingObjectLoggingService.

Exemple de code : Cadre de journalisation

L’exemple montre l’utilisation de la méthode IAudioProcessingObjectLoggingService::ApoLog et comment ce pointeur d’interface est obtenu dans IAudioProcessingObject::Initialize.

Exemple de journalisation AecApoMfx.

class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
    IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
private:
    wil::com_ptr_nothrow<IAudioProcessingObjectLoggingService> m_apoLoggingService;

public:
    // IAudioProcessingObject
    STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);

    // Implementation of IAudioProcessingObject, IAudioSystemEffects2 andIAudioSystemEffects3 has been omitted for brevity.
};

// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
    if (cbDataSize == sizeof(APOInitSystemEffects3))
    {
        APOInitSystemEffects3* apoInitSystemEffects3 = reinterpret_cast<APOInitSystemEffects3*>(pbyData);

        // Try to get the logging service, but ignore errors as failure to do logging it is not fatal.
        (void)apoInitSystemEffects3->pServiceProvider->QueryService(SID_AudioProcessingObjectLoggingService, 
            __uuidof(IAudioProcessingObjectLoggingService), IID_PPV_ARGS(&m_apoLoggingService));
    }

    // Do other APO initialization work

    if (m_apoLoggingService != nullptr)
    {
        m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"APO Initialization completed");
    }
    return S_OK;
}

Cadre de threading

Le cadre de threading permettant aux effets d’être multi-thread en utilisant des files de travail d’une tâche appropriée du service de planification de classe multimédia (MMCSS) via une simple API. La création de files de travail en série en temps réel et leur association avec le thread principal de la pompe sont gérées par le système d’exploitation. Ce cadre permet aux APOs de mettre en file d’attente des éléments de travail de courte durée. La synchronisation entre les tâches reste de la responsabilité de l’APO. Pour plus d’informations sur le threading MMCSS, veuillez consulter la section Service de planification de classe multimédia et API de file de travail en temps réel.

Définitions de l’API : Cadre de threading

Le cadre de threading introduit l’interface IAudioProcessingObjectQueueService qui donne accès à la file de travail en temps réel pour les APOs.

Pour plus d’informations, consultez les pages suivantes :

Exemple de code - Threading Framework

Cet exemple montre l’utilisation de la méthode IAudioProcessingObjectRTQueueService::GetRealTimeWorkQueue et comment le pointeur d’interface IAudioProcessingObjectRTQueueService est obtenu dans IAudioProcessingObject::Initialize.

#include <rtworkq.h>

class SampleApo3 :
    public winrt::implements<SampleApo3, IAudioProcessingObject, IAudioProcessingObjectConfiguration,
        IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
private:
    DWORD m_queueId = 0;
    wil::com_ptr_nothrow<SampleApo3AsyncCallback> m_asyncCallback;

public:
    // IAudioProcessingObject
    STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);

    // IAudioProcessingObjectConfiguration
    STDMETHOD(LockForProcess)(
        _In_ UINT32 u32NumInputConnections,
        _In_reads_(u32NumInputConnections) APO_CONNECTION_DESCRIPTOR** ppInputConnections,
        _In_ UINT32 u32NumOutputConnections,
        _In_reads_(u32NumOutputConnections) APO_CONNECTION_DESCRIPTOR** ppOutputConnections);

    // Non-interface methods called by the SampleApo3AsyncCallback helper class.
    HRESULT DoWorkOnRealTimeThread()
    {
        // Do the actual work here
        return S_OK;
    }
    void HandleWorkItemCompleted(_In_ IRtwqAsyncResult* asyncResult);

    // Implementation of IAudioProcessingObject, IAudioSystemEffects2, IAudioSystemEffects3   and IAudioProcessingObjectConfiguration is omitted
    // for brevity.
};

// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo3::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
    if (cbDataSize == sizeof(APOInitSystemEffects3))
    {
        APOInitSystemEffects3* apoInitSystemEffects3 = reinterpret_cast<APOInitSystemEffects3*>(pbyData);

        wil::com_ptr_nothrow<IAudioProcessingObjectRTQueueService> apoRtQueueService;
        RETURN_IF_FAILED(apoInitSystemEffects3->pServiceProvider->QueryService(
            SID_AudioProcessingObjectRTQueue, IID_PPV_ARGS(&apoRtQueueService)));

        // Call the GetRealTimeWorkQueue to get the ID of a work queue that can be used for scheduling tasks
        // that need to run at a real-time priority. The work queue ID is used with the Rtwq APIs.
        RETURN_IF_FAILED(apoRtQueueService->GetRealTimeWorkQueue(&m_queueId));
    }

    // Do other initialization here
    return S_OK;
}

STDMETHODIMP SampleApo3::LockForProcess(
    _In_ UINT32 u32NumInputConnections,
    _In_reads_(u32NumInputConnections) APO_CONNECTION_DESCRIPTOR** ppInputConnections,
    _In_ UINT32 u32NumOutputConnections,
    _In_reads_(u32NumOutputConnections) APO_CONNECTION_DESCRIPTOR** ppOutputConnections)
{
    // Implementation details of LockForProcess omitted for brevity
    m_asyncCallback = winrt::make<SampleApo3AsyncCallback>(m_queueId).get();
    RETURN_IF_NULL_ALLOC(m_asyncCallback);

    wil::com_ptr_nothrow<IRtwqAsyncResult> asyncResult;	
    RETURN_IF_FAILED(RtwqCreateAsyncResult(this, m_asyncCallback.get(), nullptr, &asyncResult));

    RETURN_IF_FAILED(RtwqPutWorkItem(m_queueId, 0, asyncResult.get())); 
    return S_OK;
}

void SampleApo3::HandleWorkItemCompleted(_In_ IRtwqAsyncResult* asyncResult)
{
    // check the status of the result
    if (FAILED(asyncResult->GetStatus()))
    {
        // Handle failure
    }

    // Here the app could call RtwqPutWorkItem again with m_queueId if it has more work that needs to
    // execute on a real-time thread.
}


class SampleApo3AsyncCallback :
    public winrt::implements<SampleApo3AsyncCallback, IRtwqAsyncCallback>
{
private:
    DWORD m_queueId;

public:
    SampleApo3AsyncCallback(DWORD queueId) : m_queueId(queueId) {}

    // IRtwqAsyncCallback
    STDMETHOD(GetParameters)(_Out_ DWORD* pdwFlags, _Out_ DWORD* pdwQueue)
    {
        *pdwFlags = 0;
        *pdwQueue = m_queueId;
        return S_OK;
    }
    STDMETHOD(Invoke)(_In_ IRtwqAsyncResult* asyncResult);
};


STDMETHODIMP SampleApo3AsyncCallback::Invoke(_In_ IRtwqAsyncResult* asyncResult)
{
    // We are now executing on the real-time thread. Invoke the APO and let it execute the work.
    wil::com_ptr_nothrow<IUnknown> objectUnknown;
    RETURN_IF_FAILED(asyncResult->GetObject(objectUnknown.put_unknown()));

    wil::com_ptr_nothrow<SampleApo3> sampleApo3 = static_cast<SampleApo3*>(objectUnknown.get());
    HRESULT hr = sampleApo3->DoWorkOnRealTimeThread();
    RETURN_IF_FAILED(asyncResult->SetStatus(hr));

    sampleApo3->HandleWorkItemCompleted(asyncResult);
    return S_OK;
}

Pour plus d’exemples sur la façon d’utiliser cette interface, veuillez consulter le code d’exemple suivant :

Découverte et contrôle des effets audio

Le cadre de découverte permet au système d’exploitation de contrôler les effets audio sur leur flux. Ces API prennent en charge les scénarios dans lesquels l’utilisateur d’une application doit contrôler certains effets sur les flux (par exemple, suppression profonde du bruit). Pour y parvenir, ce cadre ajoute les éléments suivants :

  • Une nouvelle API pour interroger un APO afin de déterminer si un effet audio peut être activé ou désactivé.
  • Une nouvelle API pour définir l’état d’un effet audio sur activé/désactivé.
  • Une notification lorsqu’il y a un changement dans la liste des effets audio ou lorsque des ressources deviennent disponibles pour que l’effet audio puisse maintenant être activé/désactivé.

Implémentation : Découverte des effets audio

Un APO doit implémenter l’interface IAudioSystemEffects3 s’il a l’intention d’exposer des effets pouvant être activés et désactivés dynamiquement. Un APO expose ses effets audio via la fonction IAudioSystemEffects3::GetControllableSystemEffectsList et active et désactive ses effets audio via la fonction IAudioSystemEffects3::SetAudioSystemEffectState.

Exemple de code : Découverte des effets audio

Le code d’exemple de découverte des effets audio se trouve dans l’exemple SwapAPOSFX - swapaposfx.cpp.

L’exemple de code suivant illustre comment récupérer la liste des effets configurables. Exemple GetControllableSystemEffectsList - swapaposfx.cpp

HRESULT CSwapAPOSFX::GetControllableSystemEffectsList(_Outptr_result_buffer_maybenull_(*numEffects) AUDIO_SYSTEMEFFECT** effects, _Out_ UINT* numEffects, _In_opt_ HANDLE event)
{
    RETURN_HR_IF_NULL(E_POINTER, effects);
    RETURN_HR_IF_NULL(E_POINTER, numEffects);

    *effects = nullptr;
    *numEffects = 0;

    // Always close existing effects change event handle
    if (m_hEffectsChangedEvent != NULL)
    {
        CloseHandle(m_hEffectsChangedEvent);
        m_hEffectsChangedEvent = NULL;
    }

    // If an event handle was specified, save it here (duplicated to control lifetime)
    if (event != NULL)
    {
        if (!DuplicateHandle(GetCurrentProcess(), event, GetCurrentProcess(), &m_hEffectsChangedEvent, EVENT_MODIFY_STATE, FALSE, 0))
        {
            RETURN_IF_FAILED(HRESULT_FROM_WIN32(GetLastError()));
        }
    }

    if (!IsEqualGUID(m_AudioProcessingMode, AUDIO_SIGNALPROCESSINGMODE_RAW))
    {
        wil::unique_cotaskmem_array_ptr<AUDIO_SYSTEMEFFECT> audioEffects(
            static_cast<AUDIO_SYSTEMEFFECT*>(CoTaskMemAlloc(NUM_OF_EFFECTS * sizeof(AUDIO_SYSTEMEFFECT))), NUM_OF_EFFECTS);
        RETURN_IF_NULL_ALLOC(audioEffects.get());

        for (UINT i = 0; i < NUM_OF_EFFECTS; i++)
        {
            audioEffects[i].id = m_effectInfos[i].id;
            audioEffects[i].state = m_effectInfos[i].state;
            audioEffects[i].canSetState = m_effectInfos[i].canSetState;
        }

        *numEffects = (UINT)audioEffects.size();
        *effects = audioEffects.release();
    }

    return S_OK;
}

L’exemple de code suivant illustre comment activer et désactiver les effets. Exemple SetAudioSystemEffectState - swapaposfx.cpp

HRESULT CSwapAPOSFX::SetAudioSystemEffectState(GUID effectId, AUDIO_SYSTEMEFFECT_STATE state)
{
    for (auto effectInfo : m_effectInfos)
    {
        if (effectId == effectInfo.id)
        {
            AUDIO_SYSTEMEFFECT_STATE oldState = effectInfo.state;
            effectInfo.state = state;

            // Synchronize access to the effects list and effects changed event
            m_EffectsLock.Enter();

            // If anything changed and a change event handle exists
            if (oldState != effectInfo.state)
            {
                SetEvent(m_hEffectsChangedEvent);
                m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"SetAudioSystemEffectState - effect: " GUID_FORMAT_STRING L", state: %i", effectInfo.id, effectInfo.state);
            }

            m_EffectsLock.Leave();
            
            return S_OK;
        }
    }

    return E_NOTFOUND;
}

Réutilisation des APO SFX et MFX de Windows dans Windows 11, version 22H2

À partir de Windows 11, version 22H2, les fichiers de configuration INF qui réutilisent les APO SFX et MFX intégrés de Windows, peuvent maintenant réutiliser les APO CAPX SFX et MFX. Cette section décrit les trois façons de le faire.

Il y a trois points d’insertion pour les APO : rendu pré-mix, rendu post-mix et capture. Le moteur audio de chaque appareil logique prend en charge une instance d’un APO de rendu pré-mix par flux (SFX de rendu) et un APO de rendu post-mix (MFX). Le moteur audio prend également en charge une instance d’un APO de capture (SFX de capture) qui est inséré dans chaque flux de capture. Pour plus d’informations sur la réutilisation ou l’encapsulation des APO intégrés, veuillez consulter la section Combiner APOs personnalisés et Windows.

Les APO CAPX SFX et MFX peuvent être réutilisés de l’une des trois manières suivantes.

Utilisation de la section INF DDInstall

Utilisez mssysfx.CopyFilesAndRegisterCapX à partir de wdmaudio.inf en ajoutant les entrées suivantes.

   Include=wdmaudio.inf
   Needs=mssysfx.CopyFilesAndRegisterCapX

Utilisation d’un fichier INF d’extension

Le wdmaudioapo.inf est le fichier INF de classe extension AudioProcessingObject. Il contient l’enregistrement spécifique à l’appareil des APO SFX et MFX.

Référencement direct des APO SFX et MFX de Windows pour les effets de flux et de mode

Pour référencer directement ces APO pour les effets de flux et de mode, utilisez les valeurs GUID suivantes.

  • Utilisez {C9453E73-8C5C-4463-9984-AF8BAB2F5447} en tant qu’APO SFX de Windows
  • Utilisez {13AB3EBD-137E-4903-9D89-60BE8277FD17} en tant qu’APO MFX de Windows.

SFX (Stream) et MFX (Mode) étaient appelés LFX (local) dans Windows 8.1 et MFX était appelé GFX (global). Ces entrées de registre continuent d’utiliser les anciens noms.

L’enregistrement spécifique à l’appareil utilise HKR au lieu de HKCR.

Le fichier INF devra avoir les entrées suivantes ajoutées.

  HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
  HKR,"FX\\0\\%WMALFXGFXAPO_Context%\\User",,,
  WMALFXGFXAPO_Context = "{B13412EE-07AF-4C57-B08B-E327F8DB085B}"

Ces entrées de fichier INF créeront un magasin de propriétés qui sera utilisé par les API Windows 11 pour les nouveaux APO.

PKEY_FX_Association dans l’INF ex. HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY% , doit être remplacé par HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%.

Voir aussi

Objets de traitement audio de Windows.