Compartilhar via


APIs do Windows 11 para objetos de processamento de áudio

Este tópico apresenta um conjunto de novas APIs do Windows 11 para APOs (Objetos de Processamento de Áudio) fornecidas com um driver de áudio.

O Windows permite que fabricantes de hardware de áudio de terceiros incluam efeitos de processamento de sinal digital personalizados baseados em host. Esses efeitos são empacotados como APOs (Objetos de Processamento de Áudio) no modo de usuário. Para obter mais informações sobre APOs, veja Objetos de processamento de áudio do Windows.

Algumas das APIs descritas aqui permitem novos cenários para Fornecedores Independentes de Hardware (IHV) e Fornecedores Independentes de Software (ISV), enquanto outras APIs destinam-se a fornecer alternativas que melhoram a confiabilidade geral do áudio e os recursos de depuração.

  • A estrutura de Cancelamento de Eco Acústico (AEC) permite que um APO se identifique como um APO AEC , concedendo acesso a uma transmissão de referência e controles adicionais.
  • A estrutura Configurações permitirá que os APOs exponham métodos para consultar e modificar o repositório de propriedades para efeitos de áudio ("repositório de propriedades FX") em um ponto de extremidade de áudio. Quando esses métodos são implementados por um APO, eles podem ser acionados por aplicativos de suporte de hardware (HSA) associados a esse APO.
  • A estrutura Notificações permite que os efeitos de áudio (APOs) solicitem notificações para lidar com alterações de armazenamento de propriedades de volume, ponto de extremidade e efeitos de áudio.
  • A estrutura de registros auxilia no desenvolvimento e depuração de APOs.
  • A estrutura Threading permite que os APOs sejam multithreaded usando um pool de threads gerenciado pelo SO, registrado no MMCSS.
  • As APIs de Descoberta e Controle de Efeitos de Áudio permitem que o sistema operacional detecte, habilite e desabilite efeitos disponíveis para processamento em uma transmissão.

Para aproveitar essas novas APIs, espera-se que os APOs utilizem a nova interface IAudioSystemEffects3. Quando um APO implementa essa interface, o sistema operacional interpreta isso como um sinal implícito de que o APO suporta a estrutura de configurações do APO e permite que o APO assine notificações comuns relacionadas a áudio do mecanismo de áudio.

Requisitos de desenvolvimento do Windows 11 APO CAPX

Todos os novos APOs fornecidos em um dispositivo para Windows 11 precisam ser compatíveis com as APIs listadas neste tópico, validadas via HLK. Além disso, espera-se que quaisquer APOs que utilizem AEC sigam a implementação descrita neste tópico, validada via HLK. Espera-se que as implementações personalizadas para essas extensões principais de processamento de áudio (Configurações, Log, Notificações, Threading, AEC) aproveitem as APIs CAPX. Isso será validado por meio dos testes HLK do Windows 11. Por exemplo, se um APO estiver usando dados do registro para salvar configurações em vez de usar a estrutura de configurações, o teste HLK associado falhará.

Requisitos de versão do Windows

As APIs descritas neste tópico estão disponíveis a partir da compilação 22000 do sistema operacional Windows 11, WDK e SDK. O Windows 10 não terá suporte para essas APIs. Se um APO pretende funcionar no Windows 10 e no Windows 11, ele pode examinar se ele está sendo inicializado com a estrutura APOInitSystemEffects2 ou APOInitSystemEffects3 para determinar se ele está sendo executado em um sistema operacional que oferece suporte às APIs CAPX.

As versões mais recentes do Windows, do WDK e do SDK podem ser baixadas abaixo por meio do Programa Windows Insider. Os parceiros envolvidos com a Microsoft por meio do Partner Center também podem acessar esse conteúdo por meio do Collaborate. Para obter mais informações sobre o Collaborate, veja Introdução ao Microsoft Collaborate.

O Conteúdo WHCP do Windows 11 foi atualizado para fornecer aos parceiros os meios para validar essas APIs.

