오디오 처리 개체용 Windows 11 API

이 항목에서는 오디오 드라이버와 함께 제공되는 새로운 Windows 11 API(오디오 처리 개체)를 소개합니다.

Windows를 사용하면 타사 오디오 하드웨어 제조업체에서 사용자 지정 호스트 기반 디지털 신호 처리 효과를 포함할 수 있습니다. 이러한 효과는 API(사용자 모드 오디오 처리 개체)로 패키지됩니다. 자세한 내용은 Windows 오디오 처리 개체를 참조 하세요.

여기에 설명된 API 중 일부는 IHV(Independent Hardware Vendors) 및 ISV(Independent Software Vendors)에 대한 새로운 시나리오를 사용하도록 설정하는 반면, 다른 API는 전반적인 오디오 안정성 및 디버깅 기능을 개선하는 대안을 제공하기 위한 것입니다.

  • AEC(Acoustic Echo Cancellation) 프레임워크를 사용하면 APO가 자신을 AEC APO로 식별하여 참조 스트림 및 추가 컨트롤에 대한 액세스 권한을 부여할 수 있습니다.
  • 설정 프레임워크를 사용하면 API가 오디오 엔드포인트에서 오디오 효과("FX 속성 저장소")에 대한 속성 저장소를 쿼리하고 수정하는 메서드를 노출할 수 있습니다. APO에서 이러한 메서드를 구현하는 경우 해당 APO와 연결된 HSA(하드웨어 지원 앱)에서 호출할 수 있습니다.
  • Notifications 프레임워크를 사용하면 API(오디오 효과)가 볼륨, 엔드포인트 및 오디오 효과 속성 저장소 변경 내용을 처리하기 위한 알림을 요청할 수 있습니다.
  • 로깅 프레임워크API의 개발 및 디버깅을 지원합니다.
  • 스레딩 프레임워크를 사용하면 OS 관리형 MMCSS 등록 스레드 풀을 사용하여 API를 다중 스레드할 수 있습니다.
  • 오디오 효과 검색 및 제어 API를 사용하면 OS가 스트림에서 처리하는 데 사용할 수 있는 효과를 검색, 사용 및 비활성화할 수 있습니다.

이러한 새 API를 활용하기 위해 API는 새 IAudioSystemEffects3 인터페이스를 활용해야 합니다. APO가 이 인터페이스를 구현할 때 OS는 이를 APO가 APO 설정 프레임워크를 지원하고 APO가 오디오 엔진에서 일반적인 오디오 관련 알림을 구독할 수 있도록 허용한다는 암시적 신호로 해석합니다.

Windows 11 APO CAPX 개발 요구 사항

Windows 11용 디바이스에서 제공되는 모든 새 API는 HLK를 통해 유효성을 검사하는 이 항목에 나열된 API를 준수해야 합니다. 또한 AEC를 활용하는 모든 API는 HLK를 통해 유효성을 검사하는 이 항목에 설명된 구현을 따라야 합니다. 이러한 핵심 오디오 처리 확장 프로그램(설정, 로깅, 알림, 스레딩, AEC)에 대한 사용자 지정 구현은 CAPX API를 활용할 것으로 예상됩니다. Windows 11 HLK 테스트를 통해 유효성을 검사합니다. 예를 들어 APO가 설정 Framework를 사용하는 대신 레지스트리 데이터를 사용하여 설정을 저장하는 경우 연결된 HLK 테스트가 실패합니다.

Windows 버전 요구 사항

이 항목에 설명된 API는 Windows 11 OS, WDK 및 SDK 빌드 22000부터 사용할 수 있습니다. Windows 10에서는 이러한 API를 지원하지 않습니다. APO가 Windows 10 및 Windows 11 모두에서 작동하려는 경우 APOInitSystemEffects2 또는 APOInitSystemEffects3 구조체를 사용하여 초기화되고 있는지 여부를 검사하여 CAPX API를 지원하는 OS에서 실행 중인지 여부를 확인할 수 있습니다.

최신 버전의 Windows, WDK 및 SDK는 Windows 참가자 프로그램을 통해 아래에서 다운로드할 수 있습니다. 파트너 센터를 통해 Microsoft와 협력하는 파트너는 공동 작업을 통해 이 콘텐츠에 액세스할 수도 있습니다. 공동 작업에 대한 자세한 내용은 Microsoft 공동 작업 소개를 참조 하세요.

