1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Windows: Added support for IAudioClient3 shared stream low latency mode in the WASAPI audio device, removed JUCE_WASAPI_EXCLUSIVE config flag and refactored AudioIODeviceType creation

This commit is contained in:
ed 2020-09-04 16:51:39 +01:00
parent b01e9276ba
commit 6195a5ab60
17 changed files with 482 additions and 316 deletions

View file

@ -4,6 +4,33 @@ JUCE breaking changes
Develop
=======
Change
------
The JUCE_WASAPI_EXCLUSIVE flag has been removed from juce_audio_devices and all
available WASAPI audio device modes (shared, shared low latency and exclusive)
are available by default when JUCE_WASAPI is enabled. The
AudioIODeviceType::createAudioIODeviceType_WASAPI() method which takes a single
boolean argument has also been deprecated in favour of a new method which takes
a WASAPIDeviceMode enum.
Possible Issues
---------------
Code that relied on the JUCE_WASAPI_EXCLUSIVE flag to disable WASAPI exclusive
mode will no longer work.
Workaround
----------
Override the AudioDeviceManager::createAudioDeviceTypes() method to omit the
WASAPI exclusive mode device if you do not want it to be available.
Rationale
---------
JUCE now supports shared low latency WASAPI audio devices via the AudioClient3
interface and instead of adding an additional compile time config flag to
enable this functionality, which adds complexity to the build process when not
using the Projucer, JUCE makes all WASAPI device modes available by default.
Change
------
The fields representing Mac OS X 10.4 to 10.6 inclusive have been removed from

View file

@ -177,8 +177,9 @@ static void addIfNotNull (OwnedArray<AudioIODeviceType>& list, AudioIODeviceType
void AudioDeviceManager::createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& list)
{
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (false));
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (true));
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode::shared));
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode::exclusive));
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode::sharedLowLatency));
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound());
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO());
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio());

View file

@ -42,47 +42,104 @@ void AudioIODeviceType::callDeviceChangeListeners()
}
//==============================================================================
#if ! JUCE_MAC
#if JUCE_MAC
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return new CoreAudioClasses::CoreAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; }
#endif
#if ! JUCE_IOS
#if JUCE_IOS
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return new iOSAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; }
#endif
#if ! (JUCE_WINDOWS && JUCE_WASAPI)
#if JUCE_WINDOWS && JUCE_WASAPI
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode deviceMode)
{
auto windowsVersion = SystemStats::getOperatingSystemType();
if (windowsVersion < SystemStats::WinVista
|| (WasapiClasses::isLowLatencyMode (deviceMode) && windowsVersion < SystemStats::Windows10))
return nullptr;
return new WasapiClasses::WASAPIAudioIODeviceType (deviceMode);
}
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool exclusiveMode)
{
return createAudioIODeviceType_WASAPI (exclusiveMode ? WASAPIDeviceMode::exclusive
: WASAPIDeviceMode::shared);
}
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode) { return nullptr; }
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool) { return nullptr; }
#endif
#if ! (JUCE_WINDOWS && JUCE_DIRECTSOUND)
#if JUCE_WINDOWS && JUCE_DIRECTSOUND
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return new DSoundAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; }
#endif
#if ! (JUCE_WINDOWS && JUCE_ASIO)
#if JUCE_WINDOWS && JUCE_ASIO
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return new ASIOAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; }
#endif
#if ! (JUCE_LINUX && JUCE_ALSA)
#if JUCE_LINUX && JUCE_ALSA
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return createAudioIODeviceType_ALSA_PCMDevices(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; }
#endif
#if ! (JUCE_LINUX && JUCE_JACK)
#if JUCE_LINUX && JUCE_JACK
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return new JackAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; }
#endif
#if ! (JUCE_LINUX && JUCE_BELA)
#if JUCE_LINUX && JUCE_BELA
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return new BelaAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return nullptr; }
#endif
#if ! JUCE_ANDROID
#if JUCE_ANDROID
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android()
{
#if JUCE_USE_ANDROID_OBOE
if (isOboeAvailable())
return nullptr;
#endif
#if JUCE_USE_ANDROID_OPENSLES
if (isOpenSLAvailable())
return nullptr;
#endif
return new AndroidAudioIODeviceType();
}
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; }
#endif
#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES)
#if JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES()
{
return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr;
}
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; }
#endif
#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OBOE)
#if JUCE_ANDROID && JUCE_USE_ANDROID_OBOE
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe()
{
return isOboeAvailable() ? new OboeAudioIODeviceType() : nullptr;
}
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() { return nullptr; }
#endif

