API de Windows 11 para objetos de procesamiento de audio

En este tema se presenta un conjunto de nuevas API de Windows 11 para objetos de procesamiento de audio (API) que se incluyen con un controlador de audio.

Windows permite a los fabricantes de hardware de audio de terceros incluir efectos personalizados de procesamiento de señales digitales basados en host. Estos efectos se empaquetan como objetos de procesamiento de audio (APO) en modo de usuario. Para obtener más información, vea Objetos de procesamiento de audio de Windows.

Algunas de las API que se describen aquí permiten nuevos escenarios para proveedores de hardware independientes (IHV) y proveedores de software independientes (ISV), mientras que otras API están pensadas para proporcionar alternativas que mejoran la confiabilidad general del audio y las funcionalidades de depuración.

  • El marco de cancelación de eco acústico (AEC) permite a un APO identificarse como AEC APO , concediéndole acceso a un flujo de referencia y controles adicionales.
  • El marco Configuración permitirá que las API expongan métodos para consultar y modificar el almacén de propiedades para efectos de audio ("almacén de propiedades FX") en un punto de conexión de audio. Cuando un APO implementa estos métodos, las aplicaciones de soporte técnico de hardware (HSA) que están asociadas a ese APO pueden invocarlos.
  • El marco de notificaciones permite que los efectos de audio (APO) soliciten notificaciones para controlar los cambios en el volumen, el punto de conexión y los efectos de audio.
  • El marco de registro ayuda en el desarrollo y la depuración de LAS API.
  • El marco de subprocesos permite que las API sean multiproceso mediante un grupo de subprocesos registrado por MMCSS administrado por el sistema operativo.
  • Las API de detección y control de efectos de audio permiten que el sistema operativo detecte, habilite y deshabilite los efectos que están disponibles para su procesamiento en una secuencia.

Para aprovechar estas nuevas API, se espera que las API usen la nueva interfaz IAudioSystemEffects3 . Cuando un APO implementa esta interfaz, el sistema operativo interpreta esto como una señal implícita de que el APO admite el marco de configuración de APO y permite que el APO se suscriba a notificaciones comunes relacionadas con audio desde el motor de audio.

Windows 11 requisitos de desarrollo de APO CAPX

Las nuevas API que se incluyen en un dispositivo para Windows 11 deben ser compatibles con las API enumeradas en este tema, validadas a través de HLK. Además, se espera que las API que aprovechen AEC sigan la implementación descrita en este tema, validada a través de HLK. Se espera que las implementaciones personalizadas para estas extensiones de procesamiento de audio principales (Configuración, registro, notificaciones, subprocesos, AEC) aprovechen las API capx. Esto se validará a través de las pruebas de Windows 11 HLK. Por ejemplo, si un APO usa datos del Registro para guardar la configuración en lugar de usar el marco de configuración, se producirá un error en la prueba HLK asociada.

Requisitos de la versión de Windows

Las API descritas en este tema están disponibles a partir de la compilación 22000 del sistema operativo Windows 11, WDK y SDK. Windows 10 no tendrá compatibilidad con estas API. Si un APO pretende funcionar tanto en Windows 10 como en Windows 11, puede examinar si se está inicializando con la estructura APOInitSystemEffects2 o APOInitSystemEffects3 para determinar si se ejecuta en un sistema operativo que admite las API capx.

Las versiones más recientes de Windows, WDK y el SDK se pueden descargar a continuación a través del Programa Windows Insider. Los partners que participan con Microsoft a través del Centro de partners también pueden acceder a este contenido a través de Collaborate. Para obtener más información sobre Collaborate, consulte Introducción a Microsoft Collaborate.

Windows 11 contenido de WHCP se ha actualizado para proporcionar a los asociados los medios para validar estas API.

El código de ejemplo para el contenido descrito en este tema se puede encontrar aquí: Audio/SYSVAD/APO- github

Cancelación de eco acústico (AEC)

