Compartir a través de


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 de procesamiento de señales digitales basados en host personalizados. Estos efectos se empaquetan como objetos de procesamiento de audio (APOs) 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 diseñadas 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 Controlador de Procesamiento de Audio (APO) identificarse como un APO de AEC, concediéndole acceso a un flujo de referencia y controles adicionales.
  • El marco de 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, se pueden invocar mediante aplicaciones de soporte técnico de hardware (HSA) asociadas a ese APO.
  • El marco de notificaciones permite que los efectos de audio (APOs) soliciten notificaciones para gestionar los cambios en el almacén de propiedades de volumen, punto final y efectos de audio.
  • El marco de registro ayuda al desarrollo y la depuración de los APOs.
  • 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 los APOs utilicen la nueva interfaz IAudioSystemEffects3. Cuando un APO implementa esta interfaz, el sistema operativo lo interpreta 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 las notificaciones comunes relacionadas con el audio desde el motor de audio.

Requisitos de desarrollo de APO CAPX de Windows 11

Cualquier nuevo APO que se incluya en un dispositivo para Windows 11 debe ser compatible 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 que se describe 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 HLK de Windows 11. 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 de 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 en Windows 10 y Windows 11, puede examinar si se está inicializando con APOInitSystemEffects2 o la estructura 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 están comprometidos 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.

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

El código de ejemplo del 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 (IHD) 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 implementados normalmente por IVS e ISV en que requiere 2 entradas: una secuencia de audio desde el micrófono y una secuencia de audio de un dispositivo de representación que actúa como la señal de referencia.

Este nuevo conjunto de interfaces permite a AEC APO identificarse como tal en el motor de audio. Si lo hace, 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 hará lo siguiente:

  • Configura el APO con una entrada adicional que le proporcione el flujo de referencia desde un punto de conexión de renderizado adecuado.
  • Cambie las secuencias de referencia a medida que cambia el dispositivo de representación.
  • Permitir 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

Los APOs son objetos de entrada única – de salida única. El motor de audio proporciona a AEC APO el 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 final de renderizado o usar WASAPI para abrir un flujo de audio en bucle invertido en el punto final de renderizado.

Ambos enfoques anteriores tienen desventajas:

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

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

    • La corriente de retorno no se puede abrir ni destruir cuando el motor de audio está llamando a los métodos principales de APO, ya que esto puede provocar un bloqueo.
    • Un APO de captura no conoce el estado de los flujos 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 APO que implementen la interfaz IApoAcousticEchoCancellation se les pasará una estructura 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. Las estructuras APO_CONNECTION_PROPERTY tienen una firma de APO_CONNECTION_PROPERTY_SIGNATURE, mientras que las 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 de APO_CONNECTION_PROPERTY se puede escribir de forma segura en un puntero de APO_CONNECTION_PROPERTY_V2.

El código siguiente procede del ejemplo de 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 ella. 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 los 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 del 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. Los APOs que implementen esta interfaz recibirán la estructura 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 convertir la estructura APO_CONNECTION_PROPERTY entrante en una estructura APO_CONNECTION_PROPERTY_V2.

Dado que los APO de AEC normalmente ejecutan sus algoritmos a una tasa de muestreo/recuento de canales específicos, el motor de audio proporciona soporte de remuestreo a los APO 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á de nuevo a IAudioProcessingObject::IsInputFormatSupported en el APO con un formato de salida NULL y un formato de entrada no NULL, para obtener el formato sugerido del APO. El motor de audio remuestreará 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 conversión de frecuencia de muestreo y 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.

La AEC APO implementa esta interfaz y la usa el motor de audio para inicializar la entrada de referencia. En Windows 11, el APO de AEC solo se inicializará con una sola entrada auxiliar, una que tenga el flujo 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 endpoint de renderización del cual se obtiene el stream de loopback.

El motor de audio llama al método IsInputFormatSupported para negociar formatos en la entrada auxiliar. Si el APO de AEC prefiere un formato específico, puede devolver S_FALSE en la llamada a IsInputFormatSupported y especificar un formato sugerido. El motor de audio resampleará 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 se está produciendo ningún audio en el punto de salida del renderizado, los datos de retroalimentación 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 de 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 de 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 hilo principal.
  • En la llamada a IAudioProcessingObjectRT::APOProcess, se debe leer el búfer circular para obtener el paquete de audio más reciente del flujo de referencia. Este paquete debe utilizarse para ejecutar el 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 micrófono y del altavoz.

Flujo de bucle de retorno de referencia

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

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

En la siguiente versión principal de Windows, las APO de AEC podrán solicitar la retroalimentación posterior al volumen en los puntos de conexión admitidos.

Limitaciones

A diferencia de los flujos de bucle invertido antes del volumen, que están disponibles para todos los puntos de renderizado, es posible que los flujos de bucle invertido después del volumen no estén disponibles en todos los puntos.

Solicitud de bucle de retroalimentación posterior al volumen

Las APO de AEC que desean usar el bucle de retorno después del volumen deben implementar la interfaz IApoAcousticEchoCancellation2.

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

Según el punto de conexión de representación gráfica que se está utilizando actualmente, es posible que la retroalimentación posterior al volumen no esté disponible. Se notifica a un AEC APO si se está utilizando el bucle de retroalimentación posterior al volumen cuando se llama al método IApoAuxiliaryInputConfiguration::AddAuxiliaryInput. Si el campo streamProperties de AcousticEchoCanceller_Reference_Input contiene APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK, se usa el bucle invertido post-volumen.

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 que las API expongan 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.