O código de exemplo para o conteúdo descrito neste tópico pode ser encontrado aqui: Audio/SYSVAD/APO - github

Cancelamento de Eco Acústico (AEC)

O cancelamento de eco acústico (AEC) é um efeito de áudio comum implementado por IHVs (Fornecedores de Hardware Independentes) e ISVs (Fornecedores de Software Independentes) como um APO (Objeto de Processamento de Audio) no pipeline de captura de microfone. Esse efeito é diferente de outros efeitos normalmente implementados por IHVs e ISVs, pois requer 2 entradas – uma transmissão de áudio do microfone e uma transmissão de áudio de um dispositivo de renderização que atua como o sinal de referência.

Este novo conjunto de interfaces permite que um APO AEC se identifique como tal para o mecanismo de áudio. Isso resulta no mecanismo de áudio configurando o APO adequadamente com várias entradas e uma única saída.

Quando as novas interfaces AEC são implementadas por um APO, o mecanismo de áudio irá:

  • Configure o APO com uma entrada adicional que fornece ao APO a transmissão de referência de um ponto de extremidade de renderização apropriado.
  • Alterne os fluxos de referência à medida que o dispositivo de renderização muda.
  • Permita que um APO controle o formato do microfone de entrada e a transmissão de referência.
  • Permita que um APO obtenha carimbos de data/hora no microfone e fluxos de referência.

Abordagem anterior - Windows 10

APOs são objetos de entrada única – saída única. O mecanismo de áudio fornece um APO AEC para o áudio do ponto de extremidade do microfone em sua entrada. Para obter a transmissão de referência, um APO pode interagir com o driver usando interfaces proprietárias para recuperar o áudio de referência do ponto de extremidade de renderização ou usar WASAPI para abrir uma transmissão de loopback no ponto de extremidade de renderização.

Ambas as abordagens acima têm desvantagens:

  • Um APO AEC que usa canais privados para obter uma transmissão de referência do driver, normalmente pode fazer isso apenas a partir do dispositivo de renderização de áudio integrado. Como resultado, o cancelamento de eco não funcionará se o usuário estiver reproduzindo áudio fora do dispositivo não integrado, como dispositivo de áudio USB ou Bluetooth. Somente o sistema operacional está ciente dos pontos de extremidade de renderização corretos que podem servir como pontos de extremidade de referência.

  • Um APO pode usar WASAPI para escolher o ponto de extremidade de renderização padrão para executar o cancelamento de eco. No entanto, há algumas armadilhas a serem observadas ao abrir uma transmissão de loopback do processo de audiodg.exe (que é onde o APO está hospedado).

    • A transmissão de loopback não pode ser aberta/destruída quando o mecanismo de áudio está chamando os métodos APO principais, pois isso pode resultar em um bloqueio.
    • Um APO de captura não sabe o estado das transmissões de seus clientes. ou seja, um aplicativo de captura pode ter uma transmissão de captura no estado "STOP", no entanto, o APO não está ciente desse estado e, portanto, mantém a transmissão de loopback aberta no estado "RUN", o que é ineficiente em termos de consumo de energia.

Definição de API - AEC

A estrutura AEC fornece novas estruturas e interfaces que os APOs podem aproveitar. Essas novas estruturas e interfaces são descritas a seguir.

APO_CONNECTION_PROPERTY_V2 estrutura

Os APOs que implementam a interface IApoAcousticEchoCancellation receberão uma estrutura APO_CONNECTION_PROPERTY_V2 em sua chamada para IAudioProcessingObjectRT::APOProcess. Além de todos os campos na estrutura APO_CONNECTION_PROPERTY , a versão 2 da estrutura também fornece informações de carimbo de data/hora para os buffers de áudio.