La cancelación de eco acústico (AEC) es un efecto de audio común implementado por proveedores de hardware independientes (IHVs) y proveedores de software independientes (ISV) como un objeto de procesamiento de audio (APO) en la canalización de captura de micrófono. Este efecto es diferente de otros efectos que suelen implementar los IVS e ISV en que requiere 2 entradas: una secuencia de audio del micrófono y una secuencia de audio de un dispositivo de representación que actúa como señal de referencia.

Este nuevo conjunto de interfaces permite que un AEC APO se identifique como tal en el motor de audio. Al hacerlo, el motor de audio configura el APO correctamente con varias entradas y una única salida.

Cuando un APO implementa las nuevas interfaces de AEC, el motor de audio:

  • Configure el APO con una entrada adicional que proporcione el APO con el flujo de referencia desde un punto de conexión de representación adecuado.
  • Cambie las secuencias de referencia a medida que cambia el dispositivo de representación.
  • Permita que un APO controle el formato del micrófono de entrada y el flujo de referencia.
  • Permitir que un APO obtenga marcas de tiempo en el micrófono y las secuencias de referencia.

Enfoque anterior: Windows 10

Las API son de entrada única: objetos de salida únicos. El motor de audio proporciona un APO AEC de audio desde el punto de conexión del micrófono en su entrada. Para obtener la secuencia de referencia, un APO puede interactuar con el controlador mediante interfaces propietarias para recuperar el audio de referencia del punto de conexión de representación o usar WASAPI para abrir una secuencia de bucle invertido en el punto de conexión de representación.

Ambos enfoques anteriores tienen inconvenientes:

  • Un APO de AEC que usa canales privados para obtener una secuencia de referencia del controlador, normalmente solo puede hacerlo desde el dispositivo de representación de audio integrado. Como resultado, la cancelación de eco no funcionará si el usuario está reproduciendo audio fuera del dispositivo no integrado, como el dispositivo de audio USB o Bluetooth. Solo el sistema operativo es consciente de los puntos de conexión de representación correctos que pueden servir como puntos de conexión de referencia.

  • Un APO puede usar WASAPI para elegir el punto de conexión de representación predeterminado para realizar la cancelación del eco. Sin embargo, hay algunos problemas que hay que tener en cuenta al abrir una secuencia de bucle invertido desde el proceso de audiodg.exe (que es donde se hospeda el APO).

    • La secuencia de bucle invertido no se puede abrir o destruir cuando el motor de audio llama a los métodos de APO principales, ya que esto puede dar lugar a un interbloqueo.
    • Un APO de captura no conoce el estado de las secuencias de sus clientes. Es decir, una aplicación de captura podría tener una secuencia de captura en el estado "STOP", pero el APO no es consciente de este estado y, por lo tanto, mantiene abierta la secuencia de bucle invertido en el estado "RUN", que es ineficaz en términos de consumo de energía.

Definición de API: AEC

El marco AEC proporciona nuevas estructuras e interfaces que las API pueden aprovechar. Estas nuevas estructuras e interfaces se describen a continuación.

estructura de APO_CONNECTION_PROPERTY_V2

Las API que implementan la interfaz IApoAcousticEchoCancellation se pasarán una estructura de APO_CONNECTION_PROPERTY_V2 en su llamada a IAudioProcessingObjectRT::APOProcess. Además de todos los campos de la estructura APO_CONNECTION_PROPERTY , la versión 2 de la estructura también proporciona información de marca de tiempo para los búferes de audio.

Un APO puede examinar el campo APO_CONNECTION_PROPERTY.u32Signature para determinar si la estructura que recibe del motor de audio es de tipo APO_CONNECTION_PROPERTY o APO_CONNECTION_PROPERTY_V2. APO_CONNECTION_PROPERTY estructuras tienen una firma de APO_CONNECTION_PROPERTY_SIGNATURE, mientras que APO_CONNECTION_PROPERTY_V2 tienen una firma que es igual a APO_CONNECTION_PROPERTY_V2_SIGNATURE. Si la firma tiene un valor que es igual a APO_CONNECTION_PROPERTY_V2_SIGNATURE, el puntero a la estructura APO_CONNECTION_PROPERTY se puede convertir de forma segura en un puntero de APO_CONNECTION_PROPERTY_V2.

