Compartir a través de


API para Windows 11 de objetos de procesamiento de audio

En este tema se presenta una serie de nuevas API para Windows 11 de objetos de procesamiento de audio (APO) que vienen incluidos 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, consulte Objetos de procesamiento de audio de Windows.

Algunas de las API que se describen aquí permiten nuevas aplicaciones para proveedores de hardware independientes (IHV) y proveedores de software independientes (ISV), mientras que otras API están diseñadas para aportar alternativas que mejoran la fiabilidad general del audio y las funcionalidades de depuración.

  • La plataforma Cancelación del eco acústico (Acoustic Echo Cancellation, AEC) permite a un APO identificarse como un APO de AEC, dándole acceso a una transmisión de referencia y controles adicionales.
  • La plataforma Configuración permite que los APO expongan los métodos para realizar consultas y modificaciones del 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 de hardware (HSA) asociadas a ese APO.
  • La plataforma Notificaciones permite que los efectos de audio (APO) soliciten notificaciones para controlar los cambios en el almacén de propiedades relacionados con el volumen, los puntos de conexión y los efectos de audio.
  • La plataforma de registro ayuda en el desarrollo y la depuración de los APO.
  • La plataforma Subprocesos permite que los APO se multiprocesen mediante un grupo de subprocesos registrados por MMCSS y administrados 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 procesarlos en una transmisión.

Para aprovechar estas nuevas API, se prevé que los APO 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 la plataforma Configuración de APO y permite que el APO se suscriba a las notificaciones comunes relacionadas con el audio del motor de audio.

Requisitos de desarrollo de APO CAPX en Windows 11

Los nuevos APO que se incluyen en un dispositivo para Windows 11 deben ser compatibles con las API que se describen en este tema, validadas a través de HLK. Además, se espera que los APO que hagan uso de la AEC respeten el proceso de implementación que se describe en este tema, validado a través de HLK. Se espera que las implementaciones personalizadas de estas extensiones de procesamiento de audio básicas (Configuración, Registro, Notificaciones, Subprocesos, AEC) aprovechen las API de CAPX. Esto se valida a través de las pruebas de HLK de Windows 11. Por ejemplo, si un APO usa datos de registro para guardar la configuración en lugar de usar la plataforma Configuración, se producirá un error en la prueba de HLK asociada.

Requisitos según las versiones de Windows

Las API descritas en este tema están disponibles a partir de la versión 22000 del sistema operativo Windows 11, WDK y SDK. Windows 10 no tiene compatibilidad con estas API. Si hay un APO que se quiera usar en Windows 10 y Windows 11, se puede examinar si se va a inicializar con la estructura APOInitSystemEffects2 o la estructura APOInitSystemEffects3 para determinar si se ejecuta en un sistema operativo que admite las API de CAPX.

Las versiones más recientes de Windows, WDK y SDK se pueden descargar más adelante a través del Programa Windows Insider. Los partners que trabajen 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.

Se ha modificado el contenido del programa WHCP de Windows 11 para dotar a los partners de los medios para validar estas API.

El código de ejemplo del contenido recogido 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 (IHV) y proveedores de software independientes (ISV) como un objeto de procesamiento de audio (APO) en el proceso de captura del micrófono. Este efecto difiere de otros efectos implementados normalmente por IHV e ISV, en los que se necesitan 2 entradas: una transmisión de audio desde el micrófono y una transmisión de audio desde un dispositivo de renderización que actúa como la señal de referencia.

Este nuevo conjunto de interfaces permite al APO de AEC 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 hace lo siguiente:

  • Configurar el APO con una entrada adicional que envíe al APO una transmisión de referencia desde un punto de conexión de renderización adecuado.
  • Cambiar las transmisiones de referencia a medida que cambia el dispositivo de renderización.
  • Permitir que un APO controle el formato del micrófono de entrada y la transmisión de referencia.
  • Permitir que un APO reciba marcas de tiempo en el micrófono y las transmisiones de referencia.

Método anterior: Windows 10

