API Windows 11 для объектов обработки звука

В этом разделе представлен набор новых API-интерфейсов Windows 11 для объектов обработки звука (APOs), поставляемых с аудиодрайвом.

Windows позволяет сторонним производителям звукового оборудования включать пользовательские эффекты обработки цифровых сигналов на основе узла. Эти эффекты упаковываются как объекты обработки звука в пользовательском режиме (APOs). Дополнительные сведения см. в разделе Объекты обработки звука Windows.

Некоторые api, описанные здесь, позволяют использовать новые сценарии для независимых поставщиков оборудования (IHV) и независимых поставщиков программного обеспечения (ISV), в то время как другие API предоставляют альтернативы, повышающие общую надежность звука и возможности отладки.

  • Платформа акустического эхо-подавления (AEC) позволяет APO идентифицировать себя как AEC APO, предоставляя доступ к потоку ссылок и дополнительным элементам управления.
  • Платформа параметров позволит API предоставлять методы для запроса и изменения хранилища свойств для звуковых эффектов ("хранилище свойств FX") в конечной точке звука. Если эти методы реализуются APO, они могут вызываться приложениями поддержки оборудования (HSA), связанными с этим APO.
  • Платформа уведомлений позволяет звуковым эффектам запрашивать уведомления для обработки изменений в хранилище томов, конечных точек и звуковых эффектов.
  • Платформа ведения журнала помогает при разработке и отладке API.
  • Платформа потоков позволяет многопоточности api-интерфейсов с помощью пула потоков, управляемого ОС, зарегистрированного в MMCSS.
  • API обнаружения и управления звуковыми эффектами позволяют ОС обнаруживать, включать и отключать эффекты, доступные для обработки в потоке.

Для использования этих новых API предполагается, что API будут использовать новый интерфейс IAudioSystemEffects3 . Когда APO реализует этот интерфейс, ОС интерпретирует это как неявный сигнал о том, что APO поддерживает платформу параметров APO и позволяет APO подписаться на распространенные уведомления, связанные со звуком, от обработчика звука.

Windows 11 требования к разработке APO CAPX

Все новые API, которые поставляются на устройстве для Windows 11, должны соответствовать API, перечисленным в этом разделе, и проверяться с помощью HLK. Кроме того, ожидается, что все APOs, которые используют AEC, будут следовать реализации, описанной в этом разделе, проверенной с помощью HLK. Пользовательские реализации для этих основных модулей обработки звука (параметры, ведение журнала, уведомления, потоки, AEC) должны использовать API CAPX. Это будет проверено с помощью Windows 11 тестов HLK. Например, если APO использует данные реестра для сохранения параметров вместо использования платформы параметров, соответствующий тест HLK завершится ошибкой.

Требования к версии Windows

API, описанные в этом разделе, доступны начиная с сборки 22000 Windows 11 ОС, WDK и пакета SDK. Windows 10 не будет поддерживать эти API. Если APO планирует работать как в Windows 10, так и в Windows 11, он может проверить, инициализируется ли он с помощью структуры APOInitSystemEffects2 или APOInitSystemEffects3, чтобы определить, работает ли он в ОС, поддерживающей API CAPX.

Последние версии Windows, WDK и пакета SDK можно скачать ниже в рамках программы предварительной оценки Windows. Партнеры, которые взаимодействуют с корпорацией Майкрософт через Центр партнеров, также могут получить доступ к этому содержимому через совместную работу. Дополнительные сведения о совместной работе см. в статье Общие сведения о совместной работе Майкрософт.

Windows 11 содержимое WHCP было обновлено, чтобы предоставить партнерам средства для проверки этих API.

Пример кода для содержимого, описанного в этом разделе, можно найти здесь: Audio/SYSVAD/APO — GitHub

Подавление акустического эха (AEC)

Акустическая эхо-подавление (AEC) — это распространенный звуковой эффект, реализованный независимыми поставщиками оборудования (IHV) и независимыми поставщиками программного обеспечения (ISV) в качестве объекта обработки звука (APO) в конвейере захвата микрофона. Этот эффект отличается от других эффектов, обычно реализуемых IHV и независимыми поставщиками программного обеспечения, в том, что для него требуется 2 входа: звуковой поток с микрофона и аудиопоток с устройства отрисовки, который выступает в качестве эталонного сигнала.

Этот новый набор интерфейсов позволяет AEC APO идентифицировать себя как такового в звуковом движке. Это приводит к тому, что звуковой модуль соответствующим образом настроит APO с несколькими входными данными и одним выходом.

Когда APO реализует новые интерфейсы AEC, звуковой модуль:

  • Настройте APO с дополнительными входными данными, которые предоставляют APO с потоком ссылок из соответствующей конечной точки отрисовки.
  • Переключение ссылочных потоков по мере изменения устройства отрисовки.
  • Разрешить APO управлять форматом входного микрофона и ссылочного потока.
  • Разрешите APO получать метки времени на микрофоне и ссылочные потоки.

Предыдущий подход — Windows 10