Um APO pode examinar o campo APO_CONNECTION_PROPERTY.u32Signature para determinar se a estrutura que ele recebe do mecanismo de áudio é do tipo APO_CONNECTION_PROPERTY ou APO_CONNECTION_PROPERTY_V2. Estruturas APO_CONNECTION_PROPERTY têm uma assinatura de APO_CONNECTION_PROPERTY_SIGNATURE, enquanto APO_CONNECTION_PROPERTY_V2 têm uma assinatura igual a APO_CONNECTION_PROPERTY_V2_SIGNATURE. Se a assinatura tiver um valor igual a APO_CONNECTION_PROPERTY_V2_SIGNATURE, o ponteiro para a estrutura APO_CONNECTION_PROPERTY poderá ser digitado com segurança para um ponteiro APO_CONNECTION_PROPERTY_V2.

O código a seguir é do exemplo Aec APO MFX - AecApoMfx.cpp e mostra a reformulação.

    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

A interface IApoAcousticEchoCancellation não tem métodos explícitos. Seu objetivo é identificar um APO AEC para o mecanismo de áudio. Essa interface só pode ser implementada por efeitos de modo (MFX) em pontos de extremidade de captura. A implementação dessa interface em qualquer outro APO levará a uma falha no carregamento desse APO. Para obter informações gerais sobre MFX, veja Arquitetura de objetos de processamento de áudio.

Se o efeito de modo em um ponto de extremidade de captura for implementado como uma série de APOs encadeados, somente o APO mais próximo do dispositivo poderá implementar essa interface. Os APOs que implementarem essa interface receberão a estrutura APO_CONNECTION_PROPERTY_V2 em sua chamada para IAudioProcessingobjectRT::APOProcess. O APO pode verificar se há uma assinatura APO_CONNECTION_PROPERTY_V2_SIGNATURE na propriedade de conexão e digitar a estrutura de APO_CONNECTION_PROPERTY de entrada em uma estrutura APO_CONNECTION_PROPERTY_V2.

Em reconhecimento ao fato de que os APOs AEC geralmente executam seus algoritmos em uma taxa de amostragem específica/contagem de canais, o mecanismo de áudio fornece suporte de reamostragem para APOs que implementam a interface IApoAcousticEchoCancellation.

Quando um APO AEC retorna APOERR_FORMAT_NOT_SUPPORTED na chamada para IAudioProcessingObject::OutInputFormatSupported, o mecanismo de áudio chamará IAudioProcessingObject::IsInputFormatSupported no APO novamente com um formato de saída NULL e um formato de entrada não nulo, para obter o formato sugerido do APO. O mecanismo de áudio irá então reamostrar o áudio do microfone para o formato sugerido antes de enviá-lo para o AEC APO. Isso elimina a necessidade de o APO AEC implementar a taxa de amostragem e a conversão de contagem de canais.

IApoAuxiliaryInputConfiguration

A interface IApoAuxiliaryInputConfiguration fornece métodos que os APOs podem implementar para que o mecanismo de áudio possa adicionar e remover fluxos de entrada auxiliares.

Essa interface é implementada pelo AEC APO e usada pelo mecanismo de áudio para inicializar a entrada de referência. No Windows 11, o APO AEC só será inicializado com uma única entrada auxiliar – que tenha a transmissão de áudio de referência para cancelamento de eco. O método AddAuxiliaryInput será usado para adicionar a entrada de referência ao APO. Os parâmetros de inicialização conterão uma referência ao ponto de extremidade de renderização do qual a transmissão de loopback é obtida.

O método IsInputFormatSupported é chamado pelo mecanismo de áudio para negociar formatos na entrada auxiliar. Se o APO AEC preferir um formato específico, ele poderá retornar S_FALSE na chamada para IsInputFormatSupported e especificar um formato sugerido. O mecanismo de áudio irá reamostrar o áudio de referência para o formato sugerido e fornecê-lo na entrada auxiliar do AEC APO.

IApoAuxiliaryInputRT

A interface IApoAuxiliaryInputRT é a interface segura em tempo real usada para acionar as entradas auxiliares de um APO.

Essa interface é usada para fornecer dados de áudio na entrada auxiliar do APO. Observe que as entradas de áudio auxiliares não são sincronizadas com as chamadas para IAudioProcessingObjectRT::APOProcess. Quando não houver áudio sendo renderizado no ponto de extremidade de renderização, os dados de loopback não estarão disponíveis na entrada auxiliar. ou seja, não haverá chamadas para IApoAuxiliaryInputRT::AcceptInput

