1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Handles audio loopback
#include "loopback.hpp"
// ref https://blogs.msdn.microsoft.com/matthew_van_eerde/2008/12/16/sample-wasapi-loopback-capture-record-what-you-hear/
#ifdef WASAPI_LOOPBACK
IAudioCaptureClient *pAudioCaptureClient;
UINT32 foo = 0;
PUINT32 pnFrames = &foo;
UINT32 nBlockAlign = 0;
UINT32 nPasses = 0;
bool bFirstPacket = true;
HRESULT get_default_device(IMMDevice **ppMMDevice) {
HRESULT hr = S_OK;
IMMDeviceEnumerator *pMMDeviceEnumerator;
// activate a device enumerator
hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
__uuidof(IMMDeviceEnumerator),
(void**)&pMMDeviceEnumerator
);
if (FAILED(hr)) {
ERR(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr);
return false;
}
// get the default render endpoint
hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, ppMMDevice);
if (FAILED(hr)) {
ERR(L"IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: hr = 0x%08x", hr);
return false;
}
return S_OK;
}
#endif /** WASAPI_LOOPBACK */
bool initLoopback()
{
#ifdef WASAPI_LOOPBACK
HRESULT hr;
hr = CoInitialize(NULL);
if (FAILED(hr)) {
ERR(L"CoInitialize failed: hr = 0x%08x", hr);
}
IMMDevice *pMMDevice(NULL);
// open default device if not specified
if (NULL == pMMDevice) {
hr = get_default_device(&pMMDevice);
if (FAILED(hr)) {
return false;
}
}
bool bInt16 = false;
// activate an IAudioClient
IAudioClient *pAudioClient;
hr = pMMDevice->Activate(
__uuidof(IAudioClient),
CLSCTX_ALL, NULL,
(void**)&pAudioClient
);
if (FAILED(hr)) {
ERR(L"IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x", hr);
return false;
}
// get the default device periodicity
REFERENCE_TIME hnsDefaultDevicePeriod;
hr = pAudioClient->GetDevicePeriod(&hnsDefaultDevicePeriod, NULL);
if (FAILED(hr)) {
ERR(L"IAudioClient::GetDevicePeriod failed: hr = 0x%08x", hr);
return false;
}
// get the default device format
WAVEFORMATEX *pwfx;
hr = pAudioClient->GetMixFormat(&pwfx);
if (FAILED(hr)) {
ERR(L"IAudioClient::GetMixFormat failed: hr = 0x%08x", hr);
return false;
}
if (bInt16) {
// coerce int-16 wave format
// can do this in-place since we're not changing the size of the format
// also, the engine will auto-convert from float to int for us
switch (pwfx->wFormatTag) {
case WAVE_FORMAT_IEEE_FLOAT:
pwfx->wFormatTag = WAVE_FORMAT_PCM;
pwfx->wBitsPerSample = 16;
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
break;
case WAVE_FORMAT_EXTENSIBLE:
{
// naked scope for case-local variable
PWAVEFORMATEXTENSIBLE pEx = reinterpret_cast<PWAVEFORMATEXTENSIBLE>(pwfx);
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, pEx->SubFormat)) {
pEx->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
pEx->Samples.wValidBitsPerSample = 16;
pwfx->wBitsPerSample = 16;
pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8;
pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;
}
else {
ERR(L"%s", L"Don't know how to coerce mix format to int-16");
return E_UNEXPECTED;
}
}
break;
default:
ERR(L"Don't know how to coerce WAVEFORMATEX with wFormatTag = 0x%08x to int-16", pwfx->wFormatTag);
return E_UNEXPECTED;
}
}
nBlockAlign = pwfx->nBlockAlign;
*pnFrames = 0;
// call IAudioClient::Initialize
// note that AUDCLNT_STREAMFLAGS_LOOPBACK and AUDCLNT_STREAMFLAGS_EVENTCALLBACK
// do not work together...
// the "data ready" event never gets set
// so we're going to do a timer-driven loop
hr = pAudioClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_LOOPBACK,
0, 0, pwfx, 0
);
if (FAILED(hr)) {
ERR(L"pAudioClient->Initialize error");
return false;
}
// activate an IAudioCaptureClient
hr = pAudioClient->GetService(
__uuidof(IAudioCaptureClient),
(void**)&pAudioCaptureClient
);
if (FAILED(hr)) {
ERR(L"pAudioClient->GetService error");
return false;
}
// call IAudioClient::Start
hr = pAudioClient->Start();
if (FAILED(hr)) {
ERR(L"pAudioClient->Start error");
return false;
}
bool bDone = false;
#endif /** WASAPI_LOOPBACK */
return true;
}
void configureLoopback(projectMSDL *app) {
#ifdef WASAPI_LOOPBACK
// Default to WASAPI loopback if it was enabled at compilation.
app->wasapi = true;
// Notify that loopback capture was started.
SDL_Log("Opened audio capture loopback.");
#endif
}
bool processLoopbackFrame(projectMSDL *app) {
#ifdef WASAPI_LOOPBACK
HRESULT hr;
if (app->wasapi) {
// drain data while it is available
nPasses++;
UINT32 nNextPacketSize;
for (
hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize);
SUCCEEDED(hr) && nNextPacketSize > 0;
hr = pAudioCaptureClient->GetNextPacketSize(&nNextPacketSize)
) {
// get the captured data
BYTE *pData;
UINT32 nNumFramesToRead;
DWORD dwFlags;
hr = pAudioCaptureClient->GetBuffer(
&pData,
&nNumFramesToRead,
&dwFlags,
NULL,
NULL
);
if (FAILED(hr)) {
return false;
}
LONG lBytesToWrite = nNumFramesToRead * nBlockAlign;
/** Add the waveform data */
projectm_pcm_add_float(app->projectM(), reinterpret_cast<float*>(pData), nNumFramesToRead, PROJECTM_STEREO);
*pnFrames += nNumFramesToRead;
hr = pAudioCaptureClient->ReleaseBuffer(nNumFramesToRead);
if (FAILED(hr)) {
return false;
}
bFirstPacket = false;
}
if (FAILED(hr)) {
return false;
}
}
#endif /** WASAPI_LOOPBACK */
return true;
}