View file

@ -149,8 +149,8 @@ public:
static AudioIODeviceType* createAudioIODeviceType_CoreAudio();
/** Creates an iOS device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_iOSAudio();
/** Creates a WASAPI device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode);
/** Creates a WASAPI device type in the specified mode if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_WASAPI (WASAPIDeviceMode deviceMode);
/** Creates a DirectSound device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_DirectSound();
/** Creates an ASIO device type if it's available on this platform, or returns null. */
@ -168,6 +168,9 @@ public:
/** Creates a Bela device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_Bela();
/** This method has been deprecated. You should call the method which takes a WASAPIDeviceMode instead. */
JUCE_DEPRECATED (static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode));
protected:
explicit AudioIODeviceType (const String& typeName);

View file

@ -45,6 +45,8 @@
#include "juce_audio_devices.h"
#include "native/juce_MidiDataConcatenator.h"
//==============================================================================
#if JUCE_MAC
#define Point CarbonDummyPointName
@ -55,6 +57,9 @@
#undef Point
#undef Component
#include "native/juce_mac_CoreAudio.cpp"
#include "native/juce_mac_CoreMidi.cpp"
#elif JUCE_IOS
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
@ -64,10 +69,18 @@
#import <CoreMIDI/MIDINetworkSession.h>
#endif
#include "native/juce_ios_Audio.cpp"
#include "native/juce_mac_CoreMidi.cpp"
//==============================================================================
#elif JUCE_WINDOWS
#if JUCE_WASAPI
#include <mmreg.h>
#include "native/juce_win32_WASAPI.cpp"
#endif
#if JUCE_DIRECTSOUND
#include "native/juce_win32_DirectSound.cpp"
#endif
#if JUCE_USE_WINRT_MIDI && JUCE_MSVC
@ -93,6 +106,8 @@
JUCE_END_IGNORE_WARNINGS_MSVC
#endif
#include "native/juce_win32_Midi.cpp"
#if JUCE_ASIO
/* This is very frustrating - we only need to use a handful of definitions from
a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy
@ -114,6 +129,7 @@
needed - so to simplify things, you could just copy these into your JUCE directory).
*/
#include <iasiodrv.h>
#include "native/juce_win32_ASIO.cpp"
#endif
//==============================================================================
@ -128,6 +144,7 @@
just set the JUCE_ALSA flag to 0.
*/
#include <alsa/asoundlib.h>
#include "native/juce_linux_ALSA.cpp"
#endif
#if JUCE_JACK
@ -140,6 +157,7 @@
JUCE with low latency audio support, just set the JUCE_JACK flag to 0.
*/
#include <jack/jack.h>
#include "native/juce_linux_JackAudio.cpp"
#endif
#if JUCE_BELA
@ -149,17 +167,29 @@
*/
#include <Bela.h>
#include <Midi.h>
#include "native/juce_linux_Bela.cpp"
#endif
#undef SIZEOF
#if ! JUCE_BELA
#include "native/juce_linux_Midi.cpp"
#endif
//==============================================================================
#elif JUCE_ANDROID
#include "native/juce_android_Audio.cpp"
#include "native/juce_android_Midi.cpp"
#if JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE
#include "native/juce_android_HighPerformanceAudioHelpers.h"
#if JUCE_USE_ANDROID_OPENSLES
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <SLES/OpenSLES_AndroidConfiguration.h>
#include "native/juce_android_OpenSL.cpp"
#endif
#if JUCE_USE_ANDROID_OBOE
@ -174,75 +204,7 @@
"-Wshadow-field")
#include <oboe/Oboe.h>
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#endif
#endif
#include "audio_io/juce_AudioDeviceManager.cpp"
#include "audio_io/juce_AudioIODevice.cpp"
#include "audio_io/juce_AudioIODeviceType.cpp"
#include "midi_io/juce_MidiMessageCollector.cpp"
#include "midi_io/juce_MidiDevices.cpp"
#include "sources/juce_AudioSourcePlayer.cpp"
#include "sources/juce_AudioTransportSource.cpp"
#include "native/juce_MidiDataConcatenator.h"
//==============================================================================
#if JUCE_MAC
#include "native/juce_mac_CoreAudio.cpp"
#include "native/juce_mac_CoreMidi.cpp"
//==============================================================================
#elif JUCE_IOS
#include "native/juce_ios_Audio.cpp"
#include "native/juce_mac_CoreMidi.cpp"
//==============================================================================
#elif JUCE_WINDOWS
#if JUCE_WASAPI
#include "native/juce_win32_WASAPI.cpp"
#endif
#if JUCE_DIRECTSOUND
#include "native/juce_win32_DirectSound.cpp"
#endif
#include "native/juce_win32_Midi.cpp"
#if JUCE_ASIO
#include "native/juce_win32_ASIO.cpp"
#endif
//==============================================================================
#elif JUCE_LINUX
#if JUCE_ALSA
#include "native/juce_linux_ALSA.cpp"
#endif
#if JUCE_JACK
#include "native/juce_linux_JackAudio.cpp"
#endif
#if JUCE_BELA
#include "native/juce_linux_Bela.cpp"
#else
#include "native/juce_linux_Midi.cpp"
#endif
//==============================================================================
#elif JUCE_ANDROID
#include "native/juce_android_Audio.cpp"
#include "native/juce_android_Midi.cpp"
#if JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE
#include "native/juce_android_HighPerformanceAudioHelpers.h"
#if JUCE_USE_ANDROID_OPENSLES
#include "native/juce_android_OpenSL.cpp"
#endif
#if JUCE_USE_ANDROID_OBOE
#include "native/juce_android_Oboe.cpp"
#endif
#endif
@ -259,3 +221,11 @@ namespace juce
bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; }
}
#endif
#include "audio_io/juce_AudioDeviceManager.cpp"
#include "audio_io/juce_AudioIODevice.cpp"
#include "audio_io/juce_AudioIODeviceType.cpp"
#include "midi_io/juce_MidiMessageCollector.cpp"
#include "midi_io/juce_MidiDevices.cpp"
#include "sources/juce_AudioSourcePlayer.cpp"
#include "sources/juce_AudioTransportSource.cpp"