Resumo das APIs AEC CAPX

Para obter mais informações, encontre informações adicionais nas páginas a seguir.

Código de exemplo - AEC

Consulte os seguintes exemplos de código do Sysvad Audio AecApo.

O código a seguir do cabeçalho de exemplo Aec APO - AecAPO.h mostra os três novos métodos públicos que estão sendo adicionados.

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

O código a seguir é do exemplo Aec APO MFX - AecApoMfx.cpp e mostra a implementação de AddAuxiliaryInput, quando o APO só pode manipular uma entrada auxiliar.

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;

Analise também o código de exemplo que mostra a implementação de CAecApoMFX::IsInputFormatSupported e CAecApoMFX::AcceptInput bem como o tratamento do APO_CONNECTION_PROPERTY_V2.

Sequência de operações - AEC

Na inicialização:

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

Na alteração do dispositivo de renderização:

  1. IAudioProcessingObject::Inicializar
  2. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  3. IAudioProcessingObjectConfiguration::LockForProcess
  4. Alterações de dispositivo padrão
  5. IAudioProcessingObjectConfiguration::UnlockForProcess
  6. IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
  7. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  8. IAudioProcessingObjectConfiguration::LockForProcess

Esse é o comportamento de buffer recomendado para AEC.

  • Os buffers obtidos na chamada para IApoAuxiliaryInputRT::AcceptInput devem ser gravados em um buffer circular sem bloquear o thread principal.
  • Na chamada para IAudioProcessingObjectRT::APOProcess, o buffer circular deve ser lido para o pacote de áudio mais recente da transmissão de referência, e esse pacote deve ser usado para execução através do algoritmo de cancelamento de eco.
  • Carimbos de data/hora nos dados de referência e microfone podem ser usados para alinhar os dados do alto-falante e do microfone.

Transmissão de loopback de referência

Por padrão, a transmissão de loopback "toca" (escuta) a transmissão de áudio antes de qualquer volume ou silenciamento ser aplicado. Uma transmissão de loopback tocada antes de o volume ter sido aplicado é conhecido como uma transmissão de loopback pré-volume. Uma vantagem de ter uma transmissão de loopback pré-volume é uma transmissão de áudio clara e uniforme, independentemente da configuração de volume atual.

Alguns algoritmos AEC podem preferir obter uma transmissão de loopback que tenha sido conectada após qualquer processamento de volume (incluindo silenciamento). Essa configuração é conhecida como loopback pós-volume.

Na próxima versão principal do Windows AEC, os APOs podem solicitar loopback pós-volume em pontos de extremidade com suporte.

Limitações

Ao contrário das transmissões de loopback pré-volume, que estão disponíveis para todos os pontos de extremidade de renderização, as transmissões de loopback pós-volume podem não estar disponíveis em todos os pontos de extremidade.

Solicitação de loopback pós-volume

Os APOs AEC que desejam usar loopback pós-volume devem implementar a interface IApoAcousticEchoCancellation2 .

Um APO AEC pode solicitar loopback pós-volume retornando o sinalizador APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK por meio do parâmetro Propriedades em sua implementação de IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties.

Dependendo do ponto de extremidade de renderização que está sendo usado no momento, o loopback pós-volume pode não estar disponível. Um APO AEC é notificado se o loopback pós-volume estiver sendo usado quando seu método IApoAuxiliaryInputConfiguration::AddAuxiliaryInput for chamado. Se o campo AcousticEchoCanceller_Reference_Input streamProperties contiver APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK, o loopback pós-volume estará em uso.

O código a seguir do cabeçalho de exemplo AEC APO - AecAPO.h mostra os três novos métodos públicos que estão sendo adicionados.

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;

O trecho de código a seguir é do exemplo AEC APO MFX - AecApoMfx.cpp e mostra a implementação de GetDesiredReferenceStreamProperties e a parte relevante 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.