Los APO son de entrada única: objetos de salida únicos. El motor de audio envía al APO de AEC el audio desde el punto de conexión del micrófono en su enlace de entrada. Para obtener la transmisión de referencia, un APO puede interactuar con el controlador mediante interfaces propias para recuperar el audio de referencia del punto de conexión de renderización o usar WASAPI para abrir una transmisión de bucle invertido en el punto de conexión de renderización.

Ambos métodos anteriores tienen algunas desventajas:

  • Un APO de AEC que use canales privados para obtener una transmisión de referencia del controlador normalmente solo puede hacerlo desde el dispositivo de renderización de audio integrado. Como consecuencia, la cancelación de eco no funcionará si el usuario está reproduciendo audio fuera del dispositivo no integrado, como un dispositivo USB o el audio por Bluetooth. Solo el sistema operativo detecta los puntos de conexión de renderizació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 renderización predeterminado para activar la cancelación del eco. Sin embargo, hay algunos problemas que se deben tener en cuenta al abrir una transmisión de bucle invertido a través del proceso de audiodg.exe (que es donde se aloja el APO).

    • La transmisión 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 transmisiones de sus clientes. Es decir, una aplicación de captura podría tener una transmisión de captura en estado "STOP", pero el APO no detecta dicho estado y, por lo tanto, mantiene abierta la transmisión de bucle invertido en estado "RUN", que es ineficaz en términos de consumo de energía.

Definición de API - AEC

La plataforma AEC aportar nuevas estructuras e interfaces que los APO pueden aprovechar. Estas nuevas estructuras e interfaces se describen a continuación.

Estructura APO_CONNECTION_PROPERTY_V2

Los APO que implementan la interfaz IApoAcousticEchoCancellation se pasarán a una estructura APO_CONNECTION_PROPERTY_V2 en la 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 aporta información de las marcas de tiempo de 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 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 esta reestructuració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. Lo que se pretende 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 los MFX, consulte 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 APO encadenados, solo el APO más cercano al dispositivo podrá implementar esta interfaz. Los APO que implementan esta interfaz se ofrecerán a la estructura APO_CONNECTION_PROPERTY_V2 en la 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.

Si se reconoce el hecho de que los APO de AEC suelen ejecutar sus algoritmos a una velocidad de muestreo o con un número de canales específico, el motor de audio aceptará el remuestreo en los APO que implementen 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 volverá a implementar el audio del micrófono en el formato sugerido antes de enviarlo al APO de AEC. Esto hace que no sea necesario que el APO de AEC implemente la conversión de velocidad de muestreo ni de número de canales.

IApoAuxiliaryInputConfiguration

La interfaz IApoAuxiliaryInputConfiguration ofrece métodos que los APO pueden implementar para que el motor de audio pueda agregar y quitar transmisiones 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 APO de AEC únicamente se inicializará con una sola entrada auxiliar, una que tenga la transmisión 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 incluirán una referencia al punto de conexión de renderización del que se recibe la transmisión de bucle invertido.

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, este puede devolver S_FALSE en la llamada a IsInputFormatSupported e indicar un formato sugerido. El motor de audio volverá a implementar el audio de referencia con el formato sugerido y lo proporcionará en la entrada auxiliar del APO de AEC.

IApoAuxiliaryInputRT

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

Esta interfaz se usa para aportar 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 renderice en el punto de conexión de renderización, los datos de bucle invertido no estarán disponibles en la entrada auxiliar. Es decir, no habrá llamadas a IApoAuxiliaryInputRT::AcceptInputput

Resumen de API de AEC CAPX

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

Ejemplo de código - AEC

Consulte los siguientes ejemplos de código de audio Sysvad de APO de AEC.

En el código siguiente del encabezado de ejemplo de APO de AEC: AecAPO.h se presentan 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 APO MFX de AEC: 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;

Consulte también el código de ejemplo donde se ve la implementación de CAecApoMFX::IsInputFormatSupported y CAecApoMFX::AcceptInput, así como el uso de APO_CONNECTION_PROPERTY_V2.

Secuencia de operaciones - AEC

En la inicialización:

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

Al cambiar el dispositivo de renderización:

  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