APOs — это объекты с одним вводом— одно выходными объектами. Звуковой модуль предоставляет AEC APO звук из конечной точки микрофона на входе. Чтобы получить ссылочный поток, APO может взаимодействовать с драйвером, используя собственные интерфейсы для получения эталонного звука из конечной точки отрисовки, или использовать WASAPI для открытия потока замыкания на себя в конечной точке отрисовки.

Оба указанных выше подхода имеют недостатки:

  • AEC APO, использующий частные каналы для получения ссылочного потока от драйвера, обычно может сделать это только из встроенного устройства отрисовки звука. В результате эхо-подавление не будет работать, если пользователь воспроизводит звук из не интегрированного устройства, такого как USB или Bluetooth аудиоустройства. Только ОС знает о правильных конечных точках отрисовки, которые могут служить эталонными конечными точками.

  • APO может использовать WASAPI, чтобы выбрать конечную точку отрисовки по умолчанию для выполнения подавления эха. Однако при открытии потока замыкания на себя из процесса audiodg.exe (в котором размещается APO), необходимо учитывать некоторые ошибки.

    • Поток замыкания на себя нельзя открыть или уничтожить, когда звуковой модуль вызывает методы APO main, так как это может привести к взаимоблокировке.
    • APO отслеживания не знает состояние потоков своих клиентов. Т. е. приложение записи может иметь поток захвата в состоянии STOP, однако APO не знает об этом состоянии и, следовательно, сохраняет поток замыкания на себя открытым в состоянии RUN, что неэффективно с точки зрения энергопотребления.

Определение API — AEC

Платформа AEC предоставляет новые структуры и интерфейсы, которые могут использовать APOs. Эти новые структуры и интерфейсы описаны ниже.

структура APO_CONNECTION_PROPERTY_V2

ApOs, реализующие интерфейс IApoAcousticEchoCancellation , будут переданы APO_CONNECTION_PROPERTY_V2 структуры в вызове IAudioProcessingObjectRT::APOProcess. Помимо всех полей в структуре APO_CONNECTION_PROPERTY , версия 2 структуры также предоставляет сведения о метках времени для звуковых буферов.

APO может проверить поле APO_CONNECTION_PROPERTY.u32Signature, чтобы определить, имеет ли структура, получаемая от звукового модуля, тип APO_CONNECTION_PROPERTY или APO_CONNECTION_PROPERTY_V2. APO_CONNECTION_PROPERTY структуры имеют сигнатуру APO_CONNECTION_PROPERTY_SIGNATURE, а APO_CONNECTION_PROPERTY_V2 — APO_CONNECTION_PROPERTY_V2_SIGNATURE. Если подпись имеет значение, равное APO_CONNECTION_PROPERTY_V2_SIGNATURE, указатель на структуру APO_CONNECTION_PROPERTY можно безопасно вводить в указатель APO_CONNECTION_PROPERTY_V2.

Следующий код из примера AEC APO MFX — AecApoMfx.cpp и показывает переадресирование.

    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

Интерфейс IApoAcousticEchoCancellation не содержит явных методов. Его целью является идентификация AEC APO в звуковом движке. Этот интерфейс может быть реализован только с помощью эффектов режима (MFX) на конечных точках отслеживания. Реализация этого интерфейса в любом другом APO приведет к сбою при загрузке этого APO. Общие сведения о MFX см. в разделе Архитектура объектов обработки звука.

Если влияние режима на конечную точку захвата реализовано в виде ряда связанных APOs, только APO, ближайший к устройству, может реализовать этот интерфейс. Api, реализующие этот интерфейс, будут предлагать структуру APO_CONNECTION_PROPERTY_V2 в вызове IAudioProcessingobjectRT::APOProcess. APO может проверка для сигнатуры APO_CONNECTION_PROPERTY_V2_SIGNATURE в свойстве подключения и ввести входящую структуру APO_CONNECTION_PROPERTY в структуру APO_CONNECTION_PROPERTY_V2.

В признание того факта, что AEC APOs обычно выполняют свои алгоритмы с определенной частотой выборки или количеством каналов, звуковой модуль обеспечивает поддержку повторной выборки для APOs, реализующих интерфейс IApoAcousticEchoCancellation.

Когда AEC APO возвращает APOERR_FORMAT_NOT_SUPPORTED в вызове IAudioProcessingObject::OutInputFormatSupported, звуковой модуль снова вызывает IAudioProcessingObject::IsInputFormatSupported в APO с форматом вывода NULL и форматом ввода, отличным от NULL, чтобы получить предлагаемый формат APO. Затем звуковой модуль повторно выполнит повторную выборку звука микрофона в предложенный формат перед его отправкой в AEC APO. Это устраняет необходимость в AEC APO реализовать преобразование частоты выборки и количества каналов.

IApoAuxiliaryInputConfiguration

Интерфейс IApoAuxiliaryInputConfiguration предоставляет методы, которые могут реализовать API, чтобы звуковой модуль может добавлять и удалять вспомогательные входные потоки.

Этот интерфейс реализуется AEC APO и используется обработчиком звука для инициализации ссылочных входных данных. В Windows 11 AEC APO будет инициализирован только с одним вспомогательным входом, который содержит эталонный звуковой поток для подавления эха. Метод AddAuxiliaryInput будет использоваться для добавления ссылочных входных данных в APO. Параметры инициализации будут содержать ссылку на конечную точку отрисовки, из которую получен поток замыкания на себя.

