Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом разделе представлен набор новых API Windows 11 для объектов обработки звука (API), которые поставляется с звуковым драйвером.
Windows позволяет сторонним производителям звукового оборудования включать собственные эффекты обработки цифровых сигналов, основанные на хосте. Эти эффекты упаковываются в виде объектов обработки звука в пользовательском режиме (API). Дополнительные сведения см. в Windows объектах обработки звука.
Некоторые api, описанные здесь, обеспечивают новые сценарии для независимых поставщиков оборудования (IHV) и независимых поставщиков программного обеспечения (ISV), а другие API предназначены для предоставления альтернативных вариантов, которые повышают общую надежность звука и возможности отладки.
- Платформа Acoustic Echo Cancellation (AEC) позволяет APO идентифицировать себя как AEC APO, предоставляя доступ к референсным потокам и дополнительным элементам управления.
- Платформа "Параметры " позволит API предоставлять методы для запроса и изменения хранилища свойств для звуковых эффектов ("хранилище свойств FX") в звуковой конечной точке. Когда эти методы реализуются APO, они могут вызываться приложениями поддержки оборудования (HSA), связанными с этим APO.
- Платформа уведомлений позволяет аудио процессорным объектам (АПО) запрашивать уведомления об обработке изменений громкости, конечных точек и хранилища свойств звуковых эффектов.
- Фреймворк для логирования помогает в разработке и отладке APO.
- Фреймворк потоков позволяет APO быть многопоточными с использованием управляемого операционной системой пула потоков, зарегистрированного в MMCSS.
- API-интерфейсы обнаружения и управления звуковыми эффектами позволяют ОС обнаруживать, включать и отключать эффекты, доступные для обработки в потоке.
Чтобы использовать эти новые API, предполагается, что APO воспользуются новым интерфейсом IAudioSystemEffects3. Когда APO реализует этот интерфейс, ОС интерпретирует это как неявный сигнал о том, что APO поддерживает платформу параметров APO и позволяет APO подписываться на распространенные уведомления, связанные с звуком, из звукового модуля.
Требования к разработке APO CAPX для Windows 11
Все новые APO, которые установлены на устройстве для Windows 11, должны соответствовать API, перечисленным в этом документе, проверены через HLK. Кроме того, любые API,которые используют AEC, должны следовать реализации, описанной в этом разделе, проверенной через HLK. Пользовательские реализации для этих основных расширений обработки звука (параметры, ведение журнала, уведомления, потоки, AEC), как ожидается, используют API CAPX. Это будет проверено с помощью тестов HLK Windows 11. Например, если 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 — скачивание ISO
- Предварительная версия программы предварительной оценки Windows — скачивание WDK
- Предварительная версия программы предварительной оценки Windows — скачивание пакета SDK
Содержимое WINDOWS 11 WHCP обновлено, чтобы предоставить партнерам средства для проверки этих API.
Пример кода для содержимого, описанного в этом разделе, можно найти здесь: Audio/SYSVAD/APO — github
Акустическая эхокомпенсация (AEC)
Акустическая отмена эха (AEC) — это распространенный звуковой эффект, реализованный независимыми поставщиками оборудования (IHVs) и независимыми поставщиками программного обеспечения (ISVs) в качестве объекта обработки звука (APO) в конвейере захвата микрофона. Этот эффект отличается от других эффектов, которые обычно реализуются IHVs и ISVs, для которого требуются два входных потока — аудиопоток с микрофона и аудиопоток от устройства отрисовки, который выступает в качестве эталонного сигнала.
Этот новый набор интерфейсов позволяет AEC APO идентифицировать себя как таковое в звуковом обработчике. Настройка аудио-движка приводит к соответствующей настройке APO с несколькими входными данными и одним выходом.
Когда новые интерфейсы AEC реализуются с помощью APO, звукобработающая система будет:
- Настройте APO с дополнительными входными данными, предоставляющими APO ссылочный поток из соответствующей конечной точки отрисовки.
- Переключите ссылочные потоки при изменении устройства рендеринга.
- Разрешить APO управлять форматом входного микрофона и ссылочного потока.
- Разрешить APO получать метки времени с микрофона и эталонных потоков.
Предыдущий подход — Windows 10
APO является одним входным — одним выходным объектом. Аудиосистема предоставляет AEC APO звук из конечной точки микрофона на вход. Чтобы получить ссылочный поток, APO может взаимодействовать с драйвером с помощью собственных интерфейсов для получения эталонного звука из конечной точки отрисовки или использовать WASAPI для открытия потока обратной передачи в конечной точке отрисовки.
Оба описанных выше подхода имеют недостатки:
AEC APO, использующий частные каналы для получения ссылочного потока от драйвера, обычно может сделать это только из встроенного устройства отрисовки звука. В результате отмена эхо не будет работать, если пользователь воспроизводит звук из не интегрированных устройств, таких как USB-устройство или аудиоустройство Bluetooth. Только ОС знает о правильных конечных точках отрисовки, которые могут служить эталонными конечными точками.
APO может использовать WASAPI, чтобы выбрать конечную точку воспроизведения по умолчанию для выполнения отмены эха. Однако при открытии потока обратного цикла из процесса audiodg.exe (где размещается APO), существуют некоторые недостатки.
- Поток обратной связи не может быть открыт или уничтожен, когда аудиодвижок вызывает основные методы APO, так как это может привести к взаимоблокировке.
- APO записи не знает состояние потоков своих клиентов. т. е. приложение записи может иметь поток записи в состоянии STOP, однако APO не знает об этом состоянии, и поэтому сохраняет поток обратной передачи в состоянии run, который является неэффективным с точки зрения потребления энергии.
Определение API — AEC
Фреймворк AEC предоставляет новые структуры и интерфейсы, которые могут использовать APO. Эти новые структуры и интерфейсы описаны ниже.
Структура APO_CONNECTION_PROPERTY_V2
API,реализующие интерфейс 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 см. в разделе "Архитектура объектов обработки звука".
Если влияние режима на конечную точку захвата реализуется в виде ряда цепочек API, то только APO, ближайший к устройству, может реализовать этот интерфейс. Api-интерфейсы, реализующие этот интерфейс, будут предлагать APO_CONNECTION_PROPERTY_V2 структуру в вызове IAudioProcessingobjectRT::APOProcess. APO может проверить сигнатуру APO_CONNECTION_PROPERTY_V2_SIGNATURE для свойства подключения и привести структуру входящего APO_CONNECTION_PROPERTY к структуре APO_CONNECTION_PROPERTY_V2.
Учитывая, что АПО AEC обычно выполняют свои алгоритмы с определенной частотой дискретизации или числом каналов, звуковой модуль обеспечивает поддержку ресэмплинга для АПО, реализующих интерфейс IApoAcousticEchoCancellation.
Когда APO AEC возвращает APOERR_FORMAT_NOT_SUPPORTED в вызове IAudioProcessingObject::OutInputFormatSupported, звуковой механизм вызовет IAudioProcessingObject::IsInputFormatSupported в APO снова с форматом выходных данных NULL и форматом ввода, не допускающим значение NULL, для получения предлагаемого формата APO. Затем звуковой модуль перенаправит звук микрофона в предлагаемый формат перед отправкой в AEC APO. Это устраняет необходимость AEC APO для реализации преобразования частоты выборки и подсчета каналов.
IApoAuxiliaryInputConfiguration
Интерфейс IApoAuxiliaryInputConfiguration предоставляет методы, которые APO могут реализовать, чтобы звуковой движок мог добавлять и удалять вспомогательные входные потоки.
Этот интерфейс реализуется 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 AEC CAPX
Дополнительные сведения см. на следующих страницах.
- структура APO_CONNECTION_PROPERTY_V2 (audioapotypes.h)
- Интерфейс IApoAcousticEchoCancellation
- IApoAuxiliaryInputConfiguration
- IApoAuxiliaryInputRT
Пример кода — AEC
Ознакомьтесь со следующими примерами кода Sysvad Audio AecApo.
Следующий код из примера заголовка 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
При инициализации:
- IAudioProcessingObject::Initialize
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration:: LockForProcess
- IAudioProcessingObjectConfiguration ::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
При изменении устройства отрисовки:
- IAudioProcessingObject::Initialize
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess (блокировка процесса)
- Изменения устройства по умолчанию
- IAudioProcessingObjectConfiguration::UnlockForProcess
- IApoAuxiliaryInputConfiguration::RemoveAuxiliaryInput
- IApoAuxiliaryInputConfiguration::AddAuxiliaryInput
- IAudioProcessingObjectConfiguration::LockForProcess
Рекомендуемое поведение буферизации — AEC
Это рекомендуемое поведение буфера для AEC.
- Буферы, полученные в вызове IApoAuxiliaryInputRT::AcceptInput, должны быть записаны в циклический буфер без блокировки основного потока.
- При вызове IAudioProcessingObjectRT::APOProcess следует считать из циклического буфера последний звуковой пакет из ссылочного потока, и этот пакет должен использоваться для запуска алгоритма отмены эхо.
- Метки времени для ссылочных и микрофонных данных могут использоваться для их синхронизации.
Поток обратной передачи ссылок
По умолчанию петлевой поток подключается к аудиопотоку до применения регулировки громкости или отключения звука. Поток обратной петли, доступный до применения громкости, называется потоком предварительного изменения громкости. Преимуществом потока предварительного соединения является четкий и однородный аудиопоток независимо от текущего параметра громкости.
Некоторые алгоритмы AEC могут предпочесть получение петлевого потока, который был подключен после любой обработки громкости (включая отключение звука). Эта конфигурация называется томовым обратным циклом.
В следующей основной версии Windows AEC APOs могут запрашивать лупбэк после регулировки громкости на поддерживаемых конечных точках.
Ограничения
В отличие от потоков обратной связи до регулировки громкости, доступных для всех конечных точек отрисовки, потоки обратной связи после регулировки громкости могут быть недоступны на всех конечных точках.
Запрос обратного цикла после тома
API AEC, которые хотят использовать обратный цикл после тома, должны реализовать интерфейс IApoAcousticEchoCancellation2 .
AEC APO может запросить контур возврата по объёму, возвращая флаг APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK через параметр Properties в своей реализации IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties.
В зависимости от используемой конечной точки отрисовки, обратный цикл после тома может быть недоступен. AEC APO уведомляется о том, используется ли обратная связь после объема, когда вызывается метод IApoAuxiliaryInputConfiguration::AddAuxiliaryInput. Если поле streamProperties AcousticEchoCanceller_Reference_Input содержит APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK, используется обратный цикл после тома.
Следующий код из примера заголовка AEC APO— 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.
Фреймворк настроек
Платформа Settings Framework позволяет APO предоставлять методы для запроса и изменения хранилища свойств для звуковых эффектов ("Хранилище свойств FX") на аудио конечной точке. Эта платформа может использоваться APO и приложениями поддержки оборудования (HSA), которые хотят передавать настройки этому APO. HSAs могут быть приложениями универсальной платформы Windows (UWP) и требуют специальных возможностей для вызова API в Settings Framework. Дополнительные сведения о приложениях HSA см. в разделе "Приложения устройств UWP".
Структура хранилища FxProperty
В новом хранилище FxProperty есть три вложенных хранилища: по умолчанию, user и Volatile.
Подраздел "Default" содержит свойства настраиваемых эффектов и заполняется из INF-файла. Эти свойства не сохраняются во время обновлений ОС. Например, свойства, которые обычно определены в INF, будут соответствовать здесь. Затем они будут повторно заполнены из INF.
Подраздел "Пользователь" содержит параметры пользователя, относящиеся к свойствам эффектов. Эти параметры сохраняются ОС во время обновлений и миграций. Например, все предустановки, которые пользователь может настроить, которые должны сохраняться во время обновления.
Подраздел "Volatile" содержит свойства нестабильных эффектов. Эти свойства теряются при перезагрузке устройства и очищаются при каждом переходе конечной точки в активный режим. Ожидается, что они содержат свойства варианта времени (например, на основе текущих запущенных приложений, состояния устройства и т. д.). Например, все параметры, зависящие от текущей среды.
Следует рассматривать выбор между пользовательскими и стандартными настройками в зависимости от того, хотите ли вы, чтобы свойства сохранялись при обновлениях ОС и драйверов. Свойства пользователя будут сохранены. Свойства по умолчанию будут повторно заполнены из файла INF.
Контексты APO
Платформа параметров CAPX позволяет автору APO группировать свойства APO по контекстам. Каждый APO может определять собственный контекст и обновлять свойства относительно собственного контекста. Хранилище свойств эффектов для конечной точки звука может иметь ноль или больше контекстов. Поставщики могут свободно создавать контексты так, как они предпочитают, будь то SFX/MFX/EFX или по режиму. Поставщик также может выбрать один контекст для всех API, отправленных этим поставщиком.
Ограниченные возможности в настройках
API параметров предназначен для поддержки всех OEM и разработчиков 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, настольных приложений без прав администратора и APOs. Кроме того, это может выступать в качестве механизма для 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 identified through a
// a property store context GUID, which is defined below and in the audio driver INF file.
const GUID m_propertyStoreContext = ...;
};
// Implementation of IAudioProcessingObject::Initialize
STDMETHODIMP SampleApo::Initialize(UINT32 cbDataSize, BYTE* pbyData)
{
if (cbDataSize == sizeof(APOInitSystemEffects3))
{
// SampleApo supports the new IAudioSystemEffects3 interface so it will receive APOInitSystemEffects3
// in pbyData if the audio driver has declared support for this.
// Use IMMDevice to activate IAudioSystemEffectsPropertyStore that contains the default, user and
// volatile settings.
IMMDeviceCollection* deviceCollection =
reinterpret_cast<APOInitSystemEffects3*>(pbyData)->pDeviceCollection;
if (deviceCollection != nullptr)
{
UINT32 numDevices;
wil::com_ptr_nothrow<IMMDevice> endpoint;
// Get the endpoint on which this APO has been created
// (It is the last device in the device collection)
if (SUCCEEDED(deviceCollection->GetCount(&numDevices)) &&
numDevices > 0 &&
SUCCEEDED(deviceCollection->Item(numDevices - 1, &endpoint)))
{
wil::unique_prop_variant activationParam;
RETURN_IF_FAILED(InitPropVariantFromCLSID(m_propertyStoreContext, &activationParam));
wil::com_ptr_nothrow<IAudioSystemEffectsPropertyStore> effectsPropertyStore;
RETURN_IF_FAILED(endpoint->Activate(__uuidof(effectsPropertyStore), CLSCTX_ALL, &activationParam, effectsPropertyStore.put_void()));
// Read default, user and volatile property values to set up initial operation of the APO
RETURN_IF_FAILED(effectsPropertyStore->OpenDefaultPropertyStore(STGM_READWRITE, m_defaultStore.put()));
RETURN_IF_FAILED(effectsPropertyStore->OpenUserPropertyStore(STGM_READWRITE, m_userStore.put()));
RETURN_IF_FAILED(effectsPropertyStore->OpenVolatilePropertyStore(STGM_READWRITE, m_volatileStore.put()));
// At this point the APO can read and write settings in the various property stores,
// as appropriate. (Not shown.)
// Note that APOInitSystemEffects3 contains all the members of APOInitSystemEffects2,
// so an APO that knows how to initialize from APOInitSystemEffects2 can use the same
// code to continue its initialization here.
}
}
}
else if (cbDataSize == sizeof(APOInitSystemEffects2))
{
// Use APOInitSystemEffects2 for the initialization of the APO.
// If we get here, the audio driver did not declare support for IAudioSystemEffects3.
}
else if (cbDataSize == sizeof(APOInitSystemEffects))
{
// Use APOInitSystemEffects for the initialization of the APO.
}
return S_OK;
}
Приложение регистрируется для получения уведомлений об изменении свойств.
Пример демонстрирует использование регистрации уведомлений об изменении свойств. Это не должно использоваться вместе с 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;
}
// 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;
}
Пример кода — Settings Framework
Этот образец кода из sysvad SFX Swap APO sample - 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 — Фреймворк Настроек
Синтаксис 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, используемых APO для регистрации и отмены регистрации уведомлений.
Новый API представляет интерфейс, который APO могут использовать для объявления типа уведомлений, интересующих APO. Windows будет запрашивать APO о уведомлениях, которые его интересуют, и перенаправлять уведомления в эти APO. APO больше не нужно явно вызывать API для регистрации или отмены регистрации.
Уведомления доставляются в APO с помощью последовательной очереди. Если применимо, первое уведомление транслирует исходное состояние запрашиваемого параметра (например, громкость аудио конечной точки). Уведомления останавливаются после того, как audiodg.exe перестает использовать APO для потоковой передачи. API-программы перестанут получать уведомления после вызова UnlockForProcess. Синхронизация 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 identified through a
// a property store context GUID, which is defined below and in the audio driver INF file.
const GUID m_propertyStoreContext = ...;
float m_masterVolume = 1.0f;
BOOL m_isMuted = FALSE;
BOOL m_allowOffloading = FALSE;
// The rest of the implementation of IAudioProcessingObject is omitted for brevity
};
// The OS invokes this method on the APO to find out what notifications the APO is interested in.
STDMETHODIMP SampleApo::GetApoNotificationRegistrationInfo(
_Out_writes_(count) APO_NOTIFICATION_DESCRIPTOR** apoNotificationDescriptorsReturned,
_Out_ DWORD* count)
{
*apoNotificationDescriptorsReturned = nullptr;
*count = 0;
// Before this function can be called, our m_device member variable should already have been initialized.
// This would typically be done in our implementation of IAudioProcessingObject::Initialize, by using
// APOInitSystemEffects3::pDeviceCollection to obtain the last IMMDevice in the collection.
RETURN_HR_IF_NULL(E_FAIL, m_device);
// Let the OS know what notifications we are interested in by returning an array of
// APO_NOTIFICATION_DESCRIPTORs.
constexpr DWORD numDescriptors = 3;
wil::unique_cotaskmem_ptr<APO_NOTIFICATION_DESCRIPTOR[]> apoNotificationDescriptors;
apoNotificationDescriptors.reset(static_cast<APO_NOTIFICATION_DESCRIPTOR*>(
CoTaskMemAlloc(sizeof(APO_NOTIFICATION_DESCRIPTOR) * numDescriptors)));
RETURN_IF_NULL_ALLOC(apoNotificationDescriptors);
// Our APO wants to get notified when any change occurs on the user property store on the audio endpoint
// identified by m_device.
// The user property store is different for each APO. Ours is identified by m_propertyStoreContext.
apoNotificationDescriptors[0].type = APO_NOTIFICATION_TYPE_AUDIO_SYSTEM_EFFECTS_PROPERTY_CHANGE;
(void)m_device.query_to(&apoNotificationDescriptors[0].audioSystemEffectsPropertyChange.device);
apoNotificationDescriptors[0].audioSystemEffectsPropertyChange.propertyStoreContext = m_propertyStoreContext;
// Our APO wants to get notified when an endpoint property changes on the audio endpoint.
apoNotificationDescriptors[1].type = APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE;
(void)m_device.query_to(&apoNotificationDescriptors[1].audioEndpointPropertyChange.device);
// Our APO also wants to get notified when the volume level changes on the audio endpoint.
apoNotificationDescriptors [2].type = APO_NOTIFICATION_TYPE_ENDPOINT_VOLUME;
(void)m_device.query_to(&apoNotificationDescriptors[2].audioEndpointVolume.device);
*apoNotificationDescriptorsReturned = apoNotificationDescriptors.release();
*count = numDescriptors;
return S_OK;
}
static bool IsSameEndpointId(IMMDevice* device1, IMMDevice* device2)
{
bool isSameEndpointId = false;
wil::unique_cotaskmem_string deviceId1;
if (SUCCEEDED(device1->GetId(&deviceId1)))
{
wil::unique_cotaskmem_string deviceId2;
if (SUCCEEDED(device2->GetId(&deviceId2)))
{
isSameEndpointId = (CompareStringOrdinal(deviceId1.get(), -1, deviceId2.get(), -1, TRUE) == CSTR_EQUAL);
}
}
return isSameEndpointId;
}
// HandleNotification is called whenever there is a change that matches any of the
// APO_NOTIFICATION_DESCRIPTOR elements in the array that was returned by GetApoNotificationRegistrationInfo.
// Note that the APO will have to query each property once to get its initial value because this method is
// only invoked when any of the properties have changed.
STDMETHODIMP_(void) SampleApo::HandleNotification(_In_ APO_NOTIFICATION* apoNotification)
{
// Check if a property in the user property store has changed.
if (apoNotification->type == APO_NOTIFICATION_TYPE_AUDIO_SYSTEM_EFFECTS_PROPERTY_CHANGE
&& IsSameEndpointId(apoNotification->audioSystemEffectsPropertyChange.endpoint, m_device.get())
&& apoNotification->audioSystemEffectsPropertyChange.propertyStoreContext == m_propertyStoreContext
&& apoNotification->audioSystemEffectsPropertyChange.propertyStoreType == AUDIO_SYSTEMEFFECTS_PROPERTYSTORE_TYPE_USER)
{
// Check if one of the properties that we are interested in has changed.
// As an example, we check for "PKEY_Endpoint_Enable_Channel_Swap_SFX" which is a fictitious
// PROPERTYKEY that could be set on our user property store.
if (apoNotification->audioSystemEffectsPropertyChange.propertyKey ==
PKEY_Endpoint_Enable_Channel_Swap_SFX)
{
wil::unique_prop_variant var;
if (SUCCEEDED(apoNotification->audioSystemEffectsPropertyChange.propertyStore->GetValue(
PKEY_Endpoint_Enable_Channel_Swap_SFX, &var)) &&
var.vt != VT_EMPTY)
{
// We have retrieved the property value. Now we can do something interesting with it.
}
}
}
else if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE
&& IsSameEndpointId(apoNotification->audioEndpointPropertyChange.endpoint, m_device.get())
{
// Handle changes to PROPERTYKEYs in the audio endpoint's own property store.
// In this example, we are interested in a property called "PKEY_Endpoint_AllowOffloading" that the
// user might change in the audio control panel, and we update our member variable if this
// property changes.
if (apoNotification->audioEndpointPropertyChange.propertyKey == PKEY_Endpoint_AllowOffloading)
{
wil::unique_prop_variant var;
if (SUCCEEDED(propertyStore->GetValue(PKEY_Endpoint_AllowOffloading, &var)) && var.vt == VT_BOOL)
{
m_allowOffloading = var.boolVal;
}
}
}
else if (apoNotification->type == APO_NOTIFICATION_TYPE_ENDPOINT_VOLUME
&& IsSameEndpointId(apoNotification->audioEndpointVolumeChange.endpoint, m_device.get())
{
// Handle endpoint volume change
m_masterVolume = apoNotification->audioEndpointVolumeChange.volume->fMasterVolume;
m_isMuted = apoNotification->audioEndpointVolumeChange.volume->bMuted;
}
}
В следующем коде из примера 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 an endpoint property changes on the audio endpoint.
apoNotificationDescriptors[0].type = APO_NOTIFICATION_TYPE_ENDPOINT_PROPERTY_CHANGE;
(void)m_device.query_to(&apoNotificationDescriptors[0].audioEndpointPropertyChange.device);
*apoNotifications = apoNotificationDescriptors.release();
*count = numDescriptors;
return S_OK;
}
Следующий код представлен в примере 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.
Реализация — платформа ведения журнала
Платформа ведения журнала основана на механизмах, предоставляемых трассировкой ETW. Дополнительные сведения о трассировке событий см. в разделе "Трассировка событий". Это не предназначено для ведения журнала звуковых данных, а для журналов событий, которые обычно регистрируются в рабочей среде. API журналирования не следует использовать из потока обработки данных в режиме реального времени, так как они могут потенциально быть причиной прерывания потока насоса планировщиком ЦП ОС. Логгирование должно в первую очередь использоваться для событий, которые помогут с отладкой проблем, часто обнаруживаемых на местах.
Определение API — Платформа ведения журнала
Платформа ведения журнала представляет интерфейс IAudioProcessingObjectLoggingService , который предоставляет новую службу ведения журнала для API.
Дополнительные сведения см. в разделе 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 Initialization completed");
}
return S_OK;
}
Фреймворк для многопоточности
Фреймворк многопоточности, позволяющий разрабатывать многопоточные эффекты с использованием рабочих очередей подходящей задачи службы планировщика мультимедиа-класса (MMCSS) через простой API. Создание очередей последовательной работы в режиме реального времени и их связь с основным потоком насоса обрабатываются ОС. Этот фреймворк позволяет APO ставить в очередь кратковременные задачи. Синхронизация между задачами по-прежнему находится в ведении APO. Дополнительные сведения о потоках MMCSS см. в Служба планировщика классов мультимедиа и API рабочих очередейReal-Time.
Определения API — Threading Framework
Threading предоставляет интерфейс 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;
}
Дополнительные примеры использования этого интерфейса см. в следующем примере кода:
- Определение класса SwapAPO SwapMFXApoAsyncCallback — пример
- Пример функции SwapAPO Invoke
- SwapAPO создание асинхронного обратного вызова — пример
Обнаружение и управление аудиоэффектами
Платформа обнаружения позволяет ОС управлять звуковыми эффектами в потоке. Эти 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, теперь могут повторно использовать API CAPX SFX и MFX. В этом разделе описаны три способа этого.
Существует три точки вставки для APOs: рендеринг до микширования, рендеринг после микширования и запись. Аудиодвижок каждого логического устройства поддерживает один экземпляр предварительного перемешивания APO на поток (отрисовка SFX) и одно пост-микширование APO (MFX). Звуковой модуль также поддерживает один экземпляр APO записи (запись SFX), вставленный в каждый поток записи. Дополнительные сведения о повторном использовании или оболочке 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}
в качестве APO WM SFX - Используйте
{13AB3EBD-137E-4903-9D89-60BE8277FD17}
в качестве WM MFX APO.
SFX (Stream) и MFX (режим) в Windows 8.1 обозначались как LFX (локальный) и 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 для новых APO.
PKEY_FX_Association в INF ex.
HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
, следует заменить на HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%
.