Freigeben über


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 ausgeliefert werden.

Windows ermöglicht Es Drittanbietern, Audiohardwarehersteller benutzerdefinierte hostbasierte digitale Signalverarbeitungseffekte einzuschließen. Diese Effekte werden als Benutzermodus-Audioverarbeitungsobjekte (APOs) verpackt. Weitere Informationen finden Sie unter Windows Audio Processing Objects.

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 debugging-Funktionen verbessern.

  • Das AEC-Framework (Acoustic Echo Cancellation) ermöglicht es einem APO, sich als AEC-APO zu identifizieren und Zugriff auf einen Referenzdatenstrom und zusätzliche Steuerelemente zu gewähren.
  • Das Einstellungen Framework ermöglicht APOs, Methoden zum Abfragen und Ändern des Eigenschaftenspeichers für Audioeffekte ("FX-Eigenschaftenspeicher") auf 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 Behandlung von Volume-, Endpunkt- und Audioeffekt-Eigenschaftenspeicheränderungen anzufordern.
  • Das Protokollierungsframework unterstützt die Entwicklung und das Debuggen von APOs.
  • Das Threading-Framework ermöglicht APOs, mithilfe eines vom Betriebssystem verwalteten, MMCSS-registrierten Threadpools multithreaded zu werden.
  • Mit den Audioeffekterkennungs- und Steuerelement-APIs kann das Betriebssystem Effekte erkennen, aktivieren und deaktivieren, die für die Verarbeitung in einem Datenstrom 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 die APO das APO-Einstellungen Framework unterstützt, und ermöglicht es dem APO, allgemeine audiobezogene Benachrichtigungen vom Audiomodul zu abonnieren.

Windows 11 APO CAPX-Entwicklungsanforderungen

Alle neuen APOs, die auf einem Gerät für Windows 11 ausgeliefert werden, müssen mit den in diesem Thema aufgeführten APIs kompatibel sein, die über HLK überprüft werden. Darüber hinaus wird davon ausgegangen, dass alle APOs, die AEC nutzen, die in diesem Thema beschriebene Implementierung folgen, die über HLK überprüft wird. Benutzerdefinierte Implementierungen für diese kernigen Audioverarbeitungserweiterungen (Einstellungen, Protokollierung, Benachrichtigungen, Threading, AEC) werden voraussichtlich CAPX-APIs nutzen. Dies wird durch die Windows 11 HLK-Tests überprüft. Wenn ein APO z. B. Registrierungsdaten verwendet, um Einstellungen zu speichern, anstatt das Einstellungen Framework zu verwenden, schlägt der zugeordnete 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 funktioniert, kann es untersuchen, ob es mit der APOInitSystemEffects2 - oder der APOInitSystemEffects3-Struktur initialisiert wird, um festzustellen, ob es auf einem Betriebssystem ausgeführt wird, das die CAPX-APIs unterstützt.

Die neuesten Versionen von Windows, wdk und das SDK können unten über das Windows-Insider-Programm heruntergeladen werden. Partner, die über das Partner Center mit Microsoft beschäftigt sind, können auch über die Zusammenarbeit auf diese Inhalte zugreifen. Weitere Informationen zur Zusammenarbeit finden Sie in der Einführung in Microsoft Collaborate.

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)

Acoustic Echo Cancellation (AEC) ist ein gängiger Audioeffekt, der von unabhängigen Hardwareanbietern (IHVs) und unabhängigen Softwareanbietern (ISVs) als Audio Processing Object (APO) in der Mikrofonaufnahmepipeline implementiert wird. Dieser Effekt unterscheidet sich von anderen Effekten, die in der Regel von IHVs und ISVs implementiert werden, da es 2 Eingänge erfordert – einen Audiostream vom Mikrofon und einen Audiostream von einem Rendergerät, das als Referenzsignal fungiert.