Метод IsInputFormatSupported вызывается обработчиком звука для согласования форматов вспомогательных входных данных. Если AEC APO предпочитает определенный формат, он может вернуть S_FALSE в вызове IsInputFormatSupported и указать предлагаемый формат. Звуковой модуль повторно переимпулировал эталонный звук в предлагаемый формат и предоставит его на вспомогательных входных данных AEC APO.

IApoAuxiliaryInputRT

Интерфейс IApoAuxiliaryInputRT — это безопасный в реальном времени интерфейс, используемый для управления вспомогательными входными данными APO.

Этот интерфейс используется для предоставления звуковых данных на вспомогательных входных данных в APO. Обратите внимание, что вспомогательные аудиовходы не синхронизируются с вызовами IAudioProcessingObjectRT::APOProcess. Если в конечной точке отрисовки отсутствует звук, данные замыкания на цикл не будут доступны на вспомогательном входе. т. е. вызовы IApoAuxiliaryInputRT::AcceptInput не будут

Сводка api CAPX AEC

Дополнительные сведения см. на следующих страницах.

Пример кода — AEC

Ознакомьтесь со следующими примерами кода Sysvad Audio AecApo.

В приведенном ниже коде из примера APO aec APO— AecAPO.h показаны три новых открытых метода, добавляемых.

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

Следующий код из примера AEC APO MFX — AecApoMfx.cpp и показывает реализацию AddAuxiliaryInput, когда APO может обрабатывать только один вспомогательный вход.

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;

Кроме того, просмотрите пример кода, демонстрирующий реализацию CAecApoMFX::IsInputFormatSupported и , CAecApoMFX::AcceptInput а также обработку APO_CONNECTION_PROPERTY_V2.

Последовательность операций — AEC

При инициализации:

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

При изменении устройства отрисовки:

  1. IAudioProcessingObject::Initialize
  2. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  3. IAudioProcessingObjectConfiguration::LockForProcess
  4. Изменения устройств по умолчанию
  5. IAudioProcessingObjectConfiguration::UnlockForProcess
  6. IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
  7. IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
  8. IAudioProcessingObjectConfiguration::LockForProcess

Это рекомендуемое поведение буфера для AEC.

  • Буферы, полученные при вызове IApoAuxiliaryInputRT::AcceptInput, должны быть записаны в циклический буфер без блокировки потока main.
  • При вызове IAudioProcessingObjectRT::APOProcess циклический буфер должен считываться для последнего звукового пакета из ссылочного потока, и этот пакет следует использовать для выполнения алгоритма эхо-отмены.
  • Метки времени на эталонных данных и данных микрофона можно использовать для выстраивать динамик и микрофон.

Поток замыкания на себя ссылки

По умолчанию поток замыкается на себя (прослушивает) аудиопоток перед применением громкости или отключения звука. Поток замыкания на себя перед применением тома называется потоком замыкания на себя перед томом. Преимуществом предварительного потока замыкания на себя является четкий и однородный аудиопоток независимо от текущего параметра громкости.

Некоторые алгоритмы AEC могут предпочесть получение потока замыкания на себя, подключенного после обработки любого тома (включая отключение звука). Эта конфигурация называется замыкания на себя после тома.

В следующей основной версии Windows AEC API могут запрашивать замыкания на себя цикл после тома на поддерживаемых конечных точках.

Ограничения

В отличие от потоков замыкания на себя перед тем, которые доступны для всех конечных точек отрисовки, потоки замыкания на цикл после тома могут быть недоступны на всех конечных точках.

Запрос замыкания на цикл после тома

AEC APOs, которые хотят использовать замыкание на себя после тома, должны реализовывать интерфейс IApoAcousticEchoCancellation2 .

APO AEC может запрашивать замыкание на себя после тома, возвращая флаг APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK с помощью параметра Properties в реализации IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties.

В зависимости от используемой в настоящее время конечной точки отрисовки замыкания на себя после тома может быть недоступно. APO AEC получает уведомление, если используется замыкание на себя после тома при вызове метода IApoAuxiliaryInputConfiguration::AddAuxiliaryInput . Если поле AcousticEchoCanceller_Reference_Input streamProperties содержит APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK, используется замыкания на себя цикл после тома.

В следующем коде из примера APO AEC в заголовке AecAPO.h показаны три новых добавляемых открытых метода.

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;

Следующий фрагмент кода из примера AEC APO MFX — AecApoMfx.cpp и показывает реализацию GetDesiredReferenceStreamProperties и соответствующую часть 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.

Платформа параметров

Платформа параметров позволяет API предоставлять методы для запроса и изменения хранилища свойств для звуковых эффектов ("хранилище свойств FX") в конечной точке звука. Эту платформу могут использовать apos и приложения аппаратной поддержки (HSA), которые хотят передавать параметры в этот APO. HSA могут быть универсальная платформа Windows приложениями (UWP) и требуют специальной возможности для вызова API в Settings Framework. Дополнительные сведения о приложениях HSA см. в разделе Приложения для устройств UWP.

Структура хранилища FxProperty

Новое хранилище FxProperty имеет три подхрания: Default, User и Volatile.