파트너에게 이러한 API의 유효성을 검사할 수 있는 수단을 제공하도록 Windows 11 WHCP 콘텐츠가 업데이트되었습니다.

이 항목에 설명된 콘텐츠에 대한 샘플 코드는 Audio/SYSVAD/APO - github에서 찾을 수 있습니다.

AEC(Acoustic Echo Cancellation)

AEC(Acoustic Echo Cancellation)는 마이크 캡처 파이프라인에서 IHV(Independent Hardware Vendors) 및 ISV(Independent Software Vendors)가 APO(오디오 처리 개체)로 구현하는 일반적인 오디오 효과입니다. 이 효과는 일반적으로 IHV 및 ISV에서 구현하는 다른 효과와 다릅니다. 즉, 마이크의 오디오 스트림과 참조 신호 역할을 하는 렌더링 디바이스의 오디오 스트림 등 2개의 입력이 필요하다는 것입니다.

이 새로운 인터페이스 집합을 사용하면 AEC APO가 오디오 엔진과 같이 자신을 식별할 수 있습니다. 이렇게 하면 오디오 엔진이 여러 입력 및 단일 출력으로 APO를 적절하게 구성합니다.

APO에서 새 AEC 인터페이스를 구현하는 경우 오디오 엔진은 다음을 수행합니다.

  • APO에 적절한 렌더링 엔드포인트의 참조 스트림을 제공하는 추가 입력으로 APO를 구성합니다.
  • 렌더링 디바이스가 변경됨에 따라 참조 스트림을 전환합니다.
  • APO가 입력 마이크 및 참조 스트림의 형식을 제어하도록 허용합니다.
  • APO가 마이크에 타임스탬프를 가져오고 스트림을 참조하도록 허용합니다.

이전 방법 - Windows 10

API는 단일 입력- 단일 출력 개체입니다. 오디오 엔진은 입력 시 마이크 엔드포인트의 오디오를 AEC APO에 제공합니다. 참조 스트림을 가져오기 위해 APO는 독점 인터페이스를 사용하여 드라이버와 상호 작용하여 렌더링 엔드포인트에서 참조 오디오를 검색하거나 WASAPI를 사용하여 렌더링 엔드포인트에서 루프백 스트림을 열 수 있습니다.

위의 두 방법 모두 단점이 있습니다.

  • 프라이빗 채널을 사용하여 드라이버에서 참조 스트림을 가져오는 AEC APO는 일반적으로 통합 오디오 렌더링 디바이스에서만 수행할 수 있습니다. 따라서 사용자가 USB 또는 Bluetooth 오디오 장치와 같은 통합되지 않은 장치에서 오디오를 재생하는 경우 에코 취소가 작동하지 않습니다. OS만 참조 엔드포인트로 사용할 수 있는 올바른 렌더링 엔드포인트를 알고 있습니다.

  • APO는 WASAPI를 사용하여 에코 취소를 수행하기 위해 기본 렌더링 엔드포인트를 선택할 수 있습니다. 그러나 audiodg.exe 프로세스(APO가 호스트되는 위치)에서 루프백 스트림을 열 때 알아야 할 몇 가지 문제가 있습니다.

    • 오디오 엔진이 기본 APO 메서드를 호출할 때 루프백 스트림을 열거나 제거할 수 없습니다. 이로 인해 교착 상태가 발생할 수 있습니다.
    • 캡처 APO는 해당 클라이언트 스트림의 상태를 알 수 없습니다. 즉, 캡처 앱에 캡처 스트림이 'STOP' 상태일 수 있지만 APO는 이 상태를 인식하지 못하므로 루프백 스트림을 'RUN' 상태로 열어 두므로 전력 소비 측면에서 비효율적입니다.

API 정의 - AEC

AEC 프레임워크는 API가 활용할 수 있는 새로운 구조와 인터페이스를 제공합니다. 이러한 새로운 구조 및 인터페이스는 아래에 설명되어 있습니다.

APO_CONNECTION_PROPERTY_V2 구조체