Diese neue Gruppe von Schnittstellen ermöglicht es einem AEC APO, sich als solche für das Audiomodul 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 einer zusätzlichen Eingabe, die dem APO den Referenzdatenstrom von einem geeigneten Renderendpunkt bereitstellt.
  • Wechseln Sie die Referenzdatenströme, während sich das Rendergerät ändert.
  • Zulassen, dass ein APO das Format des Eingabemikrofons und des Referenzdatenstroms steuert.
  • Zulassen, dass ein APO Zeitstempel im Mikrofon und Referenzdatenströme 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 Referenzdatenstrom abzurufen, kann ein APO entweder mit dem Treiber interagieren, indem proprietäre Schnittstellen verwendet werden, um die Referenzaudio vom Renderendpunkt abzurufen oder WASAPI zum Öffnen eines Loopbackstreams auf dem Renderendpunkt zu verwenden.

Beide oben genannten Ansätze haben Nachteile:

  • Ein AEC-APO, das private Kanäle verwendet, um einen Referenzdatenstrom vom Treiber abzurufen, kann dies in der Regel nur vom integrierten Audiowiedergabegerät aus tun. Daher funktioniert die Echounterdrückung nicht, wenn der Benutzer Audio aus dem nicht integrierten Gerät wie USB oder Bluetooth-Audiogerät abspielt. Nur das Betriebssystem kennt die richtigen Renderendpunkte, die als Referenzendpunkte dienen können.

  • Ein APO kann WASAPI verwenden, um den Standardmäßigen Renderendpunkt für die Echounterdrückung zu wählen. Es gibt jedoch einige Fallstricke, die beim Öffnen eines Loopbackdatenstroms aus dem audiodg.exe-Prozess beachtet werden müssen (wo die APO gehostet wird).

    • Der Loopbackdatenstrom kann nicht geöffnet/zerstört werden, wenn das Audiomodul die Standard APO-Methoden aufruft, da dies zu einem Deadlock führen kann.
    • A capture APO does not know the state of the streams of its clients. d. h. eine Aufnahme-App könnte einen Aufnahmedatenstrom im Zustand "STOP" aufweisen, der APO weiß jedoch nicht von diesem Zustand und hält den Loopbackdatenstrom im Zustand "RUN" geöffnet, was ineffizient im Hinblick auf den Stromverbrauch ist.

API-Definition – AEC

Das AEC-Framework bietet neue Strukturen und Schnittstellen, die APOs nutzen können. Diese neuen Strukturen und Schnittstellen werden unten beschrieben.

APO_CONNECTION_PROPERTY_V2 Struktur

APOs, die die IApoAcousticEchoCancellation-Schnittstelle implementieren, werden eine APO_CONNECTION_PROPERTY_V2 Struktur im Aufruf von IAudioProcessingObjectRT::APOProcess übergeben. 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 zu bestimmen, ob die vom Audiomodul empfangene Struktur 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 typcast auf einen APO_CONNECTION_PROPERTY_V2 Zeiger sein.

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. Der Zweck besteht darin, ein AEC APO für das Audiomodul zu identifizieren. Diese Schnittstelle kann nur durch Moduseffekte (MFX) auf Aufnahmeendpunkten 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 Aufnahmeendpunkt als eine Reihe von verketteten APOs implementiert wird, kann nur das APO, das dem Gerät am nächsten kommt, diese Schnittstelle implementieren. APOs, die diese Schnittstelle implementieren, werden die APO_CONNECTION_PROPERTY_V2 Struktur im Aufruf von IAudioProcessingobjectRT::APOProcess angeboten. Die 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 übertragen.

In Anerkennung der Tatsache, dass AEC-APOs ihre Algorithmen in der Regel mit einer bestimmten Samplingrate/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 im 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 vorgeschlagene Format des APO abzurufen. Das Audiomodul sendet dann Mikrofonaudio erneut in das vorgeschlagene Format, bevor es an das AEC APO gesendet wird. Dadurch ist es nicht erforderlich, dass die AEC APO Samplingrate und Kanalanzahl konvertierung implementiert.

