Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/wasapi/audio_driver_wasapi.cpp
21097 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
if (audio_input.active.is_set()) {
546
return ERR_ALREADY_IN_USE;
547
}
548
549
Error err = audio_device_init(&audio_input, true, p_reinit);
550
if (err != OK) {
551
// We've tried to init the device, but have failed. Time to clean up.
552
Error finish_err = finish_input_device();
553
if (finish_err != OK) {
554
ERR_PRINT("WASAPI: finish_input_device error after failed input audio_device_init");
555
}
556
return err;
557
}
558
559
// Get the max frames
560
UINT32 max_frames;
561
HRESULT hr = audio_input.audio_client->GetBufferSize(&max_frames);
562
ERR_FAIL_COND_V(hr != S_OK, ERR_CANT_OPEN);
563
564
input_buffer_init(max_frames);
565
566
return OK;
567
}
568
569
Error AudioDriverWASAPI::audio_device_finish(AudioDeviceWASAPI *p_device) {
570
if (p_device->active.is_set()) {
571
if (p_device->audio_client) {
572
p_device->audio_client->Stop();
573
}
574
p_device->active.clear();
575
}
576
577
SAFE_RELEASE(p_device->audio_client)
578
SAFE_RELEASE(p_device->render_client)
579
SAFE_RELEASE(p_device->capture_client)
580
581
return OK;
582
}
583
584
Error AudioDriverWASAPI::finish_output_device() {
585
return audio_device_finish(&audio_output);
586
}
587
588
Error AudioDriverWASAPI::finish_input_device() {
589
return audio_device_finish(&audio_input);
590
}
591
592
Error AudioDriverWASAPI::init() {
593
mix_rate = _get_configured_mix_rate();
594
595
target_latency_ms = Engine::get_singleton()->get_audio_output_latency();
596
597
exit_thread.clear();
598
599
Error err = init_output_device();
600
ERR_FAIL_COND_V_MSG(err != OK, err, "WASAPI: init_output_device error.");
601
602
thread.start(thread_func, this);
603
604
return OK;
605
}
606
607
int AudioDriverWASAPI::get_mix_rate() const {
608
return mix_rate;
609
}
610
611
float AudioDriverWASAPI::get_latency() {
612
return real_latency;
613
}
614
615
AudioDriver::SpeakerMode AudioDriverWASAPI::get_speaker_mode() const {
616
return get_speaker_mode_by_total_channels(channels);
617
}
618
619
PackedStringArray AudioDriverWASAPI::audio_device_get_list(bool p_input) {
620
PackedStringArray list;
621
ComPtr<IMMDeviceCollection> devices = nullptr;
622
ComPtr<IMMDeviceEnumerator> enumerator = nullptr;
623
624
list.push_back(String("Default"));
625
626
HRESULT hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&enumerator);
627
ERR_FAIL_COND_V(hr != S_OK, PackedStringArray());
628
629
hr = enumerator->EnumAudioEndpoints(p_input ? eCapture : eRender, DEVICE_STATE_ACTIVE, &devices);
630
ERR_FAIL_COND_V(hr != S_OK, PackedStringArray());
631
632
UINT count = 0;
633
hr = devices->GetCount(&count);
634
ERR_FAIL_COND_V(hr != S_OK, PackedStringArray());
635
636
for (ULONG i = 0; i < count; i++) {
637
ComPtr<IMMDevice> output_device = nullptr;
638
639
hr = devices->Item(i, &output_device);
640
ERR_BREAK(hr != S_OK);
641
642
ComPtr<IPropertyStore> props = nullptr;
643
hr = output_device->OpenPropertyStore(STGM_READ, &props);
644
ERR_BREAK(hr != S_OK);
645
646
PROPVARIANT propvar;
647
PropVariantInit(&propvar);
648
649
hr = props->GetValue(PKEY_Device_FriendlyNameGodot, &propvar);
650
ERR_BREAK(hr != S_OK);
651
652
list.push_back(String(propvar.pwszVal));
653
654
PropVariantClear(&propvar);
655
}
656
657
return list;
658
}
659
660
PackedStringArray AudioDriverWASAPI::get_output_device_list() {
661
return audio_device_get_list(false);
662
}
663
664
String AudioDriverWASAPI::get_output_device() {
665
lock();
666
String name = audio_output.device_name;
667
unlock();
668
669
return name;
670
}
671
672
void AudioDriverWASAPI::set_output_device(const String &p_name) {
673
lock();
674
audio_output.new_device = p_name;
675
unlock();
676
}
677
678
int32_t AudioDriverWASAPI::read_sample(WORD format_tag, int bits_per_sample, BYTE *buffer, int i) {
679
if (format_tag == WAVE_FORMAT_PCM) {
680
int32_t sample = 0;
681
switch (bits_per_sample) {
682
case 8:
683
sample = int32_t(((int8_t *)buffer)[i]) << 24;
684
break;
685
686
case 16:
687
sample = int32_t(((int16_t *)buffer)[i]) << 16;
688
break;
689
690
case 24:
691
sample |= int32_t(((int8_t *)buffer)[i * 3 + 2]) << 24;
692
sample |= int32_t(((int8_t *)buffer)[i * 3 + 1]) << 16;
693
sample |= int32_t(((int8_t *)buffer)[i * 3 + 0]) << 8;
694
break;
695
696
case 32:
697
sample = ((int32_t *)buffer)[i];
698
break;
699
}
700
701
return sample;
702
} else if (format_tag == WAVE_FORMAT_IEEE_FLOAT) {
703
return int32_t(((float *)buffer)[i] * 32768.0) << 16;
704
} else {
705
ERR_PRINT("WASAPI: Unknown format tag");
706
}
707
708
return 0;
709
}
710
711
void AudioDriverWASAPI::write_sample(WORD format_tag, int bits_per_sample, BYTE *buffer, int i, int32_t sample) {
712
if (format_tag == WAVE_FORMAT_PCM) {
713
switch (bits_per_sample) {
714
case 8:
715
((int8_t *)buffer)[i] = sample >> 24;
716
break;
717
718
case 16:
719
((int16_t *)buffer)[i] = sample >> 16;
720
break;
721
722
case 24:
723
((int8_t *)buffer)[i * 3 + 2] = sample >> 24;
724
((int8_t *)buffer)[i * 3 + 1] = sample >> 16;
725
((int8_t *)buffer)[i * 3 + 0] = sample >> 8;
726
break;
727
728
case 32:
729
((int32_t *)buffer)[i] = sample;
730
break;
731
}
732
} else if (format_tag == WAVE_FORMAT_IEEE_FLOAT) {
733
((float *)buffer)[i] = (sample >> 16) / 32768.f;
734
} else {
735
ERR_PRINT("WASAPI: Unknown format tag");
736
}
737
}
738
739
void AudioDriverWASAPI::thread_func(void *p_udata) {
740
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
741
742
AudioDriverWASAPI *ad = static_cast<AudioDriverWASAPI *>(p_udata);
743
uint32_t avail_frames = 0;
744
uint32_t write_ofs = 0;
745
746
while (!ad->exit_thread.is_set()) {
747
uint32_t read_frames = 0;
748
uint32_t written_frames = 0;
749
750
if (avail_frames == 0) {
751
ad->lock();
752
ad->start_counting_ticks();
753
754
if (ad->audio_output.active.is_set()) {
755
ad->audio_server_process(ad->buffer_frames, ad->samples_in.ptrw());
756
} else {
757
for (int i = 0; i < ad->samples_in.size(); i++) {
758
ad->samples_in.write[i] = 0;
759
}
760
}
761
762
avail_frames = ad->buffer_frames;
763
write_ofs = 0;
764
765
ad->stop_counting_ticks();
766
ad->unlock();
767
}
768
769
ad->lock();
770
ad->start_counting_ticks();
771
772
if (avail_frames > 0 && ad->audio_output.audio_client) {
773
UINT32 buffer_size;
774
UINT32 cur_frames;
775
bool invalidated = false;
776
HRESULT hr = ad->audio_output.audio_client->GetBufferSize(&buffer_size);
777
if (hr != S_OK) {
778
ERR_PRINT("WASAPI: GetBufferSize error");
779
}
780
hr = ad->audio_output.audio_client->GetCurrentPadding(&cur_frames);
781
if (hr == S_OK) {
782
// Check how much frames are available on the WASAPI buffer
783
UINT32 write_frames = MIN(buffer_size - cur_frames, avail_frames);
784
if (write_frames > 0) {
785
BYTE *buffer = nullptr;
786
hr = ad->audio_output.render_client->GetBuffer(write_frames, &buffer);
787
if (hr == S_OK) {
788
// We're using WASAPI Shared Mode so we must convert the buffer
789
if (ad->channels == ad->audio_output.channels) {
790
for (unsigned int i = 0; i < write_frames * ad->channels; i++) {
791
ad->write_sample(ad->audio_output.format_tag, ad->audio_output.bits_per_sample, buffer, i, ad->samples_in.write[write_ofs++]);
792
}
793
} else if (ad->channels == ad->audio_output.channels + 1) {
794
// Pass all channels except the last two as-is, and then mix the last two
795
// together as one channel. E.g. stereo -> mono, or 3.1 -> 2.1.
796
unsigned int last_chan = ad->audio_output.channels - 1;
797
for (unsigned int i = 0; i < write_frames; i++) {
798
for (unsigned int j = 0; j < last_chan; j++) {
799
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++]);
800
}
801
int32_t l = ad->samples_in.write[write_ofs++];
802
int32_t r = ad->samples_in.write[write_ofs++];
803
int32_t c = (int32_t)(((int64_t)l + (int64_t)r) / 2);
804
ad->write_sample(ad->audio_output.format_tag, ad->audio_output.bits_per_sample, buffer, i * ad->audio_output.channels + last_chan, c);
805
}
806
} else {
807
for (unsigned int i = 0; i < write_frames; i++) {
808
for (unsigned int j = 0; j < MIN(ad->channels, 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, ad->samples_in.write[write_ofs++]);
810
}
811
if (ad->audio_output.channels > ad->channels) {
812
for (unsigned int j = ad->channels; j < ad->audio_output.channels; j++) {
813
ad->write_sample(ad->audio_output.format_tag, ad->audio_output.bits_per_sample, buffer, i * ad->audio_output.channels + j, 0);
814
}
815
}
816
}
817
}
818
819
hr = ad->audio_output.render_client->ReleaseBuffer(write_frames, 0);
820
if (hr != S_OK) {
821
ERR_PRINT("WASAPI: Release buffer error");
822
}
823
824
avail_frames -= write_frames;
825
written_frames += write_frames;
826
} else if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
827
// output_device is not valid anymore, reopen it
828
829
Error err = ad->finish_output_device();
830
if (err != OK) {
831
ERR_PRINT("WASAPI: finish_output_device error");
832
} else {
833
// We reopened the output device and samples_in may have resized, so invalidate the current avail_frames
834
avail_frames = 0;
835
}
836
} else {
837
ERR_PRINT("WASAPI: Get buffer error");
838
ad->exit_thread.set();
839
}
840
}
841
} else if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
842
invalidated = true;
843
} else {
844
ERR_PRINT("WASAPI: GetCurrentPadding error");
845
}
846
847
if (invalidated) {
848
// output_device is not valid anymore
849
WARN_PRINT("WASAPI: Current output_device invalidated, closing output_device");
850
851
Error err = ad->finish_output_device();
852
if (err != OK) {
853
ERR_PRINT("WASAPI: finish_output_device error");
854
}
855
}
856
}
857
858
// If we're using the Default output device and it changed finish it so we'll re-init the output device
859
if (ad->audio_output.device_name == "Default" && default_output_device_changed) {
860
Error err = ad->finish_output_device();
861
if (err != OK) {
862
ERR_PRINT("WASAPI: finish_output_device error");
863
}
864
865
default_output_device_changed = false;
866
}
867
868
// User selected a new output device, finish the current one so we'll init the new output device
869
if (ad->audio_output.device_name != ad->audio_output.new_device) {
870
ad->audio_output.device_name = ad->audio_output.new_device;
871
Error err = ad->finish_output_device();
872
if (err != OK) {
873
ERR_PRINT("WASAPI: finish_output_device error");
874
}
875
}
876
877
if (!ad->audio_output.audio_client) {
878
if (output_reinit_countdown < 1) {
879
Error err = ad->init_output_device(true);
880
if (err == OK) {
881
ad->start();
882
} else {
883
output_reinit_countdown = 1000;
884
}
885
} else {
886
output_reinit_countdown--;
887
}
888
889
avail_frames = 0;
890
write_ofs = 0;
891
}
892
893
if (ad->audio_input.active.is_set()) {
894
UINT32 packet_length = 0;
895
BYTE *data;
896
UINT32 num_frames_available;
897
DWORD flags;
898
899
HRESULT hr = ad->audio_input.capture_client->GetNextPacketSize(&packet_length);
900
if (hr == S_OK) {
901
while (packet_length != 0) {
902
hr = ad->audio_input.capture_client->GetBuffer(&data, &num_frames_available, &flags, nullptr, nullptr);
903
ERR_BREAK(hr != S_OK);
904
905
// fixme: Only works for floating point atm
906
for (UINT32 j = 0; j < num_frames_available; j++) {
907
int32_t l, r;
908
909
if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
910
l = r = 0;
911
} else {
912
if (ad->audio_input.channels == 2) {
913
l = read_sample(ad->audio_input.format_tag, ad->audio_input.bits_per_sample, data, j * 2);
914
r = read_sample(ad->audio_input.format_tag, ad->audio_input.bits_per_sample, data, j * 2 + 1);
915
} else if (ad->audio_input.channels == 1) {
916
l = r = read_sample(ad->audio_input.format_tag, ad->audio_input.bits_per_sample, data, j);
917
} else {
918
l = r = 0;
919
ERR_PRINT("WASAPI: unsupported channel count in microphone!");
920
}
921
}
922
923
ad->input_buffer_write(l);
924
ad->input_buffer_write(r);
925
}
926
927
read_frames += num_frames_available;
928
929
hr = ad->audio_input.capture_client->ReleaseBuffer(num_frames_available);
930
ERR_BREAK(hr != S_OK);
931
932
hr = ad->audio_input.capture_client->GetNextPacketSize(&packet_length);
933
ERR_BREAK(hr != S_OK);
934
}
935
}
936
937
// If we're using the Default output device and it changed finish it so we'll re-init the output device
938
if (ad->audio_input.device_name == "Default" && default_input_device_changed) {
939
Error err = ad->finish_input_device();
940
if (err != OK) {
941
ERR_PRINT("WASAPI: finish_input_device error");
942
}
943
944
default_input_device_changed = false;
945
}
946
947
// User selected a new input device, finish the current one so we'll init the new input device
948
if (ad->audio_input.device_name != ad->audio_input.new_device) {
949
ad->audio_input.device_name = ad->audio_input.new_device;
950
Error err = ad->finish_input_device();
951
if (err != OK) {
952
ERR_PRINT("WASAPI: finish_input_device error");
953
}
954
}
955
956
if (!ad->audio_input.audio_client) {
957
if (input_reinit_countdown < 1) {
958
Error err = ad->init_input_device(true);
959
if (err == OK) {
960
ad->input_start();
961
} else {
962
input_reinit_countdown = 1000;
963
}
964
} else {
965
input_reinit_countdown--;
966
}
967
}
968
}
969
970
ad->stop_counting_ticks();
971
ad->unlock();
972
973
// Let the thread rest a while if we haven't read or write anything
974
if (written_frames == 0 && read_frames == 0) {
975
OS::get_singleton()->delay_usec(1000);
976
}
977
}
978
CoUninitialize();
979
}
980
981
void AudioDriverWASAPI::start() {
982
if (audio_output.audio_client) {
983
HRESULT hr = audio_output.audio_client->Start();
984
if (hr != S_OK) {
985
ERR_PRINT("WASAPI: Start failed");
986
} else {
987
audio_output.active.set();
988
}
989
}
990
}
991
992
void AudioDriverWASAPI::lock() {
993
mutex.lock();
994
}
995
996
void AudioDriverWASAPI::unlock() {
997
mutex.unlock();
998
}
999
1000
void AudioDriverWASAPI::finish() {
1001
exit_thread.set();
1002
if (thread.is_started()) {
1003
thread.wait_to_finish();
1004
}
1005
1006
finish_input_device();
1007
finish_output_device();
1008
}
1009
1010
Error AudioDriverWASAPI::input_start() {
1011
Error err = init_input_device();
1012
if (err != OK) {
1013
ERR_PRINT("WASAPI: init_input_device error");
1014
return err;
1015
}
1016
1017
if (audio_input.active.is_set()) {
1018
return FAILED;
1019
}
1020
1021
audio_input.audio_client->Start();
1022
audio_input.active.set();
1023
return OK;
1024
}
1025
1026
Error AudioDriverWASAPI::input_stop() {
1027
if (audio_input.active.is_set()) {
1028
audio_input.audio_client->Stop();
1029
audio_input.active.clear();
1030
}
1031
1032
return OK;
1033
}
1034
1035
PackedStringArray AudioDriverWASAPI::get_input_device_list() {
1036
return audio_device_get_list(true);
1037
}
1038
1039
String AudioDriverWASAPI::get_input_device() {
1040
lock();
1041
String name = audio_input.device_name;
1042
unlock();
1043
1044
return name;
1045
}
1046
1047
void AudioDriverWASAPI::set_input_device(const String &p_name) {
1048
lock();
1049
audio_input.new_device = p_name;
1050
unlock();
1051
}
1052
1053
AudioDriverWASAPI::AudioDriverWASAPI() {
1054
samples_in.clear();
1055
}
1056
1057
#endif // WASAPI_ENABLED
1058
1059