Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este tópico apresenta um conjunto de novas APIs do Windows 11 para APOs (Objetos de Processamento de Áudio) que são enviadas com um driver de áudio.
O Windows permite que os fabricantes de hardware de áudio de terceiros incluam efeitos personalizados de processamento de sinal digital baseado em host. Esses efeitos são empacotados como APOs (Objetos de Processamento de Áudio) no modo de usuário. Para obter mais informações, consulte Objetos de Processamento de Áudio do Windows.
Algumas das APIs descritas aqui permitem novos cenários para Fornecedores Independentes de Hardware (IHV) e ISV (Fornecedores Independentes de Software), enquanto outras APIs destinam-se a fornecer alternativas que melhorem os recursos gerais de confiabilidade e depuração de áudio.
- A estrutura de Cancelamento de Eco Acústico (AEC) permite que um APO se identifique como um APO de AEC, concedendo acesso a um fluxo de referência e a controles adicionais.
- A estrutura Configurações permitirá que APOs exponham métodos para consultar e modificar o repositório de propriedades para efeitos de áudio ("repositório de propriedades FX") em um ponto de extremidade de áudio. Quando esses métodos são implementados por um APO, eles podem ser invocados por HSA (Aplicativos de Suporte de Hardware) associados a esse APO.
- A estrutura de notificações permite que APOs (efeitos de áudio) solicitem notificações para lidar com alterações de armazenamento de propriedades de volume, ponto de extremidade e efeitos de áudio.
- A estrutura de registro em log ajuda no desenvolvimento e depuração de APOs.
- O framework de Threading permite que as APOs sejam multithreaded utilizando um pool de threads gerenciado pelo sistema operacional e registrado no MMCSS.
- As APIs de Descoberta e Controle de Efeitos de Áudio permitem que o sistema operacional detecte, habilite e desabilite os efeitos disponíveis para processamento em um fluxo.
Para aproveitar essas novas APIs, espera-se que as APOs utilizem a nova interface IAudioSystemEffects3 . Quando um APO implementa essa interface, o sistema operacional interpreta isso como um sinal implícito de que o APO dá suporte ao framework de Configurações do APO e permite que ele se inscreva para notificações comuns relacionadas ao áudio do mecanismo de áudio.
Requisitos de desenvolvimento do APO CAPX do Windows 11
Todas as novas APOs enviadas em um dispositivo para Windows 11 precisam estar em conformidade com as APIs listadas neste tópico, validadas via HLK. Além disso, espera-se que todas as APOs que utilizam a AEC sigam a implementação descrita neste tópico, validada via HLK. As implementações personalizadas para essas extensões principais de processamento de áudio (Configurações, Registro em Log, Notificações, Threading, AEC) devem aproveitar as APIs CAPX. Isso será validado por meio dos testes do Windows 11 HLK. Por exemplo, se um APO estiver usando dados do Registro para salvar configurações em vez de usar a Estrutura de Configurações, o teste HLK associado falhará.
Requisitos de versão do Windows
As APIs descritas neste tópico estão disponíveis a partir do build 22000 do sistema operacional Windows 11, WDK e SDK. O Windows 10 não terá suporte para essas APIs. Se um APO pretende funcionar no Windows 10 e no Windows 11, ele pode examinar se ele está sendo inicializado com a estrutura APOInitSystemEffects2 ou APOInitSystemEffects3 para determinar se ele está em execução em um sistema operacional que dá suporte às APIs CAPX.
As versões mais recentes do Windows, do WDK e do SDK podem ser baixadas abaixo por meio do Programa Windows Insider. Os parceiros que estão envolvidos com a Microsoft por meio do Partner Center também podem acessar esse conteúdo por meio do Collaborate. Para obter mais informações sobre Colaborar, consulte Introdução ao Microsoft Collaborate.
- Visualização do Windows Insider – Download do ISO
- Visualização do Windows Insider – Download do WDK
- Visualização do Windows Insider – Download do SDK
O Conteúdo WHCP do Windows 11 foi atualizado para fornecer aos parceiros os meios para validar essas APIs.
O código de exemplo para o conteúdo descrito neste tópico pode ser encontrado aqui: Áudio/SYSVAD/APO – github
Cancelamento de eco acústico (AEC)
O Cancelamento de Eco Acústico (AEC) é um efeito de áudio comum implementado por IHVs (Fornecedores Independentes de Hardware) e ISVs (Fornecedores Independentes de Software) como um Objeto de Processamento de Áudio (APO) no pipeline de captura de microfone. Esse efeito é diferente de outros efeitos normalmente implementados por IHVs e ISVs, pois requer duas entradas – um fluxo de áudio do microfone e um fluxo de áudio de um dispositivo de renderização que atua como o sinal de referência.
Este novo conjunto de interfaces permite a um AEC APO identificar-se como tal para o mecanismo de áudio. Isso faz com que o mecanismo de áudio configure o APO apropriadamente com várias entradas e uma única saída.
Quando as novas interfaces AEC são implementadas por um APO, o mecanismo de áudio:
- Configure o APO com uma entrada adicional que lhe forneça o fluxo de referência a partir de um ponto de extremidade de renderização apropriado.
- Alterne os fluxos de referência à medida que o dispositivo de renderização é alterado.
- Permitir que um APO controle o formato do microfone de entrada e do fluxo de referência.
- Permitir que um APO obtenha marcas de tempo nos fluxos de microfone e de referência.
Abordagem anterior – Windows 10
APOs são objetos de entrada única e saída única. O mecanismo de áudio fornece a um APO AEC o áudio do ponto de extremidade do microfone em sua entrada. Para obter o fluxo de referência, um APO pode interagir com o driver usando interfaces proprietárias para recuperar o áudio de referência do ponto de extremidade de renderização ou usar WASAPI para abrir um fluxo de loopback no ponto de extremidade de renderização.
Ambas as abordagens acima têm desvantagens:
Um APO do AEC que usa canais privados para obter um fluxo de referência do driver, normalmente só pode fazer isso no dispositivo de renderização de áudio integrado. Como resultado, o cancelamento de eco não funcionará se o usuário estiver reproduzindo áudio fora do dispositivo não integrado, como USB ou dispositivo de áudio Bluetooth. Somente o sistema operacional está ciente dos pontos de extremidade de renderização corretos que podem servir como pontos de extremidade de referência.
Um APO pode usar o WASAPI para escolher o ponto de extremidade padrão de renderização para executar o cancelamento de eco. No entanto, há algumas armadilhas a serem observadas ao abrir um fluxo de loopback do processo de audiodg.exe (que é onde o APO está hospedado).
- O fluxo de retorno não pode ser aberto/destruído quando o mecanismo de áudio está executando os métodos principais do APO, pois isso pode resultar em um bloqueio.
- Um APO de captura não conhece o estado dos fluxos de seus clientes. Ou seja, um aplicativo de captura pode ter um fluxo de captura no estado 'STOP', no entanto, o APO não está ciente desse estado e, portanto, mantém o fluxo de loopback aberto no estado 'RUN', que é ineficiente em termos de consumo de energia.
Definição de API – AEC
A estrutura AEC fornece novas estruturas e interfaces que as APOs podem aproveitar. Essas novas estruturas e interfaces são descritas abaixo.
estrutura APO_CONNECTION_PROPERTY_V2
APOs que implementam a interface IApoAcousticEchoCancellation receberão uma estrutura APO_CONNECTION_PROPERTY_V2 na sua chamada para IAudioProcessingObjectRT::APOProcess. Além de todos os campos na estrutura APO_CONNECTION_PROPERTY, a versão 2 da estrutura também fornece informações de timestamp para os buffers de áudio.
Um APO pode examinar o campo APO_CONNECTION_PROPERTY.u32Signature para determinar se a estrutura recebida do mecanismo de áudio é do tipo APO_CONNECTION_PROPERTY ou APO_CONNECTION_PROPERTY_V2. APO_CONNECTION_PROPERTY é uma estrutura com a assinatura APO_CONNECTION_PROPERTY_SIGNATURE, já o APO_CONNECTION_PROPERTY_V2 possui uma assinatura que é igual a APO_CONNECTION_PROPERTY_V2_SIGNATURE. Se a assinatura tiver um valor igual a APO_CONNECTION_PROPERTY_V2_SIGNATURE, o ponteiro para a estrutura APO_CONNECTION_PROPERTY poderá ser digitado com segurança para um ponteiro de APO_CONNECTION_PROPERTY_V2.
O código a seguir é do exemplo Aec APO MFX – AecApoMfx.cpp e mostra a reformulação.
if (ppInputConnections[0]->u32Signature == APO_CONNECTION_PROPERTY_V2_SIGNATURE)
{
const APO_CONNECTION_PROPERTY_V2* connectionV2 = reinterpret_cast<const APO_CONNECTION_PROPERTY_V2*>(ppInputConnections[0]);
}
IApoAcousticEchoCancellation
A interface IApoAcousticEchoCancellation não tem métodos explícitos. Sua finalidade é identificar um AEC APO para o mecanismo de áudio. Essa interface pode ser implementada apenas por efeitos de modo (MFX) nos terminais de captura. Implementar essa interface em qualquer outro APO levará a uma falha no carregamento desse APO. Para obter informações gerais sobre o MFX, consulte a arquitetura de objetos de processamento de áudio.
Se o efeito de modo em um endpoint de captura for implementado como uma série de APOs encadeados, somente o APO mais próximo do dispositivo poderá implementar essa interface. APOs que implementam essa interface receberão a estrutura APO_CONNECTION_PROPERTY_V2 em sua chamada para IAudioProcessingobjectRT::APOProcess. O APO pode verificar se há uma assinatura de APO_CONNECTION_PROPERTY_V2_SIGNATURE na propriedade de conexão e digitar a estrutura de APO_CONNECTION_PROPERTY de entrada para uma estrutura de APO_CONNECTION_PROPERTY_V2.
Reconhecendo que as APOs AEC geralmente executam seus algoritmos em uma taxa de amostragem específica/contagem de canais, o mecanismo de áudio fornece suporte para remuestreamento às APOs que implementam a interface IApoAcousticEchoCancellation.
Quando um APO do AEC retorna APOERR_FORMAT_NOT_SUPPORTED na chamada para IAudioProcessingObject::OutInputFormatSupported, o mecanismo de áudio chamará IAudioProcessingObject::IsInputFormatSupported no APO novamente com um formato de saída NULL e um formato de entrada não NULL, para obter o formato sugerido pelo APO. Em seguida, o motor de áudio resamplará o áudio do microfone para o formato sugerido antes de enviá-lo ao AEC APO. Isso elimina a necessidade de o APO do AEC implementar a conversão de taxa de amostragem e de contagem de canais.
IApoAuxiliaryInputConfiguration
A interface IApoAuxiliaryInputConfiguration fornece métodos que as APOs podem implementar para que o mecanismo de áudio possa adicionar e remover fluxos de entrada auxiliares.
Essa interface é implementada pelo APO do AEC e usada pelo mecanismo de áudio para inicializar a entrada de referência. No Windows 11, o APO do AEC será inicializado apenas com uma única entrada auxiliar– uma que tenha o fluxo de áudio de referência para cancelamento de eco. O método AddAuxiliaryInput será usado para adicionar a entrada de referência ao APO. Os parâmetros de inicialização conterão uma referência ao endpoint de renderização do qual o fluxo de loopback é obtido.
O método IsInputFormatSupported é chamado pelo mecanismo de áudio para negociar formatos na entrada auxiliar. Se o APO do AEC preferir um formato específico, ele poderá retornar S_FALSE na chamada para IsInputFormatSupported e especificar um formato sugerido. O motor de áudio fará o resample do áudio de referência para o formato sugerido e o disponibilizará na entrada auxiliar do APO do AEC.
IApoAuxiliaryInputRT
A interface IApoAuxiliaryInputRT é uma interface segura em tempo real usada para conduzir as entradas auxiliares de um APO.
Essa interface é usada para fornecer dados de áudio na entrada auxiliar para o APO. Observe que as entradas de áudio auxiliares não são sincronizadas com as chamadas para IAudioProcessingObjectRT::APOProcess. Quando não houver nenhum áudio sendo renderizado no endpoint de renderização, os dados de loopback não estarão disponíveis na entrada auxiliar. Ou seja, não haverá chamadas para IApoAuxiliaryInputRT::AcceptInput
Resumo das APIs AEC CAPX
Para obter mais informações, encontre informações adicionais nas páginas a seguir.
- APO_CONNECTION_PROPERTY_V2 estrutura (audioapotypes.h)
- Interface IApoAcousticEchoCancellation
- IApoAuxiliaryInputConfiguration
- IApoAuxiliaryInputRT
Código de exemplo – AEC
Consulte os exemplos de código AecApo de áudio do Sysvad a seguir.
O código a seguir do cabeçalho de exemplo do Aec APO– AecAPO.h mostra os três novos métodos públicos sendo adicionados.
public IApoAcousticEchoCancellation,
public IApoAuxiliaryInputConfiguration,
public IApoAuxiliaryInputRT
...
COM_INTERFACE_ENTRY(IApoAcousticEchoCancellation)
COM_INTERFACE_ENTRY(IApoAuxiliaryInputConfiguration)
COM_INTERFACE_ENTRY(IApoAuxiliaryInputRT)
...
// IAPOAuxiliaryInputConfiguration
STDMETHOD(AddAuxiliaryInput)(
DWORD dwInputId,
UINT32 cbDataSize,
BYTE *pbyData,
APO_CONNECTION_DESCRIPTOR *pInputConnection
) override;
STDMETHOD(RemoveAuxiliaryInput)(
DWORD dwInputId
) override;
STDMETHOD(IsInputFormatSupported)(
IAudioMediaType* pRequestedInputFormat,
IAudioMediaType** ppSupportedInputFormat
) override;
...
// IAPOAuxiliaryInputRT
STDMETHOD_(void, AcceptInput)(
DWORD dwInputId,
const APO_CONNECTION_PROPERTY *pInputConnection
) override;
// IAudioSystemEffects3
STDMETHODIMP GetControllableSystemEffectsList(_Outptr_result_buffer_maybenull_(*numEffects) AUDIO_SYSTEMEFFECT** effects, _Out_ UINT* numEffects, _In_opt_ HANDLE event) override
{
UNREFERENCED_PARAMETER(effects);
UNREFERENCED_PARAMETER(numEffects);
UNREFERENCED_PARAMETER(event);
return S_OK;
}
O código a seguir é do exemplo Aec APO MFX – AecApoMfx.cpp e mostra a implementação de AddAuxiliaryInput, quando o APO só pode lidar com uma entrada auxiliar.
STDMETHODIMP
CAecApoMFX::AddAuxiliaryInput(
DWORD dwInputId,
UINT32 cbDataSize,
BYTE *pbyData,
APO_CONNECTION_DESCRIPTOR * pInputConnection
)
{
HRESULT hResult = S_OK;
CComPtr<IAudioMediaType> spSupportedType;
ASSERT_NONREALTIME();
IF_TRUE_ACTION_JUMP(m_bIsLocked, hResult = APOERR_APO_LOCKED, Exit);
IF_TRUE_ACTION_JUMP(!m_bIsInitialized, hResult = APOERR_NOT_INITIALIZED, Exit);
BOOL bSupported = FALSE;
hResult = IsInputFormatSupportedForAec(pInputConnection->pFormat, &bSupported);
IF_FAILED_JUMP(hResult, Exit);
IF_TRUE_ACTION_JUMP(!bSupported, hResult = APOERR_FORMAT_NOT_SUPPORTED, Exit);
// This APO can only handle 1 auxiliary input
IF_TRUE_ACTION_JUMP(m_auxiliaryInputId != 0, hResult = APOERR_NUM_CONNECTIONS_INVALID, Exit);
m_auxiliaryInputId = dwInputId;
Revise também o código de exemplo que mostra a implementação de CAecApoMFX::IsInputFormatSupported e CAecApoMFX::AcceptInput, assim como o manuseio do APO_CONNECTION_PROPERTY_V2.
Sequência de operações – AEC
Na inicialização:
- IAudioProcessingObject::Initialize
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration:: LockForProcess
- IAudioProcessingObjectConfiguration ::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
Na alteração do dispositivo de renderização:
- IAudioProcessingObject::Initialize
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess
- Alterações de dispositivo padrão
- IAudioProcessingObjectConfiguration::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess
Comportamento de buffer recomendado – AEC
Esse é o comportamento de buffer recomendado para a AEC.
- Os buffers obtidos na chamada para IApoAuxiliaryInputRT::AcceptInput devem ser gravados em um buffer circular sem bloquear o thread principal.
- Na chamada para IAudioProcessingObjectRT::APOProcess, o buffer circular deve ser lido para o pacote de áudio mais recente do fluxo de referência e esse pacote deve ser usado para execução por meio do algoritmo de cancelamento de eco.
- Carimbos de data/hora nos dados de referência e microfone podem ser usados para alinhar os dados do alto-falante e do microfone.
Fluxo de loopback de referência
Por padrão, o fluxo de loopback conecta-se ao fluxo de áudio, escutando-o antes que qualquer volume ou mude seja aplicado. Um fluxo de loopback capturado antes da aplicação do volume é conhecido como fluxo de loopback pré-volume. Uma vantagem de ter um fluxo de loopback pré-volume é um fluxo de áudio claro e uniforme, independentemente da configuração de volume atual.
Alguns algoritmos AEC podem preferir obter um fluxo de loopback que tenha sido conectado após todo e qualquer processamento de volume (incluindo a possibilidade de estar mudo). Essa configuração é conhecida como loopback pós-volume.
Na próxima versão principal do Windows, os APOs do AEC poderão solicitar loopback pós-volume em pontos de extremidade com suporte.
Limitações
Ao contrário dos fluxos de loopback pré-volume, que estão disponíveis para todos os pontos de extremidade de renderização, os fluxos de loopback pós-volume podem não estar disponíveis em todos os pontos de extremidade.
Solicitando loopback pós-volume
As AEC APOs que desejam utilizar o loopback pós-volume devem implementar a interface IApoAcousticEchoCancellation2.
Um APO do AEC pode solicitar loopback pós-volume retornando o sinalizador APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK por meio do parâmetro Properties em sua implementação de IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties.
Dependendo do endpoint de renderização atualmente em uso, o loopback pós-volume pode não estar disponível. Um APO AEC será notificado se o loopback pós-volume estiver sendo usado quando o método IApoAuxiliaryInputConfiguration::AddAuxiliaryInput for chamado. Se o campo AcousticEchoCanceller_Reference_Input streamProperties contiver APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK, o loopback pós-volume estará em uso.
O código a seguir do cabeçalho de exemplo do AEC APO– AecAPO.h mostra os três novos métodos públicos sendo adicionados.
public:
// IApoAcousticEchoCancellation2
STDMETHOD(GetDesiredReferenceStreamProperties)(
_Out_ APO_REFERENCE_STREAM_PROPERTIES * properties) override;
// IApoAuxiliaryInputConfiguration
STDMETHOD(AddAuxiliaryInput)(
DWORD dwInputId,
UINT32 cbDataSize,
_In_ BYTE* pbyData,
_In_ APO_CONNECTION_DESCRIPTOR *pInputConnection
) override;
O snippet de código a seguir é do exemplo AEC APO MFX – AecApoMfx.cpp e mostra a implementação de GetDesiredReferenceStreamProperties e parte relevante de AddAuxiliaryInput.
STDMETHODIMP SampleApo::GetDesiredReferenceStreamProperties(
_Out_ APO_REFERENCE_STREAM_PROPERTIES * properties)
{
RETURN_HR_IF_NULL(E_INVALIDARG, properties);
// Always request that a post-volume loopback stream be used, if
// available. We will find out which type of stream was actually
// created when AddAuxiliaryInput is invoked.
*properties = APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK;
return S_OK;
}
STDMETHODIMP
CAecApoMFX::AddAuxiliaryInput(
DWORD dwInputId,
UINT32 cbDataSize,
BYTE *pbyData,
APO_CONNECTION_DESCRIPTOR * pInputConnection
)
{
// Parameter checking skipped for brevity, please see sample for
// full implementation.
AcousticEchoCanceller_Reference_Input* referenceInput = nullptr;
APOInitSystemEffects3* papoSysFxInit3 = nullptr;
if (cbDataSize == sizeof(AcousticEchoCanceller_Reference_Input))
{
referenceInput =
reinterpret_cast<AcousticEchoCanceller_Reference_Input*>(pbyData);
if (WI_IsFlagSet(
referenceInput->streamProperties,
APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK))
{
// Post-volume loopback is being used.
m_bUsingPostVolumeLoopback = TRUE;
// Note that we can get to the APOInitSystemEffects3 from
// AcousticEchoCanceller_Reference_Input.
papoSysFxInit3 = (APOInitSystemEffects3*)pbyData;
}
else if (cbDataSize == sizeof(APOInitSystemEffects3))
{
// Post-volume loopback is not supported.
papoSysFxInit3 = (APOInitSystemEffects3*)pbyData;
}
// Remainder of method skipped for brevity.
Estrutura de Configurações
A Estrutura de Configurações permite que as APOs exponham métodos para consultar e modificar o repositório de propriedades para efeitos de áudio ("Repositório de Propriedades FX") em um ponto de extremidade de áudio. Essa estrutura pode ser usada por APOs e por HSA (Aplicativos de Suporte de Hardware) que desejam comunicar as configurações para esse APO. Os HSAs podem ser aplicativos da Plataforma Universal do Windows (UWP) e exigem uma funcionalidade especial para invocar as APIs na Estrutura de Configurações. Para obter mais informações sobre aplicativos HSA, consulte aplicativos de dispositivo UWP.
Estrutura do Repositório FxProperty
O novo repositório FxProperty tem três substores: Padrão, Usuário e Volátil.
A subchave "Padrão" contém propriedades de efeitos personalizados e é preenchida do arquivo INF. Essas propriedades não persistem entre atualizações do sistema operacional. Por exemplo, as propriedades que normalmente são definidas em um INF caberiam aqui. Em seguida, eles seriam preenchidos novamente a partir do INF.
A subchave "User" contém as configurações do usuário relativas às propriedades de efeitos. Essas configurações são mantidas pelo sistema operacional entre atualizações e migrações. Por exemplo, quaisquer predefinições que o usuário possa configurar que devem persistir na atualização.
A subchave "Volatile" contém propriedades de efeitos voláteis. Essas propriedades são perdidas após a reinicialização do dispositivo e são apagadas sempre que o endpoint entra em estado ativo. Espera-se que elas contenham propriedades de variante de tempo (por exemplo, com base em aplicativos em execução atuais, postura do dispositivo etc.) Por exemplo, todas as configurações que dependem do ambiente atual.
A maneira de pensar sobre o usuário versus o padrão é se você deseja que as propriedades persistam entre atualizações do sistema operacional e do driver. As propriedades do usuário serão mantidas. As propriedades padrão serão preenchidas novamente do INF.
Contextos do APO
A estrutura de configurações CAPX permite que um autor do APO agrupe as propriedades do APO por contextos. Cada APO pode definir seu próprio contexto e atualizar propriedades relativas ao seu próprio contexto. O armazenamento de propriedades de efeitos para um dispositivo de áudio pode ter zero ou mais contextos. Os fornecedores têm liberdade para criar contextos da forma que preferirem, seja através de SFX/MFX/EFX ou por modo. Um fornecedor também pode optar por ter um único contexto para todas as APOs enviadas por esse fornecedor.
Capacidade restrita de configurações
A API de configurações destina-se a dar suporte a todos os desenvolvedores de OEMs e HSA interessados em consultar e modificar as configurações de efeitos de áudio associadas a um dispositivo de áudio. Essa API é exposta a aplicativos HSA e Win32 para fornecer acesso ao repositório de propriedades por meio da funcionalidade restrita "audioDeviceConfiguration" que deve ser declarada no manifesto. Além disso, um namespace correspondente deve ser declarado da seguinte maneira:
<Package
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
...
<Capabilities>
<rescap:Capability Name="audioDeviceConfiguration" />
</Capabilities>
</Package>
O IAudioSystemEffectsPropertyStore é legível e gravável por um serviço ISV/IHV, um aplicativo da loja UWP, aplicativos de desktop sem privilégios de administrador e APOs. Além disso, isso pode funcionar como o mecanismo para as APOs entregarem mensagens de volta a um serviço ou aplicativo de repositório UWP.
Observação
Essa é uma capacidade restrita: se um aplicativo for enviado com essa capacidade para a Microsoft Store, ele passará por um escrutínio minucioso. O aplicativo deve ser um HSA (Aplicativo de Suporte de Hardware) e será examinado para avaliar se ele é de fato um HSA antes que o envio seja aprovado.
Definição de API – Estrutura de Configurações
A nova interface IAudioSystemEffectsPropertyStore permite que um HSA acesse repositórios de propriedades de efeitos do sistema de áudio e registre-se para notificações de alteração de propriedade.
A função ActiveAudioInterfaceAsync fornece um método para obter a interface IAudioSystemEffectsPropertyStore de forma assíncrona.
Um aplicativo pode receber notificações quando o armazenamento de propriedades de efeitos do sistema muda, usando a nova interface de retorno de chamada IAudioSystemEffectsPropertyChangeNotificationClient.
Aplicativo que tenta obter o IAudioSystemEffectsPropertyStore usando IMMDevice::Activate
O exemplo demonstra como um aplicativo de suporte de hardware pode usar IMMDevice::Activate para ativar IAudioSystemEffectsPropertyStore. O exemplo mostra como usar IAudioSystemEffectsPropertyStore para abrir um IPropertyStore que tenha configurações de usuário.
#include <mmdeviceapi.h>
// This function opens an IPropertyStore with user settings on the specified IMMDevice.
// Input parameters:
// device - IMMDevice object that identifies the audio endpoint.
// propertyStoreContext - GUID that identifies the property store. Each APO can have its own GUID. These
// GUIDs are chosen by the audio driver at installation time.
HRESULT GetPropertyStoreFromMMDevice(_In_ IMMDevice* device,
REFGUID propertyStoreContext,
_COM_Outptr_ IPropertyStore** userPropertyStore)
{
*userPropertyStore = nullptr;
wil::unique_prop_variant activationParam;
RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effectsPropertyStore;
RETURN_IF_FAILED(device->Activate(__uuidof(effectsPropertyStore), CLSCTX_INPROC_SERVER, activationParam.addressof(), effectsPropertyStore.put_void()));
RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, userPropertyStore));
return S_OK;
}
Exemplo usando ActivateAudioInterfaceAsync
Este exemplo faz a mesma coisa que o exemplo anterior, mas em vez de usar IMMDevice, ele usa a API ActivateAudioInterfaceAsync para obter a interface IAudioSystemEffectsPropertyStore de forma assíncrona.
include <mmdeviceapi.h>
class PropertyStoreHelper :
public winrt::implements<PropertyStoreHelper, IActivateAudioInterfaceCompletionHandler>
{
public:
wil::unique_event_nothrow m_asyncOpCompletedEvent;
HRESULT GetPropertyStoreAsync(
_In_ PCWSTR deviceInterfacePath,
REFGUID propertyStoreContext,
_COM_Outptr_ IActivateAudioInterfaceAsyncOperation** operation);
HRESULT GetPropertyStoreResult(_COM_Outptr_ IPropertyStore** userPropertyStore);
// IActivateAudioInterfaceCompletionHandler
STDMETHOD(ActivateCompleted)(_In_ IActivateAudioInterfaceAsyncOperation *activateOperation);
private:
wil::com_ptr_nothrow<IPropertyStore> m_userPropertyStore;
HRESULT m_hrAsyncOperationResult = E_FAIL;
HRESULT GetUserPropertyStore(
_In_ IActivateAudioInterfaceAsyncOperation* operation,
_COM_Outptr_ IPropertyStore** userPropertyStore);
};
// This function opens an IPropertyStore with user settings asynchronously on the specified audio endpoint.
// Input parameters:
// deviceInterfacePath - the Device Interface Path string that identifies the audio endpoint. Can be
// obtained from Windows.Devices.Enumeration.DeviceInformation.
// propertyStoreContext - GUID that identifies the property store. Each APO can have its own GUID. These
// GUIDs are chosen by the audio driver at installation time.
//
// The function returns an IActivateAudioInterfaceAsyncOperation, which can be used to check the result of
// the asynchronous operation.
HRESULT PropertyStoreHelper::GetPropertyStoreAsync(
_In_ PCWSTR deviceInterfacePath,
REFGUID propertyStoreContext,
_COM_Outptr_ IActivateAudioInterfaceAsyncOperation** operation)
{
*operation = nullptr;
wil::unique_prop_variant activationParam;
RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));
RETURN_IF_FAILED(ActivateAudioInterfaceAsync(deviceInterfacePath,
__uuidof(IAudioSystemEffectsPropertyStore),
activationParam.addressof(),
this,
operation));
return S_OK;
}
// Once the IPropertyStore is available, the app can call this function to retrieve it.
// (The m_asyncOpCompletedEvent event is signaled when the asynchronous operation to retrieve
// the IPropertyStore has completed.)
HRESULT PropertyStoreHelper::GetPropertyStoreResult(_COM_Outptr_ IPropertyStore** userPropertyStore)
{
*userPropertyStore = nullptr;
// First check if the asynchronous operation completed. If it failed, the error code
// is stored in the m_hrAsyncOperationResult variable.
RETURN_IF_FAILED(m_hrAsyncOperationResult);
RETURN_IF_FAILED(m_userPropertyStore.copy_to(userPropertyStore));
return S_OK;
}
// Implementation of IActivateAudioInterfaceCompletionHandler::ActivateCompleted.
STDMETHODIMP PropertyStoreHelper::ActivateCompleted(_In_ IActivateAudioInterfaceAsyncOperation* operation)
{
m_hrAsyncOperationResult = GetUserPropertyStore(operation, m_userPropertyStore.put());
// Always signal the event that our caller might be waiting on before we exit,
// even in case of failure.
m_asyncOpCompletedEvent.SetEvent();
return S_OK;
}
HRESULT PropertyStoreHelper::GetUserPropertyStore(
_In_ IActivateAudioInterfaceAsyncOperation* operation,
_COM_Outptr_ IPropertyStore** userPropertyStore)
{
*userPropertyStore = nullptr;
// Check if the asynchronous operation completed successfully, and retrieve an
// IUnknown pointer to the result.
HRESULT hrActivateResult;
wil::com_ptr_nothrow<IUnknown> audioInterfaceUnknown;
RETURN_IF_FAILED(operation->GetActivateResult(&hrActivateResult, audioInterfaceUnknown.put()));
RETURN_IF_FAILED(hrActivateResult);
// Convert the result to IAudioSystemEffectsPropertyStore
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effctsPropertyStore;
RETURN_IF_FAILED(audioInterfaceUnknown.query_to(&effectsPropertyStore));
// Open an IPropertyStore with the user settings.
RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, userPropertyStore));
return S_OK;
}
IAudioProcessingObject::Initialize o código usando o IAudioSystemEffectsPropertyStore
O exemplo demonstra que, durante a inicialização de um APO, a implementação pode utilizar a estrutura APOInitSystemEffects3 para recuperar as interfaces IPropertyStore de usuário, padrão e volátil para o APO.
#include <audioenginebaseapo.h>
// Partial implementation of APO to show how an APO that implements IAudioSystemEffects3 can handle
// being initialized with the APOInitSystemEffects3 structure.
class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
public:
// IAudioProcessingObject
STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);
// Implementation of IAudioSystemEffects2, IAudioSystemEffects3 has been omitted from this sample for brevity.
private:
wil::com_ptr_nothrow<IPropertyStore> m_defaultStore;
wil::com_ptr_nothrow<IPropertyStore> m_userStore;
wil::com_ptr_nothrow<IPropertyStore> m_volatileStore;
// Each APO has its own private collection of properties. The collection is identified through a
// a property store context GUID, which is defined below and in the audio driver INF file.
const GUID m_propertyStoreContext = ...;
};
// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
if (cbDataSize == sizeof(APOInitSystemEffects3))
{
// SampleApo supports the new IAudioSystemEffects3 interface so it will receive APOInitSystemEffects3
// in pbyData if the audio driver has declared support for this.
// Use IMMDevice to activate IAudioSystemEffectsPropertyStore that contains the default, user and
// volatile settings.
IMMDeviceCollection* deviceCollection =
reinterpret_cast<APOInitSystemEffects3*>(pbyData)->pDeviceCollection;
if (deviceCollection != nullptr)
{
UINT32 numDevices;
wil::com_ptr_nothrow<IMMDevice> endpoint;
// Get the endpoint on which this APO has been created
// (It is the last device in the device collection)
if (SUCCEEDED(deviceCollection->GetCount(&numDevices)) &&
numDevices > 0 &&
SUCCEEDED(deviceCollection->Item(numDevices - 1, &endpoint)))
{
wil::unique_prop_variant activationParam;
RETURN_IF_FAILED(InitPropVariantFromCLSID(m_propertyStoreContext, &activationParam));
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effectsPropertyStore;
RETURN_IF_FAILED(endpoint->Activate(__uuidof(effectsPropertyStore), CLSCTX_ALL, &activationParam, effectsPropertyStore.put_void()));
// Read default, user and volatile property values to set up initial operation of the APO
RETURN_IF_FAILED(effectsPropertyStore->OpenDefaultPropertyStore(STGM_READWRITE, m_defaultStore.put()));
RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, m_userStore.put()));
RETURN_IF_FAILED(effectsPropertyStore->OpenVolatilePropertyStore(STGM_READWRITE, m_volatileStore.put()));
// At this point the APO can read and write settings in the various property stores,
// as appropriate. (Not shown.)
// Note that APOInitSystemEffects3 contains all the members of APOInitSystemEffects2,
// so an APO that knows how to initialize from APOInitSystemEffects2 can use the same
// code to continue its initialization here.
}
}
}
else if (cbDataSize == sizeof(APOInitSystemEffects2))
{
// Use APOInitSystemEffects2 for the initialization of the APO.
// If we get here, the audio driver did not declare support for IAudioSystemEffects3.
}
else if (cbDataSize == sizeof(APOInitSystemEffects))
{
// Use APOInitSystemEffects for the initialization of the APO.
}
return S_OK;
}
Registro de aplicativo para notificações de alteração de propriedade
O exemplo demonstra o uso do registro para notificações de alteração de propriedade. Isso não deve ser usado com o APO e deve ser utilizado por aplicativos Win32.
class PropertyChangeNotificationClient : public
winrt::implements<PropertyChangeNotificationClient, IAudioSystemEffectsPropertyChangeNotificationClient>
{
private:
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> m_propertyStore;
bool m_isListening = false;
public:
HRESULT OpenPropertyStoreOnDefaultRenderEndpoint(REFGUID propertyStoreContext);
HRESULT StartListeningForPropertyStoreChanges();
HRESULT StopListeningForPropertyStoreChanges();
// IAudioSystemEffectsPropertyChangeNotificationClient
STDMETHOD(OnPropertyChanged)(AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE type, const PROPERTYKEY key);
};
// Open the IAudioSystemEffectsPropertyStore. This should be the first method invoked on this class.
HRESULT PropertyChangeNotificationClient::OpenPropertyStoreOnDefaultRenderEndpoint(
REFGUID propertyStoreContext)
{
wil::com_ptr_nothrow<IMMDeviceEnumerator> deviceEnumerator;
RETURN_IF_FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator)));
wil::com_ptr_nothrow<IMMDevice> device;
RETURN_IF_FAILED(deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.put()));
wil::unique_prop_variant activationParam;
RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));
RETURN_IF_FAILED(device->Activate(__uuidof(m_propertyStore), CLSCTX_INPROC_SERVER,
&activationParam, m_propertyStore.put_void()));
return S_OK;
}
// Start subscribing to callbacks that are invoked when there are changes to any of the IPropertyStores
// that are managed by IAudioSystemEffectsPropertyStore.
// The OpenPropertyStoreOnDefaultRenderEndpoint should have been invoked prior to invoking this function.
HRESULT PropertyChangeNotificationClient::StartListeningForPropertyStoreChanges()
{
RETURN_HR_IF(E_FAIL, !m_propertyStore);
RETURN_IF_FAILED(m_propertyStore->RegisterPropertyChangeNotification(this));
m_isListening = true;
return S_OK;
}
// Unsubscribe to event callbacks. Since IAudioSystemEffectsPropertyStore takes a reference on our
// PropertyChangeNotificationClient class, it is important that this method is invoked prior to cleanup,
// to break the circular reference.
HRESULT PropertyChangeNotificationClient::StopListeningForPropertyStoreChanges()
{
if (m_propertyStore != nullptr && m_isListening)
{
RETURN_IF_FAILED(m_propertyStore->UnregisterPropertyChangeNotification(this));
m_isListening = false;
}
return S_OK;
}
// Callback method that gets invoked when there have been changes to any of the IPropertyStores
// that are managed by IAudioSystemEffectsPropertyStore. Note that calls to
// IAudioSystemEffectsPropertyChangeNotificationClient are not marshalled across COM apartments.
// Therefore, the OnPropertyChanged is most likely invoked on a different thread than the one used when
// invoking RegisterPropertyChangeNotification. If necessary, concurrent access to shared state should be
// protected with a critical section.
STDMETHODIMP PropertyChangeNotificationClient::OnPropertyChanged(AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE type, const PROPERTYKEY key)
{
if (type == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_USER)
{
// Handle changes to the User property store.
wil::com_ptr_nothrow<IPropertyStore> userPropertyStore;
RETURN_IF_FAILED(m_propertyStore->OpenUserPropertyStore(STGM_READ, userPropertyStore.put()));
// Here we can call IPropertyStore::GetValue to read the current value of PROPERTYKEYs that we are
// interested in.
}
else if (type == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_VOLATILE)
{
// Handle changes to the Volatile property store, if desired
}
return S_OK;
}
Código de exemplo – Estrutura de Configurações
Este código de exemplo é do exemplo de SFX Swap APO do 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);
}
}
Seção INF – Estrutura de Configurações
A sintaxe do arquivo INF para declarar propriedades de efeito usando a nova estrutura de configurações CAPX é a seguinte:
HKR, FX\0\{ApoContext}\{Default|User}, %CUSTOM_PROPERTY_KEY%,,,
Isso substitui a sintaxe mais antiga para declarar propriedades de efeito da seguinte maneira:
# Old way of declaring FX properties
HKR, FX\0, %CUSTOM_PROPERTY_KEY_1%,,,
O INF não pode ter tanto a entrada IAudioSystemEffectsPropertyStore quanto a entrada IPropertyStore para o mesmo ponto de extremidade de áudio. Observe que não há suporte para isso.
Exemplo mostrando o uso do novo repositório de propriedades:
HKR,FX\0\%SWAP_APO_CONTEXT%,%PKEY_FX_Association%,,%KSNODETYPE_ANY%
; Enable the channel swap in the APO
HKR,FX\0\%SWAP_APO_CONTEXT%\User,%PKEY_Endpoint_Enable_Channel_Swap_SFX%,REG_DWORD,0x1
PKEY_Endpoint_Enable_Channel_Swap_SFX = "{A44531EF-5377-4944-AE15-53789A9629C7},2"
REG_DWORD = 0x00010001 ; FLG_ADDREG_TYPE_DWORD
SWAP_APO_CONTEXT = "{24E7F619-5B33-4084-9607-878DA8722417}"
PKEY_FX_Association = "{D04E05A6-594B-4FB6-A80D-01AF5EED7D1D},0"
KSNODETYPE_ANY = "{00000000-0000-0000-0000-000000000000}"
Estrutura de Notificações
A estrutura de notificações permite que APOs (efeitos de áudio) solicitem e manipulem notificações de alteração de volume, ponto de extremidade e armazenamento de propriedades de efeitos de áudio. Essa framework destina-se a substituir as APIs existentes que são usadas por APOs para registrar e desregistrar notificações.
A nova API apresenta uma interface que as APOs podem utilizar para declarar o tipo de notificações em que o APO está interessado. O Windows consultará o APO para obter as notificações em que está interessado e encaminhará a notificação para as APOs. As APOs não precisam mais chamar explicitamente as APIs de registro ou cancelamento de registro.
As notificações são entregues a um APO usando uma fila serial. Quando aplicável, a primeira notificação transmite o estado inicial do valor solicitado (por exemplo, o volume do ponto de extremidade de áudio). As notificações param quando audiodg.exe deixa de ter a intenção de usar um APO para streaming. As APOs deixarão de receber notificações após UnlockForProcess. Ainda é necessário sincronizar UnlockForProcess e quaisquer notificações em andamento.
Implementação – Estrutura de Notificações
Para aproveitar a estrutura de notificações, um APO declara em quais notificações ele está interessado. Não há chamadas explícitas de registro/cancelamento de registro. Todas as notificações para o APO são serializadas e é importante não bloquear o thread de retorno de chamada de notificação por muito tempo.
Definição de API – Notifications Framework
A estrutura de notificação implementa uma nova interface IAudioProcessingObjectNotifications que pode ser implementada pelos clientes para registrar e receber notificações comuns relacionadas ao áudio, especificamente para notificações de ponto de extremidade do APO e de efeito de sistema.
Para obter mais informações, localize conteúdo adicional nas seguintes páginas:
Código de exemplo – Estrutura de Notificações
O exemplo demonstra como um APO pode implementar a interface IAudioProcessingObjectNotifications. No método GetApoNotificationRegistrationInfo, o exemplo de APO registra notificações para alterações nos repositórios de propriedades de efeitos do sistema.
O método HandleNotification é invocado pelo sistema operacional para notificar o APO de alterações que correspondem ao que o APO havia registrado.
class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3,
IAudioProcessingObjectNotifications>
{
public:
// IAudioProcessingObjectNotifications
STDMETHOD(GetApoNotificationRegistrationInfo)(
_Out_writes_(count) APO_NOTIFICATION_DESCRIPTOR** apoNotifications, _Out_ DWORD* count);
STDMETHOD_(void, HandleNotification)(_In_ APO_NOTIFICATION *apoNotification);
// Implementation of IAudioSystemEffects2, IAudioSystemEffects3 has been omitted from this sample for brevity.
private:
wil::com_ptr_nothrow<IMMDevice> m_device;
// Each APO has its own private collection of properties. The collection is identified through a
// a property store context GUID, which is defined below and in the audio driver INF file.
const GUID m_propertyStoreContext = ...;
float m_masterVolume = 1.0f;
BOOL m_isMuted = FALSE;
BOOL m_allowOffloading = FALSE;
// The rest of the implementation of IAudioProcessingObject is omitted for brevity
};
// The OS invokes this method on the APO to find out what notifications the APO is interested in.
STDMETHODIMP SampleApo::GetApoNotificationRegistrationInfo(
_Out_writes_(count) APO_NOTIFICATION_DESCRIPTOR** apoNotificationDescriptorsReturned,
_Out_ DWORD* count)
{
*apoNotificationDescriptorsReturned = nullptr;
*count = 0;
// Before this function can be called, our m_device member variable should already have been initialized.
// This would typically be done in our implementation of IAudioProcessingObject::Initialize, by using
// APOInitSystemEffects3::pDeviceCollection to obtain the last IMMDevice in the collection.
RETURN_HR_IF_NULL(E_FAIL, m_device);
// Let the OS know what notifications we are interested in by returning an array of
// APO_NOTIFICATION_DESCRIPTORs.
constexpr DWORD numDescriptors = 3;
wil::unique_cotaskmem_ptr<APO_NOTIFICATION_DESCRIPTOR[]> apoNotificationDescriptors;
apoNotificationDescriptors.reset(static_cast<APO_NOTIFICATION_DESCRIPTOR*>(
CoTaskMemAlloc(sizeof(APO_NOTIFICATION_DESCRIPTOR) * numDescriptors)));
RETURN_IF_NULL_ALLOC(apoNotificationDescriptors);
// Our APO wants to get notified when any change occurs on the user property store on the audio endpoint
// identified by m_device.
// The user property store is different for each APO. Ours is identified by m_propertyStoreContext.
apoNotificationDescriptors[0].type = APO_NOTIFICATION_TYPE_AUDIO_SYSTEM_EFFECTS_PROPERTY_CHANGE;
(void)m_device.query_to(&apoNotificationDescriptors[0].audioSystemEffectsPropertyChange.device);
apoNotificationDescriptors[0].audioSystemEffectsPropertyChange.propertyStoreContext = m_propertyStoreContext;
// Our APO wants to get notified when an endpoint property changes on the audio endpoint.
apoNotificationDescriptors[1].type = APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE;
(void)m_device.query_to(&apoNotificationDescriptors[1].audioEndpointPropertyChange.device);
// Our APO also wants to get notified when the volume level changes on the audio endpoint.
apoNotificationDescriptors [2].type = APO_NOTIFICATION_TYPE_ENDPOINT_VOLUME;
(void)m_device.query_to(&apoNotificationDescriptors[2].audioEndpointVolume.device);
*apoNotificationDescriptorsReturned = apoNotificationDescriptors.release();
*count = numDescriptors;
return S_OK;
}
static bool IsSameEndpointId(IMMDevice* device1, IMMDevice* device2)
{
bool isSameEndpointId = false;
wil::unique_cotaskmem_string deviceId1;
if (SUCCEEDED(device1->GetId(&deviceId1)))
{
wil::unique_cotaskmem_string deviceId2;
if (SUCCEEDED(device2->GetId(&deviceId2)))
{
isSameEndpointId = (CompareStringOrdinal(deviceId1.get(), -1, deviceId2.get(), -1, TRUE) == CSTR_EQUAL);
}
}
return isSameEndpointId;
}
// HandleNotification is called whenever there is a change that matches any of the
// APO_NOTIFICATION_DESCRIPTOR elements in the array that was returned by GetApoNotificationRegistrationInfo.
// Note that the APO will have to query each property once to get its initial value because this method is
// only invoked when any of the properties have changed.
STDMETHODIMP_(void) SampleApo::HandleNotification(_In_ APO_NOTIFICATION* apoNotification)
{
// Check if a property in the user property store has changed.
if (apoNotification->type == APO_NOTIFICATION_TYPE_AUDIO_SYSTEM_EFFECTS_PROPERTY_CHANGE
&& IsSameEndpointId(apoNotification->audioSystemEffectsPropertyChange.endpoint, m_device.get())
&& apoNotification->audioSystemEffectsPropertyChange.propertyStoreContext == m_propertyStoreContext
&& apoNotification->audioSystemEffectsPropertyChange.propertyStoreType == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_USER)
{
// Check if one of the properties that we are interested in has changed.
// As an example, we check for "PKEY_Endpoint_Enable_Channel_Swap_SFX" which is a fictitious
// PROPERTYKEY that could be set on our user property store.
if (apoNotification->audioSystemEffectsPropertyChange.propertyKey ==
PKEY_Endpoint_Enable_Channel_Swap_SFX)
{
wil::unique_prop_variant var;
if (SUCCEEDED(apoNotification->audioSystemEffectsPropertyChange.propertyStore->GetValue(
PKEY_Endpoint_Enable_Channel_Swap_SFX, &var)) &&
var.vt != VT_EMPTY)
{
// We have retrieved the property value. Now we can do something interesting with it.
}
}
}
else if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE
&& IsSameEndpointId(apoNotification->audioEndpointPropertyChange.endpoint, m_device.get())
{
// Handle changes to PROPERTYKEYs in the audio endpoint's own property store.
// In this example, we are interested in a property called "PKEY_Endpoint_AllowOffloading" that the
// user might change in the audio control panel, and we update our member variable if this
// property changes.
if (apoNotification->audioEndpointPropertyChange.propertyKey == PKEY_Endpoint_AllowOffloading)
{
wil::unique_prop_variant var;
if (SUCCEEDED(propertyStore->GetValue(PKEY_Endpoint_AllowOffloading, &var)) && var.vt == VT_BOOL)
{
m_allowOffloading = var.boolVal;
}
}
}
else if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_VOLUME
&& IsSameEndpointId(apoNotification->audioEndpointVolumeChange.endpoint, m_device.get())
{
// Handle endpoint volume change
m_masterVolume = apoNotification->audioEndpointVolumeChange.volume->fMasterVolume;
m_isMuted = apoNotification->audioEndpointVolumeChange.volume->bMuted;
}
}
O código a seguir é do exemplo MFX do APO de Troca – swapapomfx.cpp e mostra o registro para eventos, retornando uma matriz de APO_NOTIFICATION_DESCRIPTORs.
HRESULT CSwapAPOMFX::GetApoNotificationRegistrationInfo(_Out_writes_(*count) APO_NOTIFICATION_DESCRIPTOR **apoNotifications, _Out_ DWORD *count)
{
*apoNotifications = nullptr;
*count = 0;
RETURN_HR_IF_NULL(E_FAIL, m_device);
// Let the OS know what notifications we are interested in by returning an array of
// APO_NOTIFICATION_DESCRIPTORs.
constexpr DWORD numDescriptors = 1;
wil::unique_cotaskmem_ptr<APO_NOTIFICATION_DESCRIPTOR[]> apoNotificationDescriptors;
apoNotificationDescriptors.reset(static_cast<APO_NOTIFICATION_DESCRIPTOR*>(
CoTaskMemAlloc(sizeof(APO_NOTIFICATION_DESCRIPTOR) * numDescriptors)));
RETURN_IF_NULL_ALLOC(apoNotificationDescriptors);
// Our APO wants to get notified when an endpoint property changes on the audio endpoint.
apoNotificationDescriptors[0].type = APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE;
(void)m_device.query_to(&apoNotificationDescriptors[0].audioEndpointPropertyChange.device);
*apoNotifications = apoNotificationDescriptors.release();
*count = numDescriptors;
return S_OK;
}
O código a seguir é do exemplo SwapAPO MFX HandleNotifications - swapapomfx.cpp e mostra como lidar com notificações.
void CSwapAPOMFX::HandleNotification(APO_NOTIFICATION *apoNotification)
{
if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE)
{
// If either the master disable or our APO's enable properties changed...
if (PK_EQUAL(apoNotification->audioEndpointPropertyChange.propertyKey, PKEY_Endpoint_Enable_Channel_Swap_MFX) ||
PK_EQUAL(apoNotification->audioEndpointPropertyChange.propertyKey, PKEY_AudioEndpoint_Disable_SysFx))
{
struct KeyControl
{
PROPERTYKEY key;
LONG* value;
};
KeyControl controls[] = {
{PKEY_Endpoint_Enable_Channel_Swap_MFX, &m_fEnableSwapMFX},
};
m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"HandleNotification - pkey: " GUID_FORMAT_STRING L" %d", GUID_FORMAT_ARGS(apoNotification->audioEndpointPropertyChange.propertyKey.fmtid), apoNotification->audioEndpointPropertyChange.propertyKey.pid);
for (int i = 0; i < ARRAYSIZE(controls); i++)
{
LONG fNewValue = true;
// Get the state of whether channel swap MFX is enabled or not
fNewValue = GetCurrentEffectsSetting(m_userStore.get(), controls[i].key, m_AudioProcessingMode);
SetAudioSystemEffectState(m_effectInfos[i].id, fNewValue ? AUDIO_SYSTEMEFFECT_STATE_ON : AUDIO_SYSTEMEFFECT_STATE_OFF);
}
}
}
}
Estrutura de registro em log
O framework de Logging fornece aos desenvolvedores de APO meios adicionais de coletar dados para melhorar o desenvolvimento e a depuração. Esta estrutura unifica os diferentes métodos de registro em log utilizados por fornecedores variados e os vincula aos provedores de registro de rastreamento de áudio para criar logs mais significativos. A nova estrutura fornece uma API de registro de logs, deixando o restante do trabalho para o sistema operacional fazer.
O provedor é definido como:
IMPLEMENT_TRACELOGGING_CLASS(ApoTelemetryProvider, "Microsoft.Windows.Audio.ApoTrace",
// {8b4a0b51-5dcf-5a9c-2817-95d0ec876a87}
(0x8b4a0b51, 0x5dcf, 0x5a9c, 0x28, 0x17, 0x95, 0xd0, 0xec, 0x87, 0x6a, 0x87));
Cada APO tem seu próprio identificador de atividade. Como isso usa o mecanismo de log de rastreamento existente, as ferramentas de console existentes podem ser usadas para filtrar esses eventos e exibi-los em tempo real. Você pode usar ferramentas existentes, como tracelog e tracefmt, conforme descrito em Ferramentas para Rastreamento de Software – drivers do Windows. Para obter mais informações sobre sessões de rastreamento, consulte Criando uma sessão de rastreamento com um GUID de controle.
Os eventos de rastreamento de log não são marcados como telemetria e não serão exibidos como um provedor de telemetria em aplicações como o xperf.
Implementação – Framework de Log
A estrutura de registro em log baseia-se nos mecanismos de registro em log fornecidos pela instrumentação ETW. Para obter mais informações sobre ETW, consulte Rastreamento de Eventos. Isso não se destina ao registro em log de dados de áudio, mas sim ao registro de eventos que normalmente são registrados em produção. As APIs de registro em log não devem ser usadas no thread de streaming em tempo real, pois elas têm o potencial de fazer com que o thread de bombeamento seja preemptado pelo agendador de CPU do sistema operacional. O registro em log deve ser usado principalmente para eventos que ajudarão na depuração de problemas que geralmente são encontrados no campo.
Definição de API – Logging Framework
A estrutura de registro em log apresenta a interface IAudioProcessingObjectLoggingService que fornece um novo serviço de registro em log para APOs.
Para obter mais informações, consulte IAudioProcessingObjectLoggingService.
Código de exemplo – Logging Framework
O exemplo demonstra o uso do método IAudioProcessingObjectLoggingService::ApoLog e como esse ponteiro de interface é obtido em IAudioProcessingObject::Initialize.
Exemplo de registro em log do AecApoMfx.
class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
private:
wil::com_ptr_nothrow<IAudioProcessingObjectLoggingService> m_apoLoggingService;
public:
// IAudioProcessingObject
STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);
// Implementation of IAudioProcessingObject, IAudioSystemEffects2 andIAudioSystemEffects3 has been omitted for brevity.
};
// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
if (cbDataSize == sizeof(APOInitSystemEffects3))
{
APOInitSystemEffects3* apoInitSystemEffects3 = reinterpret_cast<APOInitSystemEffects3*>(pbyData);
// Try to get the logging service, but ignore errors as failure to do logging it is not fatal.
(void)apoInitSystemEffects3->pServiceProvider->QueryService(SID_AudioProcessingObjectLoggingService,
__uuidof(IAudioProcessingObjectLoggingService), IID_PPV_ARGS(&m_apoLoggingService));
}
// Do other APO initialization work
if (m_apoLoggingService != nullptr)
{
m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"APO Initialization completed");
}
return S_OK;
}
Estrutura de encadeamento
A estrutura de threading que permite que os efeitos sejam executados em multiencadeamento, utilizando filas de trabalho de uma tarefa adequada do Serviço de Agendador de Classe Multimídia (MMCSS), através de uma API simples. A criação de filas de trabalho seriais em tempo real e sua associação com o thread principal da bomba são tratadas pelo sistema operacional. Essa estrutura permite que as APOs enfileirem itens de trabalho de execução curta. A sincronização entre tarefas continua sendo responsabilidade do APO. Para obter mais informações sobre o threading do MMCSS, consulte Serviço de Agendador de Classe Multimídia e Real-Time API de Fila de Trabalho.
Definições de API – Estrutura de Threading
A estrutura Threading apresenta a interface IAudioProcessingObjectQueueService, que fornece acesso à fila de trabalho em tempo real para os APOs.
Para obter mais informações, localize conteúdo adicional nas seguintes páginas:
Código de exemplo – Framework de Threading
Este exemplo demonstra o uso do método IAudioProcessingObjectRTQueueService::GetRealTimeWorkQueue e como o ponteiro da interface IAudioProcessingObjectRTQueueService é obtido em IAudioProcessingObject::Initialize.
#include <rtworkq.h>
class SampleApo3 :
public winrt::implements<SampleApo3, IAudioProcessingObject, IAudioProcessingObjectConfiguration,
IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
private:
DWORD m_queueId = 0;
wil::com_ptr_nothrow<SampleApo3AsyncCallback> m_asyncCallback;
public:
// IAudioProcessingObject
STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);
// IAudioProcessingObjectConfiguration
STDMETHOD(LockForProcess)(
_In_ UINT32 u32NumInputConnections,
_In_reads_(u32NumInputConnections) APO_CONNECTION_DESCRIPTOR** ppInputConnections,
_In_ UINT32 u32NumOutputConnections,
_In_reads_(u32NumOutputConnections) APO_CONNECTION_DESCRIPTOR** ppOutputConnections);
// Non-interface methods called by the SampleApo3AsyncCallback helper class.
HRESULT DoWorkOnRealTimeThread()
{
// Do the actual work here
return S_OK;
}
void HandleWorkItemCompleted(_In_ IRtwqAsyncResult* asyncResult);
// Implementation of IAudioProcessingObject, IAudioSystemEffects2, IAudioSystemEffects3 and IAudioProcessingObjectConfiguration is omitted
// for brevity.
};
// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo3::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
if (cbDataSize == sizeof(APOInitSystemEffects3))
{
APOInitSystemEffects3* apoInitSystemEffects3 = reinterpret_cast<APOInitSystemEffects3*>(pbyData);
wil::com_ptr_nothrow<IAudioProcessingObjectRTQueueService> apoRtQueueService;
RETURN_IF_FAILED(apoInitSystemEffects3->pServiceProvider->QueryService(
SID_AudioProcessingObjectRTQueue, IID_PPV_ARGS(&apoRtQueueService)));
// Call the GetRealTimeWorkQueue to get the ID of a work queue that can be used for scheduling tasks
// that need to run at a real-time priority. The work queue ID is used with the Rtwq APIs.
RETURN_IF_FAILED(apoRtQueueService->GetRealTimeWorkQueue(&m_queueId));
}
// Do other initialization here
return S_OK;
}
STDMETHODIMP SampleApo3::LockForProcess(
_In_ UINT32 u32NumInputConnections,
_In_reads_(u32NumInputConnections) APO_CONNECTION_DESCRIPTOR** ppInputConnections,
_In_ UINT32 u32NumOutputConnections,
_In_reads_(u32NumOutputConnections) APO_CONNECTION_DESCRIPTOR** ppOutputConnections)
{
// Implementation details of LockForProcess omitted for brevity
m_asyncCallback = winrt::make<SampleApo3AsyncCallback>(m_queueId).get();
RETURN_IF_NULL_ALLOC(m_asyncCallback);
wil::com_ptr_nothrow<IRtwqAsyncResult> asyncResult;
RETURN_IF_FAILED(RtwqCreateAsyncResult(this, m_asyncCallback.get(), nullptr, &asyncResult));
RETURN_IF_FAILED(RtwqPutWorkItem(m_queueId, 0, asyncResult.get()));
return S_OK;
}
void SampleApo3::HandleWorkItemCompleted(_In_ IRtwqAsyncResult* asyncResult)
{
// check the status of the result
if (FAILED(asyncResult->GetStatus()))
{
// Handle failure
}
// Here the app could call RtwqPutWorkItem again with m_queueId if it has more work that needs to
// execute on a real-time thread.
}
class SampleApo3AsyncCallback :
public winrt::implements<SampleApo3AsyncCallback, IRtwqAsyncCallback>
{
private:
DWORD m_queueId;
public:
SampleApo3AsyncCallback(DWORD queueId) : m_queueId(queueId) {}
// IRtwqAsyncCallback
STDMETHOD(GetParameters)(_Out_ DWORD* pdwFlags, _Out_ DWORD* pdwQueue)
{
*pdwFlags = 0;
*pdwQueue = m_queueId;
return S_OK;
}
STDMETHOD(Invoke)(_In_ IRtwqAsyncResult* asyncResult);
};
STDMETHODIMP SampleApo3AsyncCallback::Invoke(_In_ IRtwqAsyncResult* asyncResult)
{
// We are now executing on the real-time thread. Invoke the APO and let it execute the work.
wil::com_ptr_nothrow<IUnknown> objectUnknown;
RETURN_IF_FAILED(asyncResult->GetObject(objectUnknown.put_unknown()));
wil::com_ptr_nothrow<SampleApo3> sampleApo3 = static_cast<SampleApo3*>(objectUnknown.get());
HRESULT hr = sampleApo3->DoWorkOnRealTimeThread();
RETURN_IF_FAILED(asyncResult->SetStatus(hr));
sampleApo3->HandleWorkItemCompleted(asyncResult);
return S_OK;
}
Para obter mais exemplos de como utilizar essa interface, consulte o seguinte código de exemplo:
- Definição da classe SwapAPO SwapMFXApoAsyncCallback – exemplo
- Função Invoke SwapAPO – Exemplo
- SwapAPO Create Async Callback – Exemplo
Descoberta e controle de efeitos de áudio para efeitos
A estrutura de descoberta permite que o sistema operacional controle os efeitos de áudio em seu fluxo. Essas APIs dão suporte a cenários em que o usuário de um aplicativo precisa controlar determinados efeitos em fluxos (por exemplo, supressão de ruído profundo). Para isso, essa estrutura adiciona o seguinte:
- Uma nova API para ser consultada a partir de um APO para determinar se um efeito de áudio pode ser habilitado ou desabilitado.
- Uma nova API para definir o estado de um efeito de áudio como ativado/desativado.
- Uma notificação quando há uma alteração na lista de efeitos de áudio ou quando os recursos ficam disponíveis para que um efeito de áudio agora possa ser habilitado/desabilitado.
Implementação – Descoberta de Efeitos de Áudio
Um APO precisa implementar a interface IAudioSystemEffects3 se ele pretende expor efeitos que podem ser habilitados e desabilitados dinamicamente. Um APO expõe seus efeitos de áudio por meio da função IAudioSystemEffects3::GetControllableSystemEffectsList e habilita e desabilita seus efeitos de áudio por meio da função IAudioSystemEffects3::SetAudioSystemEffectState .
Código de exemplo – Descoberta de Efeito de Áudio
O código de exemplo de Descoberta de Efeito de Áudio pode ser encontrado no exemplo SwapAPOSFX – swapaposfx.cpp.
O código de exemplo a seguir ilustra como recuperar a lista de efeitos configuráveis. Exemplo GetControllableSystemEffectsList – swapaposfx.cpp
HRESULT CSwapAPOSFX::GetControllableSystemEffectsList(_Outptr_result_buffer_maybenull_(*numEffects) AUDIO_SYSTEMEFFECT** effects, _Out_ UINT* numEffects, _In_opt_ HANDLE event)
{
RETURN_HR_IF_NULL(E_POINTER, effects);
RETURN_HR_IF_NULL(E_POINTER, numEffects);
*effects = nullptr;
*numEffects = 0;
// Always close existing effects change event handle
if (m_hEffectsChangedEvent != NULL)
{
CloseHandle(m_hEffectsChangedEvent);
m_hEffectsChangedEvent = NULL;
}
// If an event handle was specified, save it here (duplicated to control lifetime)
if (event != NULL)
{
if (!DuplicateHandle(GetCurrentProcess(), event, GetCurrentProcess(), &m_hEffectsChangedEvent, EVENT_MODIFY_STATE, FALSE, 0))
{
RETURN_IF_FAILED(HRESULT_FROM_WIN32(GetLastError()));
}
}
if (!IsEqualGUID(m_AudioProcessingMode, AUDIO_SIGNALPROCESSINGMODE_RAW))
{
wil::unique_cotaskmem_array_ptr<AUDIO_SYSTEMEFFECT> audioEffects(
static_cast<AUDIO_SYSTEMEFFECT*>(CoTaskMemAlloc(NUM_OF_EFFECTS * sizeof(AUDIO_SYSTEMEFFECT))), NUM_OF_EFFECTS);
RETURN_IF_NULL_ALLOC(audioEffects.get());
for (UINT i = 0; i < NUM_OF_EFFECTS; i++)
{
audioEffects[i].id = m_effectInfos[i].id;
audioEffects[i].state = m_effectInfos[i].state;
audioEffects[i].canSetState = m_effectInfos[i].canSetState;
}
*numEffects = (UINT)audioEffects.size();
*effects = audioEffects.release();
}
return S_OK;
}
O código de exemplo a seguir ilustra como habilitar e desabilitar efeitos. Exemplo de SetAudioSystemEffectState – swapaposfx.cpp
HRESULT CSwapAPOSFX::SetAudioSystemEffectState(GUID effectId, AUDIO_SYSTEMEFFECT_STATE state)
{
for (auto effectInfo : m_effectInfos)
{
if (effectId == effectInfo.id)
{
AUDIO_SYSTEMEFFECT_STATE oldState = effectInfo.state;
effectInfo.state = state;
// Synchronize access to the effects list and effects changed event
m_EffectsLock.Enter();
// If anything changed and a change event handle exists
if (oldState != effectInfo.state)
{
SetEvent(m_hEffectsChangedEvent);
m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"SetAudioSystemEffectState - effect: " GUID_FORMAT_STRING L", state: %i", effectInfo.id, effectInfo.state);
}
m_EffectsLock.Leave();
return S_OK;
}
}
return E_NOTFOUND;
}
Reutilização dos APOs do WM SFX e MFX no Windows 11, versão 22H2
A partir do Windows 11, versão 22H2, os arquivos de configuração INF que reutilizam os APOs WM SFX e MFX do inbox agora podem reutilizar os APOs CAPX SFX e MFX. Esta seção descreve as três maneiras de fazer isso.
Há três pontos de inserção para APOs: renderização pré-mix, renderização pós-mix e captura. Cada mecanismo de áudio de dispositivo lógico suporta uma instância de APO de renderização pré-mistura por fluxo (SFX de renderização) e um APO de renderização pós-mistura (MFX). O mecanismo de áudio também oferece suporte para uma instância de APO de captura (SFX de captura) que é inserido em cada fluxo de captura. Para obter mais informações sobre como reutilizar ou encapsular as APOs de caixa de entrada, consulte Combinar APOs personalizados e do Windows.
Os APOs CAPX SFX e MFX podem ser reutilizados de uma das três maneiras a seguir.
Usando a seção DDInstall do INF
Utilize mssysfx.CopyFilesAndRegisterCapX de wdmaudio.inf adicionando as seguintes entradas.
Include=wdmaudio.inf
Needs=mssysfx.CopyFilesAndRegisterCapX
Usando um arquivo INF de extensão
O arquivo wdmaudioapo.inf é a extensão inf da classe AudioProcessingObject. Ele contém o registro específico para o dispositivo dos APOs SFX e MFX.
Referenciando diretamente os APOs do WM SFX e MFX para efeitos de fluxo e modo
Para fazer referência diretamente a essas APOs para efeitos de fluxo e modo, use os seguintes valores GUID.
- Usar
{C9453E73-8C5C-4463-9984-AF8BAB2F5447}como o APO do SFX do WM - Utilize
{13AB3EBD-137E-4903-9D89-60BE8277FD17}como o APO do WM MFX.
SFX (Stream) e MFX (Mode) eram considerados no Windows 8.1 como LFX (local) e MFX como GFX (global). Essas entradas do Registro continuam a usar os nomes anteriores.
O registro específico do dispositivo usa HKR em vez de HKCR.
O arquivo INF precisará ter as seguintes entradas adicionadas.
HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
HKR,"FX\\0\\%WMALFXGFXAPO_Context%\\User",,,
WMALFXGFXAPO_Context = "{B13412EE-07AF-4C57-B08B-E327F8DB085B}"
Essas entradas de arquivo INF criarão um repositório de propriedades que será usado pelas APIs do Windows 11 para as novas APOs.
PKEY_FX_Association no INF ex.
HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%, deve ser substituído por HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%.