Estrutura de configurações

A estrutura Configurações permite que os APOs exponham métodos para consultar e modificar o repositório de propriedades para efeitos de áudio ("repositório de propriedades FX") em um ponto de extremidade de áudio. Essa estrutura pode ser usada por APOs e por Aplicativos de Suporte de Hardware (HSA) que desejam comunicar configurações a esse APO. Os HSAs podem ser aplicativos da Plataforma Universal do Windows (UWP) e exigem um recurso especial para invocar as APIs na Estrutura de Configurações. Para obter mais informações sobre aplicativos HSA, veja Aplicativos de dispositivo UWP.

Estrutura da Loja FxProperty

O novo repositório FxProperty tem três subarmazenamentos: Padrão, Usuário e Volátil.

A subchave "Padrão" contém propriedades de efeitos personalizados e é preenchida a partir do arquivo INF. Essas propriedades não persistem nas atualizações do sistema operacional. Por exemplo, as propriedades que normalmente são definidas em um INF caberiam aqui. Essas seriam então novamente preenchidas a partir do INF.

A subchave "Usuário" contém configurações de usuário referentes às propriedades de efeitos. Essas configurações são mantidas pelo sistema operacional em atualizações e migrações. Por exemplo, todas as predefinições que o usuário pode configurar que devem persistir durante a atualização.

A subchave "Volátil" contém propriedades de efeitos voláteis. Essas propriedades são perdidas na reinicialização do dispositivo e são limpas sempre que o ponto de extremidade faz a transição para ativo. Espera-se que elas contenham propriedades de variantes de tempo (por exemplo, com base em aplicativos em execução atuais, postura do dispositivo etc.) Por exemplo, quaisquer configurações que dependam do ambiente atual.

A maneira de pensar sobre o usuário versus o padrão é se você deseja que as propriedades persistam nas atualizações do sistema operacional e do driver. As propriedades do usuário serão mantidas. As propriedades padrão serão preenchidas novamente a partir do INF.

Contextos APO

A estrutura de configurações CAPX permite que um autor de APO agrupe propriedades APO por contextos. Cada APO pode definir seu próprio contexto e atualizar propriedades relativas ao seu próprio contexto. O armazenamento de propriedades de efeitos para um ponto de extremidade de áudio pode ter zero ou mais contextos. Os fornecedores são livres para criar contextos como quiserem, seja por SFX/MFX/EFX ou por modo. Um fornecedor também pode optar por ter um único contexto para todos os APOs enviados por esse fornecedor.

Capacidade restrita de configurações

A API de configurações destina-se a oferecer suporte a todos os OEMs e desenvolvedores HSA interessados em consultar e modificar as configurações de efeitos de áudio associadas a um dispositivo de áudio. Essa API é exposta a aplicativos HSA e Win32 para fornecer acesso ao repositório de propriedades por meio do recurso restrito "audioDeviceConfiguration" que deve ser declarado no manifesto. Além disso, um namespace correspondente deve ser declarado da seguinte maneira:

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

O IAudioSystemEffectsPropertyStore é legível e gravável por um serviço ISV/IHV, um aplicativo de armazenamento UWP, aplicativos de desktop não administrativos e APOs. Além disso, isso pode atuar como o mecanismo para que os APOs entreguem mensagens de volta a um serviço ou aplicativo de armazenamento UWP.

Observação

Esse é um recurso restrito: se um aplicativo for enviado com esse recurso para a Microsoft Store, ele acionará uma análise minuciosa. O aplicativo deve ser um Hardware Support App (HSA), e será examinado para avaliar se é realmente um HSA antes que o envio seja aprovado.

Definição de API - Estrutura de configurações

A nova interface IAudioSystemEffectsPropertyStore permite que um HSA acesse repositórios de propriedades de efeitos do sistema de áudio e registre-se para notificações de alteração de propriedade.

A função ActiveAudioInterfaceAsync fornece um método para obter a interface IAudioSystemEffectsPropertyStore de forma assíncrona.