IApoAcousticEchoCancellation 인터페이스를 구현하는 API는 IAudioProcessingObjectRT::APOProcess에 대한 호출에서 APO_CONNECTION_PROPERTY_V2 구조체를 전달합니다. 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는 IAudioProcessingobjectRT::APOProcess대한 호출에서 APO_CONNECTION_PROPERTY_V2 구조를 제공합니다. APO는 연결 속성의 APO_CONNECTION_PROPERTY_V2_SIGNATURE 서명에 대해 검사 들어오는 APO_CONNECTION_PROPERTY 구조를 APO_CONNECTION_PROPERTY_V2 구조체에 형식 캐스팅할 수 있습니다.

AEC APO는 일반적으로 특정 샘플링 속도/채널 수로 알고리즘을 실행한다는 사실을 인식하여 IApoAcousticEchoCancellation 인터페이스를 구현하는 API에 대한 리샘플링 지원을 제공합니다.

AEC APO가 IAudioProcessingObject::OutInputFormatSupported 호출에서 APOERR_FORMAT_NOT_SUPPORTED 반환하면 오디오 엔진은 NULL 출력 형식 및 null이 아닌 입력 형식으로 APO에서 IAudioProcessingObject::IsInputFormatSupported를 다시 호출하여 APO의 제안된 형식을 가져옵니다. 그런 다음 오디오 엔진은 마이크 오디오를 AEC APO로 보내기 전에 제안된 형식으로 다시 샘플링합니다. 이렇게 하면 AEC APO가 샘플링 속도 및 채널 개수 변환을 구현할 필요가 없습니다.

IApoAuxiliaryInputConfiguration

IApoAuxiliaryInputConfiguration 인터페이스는 오디오 엔진이 보조 입력 스트림을 추가하고 제거할 수 있도록 API가 구현할 수 있는 메서드를 제공합니다.

이 인터페이스는 AEC APO에서 구현되며 오디오 엔진에서 참조 입력을 초기화하는 데 사용됩니다. Windows 11에서 AEC APO는 단일 보조 입력(에코 취소에 대한 참조 오디오 스트림이 있는 입력)으로만 초기화됩니다. AddAuxiliaryInput 메서드는 APO에 참조 입력을 추가하는 데 사용됩니다. 초기화 매개 변수에는 루프백 스트림을 가져오는 렌더링 엔드포인트에 대한 참조가 포함됩니다.

IsInputFormatSupported 메서드는 보조 입력에 대한 형식을 협상하기 위해 오디오 엔진에서 호출됩니다. AEC APO가 특정 형식을 선호하는 경우 IsInputFormatSupported 호출에서 S_FALSE 반환하고 제안된 형식을 지정할 수 있습니다. 오디오 엔진은 참조 오디오를 제안된 형식으로 다시 샘플링하고 AEC APO의 보조 입력에서 제공합니다.

IApoAuxiliaryInputRT

IApoAuxiliaryInputRT 인터페이스는 APO의 보조 입력을 구동하는 데 사용되는 실시간 안전 인터페이스입니다.

이 인터페이스는 보조 입력에 대한 오디오 데이터를 APO에 제공하는 데 사용됩니다. 보조 오디오 입력은 IAudioProcessingObjectRT::APOProcess에 대한 호출과 동기화되지 않습니다. 렌더링 엔드포인트에서 렌더링되는 오디오가 없으면 보조 입력에서 루프백 데이터를 사용할 수 없습니다. 즉, IApoAuxiliaryInputRT::AcceptInput에 대한 호출이 없습니다.

AEC CAPX API 요약

자세한 내용은 다음 페이지에서 추가 정보를 찾습니다.

샘플 코드 - 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 APO가 보조 입력을 하나만 처리할 수 있는 경우 AddAuxiliaryInput의 구현을 보여줍니다.

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 주는 샘플 코드를 검토합니다APO_CONNECTION_PROPERTY_V2.CAecApoMFX::AcceptInput

작업 시퀀스 - 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 호출에서 얻은 버퍼는 기본 스레드를 잠그지 않고 순환 버퍼에 기록되어야 합니다.
  • IAudioProcessingObjectRT::APOProcess 호출 시 참조 스트림의 최신 오디오 패킷에 대한 순환 버퍼를 읽어야 하며 이 패킷은 에코 취소 알고리즘을 통해 실행하는 데 사용해야 합니다.
  • 참조 및 마이크 데이터의 타임스탬프를 사용하여 스피커 및 마이크 데이터를 정렬할 수 있습니다.