Esta es la operación de búfer recomendada 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 transmisión de referencia y este paquete debe usarse para ejecutarse mediante el algoritmo de cancelación de eco.
  • Las marcas de tiempo de los datos de referencia y del micrófono se pueden usar para alinear los datos del micrófono y del altavoz.

Transmisión de bucle invertido de referencia

De forma predeterminada, la transmisión de bucle invertido "pulsa" (escucha) la transmisión de audio antes de aplicar cualquier nivel de volumen o activar el silencio. Una transmisión de bucle invertido pulsada antes de que se haya aplicado el volumen se conoce como una transmisión de bucle invertido de volumen previo. La ventaja de tener una transmisión de bucle invertido de volumen previo es que la transmisión de audio es clara y uniforme, independientemente de la configuración del volumen actual.

Algunos algoritmos de AEC pueden optar por recibir una transmisión de bucle invertido que se ha conectado después de cualquier procesamiento de volumen (incluida la activación del silencio). Esta configuración se conoce como bucle invertido posterior al volumen.

En la siguiente versión principal de los APO de AEC para Windows puede solicitar un bucle invertido posterior al volumen en los puntos de conexión aceptados.

Limitaciones

A diferencia de las transmisiones de bucle invertido previo al volumen, que están disponibles para todos los puntos de conexión de renderización, es posible que las transmisiones de bucle invertido posterior al volumen no estén disponibles en todos los puntos de conexión.

Solicitud de bucle invertido posterior al volumen

Los APO de AEC que desean usar el bucle invertido posterior al volumen deben implementar la interfaz IApoAcousticEchoCancellation2.

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

Según el punto de conexión de renderización que se esté usando en ese momento, es posible que el bucle invertido posterior al volumen no esté disponible. Al APO de AEC se le informa si se va a usar el bucle invertido 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 usará el bucle invertido posterior al volumen.

En el código siguiente del encabezado de ejemplo de APO de AEC: AecAPO.h se presentan 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 de APO MFX de AEC: AecApoMfx.cpp y presenta la implementación de GetDesiredReferenceStreamProperties y la parte correspondiente 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.

Plataforma Configuración

La plataforma Configuración permite que los APO expongan los métodos para realizar consultas y modificaciones del almacén de propiedades para efectos de audio ("Almacén de propiedades FX") en un punto de conexión de audio. Los APO y las aplicaciones de soporte de hardware (HSA) que desean comunicar los ajustes a dicho APO pueden usar esta plataforma. Los HSA pueden ser aplicaciones de la Plataforma universal de Windows (UWP) y necesitan usar una función especial para invocar las API en la plataforma Configuración. Para obtener más información sobre las aplicaciones de HSA, consulte Aplicaciones de dispositivos de UWP.

Estructura de almacén FxProperty

El nuevo almacén FxProperty tiene tres subalmacenes: Default, User y Volatile.

La subclave "Default" incluye propiedades de efectos personalizados y se rellena por medio del archivo INF. Estas propiedades no se conservan en las actualizaciones del sistema operativo. Por ejemplo, las propiedades que normalmente se definen en un INF se integrarían aquí. Estas se volverían a rellenar a través del INF.

La subclave "User" incluye la configuración de usuario relativa a las propiedades de los efectos. El sistema operativo conserva esta configuración en las actualizaciones y migraciones. Por ejemplo, los valores preestablecidos que el usuario puede configurar y que se supone que se van a conservar en la actualización.

La subclave "Volatile" incluye 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 incluyan propiedades de variantes 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 diferencia entre User y Default radica en si desea que las propiedades se conserven en el sistema operativo y las actualizaciones de controladores. Las propiedades de User se conservarán. Las propiedades Default se volverán a rellenar por medio del archivo INF.

Contextos de APO

El sistema 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 las propiedades en relación con su propio contexto. El almacén de propiedades de efectos de un punto de conexión de audio puede tener ninguno o varios contextos. Sin embargo, los proveedores pueden crear contextos, ya sea por SFX/MFX/EFX o por modo. Un proveedor también puede optar por tener un único contexto para todos los APO enviados por el 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 los ajustes de efectos de audio asociada a un dispositivo de audio. Esta API se expone a una HSA y aplicaciones Win32 para dar 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>

