Add remaining files
[juce-lv2.git] / juce / source / src / native / windows / juce_win32_WASAPI.cpp
blob68c65d164f2b9208d29d8a4dd699c231bcf28bd6
1 /*
2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 // (This file gets included by juce_win32_NativeCode.cpp, rather than being
27 // compiled on its own).
28 #if JUCE_INCLUDED_FILE && JUCE_WASAPI
30 #ifndef WASAPI_ENABLE_LOGGING
31 #define WASAPI_ENABLE_LOGGING 0
32 #endif
34 //==============================================================================
35 namespace WasapiClasses
38 void logFailure (HRESULT hr)
40 (void) hr;
42 #if WASAPI_ENABLE_LOGGING
43 if (FAILED (hr))
45 String e;
46 e << Time::getCurrentTime().toString (true, true, true, true)
47 << " -- WASAPI error: ";
49 switch (hr)
51 case E_POINTER: e << "E_POINTER"; break;
52 case E_INVALIDARG: e << "E_INVALIDARG"; break;
53 case AUDCLNT_E_NOT_INITIALIZED: e << "AUDCLNT_E_NOT_INITIALIZED"; break;
54 case AUDCLNT_E_ALREADY_INITIALIZED: e << "AUDCLNT_E_ALREADY_INITIALIZED"; break;
55 case AUDCLNT_E_WRONG_ENDPOINT_TYPE: e << "AUDCLNT_E_WRONG_ENDPOINT_TYPE"; break;
56 case AUDCLNT_E_DEVICE_INVALIDATED: e << "AUDCLNT_E_DEVICE_INVALIDATED"; break;
57 case AUDCLNT_E_NOT_STOPPED: e << "AUDCLNT_E_NOT_STOPPED"; break;
58 case AUDCLNT_E_BUFFER_TOO_LARGE: e << "AUDCLNT_E_BUFFER_TOO_LARGE"; break;
59 case AUDCLNT_E_OUT_OF_ORDER: e << "AUDCLNT_E_OUT_OF_ORDER"; break;
60 case AUDCLNT_E_UNSUPPORTED_FORMAT: e << "AUDCLNT_E_UNSUPPORTED_FORMAT"; break;
61 case AUDCLNT_E_INVALID_SIZE: e << "AUDCLNT_E_INVALID_SIZE"; break;
62 case AUDCLNT_E_DEVICE_IN_USE: e << "AUDCLNT_E_DEVICE_IN_USE"; break;
63 case AUDCLNT_E_BUFFER_OPERATION_PENDING: e << "AUDCLNT_E_BUFFER_OPERATION_PENDING"; break;
64 case AUDCLNT_E_THREAD_NOT_REGISTERED: e << "AUDCLNT_E_THREAD_NOT_REGISTERED"; break;
65 case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: e << "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; break;
66 case AUDCLNT_E_ENDPOINT_CREATE_FAILED: e << "AUDCLNT_E_ENDPOINT_CREATE_FAILED"; break;
67 case AUDCLNT_E_SERVICE_NOT_RUNNING: e << "AUDCLNT_E_SERVICE_NOT_RUNNING"; break;
68 case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: e << "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED"; break;
69 case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: e << "AUDCLNT_E_EXCLUSIVE_MODE_ONLY"; break;
70 case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: e << "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; break;
71 case AUDCLNT_E_EVENTHANDLE_NOT_SET: e << "AUDCLNT_E_EVENTHANDLE_NOT_SET"; break;
72 case AUDCLNT_E_INCORRECT_BUFFER_SIZE: e << "AUDCLNT_E_INCORRECT_BUFFER_SIZE"; break;
73 case AUDCLNT_E_BUFFER_SIZE_ERROR: e << "AUDCLNT_E_BUFFER_SIZE_ERROR"; break;
74 case AUDCLNT_S_BUFFER_EMPTY: e << "AUDCLNT_S_BUFFER_EMPTY"; break;
75 case AUDCLNT_S_THREAD_ALREADY_REGISTERED: e << "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; break;
76 default: e << String::toHexString ((int) hr); break;
79 DBG (e);
80 jassertfalse;
82 #endif
85 #undef check
87 bool check (HRESULT hr)
89 logFailure (hr);
90 return SUCCEEDED (hr);
93 //==============================================================================
94 String getDeviceID (IMMDevice* const device)
96 String s;
97 WCHAR* deviceId = nullptr;
99 if (check (device->GetId (&deviceId)))
101 s = String (deviceId);
102 CoTaskMemFree (deviceId);
105 return s;
108 EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device)
110 EDataFlow flow = eRender;
111 ComSmartPtr <IMMEndpoint> endPoint;
112 if (check (device.QueryInterface (endPoint)))
113 (void) check (endPoint->GetDataFlow (&flow));
115 return flow;
118 int refTimeToSamples (const REFERENCE_TIME& t, const double sampleRate) noexcept
120 return roundDoubleToInt (sampleRate * ((double) t) * 0.0000001);
123 void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* const src) noexcept
125 memcpy (&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof (WAVEFORMATEXTENSIBLE)
126 : sizeof (WAVEFORMATEX));
129 //==============================================================================
130 class WASAPIDeviceBase
132 public:
133 WASAPIDeviceBase (const ComSmartPtr <IMMDevice>& device_, const bool useExclusiveMode_)
134 : device (device_),
135 sampleRate (0),
136 defaultSampleRate (0),
137 numChannels (0),
138 actualNumChannels (0),
139 minBufferSize (0),
140 defaultBufferSize (0),
141 latencySamples (0),
142 useExclusiveMode (useExclusiveMode_),
143 sampleRateHasChanged (false)
145 clientEvent = CreateEvent (0, false, false, _T("JuceWASAPI"));
147 ComSmartPtr <IAudioClient> tempClient (createClient());
148 if (tempClient == nullptr)
149 return;
151 REFERENCE_TIME defaultPeriod, minPeriod;
152 if (! check (tempClient->GetDevicePeriod (&defaultPeriod, &minPeriod)))
153 return;
155 WAVEFORMATEX* mixFormat = nullptr;
156 if (! check (tempClient->GetMixFormat (&mixFormat)))
157 return;
159 WAVEFORMATEXTENSIBLE format;
160 copyWavFormat (format, mixFormat);
161 CoTaskMemFree (mixFormat);
163 actualNumChannels = numChannels = format.Format.nChannels;
164 defaultSampleRate = format.Format.nSamplesPerSec;
165 minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate);
166 defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate);
168 rates.addUsingDefaultSort (defaultSampleRate);
170 static const double ratesToTest[] = { 44100.0, 48000.0, 88200.0, 96000.0 };
172 for (int i = 0; i < numElementsInArray (ratesToTest); ++i)
174 if (ratesToTest[i] == defaultSampleRate)
175 continue;
177 format.Format.nSamplesPerSec = roundDoubleToInt (ratesToTest[i]);
179 if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
180 (WAVEFORMATEX*) &format, 0)))
181 if (! rates.contains (ratesToTest[i]))
182 rates.addUsingDefaultSort (ratesToTest[i]);
186 ~WASAPIDeviceBase()
188 device = nullptr;
189 CloseHandle (clientEvent);
192 bool isOk() const noexcept { return defaultBufferSize > 0 && defaultSampleRate > 0; }
194 bool openClient (const double newSampleRate, const BigInteger& newChannels)
196 sampleRate = newSampleRate;
197 channels = newChannels;
198 channels.setRange (actualNumChannels, channels.getHighestBit() + 1 - actualNumChannels, false);
199 numChannels = channels.getHighestBit() + 1;
201 if (numChannels == 0)
202 return true;
204 client = createClient();
206 if (client != nullptr
207 && (tryInitialisingWithFormat (true, 4) || tryInitialisingWithFormat (false, 4)
208 || tryInitialisingWithFormat (false, 3) || tryInitialisingWithFormat (false, 2)))
210 sampleRateHasChanged = false;
212 channelMaps.clear();
213 for (int i = 0; i <= channels.getHighestBit(); ++i)
214 if (channels[i])
215 channelMaps.add (i);
217 REFERENCE_TIME latency;
218 if (check (client->GetStreamLatency (&latency)))
219 latencySamples = refTimeToSamples (latency, sampleRate);
221 (void) check (client->GetBufferSize (&actualBufferSize));
223 createSessionEventCallback();
225 return check (client->SetEventHandle (clientEvent));
228 return false;
231 void closeClient()
233 if (client != nullptr)
234 client->Stop();
236 deleteSessionEventCallback();
237 client = nullptr;
238 ResetEvent (clientEvent);
241 void deviceSampleRateChanged()
243 sampleRateHasChanged = true;
246 //==============================================================================
247 ComSmartPtr <IMMDevice> device;
248 ComSmartPtr <IAudioClient> client;
249 double sampleRate, defaultSampleRate;
250 int numChannels, actualNumChannels;
251 int minBufferSize, defaultBufferSize, latencySamples;
252 const bool useExclusiveMode;
253 Array <double> rates;
254 HANDLE clientEvent;
255 BigInteger channels;
256 Array <int> channelMaps;
257 UINT32 actualBufferSize;
258 int bytesPerSample;
259 bool sampleRateHasChanged;
261 virtual void updateFormat (bool isFloat) = 0;
263 private:
264 //==============================================================================
265 class SessionEventCallback : public ComBaseClassHelper <IAudioSessionEvents>
267 public:
268 SessionEventCallback (WASAPIDeviceBase& owner_) : owner (owner_) {}
270 HRESULT __stdcall OnDisplayNameChanged (LPCWSTR, LPCGUID) { return S_OK; }
271 HRESULT __stdcall OnIconPathChanged (LPCWSTR, LPCGUID) { return S_OK; }
272 HRESULT __stdcall OnSimpleVolumeChanged (float, BOOL, LPCGUID) { return S_OK; }
273 HRESULT __stdcall OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) { return S_OK; }
274 HRESULT __stdcall OnGroupingParamChanged (LPCGUID, LPCGUID) { return S_OK; }
275 HRESULT __stdcall OnStateChanged (AudioSessionState) { return S_OK; }
277 HRESULT __stdcall OnSessionDisconnected (AudioSessionDisconnectReason reason)
279 if (reason == DisconnectReasonFormatChanged)
280 owner.deviceSampleRateChanged();
282 return S_OK;
285 private:
286 WASAPIDeviceBase& owner;
288 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SessionEventCallback);
291 ComSmartPtr <IAudioSessionControl> audioSessionControl;
292 ComSmartPtr <SessionEventCallback> sessionEventCallback;
294 void createSessionEventCallback()
296 deleteSessionEventCallback();
297 client->GetService (__uuidof (IAudioSessionControl),
298 (void**) audioSessionControl.resetAndGetPointerAddress());
300 if (audioSessionControl != nullptr)
302 sessionEventCallback = new SessionEventCallback (*this);
303 audioSessionControl->RegisterAudioSessionNotification (sessionEventCallback);
304 sessionEventCallback->Release(); // (required because ComBaseClassHelper objects are constructed with a ref count of 1)
308 void deleteSessionEventCallback()
310 if (audioSessionControl != nullptr && sessionEventCallback != nullptr)
311 audioSessionControl->UnregisterAudioSessionNotification (sessionEventCallback);
313 audioSessionControl = nullptr;
314 sessionEventCallback = nullptr;
317 //==============================================================================
318 const ComSmartPtr <IAudioClient> createClient()
320 ComSmartPtr <IAudioClient> client;
322 if (device != nullptr)
324 HRESULT hr = device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER, 0, (void**) client.resetAndGetPointerAddress());
325 logFailure (hr);
328 return client;
331 bool tryInitialisingWithFormat (const bool useFloat, const int bytesPerSampleToTry)
333 WAVEFORMATEXTENSIBLE format = { 0 };
335 if (numChannels <= 2 && bytesPerSampleToTry <= 2)
337 format.Format.wFormatTag = WAVE_FORMAT_PCM;
339 else
341 format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
342 format.Format.cbSize = sizeof (WAVEFORMATEXTENSIBLE) - sizeof (WAVEFORMATEX);
345 format.Format.nSamplesPerSec = roundDoubleToInt (sampleRate);
346 format.Format.nChannels = (WORD) numChannels;
347 format.Format.wBitsPerSample = (WORD) (8 * bytesPerSampleToTry);
348 format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * numChannels * bytesPerSampleToTry);
349 format.Format.nBlockAlign = (WORD) (numChannels * bytesPerSampleToTry);
350 format.SubFormat = useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
351 format.Samples.wValidBitsPerSample = format.Format.wBitsPerSample;
353 switch (numChannels)
355 case 1: format.dwChannelMask = SPEAKER_FRONT_CENTER; break;
356 case 2: format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; break;
357 case 4: format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break;
358 case 6: format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break;
359 case 8: format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER; break;
360 default: break;
363 WAVEFORMATEXTENSIBLE* nearestFormat = nullptr;
365 HRESULT hr = client->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
366 (WAVEFORMATEX*) &format, useExclusiveMode ? nullptr : (WAVEFORMATEX**) &nearestFormat);
367 logFailure (hr);
369 if (hr == S_FALSE && format.Format.nSamplesPerSec == nearestFormat->Format.nSamplesPerSec)
371 copyWavFormat (format, (WAVEFORMATEX*) nearestFormat);
372 hr = S_OK;
375 CoTaskMemFree (nearestFormat);
377 REFERENCE_TIME defaultPeriod = 0, minPeriod = 0;
378 if (useExclusiveMode)
379 check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
381 GUID session;
382 if (hr == S_OK
383 && check (client->Initialize (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
384 AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
385 defaultPeriod, defaultPeriod, (WAVEFORMATEX*) &format, &session)))
387 actualNumChannels = format.Format.nChannels;
388 const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
389 bytesPerSample = format.Format.wBitsPerSample / 8;
391 updateFormat (isFloat);
392 return true;
395 return false;
398 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIDeviceBase);
401 //==============================================================================
402 class WASAPIInputDevice : public WASAPIDeviceBase
404 public:
405 WASAPIInputDevice (const ComSmartPtr <IMMDevice>& device_, const bool useExclusiveMode_)
406 : WASAPIDeviceBase (device_, useExclusiveMode_),
407 reservoir (1, 1)
411 ~WASAPIInputDevice()
413 close();
416 bool open (const double newSampleRate, const BigInteger& newChannels)
418 reservoirSize = 0;
419 reservoirCapacity = 16384;
420 reservoir.setSize (actualNumChannels * reservoirCapacity * sizeof (float));
421 return openClient (newSampleRate, newChannels)
422 && (numChannels == 0 || check (client->GetService (__uuidof (IAudioCaptureClient),
423 (void**) captureClient.resetAndGetPointerAddress())));
426 void close()
428 closeClient();
429 captureClient = nullptr;
430 reservoir.setSize (0);
433 template <class SourceType>
434 void updateFormatWithType (SourceType*)
436 typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> NativeType;
437 converter = new AudioData::ConverterInstance <AudioData::Pointer <SourceType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const>, NativeType> (actualNumChannels, 1);
440 void updateFormat (bool isFloat)
442 if (isFloat) updateFormatWithType ((AudioData::Float32*) 0);
443 else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) 0);
444 else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) 0);
445 else updateFormatWithType ((AudioData::Int16*) 0);
448 void copyBuffers (float** destBuffers, int numDestBuffers, int bufferSize, Thread& thread)
450 if (numChannels <= 0)
451 return;
453 int offset = 0;
455 while (bufferSize > 0)
457 if (reservoirSize > 0) // There's stuff in the reservoir, so use that...
459 const int samplesToDo = jmin (bufferSize, (int) reservoirSize);
461 for (int i = 0; i < numDestBuffers; ++i)
462 converter->convertSamples (destBuffers[i] + offset, 0, reservoir.getData(), channelMaps.getUnchecked(i), samplesToDo);
464 bufferSize -= samplesToDo;
465 offset += samplesToDo;
466 reservoirSize = 0;
468 else
470 UINT32 packetLength = 0;
471 if (! check (captureClient->GetNextPacketSize (&packetLength)))
472 break;
474 if (packetLength == 0)
476 if (thread.threadShouldExit()
477 || WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
478 break;
480 continue;
483 uint8* inputData;
484 UINT32 numSamplesAvailable;
485 DWORD flags;
487 if (check (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, 0, 0)))
489 const int samplesToDo = jmin (bufferSize, (int) numSamplesAvailable);
491 for (int i = 0; i < numDestBuffers; ++i)
492 converter->convertSamples (destBuffers[i] + offset, 0, inputData, channelMaps.getUnchecked(i), samplesToDo);
494 bufferSize -= samplesToDo;
495 offset += samplesToDo;
497 if (samplesToDo < (int) numSamplesAvailable)
499 reservoirSize = jmin ((int) (numSamplesAvailable - samplesToDo), reservoirCapacity);
500 memcpy ((uint8*) reservoir.getData(), inputData + bytesPerSample * actualNumChannels * samplesToDo,
501 bytesPerSample * actualNumChannels * reservoirSize);
504 captureClient->ReleaseBuffer (numSamplesAvailable);
510 ComSmartPtr <IAudioCaptureClient> captureClient;
511 MemoryBlock reservoir;
512 int reservoirSize, reservoirCapacity;
513 ScopedPointer <AudioData::Converter> converter;
515 private:
516 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIInputDevice);
519 //==============================================================================
520 class WASAPIOutputDevice : public WASAPIDeviceBase
522 public:
523 WASAPIOutputDevice (const ComSmartPtr <IMMDevice>& device_, const bool useExclusiveMode_)
524 : WASAPIDeviceBase (device_, useExclusiveMode_)
528 ~WASAPIOutputDevice()
530 close();
533 bool open (const double newSampleRate, const BigInteger& newChannels)
535 return openClient (newSampleRate, newChannels)
536 && (numChannels == 0 || check (client->GetService (__uuidof (IAudioRenderClient), (void**) renderClient.resetAndGetPointerAddress())));
539 void close()
541 closeClient();
542 renderClient = nullptr;
545 template <class DestType>
546 void updateFormatWithType (DestType*)
548 typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> NativeType;
549 converter = new AudioData::ConverterInstance <NativeType, AudioData::Pointer <DestType, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> > (1, actualNumChannels);
552 void updateFormat (bool isFloat)
554 if (isFloat) updateFormatWithType ((AudioData::Float32*) 0);
555 else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) 0);
556 else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) 0);
557 else updateFormatWithType ((AudioData::Int16*) 0);
560 void copyBuffers (const float** const srcBuffers, const int numSrcBuffers, int bufferSize, Thread& thread)
562 if (numChannels <= 0)
563 return;
565 int offset = 0;
567 while (bufferSize > 0)
569 UINT32 padding = 0;
570 if (! check (client->GetCurrentPadding (&padding)))
571 return;
573 int samplesToDo = useExclusiveMode ? bufferSize
574 : jmin ((int) (actualBufferSize - padding), bufferSize);
576 if (samplesToDo <= 0)
578 if (thread.threadShouldExit()
579 || WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
580 break;
582 continue;
585 uint8* outputData = nullptr;
586 if (check (renderClient->GetBuffer (samplesToDo, &outputData)))
588 for (int i = 0; i < numSrcBuffers; ++i)
589 converter->convertSamples (outputData, channelMaps.getUnchecked(i), srcBuffers[i] + offset, 0, samplesToDo);
591 renderClient->ReleaseBuffer (samplesToDo, 0);
593 offset += samplesToDo;
594 bufferSize -= samplesToDo;
599 ComSmartPtr <IAudioRenderClient> renderClient;
600 ScopedPointer <AudioData::Converter> converter;
602 private:
603 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIOutputDevice);
606 //==============================================================================
607 class WASAPIAudioIODevice : public AudioIODevice,
608 public Thread
610 public:
611 WASAPIAudioIODevice (const String& deviceName,
612 const String& outputDeviceId_,
613 const String& inputDeviceId_,
614 const bool useExclusiveMode_)
615 : AudioIODevice (deviceName, "Windows Audio"),
616 Thread ("Juce WASAPI"),
617 outputDeviceId (outputDeviceId_),
618 inputDeviceId (inputDeviceId_),
619 useExclusiveMode (useExclusiveMode_),
620 isOpen_ (false),
621 isStarted (false),
622 currentBufferSizeSamples (0),
623 currentSampleRate (0),
624 callback (nullptr)
628 ~WASAPIAudioIODevice()
630 close();
633 bool initialise()
635 latencyIn = latencyOut = 0;
636 Array <double> ratesIn, ratesOut;
638 if (createDevices())
640 jassert (inputDevice != nullptr || outputDevice != nullptr);
642 if (inputDevice != nullptr && outputDevice != nullptr)
644 defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate);
645 minBufferSize = jmin (inputDevice->minBufferSize, outputDevice->minBufferSize);
646 defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize);
647 sampleRates = inputDevice->rates;
648 sampleRates.removeValuesNotIn (outputDevice->rates);
650 else
652 WASAPIDeviceBase* d = inputDevice != nullptr ? static_cast<WASAPIDeviceBase*> (inputDevice)
653 : static_cast<WASAPIDeviceBase*> (outputDevice);
654 defaultSampleRate = d->defaultSampleRate;
655 minBufferSize = d->minBufferSize;
656 defaultBufferSize = d->defaultBufferSize;
657 sampleRates = d->rates;
660 bufferSizes.addUsingDefaultSort (defaultBufferSize);
661 if (minBufferSize != defaultBufferSize)
662 bufferSizes.addUsingDefaultSort (minBufferSize);
664 int n = 64;
665 for (int i = 0; i < 40; ++i)
667 if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n))
668 bufferSizes.addUsingDefaultSort (n);
670 n += (n < 512) ? 32 : (n < 1024 ? 64 : 128);
673 return true;
676 return false;
679 StringArray getOutputChannelNames()
681 StringArray outChannels;
683 if (outputDevice != nullptr)
684 for (int i = 1; i <= outputDevice->actualNumChannels; ++i)
685 outChannels.add ("Output channel " + String (i));
687 return outChannels;
690 StringArray getInputChannelNames()
692 StringArray inChannels;
694 if (inputDevice != nullptr)
695 for (int i = 1; i <= inputDevice->actualNumChannels; ++i)
696 inChannels.add ("Input channel " + String (i));
698 return inChannels;
701 int getNumSampleRates() { return sampleRates.size(); }
702 double getSampleRate (int index) { return sampleRates [index]; }
703 int getNumBufferSizesAvailable() { return bufferSizes.size(); }
704 int getBufferSizeSamples (int index) { return bufferSizes [index]; }
705 int getDefaultBufferSize() { return defaultBufferSize; }
707 int getCurrentBufferSizeSamples() { return currentBufferSizeSamples; }
708 double getCurrentSampleRate() { return currentSampleRate; }
709 int getCurrentBitDepth() { return 32; }
710 int getOutputLatencyInSamples() { return latencyOut; }
711 int getInputLatencyInSamples() { return latencyIn; }
712 const BigInteger getActiveOutputChannels() const { return outputDevice != nullptr ? outputDevice->channels : BigInteger(); }
713 const BigInteger getActiveInputChannels() const { return inputDevice != nullptr ? inputDevice->channels : BigInteger(); }
714 const String getLastError() { return lastError; }
717 const String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
718 double sampleRate, int bufferSizeSamples)
720 close();
721 lastError = String::empty;
723 if (sampleRates.size() == 0 && inputDevice != nullptr && outputDevice != nullptr)
725 lastError = "The input and output devices don't share a common sample rate!";
726 return lastError;
729 currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize);
730 currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate;
732 if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels))
734 lastError = "Couldn't open the input device!";
735 return lastError;
738 if (outputDevice != nullptr && ! outputDevice->open (currentSampleRate, outputChannels))
740 close();
741 lastError = "Couldn't open the output device!";
742 return lastError;
745 if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent);
746 if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent);
748 startThread (8);
749 Thread::sleep (5);
751 if (inputDevice != nullptr && inputDevice->client != nullptr)
753 latencyIn = inputDevice->latencySamples + inputDevice->actualBufferSize + inputDevice->minBufferSize;
754 HRESULT hr = inputDevice->client->Start();
755 logFailure (hr); //xxx handle this
758 if (outputDevice != nullptr && outputDevice->client != nullptr)
760 latencyOut = outputDevice->latencySamples + outputDevice->actualBufferSize + outputDevice->minBufferSize;
761 HRESULT hr = outputDevice->client->Start();
762 logFailure (hr); //xxx handle this
765 isOpen_ = true;
766 return lastError;
769 void close()
771 stop();
772 signalThreadShouldExit();
774 if (inputDevice != nullptr) SetEvent (inputDevice->clientEvent);
775 if (outputDevice != nullptr) SetEvent (outputDevice->clientEvent);
777 stopThread (5000);
779 if (inputDevice != nullptr) inputDevice->close();
780 if (outputDevice != nullptr) outputDevice->close();
782 isOpen_ = false;
785 bool isOpen() { return isOpen_ && isThreadRunning(); }
786 bool isPlaying() { return isStarted && isOpen_ && isThreadRunning(); }
788 void start (AudioIODeviceCallback* call)
790 if (isOpen_ && call != nullptr && ! isStarted)
792 if (! isThreadRunning())
794 // something's gone wrong and the thread's stopped..
795 isOpen_ = false;
796 return;
799 call->audioDeviceAboutToStart (this);
801 const ScopedLock sl (startStopLock);
802 callback = call;
803 isStarted = true;
807 void stop()
809 if (isStarted)
811 AudioIODeviceCallback* const callbackLocal = callback;
814 const ScopedLock sl (startStopLock);
815 isStarted = false;
818 if (callbackLocal != nullptr)
819 callbackLocal->audioDeviceStopped();
823 void setMMThreadPriority()
825 DynamicLibraryLoader dll ("avrt.dll");
826 DynamicLibraryImport (AvSetMmThreadCharacteristicsW, avSetMmThreadCharacteristics, HANDLE, dll, (LPCWSTR, LPDWORD))
827 DynamicLibraryImport (AvSetMmThreadPriority, avSetMmThreadPriority, HANDLE, dll, (HANDLE, AVRT_PRIORITY))
829 if (avSetMmThreadCharacteristics != 0 && avSetMmThreadPriority != 0)
831 DWORD dummy = 0;
832 HANDLE h = avSetMmThreadCharacteristics (L"Pro Audio", &dummy);
834 if (h != 0)
835 avSetMmThreadPriority (h, AVRT_PRIORITY_NORMAL);
839 void run()
841 setMMThreadPriority();
843 const int bufferSize = currentBufferSizeSamples;
844 const int numInputBuffers = getActiveInputChannels().countNumberOfSetBits();
845 const int numOutputBuffers = getActiveOutputChannels().countNumberOfSetBits();
846 bool sampleRateChanged = false;
848 AudioSampleBuffer ins (jmax (1, numInputBuffers), bufferSize + 32);
849 AudioSampleBuffer outs (jmax (1, numOutputBuffers), bufferSize + 32);
850 float** const inputBuffers = ins.getArrayOfChannels();
851 float** const outputBuffers = outs.getArrayOfChannels();
852 ins.clear();
854 while (! threadShouldExit())
856 if (inputDevice != nullptr)
858 inputDevice->copyBuffers (inputBuffers, numInputBuffers, bufferSize, *this);
860 if (threadShouldExit())
861 break;
863 if (inputDevice->sampleRateHasChanged)
864 sampleRateChanged = true;
867 JUCE_TRY
869 const ScopedLock sl (startStopLock);
871 if (isStarted)
872 callback->audioDeviceIOCallback (const_cast <const float**> (inputBuffers), numInputBuffers,
873 outputBuffers, numOutputBuffers, bufferSize);
874 else
875 outs.clear();
877 JUCE_CATCH_EXCEPTION
879 if (outputDevice != nullptr)
881 outputDevice->copyBuffers (const_cast <const float**> (outputBuffers), numOutputBuffers, bufferSize, *this);
883 if (outputDevice->sampleRateHasChanged)
884 sampleRateChanged = true;
887 if (sampleRateChanged)
889 // xxx one of the devices has had its sample rate changed externally.. not 100% sure how
890 // to handle this..
895 //==============================================================================
896 String outputDeviceId, inputDeviceId;
897 String lastError;
899 private:
900 // Device stats...
901 ScopedPointer<WASAPIInputDevice> inputDevice;
902 ScopedPointer<WASAPIOutputDevice> outputDevice;
903 const bool useExclusiveMode;
904 double defaultSampleRate;
905 int minBufferSize, defaultBufferSize;
906 int latencyIn, latencyOut;
907 Array <double> sampleRates;
908 Array <int> bufferSizes;
910 // Active state...
911 bool isOpen_, isStarted;
912 int currentBufferSizeSamples;
913 double currentSampleRate;
915 AudioIODeviceCallback* callback;
916 CriticalSection startStopLock;
918 //==============================================================================
919 bool createDevices()
921 ComSmartPtr <IMMDeviceEnumerator> enumerator;
922 if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
923 return false;
925 ComSmartPtr <IMMDeviceCollection> deviceCollection;
926 if (! check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())))
927 return false;
929 UINT32 numDevices = 0;
930 if (! check (deviceCollection->GetCount (&numDevices)))
931 return false;
933 for (UINT32 i = 0; i < numDevices; ++i)
935 ComSmartPtr <IMMDevice> device;
936 if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress())))
937 continue;
939 const String deviceId (getDeviceID (device));
940 if (deviceId.isEmpty())
941 continue;
943 const EDataFlow flow = getDataFlow (device);
945 if (deviceId == inputDeviceId && flow == eCapture)
946 inputDevice = new WASAPIInputDevice (device, useExclusiveMode);
947 else if (deviceId == outputDeviceId && flow == eRender)
948 outputDevice = new WASAPIOutputDevice (device, useExclusiveMode);
951 return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk()))
952 && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk()));
955 //==============================================================================
956 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODevice);
960 //==============================================================================
961 class WASAPIAudioIODeviceType : public AudioIODeviceType,
962 private DeviceChangeDetector
964 public:
965 WASAPIAudioIODeviceType()
966 : AudioIODeviceType ("Windows Audio"),
967 DeviceChangeDetector (L"Windows Audio"),
968 hasScanned (false)
972 //==============================================================================
973 void scanForDevices()
975 hasScanned = true;
977 outputDeviceNames.clear();
978 inputDeviceNames.clear();
979 outputDeviceIds.clear();
980 inputDeviceIds.clear();
982 scan (outputDeviceNames, inputDeviceNames,
983 outputDeviceIds, inputDeviceIds);
986 StringArray getDeviceNames (bool wantInputNames) const
988 jassert (hasScanned); // need to call scanForDevices() before doing this
990 return wantInputNames ? inputDeviceNames
991 : outputDeviceNames;
994 int getDefaultDeviceIndex (bool /*forInput*/) const
996 jassert (hasScanned); // need to call scanForDevices() before doing this
997 return 0;
1000 int getIndexOfDevice (AudioIODevice* device, bool asInput) const
1002 jassert (hasScanned); // need to call scanForDevices() before doing this
1003 WASAPIAudioIODevice* const d = dynamic_cast <WASAPIAudioIODevice*> (device);
1004 return d == nullptr ? -1 : (asInput ? inputDeviceIds.indexOf (d->inputDeviceId)
1005 : outputDeviceIds.indexOf (d->outputDeviceId));
1008 bool hasSeparateInputsAndOutputs() const { return true; }
1010 AudioIODevice* createDevice (const String& outputDeviceName,
1011 const String& inputDeviceName)
1013 jassert (hasScanned); // need to call scanForDevices() before doing this
1015 const bool useExclusiveMode = false;
1016 ScopedPointer<WASAPIAudioIODevice> device;
1018 const int outputIndex = outputDeviceNames.indexOf (outputDeviceName);
1019 const int inputIndex = inputDeviceNames.indexOf (inputDeviceName);
1021 if (outputIndex >= 0 || inputIndex >= 0)
1023 device = new WASAPIAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
1024 : inputDeviceName,
1025 outputDeviceIds [outputIndex],
1026 inputDeviceIds [inputIndex],
1027 useExclusiveMode);
1029 if (! device->initialise())
1030 device = nullptr;
1033 return device.release();
1036 //==============================================================================
1037 StringArray outputDeviceNames, outputDeviceIds;
1038 StringArray inputDeviceNames, inputDeviceIds;
1040 private:
1041 bool hasScanned;
1043 //==============================================================================
1044 static String getDefaultEndpoint (IMMDeviceEnumerator* const enumerator, const bool forCapture)
1046 String s;
1047 IMMDevice* dev = nullptr;
1048 if (check (enumerator->GetDefaultAudioEndpoint (forCapture ? eCapture : eRender,
1049 eMultimedia, &dev)))
1051 WCHAR* deviceId = nullptr;
1052 if (check (dev->GetId (&deviceId)))
1054 s = deviceId;
1055 CoTaskMemFree (deviceId);
1058 dev->Release();
1061 return s;
1064 //==============================================================================
1065 void scan (StringArray& outputDeviceNames,
1066 StringArray& inputDeviceNames,
1067 StringArray& outputDeviceIds,
1068 StringArray& inputDeviceIds)
1070 ComSmartPtr <IMMDeviceEnumerator> enumerator;
1071 if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator))))
1072 return;
1074 const String defaultRenderer (getDefaultEndpoint (enumerator, false));
1075 const String defaultCapture (getDefaultEndpoint (enumerator, true));
1077 ComSmartPtr <IMMDeviceCollection> deviceCollection;
1078 UINT32 numDevices = 0;
1080 if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress()))
1081 && check (deviceCollection->GetCount (&numDevices))))
1082 return;
1084 for (UINT32 i = 0; i < numDevices; ++i)
1086 ComSmartPtr <IMMDevice> device;
1087 if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress())))
1088 continue;
1090 DWORD state = 0;
1091 if (! (check (device->GetState (&state)) && state == DEVICE_STATE_ACTIVE))
1092 continue;
1094 const String deviceId (getDeviceID (device));
1095 String name;
1098 ComSmartPtr <IPropertyStore> properties;
1099 if (! check (device->OpenPropertyStore (STGM_READ, properties.resetAndGetPointerAddress())))
1100 continue;
1102 PROPVARIANT value;
1103 PropVariantInit (&value);
1104 if (check (properties->GetValue (PKEY_Device_FriendlyName, &value)))
1105 name = value.pwszVal;
1107 PropVariantClear (&value);
1110 const EDataFlow flow = getDataFlow (device);
1112 if (flow == eRender)
1114 const int index = (deviceId == defaultRenderer) ? 0 : -1;
1115 outputDeviceIds.insert (index, deviceId);
1116 outputDeviceNames.insert (index, name);
1118 else if (flow == eCapture)
1120 const int index = (deviceId == defaultCapture) ? 0 : -1;
1121 inputDeviceIds.insert (index, deviceId);
1122 inputDeviceNames.insert (index, name);
1126 inputDeviceNames.appendNumbersToDuplicates (false, false);
1127 outputDeviceNames.appendNumbersToDuplicates (false, false);
1130 //==============================================================================
1131 void systemDeviceChanged()
1133 StringArray newOutNames, newInNames, newOutIds, newInIds;
1134 scan (newOutNames, newInNames, newOutIds, newInIds);
1136 if (newOutNames != outputDeviceNames
1137 || newInNames != inputDeviceNames
1138 || newOutIds != outputDeviceIds
1139 || newInIds != inputDeviceIds)
1141 hasScanned = true;
1142 outputDeviceNames = newOutNames;
1143 inputDeviceNames = newInNames;
1144 outputDeviceIds = newOutIds;
1145 inputDeviceIds = newInIds;
1147 callDeviceChangeListeners();
1151 //==============================================================================
1152 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType);
1157 //==============================================================================
1158 AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI()
1160 if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista)
1161 return new WasapiClasses::WASAPIAudioIODeviceType();
1163 return nullptr;
1166 #endif