El código siguiente procede del ejemplo Aec APO MFX: AecApoMfx.cpp y muestra la redifusión.

    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

La interfaz IApoAcousticEchoCancellation no tiene métodos explícitos en él. Su propósito es identificar un APO de AEC en el motor de audio. Esta interfaz solo se puede implementar mediante efectos de modo (MFX) en puntos de conexión de captura. La implementación de esta interfaz en cualquier otro APO provocará un error al cargar ese APO. Para obtener información general sobre MFX, vea Arquitectura de objetos de procesamiento de audio.

Si el efecto de modo en un punto de conexión de captura se implementa como una serie de API encadenadas, solo el APO más cercano al dispositivo puede implementar esta interfaz. Las API que implementan esta interfaz se ofrecerán a la estructura de APO_CONNECTION_PROPERTY_V2 en su llamada a IAudioProcessingobjectRT::APOProcess. El APO puede comprobar si hay una firma de APO_CONNECTION_PROPERTY_V2_SIGNATURE en la propiedad de conexión y escribir la estructura de APO_CONNECTION_PROPERTY entrante en una estructura de APO_CONNECTION_PROPERTY_V2.

En reconocimiento del hecho de que las API de AEC normalmente ejecutan sus algoritmos a una velocidad de muestreo o recuento de canales específico, el motor de audio proporciona compatibilidad de remuestreo a las API que implementan la interfaz IApoAcousticEchoCancellation.

Cuando un APO de AEC devuelve APOERR_FORMAT_NOT_SUPPORTED en la llamada a IAudioProcessingObject::OutInputFormatSupported, el motor de audio llamará a IAudioProcessingObject::IsInputFormatSupported en el APO de nuevo con un formato de salida NULL y un formato de entrada no NULL, para obtener el formato sugerido del APO. A continuación, el motor de audio volverá a aplicar el audio del micrófono al formato sugerido antes de enviarlo al AEC APO. Esto elimina la necesidad de que el APO de AEC implemente la frecuencia de muestreo y la conversión del recuento de canales.

IApoAuxiliaryInputConfiguration

La interfaz IApoAuxiliaryInputConfiguration proporciona métodos que las API pueden implementar para que el motor de audio pueda agregar y quitar secuencias de entrada auxiliares.

El APO de AEC implementa esta interfaz y la usa el motor de audio para inicializar la entrada de referencia. En Windows 11, el AEC APO solo se inicializará con una única entrada auxiliar, una que tenga la secuencia de audio de referencia para la cancelación de eco. El método AddAuxiliaryInput se usará para agregar la entrada de referencia al APO. Los parámetros de inicialización contendrán una referencia al punto de conexión de representación del que se obtiene el flujo de bucle invertido.

El motor de audio llama al método IsInputFormatSupported para negociar formatos en la entrada auxiliar. Si AEC APO prefiere un formato específico, puede devolver S_FALSE en la llamada a IsInputFormatSupported y especificar un formato sugerido. El motor de audio volverá a aplicar el audio de referencia al formato sugerido y lo proporcionará en la entrada auxiliar del AEC APO.

IApoAuxiliaryInputRT

La interfaz IApoAuxiliaryInputRT es la interfaz segura en tiempo real que se usa para impulsar las entradas auxiliares de un APO.

Esta interfaz se usa para proporcionar datos de audio en la entrada auxiliar al APO. Tenga en cuenta que las entradas de audio auxiliares no se sincronizan con las llamadas a IAudioProcessingObjectRT::APOProcess. Cuando no hay ningún audio que se represente en el punto de conexión de representación, los datos de bucle invertido no estarán disponibles en la entrada auxiliar. Es decir, no habrá llamadas a IApoAuxiliaryInputRT::AcceptInput

