Skip to content

Commit 7cbd99a

Browse files
author
Windows classic samples
committed
May 2022 samples
* WASAPIRendering: Associating a Windows Audio Session API (WASAPI) stream with a window * FakeMenu: Demonstrate new menu theme parts and states * AcousticEchoCancellation: Setting a reference stream for acoustic echo cancellation * EventTracingEnumerateProviders: Enumerating providers with optional filters * CloudMirror: Providing status UI, also addresses #194, #221, #222
1 parent 67a8cdd commit 7cbd99a

69 files changed

Lines changed: 3666 additions & 672 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
page_type: sample
3+
languages:
4+
- cpp
5+
products:
6+
- windows-api-win32
7+
name: Acoustic echo cancellation
8+
urlFragment: AcousticEchoCancellation
9+
description: Set the reference endpoint id for acoustic echo cancellation.
10+
extendedZipContent:
11+
- path: LICENSE
12+
target: LICENSE
13+
---
14+
15+
# Acoustic Echo Cancellation Sample
16+
17+
This sample demonstrates the use of the IAcousticEchoCancellationControl::SetEchoCancellationRenderEndpoint method.
18+
19+
The Acoustic Echo Cancellation APIs provide applications using communications streams the ability to customize
20+
the reference stream of choice to use for echo cancellation. By default, the system uses the default render
21+
device as the reference stream.
22+
23+
The sample demonstrates how an application can check to see if the audio stream provides a way to configure
24+
echo cancellation. If it does, the interface is used to configure the reference endpoint to be used for echo
25+
cancellation on a communication stream by using the reference stream from a specific render endpoint.
26+
27+
To use this sample, select a render endpoint from the list (by number),
28+
and the sample use that render endpoint as the reference stream for echo cancellation.
29+
30+
This sample requires Windows 11 build 22540 or later.
31+
32+
Sample Language Implementations
33+
===============================
34+
C++
35+
36+
To build the sample using the command prompt:
37+
=============================================
38+
39+
1. Open the Command Prompt window and navigate to the directory.
40+
2. Type msbuild [Solution Filename]
41+
42+
To build the sample using Visual Studio (preferred method):
43+
================================================================
44+
45+
1. Open Windows Explorer and navigate to the directory.
46+
2. Double-click the icon for the .sln (solution) file to open the file in Visual Studio.
47+
3. In the Build menu, select Build Solution. The application will be built in the default
48+
\Debug or \Release directory
49+
50+
To run the sample:
51+
=================
52+
Type AcousticEchoCancellation.exe at the command line.
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
#include <windows.h>
2+
#include <wchar.h>
3+
#include <AudioSessionTypes.h>
4+
5+
#include "AECCapture.h"
6+
7+
#if !defined(NTDDI_WIN10_NI) || (NTDDI_VERSION < NTDDI_WIN10_NI)
8+
#error This sample requires SDK version 22540 or higher.
9+
#endif
10+
11+
#define REFTIMES_PER_SEC 10000000
12+
13+
HRESULT CAECCapture::IsAcousticEchoCancellationEffectPresent(bool* result)
14+
{
15+
*result = false;
16+
17+
// IAudioEffectsManager requires build 22000 or higher.
18+
wil::com_ptr_nothrow<IAudioEffectsManager> audioEffectsManager;
19+
HRESULT hr = _audioClient->GetService(IID_PPV_ARGS(&audioEffectsManager));
20+
if (hr == E_NOINTERFACE)
21+
{
22+
// Audio effects manager is not supported, so clearly not present.
23+
return S_OK;
24+
}
25+
26+
wil::unique_cotaskmem_array_ptr<AUDIO_EFFECT> effects;
27+
UINT32 numEffects;
28+
RETURN_IF_FAILED(audioEffectsManager->GetAudioEffects(&effects, &numEffects));
29+
30+
for (UINT32 i = 0; i < numEffects; i++)
31+
{
32+
// Check for acoustic echo cancellation Audio Processing Object (APO)
33+
if (effects[i].id == AUDIO_EFFECT_TYPE_ACOUSTIC_ECHO_CANCELLATION)
34+
{
35+
*result = true;
36+
break;
37+
}
38+
}
39+
return S_OK;
40+
}
41+
42+
HRESULT CAECCapture::InitializeCaptureClient()
43+
{
44+
wil::com_ptr_nothrow<IMMDeviceEnumerator> enumerator;
45+
RETURN_IF_FAILED(::CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&enumerator)));
46+
47+
wil::com_ptr_nothrow<IMMDevice> device;
48+
RETURN_IF_FAILED(enumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &device));
49+
50+
RETURN_IF_FAILED(device->Activate(__uuidof(IAudioClient2), CLSCTX_INPROC_SERVER, NULL, (void**)&_audioClient));
51+
52+
// Set the category as communications.
53+
AudioClientProperties clientProperties = {};
54+
clientProperties.cbSize = sizeof(AudioClientProperties);
55+
clientProperties.eCategory = AudioCategory_Communications;
56+
RETURN_IF_FAILED(_audioClient->SetClientProperties(&clientProperties));
57+
58+
wil::unique_cotaskmem_ptr<WAVEFORMATEX> wfxCapture;
59+
RETURN_IF_FAILED(_audioClient->GetMixFormat(wil::out_param(wfxCapture)));
60+
61+
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
62+
RETURN_IF_FAILED(_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
63+
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
64+
hnsRequestedDuration,
65+
0,
66+
wfxCapture.get(),
67+
NULL));
68+
69+
bool aecEffectPresent;
70+
RETURN_IF_FAILED(IsAcousticEchoCancellationEffectPresent(&aecEffectPresent));
71+
72+
if (!aecEffectPresent)
73+
{
74+
wprintf(L"Warning: Capture stream is not echo cancelled.\n");
75+
76+
// An APO vendor can add code here to insert an in-app
77+
// acoustic echo cancellation APO before starting the capture stream.
78+
}
79+
80+
wil::unique_cotaskmem_string deviceId;
81+
RETURN_IF_FAILED(device->GetId(&deviceId));
82+
wprintf(L"Created communications stream on capture endpoint %ls\n", deviceId.get());
83+
84+
RETURN_IF_FAILED(_audioClient->GetService(IID_PPV_ARGS(&_captureClient)));
85+
86+
return S_OK;
87+
}
88+
89+
HRESULT CAECCapture::RecordCommunicationsStream()
90+
{
91+
DWORD mmcssTaskIndex = 0;
92+
HANDLE mmcssTaskHandle = AvSetMmThreadCharacteristics(L"Audio", &mmcssTaskIndex);
93+
RETURN_HR_IF(HRESULT_FROM_WIN32(GetLastError()), mmcssTaskHandle == 0);
94+
95+
auto avRevertMmThreadCharacteristicsOnExit = wil::scope_exit([&]()
96+
{
97+
AvRevertMmThreadCharacteristics(mmcssTaskHandle);
98+
});
99+
100+
wil::unique_event_nothrow bufferComplete;
101+
RETURN_IF_FAILED(bufferComplete.create());
102+
RETURN_IF_FAILED(_audioClient->SetEventHandle(bufferComplete.get()));
103+
104+
RETURN_IF_FAILED(_audioClient->Start());
105+
106+
wprintf(L"Started communications capture stream.\n");
107+
108+
HANDLE events[] = { _terminationEvent.get(), bufferComplete.get() };
109+
110+
while (WAIT_OBJECT_0 != WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE, INFINITE))
111+
{
112+
UINT32 packetLength = 0;
113+
while (SUCCEEDED(_captureClient->GetNextPacketSize(&packetLength)) && packetLength > 0)
114+
{
115+
PBYTE buffer;
116+
UINT32 numFramesRead;
117+
DWORD flags = 0;
118+
RETURN_IF_FAILED(_captureClient->GetBuffer(&buffer, &numFramesRead, &flags, nullptr, nullptr));
119+
120+
// At this point, the app can send the buffer to the capture pipeline.
121+
// This program just discards the buffer without processing it.
122+
123+
RETURN_IF_FAILED(_captureClient->ReleaseBuffer(numFramesRead));
124+
}
125+
}
126+
127+
RETURN_IF_FAILED(_audioClient->Stop());
128+
129+
return S_OK;
130+
}
131+
132+
HRESULT CAECCapture::StartCapture() try
133+
{
134+
RETURN_IF_FAILED(_terminationEvent.create());
135+
136+
RETURN_IF_FAILED(InitializeCaptureClient());
137+
138+
_captureThread = std::thread(
139+
[this]()
140+
{
141+
RecordCommunicationsStream();
142+
});
143+
144+
return S_OK;
145+
} CATCH_RETURN()
146+
147+
HRESULT CAECCapture::StopCapture()
148+
{
149+
_terminationEvent.SetEvent();
150+
_captureThread.join();
151+
return S_OK;
152+
}
153+
154+
HRESULT CAECCapture::SetEchoCancellationRenderEndpoint(PCWSTR aecReferenceEndpointId)
155+
{
156+
wil::com_ptr_nothrow<IAcousticEchoCancellationControl> aecControl;
157+
HRESULT hr = _audioClient->GetService(IID_PPV_ARGS(&aecControl));
158+
159+
if (hr == E_NOINTERFACE)
160+
{
161+
// For this app, we ignore any failure to to control acoustic echo cancellation.
162+
// (Treat as best effort.)
163+
wprintf(L"Warning: Acoustic echo cancellation control is not available.\n");
164+
return S_OK;
165+
}
166+
167+
RETURN_IF_FAILED_MSG(hr, "_audioClient->GetService(IID_PPV_ARGS(&aecControl))");
168+
169+
// Call SetEchoCancellationRenderEndpoint to change the endpoint of the auxiliary input stream.
170+
RETURN_IF_FAILED(aecControl->SetEchoCancellationRenderEndpoint(aecReferenceEndpointId));
171+
172+
return S_OK;
173+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#pragma once
2+
3+
#include <AudioClient.h>
4+
#include <mmdeviceapi.h>
5+
#include <initguid.h>
6+
#include <guiddef.h>
7+
#include <mfapi.h>
8+
9+
#include <wrl\implements.h>
10+
#include <wil\com.h>
11+
#include <wil\result.h>
12+
13+
#include <thread>
14+
15+
class CAECCapture :
16+
public Microsoft::WRL::RuntimeClass< Microsoft::WRL::RuntimeClassFlags< Microsoft::WRL::ClassicCom >, Microsoft::WRL::FtmBase >
17+
{
18+
public:
19+
HRESULT StartCapture();
20+
HRESULT StopCapture();
21+
22+
HRESULT SetEchoCancellationRenderEndpoint(PCWSTR aecReferenceEndpointId);
23+
24+
private:
25+
HRESULT InitializeCaptureClient();
26+
HRESULT IsAcousticEchoCancellationEffectPresent(bool* result);
27+
HRESULT RecordCommunicationsStream();
28+
29+
std::thread _captureThread;
30+
wil::com_ptr_nothrow<IAudioCaptureClient> _captureClient;
31+
wil::com_ptr_nothrow<IAudioClient2> _audioClient;
32+
33+
wil::unique_event_nothrow _terminationEvent;
34+
};
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include <Windows.h>
2+
#include <propkeydef.h>
3+
#include <Functiondiscoverykeys_devpkey.h>
4+
#include "AECCapture.h"
5+
6+
HRESULT GetRenderEndpointId(PWSTR* endpointId)
7+
{
8+
*endpointId = nullptr;
9+
10+
wil::com_ptr_nothrow<IMMDeviceEnumerator> deviceEnumerator;
11+
RETURN_IF_FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC, __uuidof(IMMDeviceEnumerator), (void**)&deviceEnumerator));
12+
13+
wil::com_ptr_nothrow<IMMDeviceCollection> spDeviceCollection;
14+
RETURN_IF_FAILED(deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &spDeviceCollection));
15+
16+
UINT deviceCount;
17+
RETURN_IF_FAILED(spDeviceCollection->GetCount(&deviceCount));
18+
19+
wprintf(L"0: system default\n");
20+
21+
for (UINT i = 0; i < deviceCount; i++)
22+
{
23+
// Get the device from the collection.
24+
wil::com_ptr_nothrow<IMMDevice> device;
25+
RETURN_IF_FAILED(spDeviceCollection->Item(i, &device));
26+
27+
// Get the device friendly name.
28+
wil::com_ptr_nothrow<IPropertyStore> properties;
29+
RETURN_IF_FAILED(device->OpenPropertyStore(STGM_READ, &properties));
30+
wil::unique_prop_variant variant;
31+
RETURN_IF_FAILED(properties->GetValue(PKEY_Device_FriendlyName, &variant));
32+
33+
wprintf(L"%d: %ls\n", i + 1, variant.pwszVal);
34+
}
35+
36+
wprintf(L"Choose a device to use as the acoustic echo cancellation render endpoint: ");
37+
fflush(stdout);
38+
39+
UINT index;
40+
if (wscanf_s(L"%u", &index) != 1)
41+
{
42+
return HRESULT_FROM_WIN32(ERROR_CANCELLED);
43+
}
44+
if (index == 0)
45+
{
46+
// nullptr means "use the system default"
47+
*endpointId = nullptr;
48+
return S_OK;
49+
}
50+
51+
// Convert from 1-based index to 0-based index.
52+
index = index - 1;
53+
54+
if (index > deviceCount)
55+
{
56+
wprintf(L"Invalid choice.\n");
57+
return HRESULT_FROM_WIN32(ERROR_CANCELLED);
58+
}
59+
60+
// Get the chosen device from the collection.
61+
wil::com_ptr_nothrow<IMMDevice> device;
62+
RETURN_IF_FAILED(spDeviceCollection->Item(index, &device));
63+
64+
// Get and return the endpoint ID for that device.
65+
RETURN_IF_FAILED(device->GetId(endpointId));
66+
67+
return S_OK;
68+
}
69+
70+
int wmain(int argc, wchar_t* argv[])
71+
{
72+
// Print diagnostic messages to the console for developer convenience.
73+
wil::SetResultLoggingCallback([](wil::FailureInfo const& failure) noexcept
74+
{
75+
wchar_t message[1024];
76+
wil::GetFailureLogString(message, ARRAYSIZE(message), failure);
77+
wprintf(L"Diagnostic message: %ls\n", message);
78+
});
79+
80+
81+
RETURN_IF_FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED));
82+
wil::unique_couninitialize_call uninitialize;
83+
84+
wil::unique_cotaskmem_string endpointId;
85+
RETURN_IF_FAILED(GetRenderEndpointId(&endpointId));
86+
87+
CAECCapture aecCapture;
88+
RETURN_IF_FAILED(aecCapture.StartCapture());
89+
90+
// Make sure we Stop capture even if an error occurs.
91+
auto stop = wil::scope_exit([&]
92+
{
93+
aecCapture.StopCapture();
94+
});
95+
96+
RETURN_IF_FAILED(aecCapture.SetEchoCancellationRenderEndpoint(endpointId.get()));
97+
98+
// Capture for 10 seconds.
99+
wprintf(L"Capturing for 10 seconds...\n");
100+
Sleep(10000);
101+
wprintf(L"Finished.\n");
102+
103+
return 0;
104+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.30204.135
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AcousticEchoCancellation", "AcousticEchoCancellation.vcxproj", "{6E745655-513E-4713-B3AB-D6D3F62D7734}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|x64 = Debug|x64
11+
Debug|x86 = Debug|x86
12+
Release|x64 = Release|x64
13+
Release|x86 = Release|x86
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x64.ActiveCfg = Debug|x64
17+
{6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x64.Build.0 = Debug|x64
18+
{6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x86.ActiveCfg = Debug|Win32
19+
{6E745655-513E-4713-B3AB-D6D3F62D7734}.Debug|x86.Build.0 = Debug|Win32
20+
{6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x64.ActiveCfg = Release|x64
21+
{6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x64.Build.0 = Release|x64
22+
{6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x86.ActiveCfg = Release|Win32
23+
{6E745655-513E-4713-B3AB-D6D3F62D7734}.Release|x86.Build.0 = Release|Win32
24+
EndGlobalSection
25+
GlobalSection(SolutionProperties) = preSolution
26+
HideSolutionNode = FALSE
27+
EndGlobalSection
28+
GlobalSection(ExtensibilityGlobals) = postSolution
29+
SolutionGuid = {64406148-8C03-42D2-8A2A-C14A03CFF8E3}
30+
EndGlobalSection
31+
EndGlobal

0 commit comments

Comments
 (0)