Подраздел Default содержит свойства настраиваемых эффектов и заполняется из INF-файла. Эти свойства не сохраняются при обновлении ОС. Например, здесь будут соответствовать свойства, которые обычно определяются в INF. Затем они будут повторно заполнены из INF.

Подраздел "Пользователь" содержит параметры пользователя, относящиеся к свойствам эффектов. Эти параметры сохраняются операционной системой во время обновлений и миграций. Например, все предустановки, которые пользователь может настроить, которые должны сохраняться во время обновления.

Подраздел Volatile содержит свойства переменных эффектов. Эти свойства теряются при перезагрузке устройства и очищаются при каждом переходе конечной точки в активную. Они должны содержать свойства вариантов времени (например, на основе текущих запущенных приложений, состояния устройства и т. д.). Например, любые параметры, зависящие от текущей среды.

Чтобы подумать о пользователе и по умолчанию, нужно, чтобы свойства сохранялись в обновлениях ОС и драйверов. Свойства пользователя будут сохранены. Свойства по умолчанию будут повторно заполнены из INF.

Контексты APO

Настройка параметров CAPX позволяет автору APO группировать свойства APO по контекстам. Каждый объект APO может определять собственный контекст и обновлять свойства относительно собственного контекста. Хранилище свойств эффектов для конечной точки звука может иметь ноль или несколько контекстов. Поставщики могут создавать контексты по их выбору, будь то SFX,MFX/EFX или режим. Поставщик также может выбрать единый контекст для всех apos, поставляемых этим поставщиком.

Ограниченная возможность параметров

API параметров предназначен для поддержки всех изготовителей оборудования и разработчиков HSA, заинтересованных в запросе и изменении параметров звуковых эффектов, связанных со звуковым устройством. Этот API предоставляется приложениям HSA и Win32 для предоставления доступа к хранилищу свойств с помощью ограниченной возможности audioDeviceConfiguration, которая должна быть объявлена в манифесте. Кроме того, соответствующее пространство имен должно быть объявлено следующим образом:

<Package
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  IgnorableNamespaces="uap mp rescap">
  ...
 
  <Capabilities>
    <rescap:Capability Name="audioDeviceConfiguration" />
  </Capabilities>
</Package>

IAudioSystemEffectsPropertyStore доступен для чтения и записи с помощью службы ISV/IHV, приложения магазина UWP, классических приложений без прав администратора и API. Кроме того, это может выступать в качестве механизма для API для доставки сообщений обратно в службу или приложение хранилища UWP.

Примечание

Это ограниченная возможность. Если приложение отправляется с этой возможностью в Microsoft Store, это приведет к тщательному анализу. Приложение должно быть приложением поддержки оборудования (HSA), и оно будет проверено на то, что оно действительно является HSA до утверждения отправки.

Определение API — settings Framework

Новый интерфейс IAudioSystemEffectsPropertyStore позволяет HSA получать доступ к хранилищам свойств эффектов аудиосистемы и регистрироваться для получения уведомлений об изменении свойств.

Функция ActiveAudioInterfaceAsync предоставляет метод для асинхронного получения интерфейса IAudioSystemEffectsPropertyStore .

Приложение может получать уведомления при изменении хранилища свойств эффектов системы с помощью нового интерфейса обратного вызова IAudioSystemEffectsPropertyChangeNotificationClient .

Приложение пытается получить IAudioSystemEffectsPropertyStore с помощью IMMDevice::Activate

В этом примере показано, как приложение поддержки оборудования может использовать IMMDevice::Activate для активации IAudioSystemEffectsPropertyStore. В примере показано, как использовать IAudioSystemEffectsPropertyStore для открытия IPropertyStore с пользовательскими параметрами.

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

Пример использования ActivateAudioInterfaceAsync

В этом примере выполняется то же самое, что и в предыдущем примере, но вместо использования IMMDevice он использует API ActivateAudioInterfaceAsync для асинхронного получения интерфейса IAudioSystemEffectsPropertyStore.

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 с помощью IAudioSystemEffectsPropertyStore

В примере демонстрируется реализация APO может использовать структуру APOInitSystemEffects3 для получения пользовательских интерфейсов IPropertyStore по умолчанию и переменных для APO во время инициализации APO.

#include <audioenginebaseapo.h>

// Partial implementation of APO to show how an APO that implements IAudioSystemEffects3 can handle
// being initialized with the APOInitSystemEffects3 structure.
class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
    IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
public:
    // IAudioProcessingObject
    STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);

    // Implementation of IAudioSystemEffects2, IAudioSystemEffects3 has been omitted from this sample for brevity.  

private:

    wil::com_ptr_nothrow<IPropertyStore> m_defaultStore;
    wil::com_ptr_nothrow<IPropertyStore> m_userStore;
    wil::com_ptr_nothrow<IPropertyStore> m_volatileStore;

    // Each APO has its own private collection of properties. The collection is 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;
}

Регистрация приложения для уведомлений об изменении свойств

В примере демонстрируется использование регистрации для уведомлений об изменении свойств. Этот параметр не должен использоваться из APO и использоваться приложениями Win32.