IApoAuxiliaryInputConfiguration

Die IApoAuxiliaryInputConfiguration-Schnittstelle stellt Methoden bereit, die APOs implementieren können, damit das Audiomodul Zusätzliche Eingabedatenströme 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 das AEC APO nur mit einer einzigen Hilfseingabe initialisiert – einem, der den Referenzaudiostrom für die Echounterdrückung enthält. Die AddAuxiliaryInput-Methode wird verwendet, um die Verweiseingabe zum APO hinzuzufügen. Die Initialisierungsparameter enthalten einen Verweis auf den Renderendpunkt, von dem der Loopbackstream abgerufen wird.

Die IsInputFormatSupported-Methode wird vom Audiomodul aufgerufen, um Formate für die Hilfseingabe 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 wird die Referenzaudio in das vorgeschlagene Format umgestaltet und an der Zusätzlichen Eingabe des AEC APO bereitgestellt.

IApoAuxiliaryInputRT

Die IApoAuxiliaryInputRT-Schnittstelle ist die echtzeitsichere Schnittstelle, die zum Steuern der Hilfseingaben eines APO verwendet wird.

Diese Schnittstelle wird verwendet, um Audiodaten für die Hilfseingabe des APO bereitzustellen. Beachten Sie, dass die zusätzlichen Audioeingaben nicht mit den Aufrufen von IAudioProcessingObjectRT::APOProcess synchronisiert werden. Wenn kein Audio gerendert wird, stehen Loopbackdaten bei der Hilfseingabe nicht zur Verfügung. d. h. es werden keine Anrufe an IApoAuxiliaryInputRT::AcceptInput

Zusammenfassung von AEC CAPX-APIs

Weitere Informationen finden Sie auf den folgenden Seiten.

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 die APO nur eine Hilfseingabe 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 und CAecApoMFX::IsInputFormatSupportedCAecApoMFX::AcceptInput die Behandlung der APO_CONNECTION_PROPERTY_V2.

Abfolge von Vorgängen – AEC

Beim Initialisieren:

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

Ändern des Rendergeräts:

  1. IAudioProcessingObject::Initialize
  2. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  3. IAudioProcessingObjectConfiguration::LockForProcess
  4. Standardgeräteänderungen
  5. IAudioProcessingObjectConfiguration::UnlockForProcess
  6. IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
  7. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  8. IAudioProcessingObjectConfiguration::LockForProcess

Dies ist das empfohlene Pufferverhalten für AEC.

  • Puffer, die im Aufruf von IApoAuxiliaryInputRT::AcceptInput abgerufen werden, sollten in einen Kreispuffer geschrieben werden, ohne den Standard Thread zu sperren.
  • Beim Aufruf von IAudioProcessingObjectRT::APOProcess sollte der Kreispuffer für das neueste Audiopaket aus dem Referenzdatenstrom gelesen werden, und dieses Paket sollte für die Ausführung über den Echounterdrückungsalgorithmus verwendet werden.
  • Zeitstempel auf den Referenz- und Mikrofondaten können verwendet werden, um die Lautsprecher- und Mikrofondaten zu richten.

Referenz-Loopbackstream

Standardmäßig "tippt der Loopbackstream auf" (hört) den Audiodatenstrom vor der Anwendung von Lautstärke oder Stummschaltung. Ein Loopbackstream, auf den vor der Anwendung des Volumes getippt wurde, wird als Vorab-Loopbackstream bezeichnet. Ein Vorteil eines Vorab-Loopbackstreams ist ein klarer und einheitlicher Audiodatenstrom, unabhängig von der aktuellen Lautstärkeeinstellung.

Einige AEC-Algorithmen bevorzugen möglicherweise das Abrufen eines Loopbackdatenstroms, der nach jeder Volumeverarbeitung verbunden wurde (einschließlich Stummschaltung). Diese Konfiguration wird als Post-Volume-Loopback bezeichnet.