Resumen de las API CAPX de AEC

Para obtener más información, busque información adicional en las páginas siguientes.

Código de ejemplo: AEC

Consulte los siguientes ejemplos de código de Sysvad Audio AecApo.

El código siguiente del encabezado de ejemplo de Aec APO: AecAPO.h muestra los tres nuevos métodos públicos que se van a agregar.

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

El código siguiente procede del ejemplo Aec APO MFX : AecApoMfx.cpp y muestra la implementación de AddAuxiliaryInput, cuando el APO solo puede controlar una 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;

Revise también el código de ejemplo que muestra la implementación de CAecApoMFX::IsInputFormatSupported y CAecApoMFX::AcceptInput , así como el control de APO_CONNECTION_PROPERTY_V2.

Secuencia de operaciones: AEC

Al inicializar:

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

Al representar el cambio del dispositivo:

  1. IAudioProcessingObject::Initialize
  2. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  3. IAudioProcessingObjectConfiguration::LockForProcess
  4. Cambios de dispositivo predeterminados
  5. IAudioProcessingObjectConfiguration::UnlockForProcess
  6. IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
  7. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  8. IAudioProcessingObjectConfiguration::LockForProcess

Este es el comportamiento de búfer recomendado para AEC.

  • Los búferes obtenidos en la llamada a IApoAuxiliaryInputRT::AcceptInput deben escribirse en un búfer circular sin bloquear el subproceso principal.
  • En la llamada a IAudioProcessingObjectRT::APOProcess, el búfer circular debe leerse para el paquete de audio más reciente de la secuencia de referencia y este paquete debe usarse para ejecutarse a través del algoritmo de cancelación de eco.
  • Las marcas de tiempo de los datos de referencia y micrófono se pueden usar para alinear los datos del altavoz y del micrófono.

Secuencia de bucle invertido de referencia

De forma predeterminada, la secuencia de bucle invertido "pulsa" (escucha) la secuencia de audio antes de que se aplique cualquier volumen o exclusión. Una secuencia de bucle invertido pulsada antes de que se haya aplicado el volumen se conoce como un flujo de bucle invertido anterior al volumen. Una ventaja de tener una secuencia de bucle invertido de volumen previo es una secuencia de audio clara y uniforme, independientemente de la configuración actual del volumen.

Algunos algoritmos de AEC pueden preferir obtener una secuencia de bucle invertido que se haya conectado después de cualquier procesamiento de volumen (incluida la silenciación). Esta configuración se conoce como bucle invertido posterior al volumen.

En la siguiente versión principal de las API de Windows AEC puede solicitar bucle invertido posterior al volumen en los puntos de conexión admitidos.

Limitaciones

A diferencia de los flujos de bucle invertido de volumen previos, que están disponibles para todos los puntos de conexión de representación, es posible que los flujos de bucle invertido posteriores al volumen no estén disponibles en todos los puntos de conexión.

Solicitud de bucle invertido posterior al volumen

Las API de AEC que quieran usar bucle invertido posterior al volumen deben implementar la interfaz IApoAcousticEchoCancellation2 .

Un AEC APO puede solicitar bucle invertido posterior al volumen devolviendo la marca de APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK a través del parámetro Properties en su implementación de IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties.

Dependiendo del punto de conexión de representación que se esté usando actualmente, es posible que el bucle invertido posterior al volumen no esté disponible. Se notifica a AEC APO si se usa el bucle invertido posterior al volumen cuando se llama al método IApoAuxiliaryInputConfiguration::AddAuxiliaryInput . Si el campo streamProperties AcousticEchoCanceller_Reference_Input contiene APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK, el bucle invertido posterior al volumen está en uso.

El código siguiente del encabezado de ejemplo AEC APO- AecAPO.h muestra los tres nuevos métodos públicos que se van a agregar.

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;