class PropertyChangeNotificationClient : public 
    winrt::implements<PropertyChangeNotificationClient, IAudioSystemEffectsPropertyChangeNotificationClient>
{
private:
    wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> m_propertyStore;
    bool m_isListening = false;

public:
    HRESULT OpenPropertyStoreOnDefaultRenderEndpoint(REFGUID propertyStoreContext);
    HRESULT StartListeningForPropertyStoreChanges();
    HRESULT StopListeningForPropertyStoreChanges();

    // IAudioSystemEffectsPropertyChangeNotificationClient
    STDMETHOD(OnPropertyChanged)(AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE type, const PROPERTYKEY key);
};

// Open the IAudioSystemEffectsPropertyStore. This should be the first method invoked on this class.
HRESULT PropertyChangeNotificationClient::OpenPropertyStoreOnDefaultRenderEndpoint(
    REFGUID propertyStoreContext)
{
    wil::com_ptr_nothrow<IMMDeviceEnumerator> deviceEnumerator;
    RETURN_IF_FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator)));

    wil::com_ptr_nothrow<IMMDevice> device;
    RETURN_IF_FAILED(deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, device.put()));

    wil::unique_prop_variant activationParam;
    RETURN_IF_FAILED(InitPropVariantFromCLSID(propertyStoreContext, &activationParam));

    RETURN_IF_FAILED(device->Activate(__uuidof(m_propertyStore), CLSCTX_INPROC_SERVER,
        &activationParam, m_propertyStore.put_void()));
    return S_OK;
}

// Start subscribing to callbacks that are invoked when there are changes to any of the IPropertyStores
// that are managed by IAudioSystemEffectsPropertyStore.
// The OpenPropertyStoreOnDefaultRenderEndpoint should have been invoked prior to invoking this function.
HRESULT PropertyChangeNotificationClient::StartListeningForPropertyStoreChanges()
{
    RETURN_HR_IF(E_FAIL, !m_propertyStore);
    RETURN_IF_FAILED(m_propertyStore->RegisterPropertyChangeNotification(this));
    m_isListening = true;
    return S_OK;
}

// Unsubscrbe 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;
}

Пример кода — Settings Framework

Этот пример кода получен из примера sysvad SFX Swap APO — 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 — Settings Framework

Синтаксис INF-файла для объявления свойств эффекта с помощью новой платформы параметров CAPX выглядит следующим образом:

HKR, FX\0\{ApoContext}\{Default|User}, %CUSTOM_PROPERTY_KEY%,,,

Это заменяет старый синтаксис для объявления свойств эффекта следующим образом:

# Old way of declaring FX properties
HKR, FX\0, %CUSTOM_PROPERTY_KEY_1%,,,

Inf не может содержать записи IAudioSystemEffectsPropertyStore и IPropertyStore для одной конечной точки звука. Это не поддерживается.

Пример использования нового хранилища свойств:

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

Платформа уведомлений

Платформа уведомлений позволяет звуковым эффектам (APOs) запрашивать и обрабатывать уведомления об изменениях в хранилище свойств тома, конечной точки и звуковых эффектов. Эта платформа предназначена для замены существующих API, которые используются API для регистрации и отмены регистрации уведомлений.

Новый API представляет интерфейс, который api-интерфейсы могут использовать для объявления типа уведомлений, интересующих APO. Windows запросит у APO нужные уведомления и перенаправит уведомление apos. API больше не нужно явно вызывать API регистрации или отмены регистрации.

Уведомления доставляются в APO с помощью последовательной очереди. Если применимо, первое уведомление передает начальное состояние запрошенного значения (например, громкость конечной точки аудио). Уведомления останавливаются, когда audiodg.exe перестает использовать APO для потоковой передачи. После UnlockForProcess APOs перестают получать уведомления. По-прежнему необходимо синхронизировать UnlockForProcess и все уведомления в режиме выполнения.

Реализация — платформа уведомлений

Чтобы использовать платформу уведомлений, APO объявляет, какие уведомления его интересуют. Явные вызовы регистрации и отмены регистрации отсутствуют. Все уведомления для APO сериализуются, и важно не блокировать поток обратного вызова уведомлений слишком долго.

Определение API — платформа уведомлений

Платформа уведомлений реализует новый интерфейс IAudioProcessingObjectNotifications , который клиенты могут реализовать для регистрации и получения распространенных звуковых уведомлений для конечных точек APO и уведомлений о эффектах системы.

Дополнительные сведения см. на следующих страницах:

Пример кода — Платформа уведомлений

В примере показано, как APO может реализовать интерфейс IAudioProcessingObjectNotifications. В методе GetApoNotificationRegistrationInfo пример APO регистрирует уведомления об изменениях в хранилищах свойств эффектов системы.
Метод HandleNotification вызывается операционной системой для уведомления APO об изменениях, которые соответствуют тому, для чего был зарегистрирован APO.

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

Следующий код получен из примера Swap APO MFX ( swapapomfx.cpp ) и показывает регистрацию для событий, возвращая массив APO_NOTIFICATION_DESCRIPTORs.

