Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/wasapi/audio_driver_wasapi.cpp
9903 views
1
/**************************************************************************/
2
/* audio_driver_wasapi.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#ifdef WASAPI_ENABLED
32
33
#include "audio_driver_wasapi.h"
34
35
#include "core/config/project_settings.h"
36
#include "core/os/os.h"
37
38
#include <functiondiscoverykeys.h>
39
40
#include <wrl/client.h>
41
using Microsoft::WRL::ComPtr;
42
43
// Define IAudioClient3 if not already defined by MinGW headers
44
#if defined __MINGW32__ || defined __MINGW64__
45
46
#ifndef __IAudioClient3_FWD_DEFINED__
47
#define __IAudioClient3_FWD_DEFINED__
48
49
typedef interface IAudioClient3 IAudioClient3;
50
51
#endif // __IAudioClient3_FWD_DEFINED__
52
53
#ifndef __IAudioClient3_INTERFACE_DEFINED__
54
#define __IAudioClient3_INTERFACE_DEFINED__
55
56
// clang-format off
57
MIDL_INTERFACE("7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42")
58
IAudioClient3 : public IAudioClient2 {
59
public:
60
virtual HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
61
/* [annotation][in] */
62
_In_ const WAVEFORMATEX *pFormat,
63
/* [annotation][out] */
64
_Out_ UINT32 *pDefaultPeriodInFrames,
65
/* [annotation][out] */
66
_Out_ UINT32 *pFundamentalPeriodInFrames,
67
/* [annotation][out] */
68
_Out_ UINT32 *pMinPeriodInFrames,
69
/* [annotation][out] */
70
_Out_ UINT32 *pMaxPeriodInFrames) = 0;
71
72
virtual HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
73
/* [unique][annotation][out] */
74
_Out_ WAVEFORMATEX * *ppFormat,
75
/* [annotation][out] */
76
_Out_ UINT32 * pCurrentPeriodInFrames) = 0;
77
78
virtual HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
79
/* [annotation][in] */
80
_In_ DWORD StreamFlags,
81
/* [annotation][in] */
82
_In_ UINT32 PeriodInFrames,
83
/* [annotation][in] */
84
_In_ const WAVEFORMATEX *pFormat,
85
/* [annotation][in] */
86
_In_opt_ LPCGUID AudioSessionGuid) = 0;
87
};
88
// clang-format on
89
__CRT_UUID_DECL(IAudioClient3, 0x7ED4EE07, 0x8E67, 0x4CD4, 0x8C, 0x1A, 0x2B, 0x7A, 0x59, 0x87, 0xAD, 0x42)
90
91
#endif // __IAudioClient3_INTERFACE_DEFINED__
92
93
#endif // __MINGW32__ || __MINGW64__
94
95
#ifndef PKEY_Device_FriendlyNameGodot
96
97
#undef DEFINE_PROPERTYKEY
98
/* clang-format off */
99
#define DEFINE_PROPERTYKEY(id, a, b, c, d, e, f, g, h, i, j, k, l) \
100
const PROPERTYKEY id = { { a, b, c, { d, e, f, g, h, i, j, k, } }, l };
101
/* clang-format on */
102
103
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyNameGodot, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
104
#endif
105
106
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
107
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
108
const IID IID_IAudioClient = __uuidof(IAudioClient);
109
const IID IID_IAudioClient3 = __uuidof(IAudioClient3);
110
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
111
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
112
113
#define SAFE_RELEASE(memory) \
114
if ((memory) != nullptr) { \
115
(memory)->Release(); \
116
(memory) = nullptr; \
117
}
118
119
#define REFTIMES_PER_SEC 10000000
120
#define REFTIMES_PER_MILLISEC 10000
121
122
#define CAPTURE_BUFFER_CHANNELS 2
123
124
static bool default_output_device_changed = false;
125
static bool default_input_device_changed = false;
126
static int output_reinit_countdown = 0;
127
static int input_reinit_countdown = 0;
128
129
GODOT_GCC_WARNING_PUSH_AND_IGNORE("-Wnon-virtual-dtor") // Silence warning due to a COM API weirdness (GH-35194).
130
131
class CMMNotificationClient : public IMMNotificationClient {
132
LONG _cRef = 1;
133
134
public:
135
ComPtr<IMMDeviceEnumerator> enumerator = nullptr;
136
137
CMMNotificationClient() {}
138
virtual ~CMMNotificationClient() {}
139
140
ULONG STDMETHODCALLTYPE AddRef() {
141
return InterlockedIncrement(&_cRef);
142
}
143
144
ULONG STDMETHODCALLTYPE Release() {
145
ULONG ulRef = InterlockedDecrement(&_cRef);
146
if (0 == ulRef) {
147
delete this;
148
}
149
return ulRef;
150
}
151
152
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface) {
153
if (IID_IUnknown == riid) {
154
AddRef();
155
*ppvInterface = (IUnknown *)this;
156
} else if (__uuidof(IMMNotificationClient) == riid) {
157
AddRef();
158
*ppvInterface = (IMMNotificationClient *)this;
159
} else {
160
*ppvInterface = nullptr;
161
return E_NOINTERFACE;
162
}
163
return S_OK;
164
}
165
166
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId) {
167
return S_OK;
168
}
169
170
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId) {
171
return S_OK;
172
}
173
174
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState) {
175
return S_OK;
176
}
177
178
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId) {
179
if (role == eConsole) {
180
if (flow == eRender) {
181
default_output_device_changed = true;
182
} else if (flow == eCapture) {
183
default_input_device_changed = true;
184
}
185
}
186
187
return S_OK;
188
}
189
190
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key) {
191
return S_OK;
192
}
193
};
194
195
GODOT_GCC_WARNING_POP
196
197
static CMMNotificationClient notif_client;
198
199
Error AudioDriverWASAPI::audio_device_init(AudioDeviceWASAPI *p_device, bool p_input, bool p_reinit, bool p_no_audio_client_3) {
200
// This function can be called recursively, so clean up before starting:
201
audio_device_finish(p_device);
202
203
WAVEFORMATEX *pwfex;
204
ComPtr<IMMDeviceEnumerator> enumerator = nullptr;
205
ComPtr<IMMDevice> output_device = nullptr;
206
207
HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
208
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
209
210
if (p_device->device_name == "Default") {
211
hr = enumerator->GetDefaultAudioEndpoint(p_input ? eCapture : eRender, eConsole, &output_device);
212
} else {
213
ComPtr<IMMDeviceCollection> devices = nullptr;
214
215
hr = enumerator->EnumAudioEndpoints(p_input ? eCapture : eRender, DEVICE_STATE_ACTIVE, &devices);
216
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
217
218
LPWSTR strId = nullptr;
219
bool found = false;
220
221
UINT count = 0;
222
hr = devices->GetCount(&count);
223
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
224
225
for (ULONG i = 0; i < count && !found; i++) {
226
ComPtr<IMMDevice> tmp_device = nullptr;
227
228
hr = devices->Item(i, &tmp_device);
229
ERR_BREAK_MSG(hr != S_OK, "Cannot get devices item.");
230
231
ComPtr<IPropertyStore> props = nullptr;
232
hr = tmp_device->OpenPropertyStore(STGM_READ, &props);
233
ERR_BREAK_MSG(hr != S_OK, "Cannot open property store.");
234
235
PROPVARIANT propvar;
236
PropVariantInit(&propvar);
237
238
hr = props->GetValue(PKEY_Device_FriendlyNameGodot, &propvar);
239
ERR_BREAK_MSG(hr != S_OK, "Cannot get value.");
240
241
if (p_device->device_name == String(propvar.pwszVal)) {
242
hr = tmp_device->GetId(&strId);
243
if (unlikely(hr != S_OK)) {
244
PropVariantClear(&propvar);
245
ERR_PRINT("Cannot get device ID string.");
246
break;
247
}
248
249
found = true;
250
}
251
252
PropVariantClear(&propvar);
253
}
254
255
if (found) {
256
hr = enumerator->GetDevice(strId, &output_device);
257
}
258
259
if (strId) {
260
CoTaskMemFree(strId);
261
}
262
263
if (output_device == nullptr) {
264
hr = enumerator->GetDefaultAudioEndpoint(p_input ? eCapture : eRender, eConsole, &output_device);
265
}
266
}
267
268
if (p_reinit) {
269
// In case we're trying to re-initialize the device, prevent throwing this error on the console,
270
// otherwise if there is currently no device available this will spam the console.
271
if (hr != S_OK) {
272
return ERR_CANT_OPEN;
273
}
274
} else {
275
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
276
}
277
278
if (notif_client.enumerator != nullptr) {
279
notif_client.enumerator->UnregisterEndpointNotificationCallback(&notif_client);
280
notif_client.enumerator = nullptr;
281
}
282
hr = enumerator->RegisterEndpointNotificationCallback(&notif_client);
283
if (hr == S_OK) {
284
notif_client.enumerator = enumerator;
285
} else {
286
ERR_PRINT("WASAPI: RegisterEndpointNotificationCallback error");
287
}
288
289
using_audio_client_3 = !p_input; // IID_IAudioClient3 is only used for adjustable output latency (not input)
290
291
if (p_no_audio_client_3) {
292
using_audio_client_3 = false;
293
}
294
295
if (using_audio_client_3) {
296
hr = output_device->Activate(IID_IAudioClient3, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client);
297
if (hr != S_OK) {
298
// IID_IAudioClient3 will never activate on OS versions before Windows 10.
299
// Older Windows versions should fall back gracefully.
300
using_audio_client_3 = false;
301
print_verbose("WASAPI: Couldn't activate output_device with IAudioClient3 interface, falling back to IAudioClient interface");
302
} else {
303
print_verbose("WASAPI: Activated output_device using IAudioClient3 interface");
304
}
305
}
306
if (!using_audio_client_3) {
307
hr = output_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void **)&p_device->audio_client);
308
}
309
310
if (p_reinit) {
311
if (hr != S_OK) {
312
return ERR_CANT_OPEN;
313
}
314
} else {
315
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
316
}
317
318
if (using_audio_client_3) {
319
AudioClientProperties audioProps{};
320
audioProps.cbSize = sizeof(AudioClientProperties);
321
audioProps.bIsOffload = FALSE;
322
audioProps.eCategory = AudioCategory_GameEffects;
323
324
hr = ((IAudioClient3 *)p_device->audio_client)->SetClientProperties(&audioProps);
325
ERR_FAIL_COND_V_MSG(hr != S_OK, ERR_CANT_OPEN, "WASAPI: SetClientProperties failed with error 0x" + String::num_uint64(hr, 16) + ".");
326
}
327
328
hr = p_device->audio_client->GetMixFormat(&pwfex);
329
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
330
// From this point onward, CoTaskMemFree(pwfex) must be called before returning or pwfex will leak!
331
332
print_verbose("WASAPI: wFormatTag = " + itos(pwfex->wFormatTag));
333
print_verbose("WASAPI: nChannels = " + itos(pwfex->nChannels));
334
print_verbose("WASAPI: nSamplesPerSec = " + itos(pwfex->nSamplesPerSec));
335
print_verbose("WASAPI: nAvgBytesPerSec = " + itos(pwfex->nAvgBytesPerSec));
336
print_verbose("WASAPI: nBlockAlign = " + itos(pwfex->nBlockAlign));
337
print_verbose("WASAPI: wBitsPerSample = " + itos(pwfex->wBitsPerSample));
338
print_verbose("WASAPI: cbSize = " + itos(pwfex->cbSize));
339
340
WAVEFORMATEX *closest = nullptr;
341
hr = p_device->audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, pwfex, &closest);
342
if (hr == S_FALSE) {
343
WARN_PRINT("WASAPI: Mix format is not supported by the output_device");
344
if (closest) {
345
print_verbose("WASAPI: closest->wFormatTag = " + itos(closest->wFormatTag));
346
print_verbose("WASAPI: closest->nChannels = " + itos(closest->nChannels));
347
print_verbose("WASAPI: closest->nSamplesPerSec = " + itos(closest->nSamplesPerSec));
348
print_verbose("WASAPI: closest->nAvgBytesPerSec = " + itos(closest->nAvgBytesPerSec));
349
print_verbose("WASAPI: closest->nBlockAlign = " + itos(closest->nBlockAlign));
350
print_verbose("WASAPI: closest->wBitsPerSample = " + itos(closest->wBitsPerSample));
351
print_verbose("WASAPI: closest->cbSize = " + itos(closest->cbSize));
352
353
WARN_PRINT("WASAPI: Using closest match instead");
354
CoTaskMemFree(pwfex);
355
pwfex = closest;
356
}
357
}
358
359
// Since we're using WASAPI Shared Mode we can't control any of these, we just tag along
360
p_device->channels = pwfex->nChannels;
361
p_device->format_tag = pwfex->wFormatTag;
362
p_device->bits_per_sample = pwfex->wBitsPerSample;
363
p_device->frame_size = (p_device->bits_per_sample / 8) * p_device->channels;
364
365
if (p_device->format_tag == WAVE_FORMAT_EXTENSIBLE) {
366
WAVEFORMATEXTENSIBLE *wfex = (WAVEFORMATEXTENSIBLE *)pwfex;
367
368
if (wfex->SubFormat == KSDATAFORMAT_SUBTYPE_PCM) {
369
p_device->format_tag = WAVE_FORMAT_PCM;
370
} else if (wfex->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) {
371
p_device->format_tag = WAVE_FORMAT_IEEE_FLOAT;
372
} else {
373
ERR_PRINT("WASAPI: Format not supported");
374
CoTaskMemFree(pwfex);
375
ERR_FAIL_V(ERR_CANT_OPEN);
376
}
377
} else {
378
if (p_device->format_tag != WAVE_FORMAT_PCM && p_device->format_tag != WAVE_FORMAT_IEEE_FLOAT) {
379
ERR_PRINT("WASAPI: Format not supported");
380
CoTaskMemFree(pwfex);
381
ERR_FAIL_V(ERR_CANT_OPEN);
382
}
383
}
384
385
if (!using_audio_client_3) {
386
DWORD streamflags = 0;
387
if ((DWORD)mix_rate != pwfex->nSamplesPerSec) {
388
streamflags |= AUDCLNT_STREAMFLAGS_RATEADJUST;
389
pwfex->nSamplesPerSec = mix_rate;
390
pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nChannels * (pwfex->wBitsPerSample / 8);
391
}
392
hr = p_device->audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, streamflags, p_input ? REFTIMES_PER_SEC : 0, 0, pwfex, nullptr);
393
394
if (p_reinit) {
395
// In case we're trying to re-initialize the device, prevent throwing this error on the console,
396
// otherwise if there is currently no device available this will spam the console.
397
if (hr != S_OK) {
398
print_verbose("WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + ".");
399
CoTaskMemFree(pwfex);
400
return ERR_CANT_OPEN;
401
}
402
} else {
403
if (unlikely(hr != S_OK)) {
404
CoTaskMemFree(pwfex);
405
ERR_FAIL_V_MSG(ERR_CANT_OPEN, "WASAPI: Initialize failed with error 0x" + String::num_uint64(hr, 16) + ".");
406
}
407
}
408
409
UINT32 max_frames;
410
hr = p_device->audio_client->GetBufferSize(&max_frames);
411
if (unlikely(hr != S_OK)) {
412
CoTaskMemFree(pwfex);
413
ERR_FAIL_V(ERR_CANT_OPEN);
414
}
415
416
// Due to WASAPI Shared Mode we have no control of the buffer size
417
if (!p_input) {
418
buffer_frames = max_frames;
419
420
int64_t latency = 0;
421
audio_output.audio_client->GetStreamLatency(&latency);
422
// WASAPI REFERENCE_TIME units are 100 nanoseconds per unit
423
// https://docs.microsoft.com/en-us/windows/win32/directshow/reference-time
424
// Convert REFTIME to seconds as godot uses for latency
425
real_latency = (float)latency / (float)REFTIMES_PER_SEC;
426
}
427
428
} else {
429
IAudioClient3 *device_audio_client_3 = (IAudioClient3 *)p_device->audio_client;
430
431
// AUDCLNT_STREAMFLAGS_RATEADJUST is an invalid flag with IAudioClient3, therefore we have to use
432
// the closest supported mix rate supported by the audio driver.
433
mix_rate = pwfex->nSamplesPerSec;
434
print_verbose("WASAPI: mix_rate = " + itos(mix_rate));
435
436
UINT32 default_period_frames, fundamental_period_frames, min_period_frames, max_period_frames;
437
hr = device_audio_client_3->GetSharedModeEnginePeriod(
438
pwfex,
439
&default_period_frames,
440
&fundamental_period_frames,
441
&min_period_frames,
442
&max_period_frames);
443
if (hr != S_OK) {
444
print_verbose("WASAPI: GetSharedModeEnginePeriod failed with error 0x" + String::num_uint64(hr, 16) + ", falling back to IAudioClient.");
445
CoTaskMemFree(pwfex);
446
return audio_device_init(p_device, p_input, p_reinit, true);
447
}
448
449
// Period frames must be an integral multiple of fundamental_period_frames or IAudioClient3 initialization will fail,
450
// so we need to select the closest multiple to the user-specified latency.
451
UINT32 desired_period_frames = target_latency_ms * mix_rate / 1000;
452
UINT32 period_frames = (desired_period_frames / fundamental_period_frames) * fundamental_period_frames;
453
if (Math::abs((int64_t)period_frames - (int64_t)desired_period_frames) > Math::abs((int64_t)(period_frames + fundamental_period_frames) - (int64_t)desired_period_frames)) {
454
period_frames = period_frames + fundamental_period_frames;
455
}
456
period_frames = CLAMP(period_frames, min_period_frames, max_period_frames);
457
print_verbose("WASAPI: fundamental_period_frames = " + itos(fundamental_period_frames));
458
print_verbose("WASAPI: min_period_frames = " + itos(min_period_frames));
459
print_verbose("WASAPI: max_period_frames = " + itos(max_period_frames));
460
print_verbose("WASAPI: selected a period frame size of " + itos(period_frames));
461
buffer_frames = period_frames;
462
463
hr = device_audio_client_3->InitializeSharedAudioStream(0, period_frames, pwfex, nullptr);
464
if (hr != S_OK) {
465
print_verbose("WASAPI: InitializeSharedAudioStream failed with error 0x" + String::num_uint64(hr, 16) + ", falling back to IAudioClient.");
466
CoTaskMemFree(pwfex);
467
return audio_device_init(p_device, p_input, p_reinit, true);
468
} else {
469
uint32_t output_latency_in_frames;
470
WAVEFORMATEX *current_pwfex;
471
hr = device_audio_client_3->GetCurrentSharedModeEnginePeriod(&current_pwfex, &output_latency_in_frames);
472
if (hr == OK) {
473
real_latency = (float)output_latency_in_frames / (float)current_pwfex->nSamplesPerSec;
474
CoTaskMemFree(current_pwfex);
475
} else {
476
print_verbose("WASAPI: GetCurrentSharedModeEnginePeriod failed with error 0x" + String::num_uint64(hr, 16) + ", falling back to IAudioClient.");
477
CoTaskMemFree(pwfex);
478
return audio_device_init(p_device, p_input, p_reinit, true);
479
}
480
}
481
}
482
483
if (p_input) {
484
hr = p_device->audio_client->GetService(IID_IAudioCaptureClient, (void **)&p_device->capture_client);
485
} else {
486
hr = p_device->audio_client->GetService(IID_IAudioRenderClient, (void **)&p_device->render_client);
487
}
488
if (unlikely(hr != S_OK)) {
489
CoTaskMemFree(pwfex);
490
ERR_FAIL_V(ERR_CANT_OPEN);
491
}
492
493
// Free memory
494
CoTaskMemFree(pwfex);
495
496
return OK;
497
}
498
499
Error AudioDriverWASAPI::init_output_device(bool p_reinit) {
500
Error err = audio_device_init(&audio_output, false, p_reinit);
501
if (err != OK) {
502
// We've tried to init the device, but have failed. Time to clean up.
503
Error finish_err = finish_output_device();
504
if (finish_err != OK) {
505
ERR_PRINT("WASAPI: finish_output_device error after failed output audio_device_init");
506
}
507
return err;
508
}
509
510
switch (audio_output.channels) {
511
case 1: // Mono
512
case 3: // Surround 2.1
513
case 5: // Surround 5.0
514
case 7: // Surround 7.0
515
// We will downmix as required.
516
channels = audio_output.channels + 1;
517
break;
518
519
case 2: // Stereo
520
case 4: // Surround 3.1
521
case 6: // Surround 5.1
522
case 8: // Surround 7.1
523
channels = audio_output.channels;
524
break;
525
526
default:
527
WARN_PRINT("WASAPI: Unsupported number of channels: " + itos(audio_output.channels));
528
channels = 2;
529
break;
530
}
531
532
// Sample rate is independent of channels (ref: https://stackoverflow.com/questions/11048825/audio-sample-frequency-rely-on-channels)
533
samples_in.resize(buffer_frames * channels);
534
535
input_position = 0;
536
input_size = 0;
537
538
print_verbose("WASAPI: detected " + itos(audio_output.channels) + " channels");
539
print_verbose("WASAPI: audio buffer frames: " + itos(buffer_frames) + " calculated latency: " + itos(buffer_frames * 1000 / mix_rate) + "ms");
540
541
return OK;
542
}
543
544
Error AudioDriverWASAPI::init_input_device(bool p_reinit) {
545
Error err = audio_device_init(&audio_input, true, p_reinit);
546
if (err != OK) {
547
// We've tried to init the device, but have failed. Time to clean up.
548
Error finish_err = finish_input_device();
549
if (finish_err != OK) {
550
ERR_PRINT("WASAPI: finish_input_device error after failed input audio_device_init");
551
}
552
return err;
553
}
554
555
// Get the max frames
556
UINT32 max_frames;
557
HRESULT hr = audio_input.audio_client->GetBufferSize(&max_frames);
558
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
559
560
input_buffer_init(max_frames);
561
562
return OK;
563
}
564
565
Error AudioDriverWASAPI::audio_device_finish(AudioDeviceWASAPI *p_device) {
566
if (p_device->active.is_set()) {
567
if (p_device->audio_client) {
568
p_device->audio_client->Stop();
569
}
570
p_device->active.clear();
571
}
572
573
SAFE_RELEASE(p_device->audio_client)
574
SAFE_RELEASE(p_device->render_client)
575
SAFE_RELEASE(p_device->capture_client)
576
577
return OK;
578
}
579
580
Error AudioDriverWASAPI::finish_output_device() {
581
return audio_device_finish(&audio_output);
582
}
583
584
Error AudioDriverWASAPI::finish_input_device() {
585
return audio_device_finish(&audio_input);
586
}
587
588
Error AudioDriverWASAPI::init() {
589
mix_rate = _get_configured_mix_rate();
590
591
target_latency_ms = Engine::get_singleton()->get_audio_output_latency();
592
593
exit_thread.clear();
594
595
Error err = init_output_device();
596
ERR_FAIL_COND_V_MSG(err != OK, err, "WASAPI: init_output_device error.");
597
598
thread.start(thread_func, this);
599
600
return OK;
601
}
602
603
int AudioDriverWASAPI::get_mix_rate() const {
604
return mix_rate;
605
}
606
607
float AudioDriverWASAPI::get_latency() {
608
return real_latency;
609
}
610
611
AudioDriver::SpeakerMode AudioDriverWASAPI::get_speaker_mode() const {
612
return get_speaker_mode_by_total_channels(channels);
613
}
614
615
PackedStringArray AudioDriverWASAPI::audio_device_get_list(bool p_input) {
616
PackedStringArray list;
617
ComPtr<IMMDeviceCollection> devices = nullptr;
618
ComPtr<IMMDeviceEnumerator> enumerator = nullptr;
619
620
list.push_back(String("Default"));
621
622
HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
623
ERR_FAIL_COND_V(hr != S_OK, PackedStringArray());
624
625
hr = enumerator->EnumAudioEndpoints(p_input ? eCapture : eRender, DEVICE_STATE_ACTIVE, &devices);
626
ERR_FAIL_COND_V(hr != S_OK, PackedStringArray());
627
628
UINT count = 0;
629
hr = devices->GetCount(&count);
630
ERR_FAIL_COND_V(hr != S_OK, PackedStringArray());
631
632
for (ULONG i = 0; i < count; i++) {
633
ComPtr<IMMDevice> output_device = nullptr;
634
635
hr = devices->Item(i, &output_device);
636
ERR_BREAK(hr != S_OK);
637
638
ComPtr<IPropertyStore> props = nullptr;
639
hr = output_device->OpenPropertyStore(STGM_READ, &props);
640
ERR_BREAK(hr != S_OK);
641
642
PROPVARIANT propvar;
643
PropVariantInit(&propvar);
644
645
hr = props->GetValue(PKEY_Device_FriendlyNameGodot, &propvar);
646
ERR_BREAK(hr != S_OK);
647
648
list.push_back(String(propvar.pwszVal));
649
650
PropVariantClear(&propvar);
651
}
652
653
return list;
654
}
655
656
PackedStringArray AudioDriverWASAPI::get_output_device_list() {
657
return audio_device_get_list(false);
658
}
659
660
String AudioDriverWASAPI::get_output_device() {
661
lock();
662
String name = audio_output.device_name;
663
unlock();
664
665
return name;
666
}
667
668
void AudioDriverWASAPI::set_output_device(const String &p_name) {
669
lock();
670
audio_output.new_device = p_name;
671
unlock();
672
}
673
674
int32_t AudioDriverWASAPI::read_sample(WORD format_tag, int bits_per_sample, BYTE *buffer, int i) {
675
if (format_tag == WAVE_FORMAT_PCM) {
676
int32_t sample = 0;
677
switch (bits_per_sample) {
678
case 8:
679
sample = int32_t(((int8_t *)buffer)[i]) << 24;
680
break;
681
682
case 16:
683
sample = int32_t(((int16_t *)buffer)[i]) << 16;
684
break;
685
686
case 24:
687
sample |= int32_t(((int8_t *)buffer)[i * 3 + 2]) << 24;
688
sample |= int32_t(((int8_t *)buffer)[i * 3 + 1]) << 16;
689
sample |= int32_t(((int8_t *)buffer)[i * 3 + 0]) << 8;
690
break;
691
692
case 32:
693
sample = ((int32_t *)buffer)[i];
694
break;
695
}
696
697
return sample;
698
} else if (format_tag == WAVE_FORMAT_IEEE_FLOAT) {
699
return int32_t(((float *)buffer)[i] * 32768.0) << 16;
700
} else {
701
ERR_PRINT("WASAPI: Unknown format tag");
702
}
703
704
return 0;
705
}
706
707
void AudioDriverWASAPI::write_sample(WORD format_tag, int bits_per_sample, BYTE *buffer, int i, int32_t sample) {
708
if (format_tag == WAVE_FORMAT_PCM) {
709
switch (bits_per_sample) {
710
case 8:
711
((int8_t *)buffer)[i] = sample >> 24;
712
break;
713
714
case 16:
715
((int16_t *)buffer)[i] = sample >> 16;
716
break;
717
718
case 24:
719
((int8_t *)buffer)[i * 3 + 2] = sample >> 24;
720
((int8_t *)buffer)[i * 3 + 1] = sample >> 16;
721
((int8_t *)buffer)[i * 3 + 0] = sample >> 8;
722
break;
723
724
case 32:
725
((int32_t *)buffer)[i] = sample;
726
break;
727
}
728
} else if (format_tag == WAVE_FORMAT_IEEE_FLOAT) {
729
((float *)buffer)[i] = (sample >> 16) / 32768.f;
730
} else {
731
ERR_PRINT("WASAPI: Unknown format tag");
732
}
733
}
734
735
void AudioDriverWASAPI::thread_func(void *p_udata) {
736
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
737
738
AudioDriverWASAPI *ad = static_cast<AudioDriverWASAPI *>(p_udata);
739
uint32_t avail_frames = 0;
740
uint32_t write_ofs = 0;
741
742
while (!ad->exit_thread.is_set()) {
743
uint32_t read_frames = 0;
744
uint32_t written_frames = 0;
745
746
if (avail_frames == 0) {
747
ad->lock();
748
ad->start_counting_ticks();
749
750
if (ad->audio_output.active.is_set()) {
751
ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptrw());
752
} else {
753
for (int i = 0; i < ad->samples_in.size(); i++) {
754
ad->samples_in.write[i] = 0;
755
}
756
}
757
758
avail_frames = ad->buffer_frames;
759
write_ofs = 0;
760
761
ad->stop_counting_ticks();
762
ad->unlock();
763
}
764
765
ad->lock();
766
ad->start_counting_ticks();
767
768
if (avail_frames > 0 && ad->audio_output.audio_client) {
769
UINT32 buffer_size;
770
UINT32 cur_frames;
771
bool invalidated = false;
772
HRESULT hr = ad->audio_output.audio_client->GetBufferSize(&buffer_size);
773
if (hr != S_OK) {
774
ERR_PRINT("WASAPI: GetBufferSize error");
775
}
776
hr = ad->audio_output.audio_client->GetCurrentPadding(&cur_frames);
777
if (hr == S_OK) {
778
// Check how much frames are available on the WASAPI buffer
779
UINT32 write_frames = MIN(buffer_size - cur_frames, avail_frames);
780
if (write_frames > 0) {
781
BYTE *buffer = nullptr;
782
hr = ad->audio_output.render_client->GetBuffer(write_frames, &buffer);
783
if (hr == S_OK) {
784
// We're using WASAPI Shared Mode so we must convert the buffer
785
if (ad->channels == ad->audio_output.channels) {
786
for (unsigned int i = 0; i < write_frames * ad->channels; i++) {
787
ad->write_sample(ad->audio_output.format_tag, ad->audio_output.bits_per_sample, buffer, i, ad->samples_in.write[write_ofs++]);
788
}
789
} else if (ad->channels == ad->audio_output.channels + 1) {
790
// Pass all channels except the last two as-is, and then mix the last two
791
// together as one channel. E.g. stereo -> mono, or 3.1 -> 2.1.
792
unsigned int last_chan = ad->audio_output.channels - 1;
793
for (unsigned int i = 0; i < write_frames; i++) {
794
for (unsigned int j = 0; j < last_chan; j++) {
795
ad->write_sample(ad->audio_output.format_tag, ad->audio_output.bits_per_sample, buffer, i * ad->audio_output.channels + j, ad->samples_in.write[write_ofs++]);
796
}
797
int32_t l = ad->samples_in.write[write_ofs++];
798
int32_t r = ad->samples_in.write[write_ofs++];
799
int32_t c = (int32_t)(((int64_t)l + (int64_t)r) / 2);
800
ad->write_sample(ad->audio_output.format_tag, ad->audio_output.bits_per_sample, buffer, i * ad->audio_output.channels + last_chan, c);
801
}
802
} else {
803
for (unsigned int i = 0; i < write_frames; i++) {
804
for (unsigned int j = 0; j < MIN(ad->channels, ad->audio_output.channels); j++) {
805
ad->write_sample(ad->audio_output.format_tag, ad->audio_output.bits_per_sample, buffer, i * ad->audio_output.channels + j, ad->samples_in.write[write_ofs++]);
806
}
807
if (ad->audio_output.channels > ad->channels) {
808
for (unsigned int j = ad->channels; j < ad->audio_output.channels; j++) {
809
ad->write_sample(ad->audio_output.format_tag, ad->audio_output.bits_per_sample, buffer, i * ad->audio_output.channels + j, 0);
810
}
811
}
812
}
813
}
814
815
hr = ad->audio_output.render_client->ReleaseBuffer(write_frames, 0);
816
if (hr != S_OK) {
817
ERR_PRINT("WASAPI: Release buffer error");
818
}
819
820
avail_frames -= write_frames;
821
written_frames += write_frames;
822
} else if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
823
// output_device is not valid anymore, reopen it
824
825
Error err = ad->finish_output_device();
826
if (err != OK) {
827
ERR_PRINT("WASAPI: finish_output_device error");
828
} else {
829
// We reopened the output device and samples_in may have resized, so invalidate the current avail_frames
830
avail_frames = 0;
831
}
832
} else {
833
ERR_PRINT("WASAPI: Get buffer error");
834
ad->exit_thread.set();
835
}
836
}
837
} else if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
838
invalidated = true;
839
} else {
840
ERR_PRINT("WASAPI: GetCurrentPadding error");
841
}
842
843
if (invalidated) {
844
// output_device is not valid anymore
845
WARN_PRINT("WASAPI: Current output_device invalidated, closing output_device");
846
847
Error err = ad->finish_output_device();
848
if (err != OK) {
849
ERR_PRINT("WASAPI: finish_output_device error");
850
}
851
}
852
}
853
854
// If we're using the Default output device and it changed finish it so we'll re-init the output device
855
if (ad->audio_output.device_name == "Default" && default_output_device_changed) {
856
Error err = ad->finish_output_device();
857
if (err != OK) {
858
ERR_PRINT("WASAPI: finish_output_device error");
859
}
860
861
default_output_device_changed = false;
862
}
863
864
// User selected a new output device, finish the current one so we'll init the new output device
865
if (ad->audio_output.device_name != ad->audio_output.new_device) {
866
ad->audio_output.device_name = ad->audio_output.new_device;
867
Error err = ad->finish_output_device();
868
if (err != OK) {
869
ERR_PRINT("WASAPI: finish_output_device error");
870
}
871
}
872
873
if (!ad->audio_output.audio_client) {
874
if (output_reinit_countdown < 1) {
875
Error err = ad->init_output_device(true);
876
if (err == OK) {
877
ad->start();
878
} else {
879
output_reinit_countdown = 1000;
880
}
881
} else {
882
output_reinit_countdown--;
883
}
884
885
avail_frames = 0;
886
write_ofs = 0;
887
}
888
889
if (ad->audio_input.active.is_set()) {
890
UINT32 packet_length = 0;
891
BYTE *data;
892
UINT32 num_frames_available;
893
DWORD flags;
894
895
HRESULT hr = ad->audio_input.capture_client->GetNextPacketSize(&packet_length);
896
if (hr == S_OK) {
897
while (packet_length != 0) {
898
hr = ad->audio_input.capture_client->GetBuffer(&data, &num_frames_available, &flags, nullptr, nullptr);
899
ERR_BREAK(hr != S_OK);
900
901
// fixme: Only works for floating point atm
902
for (UINT32 j = 0; j < num_frames_available; j++) {
903
int32_t l, r;
904
905
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
906
l = r = 0;
907
} else {
908
if (ad->audio_input.channels == 2) {
909
l = read_sample(ad->audio_input.format_tag, ad->audio_input.bits_per_sample, data, j * 2);
910
r = read_sample(ad->audio_input.format_tag, ad->audio_input.bits_per_sample, data, j * 2 + 1);
911
} else if (ad->audio_input.channels == 1) {
912
l = r = read_sample(ad->audio_input.format_tag, ad->audio_input.bits_per_sample, data, j);
913
} else {
914
l = r = 0;
915
ERR_PRINT("WASAPI: unsupported channel count in microphone!");
916
}
917
}
918
919
ad->input_buffer_write(l);
920
ad->input_buffer_write(r);
921
}
922
923
read_frames += num_frames_available;
924
925
hr = ad->audio_input.capture_client->ReleaseBuffer(num_frames_available);
926
ERR_BREAK(hr != S_OK);
927
928
hr = ad->audio_input.capture_client->GetNextPacketSize(&packet_length);
929
ERR_BREAK(hr != S_OK);
930
}
931
}
932
933
// If we're using the Default output device and it changed finish it so we'll re-init the output device
934
if (ad->audio_input.device_name == "Default" && default_input_device_changed) {
935
Error err = ad->finish_input_device();
936
if (err != OK) {
937
ERR_PRINT("WASAPI: finish_input_device error");
938
}
939
940
default_input_device_changed = false;
941
}
942
943
// User selected a new input device, finish the current one so we'll init the new input device
944
if (ad->audio_input.device_name != ad->audio_input.new_device) {
945
ad->audio_input.device_name = ad->audio_input.new_device;
946
Error err = ad->finish_input_device();
947
if (err != OK) {
948
ERR_PRINT("WASAPI: finish_input_device error");
949
}
950
}
951
952
if (!ad->audio_input.audio_client) {
953
if (input_reinit_countdown < 1) {
954
Error err = ad->init_input_device(true);
955
if (err == OK) {
956
ad->input_start();
957
} else {
958
input_reinit_countdown = 1000;
959
}
960
} else {
961
input_reinit_countdown--;
962
}
963
}
964
}
965
966
ad->stop_counting_ticks();
967
ad->unlock();
968
969
// Let the thread rest a while if we haven't read or write anything
970
if (written_frames == 0 && read_frames == 0) {
971
OS::get_singleton()->delay_usec(1000);
972
}
973
}
974
CoUninitialize();
975
}
976
977
void AudioDriverWASAPI::start() {
978
if (audio_output.audio_client) {
979
HRESULT hr = audio_output.audio_client->Start();
980
if (hr != S_OK) {
981
ERR_PRINT("WASAPI: Start failed");
982
} else {
983
audio_output.active.set();
984
}
985
}
986
}
987
988
void AudioDriverWASAPI::lock() {
989
mutex.lock();
990
}
991
992
void AudioDriverWASAPI::unlock() {
993
mutex.unlock();
994
}
995
996
void AudioDriverWASAPI::finish() {
997
exit_thread.set();
998
if (thread.is_started()) {
999
thread.wait_to_finish();
1000
}
1001
1002
finish_input_device();
1003
finish_output_device();
1004
}
1005
1006
Error AudioDriverWASAPI::input_start() {
1007
Error err = init_input_device();
1008
if (err != OK) {
1009
ERR_PRINT("WASAPI: init_input_device error");
1010
return err;
1011
}
1012
1013
if (audio_input.active.is_set()) {
1014
return FAILED;
1015
}
1016
1017
audio_input.audio_client->Start();
1018
audio_input.active.set();
1019
return OK;
1020
}
1021
1022
Error AudioDriverWASAPI::input_stop() {
1023
if (audio_input.active.is_set()) {
1024
audio_input.audio_client->Stop();
1025
audio_input.active.clear();
1026
1027
return OK;
1028
}
1029
1030
return FAILED;
1031
}
1032
1033
PackedStringArray AudioDriverWASAPI::get_input_device_list() {
1034
return audio_device_get_list(true);
1035
}
1036
1037
String AudioDriverWASAPI::get_input_device() {
1038
lock();
1039
String name = audio_input.device_name;
1040
unlock();
1041
1042
return name;
1043
}
1044
1045
void AudioDriverWASAPI::set_input_device(const String &p_name) {
1046
lock();
1047
audio_input.new_device = p_name;
1048
unlock();
1049
}
1050
1051
AudioDriverWASAPI::AudioDriverWASAPI() {
1052
samples_in.clear();
1053
}
1054
1055
#endif // WASAPI_ENABLED
1056
1057