참조 루프백 스트림

기본적으로 루프백 스트림은 볼륨 또는 음소거가 적용되기 전에 오디오 스트림을 "탭"(수신 대기)합니다. 볼륨이 적용되기 전에 탭한 루프백 스트림을 사전 볼륨 루프백 스트림이라고 합니다. 볼륨 전 루프백 스트림을 사용하는 장점은 현재 볼륨 설정에 관계없이 명확하고 균일한 오디오 스트림입니다.

일부 AEC 알고리즘은 볼륨 처리(음소거 포함) 후에 연결된 루프백 스트림을 가져오는 것을 선호할 수 있습니다. 이 구성을 포스트 볼륨 루프백이라고 합니다.

다음 주 버전의 Windows AEC AEC API에서는 지원되는 엔드포인트에서 볼륨 후 루프백을 요청할 수 있습니다.

제한 사항

모든 렌더링 엔드포인트에 사용할 수 있는 사전 볼륨 루프백 스트림과 달리 볼륨 후 루프백 스트림은 모든 엔드포인트에서 사용할 수 없습니다.

볼륨 후 루프백 요청

포스트 볼륨 루프백을 사용하려는 AEC AEC API는 IApoAcousticEchoCancellation2 인터페이스를 구현해야 합니다.

AEC APO는 IApoAcousticEchoCancellation2::GetDesiredReferenceStreamProperties 구현에서 Properties 매개 변수를 통해 APO_REFERENCE_STREAM_PROPERTIES_POST_VOLUME_LOOPBACK 플래그를 반환하여 볼륨 후 루프백을 요청할 수 있습니다.

현재 사용 중인 렌더링 엔드포인트에 따라 볼륨 후 루프백을 사용할 수 없습니다. AEC APO는 IApoAuxiliaryInputConfiguration::AddAuxiliaryInput 메서드가 호출될 때 볼륨 후 루프백이 사용되는 경우 알림을 받습니다. AcousticEchoCanceller_Reference_Input streamProperties 필드에 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.

설정 Framework

설정 Framework를 사용하면 API가 오디오 엔드포인트에서 오디오 효과("FX 속성 저장소")에 대한 속성 저장소를 쿼리하고 수정하는 메서드를 노출할 수 있습니다. 이 프레임워크는 APO 및 해당 APO에 설정을 전달하려는 HSA(하드웨어 지원 앱)에서 사용할 수 있습니다. HSA는 UWP(유니버설 Windows 플랫폼) 앱일 수 있으며 설정 Framework에서 API를 호출하는 특별한 기능이 필요합니다. HSA 앱에 대한 자세한 내용은 UWP 디바이스 앱을 참조 하세요.

FxProperty Store 구조체

새 FxProperty 저장소에는 기본, 사용자 및 Volatile라는 세 개의 하위 저장소가 있습니다.

"기본" 하위 키는 사용자 지정 효과 속성을 포함하며 INF 파일에서 채워집니다. 이러한 속성은 OS 업그레이드에서 유지되지 않습니다. 예를 들어 일반적으로 INF에 정의된 속성은 여기에 적합합니다. 그러면 INF에서 다시 채워집니다.

"사용자" 하위 키에는 효과 속성과 관련된 사용자 설정이 포함되어 있습니다. 이러한 설정은 업그레이드 및 마이그레이션에서 OS에 의해 유지됩니다. 예를 들어 사용자가 구성할 수 있는 모든 사전 설정은 업그레이드 간에 유지되어야 합니다.

"Volatile" 하위 키에는 휘발성 효과 속성이 포함되어 있습니다. 이러한 속성은 디바이스를 다시 부팅할 때 손실되며 엔드포인트가 활성으로 전환될 때마다 지워집니다. 여기에는 시간 변형 속성(예: 현재 실행 중인 애플리케이션, 디바이스 상태 등에 따라)이 포함될 것으로 예상됩니다. 예를 들어 현재 환경에 종속된 모든 설정입니다.

사용자와 기본값을 생각하는 방법은 OS 및 드라이버 업그레이드에서 속성을 유지할지 여부입니다. 사용자 속성은 유지됩니다. 기본 속성은 INF에서 다시 채워집니다.

APO 컨텍스트