HRESULT CSwapAPOMFX::GetApoNotificationRegistrationInfo(_Out_writes_(*count) APO_NOTIFICATION_DESCRIPTOR **apoNotifications, _Out_ DWORD *count)
{
    *apoNotifications = nullptr;
    *count = 0;

    RETURN_HR_IF_NULL(E_FAIL, m_device);

    // Let the OS know what notifications we are interested in by returning an array of
    // APO_NOTIFICATION_DESCRIPTORs.
    constexpr DWORD numDescriptors = 1;
    wil::unique_cotaskmem_ptr<APO_NOTIFICATION_DESCRIPTOR[]> apoNotificationDescriptors;

    apoNotificationDescriptors.reset(static_cast<APO_NOTIFICATION_DESCRIPTOR*>(
        CoTaskMemAlloc(sizeof(APO_NOTIFICATION_DESCRIPTOR) * numDescriptors)));
    RETURN_IF_NULL_ALLOC(apoNotificationDescriptors);

    // Our APO wants to get notified when 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;
}

Следующий код из примера SwapAPO MFX HandleNotifications ( swapapomfx.cpp ) и показывает, как обрабатывать уведомления.

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

Платформа ведения журнала

Платформа ведения журнала предоставляет разработчикам APO дополнительные средства сбора данных для улучшения разработки и отладки. Эта платформа объединяет различные методы ведения журнала, используемые различными поставщиками, и связывает ее с поставщиками ведения журнала трассировки звука, чтобы создать более осмысленное ведение журнала. Новая платформа предоставляет API ведения журнала, оставляя оставшуюся часть работы, выполняемую ОПЕРАЦИОННОй системой.

Поставщик определяется следующим образом:

IMPLEMENT_TRACELOGGING_CLASS(ApoTelemetryProvider, "Microsoft.Windows.Audio.ApoTrace",
    // {8b4a0b51-5dcf-5a9c-2817-95d0ec876a87}
    (0x8b4a0b51, 0x5dcf, 0x5a9c, 0x28, 0x17, 0x95, 0xd0, 0xec, 0x87, 0x6a, 0x87));

У каждого APO есть собственный идентификатор действия. Так как используется существующий механизм ведения журнала трассировки, можно использовать существующие консольные средства для фильтрации этих событий и их отображения в режиме реального времени. Вы можете использовать существующие средства, такие как tracelog и tracefmt, как описано в разделе Средства трассировки программного обеспечения — драйверы Windows. Дополнительные сведения о сеансах трассировки см. в статье Создание сеанса трассировки с помощью GUID элемента управления.

События ведения журнала трассировки не помечаются как данные телеметрии и не будут отображаться как поставщик данных телеметрии в таких средствах, как xperf.

Реализация — платформа ведения журнала

Платформа ведения журнала основана на механизмах ведения журнала, предоставляемых трассировкой трассировки Событий Windows. Дополнительные сведения о трассировке событий Windows см. в разделе Трассировка событий. Это не предназначено для ведения журнала звуковых данных, а для регистрации событий, которые обычно регистрируются в рабочей среде. API-интерфейсы ведения журнала не следует использовать из потока потоковой передачи в режиме реального времени, так как они могут привести к упреждающе потока насоса планировщиком ЦП ОС. Ведение журнала в первую очередь следует использовать для событий, которые помогут при отладке проблем, которые часто встречаются в поле.

Определение API — платформа ведения журнала

Платформа ведения журнала представляет интерфейс IAudioProcessingObjectLoggingService , который предоставляет новую службу ведения журнала для APOs.

Дополнительные сведения см. в разделе IAudioProcessingObjectLoggingService.

Пример кода — платформа ведения журнала

В примере показано использование метода IAudioProcessingObjectLoggingService::ApoLog и получение этого указателя интерфейса в IAudioProcessingObject::Initialize.

Пример ведения журнала AecApoMfx.

class SampleApo : public winrt::implements<SampleApo, IAudioProcessingObject,
    IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
private:
    wil::com_ptr_nothrow<IAudioProcessingObjectLoggingService> m_apoLoggingService;

public:
    // IAudioProcessingObject
    STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);

    // Implementation of IAudioProcessingObject, IAudioSystemEffects2 andIAudioSystemEffects3 has been omitted for brevity.
};

// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
    if (cbDataSize == sizeof(APOInitSystemEffects3))
    {
        APOInitSystemEffects3* apoInitSystemEffects3 = reinterpret_cast<APOInitSystemEffects3*>(pbyData);

        // Try to get the logging service, but ignore errors as failure to do logging it is not fatal.
        (void)apoInitSystemEffects3->pServiceProvider->QueryService(SID_AudioProcessingObjectLoggingService, 
            __uuidof(IAudioProcessingObjectLoggingService), IID_PPV_ARGS(&m_apoLoggingService));
    }

    // Do other APO initialization work

    if (m_apoLoggingService != nullptr)
    {
        m_apoLoggingService->ApoLog(APO_LOG_LEVEL_INFO, L"APO Initializion completed");
    }
    return S_OK;
}

Threading Framework