Um aplicativo pode receber notificações quando o repositório de propriedades de efeitos do sistema é alterado, usando a nova interface de retorno de chamada IAudioSystemEffectsPropertyChangeNotificationClient .

Aplicativo tentando obter o IAudioSystemEffectsPropertyStore usando IMMDevice::Activate

O exemplo demonstra como um aplicativo de suporte de hardware pode usar IMMDevice::Activate para ativar IAudioSystemEffectsPropertyStore. O exemplo mostra como usar IAudioSystemEffectsPropertyStore para abrir um IPropertyStore que tem configurações de usuário.

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

Exemplo usando ActivateAudioInterfaceAsync

Este exemplo faz a mesma coisa que o exemplo anterior, mas em vez de usar IMMDevice, ele usa a API ActivateAudioInterfaceAsync para obter a interface IAudioSystemEffectsPropertyStore de forma assíncrona.

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::Inicialize o código usando o IAudioSystemEffectsPropertyStore

O exemplo demonstra que a implementação de um APO pode usar a estrutura APOInitSystemEffects3 para recuperar as interfaces IPropertyStore padrão, padrão e volátil para a APO, durante a inicialização da 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;
}

Registro de aplicativo para notificações de alteração de propriedade

O exemplo demonstra o uso do registro para notificações de alteração de propriedade. Isso não deve ser usado com o APO e deve ser utilizado por aplicativos 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;
}

Código de exemplo - Estrutura de configurações

Este código de exemplo é do exemplo sysvad 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);
    }
}

Seção INF - Estrutura de configurações

A sintaxe do arquivo INF para declarar propriedades de efeito usando a nova estrutura de configurações CAPX é a seguinte:

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

Isso substitui a sintaxe mais antiga para declarar propriedades de efeito da seguinte maneira:

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

O INF não pode ter a entrada IAudioSystemEffectsPropertyStore e a entrada IPropertyStore para o mesmo ponto de extremidade de áudio. Observe que não há suporte para isso.

Exemplo mostrando o uso da nova loja de propriedades:

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}"

Estrutura de notificações

A estrutura Notificações permite que os efeitos de áudio (APOs) solicitem e lidem com notificações de alterações de armazenamento de propriedades de volume, ponto de extremidade e efeitos de áudio. Essa estrutura destina-se a substituir as APIs existentes usadas por APOs para registrar e cancelar o registro de notificações.

A nova API introduz uma interface que os APOs podem utilizar para declarar o tipo de notificações em que o APO está interessado. O Windows consultará o APO para obter as notificações nas quais ele está interessado e encaminhará a notificação para os APOs. Os APOs não precisam mais chamar explicitamente as APIs de registro ou cancelamento.

As notificações são entregues a um APO usando uma fila serial. Quando aplicável, a primeira notificação transmite o estado inicial do valor solicitado (por exemplo, o volume do ponto de extremidade de áudio). As notificações param quando audiodg.exe interrompe a intenção de usar um APO para streaming. Os APOs deixarão de receber notificações após o UnlockForProcess. Ainda é necessário sincronizar o UnlockForProcess e quaisquer notificações a bordo.

Implementação - Quadro de notificações

Para aproveitar a estrutura de notificações, um APO declara em quais notificações está interessado. Não há chamadas explícitas de registro/cancelamento de registro. Todas as notificações para o APO são serializadas e é importante não bloquear o thread de retorno de chamada de notificação por muito tempo.

Definição de API - Estrutura de notificações

A estrutura de notificação implementa uma nova interface IAudioProcessingObjectNotifications que pode ser implementada pelos clientes para registrar e receber notificações comuns relacionadas a áudio para notificações de ponto de extremidade APO e efeito do sistema.

Para obter mais informações, localize conteúdo adicional nas seguintes páginas:

Código de exemplo - Estrutura de notificações

O exemplo demonstra como um APO pode implementar a interface IAudioProcessingObjectNotifications. No método GetApoNotificationRegistrationInfo, o APO de exemplo registra notificações para alterações nos repositórios de propriedades de efeitos do sistema.
O método HandleNotification é chamado pelo sistema operacional para notificar o APO sobre alterações que correspondem ao que o APO havia registrado.

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