CAPX 설정 framwork를 사용하면 APO 작성자가 컨텍스트별로 APO 속성을 그룹화할 수 있습니다. 각 APO는 자체 컨텍스트를 정의하고 자체 컨텍스트를 기준으로 속성을 업데이트할 수 있습니다. 오디오 엔드포인트에 대한 effects 속성 저장소에는 0개 이상의 컨텍스트가 있을 수 있습니다. 공급업체는 SFX/MFX/EFX 또는 모드에 따라 원하는 컨텍스트를 자유롭게 만들 수 있습니다. 공급업체는 해당 공급업체에서 제공하는 모든 API에 대한 단일 컨텍스트를 선택할 수도 있습니다.

제한된 기능 설정

설정 API는 오디오 디바이스와 연결된 오디오 효과 설정을 쿼리하고 수정하는 데 관심이 있는 모든 OEM 및 HSA 개발자를 지원하기 위한 것입니다. 이 API는 매니페스트에서 선언해야 하는 제한된 기능 "audioDeviceConfiguration"을 통해 속성 저장소에 대한 액세스를 제공하기 위해 HSA 및 Win32 애플리케이션에 노출됩니다. 또한 다음과 같이 해당 네임스페이스를 선언해야 합니다.

<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 정의 - 설정 Framework

IAudioSystemEffectsPropertyStore 인터페이스를 사용하면 HSA가 오디오 시스템 효과 속성 저장소에 액세스하고 속성 변경 알림을 등록할 수 있습니다.

ActiveAudioInterfaceAsync 함수는 IAudioSystemEffectsPropertyStore 인터페이스를 비동기적으로 가져오는 메서드를 제공합니다.

앱은 새 IAudioSystemEffectsPropertyChangeNotificationClient 콜백 인터페이스를 사용하여 시스템 효과 속성 저장소가 변경될 때 알림을 받을 수 있습니다.

IMMDevice::Activate를 사용하여 IAudioSystemEffectsPropertyStore를 가져오는 애플리케이션

이 샘플에서는 하드웨어 지원 앱이 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를 사용하는 대신 ActivateAudioInterfaceAsync API를 사용하여 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::IAudioSystemEffectsPropertyStore를 사용하여 코드 초기화

이 샘플에서는 APO를 초기화하는 동안 APO 구현에서 APOInitSystemEffects3 구조를 사용하여 APO에 대한 기본 및 휘발성 IPropertyStore 인터페이스를 검색할 수 있음을 보여 줍니다.

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

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

샘플 코드 - 설정 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 섹션 - 설정 Framework

새 CAPX 설정 프레임워크를 사용하여 효과 속성을 선언하는 INF 파일 구문은 다음과 같습니다.

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

Notifications Framework

알림 프레임워크를 사용하면 API(오디오 효과)가 볼륨, 엔드포인트 및 오디오 효과 속성 저장소 변경 알림을 요청하고 처리할 수 있습니다. 이 프레임워크는 API가 알림을 등록 및 등록 취소하는 데 사용하는 기존 API를 대체하기 위한 것입니다.

새 API는 APO가 APO에 관심이 있는 알림 유형을 선언하는 데 활용할 수 있는 인터페이스를 도입합니다. Windows는 관심 있는 알림을 APO에 쿼리하고 APO에 알림을 전달합니다. API는 더 이상 등록 또는 등록 취소 API를 명시적으로 호출할 필요가 없습니다.

알림은 직렬 큐를 사용하여 APO에 전달됩니다. 해당하는 경우 첫 번째 알림은 요청된 값(예: 오디오 엔드포인트 볼륨)의 초기 상태를 브로드캐스트합니다. 스트리밍에 APO를 사용하려는 audiodg.exe 중지되면 알림이 중지됩니다. API는 UnlockForProcess 이후 알림 수신을 중지합니다. UnlockForProcess 및 모든 진행 중인 알림을 동기화해야 합니다.

구현 - Notifications Framework

알림 프레임워크를 활용하기 위해 APO는 관심 있는 알림을 선언합니다. 명시적 등록/등록 취소 호출이 없습니다. APO에 대한 모든 알림은 직렬화되며 알림 콜백 스레드를 너무 오랫동안 차단하지 않는 것이 중요합니다.

API 정의 - Notifications Framework

