Windows 11-APIs für Audioverarbeitungsobjekte
In diesem Thema wird eine Reihe neuer Windows 11-APIs für Audioverarbeitungsobjekte (APOs) vorgestellt, die mit einem Audiotreiber bereitgestellt werden.
Mit Windows können Drittanbieter von Audiohardware benutzerdefinierte hostbasierte digitale Signalverarbeitungseffekte integrieren. Diese Effekte sind als Benutzermodus-Audioverarbeitungsobjekte (APOs) paketiert. Weitere Informationen finden Sie unter Windows-Audioverarbeitungsobjekte.
Einige der hier beschriebenen APIs ermöglichen neue Szenarien für unabhängige Hardwareanbieter (Independent Hardware Vendors, IHV) und unabhängige Softwareanbieter (ISV), während andere APIs Alternativen bereitstellen sollen, die die allgemeine Audiozuverlässigkeit und die Debugging-Funktionen verbessern.
- Das AEC-Framework (Acoustic Echo Cancellation) ermöglicht es einem APO, sich als AEC-APO zu identifizieren und Zugriff auf einen Referenzstream und zusätzliche Steuerelemente zu gewähren.
- Das Einstellungsframework ermöglicht APOs, Methoden zum Abfragen und Ändern des Eigenschaftenspeichers für Audioeffekte („FX-Eigenschaftenspeicher“) an einem Audioendpunkt verfügbar zu machen. Wenn diese Methoden von einem APO implementiert werden, können sie von Hardware Support Apps (HSA) aufgerufen werden, die diesem APO zugeordnet sind.
- Das Benachrichtigungsframework ermöglicht es Audioeffekten (APOs), Benachrichtigungen für die Verarbeitung von Lautstärke-, Endpunkt- und Audioeffekt-Eigenschaftsspeicheränderungen anzufordern.
- Das Protokollierungsframework unterstützt die Entwicklung und das Debuggen von APOs.
- Das Threading-Framework ermöglicht das Multithreading von APOs mithilfe eines vom Betriebssystem verwalteten, MMCSS-registrierten Thread-Pools.
- Mit den Audioeffekterkennungs- und Steuerelement-APIs kann das Betriebssystem Effekte erkennen, aktivieren und deaktivieren, die für die Verarbeitung in einem Stream verfügbar sind.
Um diese neuen APIs zu nutzen, wird erwartet, dass APOs die neue IAudioSystemEffects3-Schnittstelle verwenden. Wenn ein APO diese Schnittstelle implementiert, interpretiert das Betriebssystem dies als implizites Signal, dass das APO das APO-Einstellungsframework unterstützt und es dem APO ermöglicht, allgemeine audiobezogene Benachrichtigungen vom Audiomodul zu abonnieren.
Windows 11 APO CAPX-Entwicklungsanforderungen
Alle neuen APOs, die auf einem Gerät für Windows 11 bereitgestellt werden, müssen mit den in diesem Thema aufgeführten APIs kompatibel sein und über HLK validiert werden. Darüber hinaus wird von allen APOs, die AEC nutzen, erwartet, dass sie der in diesem Thema beschriebenen und über HLK validierten Implementierung folgen. Benutzerdefinierte Implementierungen für diese zentralen Audioverarbeitungserweiterungen (Einstellungen, Protokollierung, Benachrichtigungen, Threading, AEC) nutzen voraussichtlich CAPX-APIs. Dies wird durch die Windows 11 HLK-Tests überprüft. Wenn ein APO beispielsweise Registrierungsdaten zum Speichern von Einstellungen verwendet, anstatt das Einstellungsframework zu verwenden, schlägt der zugehörige HLK-Test fehl.
Windows-Versionsanforderungen
Die in diesem Thema beschriebenen APIs sind ab Build 22000 des Windows 11-Betriebssystems, WDK und SDK verfügbar. Windows 10 unterstützt diese APIs nicht. Wenn ein APO sowohl unter Windows 10 als auch unter Windows 11 funktionieren soll, kann es prüfen, ob es mit der APOInitSystemEffects2- oder der APOInitSystemEffects3-Struktur initialisiert wird, um festzustellen, ob es auf einem Betriebssystem läuft, das die CAPX-APIs unterstützt.
Die neuesten Versionen von Windows, dem WDK und dem SDK können unten über das Windows Insider-Programm heruntergeladen werden. Partner, die über Partner Center mit Microsoft zusammenarbeiten, können auch über Collaborate auf diese Inhalte zugreifen. Weitere Informationen zur Zusammenarbeit finden Sie in Einführung in Microsoft Collaborate.
- Windows Insider-Vorschauversion – ISO-Download
- Windows Insider-Vorschauversion – WDK-Download
- Windows Insider-Vorschauversion – SDK-Download
Windows 11 WHCP-Inhalte wurden aktualisiert, um Partnern die Mittel zur Überprüfung dieser APIs bereitzustellen.
Der Beispielcode für die in diesem Thema beschriebenen Inhalte finden Sie hier: Audio/SYSVAD/APO – GitHub
Akustische Echounterdrückung (AEC)
Die akustische Echounterdrückung (AEC) ist ein allgemeiner Audioeffekt, der von unabhängigen Hardwareanbietern (IHVs) und unabhängigen Softwareanbietern (ISVs) als Audioverarbeitungsobjekt (APO) in der Mikrofonerfassungspipeline implementiert wird. Dieser Effekt unterscheidet sich von anderen normalerweise von IHVs und ISVs implementierten Effekten dadurch, dass er zwei Eingänge erfordert – einen Audiostream vom Mikrofon und einen Audiostream von einem Rendergerät, das als Referenzsignal fungiert.
Dieser neue Satz von Schnittstellen ermöglicht es einem AEC APO, sich gegenüber dem Audiomodul als solcher zu identifizieren. Dies führt dazu, dass das Audiomodul die APO entsprechend mit mehreren Eingaben und einer einzelnen Ausgabe konfiguriert.
Wenn die neuen AEC-Schnittstellen von einem APO implementiert werden, führt das Audiomodul Folgendes aus:
- Konfigurieren Sie das APO mit einem zusätzlichen Eingang, der dem APO den Referenzstream von einem geeigneten Renderendpunkt bereitstellt.
- Wechseln Sie die Referenzstreams, während sich das Rendergerät ändert.
- Lassen Sie zu, dass ein APO das Format des Eingabemikrofons und des Referenzstreams steuert.
- Lassen Sie zu, dass ein APO Zeitstempel im Mikrofon und Referenzstreams abruft.
Vorheriger Ansatz – Windows 10
APOs sind einzelne Eingaben – einzelne Ausgabeobjekte. Das Audiomodul stellt ein AEC APO des Audiosignals vom Mikrofonendpunkt an seiner Eingabe bereit. Um den Referenzstream zu erhalten, kann ein APO entweder über proprietäre Schnittstellen mit dem Treiber interagieren, um das Referenzaudio vom Renderendpunkt abzurufen, oder WASAPI verwenden, um einen Loopback-Stream am Renderendpunkt zu öffnen.
Beide oben genannten Ansätze haben Nachteile:
Ein AEC-APO, das private Kanäle verwendet, um einen Referenzstream vom Treiber zu erhalten, kann dies normalerweise nur über das integrierte Audio-Rendergerät vornehmen. Daher funktioniert die Echounterdrückung nicht, wenn der Benutzer Audio über ein nicht integriertes Gerät wie ein USB- oder Bluetooth-Audiogerät wiedergibt. Nur das Betriebssystem kennt die richtigen Renderendpunkte, die als Referenzendpunkte dienen können.
Ein APO kann WASAPI verwenden, um den Standard-Renderendpunkt für die Echounterdrückung auszuwählen. Beim Öffnen eines Loopback-Streams vom audiodg.exe-Prozess (wo das APO gehostet wird) sind jedoch einige Fallstricke zu beachten.
- Der Loopback-Stream kann nicht geöffnet/deaktiviert werden, wenn das Audiomodul die Haupt-APO-Methoden aufruft, da dies zu einem Deadlock führen kann.
- Ein Erfassungs-APO kennt den Status der Streams seiner Clients nicht. Das heißt, eine Erfassungs-App könnte einen Erfassungs-Stream im Status „STOP“ haben, das APO ist sich dieses Status jedoch nicht bewusst und hält daher den Loopback-Stream im Status „RUN“ offen, was im Hinblick auf den Stromverbrauch ineffizient ist.
API-Definition – AEC
Das AEC-Framework bietet neue Strukturen und Schnittstellen, die APOs nutzen können. Diese neuen Strukturen und Schnittstellen werden im Folgenden beschrieben.
APO_CONNECTION_PROPERTY_V2-Struktur
APOs, die die IApoAcousticEchoCancellation-Schnittstelle implementieren, übergeben eine APO_CONNECTION_PROPERTY_V2-Struktur im Aufruf von IAudioProcessingObjectRT::APOProcess. Neben allen Feldern in der APO_CONNECTION_PROPERTY-Struktur stellt Version 2 der Struktur auch Zeitstempelinformationen für die Audiopuffer bereit.
Ein APO kann das Feld APO_CONNECTION_PROPERTY.u32Signature untersuchen, um festzustellen, ob die Struktur, die es vom Audiomodul empfängt, vom Typ APO_CONNECTION_PROPERTY oder APO_CONNECTION_PROPERTY_V2 ist. APO_CONNECTION_PROPERTY-Strukturen weisen eine Signatur von APO_CONNECTION_PROPERTY_SIGNATURE auf, während APO_CONNECTION_PROPERTY_V2 eine Signatur aufweisen, die APO_CONNECTION_PROPERTY_V2_SIGNATURE entspricht. Wenn die Signatur einen Wert aufweist, der APO_CONNECTION_PROPERTY_V2_SIGNATURE entspricht, kann der Zeiger auf die APO_CONNECTION_PROPERTY-Struktur sicher in einen APO_CONNECTION_PROPERTY_V2-Zeiger typisiert werden.
Der folgende Code stammt aus dem Aec APO MFX-Beispiel – AecApoMfx.cpp und zeigt die Neufassung.
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
Die IApoAcousticEchoCancellation-Schnittstelle hat keine expliziten Methoden dafür. Ihr Zweck besteht darin, ein AEC APO für das Audiomodul zu identifizieren. Diese Schnittstelle kann nur durch Moduseffekte (MFX) an Erfassungsendpunkten implementiert werden. Die Implementierung dieser Schnittstelle auf einem anderen APO führt zu einem Fehler beim Laden dieses APO. Allgemeine Informationen zu MFX finden Sie unter Architektur von Audioverarbeitungsobjekten.
Wenn der Moduseffekt auf einen Erfassungsendpunkt als Reihe verketteter APOs implementiert wird, darf nur das APO, das dem Gerät am nächsten liegt, diese Schnittstelle implementieren. APOs, die diese Schnittstelle implementieren, wird die APO_CONNECTION_PROPERTY_V2-Struktur beim Aufruf von IAudioProcessingobjectRT::APOProcess angeboten. Das APO kann eine APO_CONNECTION_PROPERTY_V2_SIGNATURE-Signatur für die Verbindungseigenschaft überprüfen und die eingehende APO_CONNECTION_PROPERTY-Struktur in eine APO_CONNECTION_PROPERTY_V2-Struktur typisieren.
In Anbetracht der Tatsache, dass AEC-APOs ihre Algorithmen normalerweise mit einer bestimmten Abtastrate/Kanalanzahl ausführen, bietet das Audiomodul Resampling-Unterstützung für APOs, die die IApoAcousticEchoCancellation-Schnittstelle implementieren.
Wenn ein AEC APO APOERR_FORMAT_NOT_SUPPORTED beim Aufruf von IAudioProcessingObject::OutInputFormatSupported zurückgibt, ruft das Audiomodul IAudioProcessingObject::IsInputFormatSupported auf dem APO erneut mit einem NULL-Ausgabeformat und einem Nicht-Null-Eingabeformat auf, um das vom APO vorgeschlagene Format zu erhalten. Das Audiomodul führt dann ein Resampling des Mikrofon-Audios in das vorgeschlagene Format durch, bevor es an das AEC APO gesendet wird. Dadurch ist es nicht erforderlich, dass das AEC APO die Konvertierung der Abtastrate und der Kanalanzahl implementiert.
IApoAuxiliaryInputConfiguration
Die IApoAuxiliaryInputConfiguration-Schnittstelle stellt Methoden bereit, die APOs implementieren können, damit das Audiomodul zusätzliche Eingabestreams hinzufügen und entfernen kann.
Diese Schnittstelle wird vom AEC APO implementiert und vom Audiomodul verwendet, um die Referenzeingabe zu initialisieren. In Windows 11 wird der AEC APO nur mit einem einzigen Hilfseingang initialisiert – einem, der den Referenz-Audiostream für die Echounterdrückung enthält. Die AddAuxiliaryInput-Methode wird verwendet, um die Referenzeingabe zum APO hinzuzufügen. Die Initialisierungsparameter enthalten einen Verweis auf den Renderendpunkt, von dem der Loopback-Stream abgerufen wird.
Die IsInputFormatSupported-Methode wird vom Audiomodul aufgerufen, um Formate für den Hilfseingang auszuhandeln. Wenn das AEC APO ein bestimmtes Format bevorzugt, kann es S_FALSE im Aufruf von IsInputFormatSupported zurückgeben und ein vorgeschlagenes Format angeben. Das Audiomodul sampelt erneut das Referenzaudio in das vorgeschlagene Format und stellt es am Hilfseingang des AEC APO bereit.
IApoAuxiliaryInputRT
Die IApoAuxiliaryInputRT-Schnittstelle ist die echtzeitsichere Schnittstelle zur Ansteuerung der Hilfseingänge eines APO.
Über diese Schnittstelle werden dem APO Audiodaten über den Hilfseingang zur Verfügung gestellt. Beachten Sie, dass die zusätzlichen Audioeingänge nicht mit den Aufrufen von IAudioProcessingObjectRT::APOProcess synchronisiert sind. Wenn am Renderendpunkt kein Audio gerendert wird, sind am Hilfseingang keine Loopback-Daten verfügbar. Das heißt, es erfolgt kein Aufruf von IApoAuxiliaryInputRT::AcceptInput.
Übersicht über die AEC CAPX-APIs
Weitere Informationen finden Sie auf den folgenden Seiten.
- APO_CONNECTION_PROPERTY_V2-Struktur (audioapotypes.h)
- IApoAcousticEchoCancellation-Schnittstelle
- IApoAuxiliaryInputConfiguration
- IApoAuxiliaryInputRT
Beispielcode – AEC
Weitere Informationen finden Sie in den folgenden Sysvad Audio AecApo-Codebeispielen.
Der folgende Code aus dem Aec APO-Beispielheader – AecAPO.h zeigt die drei neuen öffentlichen Methoden, die hinzugefügt werden.
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;
}
Der folgende Code stammt aus dem Aec APO MFX-Beispiel – AecApoMfx.cpp und zeigt die Implementierung von AddAuxiliaryInput an, wenn das APO nur einen Hilfseingang verarbeiten kann.
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;
Überprüfen Sie auch den Beispielcode, der die Implementierung von CAecApoMFX::IsInputFormatSupported
und CAecApoMFX::AcceptInput
sowie die Behandlung von APO_CONNECTION_PROPERTY_V2
zeigt.
Abfolge der Vorgänge – AEC
Bei der Initialisierung:
- IAudioProcessingObject::Initialize
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration:: LockForProcess
- IAudioProcessingObjectConfiguration ::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
Beim Ändern des Rendergeräts:
- IAudioProcessingObject::Initialize
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess
- Standardgeräteänderungen
- IAudioProcessingObjectConfiguration::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess
Empfohlenes Pufferverhalten – AEC
Dies ist das empfohlene Pufferverhalten für AEC.
- Puffer, die beim Aufruf von IApoAuxiliaryInputRT::AcceptInput abgerufen werden, sollten in einen Ringpuffer geschrieben werden, ohne den Standard-Thread zu sperren.
- Beim Aufruf von IAudioProcessingObjectRT::APOProcess sollte der Ringpuffer für das neueste Audiopaket aus dem Referenzstream gelesen werden, und dieses Paket sollte für die Ausführung über den Echounterdrückungsalgorithmus verwendet werden.
- Zeitstempel der Referenz- und Mikrofondaten können verwendet werden, um die Lautsprecher- und Mikrofondaten abzugleichen.
Referenz-Loopbackstream
Standardmäßig „überwacht“ (lauscht) der Loopback-Stream den Audiostream, bevor Lautstärke oder Stummschaltung angewendet wird. Ein Loopback-Stream, der abgegriffen wird, bevor die Lautstärke angewendet wurde, wird als Pre-Volume-Loopback-Stream bezeichnet. Ein Vorteil eines Pre-Volume-Loopback-Streams ist ein klarer und gleichmäßiger Audiostream, unabhängig von der aktuellen Lautstärkeeinstellung.
Einige AEC-Algorithmen ziehen es möglicherweise vor, einen Loopback-Stream zu erhalten, der nach jeder Lautstärkeverarbeitung (einschließlich der Stummschaltung) verbunden wurde. Diese Konfiguration wird als Post-Volume-Loopback bezeichnet.
In der nächsten Hauptversion von Windows AEC APOs kann ein Post-Volume-Loopback an unterstützten Endpunkten angefordert werden.
Begrenzungen
Im Gegensatz zu Pre-Volume-Loopbackstreams, die für alle Renderendpunkte verfügbar sind, sind Post-Volume-Loopbackstreams möglicherweise nicht für alle Endpunkte verfügbar.
Anfordern von Post-Volume-Loopback
AEC APOs, die Post-Volume-Loopback verwenden möchten, sollten die IApoAcousticEchoCancellation2-Schnittstelle implementieren.
Ein AEC APO kann ein Post-Volume-Loopback anfordern, indem das APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK-Flag über den Properties-Parameter in seiner Implementierung von IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties zurückgegeben wird.
Abhängig vom derzeit verwendeten Renderendpunkt ist das Post-Volume-Loopback möglicherweise nicht verfügbar. Ein AEC APO wird benachrichtigt, wenn ein Post-Volume-Loopback beim Aufruf der IApoAuxiliaryInputConfiguration::AddAuxiliaryInput-Methode verwendet wird. Wenn das AcousticEchoCanceller_Reference_Input-streamProperties-Feld APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK enthält, wird das Post-Volume-Loopback verwendet.
Der folgende Code aus dem AEC APO-Beispielheader – AecAPO.h zeigt die drei neuen öffentlichen Methoden, die hinzugefügt werden.
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;
Der folgende Codeausschnitt stammt aus dem AEC APO MFX-Beispiel – AecApoMfx.cpp und zeigt die Implementierung von GetDesiredReferenceStreamProperties und den relevanten Teil von 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.
Einstellungsframework
Das Einstellungsframework ermöglicht APOs, Methoden zum Abfragen und Ändern des Eigenschaftenspeichers für Audioeffekte („FX-Eigenschaftenspeicher“) an einem Audioendpunkt bereitzustellen. Dieses Framework kann von APOs und Hardware Support Apps (HSA) verwendet werden, die diesem APO Einstellungen mitteilen möchten. HSAs können UWP-Apps (Universal Windows Platform) sein und erfordern eine spezielle Funktion zum Aufrufen der APIs im Einstellungsframework. Weitere Informationen zu HSA-Apps finden Sie unter UWP-Geräte-Apps.
FxProperty-Speicherstruktur
Der neue FxProperty-Speicher verfügt über drei Unterspeicher: „Default“, „User“ und „Volatile“.
Der Unterschlüssel „Default“ enthält benutzerdefinierte Effekteigenschaften und wird aus der INF-Datei gefüllt. Diese Eigenschaften bleiben bei Betriebssystem-Upgrades nicht bestehen. Hier würden beispielsweise Eigenschaften passen, die typischerweise in einer INF definiert sind. Diese werden dann aus der INF erneut aufgefüllt.
Der Unterschlüssel „User“ enthält Benutzereinstellungen für Effekteigenschaften. Diese Einstellungen werden vom Betriebssystem über Upgrades und Migrationen hinweg beibehalten. Zum Beispiel alle Voreinstellungen, die der Benutzer konfigurieren kann und von denen erwartet wird, dass sie über das Upgrade hinweg bestehen bleiben.
Der Unterschlüssel „Volatile“ enthält Eigenschaften für flüchtige Effekte. Diese Eigenschaften gehen beim Neustart des Geräts verloren und werden jedes Mal gelöscht, wenn der Endpunkt in den aktiven Zustand übergeht. Es wird erwartet, dass diese zeitvariante Eigenschaften enthalten (z. B. basierend auf aktuell ausgeführten Anwendungen, Gerätestatus usw.), beispielsweise alle Einstellungen, die von der aktuellen Umgebung abhängig sind.
Beim Vergleich zwischen Benutzer und Standard geht es darum, ob die Eigenschaften über Betriebssystem- und Treiber-Upgrades hinweg bestehen bleiben sollen. Benutzereigenschaften werden beibehalten. Standardeigenschaften werden aus der INF erneut aufgefüllt.
APO-Kontexte
Das CAPX-Einstellungsframework ermöglicht es einem APO-Autor, APO-Eigenschaften nach Kontexten zu gruppieren. Jedes APO kann einen eigenen Kontext definieren und Eigenschaften relativ zu seinem eigenen Kontext aktualisieren. Der Effekteigenschaftenspeicher für einen Audioendpunkt hat möglicherweise null oder mehr Kontexte. Es steht den Anbietern frei, Kontexte nach Belieben zu erstellen, sei es nach SFX/MFX/EFX oder nach Modus. Ein Anbieter könnte sich auch dafür entscheiden, einen einzigen Kontext für alle von diesem Anbieter versendeten APOs zu haben.
Einstellungen für eingeschränkte Funktion
Die Einstellungs-API soll alle OEMs und HSA-Entwickler unterstützen, die daran interessiert sind, die mit einem Audiogerät verbundenen Audioeffekteinstellungen abzufragen und zu ändern. Diese API wird für HSA- und Win32-Anwendungen verfügbar gemacht, um über die eingeschränkte Funktion „audioDeviceConfiguration“, die im Manifest deklariert werden muss, Zugriff auf den Eigenschaftenspeicher zu ermöglichen. Darüber hinaus muss ein entsprechender Namespace wie folgt deklariert werden:
<Package
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap mp rescap">
...
<Capabilities>
<rescap:Capability Name="audioDeviceConfiguration" />
</Capabilities>
</Package>
IAudioSystemEffectsPropertyStore kann von einem ISV/IHV-Dienst, einer UWP-Speicheranwendung, Nicht-Administrator-Desktopanwendungen und APOs gelesen und geschrieben werden. Darüber hinaus kann dies als Mechanismus für APOs dienen, um Nachrichten an einen Dienst oder eine UWP-Speicheranwendung zurückzusenden.
Hinweis
Dies ist eine eingeschränkte Funktion: Wenn eine Anwendung mit dieser Funktion an den Microsoft Store übermittelt wird, wird eine genaue Prüfung ausgelöst. Bei der App muss es sich um eine Hardware-Support-App (HSA) handeln. Bevor die Einreichung genehmigt wird, wird geprüft, ob es sich tatsächlich um eine HSA handelt.
API-Definition – Einstellungsframework
Die neue IAudioSystemEffectsPropertyStore-Schnittstelle ermöglicht es einer HSA, auf Eigenschaftenspeicher für Audiosystemeffekte zuzugreifen und sich für Eigenschaftsänderungsbenachrichtigungen zu registrieren.
Die ActiveAudioInterfaceAsync-Funktion bietet eine Methode zum asynchronen Abrufen der IAudioSystemEffectsPropertyStore-Schnittstelle.
Eine App kann mithilfe der neuen IAudioSystemEffectsPropertyChangeNotificationClient-Rückrufschnittstelle Benachrichtigungen empfangen, wenn sich der Systemeffekteigenschaftenspeicher ändert.
Anwendung, die versucht, den IAudioSystemEffectsPropertyStore mit IMMDevice::Activate abzurufen
Das Beispiel zeigt, wie eine Hardware-Support-App IMMDevice::Activate verwenden kann, um IAudioSystemEffectsPropertyStore zu aktivieren. Das Beispiel zeigt, wie Sie mit IAudioSystemEffectsPropertyStore einen IPropertyStore mit Benutzereinstellungen öffnen.
#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;
}
Beispiel mit ActivateAudioInterfaceAsync
In diesem Beispiel wird die gleiche Funktion wie im vorherigen Beispiel ausgeführt, aber statt IMMDevice zu verwenden, wird die ActivateAudioInterfaceAsync-API verwendet, um die IAudioSystemEffectsPropertyStore-Schnittstelle asynchron abzurufen.
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-Code mit dem IAudioSystemEffectsPropertyStore
Das Beispiel zeigt, dass die Implementierung eines APO die APOInitSystemEffects3-Struktur verwenden kann, um die Benutzer-, Standard- und flüchtigen IPropertyStore-Schnittstellen für das APO während der Initialisierung des APO abzurufen.
#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;
}
Anwendungsregistrierung für Eigenschaftsänderungsbenachrichtigungen
Das Beispiel veranschaulicht die Verwendung der Registrierung für Eigenschaftsänderungsbenachrichtigungen. Dies sollte nicht mit dem APO verwendet werden und sollte von Win32-Anwendungen verwendet werden.
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;
}
Beispielcode – Einstellungsframework
Dieser Beispielcode stammt aus dem sysvad SFX Swap APO-Beispiel – 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-Abschnitt – Einstellungsframework
Die INF-Dateisyntax zum Deklarieren von Effekteigenschaften mithilfe des neuen CAPX-Einstellungsframeworks lautet wie folgt:
HKR, FX\0\{ApoContext}\{Default|User}, %CUSTOM_PROPERTY_KEY%,,,
Dadurch wird die ältere Syntax für das Deklarieren von Effekteigenschaften wie folgt ersetzt:
# Old way of declaring FX properties
HKR, FX\0, %CUSTOM_PROPERTY_KEY_1%,,,
Die INF kann nicht sowohl den IAudioSystemEffectsPropertyStore-Eintrag als auch den IPropertyStore-Eintrag für denselben Audioendpunkt aufweisen. Dies wird nicht unterstützt.
Beispiel für die Verwendung des neuen Eigenschaftenspeichers:
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}"
Benachrichtigungsframework
Das Benachrichtigungsframework ermöglicht es Audioeffekten (APOs), Benachrichtigungen für die Verarbeitung von Lautstärke-, Endpunkt- und Audioeffekt-Eigenschaftsspeicheränderungen anzufordern und zu verarbeiten. Dieses Framework soll vorhandene APIs ersetzen, die von APOs zum Registrieren und Aufheben der Registrierung für Benachrichtigungen verwendet werden.
Die neue API führt eine Schnittstelle ein, die APOs verwenden können, um den Typ der Benachrichtigungen zu deklarieren, an denen das APO interessiert ist. Windows fragt das APO nach den interessierten Benachrichtigungen ab und leitet die Benachrichtigung an die APOs weiter. APOs müssen die Registrierungs- oder Aufhebungs-APIs nicht mehr explizit aufrufen.
Benachrichtigungen werden mithilfe einer seriellen Warteschlange an ein APO übermittelt. Gegebenenfalls sendet die erste Benachrichtigung den Anfangsstatus des angeforderten Werts (z. B. die Lautstärke des Audioendpunkts). Benachrichtigungen werden gestoppt, sobald audiodg.exe nicht mehr beabsichtigt, einen APO für das Streaming zu verwenden. APOs empfangen nach UnlockForProcess keine Benachrichtigungen mehr. Es ist weiterhin erforderlich, UnlockForProcess und alle aktiven Benachrichtigungen zu synchronisieren.
Implementierung – Benachrichtigungsframework
Um das Benachrichtigungsframework zu nutzen, deklariert ein APO, an welchen Benachrichtigungen es interessiert ist. Es gibt keine expliziten Registrierungs-/Aufhebungsaufrufe. Alle Benachrichtigungen an den APO werden serialisiert und es ist wichtig, den Benachrichtigungs-Rückrufthread nicht zu lange zu blockieren.
API-Definition – Benachrichtigungsframework
Das Benachrichtigungsframework implementiert eine neue IAudioProcessingObjectNotifications-Schnittstelle, die von Clients implementiert werden kann, um allgemeine audiobezogene Benachrichtigungen für APO-Endpunkt- und Systemeffektbenachrichtigungen zu registrieren und zu empfangen.
Weitere Informationen finden Sie auf den folgenden Seiten:
Beispielcode – Benachrichtigungsframework
Das Beispiel veranschaulicht, wie ein APO die IAudioProcessingObjectNotifications-Schnittstelle implementieren kann. In der GetApoNotificationRegistrationInfo-Methode registriert das Beispiel-APO Benachrichtigungen bei Änderungen an den Systemeffekteigenschaftenspeichern.
Die HandleNotification-Methode wird vom Betriebssystem aufgerufen, um das APO über Änderungen zu benachrichtigen, die mit dem APO übereinstimmen, für das die APO registriert wurde.
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;
}
}
Der folgende Code stammt aus dem Swap APO MFX-Beispiel – swapapomfx.cpp und zeigt die Registrierung für Ereignisse an, indem ein Array von APO_NOTIFICATION_DESCRIPTORs zurückgegeben wird.
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;
}
Der folgende Code stammt aus dem SwapAPO MFX HandleNotifications-Beispiel – swapapomfx.cpp und zeigt, wie Benachrichtigungen behandelt werden.
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);
}
}
}
}
Protokollierungsframework
Das Protokollierungsframework bietet APO-Entwicklern zusätzliche Möglichkeiten, Daten zu sammeln, um die Entwicklung und das Debuggen zu verbessern. Dieses Framework vereint die unterschiedlichen Methoden der Protokollierung, die von verschiedenen Anbietern verwendet werden, und verknüpft sie mit den Anbietern von Audioablaufverfolgungsprotokollierungen, um eine aussagekräftigere Protokollierung zu erstellen. Das neue Framework stellt eine Protokollierungs-API bereit, sodass der Rest der Arbeit vom Betriebssystem erledigt werden muss.
Der Anbieter wird folgendermaßen definiert:
IMPLEMENT_TRACELOGGING_CLASS(ApoTelemetryProvider, "Microsoft.Windows.Audio.ApoTrace",
// {8b4a0b51-5dcf-5a9c-2817-95d0ec876a87}
(0x8b4a0b51, 0x5dcf, 0x5a9c, 0x28, 0x17, 0x95, 0xd0, 0xec, 0x87, 0x6a, 0x87));
Jedes APO hat seine eigene Aktivitäts-ID. Da dabei der vorhandene Trace-Protokollierungsmechanismus verwendet wird, können vorhandene Konsolentools verwendet werden, um nach diesen Ereignissen zu filtern und sie in Echtzeit anzuzeigen. Sie können vorhandene Tools wie Tracelog und tracefmt verwenden, wie unter Tools für Softwareverfolgung – Windows-Treiber beschrieben. Weitere Informationen zu Ablaufverfolgungssitzungen finden Sie unter Erstellen einer Ablaufverfolgungssitzung mit einer Steuerelement-GUID.
Die Ereignisse der Ablaufverfolgungsprotokollierung sind nicht als Telemetrie gekennzeichnet und werden nicht als Telemetrieanbieter in Tools wie xperf angezeigt.
Implementierung – Protokollierungsframework
Das Protokollierungsframework basiert auf den Protokollierungsmechanismen, die von der ETW-Ablaufverfolgung bereitgestellt werden. Weitere Informationen zu ETW finden Sie unter Ereignisablaufverfolgung. Dies ist nicht für die Protokollierung von Audiodaten gedacht, sondern zum Protokollieren von Ereignissen, die in der Regel in der Produktion protokolliert werden. Protokollierungs-APIs sollten nicht vom Echtzeit-Streaming-Thread verwendet werden, da diese möglicherweise dazu führen können, dass der Pump-Thread vom CPU-Scheduler des Betriebssystems vorbelegt wird. Die Protokollierung sollte in erster Linie für Ereignisse verwendet werden, die beim Debuggen von Problemen helfen, die häufig im Feld zu finden sind.
API-Definition – Protokollierungsframework
Das Protokollierungsframework führt die IAudioProcessingObjectLoggingService-Schnittstelle ein, die einen neuen Protokollierungsdienst für APOs bereitstellt.
Weitere Informationen finden Sie unter IAudioProcessingObjectLoggingService.
Beispielcode – Protokollierungsframework
Das Beispiel veranschaulicht die Verwendung der Methode IAudioProcessingObjectLoggingService::ApoLog und wie dieser Schnittstellenzeiger in IAudioProcessingObject::Initialize abgerufen wird.
AecApoMfx-Protokollierungsbeispiel.
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;
}
Threading-Framework
Das Threading-Framework ermöglicht das Multithreading von Effekten durch die Verwendung von Arbeitswarteschlangen aus einer entsprechenden MMCSS-Aufgabe (Multimedia Class Scheduler Service) über eine einfache API. Die Erstellung serieller Echtzeit-Arbeitswarteschlangen und deren Zuordnung zum Hauptpump-Thread werden vom Betriebssystem übernommen. Dieses Framework ermöglicht es APOs, Arbeitselemente mit kurzer Laufzeit in die Warteschlange zu stellen. Die Synchronisierung zwischen Vorgängen obliegt weiterhin dem APO. Weitere Informationen zu MMCSS-Threading finden Sie unter Multimedia Class Scheduler Service und Echtzeitarbeitswarteschlangen-API.
API-Definitionen – Threading-Framework
Das Threading-Framework führt die IAudioProcessingObjectQueueService-Schnittstelle ein, die Zugriff auf die Echtzeitarbeitswarteschlange für APOs ermöglicht.
Weitere Informationen finden Sie auf den folgenden Seiten:
Beispielcode – Threading-Framework
Dieses Beispiel veranschaulicht die Verwendung der Methode IAudioProcessingObjectRTQueueService::GetRealTimeWorkQueue und wie der IAudioProcessingObjectRTQueueService-Schnittstellenzeiger in IAudioProcessingObject::Initialize abgerufen wird.
#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;
}
Weitere Beispiele für die Verwendung dieser Schnittstelle finden Sie im folgenden Beispielcode:
- SwapAPO SwapMFXApoAsyncCallback-Klassendefinition – Beispiel
- SwapAPO Invoke-Funktion – Beispiel
- SwapAPO Create Async Callback – Beispiel
Erkennung und Steuerung von Audioeffekten für Effekte
Das Ermittlungsframework ermöglicht es dem Betriebssystem, Audioeffekte in ihrem Stream zu steuern. Diese APIs bieten Unterstützung für Szenarien, in denen der Benutzer einer Anwendung bestimmte Auswirkungen auf Streams steuern muss (z. B. starke Rauschunterdrückung). Um dies zu erreichen, fügt dieses Framework Folgendes hinzu:
- Eine neue API, die von einem APO abgefragt werden soll, um festzustellen, ob ein Audioeffekt aktiviert oder deaktiviert werden kann.
- Eine neue API zum Festlegen des Status eines Audioeffekts auf „Ein/Aus“.
- Eine Benachrichtigung, wenn es eine Änderung in der Liste der Audioeffekte gibt oder wenn Ressourcen verfügbar werden, sodass ein Audioeffekt jetzt aktiviert/deaktiviert werden kann.
Implementierung – Audioeffektermittlung
Ein APO muss die IAudioSystemEffects3-Schnittstelle implementieren, wenn es Effekte bereitstellen möchte, die dynamisch aktiviert und deaktiviert werden können. Ein APO stellt seine Audioeffekte über die IAudioSystemEffects3::GetControllableSystemEffectsList-Funktion bereit und deaktiviert seine Audioeffekte über die IAudioSystemEffects3::SetAudioSystemEffectState-Funktion.
Beispielcode – Audioeffektermittlung
Den Beispielcode „Audioeffektermittlung“ finden Sie im SwapAPOSFX-Beispiel – swapaposfx.cpp.
Der folgende Beispielcode veranschaulicht, wie die Liste der konfigurierbaren Effekte abgerufen wird. GetControllableSystemEffectsList-Beispiel – 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;
}
Im folgenden Beispielcode wird veranschaulicht, wie Effekte aktiviert und deaktiviert werden. SetAudioSystemEffectState-Beispiel – 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;
}
Wiederverwendung der WM-SFX- und MFX-APOs in Windows 11, Version 22H2
Ab Windows 11, Version 22H2, können die INF-Konfigurationsdateien, die die WM-SFX- und MFX-APOs wiederverwenden, jetzt die CAPX-SFX- und MFX-APOs wiederverwenden. In diesem Abschnitt werden die drei Möglichkeiten hierfür beschrieben.
Es gibt drei Einfügepunkte für APOs: Pre-Mix-Render, Post-Mix-Render und Erfassen. Das Audiomodul jedes logischen Geräts unterstützt eine Instanz eines Pre-Mix-Render-APO pro Stream (Render-SFX) und eines Post-Mix-Render-APO (MFX). Das Audiomodul unterstützt auch eine Instanz eines Erfassungs-APO (Erfassungs-SFX), das in jeden Erfassungsstream eingefügt wird. Weitere Informationen zum Wiederverwenden oder Umschließen der Posteingangs-APOs finden Sie unter Kombinieren von benutzerdefinierten und Windows-APOs.
Die CAPX-SFX- und MFX-APOs können auf eine der folgenden drei Arten wiederverwendet werden.
Verwenden des Abschnitts „INF DDInstall“
Verwenden Sie mssysfx. CopyFilesAndRegisterCapX von wdmaudio.inf durch Hinzufügen der folgenden Einträge.
Include=wdmaudio.inf
Needs=mssysfx.CopyFilesAndRegisterCapX
Verwenden einer Erweiterungs-INF-Datei
Die wdmaudioapo.inf ist die AudioProcessingObject-Klassenerweiterung inf. Sie enthält die gerätespezifische Registrierung der SFX- und MFX-APOs.
Direktes Verweisen auf die WM-SFX- und MFX-APOs für Stream- und Moduseffekte
Verwenden Sie die folgenden GUID-Werte, um direkt auf diese APOs für Stream- und Moduseffekte zu verweisen.
{C9453E73-8C5C-4463-9984-AF8BAB2F5447}
als WM-SFX-APO verwenden{13AB3EBD-137E-4903-9D89-60BE8277FD17}
als WM-MFX-APO verwenden
SFX (Stream) und MFX (Modus) wurden in Windows 8.1 als LFX (lokal) bezeichnet und MFX wurde als GFX (global) bezeichnet. Diese Registrierungseinträge verwenden weiterhin die vorherigen Namen.
Die gerätespezifische Registrierung verwendet HKR anstelle von HKCR.
Der INF-Datei müssen die folgenden Einträge hinzugefügt werden.
HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
HKR,"FX\\0\\%WMALFXGFXAPO_Context%\\User",,,
WMALFXGFXAPO_Context = "{B13412EE-07AF-4C57-B08B-E327F8DB085B}"
Diese INF-Dateieinträge erstellen einen Eigenschaftenspeicher, der von den Windows 11-APIs für die neuen APOs verwendet wird.
PKEY_FX_Association im INF-Beispiel HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
sollte durch HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
ersetzt werden.