1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21 */
22
23 namespace juce
24 {
25
26 #ifndef JUCE_WASAPI_LOGGING
27 #define JUCE_WASAPI_LOGGING 0
28 #endif
29
30 //==============================================================================
31 namespace WasapiClasses
32 {
33
logFailure(HRESULT hr)34 void logFailure (HRESULT hr)
35 {
36 ignoreUnused (hr);
37 jassert (hr != (HRESULT) 0x800401f0); // If you hit this, it means you're trying to call from
38 // a thread which hasn't been initialised with CoInitialize().
39
40 #if JUCE_WASAPI_LOGGING
41 if (FAILED (hr))
42 {
43 const char* m = nullptr;
44
45 switch (hr)
46 {
47 case E_POINTER: m = "E_POINTER"; break;
48 case E_INVALIDARG: m = "E_INVALIDARG"; break;
49 case E_NOINTERFACE: m = "E_NOINTERFACE"; break;
50
51 #define JUCE_WASAPI_ERR(desc, n) \
52 case MAKE_HRESULT(1, 0x889, n): m = #desc; break;
53
54 JUCE_WASAPI_ERR (AUDCLNT_E_NOT_INITIALIZED, 0x001)
55 JUCE_WASAPI_ERR (AUDCLNT_E_ALREADY_INITIALIZED, 0x002)
56 JUCE_WASAPI_ERR (AUDCLNT_E_WRONG_ENDPOINT_TYPE, 0x003)
57 JUCE_WASAPI_ERR (AUDCLNT_E_DEVICE_INVALIDATED, 0x004)
58 JUCE_WASAPI_ERR (AUDCLNT_E_NOT_STOPPED, 0x005)
59 JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_TOO_LARGE, 0x006)
60 JUCE_WASAPI_ERR (AUDCLNT_E_OUT_OF_ORDER, 0x007)
61 JUCE_WASAPI_ERR (AUDCLNT_E_UNSUPPORTED_FORMAT, 0x008)
62 JUCE_WASAPI_ERR (AUDCLNT_E_INVALID_SIZE, 0x009)
63 JUCE_WASAPI_ERR (AUDCLNT_E_DEVICE_IN_USE, 0x00a)
64 JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_OPERATION_PENDING, 0x00b)
65 JUCE_WASAPI_ERR (AUDCLNT_E_THREAD_NOT_REGISTERED, 0x00c)
66 JUCE_WASAPI_ERR (AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED, 0x00e)
67 JUCE_WASAPI_ERR (AUDCLNT_E_ENDPOINT_CREATE_FAILED, 0x00f)
68 JUCE_WASAPI_ERR (AUDCLNT_E_SERVICE_NOT_RUNNING, 0x010)
69 JUCE_WASAPI_ERR (AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED, 0x011)
70 JUCE_WASAPI_ERR (AUDCLNT_E_EXCLUSIVE_MODE_ONLY, 0x012)
71 JUCE_WASAPI_ERR (AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL, 0x013)
72 JUCE_WASAPI_ERR (AUDCLNT_E_EVENTHANDLE_NOT_SET, 0x014)
73 JUCE_WASAPI_ERR (AUDCLNT_E_INCORRECT_BUFFER_SIZE, 0x015)
74 JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_SIZE_ERROR, 0x016)
75 JUCE_WASAPI_ERR (AUDCLNT_E_CPUUSAGE_EXCEEDED, 0x017)
76 JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_ERROR, 0x018)
77 JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED, 0x019)
78 JUCE_WASAPI_ERR (AUDCLNT_E_INVALID_DEVICE_PERIOD, 0x020)
79 default: break;
80 }
81
82 Logger::writeToLog ("WASAPI error: " + (m != nullptr ? String (m)
83 : String::toHexString ((int) hr)));
84 }
85 #endif
86 }
87
88 #undef check
89
check(HRESULT hr)90 bool check (HRESULT hr)
91 {
92 logFailure (hr);
93 return SUCCEEDED (hr);
94 }
95
96 //==============================================================================
97 }
98
99 #if JUCE_MINGW
100 struct PROPERTYKEY
101 {
102 GUID fmtid;
103 DWORD pid;
104 };
105
106 WINOLEAPI PropVariantClear (PROPVARIANT*);
107 #endif
108
109 #if JUCE_MINGW && defined (KSDATAFORMAT_SUBTYPE_PCM)
110 #undef KSDATAFORMAT_SUBTYPE_PCM
111 #undef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
112 #endif
113
114 #ifndef KSDATAFORMAT_SUBTYPE_PCM
115 #define KSDATAFORMAT_SUBTYPE_PCM uuidFromString ("00000001-0000-0010-8000-00aa00389b71")
116 #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT uuidFromString ("00000003-0000-0010-8000-00aa00389b71")
117 #endif
118
119 #define JUCE_IUNKNOWNCLASS(name, guid) JUCE_COMCLASS(name, guid) : public IUnknown
120 #define JUCE_COMCALL virtual HRESULT STDMETHODCALLTYPE
121
122 enum EDataFlow
123 {
124 eRender = 0,
125 eCapture = (eRender + 1),
126 eAll = (eCapture + 1)
127 };
128
129 enum
130 {
131 DEVICE_STATE_ACTIVE = 1
132 };
133
134 enum
135 {
136 AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 1,
137 AUDCLNT_BUFFERFLAGS_SILENT = 2
138 };
139
140 JUCE_IUNKNOWNCLASS (IPropertyStore, "886d8eeb-8cf2-4446-8d02-cdba1dbdcf99")
141 {
142 JUCE_COMCALL GetCount (DWORD*) = 0;
143 JUCE_COMCALL GetAt (DWORD, PROPERTYKEY*) = 0;
144 JUCE_COMCALL GetValue (const PROPERTYKEY&, PROPVARIANT*) = 0;
145 JUCE_COMCALL SetValue (const PROPERTYKEY&, const PROPVARIANT&) = 0;
146 JUCE_COMCALL Commit() = 0;
147 };
148
149 JUCE_IUNKNOWNCLASS (IMMDevice, "D666063F-1587-4E43-81F1-B948E807363F")
150 {
151 JUCE_COMCALL Activate (REFIID, DWORD, PROPVARIANT*, void**) = 0;
152 JUCE_COMCALL OpenPropertyStore (DWORD, IPropertyStore**) = 0;
153 JUCE_COMCALL GetId (LPWSTR*) = 0;
154 JUCE_COMCALL GetState (DWORD*) = 0;
155 };
156
157 JUCE_IUNKNOWNCLASS (IMMEndpoint, "1BE09788-6894-4089-8586-9A2A6C265AC5")
158 {
159 JUCE_COMCALL GetDataFlow (EDataFlow*) = 0;
160 };
161
162 struct IMMDeviceCollection : public IUnknown
163 {
164 JUCE_COMCALL GetCount (UINT*) = 0;
165 JUCE_COMCALL Item (UINT, IMMDevice**) = 0;
166 };
167
168 enum ERole
169 {
170 eConsole = 0,
171 eMultimedia = (eConsole + 1),
172 eCommunications = (eMultimedia + 1)
173 };
174
175 JUCE_IUNKNOWNCLASS (IMMNotificationClient, "7991EEC9-7E89-4D85-8390-6C703CEC60C0")
176 {
177 JUCE_COMCALL OnDeviceStateChanged (LPCWSTR, DWORD) = 0;
178 JUCE_COMCALL OnDeviceAdded (LPCWSTR) = 0;
179 JUCE_COMCALL OnDeviceRemoved (LPCWSTR) = 0;
180 JUCE_COMCALL OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) = 0;
181 JUCE_COMCALL OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) = 0;
182 };
183
184 JUCE_IUNKNOWNCLASS (IMMDeviceEnumerator, "A95664D2-9614-4F35-A746-DE8DB63617E6")
185 {
186 JUCE_COMCALL EnumAudioEndpoints (EDataFlow, DWORD, IMMDeviceCollection**) = 0;
187 JUCE_COMCALL GetDefaultAudioEndpoint (EDataFlow, ERole, IMMDevice**) = 0;
188 JUCE_COMCALL GetDevice (LPCWSTR, IMMDevice**) = 0;
189 JUCE_COMCALL RegisterEndpointNotificationCallback (IMMNotificationClient*) = 0;
190 JUCE_COMCALL UnregisterEndpointNotificationCallback (IMMNotificationClient*) = 0;
191 };
192
193 JUCE_COMCLASS (MMDeviceEnumerator, "BCDE0395-E52F-467C-8E3D-C4579291692E");
194
195 using REFERENCE_TIME = LONGLONG;
196
197 enum AVRT_PRIORITY
198 {
199 AVRT_PRIORITY_LOW = -1,
200 AVRT_PRIORITY_NORMAL,
201 AVRT_PRIORITY_HIGH,
202 AVRT_PRIORITY_CRITICAL
203 };
204
205 enum AUDCLNT_SHAREMODE
206 {
207 AUDCLNT_SHAREMODE_SHARED,
208 AUDCLNT_SHAREMODE_EXCLUSIVE
209 };
210
211 enum AUDIO_STREAM_CATEGORY
212 {
213 AudioCategory_Other = 0,
214 AudioCategory_ForegroundOnlyMedia,
215 AudioCategory_BackgroundCapableMedia,
216 AudioCategory_Communications,
217 AudioCategory_Alerts,
218 AudioCategory_SoundEffects,
219 AudioCategory_GameEffects,
220 AudioCategory_GameMedia,
221 AudioCategory_GameChat,
222 AudioCategory_Speech,
223 AudioCategory_Movie,
224 AudioCategory_Media
225 };
226
227 struct AudioClientProperties
228 {
229 UINT32 cbSize;
230 BOOL bIsOffload;
231 AUDIO_STREAM_CATEGORY eCategory;
232 };
233
234 JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2")
235 {
236 JUCE_COMCALL Initialize (AUDCLNT_SHAREMODE, DWORD, REFERENCE_TIME, REFERENCE_TIME, const WAVEFORMATEX*, LPCGUID) = 0;
237 JUCE_COMCALL GetBufferSize (UINT32*) = 0;
238 JUCE_COMCALL GetStreamLatency (REFERENCE_TIME*) = 0;
239 JUCE_COMCALL GetCurrentPadding (UINT32*) = 0;
240 JUCE_COMCALL IsFormatSupported (AUDCLNT_SHAREMODE, const WAVEFORMATEX*, WAVEFORMATEX**) = 0;
241 JUCE_COMCALL GetMixFormat (WAVEFORMATEX**) = 0;
242 JUCE_COMCALL GetDevicePeriod (REFERENCE_TIME*, REFERENCE_TIME*) = 0;
243 JUCE_COMCALL Start() = 0;
244 JUCE_COMCALL Stop() = 0;
245 JUCE_COMCALL Reset() = 0;
246 JUCE_COMCALL SetEventHandle (HANDLE) = 0;
247 JUCE_COMCALL GetService (REFIID, void**) = 0;
248 };
249
250 JUCE_COMCLASS (IAudioClient2, "726778CD-F60A-4eda-82DE-E47610CD78AA") : public IAudioClient
251 {
252 JUCE_COMCALL IsOffloadCapable (AUDIO_STREAM_CATEGORY, BOOL*) = 0;
253 JUCE_COMCALL SetClientProperties (const AudioClientProperties*) = 0;
254 JUCE_COMCALL GetBufferSizeLimits (const WAVEFORMATEX*, BOOL, REFERENCE_TIME*, REFERENCE_TIME*) = 0;
255 };
256
257 JUCE_COMCLASS (IAudioClient3, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") : public IAudioClient2
258 {
259 JUCE_COMCALL GetSharedModeEnginePeriod (const WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32*) = 0;
260 JUCE_COMCALL GetCurrentSharedModeEnginePeriod (WAVEFORMATEX**, UINT32*) = 0;
261 JUCE_COMCALL InitializeSharedAudioStream (DWORD, UINT32, const WAVEFORMATEX*, LPCGUID) = 0;
262 };
263
264 JUCE_IUNKNOWNCLASS (IAudioCaptureClient, "C8ADBD64-E71E-48a0-A4DE-185C395CD317")
265 {
266 JUCE_COMCALL GetBuffer (BYTE**, UINT32*, DWORD*, UINT64*, UINT64*) = 0;
267 JUCE_COMCALL ReleaseBuffer (UINT32) = 0;
268 JUCE_COMCALL GetNextPacketSize (UINT32*) = 0;
269 };
270
271 JUCE_IUNKNOWNCLASS (IAudioRenderClient, "F294ACFC-3146-4483-A7BF-ADDCA7C260E2")
272 {
273 JUCE_COMCALL GetBuffer (UINT32, BYTE**) = 0;
274 JUCE_COMCALL ReleaseBuffer (UINT32, DWORD) = 0;
275 };
276
277 JUCE_IUNKNOWNCLASS (IAudioEndpointVolume, "5CDF2C82-841E-4546-9722-0CF74078229A")
278 {
279 JUCE_COMCALL RegisterControlChangeNotify (void*) = 0;
280 JUCE_COMCALL UnregisterControlChangeNotify (void*) = 0;
281 JUCE_COMCALL GetChannelCount (UINT*) = 0;
282 JUCE_COMCALL SetMasterVolumeLevel (float, LPCGUID) = 0;
283 JUCE_COMCALL SetMasterVolumeLevelScalar (float, LPCGUID) = 0;
284 JUCE_COMCALL GetMasterVolumeLevel (float*) = 0;
285 JUCE_COMCALL GetMasterVolumeLevelScalar (float*) = 0;
286 JUCE_COMCALL SetChannelVolumeLevel (UINT, float, LPCGUID) = 0;
287 JUCE_COMCALL SetChannelVolumeLevelScalar (UINT, float, LPCGUID) = 0;
288 JUCE_COMCALL GetChannelVolumeLevel (UINT, float*) = 0;
289 JUCE_COMCALL GetChannelVolumeLevelScalar (UINT, float*) = 0;
290 JUCE_COMCALL SetMute (BOOL, LPCGUID) = 0;
291 JUCE_COMCALL GetMute (BOOL*) = 0;
292 JUCE_COMCALL GetVolumeStepInfo (UINT*, UINT*) = 0;
293 JUCE_COMCALL VolumeStepUp (LPCGUID) = 0;
294 JUCE_COMCALL VolumeStepDown (LPCGUID) = 0;
295 JUCE_COMCALL QueryHardwareSupport (DWORD*) = 0;
296 JUCE_COMCALL GetVolumeRange (float*, float*, float*) = 0;
297 };
298
299 enum AudioSessionDisconnectReason
300 {
301 DisconnectReasonDeviceRemoval = 0,
302 DisconnectReasonServerShutdown = 1,
303 DisconnectReasonFormatChanged = 2,
304 DisconnectReasonSessionLogoff = 3,
305 DisconnectReasonSessionDisconnected = 4,
306 DisconnectReasonExclusiveModeOverride = 5
307 };
308
309 enum AudioSessionState
310 {
311 AudioSessionStateInactive = 0,
312 AudioSessionStateActive = 1,
313 AudioSessionStateExpired = 2
314 };
315
316 JUCE_IUNKNOWNCLASS (IAudioSessionEvents, "24918ACC-64B3-37C1-8CA9-74A66E9957A8")
317 {
318 JUCE_COMCALL OnDisplayNameChanged (LPCWSTR, LPCGUID) = 0;
319 JUCE_COMCALL OnIconPathChanged (LPCWSTR, LPCGUID) = 0;
320 JUCE_COMCALL OnSimpleVolumeChanged (float, BOOL, LPCGUID) = 0;
321 JUCE_COMCALL OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) = 0;
322 JUCE_COMCALL OnGroupingParamChanged (LPCGUID, LPCGUID) = 0;
323 JUCE_COMCALL OnStateChanged (AudioSessionState) = 0;
324 JUCE_COMCALL OnSessionDisconnected (AudioSessionDisconnectReason) = 0;
325 };
326
327 JUCE_IUNKNOWNCLASS (IAudioSessionControl, "F4B1A599-7266-4319-A8CA-E70ACB11E8CD")
328 {
329 JUCE_COMCALL GetState (AudioSessionState*) = 0;
330 JUCE_COMCALL GetDisplayName (LPWSTR*) = 0;
331 JUCE_COMCALL SetDisplayName (LPCWSTR, LPCGUID) = 0;
332 JUCE_COMCALL GetIconPath (LPWSTR*) = 0;
333 JUCE_COMCALL SetIconPath (LPCWSTR, LPCGUID) = 0;
334 JUCE_COMCALL GetGroupingParam (GUID*) = 0;
335 JUCE_COMCALL SetGroupingParam (LPCGUID, LPCGUID) = 0;
336 JUCE_COMCALL RegisterAudioSessionNotification (IAudioSessionEvents*) = 0;
337 JUCE_COMCALL UnregisterAudioSessionNotification (IAudioSessionEvents*) = 0;
338 };
339
340 #undef JUCE_COMCALL
341 #undef JUCE_COMCLASS
342 #undef JUCE_IUNKNOWNCLASS
343
344 //==============================================================================
345 namespace WasapiClasses
346 {
347
348 String getDeviceID (IMMDevice* device)
349 {
350 String s;
351 WCHAR* deviceId = nullptr;
352
353 if (check (device->GetId (&deviceId)))
354 {
355 s = String (deviceId);
356 CoTaskMemFree (deviceId);
357 }
358
359 return s;
360 }
361
362 static EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device)
363 {
364 EDataFlow flow = eRender;
365 if (auto endpoint = device.getInterface<IMMEndpoint>())
366 (void) check (endpoint->GetDataFlow (&flow));
367
368 return flow;
369 }
370
371 static int refTimeToSamples (const REFERENCE_TIME& t, double sampleRate) noexcept
372 {
373 return roundToInt (sampleRate * ((double) t) * 0.0000001);
374 }
375
376 static REFERENCE_TIME samplesToRefTime (int numSamples, double sampleRate) noexcept
377 {
378 return (REFERENCE_TIME) ((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5);
379 }
380
381 static void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcept
382 {
383 memcpy (&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof (WAVEFORMATEXTENSIBLE)
384 : sizeof (WAVEFORMATEX));
385 }
386
387 static bool isExclusiveMode (WASAPIDeviceMode deviceMode) noexcept
388 {
389 return deviceMode == WASAPIDeviceMode::exclusive;
390 }
391
392 static bool isLowLatencyMode (WASAPIDeviceMode deviceMode) noexcept
393 {
394 return deviceMode == WASAPIDeviceMode::sharedLowLatency;
395 }
396
397 static bool supportsSampleRateConversion (WASAPIDeviceMode deviceMode) noexcept
398 {
399 return deviceMode == WASAPIDeviceMode::shared;
400 }
401
402 //==============================================================================
403 class WASAPIDeviceBase
404 {
405 public:
406 WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
407 : device (d),
408 deviceMode (mode)
409 {
410 clientEvent = CreateEvent (nullptr, false, false, nullptr);
411
412 ComSmartPtr<IAudioClient> tempClient (createClient());
413
414 if (tempClient == nullptr)
415 return;
416
417 WAVEFORMATEXTENSIBLE format;
418
419 if (! getClientMixFormat (tempClient, format))
420 return;
421
422 actualNumChannels = numChannels = format.Format.nChannels;
423 defaultSampleRate = format.Format.nSamplesPerSec;
424 rates.addUsingDefaultSort (defaultSampleRate);
425 mixFormatChannelMask = format.dwChannelMask;
426
427 if (isExclusiveMode (deviceMode))
428 findSupportedFormat (tempClient, defaultSampleRate, mixFormatChannelMask, format);
429
430 querySupportedBufferSizes (format, tempClient);
431 querySupportedSampleRates (format, tempClient);
432 }
433
434 virtual ~WASAPIDeviceBase()
435 {
436 device = nullptr;
437 CloseHandle (clientEvent);
438 }
439
440 bool isOk() const noexcept { return defaultBufferSize > 0 && defaultSampleRate > 0; }
441
442 bool openClient (const double newSampleRate, const BigInteger& newChannels, const int bufferSizeSamples)
443 {
444 sampleRate = newSampleRate;
445 channels = newChannels;
446 channels.setRange (actualNumChannels, channels.getHighestBit() + 1 - actualNumChannels, false);
447 numChannels = channels.getHighestBit() + 1;
448
449 if (numChannels == 0)
450 return true;
451
452 client = createClient();
453
454 if (client != nullptr
455 && tryInitialisingWithBufferSize (bufferSizeSamples))
456 {
457 sampleRateHasChanged = false;
458 shouldShutdown = false;
459
460 channelMaps.clear();
461
462 for (int i = 0; i <= channels.getHighestBit(); ++i)
463 if (channels[i])
464 channelMaps.add (i);
465
466 REFERENCE_TIME latency;
467
468 if (check (client->GetStreamLatency (&latency)))
469 latencySamples = refTimeToSamples (latency, sampleRate);
470
471 (void) check (client->GetBufferSize (&actualBufferSize));
472 createSessionEventCallback();
473 return check (client->SetEventHandle (clientEvent));
474 }
475
476 return false;
477 }
478
479 void closeClient()
480 {
481 if (client != nullptr)
482 client->Stop();
483
484 // N.B. this is needed to prevent a double-deletion of the IAudioSessionEvents object
485 // on older versions of Windows
486 Thread::sleep (5);
487
488 deleteSessionEventCallback();
489 client = nullptr;
490 ResetEvent (clientEvent);
491 }
492
493 void deviceSampleRateChanged()
494 {
495 sampleRateHasChanged = true;
496 }
497
498 void deviceSessionBecameInactive()
499 {
500 isActive = false;
501 }
502
503 void deviceSessionExpired()
504 {
505 shouldShutdown = true;
506 }
507
508 void deviceSessionBecameActive()
509 {
510 isActive = true;
511 }
512
513 //==============================================================================
514 ComSmartPtr<IMMDevice> device;
515 ComSmartPtr<IAudioClient> client;
516
517 WASAPIDeviceMode deviceMode;
518
519 double sampleRate = 0, defaultSampleRate = 0;
520 int numChannels = 0, actualNumChannels = 0;
521 int minBufferSize = 0, defaultBufferSize = 0, latencySamples = 0;
522 int lowLatencyBufferSizeMultiple = 0, lowLatencyMaxBufferSize = 0;
523 DWORD mixFormatChannelMask = 0;
524 Array<double> rates;
525 HANDLE clientEvent = {};
526 BigInteger channels;
527 Array<int> channelMaps;
528 UINT32 actualBufferSize = 0;
529 int bytesPerSample = 0, bytesPerFrame = 0;
530 std::atomic<bool> sampleRateHasChanged { false }, shouldShutdown { false }, isActive { true };
531
532 virtual void updateFormat (bool isFloat) = 0;
533
534 private:
535 //==============================================================================
536 struct SessionEventCallback : public ComBaseClassHelper<IAudioSessionEvents>
537 {
538 SessionEventCallback (WASAPIDeviceBase& d) : owner (d) {}
539
540 JUCE_COMRESULT OnDisplayNameChanged (LPCWSTR, LPCGUID) { return S_OK; }
541 JUCE_COMRESULT OnIconPathChanged (LPCWSTR, LPCGUID) { return S_OK; }
542 JUCE_COMRESULT OnSimpleVolumeChanged (float, BOOL, LPCGUID) { return S_OK; }
543 JUCE_COMRESULT OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) { return S_OK; }
544 JUCE_COMRESULT OnGroupingParamChanged (LPCGUID, LPCGUID) { return S_OK; }
545
546 JUCE_COMRESULT OnStateChanged (AudioSessionState state)
547 {
548 switch (state)
549 {
550 case AudioSessionStateInactive:
551 owner.deviceSessionBecameInactive();
552 break;
553 case AudioSessionStateExpired:
554 owner.deviceSessionExpired();
555 break;
556 case AudioSessionStateActive:
557 owner.deviceSessionBecameActive();
558 break;
559 }
560
561 return S_OK;
562 }
563
564 JUCE_COMRESULT OnSessionDisconnected (AudioSessionDisconnectReason reason)
565 {
566 if (reason == DisconnectReasonFormatChanged)
567 owner.deviceSampleRateChanged();
568
569 return S_OK;
570 }
571
572 WASAPIDeviceBase& owner;
573 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SessionEventCallback)
574 };
575
576 ComSmartPtr<IAudioSessionControl> audioSessionControl;
577 ComSmartPtr<SessionEventCallback> sessionEventCallback;
578
579 void createSessionEventCallback()
580 {
581 deleteSessionEventCallback();
582 client->GetService (__uuidof (IAudioSessionControl),
583 (void**) audioSessionControl.resetAndGetPointerAddress());
584
585 if (audioSessionControl != nullptr)
586 {
587 sessionEventCallback = new SessionEventCallback (*this);
588 audioSessionControl->RegisterAudioSessionNotification (sessionEventCallback);
589 sessionEventCallback->Release(); // (required because ComBaseClassHelper objects are constructed with a ref count of 1)
590 }
591 }
592
593 void deleteSessionEventCallback()
594 {
595 if (audioSessionControl != nullptr && sessionEventCallback != nullptr)
596 audioSessionControl->UnregisterAudioSessionNotification (sessionEventCallback);
597
598 audioSessionControl = nullptr;
599 sessionEventCallback = nullptr;
600 }
601
602 //==============================================================================
603 ComSmartPtr<IAudioClient> createClient()
604 {
605 ComSmartPtr<IAudioClient> newClient;
606
607 if (device != nullptr)
608 logFailure (device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER,
609 nullptr, (void**) newClient.resetAndGetPointerAddress()));
610
611 return newClient;
612 }
613
614 static bool getClientMixFormat (ComSmartPtr<IAudioClient>& client, WAVEFORMATEXTENSIBLE& format)
615 {
616 WAVEFORMATEX* mixFormat = nullptr;
617
618 if (! check (client->GetMixFormat (&mixFormat)))
619 return false;
620
621 copyWavFormat (format, mixFormat);
622 CoTaskMemFree (mixFormat);
623
624 return true;
625 }
626
627 //==============================================================================
628 void querySupportedBufferSizes (WAVEFORMATEXTENSIBLE format, ComSmartPtr<IAudioClient>& audioClient)
629 {
630 if (isLowLatencyMode (deviceMode))
631 {
632 if (auto audioClient3 = audioClient.getInterface<IAudioClient3>())
633 {
634 UINT32 defaultPeriod = 0, fundamentalPeriod = 0, minPeriod = 0, maxPeriod = 0;
635
636 if (check (audioClient3->GetSharedModeEnginePeriod ((WAVEFORMATEX*) &format,
637 &defaultPeriod,
638 &fundamentalPeriod,
639 &minPeriod,
640 &maxPeriod)))
641 {
642 minBufferSize = minPeriod;
643 defaultBufferSize = defaultPeriod;
644 lowLatencyMaxBufferSize = maxPeriod;
645 lowLatencyBufferSizeMultiple = fundamentalPeriod;
646 }
647 }
648 }
649 else
650 {
651 REFERENCE_TIME defaultPeriod, minPeriod;
652
653 if (! check (audioClient->GetDevicePeriod (&defaultPeriod, &minPeriod)))
654 return;
655
656 minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate);
657 defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate);
658 }
659 }
660
661 void querySupportedSampleRates (WAVEFORMATEXTENSIBLE format, ComSmartPtr<IAudioClient>& audioClient)
662 {
663 for (auto rate : { 8000, 11025, 16000, 22050, 32000,
664 44100, 48000, 88200, 96000, 176400,
665 192000, 352800, 384000, 705600, 768000 })
666 {
667 if (rates.contains (rate))
668 continue;
669
670 format.Format.nSamplesPerSec = (DWORD) rate;
671 format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8);
672
673 WAVEFORMATEX* nearestFormat = nullptr;
674
675 if (SUCCEEDED (audioClient->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
676 : AUDCLNT_SHAREMODE_SHARED,
677 (WAVEFORMATEX*) &format,
678 isExclusiveMode (deviceMode) ? nullptr
679 : &nearestFormat)))
680 {
681 if (nearestFormat != nullptr)
682 rate = nearestFormat->nSamplesPerSec;
683
684 if (! rates.contains (rate))
685 rates.addUsingDefaultSort (rate);
686 }
687
688 CoTaskMemFree (nearestFormat);
689 }
690 }
691
692 struct AudioSampleFormat
693 {
694 bool useFloat;
695 int bitsPerSampleToTry;
696 int bytesPerSampleContainer;
697 };
698
699 bool tryFormat (const AudioSampleFormat sampleFormat, IAudioClient* clientToUse, double newSampleRate,
700 DWORD newMixFormatChannelMask, WAVEFORMATEXTENSIBLE& format) const
701 {
702 zerostruct (format);
703
704 if (numChannels <= 2 && sampleFormat.bitsPerSampleToTry <= 16)
705 {
706 format.Format.wFormatTag = WAVE_FORMAT_PCM;
707 }
708 else
709 {
710 format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
711 format.Format.cbSize = sizeof (WAVEFORMATEXTENSIBLE) - sizeof (WAVEFORMATEX);
712 }
713
714 format.Format.nSamplesPerSec = (DWORD) newSampleRate;
715 format.Format.nChannels = (WORD) numChannels;
716 format.Format.wBitsPerSample = (WORD) (8 * sampleFormat.bytesPerSampleContainer);
717 format.Samples.wValidBitsPerSample = (WORD) (sampleFormat.bitsPerSampleToTry);
718 format.Format.nBlockAlign = (WORD) (format.Format.nChannels * format.Format.wBitsPerSample / 8);
719 format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nBlockAlign);
720 format.SubFormat = sampleFormat.useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
721 format.dwChannelMask = newMixFormatChannelMask;
722
723 WAVEFORMATEX* nearestFormat = nullptr;
724
725 HRESULT hr = clientToUse->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
726 : AUDCLNT_SHAREMODE_SHARED,
727 (WAVEFORMATEX*) &format,
728 isExclusiveMode (deviceMode) ? nullptr
729 : &nearestFormat);
730 logFailure (hr);
731
732 auto supportsSRC = supportsSampleRateConversion (deviceMode);
733
734 if (hr == S_FALSE
735 && nearestFormat != nullptr
736 && (format.Format.nSamplesPerSec == nearestFormat->nSamplesPerSec
737 || supportsSRC))
738 {
739 copyWavFormat (format, nearestFormat);
740
741 if (supportsSRC)
742 {
743 format.Format.nSamplesPerSec = (DWORD) newSampleRate;
744 format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nBlockAlign);
745 }
746
747 hr = S_OK;
748 }
749
750 CoTaskMemFree (nearestFormat);
751 return hr == S_OK;
752 }
753
754 bool findSupportedFormat (IAudioClient* clientToUse, double newSampleRate,
755 DWORD newMixFormatChannelMask, WAVEFORMATEXTENSIBLE& format) const
756 {
757 static const AudioSampleFormat formats[] =
758 {
759 { true, 32, 4 },
760 { false, 32, 4 },
761 { false, 24, 4 },
762 { false, 24, 3 },
763 { false, 20, 4 },
764 { false, 20, 3 },
765 { false, 16, 2 }
766 };
767
768 for (int i = 0; i < numElementsInArray (formats); ++i)
769 if (tryFormat (formats[i], clientToUse, newSampleRate, newMixFormatChannelMask, format))
770 return true;
771
772 return false;
773 }
774
775 DWORD getStreamFlags()
776 {
777 DWORD streamFlags = 0x40000; /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/
778
779 if (supportsSampleRateConversion (deviceMode))
780 streamFlags |= (0x80000000 /*AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM*/
781 | 0x8000000); /*AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY*/
782
783 return streamFlags;
784 }
785
786 bool initialiseLowLatencyClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format)
787 {
788 if (auto audioClient3 = client.getInterface<IAudioClient3>())
789 return check (audioClient3->InitializeSharedAudioStream (getStreamFlags(),
790 bufferSizeSamples,
791 (WAVEFORMATEX*) &format,
792 nullptr));
793
794 return false;
795 }
796
797 bool initialiseStandardClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format)
798 {
799 REFERENCE_TIME defaultPeriod = 0, minPeriod = 0;
800
801 check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
802
803 if (isExclusiveMode (deviceMode) && bufferSizeSamples > 0)
804 defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec));
805
806 for (;;)
807 {
808 GUID session;
809 auto hr = client->Initialize (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
810 : AUDCLNT_SHAREMODE_SHARED,
811 getStreamFlags(),
812 defaultPeriod,
813 isExclusiveMode (deviceMode) ? defaultPeriod : 0,
814 (WAVEFORMATEX*) &format,
815 &session);
816
817 if (check (hr))
818 return true;
819
820 // Handle the "alignment dance" : http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx (see Remarks)
821 if (hr != MAKE_HRESULT (1, 0x889, 0x19)) // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED
822 break;
823
824 UINT32 numFrames = 0;
825 if (! check (client->GetBufferSize (&numFrames)))
826 break;
827
828 // Recreate client
829 client = nullptr;
830 client = createClient();
831
832 defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec);
833 }
834
835 return false;
836 }
837
838 bool tryInitialisingWithBufferSize (int bufferSizeSamples)
839 {
840 WAVEFORMATEXTENSIBLE format;
841
842 if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format))
843 {
844 auto isInitialised = isLowLatencyMode (deviceMode) ? initialiseLowLatencyClient (bufferSizeSamples, format)
845 : initialiseStandardClient (bufferSizeSamples, format);
846
847 if (isInitialised)
848 {
849 actualNumChannels = format.Format.nChannels;
850 const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
851 bytesPerSample = format.Format.wBitsPerSample / 8;
852 bytesPerFrame = format.Format.nBlockAlign;
853
854 updateFormat (isFloat);
855
856 return true;
857 }
858 }
859
860 return false;
861 }
862
863 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIDeviceBase)
864 };
865
866 //==============================================================================
867 class WASAPIInputDevice : public WASAPIDeviceBase
868 {
869 public:
870 WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
871 : WASAPIDeviceBase (d, mode)
872 {
873 }
874
875 ~WASAPIInputDevice()
876 {
877 close();
878 }
879
880 bool open (double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples)
881 {
882 return openClient (newSampleRate, newChannels, bufferSizeSamples)
883 && (numChannels == 0 || check (client->GetService (__uuidof (IAudioCaptureClient),
884 (void**) captureClient.resetAndGetPointerAddress())));
885 }
886
887 void close()
888 {
889 closeClient();
890 captureClient = nullptr;
891 reservoir.reset();
892 reservoirReadPos = 0;
893 reservoirWritePos = 0;
894 }
895
896 template<class SourceType>
897 void updateFormatWithType (SourceType*) noexcept
898 {
899 using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
900 converter.reset (new AudioData::ConverterInstance<AudioData::Pointer<SourceType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const>, NativeType> (actualNumChannels, 1));
901 }
902
903 void updateFormat (bool isFloat) override
904 {
905 if (isFloat) updateFormatWithType ((AudioData::Float32*) nullptr);
906 else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) nullptr);
907 else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) nullptr);
908 else updateFormatWithType ((AudioData::Int16*) nullptr);
909 }
910
911 bool start (int userBufferSize)
912 {
913 reservoirSize = actualBufferSize + userBufferSize;
914 reservoirMask = nextPowerOfTwo (reservoirSize) - 1;
915 reservoir.setSize ((reservoirMask + 1) * bytesPerFrame, true);
916 reservoirReadPos = 0;
917 reservoirWritePos = 0;
918 xruns = 0;
919
920 if (! check (client->Start()))
921 return false;
922
923 purgeInputBuffers();
924 isActive = true;
925
926 return true;
927 }
928
929 void purgeInputBuffers()
930 {
931 uint8* inputData;
932 UINT32 numSamplesAvailable;
933 DWORD flags;
934
935 while (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, nullptr, nullptr)
936 != MAKE_HRESULT (0, 0x889, 0x1) /* AUDCLNT_S_BUFFER_EMPTY */)
937 captureClient->ReleaseBuffer (numSamplesAvailable);
938 }
939
940 int getNumSamplesInReservoir() const noexcept { return reservoirWritePos.load() - reservoirReadPos.load(); }
941
942 void handleDeviceBuffer()
943 {
944 if (numChannels <= 0)
945 return;
946
947 uint8* inputData;
948 UINT32 numSamplesAvailable;
949 DWORD flags;
950
951 while (check (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, nullptr, nullptr)) && numSamplesAvailable > 0)
952 {
953 if ((flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0)
954 xruns++;
955
956 int samplesLeft = (int) numSamplesAvailable;
957
958 while (samplesLeft > 0)
959 {
960 auto localWrite = reservoirWritePos.load() & reservoirMask;
961 auto samplesToDo = jmin (samplesLeft, reservoirMask + 1 - localWrite);
962 auto samplesToDoBytes = samplesToDo * bytesPerFrame;
963
964 void* reservoirPtr = addBytesToPointer (reservoir.getData(), localWrite * bytesPerFrame);
965
966 if ((flags & AUDCLNT_BUFFERFLAGS_SILENT) != 0)
967 zeromem (reservoirPtr, samplesToDoBytes);
968 else
969 memcpy (reservoirPtr, inputData, samplesToDoBytes);
970
971 reservoirWritePos += samplesToDo;
972 inputData += samplesToDoBytes;
973 samplesLeft -= samplesToDo;
974 }
975
976 if (getNumSamplesInReservoir() > reservoirSize)
977 reservoirReadPos = reservoirWritePos.load() - reservoirSize;
978
979 captureClient->ReleaseBuffer (numSamplesAvailable);
980 }
981 }
982
983 void copyBuffersFromReservoir (float** destBuffers, int numDestBuffers, int bufferSize)
984 {
985 if ((numChannels <= 0 && bufferSize == 0) || reservoir.getSize() == 0)
986 return;
987
988 int offset = jmax (0, bufferSize - getNumSamplesInReservoir());
989
990 if (offset > 0)
991 {
992 for (int i = 0; i < numDestBuffers; ++i)
993 zeromem (destBuffers[i], offset * sizeof (float));
994
995 bufferSize -= offset;
996 reservoirReadPos -= offset / 2;
997 }
998
999 while (bufferSize > 0)
1000 {
1001 auto localRead = reservoirReadPos.load() & reservoirMask;
1002 auto samplesToDo = jmin (bufferSize, getNumSamplesInReservoir(), reservoirMask + 1 - localRead);
1003
1004 if (samplesToDo <= 0)
1005 break;
1006
1007 auto reservoirOffset = localRead * bytesPerFrame;
1008
1009 for (int i = 0; i < numDestBuffers; ++i)
1010 converter->convertSamples (destBuffers[i] + offset, 0, addBytesToPointer (reservoir.getData(), reservoirOffset), channelMaps.getUnchecked(i), samplesToDo);
1011
1012 bufferSize -= samplesToDo;
1013 offset += samplesToDo;
1014 reservoirReadPos += samplesToDo;
1015 }
1016 }
1017
1018 ComSmartPtr<IAudioCaptureClient> captureClient;
1019 MemoryBlock reservoir;
1020 int reservoirSize, reservoirMask, xruns;
1021 std::atomic<int> reservoirReadPos, reservoirWritePos;
1022
1023 std::unique_ptr<AudioData::Converter> converter;
1024
1025 private:
1026 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIInputDevice)
1027 };
1028
1029 //==============================================================================
1030 class WASAPIOutputDevice : public WASAPIDeviceBase
1031 {
1032 public:
1033 WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
1034 : WASAPIDeviceBase (d, mode)
1035 {
1036 }
1037
1038 ~WASAPIOutputDevice()
1039 {
1040 close();
1041 }
1042
1043 bool open (double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples)
1044 {
1045 return openClient (newSampleRate, newChannels, bufferSizeSamples)
1046 && (numChannels == 0 || check (client->GetService (__uuidof (IAudioRenderClient),
1047 (void**) renderClient.resetAndGetPointerAddress())));
1048 }
1049
1050 void close()
1051 {
1052 closeClient();
1053 renderClient = nullptr;
1054 }
1055
1056 template<class DestType>
1057 void updateFormatWithType (DestType*)
1058 {
1059 using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
1060 converter.reset (new AudioData::ConverterInstance<NativeType, AudioData::Pointer<DestType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst>> (1, actualNumChannels));
1061 }
1062
1063 void updateFormat (bool isFloat) override
1064 {
1065 if (isFloat) updateFormatWithType ((AudioData::Float32*) nullptr);
1066 else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) nullptr);
1067 else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) nullptr);
1068 else updateFormatWithType ((AudioData::Int16*) nullptr);
1069 }
1070
1071 bool start()
1072 {
1073 auto samplesToDo = getNumSamplesAvailableToCopy();
1074 uint8* outputData;
1075
1076 if (check (renderClient->GetBuffer (samplesToDo, &outputData)))
1077 renderClient->ReleaseBuffer (samplesToDo, AUDCLNT_BUFFERFLAGS_SILENT);
1078
1079 if (! check (client->Start()))
1080 return false;
1081
1082 isActive = true;
1083
1084 return true;
1085 }
1086
1087 int getNumSamplesAvailableToCopy() const
1088 {
1089 if (numChannels <= 0)
1090 return 0;
1091
1092 if (! isExclusiveMode (deviceMode))
1093 {
1094 UINT32 padding = 0;
1095
1096 if (check (client->GetCurrentPadding (&padding)))
1097 return actualBufferSize - (int) padding;
1098 }
1099
1100 return actualBufferSize;
1101 }
1102
1103 void copyBuffers (const float** srcBuffers, int numSrcBuffers, int bufferSize,
1104 WASAPIInputDevice* inputDevice, Thread& thread)
1105 {
1106 if (numChannels <= 0)
1107 return;
1108
1109 int offset = 0;
1110
1111 while (bufferSize > 0)
1112 {
1113 // This is needed in order not to drop any input data if the output device endpoint buffer was full
1114 if ((! isExclusiveMode (deviceMode)) && inputDevice != nullptr
1115 && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0)
1116 inputDevice->handleDeviceBuffer();
1117
1118 int samplesToDo = jmin (getNumSamplesAvailableToCopy(), bufferSize);
1119
1120 if (samplesToDo == 0)
1121 {
1122 // This can ONLY occur in non-exclusive mode
1123 if (! thread.threadShouldExit() && WaitForSingleObject (clientEvent, 1000) == WAIT_OBJECT_0)
1124 continue;
1125
1126 break;
1127 }
1128
1129 if (isExclusiveMode (deviceMode) && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
1130 break;
1131
1132 uint8* outputData = nullptr;
1133 if (check (renderClient->GetBuffer ((UINT32) samplesToDo, &outputData)))
1134 {
1135 for (int i = 0; i < numSrcBuffers; ++i)
1136 converter->convertSamples (outputData, channelMaps.getUnchecked(i), srcBuffers[i] + offset, 0, samplesToDo);
1137
1138 renderClient->ReleaseBuffer ((UINT32) samplesToDo, 0);
1139 }
1140
1141 bufferSize -= samplesToDo;
1142 offset += samplesToDo;
1143 }
1144 }
1145
1146 ComSmartPtr<IAudioRenderClient> renderClient;
1147 std::unique_ptr<AudioData::Converter> converter;
1148
1149 private:
1150 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIOutputDevice)
1151 };
1152
1153 //==============================================================================
1154 class WASAPIAudioIODevice : public AudioIODevice,
1155 public Thread,
1156 private AsyncUpdater
1157 {
1158 public:
1159 WASAPIAudioIODevice (const String& deviceName,
1160 const String& typeName,
1161 const String& outputDeviceID,
1162 const String& inputDeviceID,
1163 WASAPIDeviceMode mode)
1164 : AudioIODevice (deviceName, typeName),
1165 Thread ("JUCE WASAPI"),
1166 outputDeviceId (outputDeviceID),
1167 inputDeviceId (inputDeviceID),
1168 deviceMode (mode)
1169 {
1170 }
1171
1172 ~WASAPIAudioIODevice()
1173 {
1174 cancelPendingUpdate();
1175 close();
1176 }
1177
1178 bool initialise()
1179 {
1180 latencyIn = latencyOut = 0;
1181 Array<double> ratesIn, ratesOut;
1182
1183 if (createDevices())
1184 {
1185 jassert (inputDevice != nullptr || outputDevice != nullptr);
1186
1187 sampleRates.clear();
1188
1189 if (inputDevice != nullptr && outputDevice != nullptr)
1190 {
1191 defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate);
1192 minBufferSize = jmax (inputDevice->minBufferSize, outputDevice->minBufferSize);
1193 defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize);
1194
1195 if (isLowLatencyMode (deviceMode))
1196 {
1197 lowLatencyMaxBufferSize = jmin (inputDevice->lowLatencyMaxBufferSize, outputDevice->lowLatencyMaxBufferSize);
1198 lowLatencyBufferSizeMultiple = jmax (inputDevice->lowLatencyBufferSizeMultiple, outputDevice->lowLatencyBufferSizeMultiple);
1199 }
1200
1201 sampleRates.addArray (inputDevice->rates);
1202
1203 if (supportsSampleRateConversion (deviceMode))
1204 {
1205 for (auto r : outputDevice->rates)
1206 if (! sampleRates.contains (r))
1207 sampleRates.addUsingDefaultSort (r);
1208 }
1209 else
1210 {
1211 sampleRates.removeValuesNotIn (outputDevice->rates);
1212 }
1213 }
1214 else
1215 {
1216 auto* d = inputDevice != nullptr ? static_cast<WASAPIDeviceBase*> (inputDevice.get())
1217 : static_cast<WASAPIDeviceBase*> (outputDevice.get());
1218
1219 defaultSampleRate = d->defaultSampleRate;
1220 minBufferSize = d->minBufferSize;
1221 defaultBufferSize = d->defaultBufferSize;
1222
1223 if (isLowLatencyMode (deviceMode))
1224 {
1225 lowLatencyMaxBufferSize = d->lowLatencyMaxBufferSize;
1226 lowLatencyBufferSizeMultiple = d->lowLatencyBufferSizeMultiple;
1227 }
1228
1229 sampleRates = d->rates;
1230 }
1231
1232 bufferSizes.clear();
1233 bufferSizes.addUsingDefaultSort (defaultBufferSize);
1234
1235 if (minBufferSize != defaultBufferSize)
1236 bufferSizes.addUsingDefaultSort (minBufferSize);
1237
1238 if (isLowLatencyMode (deviceMode))
1239 {
1240 auto size = minBufferSize;
1241
1242 while (size < lowLatencyMaxBufferSize)
1243 {
1244 size += lowLatencyBufferSizeMultiple;
1245
1246 if (! bufferSizes.contains (size))
1247 bufferSizes.addUsingDefaultSort (size);
1248 }
1249 }
1250 else
1251 {
1252 int n = 64;
1253 for (int i = 0; i < 40; ++i)
1254 {
1255 if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n))
1256 bufferSizes.addUsingDefaultSort (n);
1257
1258 n += (n < 512) ? 32 : (n < 1024 ? 64 : 128);
1259 }
1260 }
1261
1262 return true;
1263 }
1264
1265 return false;
1266 }
1267
1268 StringArray getOutputChannelNames() override
1269 {
1270 StringArray outChannels;
1271
1272 if (outputDevice != nullptr)
1273 for (int i = 1; i <= outputDevice->actualNumChannels; ++i)
1274 outChannels.add ("Output channel " + String (i));
1275
1276 return outChannels;
1277 }
1278
1279 StringArray getInputChannelNames() override
1280 {
1281 StringArray inChannels;
1282
1283 if (inputDevice != nullptr)
1284 for (int i = 1; i <= inputDevice->actualNumChannels; ++i)
1285 inChannels.add ("Input channel " + String (i));
1286
1287 return inChannels;
1288 }
1289
1290 Array<double> getAvailableSampleRates() override { return sampleRates; }
1291 Array<int> getAvailableBufferSizes() override { return bufferSizes; }
1292 int getDefaultBufferSize() override { return defaultBufferSize; }
1293
1294 int getCurrentBufferSizeSamples() override { return currentBufferSizeSamples; }
1295 double getCurrentSampleRate() override { return currentSampleRate; }
1296 int getCurrentBitDepth() override { return 32; }
1297 int getOutputLatencyInSamples() override { return latencyOut; }
1298 int getInputLatencyInSamples() override { return latencyIn; }
1299 BigInteger getActiveOutputChannels() const override { return outputDevice != nullptr ? outputDevice->channels : BigInteger(); }
1300 BigInteger getActiveInputChannels() const override { return inputDevice != nullptr ? inputDevice->channels : BigInteger(); }
1301 String getLastError() override { return lastError; }
1302 int getXRunCount() const noexcept override { return inputDevice != nullptr ? inputDevice->xruns : -1; }
1303
1304 String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
1305 double sampleRate, int bufferSizeSamples) override
1306 {
1307 close();
1308 lastError.clear();
1309
1310 if (sampleRates.size() == 0 && inputDevice != nullptr && outputDevice != nullptr)
1311 {
1312 lastError = TRANS("The input and output devices don't share a common sample rate!");
1313 return lastError;
1314 }
1315
1316 currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize);
1317 currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate;
1318 lastKnownInputChannels = inputChannels;
1319 lastKnownOutputChannels = outputChannels;
1320
1321 if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels, bufferSizeSamples))
1322 {
1323 lastError = TRANS("Couldn't open the input device!");
1324 return lastError;
1325 }
1326
1327 if (outputDevice != nullptr && ! outputDevice->open (currentSampleRate, outputChannels, bufferSizeSamples))
1328 {
1329 close();
1330 lastError = TRANS("Couldn't open the output device!");
1331 return lastError;
1332 }
1333
1334 if (isExclusiveMode (deviceMode))
1335 {
1336 // This is to make sure that the callback uses actualBufferSize in case of exclusive mode
1337 if (inputDevice != nullptr && outputDevice != nullptr && inputDevice->actualBufferSize != outputDevice->actualBufferSize)
1338 {
1339 close();
1340 lastError = TRANS("Couldn't open the output device (buffer size mismatch)");
1341 return lastError;
1342 }
1343
1344 currentBufferSizeSamples = outputDevice != nullptr ? outputDevice->actualBufferSize
1345 : inputDevice->actualBufferSize;
1346 }
1347
1348 if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent);
1349 if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent);
1350
1351 shouldShutdown = false;
1352 deviceSampleRateChanged = false;
1353
1354 startThread (8);
1355 Thread::sleep (5);
1356
1357 if (inputDevice != nullptr && inputDevice->client != nullptr)
1358 {
1359 latencyIn = (int) (inputDevice->latencySamples + currentBufferSizeSamples);
1360
1361 if (! inputDevice->start (currentBufferSizeSamples))
1362 {
1363 close();
1364 lastError = TRANS("Couldn't start the input device!");
1365 return lastError;
1366 }
1367 }
1368
1369 if (outputDevice != nullptr && outputDevice->client != nullptr)
1370 {
1371 latencyOut = (int) (outputDevice->latencySamples + currentBufferSizeSamples);
1372
1373 if (! outputDevice->start())
1374 {
1375 close();
1376 lastError = TRANS("Couldn't start the output device!");
1377 return lastError;
1378 }
1379 }
1380
1381 isOpen_ = true;
1382 return lastError;
1383 }
1384
1385 void close() override
1386 {
1387 stop();
1388 signalThreadShouldExit();
1389
1390 if (inputDevice != nullptr) SetEvent (inputDevice->clientEvent);
1391 if (outputDevice != nullptr) SetEvent (outputDevice->clientEvent);
1392
1393 stopThread (5000);
1394
1395 if (inputDevice != nullptr) inputDevice->close();
1396 if (outputDevice != nullptr) outputDevice->close();
1397
1398 isOpen_ = false;
1399 }
1400
1401 bool isOpen() override { return isOpen_ && isThreadRunning(); }
1402 bool isPlaying() override { return isStarted && isOpen_ && isThreadRunning(); }
1403
1404 void start (AudioIODeviceCallback* call) override
1405 {
1406 if (isOpen_ && call != nullptr && ! isStarted)
1407 {
1408 if (! isThreadRunning())
1409 {
1410 // something's gone wrong and the thread's stopped..
1411 isOpen_ = false;
1412 return;
1413 }
1414
1415 call->audioDeviceAboutToStart (this);
1416
1417 const ScopedLock sl (startStopLock);
1418 callback = call;
1419 isStarted = true;
1420 }
1421 }
1422
1423 void stop() override
1424 {
1425 if (isStarted)
1426 {
1427 auto* callbackLocal = callback;
1428
1429 {
1430 const ScopedLock sl (startStopLock);
1431 isStarted = false;
1432 }
1433
1434 if (callbackLocal != nullptr)
1435 callbackLocal->audioDeviceStopped();
1436 }
1437 }
1438
1439 void setMMThreadPriority()
1440 {
1441 DynamicLibrary dll ("avrt.dll");
1442 JUCE_LOAD_WINAPI_FUNCTION (dll, AvSetMmThreadCharacteristicsW, avSetMmThreadCharacteristics, HANDLE, (LPCWSTR, LPDWORD))
1443 JUCE_LOAD_WINAPI_FUNCTION (dll, AvSetMmThreadPriority, avSetMmThreadPriority, HANDLE, (HANDLE, AVRT_PRIORITY))
1444
1445 if (avSetMmThreadCharacteristics != 0 && avSetMmThreadPriority != 0)
1446 {
1447 DWORD dummy = 0;
1448
1449 if (auto h = avSetMmThreadCharacteristics (L"Pro Audio", &dummy))
1450 avSetMmThreadPriority (h, AVRT_PRIORITY_NORMAL);
1451 }
1452 }
1453
1454 void run() override
1455 {
1456 setMMThreadPriority();
1457
1458 auto bufferSize = currentBufferSizeSamples;
1459 auto numInputBuffers = getActiveInputChannels().countNumberOfSetBits();
1460 auto numOutputBuffers = getActiveOutputChannels().countNumberOfSetBits();
1461
1462 AudioBuffer<float> ins (jmax (1, numInputBuffers), bufferSize + 32);
1463 AudioBuffer<float> outs (jmax (1, numOutputBuffers), bufferSize + 32);
1464 auto inputBuffers = ins.getArrayOfWritePointers();
1465 auto outputBuffers = outs.getArrayOfWritePointers();
1466 ins.clear();
1467 outs.clear();
1468
1469 while (! threadShouldExit())
1470 {
1471 if ((outputDevice != nullptr && outputDevice->shouldShutdown)
1472 || (inputDevice != nullptr && inputDevice->shouldShutdown))
1473 {
1474 shouldShutdown = true;
1475 triggerAsyncUpdate();
1476
1477 break;
1478 }
1479
1480 auto inputDeviceActive = (inputDevice != nullptr && inputDevice->isActive);
1481 auto outputDeviceActive = (outputDevice != nullptr && outputDevice->isActive);
1482
1483 if (! inputDeviceActive && ! outputDeviceActive)
1484 continue;
1485
1486 if (inputDeviceActive)
1487 {
1488 if (outputDevice == nullptr)
1489 {
1490 if (WaitForSingleObject (inputDevice->clientEvent, 1000) == WAIT_TIMEOUT)
1491 break;
1492
1493 inputDevice->handleDeviceBuffer();
1494
1495 if (inputDevice->getNumSamplesInReservoir() < bufferSize)
1496 continue;
1497 }
1498 else
1499 {
1500 if (isExclusiveMode (deviceMode) && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0)
1501 inputDevice->handleDeviceBuffer();
1502 }
1503
1504 inputDevice->copyBuffersFromReservoir (inputBuffers, numInputBuffers, bufferSize);
1505
1506 if (inputDevice->sampleRateHasChanged)
1507 {
1508 deviceSampleRateChanged = true;
1509 triggerAsyncUpdate();
1510
1511 break;
1512 }
1513 }
1514
1515 {
1516 const ScopedTryLock sl (startStopLock);
1517
1518 if (sl.isLocked() && isStarted)
1519 callback->audioDeviceIOCallback (const_cast<const float**> (inputBuffers), numInputBuffers,
1520 outputBuffers, numOutputBuffers, bufferSize);
1521 else
1522 outs.clear();
1523 }
1524
1525 if (outputDeviceActive)
1526 {
1527 // Note that this function is handed the input device so it can check for the event and make sure
1528 // the input reservoir is filled up correctly even when bufferSize > device actualBufferSize
1529 outputDevice->copyBuffers (const_cast<const float**> (outputBuffers), numOutputBuffers, bufferSize, inputDevice.get(), *this);
1530
1531 if (outputDevice->sampleRateHasChanged)
1532 {
1533 deviceSampleRateChanged = true;
1534 triggerAsyncUpdate();
1535
1536 break;
1537 }
1538 }
1539 }
1540 }
1541
1542 //==============================================================================
1543 String outputDeviceId, inputDeviceId;
1544 String lastError;
1545
1546 private:
1547 // Device stats...
1548 std::unique_ptr<WASAPIInputDevice> inputDevice;
1549 std::unique_ptr<WASAPIOutputDevice> outputDevice;
1550 WASAPIDeviceMode deviceMode;
1551 double defaultSampleRate = 0;
1552 int minBufferSize = 0, defaultBufferSize = 0;
1553 int lowLatencyMaxBufferSize = 0, lowLatencyBufferSizeMultiple = 0;
1554 int latencyIn = 0, latencyOut = 0;
1555 Array<double> sampleRates;
1556 Array<int> bufferSizes;
1557
1558 // Active state...
1559 bool isOpen_ = false, isStarted = false;
1560 int currentBufferSizeSamples = 0;
1561 double currentSampleRate = 0;
1562
1563 AudioIODeviceCallback* callback = {};
1564 CriticalSection startStopLock;
1565
1566 std::atomic<bool> shouldShutdown { false }, deviceSampleRateChanged { false };
1567
1568 BigInteger lastKnownInputChannels, lastKnownOutputChannels;
1569
1570 //==============================================================================
1571 bool createDevices()
1572 {
1573 ComSmartPtr<IMMDeviceEnumerator> enumerator;
1574
1575 if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
1576 return false;
1577
1578 ComSmartPtr<IMMDeviceCollection> deviceCollection;
1579
1580 if (! check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())))
1581 return false;
1582
1583 UINT32 numDevices = 0;
1584
1585 if (! check (deviceCollection->GetCount (&numDevices)))
1586 return false;
1587
1588 for (UINT32 i = 0; i < numDevices; ++i)
1589 {
1590 ComSmartPtr<IMMDevice> device;
1591
1592 if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress())))
1593 continue;
1594
1595 auto deviceId = getDeviceID (device);
1596
1597 if (deviceId.isEmpty())
1598 continue;
1599
1600 auto flow = getDataFlow (device);
1601
1602 if (deviceId == inputDeviceId && flow == eCapture)
1603 inputDevice.reset (new WASAPIInputDevice (device, deviceMode));
1604 else if (deviceId == outputDeviceId && flow == eRender)
1605 outputDevice.reset (new WASAPIOutputDevice (device, deviceMode));
1606 }
1607
1608 return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk()))
1609 && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk()));
1610 }
1611
1612 //==============================================================================
1613 void handleAsyncUpdate() override
1614 {
1615 auto closeDevices = [this]
1616 {
1617 close();
1618
1619 outputDevice = nullptr;
1620 inputDevice = nullptr;
1621 };
1622
1623 if (shouldShutdown)
1624 {
1625 closeDevices();
1626 }
1627 else if (deviceSampleRateChanged)
1628 {
1629 auto sampleRateChangedByInput = (inputDevice != nullptr && inputDevice->sampleRateHasChanged);
1630
1631 closeDevices();
1632 initialise();
1633
1634 auto changedSampleRate = [this, sampleRateChangedByInput]()
1635 {
1636 if (inputDevice != nullptr && sampleRateChangedByInput)
1637 return inputDevice->defaultSampleRate;
1638
1639 if (outputDevice != nullptr && ! sampleRateChangedByInput)
1640 return outputDevice->defaultSampleRate;
1641
1642 return 0.0;
1643 }();
1644
1645 open (lastKnownInputChannels, lastKnownOutputChannels,
1646 changedSampleRate, currentBufferSizeSamples);
1647
1648 start (callback);
1649 }
1650 }
1651
1652 //==============================================================================
1653 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODevice)
1654 };
1655
1656
1657 //==============================================================================
1658 class WASAPIAudioIODeviceType : public AudioIODeviceType,
1659 private DeviceChangeDetector
1660 {
1661 public:
1662 WASAPIAudioIODeviceType (WASAPIDeviceMode mode)
1663 : AudioIODeviceType (getDeviceTypename (mode)),
1664 DeviceChangeDetector (L"Windows Audio"),
1665 deviceMode (mode)
1666 {
1667 }
1668
1669 ~WASAPIAudioIODeviceType()
1670 {
1671 if (notifyClient != nullptr)
1672 enumerator->UnregisterEndpointNotificationCallback (notifyClient);
1673 }
1674
1675 //==============================================================================
1676 void scanForDevices() override
1677 {
1678 hasScanned = true;
1679
1680 outputDeviceNames.clear();
1681 inputDeviceNames.clear();
1682 outputDeviceIds.clear();
1683 inputDeviceIds.clear();
1684
1685 scan (outputDeviceNames, inputDeviceNames,
1686 outputDeviceIds, inputDeviceIds);
1687 }
1688
1689 StringArray getDeviceNames (bool wantInputNames) const override
1690 {
1691 jassert (hasScanned); // need to call scanForDevices() before doing this
1692
1693 return wantInputNames ? inputDeviceNames
1694 : outputDeviceNames;
1695 }
1696
1697 int getDefaultDeviceIndex (bool /*forInput*/) const override
1698 {
1699 jassert (hasScanned); // need to call scanForDevices() before doing this
1700 return 0;
1701 }
1702
1703 int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
1704 {
1705 jassert (hasScanned); // need to call scanForDevices() before doing this
1706
1707 if (auto d = dynamic_cast<WASAPIAudioIODevice*> (device))
1708 return asInput ? inputDeviceIds.indexOf (d->inputDeviceId)
1709 : outputDeviceIds.indexOf (d->outputDeviceId);
1710
1711 return -1;
1712 }
1713
1714 bool hasSeparateInputsAndOutputs() const override { return true; }
1715
1716 AudioIODevice* createDevice (const String& outputDeviceName,
1717 const String& inputDeviceName) override
1718 {
1719 jassert (hasScanned); // need to call scanForDevices() before doing this
1720
1721 std::unique_ptr<WASAPIAudioIODevice> device;
1722
1723 auto outputIndex = outputDeviceNames.indexOf (outputDeviceName);
1724 auto inputIndex = inputDeviceNames.indexOf (inputDeviceName);
1725
1726 if (outputIndex >= 0 || inputIndex >= 0)
1727 {
1728 device.reset (new WASAPIAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
1729 : inputDeviceName,
1730 getTypeName(),
1731 outputDeviceIds [outputIndex],
1732 inputDeviceIds [inputIndex],
1733 deviceMode));
1734
1735 if (! device->initialise())
1736 device = nullptr;
1737 }
1738
1739 return device.release();
1740 }
1741
1742 //==============================================================================
1743 StringArray outputDeviceNames, outputDeviceIds;
1744 StringArray inputDeviceNames, inputDeviceIds;
1745
1746 private:
1747 WASAPIDeviceMode deviceMode;
1748 bool hasScanned = false;
1749 ComSmartPtr<IMMDeviceEnumerator> enumerator;
1750
1751 //==============================================================================
1752 class ChangeNotificationClient : public ComBaseClassHelper<IMMNotificationClient>
1753 {
1754 public:
1755 ChangeNotificationClient (WASAPIAudioIODeviceType* d)
1756 : ComBaseClassHelper<IMMNotificationClient> (0), device (d) {}
1757
1758 HRESULT STDMETHODCALLTYPE OnDeviceAdded (LPCWSTR) { return notify(); }
1759 HRESULT STDMETHODCALLTYPE OnDeviceRemoved (LPCWSTR) { return notify(); }
1760 HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR, DWORD) { return notify(); }
1761 HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) { return notify(); }
1762 HRESULT STDMETHODCALLTYPE OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); }
1763
1764 private:
1765 WeakReference<WASAPIAudioIODeviceType> device;
1766
1767 HRESULT notify()
1768 {
1769 if (device != nullptr)
1770 device->triggerAsyncDeviceChangeCallback();
1771
1772 return S_OK;
1773 }
1774
1775 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChangeNotificationClient)
1776 };
1777
1778 ComSmartPtr<ChangeNotificationClient> notifyClient;
1779
1780 //==============================================================================
1781 static String getDefaultEndpoint (IMMDeviceEnumerator* enumerator, bool forCapture)
1782 {
1783 String s;
1784 IMMDevice* dev = nullptr;
1785
1786 if (check (enumerator->GetDefaultAudioEndpoint (forCapture ? eCapture : eRender,
1787 eMultimedia, &dev)))
1788 {
1789 WCHAR* deviceId = nullptr;
1790
1791 if (check (dev->GetId (&deviceId)))
1792 {
1793 s = deviceId;
1794 CoTaskMemFree (deviceId);
1795 }
1796
1797 dev->Release();
1798 }
1799
1800 return s;
1801 }
1802
1803 //==============================================================================
1804 void scan (StringArray& outDeviceNames,
1805 StringArray& inDeviceNames,
1806 StringArray& outDeviceIds,
1807 StringArray& inDeviceIds)
1808 {
1809 if (enumerator == nullptr)
1810 {
1811 if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
1812 return;
1813
1814 notifyClient = new ChangeNotificationClient (this);
1815 enumerator->RegisterEndpointNotificationCallback (notifyClient);
1816 }
1817
1818 auto defaultRenderer = getDefaultEndpoint (enumerator, false);
1819 auto defaultCapture = getDefaultEndpoint (enumerator, true);
1820
1821 ComSmartPtr<IMMDeviceCollection> deviceCollection;
1822 UINT32 numDevices = 0;
1823
1824 if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress()))
1825 && check (deviceCollection->GetCount (&numDevices))))
1826 return;
1827
1828 for (UINT32 i = 0; i < numDevices; ++i)
1829 {
1830 ComSmartPtr<IMMDevice> device;
1831
1832 if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress())))
1833 continue;
1834
1835 DWORD state = 0;
1836
1837 if (! (check (device->GetState (&state)) && state == DEVICE_STATE_ACTIVE))
1838 continue;
1839
1840 auto deviceId = getDeviceID (device);
1841 String name;
1842
1843 {
1844 ComSmartPtr<IPropertyStore> properties;
1845
1846 if (! check (device->OpenPropertyStore (STGM_READ, properties.resetAndGetPointerAddress())))
1847 continue;
1848
1849 PROPVARIANT value;
1850 zerostruct (value);
1851
1852 const PROPERTYKEY PKEY_Device_FriendlyName
1853 = { { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } }, 14 };
1854
1855 if (check (properties->GetValue (PKEY_Device_FriendlyName, &value)))
1856 name = value.pwszVal;
1857
1858 PropVariantClear (&value);
1859 }
1860
1861 auto flow = getDataFlow (device);
1862
1863 if (flow == eRender)
1864 {
1865 const int index = (deviceId == defaultRenderer) ? 0 : -1;
1866 outDeviceIds.insert (index, deviceId);
1867 outDeviceNames.insert (index, name);
1868 }
1869 else if (flow == eCapture)
1870 {
1871 const int index = (deviceId == defaultCapture) ? 0 : -1;
1872 inDeviceIds.insert (index, deviceId);
1873 inDeviceNames.insert (index, name);
1874 }
1875 }
1876
1877 inDeviceNames.appendNumbersToDuplicates (false, false);
1878 outDeviceNames.appendNumbersToDuplicates (false, false);
1879 }
1880
1881 //==============================================================================
1882 void systemDeviceChanged() override
1883 {
1884 StringArray newOutNames, newInNames, newOutIds, newInIds;
1885 scan (newOutNames, newInNames, newOutIds, newInIds);
1886
1887 if (newOutNames != outputDeviceNames
1888 || newInNames != inputDeviceNames
1889 || newOutIds != outputDeviceIds
1890 || newInIds != inputDeviceIds)
1891 {
1892 hasScanned = true;
1893 outputDeviceNames = newOutNames;
1894 inputDeviceNames = newInNames;
1895 outputDeviceIds = newOutIds;
1896 inputDeviceIds = newInIds;
1897 }
1898
1899 callDeviceChangeListeners();
1900 }
1901
1902 //==============================================================================
1903 static String getDeviceTypename (WASAPIDeviceMode mode)
1904 {
1905 if (mode == WASAPIDeviceMode::shared) return "Windows Audio";
1906 if (mode == WASAPIDeviceMode::sharedLowLatency) return "Windows Audio (Low Latency Mode)";
1907 if (mode == WASAPIDeviceMode::exclusive) return "Windows Audio (Exclusive Mode)";
1908
1909 jassertfalse;
1910 return {};
1911 }
1912
1913 //==============================================================================
1914 JUCE_DECLARE_WEAK_REFERENCEABLE (WASAPIAudioIODeviceType)
1915 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType)
1916 };
1917
1918 //==============================================================================
1919 struct MMDeviceMasterVolume
1920 {
1921 MMDeviceMasterVolume()
1922 {
1923 ComSmartPtr<IMMDeviceEnumerator> enumerator;
1924
1925 if (check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
1926 {
1927 ComSmartPtr<IMMDevice> device;
1928
1929 if (check (enumerator->GetDefaultAudioEndpoint (eRender, eConsole, device.resetAndGetPointerAddress())))
1930 check (device->Activate (__uuidof (IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr,
1931 (void**) endpointVolume.resetAndGetPointerAddress()));
1932 }
1933 }
1934
1935 float getGain() const
1936 {
1937 float vol = 0.0f;
1938
1939 if (endpointVolume != nullptr)
1940 check (endpointVolume->GetMasterVolumeLevelScalar (&vol));
1941
1942 return vol;
1943 }
1944
1945 bool setGain (float newGain) const
1946 {
1947 return endpointVolume != nullptr
1948 && check (endpointVolume->SetMasterVolumeLevelScalar (jlimit (0.0f, 1.0f, newGain), nullptr));
1949 }
1950
1951 bool isMuted() const
1952 {
1953 BOOL mute = 0;
1954 return endpointVolume != nullptr
1955 && check (endpointVolume->GetMute (&mute)) && mute != 0;
1956 }
1957
1958 bool setMuted (bool shouldMute) const
1959 {
1960 return endpointVolume != nullptr
1961 && check (endpointVolume->SetMute (shouldMute, nullptr));
1962 }
1963
1964 ComSmartPtr<IAudioEndpointVolume> endpointVolume;
1965
1966 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MMDeviceMasterVolume)
1967 };
1968
1969 }
1970
1971 //==============================================================================
1972 #define JUCE_SYSTEMAUDIOVOL_IMPLEMENTED 1
1973 float JUCE_CALLTYPE SystemAudioVolume::getGain() { return WasapiClasses::MMDeviceMasterVolume().getGain(); }
1974 bool JUCE_CALLTYPE SystemAudioVolume::setGain (float gain) { return WasapiClasses::MMDeviceMasterVolume().setGain (gain); }
1975 bool JUCE_CALLTYPE SystemAudioVolume::isMuted() { return WasapiClasses::MMDeviceMasterVolume().isMuted(); }
1976 bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool mute) { return WasapiClasses::MMDeviceMasterVolume().setMuted (mute); }
1977
1978 } // namespace juce
1979