알림 프레임워크는 APO 엔드포인트 및 시스템 효과 알림에 대한 일반적인 오디오 관련 알림을 등록하고 수신하기 위해 클라이언트에서 구현할 수 있는 새 IAudioProcessingObjectNotifications 인터페이스를 구현합니다.

자세한 내용은 다음 페이지에서 추가 콘텐츠를 찾습니다.

샘플 코드 - Notifications Framework

이 샘플에서는 APO가 IAudioProcessingObjectNotifications 인터페이스를 구현하는 방법을 보여 줍니다. GetApoNotificationRegistrationInfo 메서드에서 샘플 APO는 시스템 효과 속성 저장소의 변경 내용에 대한 알림을 등록합니다.
HandleNotification 메서드는 APO가 등록한 내용과 일치하는 변경 내용을 APO에 알리기 위해 OS에서 호출합니다.

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를 제공하여 OS에서 수행할 작업의 재기본더를 남깁니다.

공급자는 다음과 같이 정의됩니다.

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에는 자체 활동 ID가 있습니다. 기존 추적 로깅 메커니즘을 사용하므로 기존 콘솔 도구를 사용하여 이러한 이벤트를 필터링하고 실시간으로 표시할 수 있습니다. 소프트웨어 추적 도구 - Windows 드라이버에 설명된 대로 tracelog 및 tracefmt와 같은 기존 도구를 사용할 수 있습니다. 추적 세션에 대한 자세한 내용은 컨트롤 GUID를 사용하여 추적 세션 만들기를 참조 하세요.

추적 로깅 이벤트는 원격 분석으로 표시되지 않으며 xperf와 같은 도구에서 원격 분석 공급자로 표시되지 않습니다.

구현 - 로깅 프레임워크

로깅 프레임워크는 ETW 추적에서 제공하는 로깅 메커니즘을 기반으로 합니다. ETW에 대한 자세한 내용은 이벤트 추적을 참조하세요. 이는 오디오 데이터를 로깅하는 것이 아니라 일반적으로 프로덕션에 기록되는 이벤트를 기록하기 위한 것입니다. 로깅 API는 OS CPU 스케줄러에 의해 펌프 스레드가 선점될 가능성이 있으므로 실시간 스트리밍 스레드에서 사용해서는 안 됩니다. 로깅은 주로 필드에서 자주 발견되는 문제를 디버깅하는 데 도움이 되는 이벤트에 사용해야 합니다.

API 정의 - 로깅 프레임워크

로깅 프레임워크는 API에 대한 새 로깅 서비스를 제공하는 IAudioProcessingObjectLoggingService 인터페이스를 도입했습니다.

자세한 내용은 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;
}

스레딩 프레임워크

스레딩 프레임워크는 간단한 API를 통해 적절한 MMCSS(멀티미디어 클래스 스케줄러 서비스) 작업의 작업 큐를 사용하여 효과를 다중 스레드할 수 있도록 합니다. 실시간 직렬 작업 큐 만들기 및 기본 펌프 스레드와의 연결은 OS에서 처리됩니다. 이 프레임워크를 사용하면 API가 짧은 실행 작업 항목을 큐에 대기할 수 있습니다. 작업 간의 동기화는 APO의 책임입니다. MMCSS 스레딩에 대한 자세한 내용은 멀티미디어 클래스 스케줄러 서비스실시간 작업 큐 API를 참조하세요.

API 정의 - 스레딩 프레임워크

스레딩 프레임워크는 API에 대한 실시간 작업 큐에 대한 액세스를 제공하는 IAudioProcessingObjectQueueService 인터페이스를 도입했습니다.

자세한 내용은 다음 페이지에서 추가 콘텐츠를 찾습니다.

샘플 코드 - 스레딩 프레임워크

이 샘플에서는 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;
}

이 인터페이스를 활용하는 방법에 대한 자세한 예제는 다음 샘플 코드를 참조하세요.

효과에 대한 오디오 효과 검색 및 컨트롤