O código a seguir é do exemplo Swap APO MFX - swapapomfx.cpp e mostra o registro de eventos, retornando uma matriz de APO_NOTIFICATION_DESCRIPTORs.

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

O código a seguir é do exemplo HandleNotifications do SwapAPO MFX - swapapomfx.cpp e mostra como lidar com notificações.

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

Estrutura de registros

A estrutura de registros fornece aos desenvolvedores de APO meios adicionais de coletar dados para melhorar o desenvolvimento e a depuração. Essa estrutura unifica os vários métodos de registros em log usados por diferentes fornecedores e o vincula aos provedores de registros em log de rastreamento de áudio para criar um registro mais significativo. A nova estrutura fornece uma API de registro em log, deixando o restante do trabalho a ser feito pelo sistema operacional.

O provedor é definido como:

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

Cada APO tem sua própria ID de atividade. Como isso usa o mecanismo de registro de rastreamento existente, as ferramentas de console existentes podem ser usadas para filtrar esses eventos e exibi-los em tempo real. Você pode usar ferramentas existentes, como tracelog e tracefmt, conforme descrito em Ferramentas para rastreamento de software - drivers do Windows. Para obter mais informações sobre sessões de rastreamento, consulte Como criar uma sessão de rastreamento com um GUID de controle.

Os eventos de registro em log de rastreamento não são marcados como telemetria e não serão exibidos como um provedor de telemetria em ferramentas como xperf.

Implementação - Estrutura de registros

A estrutura de registros é baseada nos mecanismos de registros em log fornecidos pelo rastreamento ETW. Para obter mais informações sobre o ETW, veja Rastreamento de eventos. Isso não se destina a registrar dados de áudio, mas sim a registrar eventos que normalmente são registrados na produção. As APIs de log não devem ser usadas a partir do thread de streaming em tempo real, pois elas têm o potencial de fazer com que o thread de carregamento de dados seja antecipado pelo agendador da CPU do sistema operacional. O registro em log deve ser usado principalmente para eventos que ajudarão com problemas de depuração que são frequentemente encontrados no campo.

Definição de API - Estrutura de registros

A estrutura de registros introduz a interface IAudioProcessingObjectLoggingService que fornece um novo serviço de log para APOs.

Para obter mais informações, consulte IAudioProcessingObjectLoggingService.

Código de exemplo - Estrutura de registros

O exemplo demonstra o uso do método IAudioProcessingObjectLoggingService::ApoLog e como esse ponteiro de interface é obtido em IAudioProcessingObject::Initialize.

Exemplo de log do 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;
}

Estrutura de registros

A estrutura de threading que permite que os efeitos sejam multithreaded usando filas de trabalho de uma tarefa MMCSS (Serviços do agendador de classe multimídia) apropriada por meio de uma API simples. A criação de filas de trabalho seriais em tempo real e sua associação com o thread principal de carregamento de dados são tratadas pelo sistema operacional. Essa estrutura permite que os APOs coloquem itens de trabalho de execução curta na fila. A sincronização entre tarefas continua a ser de responsabilidade do APO. Para obter mais informações sobre threading MMCSS, consulte Serviço Agendador de Classe Multimídia e API de Fila de Trabalho em Tempo Real.

Definições de API - Estrutura de Threading

A estrutura Threading introduz a interface IAudioProcessingObjectQueueService que fornece acesso à fila de trabalho em tempo real para APOs.

Para obter mais informações, localize conteúdo adicional nas seguintes páginas:

Código de exemplo - Estrutura de Threading

Este exemplo demonstra o uso do método IAudioProcessingObjectRTQueueService::GetRealTimeWorkQueue e como o ponteiro de interface IAudioProcessingObjectRTQueueService é obtido em 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;
}

Para obter mais exemplos de como utilizar essa interface, consulte o seguinte código de exemplo:

Descoberta e controle de efeitos de áudio para efeitos

A estrutura de descoberta permite que o sistema operacional controle os efeitos de áudio em sua transmissão. Essas APIs fornecem suporte para cenários nos quais o usuário de um aplicativo precisa controlar certos efeitos em transmissões (por exemplo, supressão de ruído profundo). Para conseguir isso, essa estrutura adiciona o seguinte:

  • Uma nova API para consulta de um APO para determinar se um efeito de áudio pode ser habilitado ou desabilitado.
  • Uma nova API para definir o estado de um efeito de áudio como ativado/desativado.
  • Uma notificação quando há uma alteração na lista de efeitos de áudio ou quando os recursos ficam disponíveis para que um efeito de áudio agora possa ser ativado/desabilitado.

Implementação - Descoberta de efeitos de áudio

Um APO precisa implementar a interface IAudioSystemEffects3 se pretende expor efeitos que podem ser habilitados e desabilitados dinamicamente. Um APO expõe seus efeitos de áudio por meio da função IAudioSystemEffects3::GetControllableSystemEffectsList e habilita e desabilita seus efeitos de áudio por meio da função IAudioSystemEffects3::SetAudioSystemEffectState .

Código de exemplo - Descoberta de efeito de áudio

O código de exemplo Descoberta de efeito de áudio pode ser encontrado no exemplo SwapAPOSFX - swapaposfx.cpp.

O código de exemplo a seguir ilustra como recuperar a lista de efeitos configuráveis. Exemplo de 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;
}

O código de exemplo a seguir ilustra como habilitar e desabilitar efeitos. Exemplo de 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;
}

Reutilização dos APOs WM SFX e MFX no Windows 11, versão 22H2

A partir do Windows 11, versão 22H2, os arquivos de configuração INF que reutilizam as caixas de entrada WM SFX e MFX APOs, agora podem reutilizar as APOs CAPX SFX e MFX. Esta seção descreve as três maneiras de fazer isso.

Há três pontos de inserção para APOs: renderização pré-mix, renderização pós-mix e captura. O mecanismo de áudio de cada dispositivo lógico oferece suporte a uma instância de um APO de renderização pré-mix por transmissão (SFX de renderização) e um APO de renderização pós-mix (MFX). O mecanismo de áudio também suporta uma instância de um APO de captura (captura SFX) que é inserido em cada transmissão de captura. Para obter mais informações sobre como reutilizar ou encapsular os APOs da caixa de entrada, consulte Combinar APOs personalizados e do Windows.

Os APOs CAPX SFX e MFX podem ser reutilizados de uma das três maneiras a seguir.

Uso da seção INF DDInstall

Use mssysfx. CopyFilesAndRegisterCapX de wdmaudio.inf adicionando as seguintes entradas.

   Include=wdmaudio.inf
   Needs=mssysfx.CopyFilesAndRegisterCapX

Uso de um arquivo INF de extensão

O wdmaudioapo.inf é a extensão de classe AudioProcessingObject inf. Ele contém o registro específico do dispositivo dos APOs SFX e MFX.

Fazendo referência direta aos APOs WM SFX e MFX para efeitos de transmissão e modo

Para fazer referência direta a esses APOs para efeitos de transmissão e modo, use os seguintes valores GUID.

  • Use {C9453E73-8C5C-4463-9984-AF8BAB2F5447} como o WM SFX APO
  • Use {13AB3EBD-137E-4903-9D89-60BE8277FD17} como o WM MFX APO.

SFX (Transmissão) e MFX (Modo) foram referidos no Windows 8.1 para LFX (local) e MFX foi referido como GFX (global). Essas entradas do registro continuam a usar os nomes anteriores.

O registro específico do dispositivo usa HKR em vez de HKCR.

O arquivo INF precisará ter as seguintes entradas adicionadas.

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

Essas entradas de arquivo INF criarão um repositório de propriedades que será usado pelas APIs do Windows 11 para os novos APOs.

PKEY_FX_Association no INF ex. HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY% deve ser substituído por HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%.

Confira também

Objetos de processamento de áudio do Windows.