Estructura de la Tienda de Propiedades Fx

El nuevo almacén FxProperty tiene tres subtiendas: 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í. Estos serían rellenados de nuevo desde el INF.

La subclave "Usuario" contiene la configuración del usuario relativa a las propiedades de efectos. El sistema operativo conserva esta configuración en las actualizaciones y migraciones. Por ejemplo, los valores preestablecidos que el usuario puede 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 manera de pensar en el usuario frente al valor predeterminado es si desea que las propiedades persistan a través de las actualizaciones del sistema operativo y los controladores. Las propiedades del usuario se conservarán. Las propiedades predeterminadas se restablecerán desde el INF.

Contextos de APO

El marco de configuración CAPX permite a un autor de APO agrupar las propiedades de APO por contextos. Cada APO puede definir su propio contexto y actualizar propiedades en relación con su propio contexto. El almacén de propiedades de efectos de un endpoint de audio puede tener cero o más contextos. Los proveedores pueden crear contextos libremente, ya sea que lo hagan 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.

Capacidad restringida de ajustes

La API de configuración está pensada para admitir a todos los OEM y desarrolladores de HSA interesados en consultar y modificar las configuraciones de efectos de audio asociadas a un dispositivo de audio. Esta API se expone a aplicaciones HSA y Win32 con el fin de proporcionar acceso al almacén de propiedades a través de la capacidad 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 editable por un servicio ISV/IHV, una aplicación de la tienda UWP, aplicaciones de escritorio sin privilegios de administrador y APOs. Además, esto puede actuar como mecanismo para que las API devuelvan mensajes a un servicio o a una 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 que un HSA acceda a los almacenes de propiedades de efectos del sistema de audio y registre 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;
}

El código IAudioProcessingObject::Initialize usando el IAudioSystemEffectsPropertyStore

El ejemplo demuestra que la implementación de un APO puede emplear la estructura APOInitSystemEffects3 para recuperar las interfaces IPropertyStore del usuario, predeterminadas y volátiles del APO, durante su inicialización.

#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 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 el APO y deben utilizarse las aplicaciones 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 ejemplo: Marco de configuración

Este código de ejemplo procede del ejemplo de SFX Swap APO de 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%,,,

El INF no puede tener la entrada IAudioSystemEffectsPropertyStore y la entrada IPropertyStore para el mismo punto de conexión de audio. 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 (APOs) soliciten y manejen las notificaciones de cambios de volumen, el punto de conexión y el almacén de propiedades de efectos de audio. Este marco está diseñado para reemplazar las API existentes que usan las APO para suscribirse y cancelar la suscripción a notificaciones.

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

Las notificaciones se entregan a un APO mediante una cola en 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 que audiodg.exe deja de tener la intención de usar un APO para streaming. Los APO 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

La estructura de notificaciones implementa una nueva interfaz IAudioProcessingObjectNotifications que los clientes pueden implementar para registrar y recibir notificaciones comunes relacionadas con audio, tanto para el punto de conexión de APO como para 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 se registra para recibir 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 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;
    }
}

El siguiente código es del ejemplo de intercambio de APO MFX: swapapomfx.cpp y muestra el registro para eventos, devolviendo una matriz de descriptores de notificación APO.

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

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 que usan los 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 realizará el sistema operativo.

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, se pueden usar herramientas de consola existentes para filtrar estos eventos y mostrarlos en tiempo real. Puede usar herramientas existentes como tracelog y tracefmt, como se describe en Herramientas para 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 rastreo no se marcan como telemetría y no se mostrarán como un 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 deben usarse desde el hilo de transmisión en tiempo real, ya que pueden hacer que el hilo de bombeo sea interrumpido por el planificador de CPU del sistema operativo. El registro se debe usar principalmente para eventos que ayudarán a identificar y depurar problemas que a menudo se encuentran en el entorno de producción.

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 Initialization completed");
    }
    return S_OK;
}

Marco de subprocesos

El marco de subproceso que permite que los efectos sean multiproceso mediante colas de trabajo de una tarea adecuada del Servicio de programador de clases multimedia (MMCSS) mediante 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 principal de la bomba. 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 programador de clases multimedia y Real-Time Work Queue API.

Definiciones de API: Threading Framework

El marco de subprocesos presenta la interfaz IAudioProcessingObjectQueueService que proporciona acceso a la cola de trabajo en tiempo real para los APO.

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

Código de ejemplo - Framework 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

El marco de descubrimiento permite al sistema operativo controlar los efectos de audio en su flujo. 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 lograrlo, este marco agrega lo siguiente:

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

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 descubrimiento 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 los WM SFX y MFX APOs integrados, ahora pueden reutilizar también los CAPX SFX y MFX APOs. 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 renderizado previo a la mezcla por secuencia (SFX) y un APO de renderizado posterior a la mezcla (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 los APO de la bandeja de entrada, consulte Combinar APO personalizados y Windows.

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

Uso de la sección INF DDInstall

Utilice mssysfx.CopyFilesAndRegisterCapX desde wdmaudio.inf añadiendo 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 flujo y modo

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

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

SFX (Stream) y MFX (Mode) se referían en Windows 8.1 como LFX (local) y MFX se refería como 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 deberá tener agregadas las siguientes entradas.

  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 los nuevos APOs.

PKEY_FX_Association en el INF ej. 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.