Платформа потоков, обеспечивающая многопоточность эффектов с помощью рабочих очередей из соответствующей задачи службы мультимедийного планировщика классов (MMCSS) с помощью простого API. Создание очередей последовательной работы в режиме реального времени и их связь с потоком main насоса обрабатываются операционной системой. Эта платформа позволяет APOs ставить в очередь кратковременные рабочие элементы. Синхронизация между задачами по-прежнему является ответственностью APO. Дополнительные сведения о потоках MMCSS см. в разделах Служба планировщика мультимедийных классов и API рабочих очередей в режиме реального времени.

Определения API — Threading Framework

Платформа потоков представляет интерфейс IAudioProcessingObjectQueueService , который предоставляет доступ к рабочей очереди реального времени для APOs.

Дополнительные сведения см. на следующих страницах:

Пример кода — threading Framework

В этом примере показано использование метода IAudioProcessingObjectRTQueueService::GetRealTimeWorkQueue и получение указателя интерфейса IAudioProcessingObjectRTQueueService в IAudioProcessingObject::Initialize.

#include <rtworkq.h>

class SampleApo3 :
    public winrt::implements<SampleApo3, IAudioProcessingObject, IAudioProcessingObjectConfiguration,
        IAudioSystemEffects, IAudioSystemEffects2, IAudioSystemEffects3>
{
private:
    DWORD m_queueId = 0;
    wil::com_ptr_nothrow<SampleApo3AsyncCallback> m_asyncCallback;

public:
    // IAudioProcessingObject
    STDMETHOD(Initialize)(UINT32 cbDataSize, BYTE* pbyData);

    // IAudioProcessingObjectConfiguration
    STDMETHOD(LockForProcess)(
        _In_ UINT32 u32NumInputConnections,
        _In_reads_(u32NumInputConnections) APO_CONNECTION_DESCRIPTOR** ppInputConnections,
        _In_ UINT32 u32NumOutputConnections,
        _In_reads_(u32NumOutputConnections) APO_CONNECTION_DESCRIPTOR** ppOutputConnections);

    // Non-interface methods called by the SampleApo3AsyncCallback helper class.
    HRESULT DoWorkOnRealTimeThread()
    {
        // Do the actual work here
        return S_OK;
    }
    void HandleWorkItemCompleted(_In_ IRtwqAsyncResult* asyncResult);

    // Implementation of IAudioProcessingObject, IAudioSystemEffects2, IAudioSystemEffects3   and IAudioProcessingObjectConfiguration is omitted
    // for brevity.
};

// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo3::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
    if (cbDataSize == sizeof(APOInitSystemEffects3))
    {
        APOInitSystemEffects3* apoInitSystemEffects3 = reinterpret_cast<APOInitSystemEffects3*>(pbyData);

        wil::com_ptr_nothrow<IAudioProcessingObjectRTQueueService> apoRtQueueService;
        RETURN_IF_FAILED(apoInitSystemEffects3->pServiceProvider->QueryService(
            SID_AudioProcessingObjectRTQueue, IID_PPV_ARGS(&apoRtQueueService)));

        // Call the GetRealTimeWorkQueue to get the ID of a work queue that can be used for scheduling tasks
        // that need to run at a real-time priority. The work queue ID is used with the Rtwq APIs.
        RETURN_IF_FAILED(apoRtQueueService->GetRealTimeWorkQueue(&m_queueId));
    }

    // Do other initialization here
    return S_OK;
}

STDMETHODIMP SampleApo3::LockForProcess(
    _In_ UINT32 u32NumInputConnections,
    _In_reads_(u32NumInputConnections) APO_CONNECTION_DESCRIPTOR** ppInputConnections,
    _In_ UINT32 u32NumOutputConnections,
    _In_reads_(u32NumOutputConnections) APO_CONNECTION_DESCRIPTOR** ppOutputConnections)
{
    // Implementation details of LockForProcess omitted for brevity
    m_asyncCallback = winrt::make<SampleApo3AsyncCallback>(m_queueId).get();
    RETURN_IF_NULL_ALLOC(m_asyncCallback);

    wil::com_ptr_nothrow<IRtwqAsyncResult> asyncResult;	
    RETURN_IF_FAILED(RtwqCreateAsyncResult(this, m_asyncCallback.get(), nullptr, &asyncResult));

    RETURN_IF_FAILED(RtwqPutWorkItem(m_queueId, 0, asyncResult.get())); 
    return S_OK;
}

void SampleApo3::HandleWorkItemCompleted(_In_ IRtwqAsyncResult* asyncResult)
{
    // check the status of the result
    if (FAILED(asyncResult->GetStatus()))
    {
        // Handle failure
    }

    // Here the app could call RtwqPutWorkItem again with m_queueId if it has more work that needs to
    // execute on a real-time thread.
}


class SampleApo3AsyncCallback :
    public winrt::implements<SampleApo3AsyncCallback, IRtwqAsyncCallback>
{
private:
    DWORD m_queueId;

public:
    SampleApo3AsyncCallback(DWORD queueId) : m_queueId(queueId) {}

    // IRtwqAsyncCallback
    STDMETHOD(GetParameters)(_Out_ DWORD* pdwFlags, _Out_ DWORD* pdwQueue)
    {
        *pdwFlags = 0;
        *pdwQueue = m_queueId;
        return S_OK;
    }
    STDMETHOD(Invoke)(_In_ IRtwqAsyncResult* asyncResult);
};