El siguiente fragmento de código procede del ejemplo AEC APO MFX: AecApoMfx.cpp y muestra la implementación de GetDesiredReferenceStreamProperties y la parte 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.

Marco de configuración

El marco de configuración permite a las API exponer métodos para consultar y modificar el almacén de propiedades para efectos de audio ("FX Property Store") en un punto de conexión de audio. Las API y las aplicaciones de soporte técnico de hardware (HSA) que desean comunicar la configuración a ese APO pueden usar este marco. Los HSA pueden ser aplicaciones para la Plataforma universal de Windows (UWP) y requieren una funcionalidad especial para invocar las API en el marco de configuración. Para obtener más información sobre las aplicaciones de HSA, consulta Aplicaciones de dispositivos para UWP.

FxProperty Store (Estructura del almacén)

El nuevo almacén fxProperty tiene tres substores: Default, User y Volatile.

La subclave "Default" contiene propiedades de efectos personalizados y se rellena desde el archivo INF. Estas propiedades no se conservan en las actualizaciones del sistema operativo. Por ejemplo, las propiedades que normalmente se definen en un INF caben aquí. A continuación, estos se volverían a rellenar desde el INF.

La subclave "Usuario" contiene la configuración del usuario relativa a las propiedades de efectos. El sistema operativo conserva estas configuraciones en las actualizaciones y migraciones. Por ejemplo, los valores preestablecidos que el usuario pueda configurar que se espera que persistan en la actualización.

La subclave "Volatile" contiene propiedades de efectos volátiles. Estas propiedades se pierden al reiniciar el dispositivo y se borran cada vez que el punto de conexión pasa a activo. Se espera que contengan propiedades de variante de tiempo (por ejemplo, en función de las aplicaciones en ejecución actuales, la posición del dispositivo, etc.). Por ejemplo, cualquier configuración que dependa del entorno actual.

La forma de pensar en el usuario frente al valor predeterminado es si desea que las propiedades se conserven en el sistema operativo y las actualizaciones de controladores. Las propiedades de usuario se conservarán. Las propiedades predeterminadas se volverán a rellenar desde inf.

Contextos de APO

La configuración capx framwork permite a un autor de APO agrupar las propiedades de APO por contextos. Cada APO puede definir su propio contexto y actualizar las propiedades relativas a su propio contexto. El almacén de propiedades de efectos para un punto de conexión de audio puede tener cero o más contextos. Sin embargo, los proveedores pueden crear contextos, ya sea por SFX/MFX/EFX o por modo. Un proveedor también podría optar por tener un único contexto para todas las API enviadas por ese proveedor.

Funcionalidad restringida de configuración

La API de configuración está pensada para admitir todos los oem y desarrolladores de HSA interesados en consultar y modificar la configuración de efectos de audio asociada a un dispositivo de audio. Esta API se expone a una aplicación HSA y Win32 para proporcionar acceso al almacén de propiedades a través de la funcionalidad restringida "audioDeviceConfiguration" que se debe declarar en el manifiesto. Además, se debe declarar un espacio de nombres correspondiente de la siguiente manera:

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

IAudioSystemEffectsPropertyStore es legible y grabable por un servicio ISV/IHV, una aplicación de almacén de UWP, aplicaciones de escritorio que no son de administración y API. Además, esto puede actuar como mecanismo para que las API devuelvan mensajes a un servicio o aplicación de almacén de UWP.

Nota

Se trata de una funcionalidad restringida: si se envía una aplicación con esta funcionalidad a Microsoft Store, se desencadenará un examen cercano. La aplicación debe ser una aplicación de soporte técnico de hardware (HSA) y se examinará para evaluar que realmente es un HSA antes de que se apruebe el envío.

Definición de API: Marco de configuración

La nueva interfaz IAudioSystemEffectsPropertyStore permite a un HSA acceder a los almacenes de propiedades de efectos del sistema de audio y registrar las notificaciones de cambio de propiedad.

La función ActiveAudioInterfaceAsync proporciona un método para obtener la interfaz IAudioSystemEffectsPropertyStore de forma asincrónica.