El IAudioSystemEffectsPropertyStore lo pueden leer y escribir un servicio de ISV/IHV, una aplicación de almacén de UWP, aplicaciones de escritorio no administrativas y APO. Además, esto puede servir como mecanismo para que los APO 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 activará un examen estricto. La aplicación debe ser una aplicación de soporte 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: Plataforma 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 ofrece 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 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

En este ejemplo pasa lo mismo que el ejemplo anterior, pero en lugar de usar IMMDevice, se 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

En el ejemplo se muestra la implementación de un APO puede usar la estructura APOInitSystemEffects3 para recuperar las interfaces IPropertyStore tipo User, Default y Volatile 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 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 a través del APO y lo debe utilizar 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: Plataforma Configuración

Este código de ejemplo procede del ejemplo de Swap APO de SFX: SwapAPOSFX.cpp de sysvad.

// 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: Plataforma Configuración

La sintaxis del archivo INF para declarar propiedades de efecto mediante el nuevo modelo 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 de efectos 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 ni la entrada IPropertyStore en el mismo punto de conexión de audio. Esto no se admite.

Ejemplo que indica el modo de 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}"

Plataforma Notificaciones

La plataforma Notificaciones permite que los efectos de audio (APO) soliciten y controlen los cambios en el almacén de propiedades relacionados con el volumen, los puntos de conexión y los efectos de audio. Esta plataforma está diseñada para reemplazar las API existentes que usan los APO para registrar y anular el registro de las notificaciones.

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

Las notificaciones se envían a un APO mediante una cola en serie. Si procede, la primera notificación avisa del estado inicial del valor solicitado (por ejemplo, el volumen del punto de conexión de audio). Las notificaciones se detienen cuando audiodg.exe deja de intentar usar un APO para el streaming. Los APO dejarán de recibir notificaciones después de UnlockForProcess. Todavía será necesario sincronizar UnlockForProcess y las notificaciones en curso.

Implementación: Plataforma Notificaciones

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

Definición de API: Plataforma Notificaciones

La plataforma de notificaciones implementa la nueva interfaz IAudioProcessingObjectNotifications que los clientes pueden implementar para registrar y recibir notificaciones comunes relacionadas con el audio para el APO y notificaciones de efectos del sistema.

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

Código de ejemplo: Plataforma Notificaciones

En el ejemplo se ve cómo un APO puede implementar la interfaz IAudioProcessingObjectNotifications. En el método GetApoNotificationRegistrationInfo, el APO de ejemplo se registra para recibir notificaciones sobre 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 para los que el APO se 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 código siguiente procede del ejemplo de Swap APO de MFX: swapapomfx.cpp y en él se ve cómo registrarse en 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 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 Swap APO de MFX HandleNotifications: swapapomfx.cpp y en él se ve 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);
            }
        }
    }
}

Plataforma de registro

La plataforma de registro proporciona a los desarrolladores de APO medios adicionales para recopilar datos y así mejorar el desarrollo y la depuración. Esta plataforma 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 relevante. La nueva plataforma incluye una API de registro, dejando que el resto del trabajo lo lleve a cabo 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 ID de actividad. Como 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, tal como se describe en Herramientas de 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 verán como proveedor de telemetría en herramientas como xperf.

Implementación: Plataforma de registro

La plataforma de registro se basa en los mecanismos de registro proporcionados por el sistema de 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 a través del subproceso de streaming en tiempo real, ya que existe la posibilidad de que el programador de CPU del sistema operativo anticipe el subproceso de inyección. El registro se debe usar principalmente para eventos que ayudan a depurar problemas que a menudo se encuentran en el campo.

Definición de API: Plataforma de registro

La plataforma de registro presenta la interfaz IAudioProcessingObjectLoggingService que ofrece un nuevo servicio de registro para los APO.

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

Código de ejemplo: Plataforma 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;
}

Plataforma de subprocesos