In der nächsten Hauptversion von Windows AEC-APOs können Post-Volume-Loopback auf 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 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 der Post-Volume-Loopback möglicherweise nicht verfügbar. Ein AEC APO wird benachrichtigt, wenn post-volume loopback verwendet wird, wenn die IApoAuxiliaryInputConfiguration::AddAuxiliaryInput-Methode aufgerufen wird. Wenn das AcousticEchoCanceller_Reference_Input streamProperties-Feld APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK enthält, wird postvolume 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.

Einstellungen Framework

Das Einstellungen Framework ermöglicht APOs, Methoden zum Abfragen und Ändern des Eigenschaftenspeichers für Audioeffekte ("FX Property Store") auf einem Audioendpunkt verfügbar zu machen. Dieses Framework kann von APOs und von Hardwaresupport-Apps (HSA) verwendet werden, die Einstellungen mit diesem APO kommunizieren möchten. HSAs können Universelle Windows-Plattform (UWP)-Apps sein und erfordern eine spezielle Funktion zum Aufrufen der APIs im Einstellungen Framework. Weitere Informationen zu HSA-Apps finden Sie unter UWP-Geräte-Apps.

FxProperty Store-Struktur

Der neue FxProperty-Speicher verfügt über drei Unterspeicher: "Default", "User" und "Volatile".

Der Unterschlüssel "Standard" enthält benutzerdefinierte Effekteigenschaften und wird aus der INF-Datei aufgefüllt. Diese Eigenschaften werden nicht über Betriebssystemupgrades hinweg beibehalten. Eigenschaften, die normalerweise in einem INF definiert sind, würden hier passen. Diese werden dann aus dem INF erneut aufgefüllt.

Der Unterschlüssel "Benutzer" enthält Benutzereinstellungen, die sich auf Effekteigenschaften beziehen. Diese Einstellungen werden vom Betriebssystem über Upgrades und Migrationen hinweg beibehalten. Beispielsweise können alle Voreinstellungen, die der Benutzer konfigurieren kann, die für das Upgrade beibehalten werden sollen.

Der Unterschlüssel "Veränderlich" enthält veränderliche Effekteigenschaften. Diese Eigenschaften gehen beim Neustart des Geräts verloren und werden jedes Mal gelöscht, wenn der Endpunkt zu aktiv wechselt. Diese werden voraussichtlich Zeitvarianteneigenschaften enthalten (z. B. basierend auf aktuellen ausgeführten Anwendungen, Gerätestatus usw.) Beispielsweise alle Einstellungen, die von der aktuellen Umgebung abhängig sind.

Die Möglichkeit, den Benutzer im Vergleich zum Standard zu berücksichtigen, besteht darin, ob die Eigenschaften über Betriebssystem- und Treiberupgrades hinweg beibehalten werden sollen. Benutzereigenschaften werden beibehalten. Standardeigenschaften werden aus dem INF erneut aufgefüllt.

APO-Kontexte

Die CAPX-Einstellungsrahmen 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. Anbieter können jedoch kontextbezogene Kontexte erstellen, unabhängig davon, ob es sich um SFX/MFX/EFX oder um einen Modus handelt. Ein Anbieter könnte auch einen einzigen Kontext für alle APOs haben, die von diesem Anbieter ausgeliefert werden.

Einstellungen Eingeschränkte Funktion

Die Einstellungs-API soll alle OEMs und HSA-Entwickler unterstützen, die an der Abfrage und Änderung der Audioeffekteeinstellungen interessiert sind, die einem Audiogerät zugeordnet sind. Diese API wird für eine HSA- und Win32-Anwendung verfügbar gemacht, um zugriff auf den Eigenschaftenspeicher über die eingeschränkte Funktion "audioDeviceConfiguration" bereitzustellen, die im Manifest deklariert werden muss. 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>

Der 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 fungieren, um Nachrichten an einen Dienst oder eine UWP-Speicheranwendung zurückzustellen.