Una aplicación puede recibir notificaciones cuando cambia el almacén de propiedades de efectos del sistema mediante la nueva interfaz de devolución de llamada IAudioSystemEffectsPropertyChangeNotificationClient .

Aplicación que intenta obtener IAudioSystemEffectsPropertyStore mediante IMMDevice::Activate

En el ejemplo se muestra cómo una aplicación de soporte técnico de hardware puede usar IMMDevice::Activate para activar IAudioSystemEffectsPropertyStore. En el ejemplo se muestra cómo usar IAudioSystemEffectsPropertyStore para abrir un IPropertyStore que tenga la configuración de usuario.

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

Ejemplo con ActivateAudioInterfaceAsync

Este ejemplo hace lo mismo que el ejemplo anterior, pero en lugar de usar IMMDevice, usa la API ActivateAudioInterfaceAsync para obtener la interfaz IAudioSystemEffectsPropertyStore de forma asincrónica.

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

Código IAudioProcessingObject::Initialize mediante IAudioSystemEffectsPropertyStore

El ejemplo muestra la implementación de un APO puede usar la estructura APOInitSystemEffects3 para recuperar las interfaces IPropertyStore predeterminadas y volátiles del usuario para el APO, durante la inicialización del 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 dentified 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 aplicaciones para notificaciones de cambio de propiedad

En el ejemplo se muestra el uso del registro para las notificaciones de cambio de propiedad. Esto no debe usarse desde con el APO y las aplicaciones win32 deben usarse.

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

// Unsubscrbe 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 ejemplo: Marco de configuración

Este código de ejemplo procede del ejemplo de APO de intercambio de SFX sysvad: 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);
    }
}

Sección INF: Marco de configuración

La sintaxis del archivo INF para declarar propiedades de efecto mediante el nuevo marco de configuración CAPX es la siguiente:

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

Esto reemplaza la sintaxis anterior para declarar las propiedades del efecto de la siguiente manera:

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

Inf no puede tener tanto la entrada IAudioSystemEffectsPropertyStore como la entrada IPropertyStore para el mismo punto de conexión de audio. Esto no se admite.

Ejemplo que muestra el uso del nuevo almacén de propiedades:

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

Marco de notificaciones

El marco de notificaciones permite que los efectos de audio (APO) soliciten y controle el volumen, el punto de conexión y las notificaciones de cambios del almacén de efectos de audio. Este marco está pensado para reemplazar las API existentes que usan las API para registrar y anular el registro de las notificaciones.

La nueva API presenta una interfaz que las API pueden usar para declarar el tipo de notificaciones en las que está interesado APO. Windows consultará el APO para las notificaciones que le interesan y reenviará la notificación a las API. Las API ya no necesitan llamar explícitamente a las API de registro o anulación del registro.

Las notificaciones se entregan a un APO mediante una cola serie. Cuando procede, la primera notificación difunde el estado inicial del valor solicitado (por ejemplo, el volumen del punto de conexión de audio). Las notificaciones se detienen una vez audiodg.exe dejan de usar un APO para el streaming. Las API dejarán de recibir notificaciones después de UnlockForProcess. Todavía es necesario sincronizar UnlockForProcess y las notificaciones en curso.

Implementación: marco de notificaciones

Para aprovechar el marco de notificaciones, un APO declara las notificaciones que le interesan. No hay llamadas explícitas de registro o anulación del registro. Todas las notificaciones al APO se serializan y es importante no bloquear el subproceso de devolución de llamada de notificación durante demasiado tiempo.

Definición de API: Marco de notificaciones

El marco de notificaciones implementa una nueva interfaz IAudioProcessingObjectNotifications que los clientes pueden implementar para registrar y recibir notificaciones comunes relacionadas con audio para el punto de conexión de APO y las notificaciones de efectos del sistema.

Para obtener más información, busque contenido adicional en las páginas siguientes:

Código de ejemplo: Marco de notificaciones