La plataforma de subprocesos que permite que los efectos sean multiproceso mediante colas de trabajo a partir de una tarea del Servicio de programador de clases multimedia (MMCSS) mediante una API sencilla. El sistema operativo gestiona la creación de colas de trabajo en serie en tiempo real y su asociación con el subproceso principal de inyección. Esta plataforma permite que los APO puedan poner en cola elementos de trabajo de ejecución rápida. La sincronización entre tareas sigue siendo responsabilidad del APO. Para obtener más información sobre los subprocesos de MMCSS, consulte Servicio programador de clases multimedia y API de cola de trabajo en tiempo real.

Definiciones de API: Plataforma de subprocesos

La plataforma de subprocesos presenta la interfaz IAudioProcessingObjectQueueService que da acceso a la cola de trabajo en tiempo real para los APO.

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

Código de ejemplo: Plataforma 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 ver más ejemplos de cómo usar esta interfaz, consulte el código de ejemplo siguiente:

Detección de efectos de audio y control de efectos

La plataforma de detección permite al sistema operativo controlar los efectos de audio en su transmisión. Estas API pueden usarse en casos en los que el usuario de una aplicación necesita controlar determinados efectos en transmisiones (por ejemplo, supresión de ruido profundo). Para lograr esto, esta plataforma agrega lo siguiente:

  • Una nueva API que consultar a través de un APO para determinar si se puede habilitar o deshabilitar un efecto de audio.
  • Un nueva API para cambiar el estado de un efecto de audio a 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 se pueda habilitar o deshabilitar.

Implementación: Detección de efectos de audio

Un APO debe implementar la interfaz IAudioSystemEffects3 si pretende exponer los efectos que se pueden habilitar y deshabilitar dinámicamente. Un APO expone los efectos de audio a través de la función IAudioSystemEffects3::GetControllableSystemEffectsList y habilita y deshabilita los 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 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 de GetControllableSystemEffectsList: swapaposfx.cpp

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

    *effects = nullptr;
    *numEffects = 0;

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

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

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

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

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

    return S_OK;
}

En el código de ejemplo siguiente se muestra cómo habilitar y deshabilitar efectos. Ejemplo de SetAudioSystemEffectState: swapaposfx.cpp

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

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

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

            m_EffectsLock.Leave();
            
            return S_OK;
        }
    }

    return E_NOTFOUND;
}

Reutilización de los APO 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 APO de MFX y WM SFX de serie, ahora pueden reutilizar los APO de CAPX SFX y MFX. En esta sección se describen las tres maneras de hacerlo.

Hay tres puntos de inserción para los APO: renderización previa a la mezcla, renderización posterior a la mezcla y captura. El motor de audio de cada dispositivo lógico admite la instancia de un APO de renderización previa a la mezcla por transmisión (SFX de renderización) y un APO de renderización posterior a la mezcla (MFX). El motor de audio también admite una instancia de un APO de captura (SFX de captura) que se inserta en cada transmisión de captura. Para obtener más información sobre cómo reutilizar o empaquetar los APO de serie, consulte Combinación de APO personalizados y de Windows.

Los APO de CAPX SFX y MFX se pueden reutilizar de una de las tres maneras siguientes.

Uso de la sección DDInstall de INF

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

   Include=wdmaudio.inf
   Needs=mssysfx.CopyFilesAndRegisterCapX

Uso de un archivo INF de extensión

El wdmaudioapo.inf es el INF de extensión de la clase AudioProcessingObject. Incluye el registro específico del dispositivo de los APO de SFX y MFX.

Hacer referencia directamente a los APO de WM SFX y MFX para efectos de modo y transmisiones

Para hacer referencia directamente a estos APO para efectos de modo y transmisiones, use los siguientes valores GUID.

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

SFX (transmisión) y MFX (modo) se conocían en Windows 8.1 como LFX (local) y MFX se denominaba GFX (global). Estas entradas de registro siguen usando los nombres anteriores.

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

El archivo INF deberá tener incluidas 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 de INF crearán un almacén de propiedades que usarán las API de Windows 11 para los nuevos APO.

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

Consulte también

Objetos de procesamiento de audio de Windows.