Hinweis

Dies ist eine eingeschränkte Funktion: Wenn eine Anwendung mit dieser Funktion an den Microsoft Store übermittelt wird, löst sie eine enge Prüfung aus. Die App muss eine Hardwareunterstützungs-App (Hardware Support App, HSA) sein, und sie wird untersucht, um zu bewerten, dass es sich tatsächlich um eine HSA handelt, bevor die Übermittlung genehmigt wird.

API-Definition – Einstellungen Framework

Die neue IAudioSystemEffectsPropertyStore-Schnittstelle ermöglicht es einem HSA, auf Eigenschaftenspeicher für Audiosystemeffekte zuzugreifen und für Eigenschaftsänderungsbenachrichtigungen zu registrieren.

Die ActiveAudioInterfaceAsync-Funktion stellt eine Methode zum asynchronen Abrufen der IAudioSystemEffectsPropertyStore-Schnittstelle bereit.

Eine App kann Benachrichtigungen empfangen, wenn sich der Systemeffekteigenschaftenspeicher ändert, mithilfe der neuen IAudioSystemEffectsPropertyChangeNotificationClient-Rückrufschnittstelle .

Anwendung, die versucht, den IAudioSystemEffectsPropertyStore mit IMMDevice::Activate abzurufen

Das Beispiel veranschaulicht, wie eine Hardwaresupport-App IMMDevice::Activate zum Aktivieren von IAudioSystemEffectsPropertyStore verwenden kann. Das Beispiel zeigt, wie Sie IAudioSystemEffectsPropertyStore verwenden, um einen IPropertyStore mit Benutzereinstellungen zu ö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 veranschaulicht die Implementierung eines APO kann die APOInitSystemEffects3-Struktur verwenden, um die Benutzer-, Standard- und veränderlichen 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 dentified 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 – Einstellungen Framework

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

ABSCHNITT INF - Einstellungen Framework

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

Notifications Framework

Das Benachrichtigungsframework ermöglicht es Audioeffekten (APOs), Änderungsbenachrichtigungen für Lautstärke, Endpunkt und Audioeffekte 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 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 Registrierungs-APIs nicht mehr explizit aufrufen.

Benachrichtigungen werden mithilfe einer seriellen Warteschlange an ein APO übermittelt. Wenn zutreffend, sendet die erste Benachrichtigung den Anfangszustand des angeforderten Werts (z. B. das Audioendpunktvolume). Benachrichtigungen werden beendet, sobald audiodg.exe die Absicht beenden, ein APO zum Streaming zu verwenden. APOs empfangen nach UnlockForProcess keine Benachrichtigungen mehr. Es ist weiterhin erforderlich, UnlockForProcess und alle In-Flight-Benachrichtigungen zu synchronisieren.

Implementierung – Notifications Framework

Um das Benachrichtigungsframework zu nutzen, deklariert ein APO, an welchen Benachrichtigungen es interessiert ist. Es gibt keine expliziten Registrierungs-/Registrierungsaufrufe. Alle Benachrichtigungen an das APO werden serialisiert, und es ist wichtig, den Benachrichtigungsrückrufthread nicht zu lange zu blockieren.

API-Definition – Notifications Framework

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 – Notifications Framework

Das Beispiel veranschaulicht, wie ein APO die IAudioProcessingObjectNotifications-Schnittstelle implementieren kann. In der GetApoNotificationRegistrationInfo-Methode registriert das APO-Beispiel 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 dentified 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 a 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 ficticious
        // 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 a 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 Audioablaufverfolgungsprotokollierungsanbietern, um eine aussagekräftigere Protokollierung zu erstellen. Das neue Framework stellt eine Protokollierungs-API bereit, sodass die Erneute Standard der der Vom Betriebssystem zu erledigenden Arbeit übrig bleibt.