En el ejemplo se muestra cómo un APO puede implementar la interfaz IAudioProcessingObjectNotifications. En el método GetApoNotificationRegistrationInfo, el APO de ejemplo registra las notificaciones en los cambios en los almacenes de propiedades de efectos del sistema.
El sistema operativo invoca el método HandleNotification para notificar al APO los cambios que coinciden con lo que el APO había 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 dentified 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 a 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 ficticious
        // 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;
    }
}

El código siguiente procede del ejemplo swap APO MFX: swapapomfx.cpp y muestra el registro de eventos, devolviendo una 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 a 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;
}

El código siguiente procede del ejemplo SwapAPO MFX HandleNotifications: swapapomfx.cpp y muestra cómo controlar las notificaciones.

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

Marco de registro

El marco de registro proporciona a los desarrolladores de APO medios adicionales para recopilar datos para mejorar el desarrollo y la depuración. Este marco unifica los distintos métodos de registro utilizados por distintos proveedores y lo vincula a los proveedores de registro de seguimiento de audio para crear un registro más significativo. El nuevo marco proporciona una API de registro, dejando el resto del trabajo que el sistema operativo debe realizar.

El proveedor se define 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 tiene su propio identificador de actividad. Dado que usa el mecanismo de registro de seguimiento existente, las herramientas de consola existentes se pueden usar para filtrar estos eventos y mostrarlos en tiempo real. Puede usar herramientas existentes como tracelog y tracefmt, como se describe en Herramientas para el seguimiento de software: controladores de Windows. Para obtener más información sobre las sesiones de seguimiento, consulte Creación de una sesión de seguimiento con un GUID de control.

Los eventos de registro de seguimiento no se marcan como telemetría y no se mostrarán como proveedor de telemetría en herramientas como xperf.

Implementación: marco de registro

El marco de registro se basa en los mecanismos de registro proporcionados por el seguimiento de ETW. Para obtener más información sobre ETW, consulte Seguimiento de eventos. Esto no está pensado para registrar datos de audio, sino para registrar eventos que normalmente se registran en producción. Las API de registro no se deben usar desde el subproceso de streaming en tiempo real, ya que tienen la posibilidad de hacer que el subproceso de bomba se empiece previamente mediante el programador de CPU del sistema operativo. El registro debe usarse principalmente para eventos que le ayudarán a depurar problemas que a menudo se encuentran en el campo.

Definición de API: Marco de registro

El marco de registro presenta la interfaz IAudioProcessingObjectLoggingService que proporciona un nuevo servicio de registro para las API.

Para obtener más información, vea IAudioProcessingObjectLoggingService.

Código de ejemplo: marco de registro

En el ejemplo se muestra el uso del método IAudioProcessingObjectLoggingService::ApoLog y cómo se obtiene este puntero de interfaz en IAudioProcessingObject::Initialize.

Ejemplo de registro de 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 Initializion completed");
    }
    return S_OK;
}

Marco de subprocesos

El marco de subprocesos que permite que los efectos sean multiproceso mediante colas de trabajo de una tarea adecuada del Servicio de programador de clases multimedia (MMCSS) a través de una API sencilla. El sistema operativo controla la creación de colas de trabajo en serie en tiempo real y su asociación con el subproceso de bomba principal. Este marco permite a las API poner en cola elementos de trabajo de ejecución corta. La sincronización entre tareas sigue siendo responsabilidad del APO. Para obtener más información sobre el subproceso MMCSS, consulte Servicio de programador de clases multimedia y API de cola de trabajo en tiempo real.

Definiciones de API: Threading Framework

El marco threading presenta la interfaz IAudioProcessingObjectQueueService que proporciona acceso a la cola de trabajo en tiempo real para las API.

Para obtener más información, busque contenido adicional en las páginas siguientes:

Código de ejemplo: Marco de subprocesos

