Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Det här avsnittet introducerar en uppsättning nya Windows 11-API:er för ljudbearbetningsobjekt (API:er) som levereras med en ljuddrivrutin.
Med Windows kan tillverkare av ljudmaskinvara från tredje part inkludera anpassade, värdbaserade digitala signalbearbetningseffekter. Dessa effekter paketeras som användarlägesljudbearbetningsobjekt (APO:er). Mer information finns i Windows-ljudbearbetningsobjekt.
Några av de API:er som beskrivs här möjliggör nya scenarier för oberoende maskinvaruleverantörer (IHV) och oberoende programvaruleverantörer (ISV), medan andra API:er är avsedda att tillhandahålla alternativ som förbättrar den övergripande ljudtillförlitligheten och felsökningsfunktionerna.
- AEC-ramverket (Acoustic Echo Cancellation) gör det möjligt för en APO att identifiera sig som en AEC-APO, vilket ger åtkomst till en referensström och ytterligare kontroller.
- Ramverket Inställningar gör det möjligt för API:er att exponera metoder för att fråga och ändra egenskapsarkivet för ljudeffekter ("FX-egenskapsarkiv") på en ljudslutpunkt. När dessa metoder implementeras av en APO kan de anropas av maskinvarusupportappar (HSA) som är associerade med den APO:en.
- Ramverket Meddelanden gör att ljudeffekter (API:er) kan begära meddelanden för hantering av volym-, slutpunkts- och ljudeffekters egenskapslagringsändringar.
- Loggningsramverket underlättar utveckling och felsökning av API:er.
- Threading-ramverket gör att API:er kan vara flertrådade med hjälp av en OS-hanterad, MMCSS-registrerad trådpool.
- Api:erna för identifiering och kontroll av ljudeffekter gör att operativsystemet kan identifiera, aktivera och inaktivera effekter som är tillgängliga för bearbetning på en ström.
För att utnyttja dessa nya API:er förväntas API:er använda det nya gränssnittet IAudioSystemEffects3 . När en APO implementerar det här gränssnittet tolkar operativsystemet detta som en implicit signal om att APO stöder ramverket för APO-inställningar och tillåter APO att prenumerera på vanliga ljudrelaterade meddelanden från ljudmotorn.
Windows 11 APO CAPX-utvecklingskrav
Alla nya API:er som levereras på en enhet för Windows 11 måste vara kompatibla med API:erna som anges i det här avsnittet, verifierade via HLK. Dessutom förväntas alla API:er som utnyttjar AEC följa implementeringen som beskrivs i det här avsnittet, som verifieras via HLK. Anpassade implementeringar för dessa grundläggande tillägg för ljudbearbetning (inställningar, loggning, meddelanden, trådning, AEC) förväntas utnyttja CAPX-API:er. Detta verifieras via Windows 11 HLK-testerna. Om ett APO till exempel använder registerdata för att spara inställningar i stället för att använda Settings Framework misslyckas det associerade HLK-testet.
Krav för Windows-version
API:erna som beskrivs i det här avsnittet är tillgängliga från och med version 22000 av Windows 11 OS, WDK och SDK. Windows 10 har inte stöd för dessa API:er. Om en APO avser att fungera på både Windows 10 och Windows 11 kan den undersöka om den initieras med APOInitSystemEffects2 eller APOInitSystemEffects3-strukturen för att avgöra om den körs på ett operativsystem som stöder CAPX-API:er.
De senaste versionerna av Windows, WDK och SDK kan laddas ned nedan via Windows Insider Program. Partner som samarbetar med Microsoft via Partnercenter kan också komma åt det här innehållet via Samarbeta. Mer information om Samarbeta finns i Introduktion till Microsoft Collaborate.
- Förhandsversion av Windows Insider – ISO-nedladdning
- Förhandsversion av Windows Insider – WDK-nedladdning
- Windows Insider Preview – nedladdning av SDK
Windows 11 WHCP-innehåll har uppdaterats för att ge partner möjlighet att verifiera dessa API:er.
Exempelkoden för innehållet som beskrivs i det här avsnittet finns här: Audio/SYSVAD/APO – github
Akustisk ekoreducering (AEC)
Acoustic Echo Cancellation (AEC) är en vanlig ljudeffekt som implementeras av oberoende maskinvaruleverantörer (IHV:er) och oberoende programvaruleverantörer (ISV:er) som ett ljudbearbetningsobjekt (APO) i mikrofoninspelningspipelinen. Den här effekten skiljer sig från andra effekter som vanligtvis implementeras av IHV:er och ISV:er eftersom den kräver 2 indata – en ljudström från mikrofonen och en ljudström från en återgivningsenhet som fungerar som referenssignal.
Med den här nya uppsättningen gränssnitt kan en AEC-APO identifiera sig som en sådan för ljudmotorn. Detta resulterar i att ljudmotorn konfigurerar APO på rätt sätt med flera indata och en enda utdata.
När de nya AEC-gränssnitten implementeras av en APO kommer ljudmotorn att:
- Konfigurera APO med ytterligare indata som ger APO referensströmmen från en passande renderingsslutpunkt.
- Växla ut referensströmmarna när återgivningsenheten ändras.
- Tillåt en APO att styra formatet på indatamikrofonen och referensströmmen.
- Tillåt att en APO erhåller tidsstämplar på mikrofonen och referensströmmarna.
Föregående metod – Windows 10
APO:er är enkel indata – enkla utdataobjekt. Ljudmotorn skickar ljudet från mikrofonens slutpunkt till AEC APO vid dess ingång. För att hämta referensströmmen kan en APO antingen interagera med drivrutinen med hjälp av egna gränssnitt för att hämta referensljudet från återgivningsslutpunkten eller använda WASAPI för att öppna en loopback-ström på återgivningsslutpunkten.
Båda ovanstående metoder har nackdelar:
En AEC-APO som använder privata kanaler för att hämta en referensström från drivrutinen kan vanligtvis bara göra det från den integrerade ljudåtergivningsenheten. Därför fungerar inte ekoreducering om användaren spelar upp ljud från den icke-integrerade enheten, till exempel USB- eller Bluetooth-ljudenhet. Endast operativsystemet känner till rätt återgivningsslutpunkter som kan fungera som referensslutpunkter.
En APO kan använda WASAPI för att välja standardslutpunkten för återgivning för att utföra ekoreducering. Det finns dock vissa fallgropar att vara medvetna om när du öppnar en loopback-ström från audiodg.exe -processen (där APO finns).
- Loopback-strömmen kan inte öppnas/förstöras när ljudmotorn anropar till de huvudsakliga APO-metoderna, eftersom detta kan leda till ett dödläge.
- En capture-APO känner inte till tillståndet för strömmarna för sina klienter. Det vill säga en avbildningsapp kan ha en avbildningsström i tillståndet "STOP", men APO:n känner inte till det här tillståndet och håller därför loopback-strömmen öppen i körningstillståndet, vilket är ineffektivt när det gäller strömförbrukning.
API-definition – AEC
AEC-ramverket tillhandahåller nya strukturer och gränssnitt som API:er kan utnyttja. Dessa nya strukturer och gränssnitt beskrivs nedan.
APO_CONNECTION_PROPERTY_V2 struktur
API:er som implementerar gränssnittet IApoAcousticEchoCancellation kommer att tilldelas en APO_CONNECTION_PROPERTY_V2-struktur i anropet till IAudioProcessingObjectRT::APOProcess. Förutom alla fält i APO_CONNECTION_PROPERTY struktur tillhandahåller version 2 av strukturen även tidsstämpelinformation för ljudbuffertarna.
En APO kan undersöka fältet APO_CONNECTION_PROPERTY.u32Signature för att avgöra om strukturen den tar emot från ljudmotorn är av typen APO_CONNECTION_PROPERTY eller APO_CONNECTION_PROPERTY_V2. APO_CONNECTION_PROPERTY strukturer har en signatur för APO_CONNECTION_PROPERTY_SIGNATURE, medan APO_CONNECTION_PROPERTY_V2 har en signatur som är lika med APO_CONNECTION_PROPERTY_V2_SIGNATURE. Om signaturen har ett värde som är lika med APO_CONNECTION_PROPERTY_V2_SIGNATURE kan pekaren till strukturen APO_CONNECTION_PROPERTY på ett säkert sätt skrivas om till en APO_CONNECTION_PROPERTY_V2 pekare.
Följande kod kommer från Aec APO MFX-exemplet – AecApoMfx.cpp och visar omarbetningen.
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
Gränssnittet IApoAcousticEchoCancellation har inga explicita metoder. Syftet är att identifiera en AEC-APO för ljudmotorn. Det här gränssnittet kan bara implementeras av lägeseffekter (MFX) på avbildningsslutpunkter. Implementering av det här gränssnittet på andra APO leder till ett fel vid inläsning av APO. Allmän information om MFX finns i Arkitektur för ljudbearbetningsobjekt.
Om lägeseffekten på en avbildningsslutpunkt implementeras som en serie länkade API:er kan endast det APO som är närmast enheten implementera det här gränssnittet. APO:er som implementerar det här gränssnittet erbjuds strukturen APO_CONNECTION_PROPERTY_V2 i anropet till IAudioProcessingobjectRT::APOProcess. APO kan söka efter en APO_CONNECTION_PROPERTY_V2_SIGNATURE-signatur för anslutningsegenskapen och typekasta den inkommande strukturen APO_CONNECTION_PROPERTY till en APO_CONNECTION_PROPERTY_V2-struktur.
Som ett erkännande av det faktum att AEC-API:er vanligtvis kör sina algoritmer med en specifik samplingshastighet/kanalantal, ger ljudmotorn återsamplingsstöd till API:er som implementerar gränssnittet IApoAcousticEchoCancellation.
När en AEC-APO returnerar APOERR_FORMAT_NOT_SUPPORTED i anropet till IAudioProcessingObject::OutInputFormatSupported anropar ljudmotorn IAudioProcessingObject::IsInputFormatSupported på APO igen med ett NULL-utdataformat och ett indataformat som inte är null för att hämta APO:s föreslagna format. Ljudmotorn kommer sedan att sampla om mikrofonljud till det föreslagna formatet innan det skickas till AEC APO. Detta eliminerar behovet av att AEC APO implementerar samplingsfrekvens och kanalräkningskonvertering.
IApoAuxiliaryInputConfiguration
Gränssnittet IApoAuxiliaryInputConfiguration innehåller metoder som API:er kan implementera så att ljudmotorn kan lägga till och ta bort extra indataströmmar.
Det här gränssnittet implementeras av AEC APO och används av ljudmotorn för att initiera referensindata. I Windows 11 startar AEC APO endast med en enda extra indata – en med referensljudströmmen för ekodämpning. Metoden AddAuxiliaryInput används för att lägga till referensindata till APO. Initieringsparametrarna innehåller en referens till den återgivningsslutpunkt som loopback-strömmen hämtas från.
Metoden IsInputFormatSupported anropas av ljudmotorn för att förhandla om format på de extra indata. Om AEC APO föredrar ett visst format kan det returnera S_FALSE i anropet till IsInputFormatSupported och ange ett föreslaget format. Ljudmotor resamplar referensljudet till det föreslagna formatet och tillhandahåller det vid hjälpindata på AEC APO.
IApoAuxiliaryInputRT
Gränssnittet IApoAuxiliaryInputRT är det realtidssäkra gränssnitt som används för att köra extra indata för en APO.
Det här gränssnittet används för att förse APO med ljuddata via hjälpingången. Observera att hjälp-ljudingångarna inte synkroniseras med anropen till IAudioProcessingObjectRT::APOProcess. När inget ljud återges i återgivningsslutpunkten blir loopback-data inte tillgängliga vid extraindata. d.v.s. inga anrop till IApoAuxiliaryInputRT::AcceptInput
Sammanfattning av AEC CAPX-API:er
Mer information finns på följande sidor.
- Struktur för APO_CONNECTION_PROPERTY_V2 (audioapotypes.h)
- Gränssnittet IApoAcousticEchoCancellation
- IApoAuxiliaryInputConfiguration
- IApoAuxiliaryInputRT
Exempelkod – AEC
Se följande Sysvad Audio AecApo-kodexempel.
Följande kod från Aec APO-exempelrubriken AecAPO.h visar de tre nya offentliga metoderna som läggs till.
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;
}
Följande kod kommer från Aec APO MFX-exemplet – AecApoMfx.cpp och visar implementeringen av AddAuxiliaryInput, när APO bara kan hantera en extra indata.
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;
Granska även exempelkoden som visar implementeringen av CAecApoMFX::IsInputFormatSupported och CAecApoMFX::AcceptInput samt hanteringen av APO_CONNECTION_PROPERTY_V2.
Sekvens av åtgärder – AEC
Vid initialisering:
- IAudioProcessingObject::Initialize
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration:: LockForProcess
- IAudioProcessingObjectConfiguration ::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
Vid ändring av enhetsåtergivning
- IAudioProcessingObject::Initiera
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess
- Ändringar av standardenhet
- IAudioProcessingObjectConfiguration::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess
Rekommenderat buffringsbeteende – AEC
Detta är det rekommenderade buffertbeteendet för AEC.
- Buffertar som hämtas i anropet till IApoAuxiliaryInputRT::AcceptInput ska skrivas till en cirkelbuffert utan att huvudtråden låses.
- Vid anropet till IAudioProcessingObjectRT::APOProcess ska den cirkulära bufferten läsas för det senaste ljudpaketet från referensströmmen och det här paketet ska användas för att köras via ekoreduceringsalgoritmen.
- Tidsstämplar för referens- och mikrofondata kan användas för att rada upp talar- och mikrofondata.
Referensloopback-ström
Som standardinställning ansluter loopback-strömmen till ljudströmmen innan volym eller avstängning tillämpas. En loopback-ström som avläses innan volymen tillämpas kallas för en pre-volym-loopback. En fördel med att ha en återkopplingsström före volyminställningen är en klar och enhetlig ljudström, oavsett aktuell volyminställning.
Vissa AEC-algoritmer kanske föredrar att få tillgång till en loopback-ström som har anslutits efter all volymbearbetning, inklusive avstängning. Den här konfigurationen kallas loopback efter volym.
I nästa huvudversion av Windows kan AEC APOs begära loopback efter volym på slutpunkter som stöds.
Begränsningar
Till skillnad från loopback-strömmar före volyminställning, som är tillgängliga för alla uppspelningsslutpunkter, kanske loopback-strömmar efter volyminställning inte är tillgängliga på alla slutpunkter.
Begära loopback efter volymbearbetning
AEC-API:er som vill använda loopback efter volym bör implementera gränssnittet IApoAcousticEchoCancellation2 .
Ett AEC-APO kan begära loopback efter volym genom att returnera flaggan APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK via parametern Egenskaper i implementeringen av IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties.
Beroende på vilken återgivningsslutpunkt som för närvarande används kanske loopback efter volym inte är tillgänglig. Ett AEC-APO meddelas om loopback efter volym används när dess IApoAuxiliaryInputConfiguration::AddAuxiliaryInput-metod anropas. Om fältet AcousticEchoCanceller_Reference_Input streamProperties innehåller APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK används loopback efter volymjustering.
Följande kod från AEC APO-exempelrubriken AecAPO.h visar de tre nya offentliga metoderna som läggs till.
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;
Följande kodfragment kommer från AEC APO MFX-exemplet – AecApoMfx.cpp och visar implementeringen av GetDesiredReferenceStreamProperties och relevant del av 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.
Inställningsramverk
Med Settings Framework kan API:er exponera metoder för att fråga och ändra egenskapsarkivet för ljudeffekter ("FX Property Store") på en ljudslutpunkt. Det här ramverket kan användas av API:er och maskinvarusupportappar (HSA) som vill kommunicera inställningar till den APO:n. HSA:er kan vara UWP-appar (Universal Windows Platform) och kräver en särskild funktion för att anropa API:erna i Settings Framework. Mer information om HSA-appar finns i UWP-enhetsappar.
FxProperty Store-struktur
Det nya FxProperty-lagringen har tre underförvar: Default, User och Volatile.
Undernyckeln "Standard" innehåller egenskaper för anpassade effekter och fylls i från INF-filen. Dessa egenskaper bevaras inte mellan os-uppgraderingar. Egenskaper som vanligtvis definieras i en INF skulle till exempel passa här. Dessa fylls sedan i igen från INF.
Undernyckeln "Användare" innehåller användarinställningar som rör effektegenskaper. De här inställningarna sparas av operativsystemet över uppgraderingar och migreringar. Till exempel eventuella förinställningar som användaren kan konfigurera som förväntas finnas kvar under uppgraderingen.
Undernyckeln "Volatile" innehåller egenskaper för volatila effekter. Dessa egenskaper går förlorade när enheten startas om och rensas varje gång slutpunkten övergår till aktiv. Dessa förväntas innehålla egenskaper för tidsvarianter (t.ex. baserat på aktuella program som körs, enhetsstatus osv.) Till exempel alla inställningar som är beroende av den aktuella miljön.
Sättet att tänka på användare jämfört med standard är om du vill att egenskaperna ska bevaras mellan operativsystem- och drivrutinsuppgraderingar. Användaregenskaper sparas. Standardegenskaper fylls i igen från INF.
APO-kontexter
Med CAPX-inställningsramverket kan en APO-författare gruppera APO-egenskaper efter kontexter. Varje APO kan definiera sin egen kontext och uppdatera egenskaper i förhållande till sin egen kontext. Egenskapsarkivet för effekter för en ljudslutpunkt kan ha noll eller fler kontexter. Leverantörer är fria att skapa kontexter på det sätt de väljer, oavsett om det är via SFX/MFX/EFX eller efter läge. En leverantör kan också välja att ha en enda kontext för alla API:er som levereras av leverantören.
Begränsad kapacitet för inställningar
API:et för inställningar är avsett att stödja alla OEM-tillverkare och HSA-utvecklare som är intresserade av att fråga efter och ändra inställningarna för ljudeffekter som är associerade med en ljudenhet. Det här API:et exponeras för ett HSA- och Win32-program för att ge åtkomst till egenskapsarkivet via den begränsade funktionen "audioDeviceConfiguration" som måste deklareras i manifestet. Dessutom måste ett motsvarande namnområde deklareras på följande sätt:
<Package
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
...
<Capabilities>
<rescap:Capability Name="audioDeviceConfiguration" />
</Capabilities>
</Package>
IAudioSystemEffectsPropertyStore kan läsas och skrivas av en ISV/IHV-tjänst, ett UWP-lagringsprogram, skrivbordsprogram som inte är administratörer och API:er. Dessutom kan detta fungera som en mekanism för API:er att leverera meddelanden tillbaka till en tjänst eller ett UWP-lagringsprogram.
Anmärkning
Det här är en begränsad funktion: Om ett program skickas med den här funktionen till Microsoft Store utlöser det noggrann granskning. Appen måste vara en maskinvarusupportapp (HSA) och den kommer att undersökas för att utvärdera att den verkligen är en HSA innan överföringen godkänns.
API-definition – Settings Framework
Med det nya gränssnittet IAudioSystemEffectsPropertyStore kan en HSA komma åt egenskapsarkiv för ljudsystemeffekter och registrera sig för meddelanden om egenskapsändring.
Funktionen ActiveAudioInterfaceAsync tillhandahåller en metod för att hämta gränssnittet IAudioSystemEffectsPropertyStore asynkront.
En applikation kan ta emot meddelanden när egenskapssamlingen för systemeffekter ändras med hjälp av det nya IAudioSystemEffectsPropertyChangeNotificationClient-återanropsgränssnittet.
Program som försöker hämta IAudioSystemEffectsPropertyStore med IMMDevice::Activate
Exemplet visar hur en maskinvarusupportapp kan använda IMMDevice::Activate för att aktivera IAudioSystemEffectsPropertyStore. Exemplet visar hur du använder IAudioSystemEffectsPropertyStore för att öppna en IPropertyStore som har användarinställningar.
#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;
}
Exempel på användning av ActivateAudioInterfaceAsync
Det här exemplet gör samma sak som föregående exempel, men i stället för att använda IMMDevice använder det API:et ActivateAudioInterfaceAsync för att hämta gränssnittet IAudioSystemEffectsPropertyStore asynkront.
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::Initiera kod med hjälp av IAudioSystemEffectsPropertyStore
Exemplet visar att implementeringen av ett APO kan använda APOInitSystemEffects3-strukturen för att hämta användar-, standard- och flyktiga IPropertyStore-gränssnitt för APO under initieringen av 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;
}
Applikationen registrerar sig för meddelanden om egenskapsändringar
Exemplet visar användningen av registrering för egenskapsändringsmeddelanden. Detta bör inte användas med APO, och bör användas av Win32-applikationer.
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;
}
Exempelkod – Settings Framework
Den här exempelkoden kommer från sysvad SFX Swap APO-exemplet – 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);
}
}
INF-avsnitt – Inställningsramverk
INF-filsyntaxen för att deklarera effektegenskaper med hjälp av det nya CAPX-inställningsramverket är följande:
HKR, FX\0\{ApoContext}\{Default|User}, %CUSTOM_PROPERTY_KEY%,,,
Detta ersätter den äldre syntaxen för att deklarera effektegenskaper på följande sätt:
# Old way of declaring FX properties
HKR, FX\0, %CUSTOM_PROPERTY_KEY_1%,,,
INF kan inte ha både posten IAudioSystemEffectsPropertyStore och IPropertyStore-posten för samma ljudslutpunkt. Det stöds inte.
Exempel som visar användning av det nya egenskapslagret:
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}"
Meddelanderamverk
Ramverket Notifikationer gör att ljudeffekter (APO:er) kan begära och hantera volym-, slutpunkts- och ljudeffekters egenskapsändringsnotiser. Det här ramverket är avsett att ersätta befintliga API:er som används av APO:er för att registrera sig och avregistrera sig för notifikationer.
Det nya API:et introducerar ett gränssnitt som API:er kan använda för att deklarera vilken typ av meddelanden som APO är intresserad av. Windows frågar APO efter de meddelanden som den är intresserad av och vidarebefordrar meddelandet till API:erna. APO:er behöver inte längre uttryckligen anropa registrerings- eller avregistrerings-API:erna.
Meddelanden levereras till en APO via en seriekö. När det är tillämpligt sänder det första meddelandet det begärda värdets initiala tillstånd (till exempel ljudslutpunktsvolymen). Meddelanden stoppas när audiodg.exe slutar att använda en APO för strömning. API:er slutar ta emot meddelanden efter UnlockForProcess. Det är fortfarande nödvändigt att synkronisera UnlockForProcess och eventuella aviseringar under flygning.
Implementering – Notifikationsramverk
För att utnyttja ramverket för meddelanden deklarerar en APO vilka meddelanden den är intresserad av. Det finns inga explicita registrerings-/avregistreringsanrop. Alla meddelanden till APO serialiseras och det är viktigt att inte blockera återanropstråden för länge.
API-definition – Notifikationsramverk
Meddelanderamverket implementerar ett nytt IAudioProcessingObjectNotifications-gränssnitt som kan implementeras av klienter för att registrera och ta emot vanliga ljudrelaterade meddelanden för APO-slutpunkts- och systemeffektmeddelanden.
Mer information finns på följande sidor:
Exempelkod – Notifications Framework
Exemplet visar hur en APO kan implementera gränssnittet IAudioProcessingObjectNotifications. I GetApoNotificationRegistrationInfo-metoden registrerar sig exempel-APO för meddelanden om ändringar i egenskapslagringen för systemeffekter.
Metoden HandleNotification anropas av operativsystemet för att meddela APO om ändringar som matchar vad APO har registrerats för.
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;
}
}
Följande kod kommer från Exemplet Swap APO MFX – swapapomfx.cpp och visar hur man registrerar händelser som returnerar en matris med 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;
}
Följande kod kommer från SwapAPO MFX HandleNotifications-exemplet – swapapomfx.cpp och visar hur du hanterar meddelanden.
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);
}
}
}
}
Loggningsramverk
Loggningsramverket ger APO-utvecklare ytterligare sätt att samla in data för att förbättra utveckling och felsökning. Det här ramverket förenar de olika metoderna för loggning som används av olika leverantörer och kopplar den till ljudspårningsloggningsleverantörerna för att skapa mer meningsfull loggning. Det nya ramverket tillhandahåller ett loggnings-API, vilket lämnar resten av arbetet som ska utföras av operativsystemet.
Providern definieras som:
IMPLEMENT_TRACELOGGING_CLASS(ApoTelemetryProvider, "Microsoft.Windows.Audio.ApoTrace",
// {8b4a0b51-5dcf-5a9c-2817-95d0ec876a87}
(0x8b4a0b51, 0x5dcf, 0x5a9c, 0x28, 0x17, 0x95, 0xd0, 0xec, 0x87, 0x6a, 0x87));
Varje APO har ett eget aktivitets-ID. Eftersom detta använder den befintliga mekanismen för spårningsloggning kan befintliga konsolverktyg användas för att filtrera efter dessa händelser och visa dem i realtid. Du kan använda befintliga verktyg som tracelog och tracefmt enligt beskrivningen i Verktyg för programvaruspårning – Windows-drivrutiner. Mer information om spårningssessioner finns i Skapa en spårningssession med ett kontroll-GUID.
Spårningsloggningshändelserna är inte markerade som telemetri och visas inte som telemetriprovider i verktyg som xperf.
Implementering – Loggningsramverk
Loggningsramverket baseras på de loggningsmekanismer som tillhandahålls av ETW-spårning. Mer information om ETW finns i Händelsespårning. Detta är inte avsett för loggning av ljuddata, utan snarare för att logga händelser som vanligtvis loggas i produktion. Loggnings-API:er bör inte användas från realtidsströmningstråden eftersom dessa kan leda till att pumptråden föregrips av OS CPU-schemaläggaren. Loggning bör främst användas för händelser som hjälper till med felsökningsproblem som ofta finns i fältet.
API-definition – Loggningsramverk
Loggningsramverket introducerar gränssnittet IAudioProcessingObjectLoggingService som tillhandahåller en ny loggningstjänst för API:er.
Mer information finns i IAudioProcessingObjectLoggingService.
Exempelkod – Logging Framework
Exemplet visar användningen av metoden IAudioProcessingObjectLoggingService::ApoLog och hur den här gränssnittspekaren hämtas i IAudioProcessingObject::Initialize.
Exempel på AecApoMfx-loggning.
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;
}
Trådramverk
Trådramverket som gör det möjligt att flertråda effekter med hjälp av arbetsköer från en lämplig MMCSS-uppgift (Multimedia Class Scheduler Service) via ett enkelt API. Skapandet av seriella arbetsköer i realtid och deras koppling till huvudpumptråden hanteras av operativsystemet. Det här ramverket gör att API:er kan köa kortvariga arbetsobjekt. Synkronisering mellan aktiviteter fortsätter att vara APO:s ansvar. Mer information om MMCSS-trådning finns i Multimedia Class Scheduler Service och Real-Time Work Queue API.
API-definitioner – Threading Framework
Threading-ramverket introducerar gränssnittet IAudioProcessingObjectQueueService som ger åtkomst till realtidsarbetskön för API:er.
Mer information finns på följande sidor:
Exempelkod – Threading Framework
Det här exemplet visar användningen av metoden IAudioProcessingObjectRTQueueService::GetRealTimeWorkQueue och hur gränssnittspekaren IAudioProcessingObjectRTQueueService hämtas i 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;
}
Fler exempel på hur du använder det här gränssnittet finns i följande exempelkod:
- SwapAPO SwapMFXApoAsyncCallback klassdefinition – exempel
- SwapAPO Invoke Function – exempel
- SwapAPO Create Async Callback – exempel
Identifiering och kontroll av ljudeffekter
Upptäcktsramverket tillåter operativsystemet att styra ljudeffekterna på deras ström. Dessa API:er ger stöd för scenarier där användaren av ett program behöver kontrollera vissa effekter på strömmar (t.ex. djup brusreducering). För att uppnå detta lägger det här ramverket till följande:
- Ett nytt API för att fråga från en APO för att avgöra om en ljudeffekt kan aktiveras eller inaktiveras.
- Ett nytt API för att ange tillståndet för en ljudeffekt till på/av.
- Ett meddelande när det sker en ändring i listan över ljudeffekter eller när resurser blir tillgängliga så att en ljudeffekt nu kan aktiveras/inaktiveras.
Implementering – Identifiering av ljudeffekter
En APO måste implementera gränssnittet IAudioSystemEffects3 om det avser att exponera effekter som kan aktiveras och inaktiveras dynamiskt. En APO exponerar dess ljudeffekter via funktionen IAudioSystemEffects3::GetControllableSystemEffectsList och aktiverar och inaktiverar dess ljudeffekter via funktionen IAudioSystemEffects3::SetAudioSystemEffectState .
Exempelkod – Identifiering av ljudeffekt
Exempelkoden för identifiering av ljudeffekt finns i SwapAPOSFX-exemplet – swapaposfx.cpp.
Följande exempelkod visar hur du hämtar listan över konfigurerbara effekter. GetControllableSystemEffectsList-exempel – 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;
}
Följande exempelkod visar hur du aktiverar och inaktiverar effekter. SetAudioSystemEffectState-exempel – 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;
}
Återanvändning av WM SFX- och MFX-API:er i Windows 11, version 22H2
Från och med Windows 11 version 22H2 kan nu INF-konfigurationsfilerna som återanvänder inkorgen WM SFX- och MFX-API:er återanvända CAPX SFX- och MFX-API:erna. I det här avsnittet beskrivs de tre sätten att göra detta på.
Det finns tre insättningspunkter för APO:er: återgivning före mix, återgivning efter mix och inspelning. Varje logisk enhets ljudmotor handhar en instans av en förmix-rendering APO per ström (rendera SFX) och en eftermix-rendering APO (MFX). Ljudmotorn stöder också en instans av en capture APO (capture SFX) som infogas i varje inspelningsström. Mer information om hur du återanvänder eller omsluter API:er för inkorgen finns i Kombinera anpassade API:er och Windows-API:er.
CAPX SFX- och MFX-API:erna kan återanvändas på något av följande tre sätt.
Så här använder du avsnittet INF DDInstall
Använd mssysfx.CopyFilesAndRegisterCapX från wdmaudio.inf genom att lägga till följande poster.
Include=wdmaudio.inf
Needs=mssysfx.CopyFilesAndRegisterCapX
Använda en INF-fil för utökning
wdmaudioapo.inf är ett klassförlängnings-INF för AudioProcessingObject. Den innehåller enhetsspecifik registrering av SFX- och MFX-API:erna.
Referera direkt till WM SFX- och MFX-API:er för ström- och lägeseffekter
Om du vill referera direkt till dessa API:er för ström- och lägeseffekter använder du följande GUID-värden.
- Använd
{C9453E73-8C5C-4463-9984-AF8BAB2F5447}som WM SFX-APO - Använd
{13AB3EBD-137E-4903-9D89-60BE8277FD17}som WM MFX APO.
SFX (Stream) och MFX (mode) refererades i Windows 8.1 till LFX (lokal) och MFX kallades GFX (global). Dessa registerposter fortsätter att använda de tidigare namnen.
Enhetsspecifik registrering använder HKR i stället för HKCR.
INF-filen måste ha följande poster tillagda.
HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
HKR,"FX\\0\\%WMALFXGFXAPO_Context%\\User",,,
WMALFXGFXAPO_Context = "{B13412EE-07AF-4C57-B08B-E327F8DB085B}"
Dessa INF-filposter skapar en egenskapssamling som ska användas av Windows 11-API:erna för de nya APO:erna.
PKEY_FX_Association i INF exempel.
HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%, bör ersättas med HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%.