View file

@ -88,21 +88,12 @@
#endif
/** Config: JUCE_WASAPI
Enables WASAPI audio devices (Windows Vista and above). See also the
JUCE_WASAPI_EXCLUSIVE flag.
Enables WASAPI audio devices (Windows Vista and above).
*/
#ifndef JUCE_WASAPI
#define JUCE_WASAPI 1
#endif
/** Config: JUCE_WASAPI_EXCLUSIVE
Enables WASAPI audio devices in exclusive mode (Windows Vista and above).
*/
#ifndef JUCE_WASAPI_EXCLUSIVE
#define JUCE_WASAPI_EXCLUSIVE 0
#endif
/** Config: JUCE_DIRECTSOUND
Enables DirectSound audio (MS Windows only).
*/
@ -174,6 +165,19 @@
//==============================================================================
#include "midi_io/juce_MidiDevices.h"
#include "midi_io/juce_MidiMessageCollector.h"
/** Available modes for the WASAPI audio device.
Pass one of these to the AudioIODeviceType::createAudioIODeviceType_WASAPI()
method to create a WASAPI AudioIODeviceType object in this mode.
*/
enum class WASAPIDeviceMode
{
shared,
exclusive,
sharedLowLatency
};
#include "audio_io/juce_AudioIODevice.h"
#include "audio_io/juce_AudioIODeviceType.h"
#include "audio_io/juce_SystemAudioVolume.h"

View file

@ -478,19 +478,4 @@ private:
extern bool isOboeAvailable();
extern bool isOpenSLAvailable();
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android()
{
#if JUCE_USE_ANDROID_OBOE
if (isOboeAvailable())
return nullptr;
#endif
#if JUCE_USE_ANDROID_OPENSLES
if (isOpenSLAvailable())
return nullptr;
#endif
return new AndroidAudioIODeviceType();
}
} // namespace juce

View file

@ -1310,15 +1310,8 @@ public:
const char* const OboeAudioIODevice::oboeTypeName = "Android Oboe";
//==============================================================================
bool isOboeAvailable() { return OboeAudioIODeviceType::isOboeAvailable(); }
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe()
{
return isOboeAvailable() ? new OboeAudioIODeviceType() : nullptr;
}
//==============================================================================
class OboeRealtimeThread : private oboe::AudioStreamCallback
{

View file

@ -1120,11 +1120,6 @@ const char* const OpenSLAudioIODevice::openSLTypeName = "Android OpenSL";
//==============================================================================
bool isOpenSLAvailable() { return OpenSLAudioDeviceType::isOpenSLAvailable(); }
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES()
{
return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr;
}
//==============================================================================
class SLRealtimeThread
{

View file

@ -1431,12 +1431,6 @@ void iOSAudioIODeviceType::handleAsyncUpdate()
callDeviceChangeListeners();
}
//==============================================================================
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio()
{
return new iOSAudioIODeviceType();
}
//==============================================================================
AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; }
AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; }