STDMETHODIMP SampleApo3AsyncCallback::Invoke(_In_ IRtwqAsyncResult* asyncResult)
{
    // We are now executing on the real-time thread. Invoke the APO and let it execute the work.
    wil::com_ptr_nothrow<IUnknown> objectUnknown;
    RETURN_IF_FAILED(asyncResult->GetObject(objectUnknown.put_unknown()));

    wil::com_ptr_nothrow<SampleApo3> sampleApo3 = static_cast<SampleApo3*>(objectUnknown.get());
    HRESULT hr = sampleApo3->DoWorkOnRealTimeThread();
    RETURN_IF_FAILED(asyncResult->SetStatus(hr));

    sampleApo3->HandleWorkItemCompleted(asyncResult);
    return S_OK;
}

Дополнительные примеры использования этого интерфейса см. в следующем примере кода:

Обнаружение звуковых эффектов и управление ими для эффектов

Платформа обнаружения позволяет ОС управлять звуковыми эффектами в своем потоке. Эти API обеспечивают поддержку сценариев, в которых пользователю приложения необходимо контролировать определенные эффекты на потоки (например, глубокое подавление шума). Для этого эта платформа добавляет следующие компоненты:

  • Новый API для запроса из APO, чтобы определить, можно ли включить или отключить звуковой эффект.
  • Новый API для установки состояния звукового эффекта в положение "Вкл. или выкл.".
  • Уведомление об изменении списка звуковых эффектов или о том, что ресурсы становятся доступными, чтобы включить или отключить звуковой эффект.

Реализация — обнаружение звуковых эффектов

APO должен реализовать интерфейс IAudioSystemEffects3 , если он намерен предоставлять эффекты, которые можно динамически включать и отключать. APO предоставляет свои звуковые эффекты с помощью функции IAudioSystemEffects3::GetControllableSystemEffectsList , а также включает и отключает звуковые эффекты с помощью функции IAudioSystemEffects3::SetAudioSystemEffectState .

Пример кода— обнаружение звуковых эффектов

Пример кода обнаружения звуковых эффектов можно найти в примере SwapAPOSFX — swapaposfx.cpp.

В следующем примере кода показано, как получить список настраиваемых эффектов. Пример GetControllableSystemEffectsList — swapaposfx.cpp

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

    *effects = nullptr;
    *numEffects = 0;

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

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

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

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

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

    return S_OK;
}

В следующем примере кода показано, как включить и отключить эффекты. Пример SetAudioSystemEffectState — swapaposfx.cpp

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

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

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

            m_EffectsLock.Leave();
            
            return S_OK;
        }
    }

    return E_NOTFOUND;
}

Повторное использование API-интерфейсов WM SFX и MFX в Windows 11 версии 22H2

Начиная с Windows 11 версии 22H2, файлы конфигурации INF, которые повторно используют api-интерфейсы WM SFX и MFX для почтовых ящиков, теперь могут повторно использовать APOS CAPX SFX и MFX. В этом разделе описаны три способа.

Существует три точки вставки для APOs: отрисовка до смешивания, отрисовка после микширования и запись. Звуковой модуль каждого логического устройства поддерживает один экземпляр APO предварительной отрисовки для каждого потока (отрисовка SFX) и один APO (MFX). Звуковой модуль также поддерживает один экземпляр APO записи (SFX), который вставляется в каждый поток захвата. Дополнительные сведения о повторном использовании или оболочке API папки "Входящие" см. в статье Объединение пользовательских API и API Windows.

Api-интерфейсы CAPX SFX и MFX можно повторно использовать одним из следующих трех способов.

Использование раздела INF DDInstall

Используйте mssysfx. CopyFilesAndRegisterCapX из wdmaudio.inf, добавив следующие записи.

   Include=wdmaudio.inf
   Needs=mssysfx.CopyFilesAndRegisterCapX

Использование INF-файла расширения

Wdmaudioapo.inf — это расширение класса AudioProcessingObject inf. Он содержит регистрацию api-интерфейсов SFX и MFX для конкретного устройства.

Прямая ссылка на API WM SFX и MFX для потоковых и режимных эффектов

Чтобы напрямую ссылаться на эти API для потоковых и режимных эффектов, используйте следующие значения GUID.

  • Использование {C9453E73-8C5C-4463-9984-AF8BAB2F5447} в качестве WM SFX APO
  • Используйте {13AB3EBD-137E-4903-9D89-60BE8277FD17} в качестве WM MFX APO.

SFX (Stream) и MFX (mode) были переданы в Windows 8.1 LFX (локальный), а MFX — GFX (глобальный). Эти записи реестра продолжают использовать предыдущие имена.

Регистрация для конкретного устройства использует HKR вместо HKCR.

В INF-файл необходимо добавить следующие элементы.

  HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
  HKR,"FX\\0\\%WMALFXGFXAPO_Context%\\User",,,
  WMALFXGFXAPO_Context = "{B13412EE-07AF-4C57-B08B-E327F8DB085B}"

Эти записи INF-файла создадут хранилище свойств, которое будет использоваться API Windows 11 для новых API.

PKEY_FX_Association в inf ex. HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%Следует заменить на HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%.

См. также раздел

Объекты обработки звука Windows.