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:
parent
b01e9276ba
commit
6195a5ab60
17 changed files with 482 additions and 316 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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]; }
|
||||
|
|
|
|||
|
|
@ -1299,9 +1299,4 @@ AudioIODeviceType* createAudioIODeviceType_ALSA_PCMDevices()
|
|||
return new ALSAAudioIODeviceType (false, "ALSA");
|
||||
}
|
||||
|
||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA()
|
||||
{
|
||||
return createAudioIODeviceType_ALSA_PCMDevices();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -667,10 +667,4 @@ private:
|
|||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK()
|
||||
{
|
||||
return new JackAudioIODeviceType();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -2234,12 +2234,6 @@ private:
|
|||
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio()
|
||||
{
|
||||
return new CoreAudioClasses::CoreAudioIODeviceType();
|
||||
}
|
||||
|
||||
#undef JUCE_COREAUDIOLOG
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -1640,9 +1640,4 @@ void sendASIODeviceChangeToListeners (ASIOAudioIODeviceType* type)
|
|||
type->sendDeviceChangeToListeners();
|
||||
}
|
||||
|
||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO()
|
||||
{
|
||||
return new ASIOAudioIODeviceType();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -1282,10 +1282,4 @@ private:
|
|||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound()
|
||||
{
|
||||
return new DSoundAudioIODeviceType();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
|
|
|||
|
|
@ -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,11 +730,11 @@ private:
|
|||
|
||||
WAVEFORMATEXTENSIBLE* nearestFormat = nullptr;
|
||||
|
||||
HRESULT hr = clientToUse->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE
|
||||
: AUDCLNT_SHAREMODE_SHARED,
|
||||
HRESULT hr = clientToUse->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
|
||||
: AUDCLNT_SHAREMODE_SHARED,
|
||||
(WAVEFORMATEX*) &format,
|
||||
useExclusiveMode ? nullptr
|
||||
: (WAVEFORMATEX**) &nearestFormat);
|
||||
isExclusiveMode (deviceMode) ? nullptr
|
||||
: (WAVEFORMATEX**) &nearestFormat);
|
||||
logFailure (hr);
|
||||
|
||||
if (hr == S_FALSE
|
||||
|
|
@ -642,7 +746,7 @@ private:
|
|||
}
|
||||
|
||||
CoTaskMemFree (nearestFormat);
|
||||
return check (hr);
|
||||
return hr == S_OK;
|
||||
}
|
||||
|
||||
bool findSupportedFormat (IAudioClient* clientToUse, double newSampleRate,
|
||||
|
|
@ -666,55 +770,88 @@ private:
|
|||
return false;
|
||||
}
|
||||
|
||||
DWORD getStreamFlags()
|
||||
{
|
||||
DWORD streamFlags = 0x40000; /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/
|
||||
|
||||
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 (isExclusiveMode (deviceMode) && bufferSizeSamples > 0)
|
||||
defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec));
|
||||
|
||||
for (;;)
|
||||
{
|
||||
GUID session;
|
||||
auto hr = client->Initialize (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE
|
||||
: AUDCLNT_SHAREMODE_SHARED,
|
||||
getStreamFlags(),
|
||||
defaultPeriod,
|
||||
isExclusiveMode (deviceMode) ? defaultPeriod : 0,
|
||||
(WAVEFORMATEX*) &format,
|
||||
&session);
|
||||
|
||||
if (check (hr))
|
||||
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
|
||||
break;
|
||||
|
||||
UINT32 numFrames = 0;
|
||||
if (! check (client->GetBufferSize (&numFrames)))
|
||||
break;
|
||||
|
||||
// Recreate client
|
||||
client = nullptr;
|
||||
client = createClient();
|
||||
|
||||
defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tryInitialisingWithBufferSize (int bufferSizeSamples)
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE format;
|
||||
|
||||
if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format))
|
||||
{
|
||||
REFERENCE_TIME defaultPeriod = 0, minPeriod = 0;
|
||||
auto isInitialised = isLowLatencyMode (deviceMode) ? initialiseLowLatencyClient (bufferSizeSamples, format)
|
||||
: initialiseStandardClient (bufferSizeSamples, format);
|
||||
|
||||
check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
|
||||
|
||||
if (useExclusiveMode && bufferSizeSamples > 0)
|
||||
defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec));
|
||||
|
||||
for (;;)
|
||||
if (isInitialised)
|
||||
{
|
||||
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*/,
|
||||
defaultPeriod,
|
||||
useExclusiveMode ? defaultPeriod : 0,
|
||||
(WAVEFORMATEX*) &format,
|
||||
&session);
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
break;
|
||||
|
||||
UINT32 numFrames = 0;
|
||||
if (! check (client->GetBufferSize (&numFrames)))
|
||||
break;
|
||||
|
||||
// Recreate client
|
||||
client = nullptr;
|
||||
client = createClient();
|
||||
|
||||
defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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,14 +1187,27 @@ 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);
|
||||
|
||||
for (auto r : outputDevice->rates)
|
||||
if (! sampleRates.contains (r))
|
||||
sampleRates.addUsingDefaultSort (r);
|
||||
if (supportsSampleRateConversion (deviceMode))
|
||||
{
|
||||
for (auto r : outputDevice->rates)
|
||||
if (! sampleRates.contains (r))
|
||||
sampleRates.addUsingDefaultSort (r);
|
||||
}
|
||||
else
|
||||
{
|
||||
sampleRates.removeValuesNotIn (outputDevice->rates);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -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,13 +1233,28 @@ public:
|
|||
if (minBufferSize != defaultBufferSize)
|
||||
bufferSizes.addUsingDefaultSort (minBufferSize);
|
||||
|
||||
int n = 64;
|
||||
for (int i = 0; i < 40; ++i)
|
||||
if (isLowLatencyMode (deviceMode))
|
||||
{
|
||||
if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n))
|
||||
bufferSizes.addUsingDefaultSort (n);
|
||||
auto size = minBufferSize;
|
||||
|
||||
n += (n < 512) ? 32 : (n < 1024 ? 64 : 128);
|
||||
while (size < lowLatencyMaxBufferSize)
|
||||
{
|
||||
size += lowLatencyBufferSizeMultiple;
|
||||
|
||||
if (! bufferSizes.contains (size))
|
||||
bufferSizes.addUsingDefaultSort (size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int n = 64;
|
||||
for (int i = 0; i < 40; ++i)
|
||||
{
|
||||
if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n))
|
||||
bufferSizes.addUsingDefaultSort (n);
|
||||
|
||||
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(); }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue