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
34 //==============================================================================
35 namespace WasapiClasses
38 void logFailure (HRESULT hr
)
42 #if WASAPI_ENABLE_LOGGING
46 e
<< Time::getCurrentTime().toString (true, true, true, true)
47 << " -- WASAPI error: ";
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;
87 bool check (HRESULT hr
)
90 return SUCCEEDED (hr
);
93 //==============================================================================
94 String
getDeviceID (IMMDevice
* const device
)
97 WCHAR
* deviceId
= nullptr;
99 if (check (device
->GetId (&deviceId
)))
101 s
= String (deviceId
);
102 CoTaskMemFree (deviceId
);
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
));
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
133 WASAPIDeviceBase (const ComSmartPtr
<IMMDevice
>& device_
, const bool useExclusiveMode_
)
136 defaultSampleRate (0),
138 actualNumChannels (0),
140 defaultBufferSize (0),
142 useExclusiveMode (useExclusiveMode_
),
143 sampleRateHasChanged (false)
145 clientEvent
= CreateEvent (0, false, false, _T("JuceWASAPI"));
147 ComSmartPtr
<IAudioClient
> tempClient (createClient());
148 if (tempClient
== nullptr)
151 REFERENCE_TIME defaultPeriod
, minPeriod
;
152 if (! check (tempClient
->GetDevicePeriod (&defaultPeriod
, &minPeriod
)))
155 WAVEFORMATEX
* mixFormat
= nullptr;
156 if (! check (tempClient
->GetMixFormat (&mixFormat
)))
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
)
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
]);
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)
204 client
= createClient();
206 if (client
!= nullptr
207 && (tryInitialisingWithFormat (true, 4) || tryInitialisingWithFormat (false, 4)
208 || tryInitialisingWithFormat (false, 3) || tryInitialisingWithFormat (false, 2)))
210 sampleRateHasChanged
= false;
213 for (int i
= 0; i
<= channels
.getHighestBit(); ++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
));
233 if (client
!= nullptr)
236 deleteSessionEventCallback();
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
;
256 Array
<int> channelMaps
;
257 UINT32 actualBufferSize
;
259 bool sampleRateHasChanged
;
261 virtual void updateFormat (bool isFloat
) = 0;
264 //==============================================================================
265 class SessionEventCallback
: public ComBaseClassHelper
<IAudioSessionEvents
>
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();
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());
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
;
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
;
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;
363 WAVEFORMATEXTENSIBLE
* nearestFormat
= nullptr;
365 HRESULT hr
= client
->IsFormatSupported (useExclusiveMode
? AUDCLNT_SHAREMODE_EXCLUSIVE
: AUDCLNT_SHAREMODE_SHARED
,
366 (WAVEFORMATEX
*) &format
, useExclusiveMode
? nullptr : (WAVEFORMATEX
**) &nearestFormat
);
369 if (hr
== S_FALSE
&& format
.Format
.nSamplesPerSec
== nearestFormat
->Format
.nSamplesPerSec
)
371 copyWavFormat (format
, (WAVEFORMATEX
*) nearestFormat
);
375 CoTaskMemFree (nearestFormat
);
377 REFERENCE_TIME defaultPeriod
= 0, minPeriod
= 0;
378 if (useExclusiveMode
)
379 check (client
->GetDevicePeriod (&defaultPeriod
, &minPeriod
));
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
);
398 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIDeviceBase
);
401 //==============================================================================
402 class WASAPIInputDevice
: public WASAPIDeviceBase
405 WASAPIInputDevice (const ComSmartPtr
<IMMDevice
>& device_
, const bool useExclusiveMode_
)
406 : WASAPIDeviceBase (device_
, useExclusiveMode_
),
416 bool open (const double newSampleRate
, const BigInteger
& newChannels
)
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())));
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)
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
;
470 UINT32 packetLength
= 0;
471 if (! check (captureClient
->GetNextPacketSize (&packetLength
)))
474 if (packetLength
== 0)
476 if (thread
.threadShouldExit()
477 || WaitForSingleObject (clientEvent
, 1000) == WAIT_TIMEOUT
)
484 UINT32 numSamplesAvailable
;
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
;
516 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIInputDevice
);
519 //==============================================================================
520 class WASAPIOutputDevice
: public WASAPIDeviceBase
523 WASAPIOutputDevice (const ComSmartPtr
<IMMDevice
>& device_
, const bool useExclusiveMode_
)
524 : WASAPIDeviceBase (device_
, useExclusiveMode_
)
528 ~WASAPIOutputDevice()
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())));
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)
567 while (bufferSize
> 0)
570 if (! check (client
->GetCurrentPadding (&padding
)))
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
)
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
;
603 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIOutputDevice
);
606 //==============================================================================
607 class WASAPIAudioIODevice
: public AudioIODevice
,
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_
),
622 currentBufferSizeSamples (0),
623 currentSampleRate (0),
628 ~WASAPIAudioIODevice()
635 latencyIn
= latencyOut
= 0;
636 Array
<double> ratesIn
, ratesOut
;
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
);
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
);
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);
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
));
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
));
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
)
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!";
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!";
738 if (outputDevice
!= nullptr && ! outputDevice
->open (currentSampleRate
, outputChannels
))
741 lastError
= "Couldn't open the output device!";
745 if (inputDevice
!= nullptr) ResetEvent (inputDevice
->clientEvent
);
746 if (outputDevice
!= nullptr) ResetEvent (outputDevice
->clientEvent
);
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
772 signalThreadShouldExit();
774 if (inputDevice
!= nullptr) SetEvent (inputDevice
->clientEvent
);
775 if (outputDevice
!= nullptr) SetEvent (outputDevice
->clientEvent
);
779 if (inputDevice
!= nullptr) inputDevice
->close();
780 if (outputDevice
!= nullptr) outputDevice
->close();
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..
799 call
->audioDeviceAboutToStart (this);
801 const ScopedLock
sl (startStopLock
);
811 AudioIODeviceCallback
* const callbackLocal
= callback
;
814 const ScopedLock
sl (startStopLock
);
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)
832 HANDLE h
= avSetMmThreadCharacteristics (L
"Pro Audio", &dummy
);
835 avSetMmThreadPriority (h
, AVRT_PRIORITY_NORMAL
);
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();
854 while (! threadShouldExit())
856 if (inputDevice
!= nullptr)
858 inputDevice
->copyBuffers (inputBuffers
, numInputBuffers
, bufferSize
, *this);
860 if (threadShouldExit())
863 if (inputDevice
->sampleRateHasChanged
)
864 sampleRateChanged
= true;
869 const ScopedLock
sl (startStopLock
);
872 callback
->audioDeviceIOCallback (const_cast <const float**> (inputBuffers
), numInputBuffers
,
873 outputBuffers
, numOutputBuffers
, bufferSize
);
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
895 //==============================================================================
896 String outputDeviceId
, inputDeviceId
;
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
;
911 bool isOpen_
, isStarted
;
912 int currentBufferSizeSamples
;
913 double currentSampleRate
;
915 AudioIODeviceCallback
* callback
;
916 CriticalSection startStopLock
;
918 //==============================================================================
921 ComSmartPtr
<IMMDeviceEnumerator
> enumerator
;
922 if (! check (enumerator
.CoCreateInstance (__uuidof (MMDeviceEnumerator
))))
925 ComSmartPtr
<IMMDeviceCollection
> deviceCollection
;
926 if (! check (enumerator
->EnumAudioEndpoints (eAll
, DEVICE_STATE_ACTIVE
, deviceCollection
.resetAndGetPointerAddress())))
929 UINT32 numDevices
= 0;
930 if (! check (deviceCollection
->GetCount (&numDevices
)))
933 for (UINT32 i
= 0; i
< numDevices
; ++i
)
935 ComSmartPtr
<IMMDevice
> device
;
936 if (! check (deviceCollection
->Item (i
, device
.resetAndGetPointerAddress())))
939 const String
deviceId (getDeviceID (device
));
940 if (deviceId
.isEmpty())
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
965 WASAPIAudioIODeviceType()
966 : AudioIODeviceType ("Windows Audio"),
967 DeviceChangeDetector (L
"Windows Audio"),
972 //==============================================================================
973 void scanForDevices()
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
994 int getDefaultDeviceIndex (bool /*forInput*/) const
996 jassert (hasScanned
); // need to call scanForDevices() before doing this
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
1025 outputDeviceIds
[outputIndex
],
1026 inputDeviceIds
[inputIndex
],
1029 if (! device
->initialise())
1033 return device
.release();
1036 //==============================================================================
1037 StringArray outputDeviceNames
, outputDeviceIds
;
1038 StringArray inputDeviceNames
, inputDeviceIds
;
1043 //==============================================================================
1044 static String
getDefaultEndpoint (IMMDeviceEnumerator
* const enumerator
, const bool forCapture
)
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
)))
1055 CoTaskMemFree (deviceId
);
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
))))
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
))))
1084 for (UINT32 i
= 0; i
< numDevices
; ++i
)
1086 ComSmartPtr
<IMMDevice
> device
;
1087 if (! check (deviceCollection
->Item (i
, device
.resetAndGetPointerAddress())))
1091 if (! (check (device
->GetState (&state
)) && state
== DEVICE_STATE_ACTIVE
))
1094 const String
deviceId (getDeviceID (device
));
1098 ComSmartPtr
<IPropertyStore
> properties
;
1099 if (! check (device
->OpenPropertyStore (STGM_READ
, properties
.resetAndGetPointerAddress())))
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
)
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();