View file

@ -1299,9 +1299,4 @@ AudioIODeviceType* createAudioIODeviceType_ALSA_PCMDevices()
return new ALSAAudioIODeviceType (false, "ALSA");
}
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA()
{
return createAudioIODeviceType_ALSA_PCMDevices();
}
} // namespace juce

View file

@ -521,12 +521,6 @@ struct BelaAudioIODeviceType : public AudioIODeviceType
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODeviceType)
};
//==============================================================================
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela()
{
return new BelaAudioIODeviceType();
}
//==============================================================================
MidiInput::MidiInput (const String& deviceName, const String& deviceID)
: deviceInfo (deviceName, deviceID)

View file

@ -667,10 +667,4 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType)
};
//==============================================================================
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK()
{
return new JackAudioIODeviceType();
}
} // namespace juce

View file

@ -2234,12 +2234,6 @@ private:
};
//==============================================================================
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio()
{
return new CoreAudioClasses::CoreAudioIODeviceType();
}
#undef JUCE_COREAUDIOLOG
} // namespace juce

View file

@ -1640,9 +1640,4 @@ void sendASIODeviceChangeToListeners (ASIOAudioIODeviceType* type)
type->sendDeviceChangeToListeners();
}
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO()
{
return new ASIOAudioIODeviceType();
}
} // namespace juce

View file

@ -1282,10 +1282,4 @@ private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType)
};
//==============================================================================
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound()
{
return new DSoundAudioIODeviceType();
}
} // namespace juce

View file

