2025-11-28 00:35:46 +09:00

174 lines
5.6 KiB
C++

#include <windows.h>
#include <wchar.h>
#include <AudioSessionTypes.h>
#include "AECCapture.h"
#if !defined(NTDDI_WIN10_NI) || (NTDDI_VERSION < NTDDI_WIN10_NI)
#error This sample requires SDK version 22540 or higher.
#endif
#define REFTIMES_PER_SEC 10000000
HRESULT CAECCapture::IsAcousticEchoCancellationEffectPresent(bool* result)
{
*result = false;
// IAudioEffectsManager requires build 22000 or higher.
wil::com_ptr_nothrow<IAudioEffectsManager> audioEffectsManager;
HRESULT hr = _audioClient->GetService(IID_PPV_ARGS(&audioEffectsManager));
if (hr == E_NOINTERFACE)
{
// Audio effects manager is not supported, so clearly not present.
return S_OK;
}
wil::unique_cotaskmem_array_ptr<AUDIO_EFFECT> effects;
UINT32 numEffects;
RETURN_IF_FAILED(audioEffectsManager->GetAudioEffects(&effects, &numEffects));
for (UINT32 i = 0; i < numEffects; i++)
{
// Check for acoustic echo cancellation Audio Processing Object (APO)
if (effects[i].id == AUDIO_EFFECT_TYPE_ACOUSTIC_ECHO_CANCELLATION)
{
*result = true;
break;
}
}
return S_OK;
}
HRESULT CAECCapture::InitializeCaptureClient()
{
wil::com_ptr_nothrow<IMMDeviceEnumerator> enumerator;
RETURN_IF_FAILED(::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&enumerator)));
wil::com_ptr_nothrow<IMMDevice> device;
RETURN_IF_FAILED(enumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &device));
RETURN_IF_FAILED(device->Activate(__uuidof(IAudioClient2), CLSCTX_INPROC_SERVER, NULL, (void**)&_audioClient));
// Set the category as communications.
AudioClientProperties clientProperties = {};
clientProperties.cbSize = sizeof(AudioClientProperties);
clientProperties.eCategory = AudioCategory_Communications;
RETURN_IF_FAILED(_audioClient->SetClientProperties(&clientProperties));
wil::unique_cotaskmem_ptr<WAVEFORMATEX> wfxCapture;
RETURN_IF_FAILED(_audioClient->GetMixFormat(wil::out_param(wfxCapture)));
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
RETURN_IF_FAILED(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
hnsRequestedDuration,
0,
wfxCapture.get(),
NULL));
bool aecEffectPresent;
RETURN_IF_FAILED(IsAcousticEchoCancellationEffectPresent(&aecEffectPresent));
if (!aecEffectPresent)
{
wprintf(L"Warning: Capture stream is not echo cancelled.\n");
// An APO vendor can add code here to insert an in-app
// acoustic echo cancellation APO before starting the capture stream.
}
wil::unique_cotaskmem_string deviceId;
RETURN_IF_FAILED(device->GetId(&deviceId));
wprintf(L"Created communications stream on capture endpoint %ls\n", deviceId.get());
RETURN_IF_FAILED(_audioClient->GetService(IID_PPV_ARGS(&_captureClient)));
return S_OK;
}
HRESULT CAECCapture::RecordCommunicationsStream()
{
DWORD mmcssTaskIndex = 0;
HANDLE mmcssTaskHandle = AvSetMmThreadCharacteristics(L"Audio", &mmcssTaskIndex);
RETURN_HR_IF(HRESULT_FROM_WIN32(GetLastError()), mmcssTaskHandle == 0);
auto avRevertMmThreadCharacteristicsOnExit = wil::scope_exit([&]()
{
AvRevertMmThreadCharacteristics(mmcssTaskHandle);
});
wil::unique_event_nothrow bufferComplete;
RETURN_IF_FAILED(bufferComplete.create());
RETURN_IF_FAILED(_audioClient->SetEventHandle(bufferComplete.get()));
RETURN_IF_FAILED(_audioClient->Start());
wprintf(L"Started communications capture stream.\n");
HANDLE events[] = { _terminationEvent.get(), bufferComplete.get() };
while (WAIT_OBJECT_0 != WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE, INFINITE))
{
UINT32 packetLength = 0;
while (SUCCEEDED(_captureClient->GetNextPacketSize(&packetLength)) && packetLength > 0)
{
PBYTE buffer;
UINT32 numFramesRead;
DWORD flags = 0;
RETURN_IF_FAILED(_captureClient->GetBuffer(&buffer, &numFramesRead, &flags, nullptr, nullptr));
// At this point, the app can send the buffer to the capture pipeline.
// This program just discards the buffer without processing it.
RETURN_IF_FAILED(_captureClient->ReleaseBuffer(numFramesRead));
}
}
RETURN_IF_FAILED(_audioClient->Stop());
return S_OK;
}
HRESULT CAECCapture::StartCapture() try
{
RETURN_IF_FAILED(_terminationEvent.create());
RETURN_IF_FAILED(InitializeCaptureClient());
_captureThread = std::thread(
[this]()
{
RecordCommunicationsStream();
});
return S_OK;
} CATCH_RETURN()
HRESULT CAECCapture::StopCapture()
{
_terminationEvent.SetEvent();
_captureThread.join();
return S_OK;
}
HRESULT CAECCapture::SetEchoCancellationRenderEndpoint(PCWSTR aecReferenceEndpointId)
{
wil::com_ptr_nothrow<IAcousticEchoCancellationControl> aecControl;
HRESULT hr = _audioClient->GetService(IID_PPV_ARGS(&aecControl));
if (hr == E_NOINTERFACE)
{
// For this app, we ignore any failure to to control acoustic echo cancellation.
// (Treat as best effort.)
wprintf(L"Warning: Acoustic echo cancellation control is not available.\n");
return S_OK;
}
RETURN_IF_FAILED_MSG(hr, "_audioClient->GetService(IID_PPV_ARGS(&aecControl))");
// Call SetEchoCancellationRenderEndpoint to change the endpoint of the auxiliary input stream.
RETURN_IF_FAILED(aecControl->SetEchoCancellationRenderEndpoint(aecReferenceEndpointId));
return S_OK;
}