검색 프레임워크를 사용하면 OS가 스트림에 대한 오디오 효과를 제어할 수 있습니다. 이러한 API는 애플리케이션 사용자가 스트림에 대한 특정 효과(예: 심층 노이즈 억제)를 제어해야 하는 시나리오를 지원합니다. 이를 위해 이 프레임워크는 다음을 추가합니다.

  • 오디오 효과를 사용하거나 사용하지 않도록 설정할 수 있는지 여부를 확인하기 위해 APO에서 쿼리할 새 API입니다.
  • 오디오 효과의 상태를 켜기/끄기로 설정하는 새 API입니다.
  • 오디오 효과 목록에 변경 내용이 있거나 오디오 효과를 사용하거나 사용하지 않도록 설정할 수 있도록 리소스를 사용할 수 있게 될 때 알림입니다.

구현 - 오디오 효과 검색

APO는 동적으로 사용하도록 설정하고 사용하지 않도록 설정할 수 있는 효과를 노출하려는 경우 IAudioSystemEffects3 인터페이스를 구현해야 합니다. APO는 IAudioSystemEffects3::GetControllableSystemEffectsList 함수를 통해 오디오 효과를 노출하고 IAudioSystemEffects3::SetAudioSystemEffectState 함수를 통해 오디오 효과를 사용하도록 설정하고 사용하지 않도록 설정합니다.

샘플 코드 - 오디오 효과 검색

오디오 효과 검색 샘플 코드는 Swapaposfx.cpp SwapAPOSFX 샘플 내에서 찾을 수 있습니다.

다음 샘플 코드에서는 구성 가능한 효과 목록을 검색하는 방법을 보여 줍니다. 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;
}

Windows 11 버전 22H2에서 WM SFX 및 MFX API 재사용

Windows 11 버전 22H2부터 받은 편지함 WM SFX 및 MFX API를 다시 사용하는 INF 구성 파일을 이제 CAPX SFX 및 MFX API를 다시 사용할 수 있습니다. 이 섹션에서는 이 작업을 수행하는 세 가지 방법을 설명합니다.

API에는 사전 혼합 렌더링, 혼합 후 렌더링 및 캡처의 세 가지 삽입 지점이 있습니다. 각 논리 디바이스의 오디오 엔진은 스트림당 하나의 사전 혼합 렌더링 APO 인스턴스(SFX 렌더링) 및 하나의 MFX(혼합 후 렌더링 APO) 인스턴스를 지원합니다. 또한 오디오 엔진은 각 캡처 스트림에 삽입되는 캡처 APO(캡처 SFX)의 인스턴스 하나를 지원합니다. 받은 편지함 API를 다시 사용하거나 래핑하는 방법에 대한 자세한 내용은 사용자 지정 및 Windows API 결합을 참조 하세요.

CAPX SFX 및 MFX API는 다음 세 가지 방법 중 하나로 재사용할 수 있습니다.

INF DDInstall 섹션 사용

mssysfx를 사용합니다. 다음 항목을 추가하여 wdmaudio.inf의 CopyFilesAndRegisterCapX입니다.

   Include=wdmaudio.inf
   Needs=mssysfx.CopyFilesAndRegisterCapX

확장 INF 파일 사용

wdmaudioapo.inf는 AudioProcessingObject 클래스 확장 inf입니다. 여기에는 SFX 및 MFX API의 디바이스별 등록이 포함됩니다.

스트림 및 모드 효과에 대한 WM SFX 및 MFX API 직접 참조

스트림 및 모드 효과에 대해 이러한 API를 직접 참조하려면 다음 GUID 값을 사용합니다.

  • WM SFX APO로 사용 {C9453E73-8C5C-4463-9984-AF8BAB2F5447}
  • WM MFX APO로 사용합니다 {13AB3EBD-137E-4903-9D89-60BE8277FD17} .

SFX(스트림) 및 MFX(모드)는 Windows 8.1에서 LFX(로컬)로 참조되었으며 MFX를 GFX(전역)라고 합니다. 이러한 레지스트리 항목은 이전 이름을 계속 사용합니다.

디바이스별 등록은 HKCR 대신 HKR을 사용합니다.

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 파일 항목은 Windows 11 API에서 새 API에 사용할 속성 저장소를 만듭니다.

INF 예에서 PKEY_FX_Association. HKR,"FX\\0",%PKEY_FX_Association%,,%KSNODETYPE_ANY%를 으로 HKR,"FX\\0\\%WMALFXGFXAPO_Context%",%PKEY_FX_Association%,,%KSNODETYPE_ANY%바꿔야 합니다.

참고 항목

Windows 오디오 처리 개체입니다.