Der Anbieter wird wie folgt 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 verfügt über eine eigene Aktivitäts-ID. Da dadurch der vorhandene Ablaufverfolgungsprotokollierungsmechanismus verwendet wird, können vorhandene Konsolentools verwendet werden, um nach diesen Ereignissen zu filtern und in Echtzeit anzuzeigen. Sie können vorhandene Tools wie Tracelog und tracefmt verwenden, wie unter Tools for Software Tracing – Windows Drivers beschrieben. Weitere Informationen zu Ablaufverfolgungssitzungen finden Sie unter Erstellen einer Ablaufverfolgungssitzung mit einer Steuerelement-GUID.

Die Ablaufverfolgungsprotokollierungsereignisse 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 aus dem Echtzeitstreamingthread verwendet werden, da diese das Potenzial haben, dass der Pumpthread vom Betriebssystem-CPU-Scheduler vorab ausgeführt 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 Threadingframework, mit dem Effekte mithilfe von Arbeitswarteschlangen aus einer entsprechenden MMCSS-Aufgabe (Multimedia Class Scheduler Service) mithilfe einer einfachen API multithreadiert werden können. Die Erstellung von seriellen Arbeitswarteschlangen in Echtzeit und deren Zuordnung zum Standard Pumpthread werden vom Betriebssystem behandelt. Dieses Framework ermöglicht APOs, kurz ausgeführte Arbeitsaufgaben in die Warteschlange zu stellen. Die Synchronisierung zwischen Vorgängen obliegt weiterhin der 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:

Erkennung und Steuerung von Audioeffekten für Effekte

Das Ermittlungsframework ermöglicht es dem Betriebssystem, Audioeffekte in ihrem Datenstrom zu steuern. Diese APIs bieten Unterstützung für Szenarien, in denen der Benutzer einer Anwendung bestimmte Auswirkungen auf Datenströme steuern muss (z. B. Tiefenrauschunterdrü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 eine Änderung in der Liste der Audioeffekte vorliegt oder ressourcen verfügbar werden, damit ein Audioeffekt jetzt aktiviert/deaktiviert werden kann.

Implementierung – Audioeffekte-Ermittlung

Ein APO muss die IAudioSystemEffects3-Schnittstelle implementieren, wenn sie Effekte verfügbar machen möchte, die dynamisch aktiviert und deaktiviert werden können. Ein APO macht seine Audioeffekte über die Funktion "IAudioSystemEffects3::GetControllableSystemEffectsList " verfügbar und aktiviert und deaktiviert seine Audioeffekte über die Funktion "IAudioSystemEffects3::SetAudioSystemEffectState ".

Beispielcode – Audioeffektermittlung

Der Beispielcode "Audio Effect Discovery" 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 beschrieben, um dies zu tun.

Es gibt drei Einfügepunkte für APOs: Pre-Mix-Rendern, Postmix-Rendern und Erfassen. Das Audiomodul jedes logischen Geräts unterstützt eine Instanz eines Vorabmix-Render-APO pro Datenstrom (Render-SFX) und eines Postmix-Render-APO (MFX). Das Audiomodul unterstützt auch eine Instanz eines Aufnahme-APO (Capture SFX), das in jeden Aufnahmedatenstrom 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

Wdmaudioapo.inf ist die Erweiterung der AudioProcessingObject-Klasse 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.

  • Als WM SFX APO verwenden {C9453E73-8C5C-4463-9984-AF8BAB2F5447}
  • Wird als WM MFX APO verwendet {13AB3EBD-137E-4903-9D89-60BE8277FD17} .

SFX (Stream) und MFX (Mode) wurden in Windows 8.1 auf LFX (lokal) und MFX (global) bezeichnet. Diese Registrierungseinträge verwenden weiterhin die vorherigen Namen.

Gerätespezifische Registrierung verwendet HKR anstelle von HKCR.

Die INF-Datei muss die folgenden Einträge hinzugefügt haben.

  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 ex. HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%, sollte durch ersetzt werden.HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%

Weitere Informationen

Windows-Audioverarbeitungsobjekte.