En este ejemplo se muestra el uso del método IAudioProcessingObjectRTQueueService::GetRealTimeWorkQueue y cómo se obtiene el puntero de interfaz IAudioProcessingObjectRTQueueService en 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 obtener más ejemplos de cómo usar esta interfaz, consulte el código de ejemplo siguiente:

Detección y control de efectos de audio para efectos

El marco de detección permite que el sistema operativo controle los efectos de audio en su secuencia. Estas API proporcionan compatibilidad con escenarios en los que el usuario de una aplicación necesita controlar determinados efectos en secuencias (por ejemplo, supresión de ruido profundo). Para ello, este marco agrega lo siguiente:

  • Una nueva API para consultar desde un APO para determinar si se puede habilitar o deshabilitar un efecto de audio.
  • Nueva API para establecer el estado de un efecto de audio en activado o desactivado.
  • Notificación cuando hay un cambio en la lista de efectos de audio o cuando los recursos están disponibles para que ahora se pueda habilitar o deshabilitar un efecto de audio.

Implementación: detección de efectos de audio

Un APO debe implementar la interfaz IAudioSystemEffects3 si pretende exponer efectos que se pueden habilitar y deshabilitar dinámicamente. Un APO expone sus efectos de audio a través de la función IAudioSystemEffects3::GetControllableSystemEffectsList y habilita y deshabilita sus efectos de audio a través de la función IAudioSystemEffects3::SetAudioSystemEffectState .

Código de ejemplo: detección de efectos de audio

El código de ejemplo de detección de efectos de audio se puede encontrar en el ejemplo SwapAPOSFX - swapaposfx.cpp.

En el código de ejemplo siguiente se muestra cómo recuperar la lista de efectos configurables. Ejemplo 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;
}

En el código de ejemplo siguiente se muestra cómo habilitar y deshabilitar efectos. Ejemplo 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;
}

Reutilización de las API de WM SFX y MFX en Windows 11, versión 22H2

A partir de Windows 11, versión 22H2, los archivos de configuración inf que reutilizan las API de SFX y MFX de bandeja de entrada, ahora pueden reutilizar las API de CAPX SFX y MFX. En esta sección se describen las tres maneras de hacerlo.

Hay tres puntos de inserción para las API: representación previa a la combinación, representación posterior a la combinación y captura. El motor de audio de cada dispositivo lógico admite una instancia de un APO de representación previa de la combinación por secuencia (representar SFX) y un APO de representación posterior a la combinación (MFX). El motor de audio también admite una instancia de un APO de captura (capture SFX) que se inserta en cada secuencia de captura. Para obtener más información sobre cómo reutilizar o encapsular las API de bandeja de entrada, consulte Combinar API personalizadas y windows.

Las API de CAPX SFX y MFX se pueden reutilizar de una de las tres maneras siguientes.

Usar la sección INF DDInstall

Use mssysfx. CopyFilesAndRegisterCapX desde wdmaudio.inf agregando las siguientes entradas.

   Include=wdmaudio.inf
   Needs=mssysfx.CopyFilesAndRegisterCapX

Uso de un archivo INF de extensión

wdmaudioapo.inf es la extensión de clase AudioProcessingObject inf. Contiene el registro específico del dispositivo de las API de SFX y MFX.

Hacer referencia directamente a las API de WM SFX y MFX para efectos de secuencia y modo

Para hacer referencia directamente a estas API para los efectos de secuencia y modo, use los siguientes valores GUID.

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

SFX (Stream) y MFX (Mode) se denominaron en Windows 8.1 a LFX (local) y MFX se denominaba GFX (global). Estas entradas del Registro siguen usando los nombres anteriores.

El registro específico del dispositivo usa HKR en lugar de HKCR.

El archivo INF tendrá que agregar las siguientes propiedades.

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

Estas entradas de archivo INF crearán un almacén de propiedades que usarán las API de Windows 11 para las nuevas API.

PKEY_FX_Association en el ex INF. HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%, debe reemplazarse por HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%.

Consulte también

Objetos de procesamiento de audio de Windows.