@ -208,6 +208,29 @@ enum AUDCLNT_SHAREMODE
AUDCLNT_SHAREMODE_EXCLUSIVE
};
enum AUDIO_STREAM_CATEGORY
{
AudioCategory_Other = 0,
AudioCategory_ForegroundOnlyMedia,
AudioCategory_BackgroundCapableMedia,
AudioCategory_Communications,
AudioCategory_Alerts,
AudioCategory_SoundEffects,
AudioCategory_GameEffects,
AudioCategory_GameMedia,
AudioCategory_GameChat,
AudioCategory_Speech,
AudioCategory_Movie,
AudioCategory_Media
};
struct AudioClientProperties
{
UINT32 cbSize;
BOOL bIsOffload;
AUDIO_STREAM_CATEGORY eCategory;
};
JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2")
{
JUCE_COMCALL Initialize (AUDCLNT_SHAREMODE, DWORD, REFERENCE_TIME, REFERENCE_TIME, const WAVEFORMATEX*, LPCGUID) = 0;
@ -224,6 +247,20 @@ JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2")
JUCE_COMCALL GetService (REFIID, void**) = 0;
};
JUCE_COMCLASS (IAudioClient2, "726778CD-F60A-4eda-82DE-E47610CD78AA") : public IAudioClient
{
JUCE_COMCALL IsOffloadCapable (AUDIO_STREAM_CATEGORY, BOOL*) = 0;
JUCE_COMCALL SetClientProperties (const AudioClientProperties*) = 0;
JUCE_COMCALL GetBufferSizeLimits (const WAVEFORMATEX*, BOOL, REFERENCE_TIME*, REFERENCE_TIME*) = 0;
};
JUCE_COMCLASS (IAudioClient3, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") : public IAudioClient2
{
JUCE_COMCALL GetSharedModeEnginePeriod (const WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32*) = 0;
JUCE_COMCALL GetCurrentSharedModeEnginePeriod (WAVEFORMATEX**, UINT32*) = 0;
JUCE_COMCALL InitializeSharedAudioStream (DWORD, UINT32, const WAVEFORMATEX*, LPCGUID) = 0;
};
JUCE_IUNKNOWNCLASS (IAudioCaptureClient, "C8ADBD64-E71E-48a0-A4DE-185C395CD317")
{
JUCE_COMCALL GetBuffer (BYTE**, UINT32*, DWORD*, UINT64*, UINT64*) = 0;
@ -322,7 +359,7 @@ String getDeviceID (IMMDevice* device)
return s;
}
EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device)
static EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device)
{
EDataFlow flow = eRender;
ComSmartPtr<IMMEndpoint> endPoint;
@ -332,88 +369,64 @@ EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device)
return flow;
}
int refTimeToSamples (const REFERENCE_TIME& t, double sampleRate) noexcept
static int refTimeToSamples (const REFERENCE_TIME& t, double sampleRate) noexcept
{
return roundToInt (sampleRate * ((double) t) * 0.0000001);
}
REFERENCE_TIME samplesToRefTime (int numSamples, double sampleRate) noexcept
static REFERENCE_TIME samplesToRefTime (int numSamples, double sampleRate) noexcept
{
return (REFERENCE_TIME) ((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5);
}
void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcept
static void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcept
{
memcpy (&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof (WAVEFORMATEXTENSIBLE)
: sizeof (WAVEFORMATEX));
}
static bool isExclusiveMode (WASAPIDeviceMode deviceMode) noexcept
{
return deviceMode == WASAPIDeviceMode::exclusive;
}
static bool isLowLatencyMode (WASAPIDeviceMode deviceMode) noexcept
{
return deviceMode == WASAPIDeviceMode::sharedLowLatency;
}
static bool supportsSampleRateConversion (WASAPIDeviceMode deviceMode) noexcept
{
return deviceMode == WASAPIDeviceMode::shared;
}
//==============================================================================
class WASAPIDeviceBase
{
public:
WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode)
: device (d), useExclusiveMode (exclusiveMode)
WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
: device (d),
deviceMode (mode)
{
clientEvent = CreateEvent (nullptr, false, false, nullptr);
ComSmartPtr<IAudioClient> tempClient (createClient());
if (tempClient == nullptr)
return;
REFERENCE_TIME defaultPeriod, minPeriod;
if (! check (tempClient->GetDevicePeriod (&defaultPeriod, &minPeriod)))
return;
WAVEFORMATEX* mixFormat = nullptr;
if (! check (tempClient->GetMixFormat (&mixFormat)))
return;
WAVEFORMATEXTENSIBLE format;
copyWavFormat (format, mixFormat);
CoTaskMemFree (mixFormat);
if (! getClientMixFormat (tempClient, format))
return;
actualNumChannels = numChannels = format.Format.nChannels;
defaultSampleRate = format.Format.nSamplesPerSec;
minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate);
defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate);
rates.addUsingDefaultSort (defaultSampleRate);
mixFormatChannelMask = format.dwChannelMask;
rates.addUsingDefaultSort (defaultSampleRate);
if (useExclusiveMode
&& findSupportedFormat (tempClient, defaultSampleRate, format.dwChannelMask, format))
{
// Got a format that is supported by the device so we can ask what sample rates are supported (in whatever format)
}
for (auto rate : { 8000, 11025, 16000, 22050, 32000,
44100, 48000, 88200, 96000, 176400,
192000, 352800, 384000, 705600, 768000 })
{
if (rates.contains (rate))
continue;
format.Format.nSamplesPerSec = (DWORD) rate;
format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8);
WAVEFORMATEX* nearestFormat = nullptr;
if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE
: AUDCLNT_SHAREMODE_SHARED,
(WAVEFORMATEX*) &format,
useExclusiveMode ? nullptr
: (WAVEFORMATEX**) &nearestFormat)))
{
if (nearestFormat != nullptr)
rate = nearestFormat->nSamplesPerSec;
if (! rates.contains (rate))
rates.addUsingDefaultSort (rate);
}
CoTaskMemFree (nearestFormat);
}
querySupportedBufferSizes (format, tempClient);
querySupportedSampleRates (format, tempClient);
}
virtual ~WASAPIDeviceBase()
@ -498,11 +511,14 @@ public:
//==============================================================================
ComSmartPtr<IMMDevice> device;
ComSmartPtr<IAudioClient> client;
WASAPIDeviceMode deviceMode;
double sampleRate = 0, defaultSampleRate = 0;
int numChannels = 0, actualNumChannels = 0;
int minBufferSize = 0, defaultBufferSize = 0, latencySamples = 0;
int lowLatencyBufferSizeMultiple = 0, lowLatencyMaxBufferSize = 0;
DWORD mixFormatChannelMask = 0;
const bool useExclusiveMode;
Array<double> rates;
HANDLE clientEvent = {};
BigInteger channels;
@ -593,6 +609,94 @@ private:
return newClient;
}
static bool getClientMixFormat (ComSmartPtr<IAudioClient>& client, WAVEFORMATEXTENSIBLE& format)
{
WAVEFORMATEX* mixFormat = nullptr;
if (! check (client->GetMixFormat (&mixFormat)))
return false;
copyWavFormat (format, mixFormat);
CoTaskMemFree (mixFormat);
return true;
}
static ComSmartPtr<IAudioClient3> getClientAsVersion3 (ComSmartPtr<IAudioClient>& clientVersion1)
{
ComSmartPtr<IAudioClient3> newClient;
if (clientVersion1 != nullptr)
clientVersion1.QueryInterface (__uuidof (IAudioClient3), newClient);
return newClient;
}
//==============================================================================
void querySupportedBufferSizes (WAVEFORMATEXTENSIBLE format, ComSmartPtr<IAudioClient>& audioClient)
{
if (isLowLatencyMode (deviceMode))
{
if (auto audioClient3 = getClientAsVersion3 (audioClient))
{
UINT32 defaultPeriod = 0, fundamentalPeriod = 0, minPeriod = 0, maxPeriod = 0;
if (check (audioClient3->GetSharedModeEnginePeriod ((WAVEFORMATEX*) &format,
&defaultPeriod,
&fundamentalPeriod,
&minPeriod,
&maxPeriod)))
{
minBufferSize = minPeriod;
defaultBufferSize = defaultPeriod;
lowLatencyMaxBufferSize = maxPeriod;
lowLatencyBufferSizeMultiple = fundamentalPeriod;
}
}
}
else
{
REFERENCE_TIME defaultPeriod, minPeriod;
if (! check (audioClient->GetDevicePeriod (&defaultPeriod, &minPeriod)))
return;
minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate);
defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate);
}
}
void querySupportedSampleRates (WAVEFORMATEXTENSIBLE format, ComSmartPtr<IAudioClient>& audioClient)
{
for (auto rate : { 8000, 11025, 16000, 22050, 32000,
44100, 48000, 88200, 96000, 176400,
192000, 352800, 384000, 705600, 768000 })
{
if (rates.contains (rate))
continue;
format.Format.nSamplesPerSec = (DWORD) rate;
format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8);
WAVEFORMATEX* nearestFormat = nullptr;
if (SUCCEEDED (audioClient->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
: AUDCLNT_SHAREMODE_SHARED,
(WAVEFORMATEX*) &format,
isExclusiveMode (deviceMode) ? nullptr
: (WAVEFORMATEX**) &nearestFormat)))
{
if (nearestFormat != nullptr)
rate = nearestFormat->nSamplesPerSec;
if (! rates.contains (rate))
rates.addUsingDefaultSort (rate);
}
CoTaskMemFree (nearestFormat);
}
}
struct AudioSampleFormat
{
bool useFloat;
@ -626,10 +730,10 @@ private:
WAVEFORMATEXTENSIBLE* nearestFormat = nullptr;
HRESULT hr = clientToUse->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE
HRESULT hr = clientToUse->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
: AUDCLNT_SHAREMODE_SHARED,
(WAVEFORMATEX*) &format,
useExclusiveMode ? nullptr
isExclusiveMode (deviceMode) ? nullptr
: (WAVEFORMATEX**) &nearestFormat);
logFailure (hr);
@ -642,7 +746,7 @@ private:
}
CoTaskMemFree (nearestFormat);
return check (hr);
return hr == S_OK;
}
bool findSupportedFormat (IAudioClient* clientToUse, double newSampleRate,
@ -666,41 +770,50 @@ private:
return false;
}
bool tryInitialisingWithBufferSize (int bufferSizeSamples)
DWORD getStreamFlags()
{
WAVEFORMATEXTENSIBLE format;
DWORD streamFlags = 0x40000; /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/
if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format))
if (supportsSampleRateConversion (deviceMode))
streamFlags |= (0x80000000 /*AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM*/
| 0x8000000); /*AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY*/
return streamFlags;
}
bool initialiseLowLatencyClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format)
{
if (auto audioClient3 = getClientAsVersion3 (client))
return check (audioClient3->InitializeSharedAudioStream (getStreamFlags(),
bufferSizeSamples,
(WAVEFORMATEX*) &format,
nullptr));
return false;
}
bool initialiseStandardClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format)
{
REFERENCE_TIME defaultPeriod = 0, minPeriod = 0;
check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
if (useExclusiveMode && bufferSizeSamples > 0)
if (isExclusiveMode (deviceMode) && bufferSizeSamples > 0)
defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec));
for (;;)
{
GUID session;
HRESULT hr = client->Initialize (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
0x40000 /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/
| 0x80000000 /*AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM*/
| 0x08000000 /*AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY*/,
auto hr = client->Initialize (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
: AUDCLNT_SHAREMODE_SHARED,
getStreamFlags(),
defaultPeriod,
useExclusiveMode ? defaultPeriod : 0,
isExclusiveMode (deviceMode) ? defaultPeriod : 0,
(WAVEFORMATEX*) &format,
&session);
if (check (hr))
{
actualNumChannels = format.Format.nChannels;
const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
bytesPerSample = format.Format.wBitsPerSample / 8;
bytesPerFrame = format.Format.nBlockAlign;
updateFormat (isFloat);
return true;
}
// Handle the "alignment dance" : http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx (see Remarks)
if (hr != MAKE_HRESULT (1, 0x889, 0x19)) // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED
@ -716,6 +829,30 @@ private:
defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec);
}
return false;
}
bool tryInitialisingWithBufferSize (int bufferSizeSamples)
{
WAVEFORMATEXTENSIBLE format;
if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format))
{
auto isInitialised = isLowLatencyMode (deviceMode) ? initialiseLowLatencyClient (bufferSizeSamples, format)
: initialiseStandardClient (bufferSizeSamples, format);
if (isInitialised)
{
actualNumChannels = format.Format.nChannels;
const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
bytesPerSample = format.Format.wBitsPerSample / 8;
bytesPerFrame = format.Format.nBlockAlign;
updateFormat (isFloat);
return true;
}
}
return false;
@ -728,8 +865,8 @@ private:
class WASAPIInputDevice : public WASAPIDeviceBase
{
public:
WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode)
: WASAPIDeviceBase (d, exclusiveMode)
WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
: WASAPIDeviceBase (d, mode)
{
}
@ -891,8 +1028,8 @@ private:
class WASAPIOutputDevice : public WASAPIDeviceBase
{
public:
WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode)
: WASAPIDeviceBase (d, exclusiveMode)
WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
: WASAPIDeviceBase (d, mode)
{
}
@ -950,7 +1087,7 @@ public:
if (numChannels <= 0)
return 0;
if (! useExclusiveMode)
if (! isExclusiveMode (deviceMode))
{
UINT32 padding = 0;
@ -972,7 +1109,7 @@ public:
while (bufferSize > 0)
{
// This is needed in order not to drop any input data if the output device endpoint buffer was full
if ((! useExclusiveMode) && inputDevice != nullptr
if ((! isExclusiveMode (deviceMode)) && inputDevice != nullptr
&& WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0)
inputDevice->handleDeviceBuffer();
@ -987,7 +1124,7 @@ public:
break;
}
if (useExclusiveMode && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
if (isExclusiveMode (deviceMode) && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
break;
uint8* outputData = nullptr;
@ -1021,12 +1158,12 @@ public:
const String& typeName,
const String& outputDeviceID,
const String& inputDeviceID,
bool exclusiveMode)
WASAPIDeviceMode mode)
: AudioIODevice (deviceName, typeName),
Thread ("JUCE WASAPI"),
outputDeviceId (outputDeviceID),
inputDeviceId (inputDeviceID),
useExclusiveMode (exclusiveMode)
deviceMode (mode)
{
}
@ -1050,16 +1187,29 @@ public:
if (inputDevice != nullptr && outputDevice != nullptr)
{
defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate);
minBufferSize = jmin (inputDevice->minBufferSize, outputDevice->minBufferSize);
minBufferSize = jmax (inputDevice->minBufferSize, outputDevice->minBufferSize);
defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize);
if (isLowLatencyMode (deviceMode))
{
lowLatencyMaxBufferSize = jmin (inputDevice->lowLatencyMaxBufferSize, outputDevice->lowLatencyMaxBufferSize);
lowLatencyBufferSizeMultiple = jmax (inputDevice->lowLatencyBufferSizeMultiple, outputDevice->lowLatencyBufferSizeMultiple);
}
sampleRates.addArray (inputDevice->rates);
if (supportsSampleRateConversion (deviceMode))
{
for (auto r : outputDevice->rates)
if (! sampleRates.contains (r))
sampleRates.addUsingDefaultSort (r);
}
else
{
sampleRates.removeValuesNotIn (outputDevice->rates);
}
}
else
{
auto* d = inputDevice != nullptr ? static_cast<WASAPIDeviceBase*> (inputDevice.get())
: static_cast<WASAPIDeviceBase*> (outputDevice.get());
@ -1067,6 +1217,13 @@ public:
defaultSampleRate = d->defaultSampleRate;
minBufferSize = d->minBufferSize;
defaultBufferSize = d->defaultBufferSize;
if (isLowLatencyMode (deviceMode))
{
lowLatencyMaxBufferSize = d->lowLatencyMaxBufferSize;
lowLatencyBufferSizeMultiple = d->lowLatencyBufferSizeMultiple;
}
sampleRates = d->rates;
}
@ -1076,6 +1233,20 @@ public:
if (minBufferSize != defaultBufferSize)
bufferSizes.addUsingDefaultSort (minBufferSize);
if (isLowLatencyMode (deviceMode))
{
auto size = minBufferSize;
while (size < lowLatencyMaxBufferSize)
{
size += lowLatencyBufferSizeMultiple;
if (! bufferSizes.contains (size))
bufferSizes.addUsingDefaultSort (size);
}
}
else
{
int n = 64;
for (int i = 0; i < 40; ++i)
{
@ -1084,6 +1255,7 @@ public:
n += (n < 512) ? 32 : (n < 1024 ? 64 : 128);
}
}
return true;
}
@ -1157,7 +1329,7 @@ public:
return lastError;
}
if (useExclusiveMode)
if (isExclusiveMode (deviceMode))
{
// This is to make sure that the callback uses actualBufferSize in case of exclusive mode
if (inputDevice != nullptr && outputDevice != nullptr && inputDevice->actualBufferSize != outputDevice->actualBufferSize)
@ -1323,7 +1495,7 @@ public:
}
else
{
if (useExclusiveMode && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0)
if (isExclusiveMode (deviceMode) && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0)
inputDevice->handleDeviceBuffer();
}
@ -1373,9 +1545,10 @@ private:
// Device stats...
std::unique_ptr<WASAPIInputDevice> inputDevice;
std::unique_ptr<WASAPIOutputDevice> outputDevice;
const bool useExclusiveMode;
WASAPIDeviceMode deviceMode;
double defaultSampleRate = 0;
int minBufferSize = 0, defaultBufferSize = 0;
int lowLatencyMaxBufferSize = 0, lowLatencyBufferSizeMultiple = 0;
int latencyIn = 0, latencyOut = 0;
Array<double> sampleRates;
Array<int> bufferSizes;
@ -1425,9 +1598,9 @@ private:
auto flow = getDataFlow (device);
if (deviceId == inputDeviceId && flow == eCapture)
inputDevice.reset (new WASAPIInputDevice (device, useExclusiveMode));
inputDevice.reset (new WASAPIInputDevice (device, deviceMode));
else if (deviceId == outputDeviceId && flow == eRender)
outputDevice.reset (new WASAPIOutputDevice (device, useExclusiveMode));
outputDevice.reset (new WASAPIOutputDevice (device, deviceMode));
}
return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk()))
@ -1484,10 +1657,10 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType,
private DeviceChangeDetector
{
public:
WASAPIAudioIODeviceType (bool exclusive)
: AudioIODeviceType (exclusive ? "Windows Audio (Exclusive Mode)" : "Windows Audio"),
WASAPIAudioIODeviceType (WASAPIDeviceMode mode)
: AudioIODeviceType (getDeviceTypename (mode)),
DeviceChangeDetector (L"Windows Audio"),
exclusiveMode (exclusive)
deviceMode (mode)
{
}
@ -1555,7 +1728,7 @@ public:
getTypeName(),
outputDeviceIds [outputIndex],
inputDeviceIds [inputIndex],
exclusiveMode));
deviceMode));
if (! device->initialise())
device = nullptr;
@ -1569,7 +1742,7 @@ public:
StringArray inputDeviceNames, inputDeviceIds;
private:
const bool exclusiveMode;
WASAPIDeviceMode deviceMode;
bool hasScanned = false;
ComSmartPtr<IMMDeviceEnumerator> enumerator;
@ -1724,6 +1897,17 @@ private:
callDeviceChangeListeners();
}
//==============================================================================
static String getDeviceTypename (WASAPIDeviceMode mode)
{
if (mode == WASAPIDeviceMode::shared) return "Windows Audio";
if (mode == WASAPIDeviceMode::sharedLowLatency) return "Windows Audio (Low Latency Mode)";
if (mode == WASAPIDeviceMode::exclusive) return "Windows Audio (Exclusive Mode)";
jassertfalse;
return {};
}
//==============================================================================
JUCE_DECLARE_WEAK_REFERENCEABLE (WASAPIAudioIODeviceType)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType)
@ -1782,19 +1966,6 @@ struct MMDeviceMasterVolume
}
//==============================================================================
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool exclusiveMode)
{
#if ! JUCE_WASAPI_EXCLUSIVE
if (exclusiveMode)
return nullptr;
#endif
return SystemStats::getOperatingSystemType() >= SystemStats::WinVista
? new WasapiClasses::WASAPIAudioIODeviceType (exclusiveMode)
: nullptr;
}
//==============================================================================
#define JUCE_SYSTEMAUDIOVOL_IMPLEMENTED 1
float JUCE_CALLTYPE SystemAudioVolume::getGain() { return WasapiClasses::MMDeviceMasterVolume().getGain(); }