mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Fixes for WASAPI exclusive mode.
This commit is contained in:
parent
5c802e340c
commit
79f59afb32
3 changed files with 111 additions and 49 deletions
|
|
@ -290,7 +290,7 @@
|
|||
</GROUP>
|
||||
</GROUP>
|
||||
</MAINGROUP>
|
||||
<JUCEOPTIONS JUCE_ENABLE_LIVE_CONSTANT_EDITOR="enabled"/>
|
||||
<JUCEOPTIONS JUCE_ENABLE_LIVE_CONSTANT_EDITOR="enabled" JUCE_WASAPI_EXCLUSIVE="enabled"/>
|
||||
<MODULES>
|
||||
<MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0"/>
|
||||
<MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0"/>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ extern bool juceDemoRepaintDebuggingActive;
|
|||
#endif
|
||||
|
||||
#ifndef JUCE_WASAPI_EXCLUSIVE
|
||||
//#define JUCE_WASAPI_EXCLUSIVE
|
||||
#define JUCE_WASAPI_EXCLUSIVE 1
|
||||
#endif
|
||||
|
||||
#ifndef JUCE_DIRECTSOUND
|
||||
|
|
|
|||
|
|
@ -376,16 +376,39 @@ public:
|
|||
|
||||
rates.addUsingDefaultSort (defaultSampleRate);
|
||||
|
||||
static const int ratesToTest[] = { 44100, 48000, 88200, 96000, 176400, 192000 };
|
||||
while (useExclusiveMode)
|
||||
{
|
||||
bool isOK = false;
|
||||
|
||||
for (int bitsToTest = format.Format.wBitsPerSample; bitsToTest >= 16; bitsToTest = ((bitsToTest - 1) & ~7))
|
||||
{
|
||||
format.Samples.wValidBitsPerSample = (bitsToTest + 7) & ~7;
|
||||
|
||||
if (SUCCEEDED (tempClient->IsFormatSupported (AUDCLNT_SHAREMODE_EXCLUSIVE, (WAVEFORMATEX*) &format, 0)))
|
||||
{
|
||||
isOK = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isOK || format.SubFormat != KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
|
||||
break;
|
||||
|
||||
format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; // Try PCM as a fallback from float..
|
||||
}
|
||||
|
||||
static const int ratesToTest[] = { 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 };
|
||||
|
||||
for (int i = 0; i < numElementsInArray (ratesToTest); ++i)
|
||||
{
|
||||
if (ratesToTest[i] == defaultSampleRate)
|
||||
if (rates.contains (ratesToTest[i]))
|
||||
continue;
|
||||
|
||||
format.Format.nSamplesPerSec = (DWORD) ratesToTest[i];
|
||||
format.Format.nSamplesPerSec = (DWORD) ratesToTest[i];
|
||||
format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8);
|
||||
|
||||
if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
|
||||
if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE
|
||||
: AUDCLNT_SHAREMODE_SHARED,
|
||||
(WAVEFORMATEX*) &format, 0)))
|
||||
if (! rates.contains (ratesToTest[i]))
|
||||
rates.addUsingDefaultSort (ratesToTest[i]);
|
||||
|
|
@ -400,7 +423,7 @@ public:
|
|||
|
||||
bool isOk() const noexcept { return defaultBufferSize > 0 && defaultSampleRate > 0; }
|
||||
|
||||
bool openClient (const double newSampleRate, const BigInteger& newChannels)
|
||||
bool openClient (const double newSampleRate, const BigInteger& newChannels, const int bufferSizeSamples)
|
||||
{
|
||||
sampleRate = newSampleRate;
|
||||
channels = newChannels;
|
||||
|
|
@ -413,8 +436,11 @@ public:
|
|||
client = createClient();
|
||||
|
||||
if (client != nullptr
|
||||
&& (tryInitialisingWithFormat (true, 4) || tryInitialisingWithFormat (false, 4)
|
||||
|| tryInitialisingWithFormat (false, 3) || tryInitialisingWithFormat (false, 2)))
|
||||
&& (tryInitialisingWithFormat (true, 4, 4, bufferSizeSamples)
|
||||
|| tryInitialisingWithFormat (false, 4, 4, bufferSizeSamples)
|
||||
|| tryInitialisingWithFormat (false, 3, 4, bufferSizeSamples)
|
||||
|| tryInitialisingWithFormat (false, 3, 3, bufferSizeSamples)
|
||||
|| tryInitialisingWithFormat (false, 2, 2, bufferSizeSamples)))
|
||||
{
|
||||
sampleRateHasChanged = false;
|
||||
|
||||
|
|
@ -536,7 +562,7 @@ private:
|
|||
return client;
|
||||
}
|
||||
|
||||
bool tryInitialisingWithFormat (const bool useFloat, const int bytesPerSampleToTry)
|
||||
bool tryInitialisingWithFormat (const bool useFloat, const int bytesPerSampleToTry, const int bytesPerSampleContainer, const int bufferSizeSamples)
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE format;
|
||||
zerostruct (format);
|
||||
|
|
@ -553,11 +579,11 @@ private:
|
|||
|
||||
format.Format.nSamplesPerSec = (DWORD) sampleRate;
|
||||
format.Format.nChannels = (WORD) numChannels;
|
||||
format.Format.wBitsPerSample = (WORD) (8 * bytesPerSampleToTry);
|
||||
format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * numChannels * bytesPerSampleToTry);
|
||||
format.Format.nBlockAlign = (WORD) (numChannels * bytesPerSampleToTry);
|
||||
format.Format.wBitsPerSample = (WORD) (8 * bytesPerSampleContainer);
|
||||
format.Samples.wValidBitsPerSample = (WORD) (8 * bytesPerSampleToTry);
|
||||
format.Format.nBlockAlign = (WORD) (format.Format.nChannels * format.Format.wBitsPerSample / 8);
|
||||
format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nBlockAlign);
|
||||
format.SubFormat = useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
|
||||
format.Samples.wValidBitsPerSample = format.Format.wBitsPerSample;
|
||||
format.dwChannelMask = mixFormatChannelMask;
|
||||
|
||||
WAVEFORMATEXTENSIBLE* nearestFormat = nullptr;
|
||||
|
|
@ -576,22 +602,49 @@ private:
|
|||
|
||||
CoTaskMemFree (nearestFormat);
|
||||
|
||||
REFERENCE_TIME defaultPeriod = 0, minPeriod = 0;
|
||||
if (useExclusiveMode)
|
||||
check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
|
||||
|
||||
GUID session;
|
||||
if (hr == S_OK
|
||||
&& check (client->Initialize (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
|
||||
0x40000 /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/,
|
||||
defaultPeriod, defaultPeriod, (WAVEFORMATEX*) &format, &session)))
|
||||
if (hr == S_OK)
|
||||
{
|
||||
actualNumChannels = format.Format.nChannels;
|
||||
const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
bytesPerSample = format.Format.wBitsPerSample / 8;
|
||||
REFERENCE_TIME defaultPeriod = 0, minPeriod = 0;
|
||||
|
||||
updateFormat (isFloat);
|
||||
return true;
|
||||
if (useExclusiveMode)
|
||||
{
|
||||
check (client->GetDevicePeriod (&defaultPeriod, &minPeriod));
|
||||
|
||||
if (bufferSizeSamples > 0)
|
||||
defaultPeriod = jmax (minPeriod, (REFERENCE_TIME) ((10000.0 * 1000.0 / format.Format.nSamplesPerSec * bufferSizeSamples) + 0.5));
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
GUID session;
|
||||
hr = client->Initialize (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED,
|
||||
0x40000 /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/,
|
||||
defaultPeriod, defaultPeriod, (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;
|
||||
|
||||
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 = (REFERENCE_TIME) ((10000.0 * 1000.0 / format.Format.nSamplesPerSec * numFrames) + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -615,12 +668,13 @@ public:
|
|||
close();
|
||||
}
|
||||
|
||||
bool open (const double newSampleRate, const BigInteger& newChannels)
|
||||
bool open (const double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples)
|
||||
{
|
||||
reservoirSize = 0;
|
||||
reservoirCapacity = 16384;
|
||||
reservoir.setSize (actualNumChannels * reservoirCapacity * sizeof (float));
|
||||
return openClient (newSampleRate, newChannels)
|
||||
|
||||
return openClient (newSampleRate, newChannels, bufferSizeSamples)
|
||||
&& (numChannels == 0 || check (client->GetService (__uuidof (IAudioCaptureClient),
|
||||
(void**) captureClient.resetAndGetPointerAddress())));
|
||||
}
|
||||
|
|
@ -670,7 +724,7 @@ public:
|
|||
else
|
||||
{
|
||||
UINT32 packetLength = 0;
|
||||
if (! check (captureClient->GetNextPacketSize (&packetLength)))
|
||||
if (! (useExclusiveMode || check (captureClient->GetNextPacketSize (&packetLength))))
|
||||
break;
|
||||
|
||||
if (packetLength == 0)
|
||||
|
|
@ -679,7 +733,8 @@ public:
|
|||
|| WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
|
||||
break;
|
||||
|
||||
continue;
|
||||
if (! useExclusiveMode)
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8* inputData;
|
||||
|
|
@ -732,10 +787,11 @@ public:
|
|||
close();
|
||||
}
|
||||
|
||||
bool open (const double newSampleRate, const BigInteger& newChannels)
|
||||
bool open (const double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples)
|
||||
{
|
||||
return openClient (newSampleRate, newChannels)
|
||||
&& (numChannels == 0 || check (client->GetService (__uuidof (IAudioRenderClient), (void**) renderClient.resetAndGetPointerAddress())));
|
||||
return openClient (newSampleRate, newChannels, bufferSizeSamples)
|
||||
&& (numChannels == 0 || check (client->GetService (__uuidof (IAudioRenderClient),
|
||||
(void**) renderClient.resetAndGetPointerAddress())));
|
||||
}
|
||||
|
||||
void close()
|
||||
|
|
@ -753,10 +809,10 @@ public:
|
|||
|
||||
void updateFormat (bool isFloat)
|
||||
{
|
||||
if (isFloat) updateFormatWithType ((AudioData::Float32*) 0);
|
||||
else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) 0);
|
||||
else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) 0);
|
||||
else updateFormatWithType ((AudioData::Int16*) 0);
|
||||
if (isFloat) updateFormatWithType ((AudioData::Float32*) nullptr);
|
||||
else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) nullptr);
|
||||
else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) nullptr);
|
||||
else updateFormatWithType ((AudioData::Int16*) nullptr);
|
||||
}
|
||||
|
||||
void copyBuffers (const float** const srcBuffers, const int numSrcBuffers, int bufferSize, Thread& thread)
|
||||
|
|
@ -768,17 +824,21 @@ public:
|
|||
|
||||
while (bufferSize > 0)
|
||||
{
|
||||
// In exclusive mode, GetCurrentPadding ALWAYS returns the buffer size, so we need
|
||||
// to wait for the event before getting the buffer.
|
||||
if (useExclusiveMode && (thread.threadShouldExit() || WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT))
|
||||
break;
|
||||
|
||||
UINT32 padding = 0;
|
||||
if (! check (client->GetCurrentPadding (&padding)))
|
||||
return;
|
||||
|
||||
int samplesToDo = useExclusiveMode ? bufferSize
|
||||
int samplesToDo = useExclusiveMode ? actualBufferSize
|
||||
: jmin ((int) (actualBufferSize - padding), bufferSize);
|
||||
|
||||
if (samplesToDo <= 0)
|
||||
{
|
||||
if (thread.threadShouldExit()
|
||||
|| WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
|
||||
if (thread.threadShouldExit() || WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT)
|
||||
break;
|
||||
|
||||
continue;
|
||||
|
|
@ -812,13 +872,14 @@ class WASAPIAudioIODevice : public AudioIODevice,
|
|||
{
|
||||
public:
|
||||
WASAPIAudioIODevice (const String& deviceName,
|
||||
const String& outputDeviceId_,
|
||||
const String& inputDeviceId_,
|
||||
const String& typeName,
|
||||
const String& outputDeviceID,
|
||||
const String& inputDeviceID,
|
||||
const bool exclusiveMode)
|
||||
: AudioIODevice (deviceName, "Windows Audio"),
|
||||
: AudioIODevice (deviceName, typeName),
|
||||
Thread ("Juce WASAPI"),
|
||||
outputDeviceId (outputDeviceId_),
|
||||
inputDeviceId (inputDeviceId_),
|
||||
outputDeviceId (outputDeviceID),
|
||||
inputDeviceId (inputDeviceID),
|
||||
useExclusiveMode (exclusiveMode),
|
||||
isOpen_ (false),
|
||||
isStarted (false),
|
||||
|
|
@ -932,13 +993,13 @@ public:
|
|||
lastKnownInputChannels = inputChannels;
|
||||
lastKnownOutputChannels = outputChannels;
|
||||
|
||||
if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels))
|
||||
if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels, bufferSizeSamples))
|
||||
{
|
||||
lastError = TRANS("Couldn't open the input device!");
|
||||
return lastError;
|
||||
}
|
||||
|
||||
if (outputDevice != nullptr && ! outputDevice->open (currentSampleRate, outputChannels))
|
||||
if (outputDevice != nullptr && ! outputDevice->open (currentSampleRate, outputChannels, bufferSizeSamples))
|
||||
{
|
||||
close();
|
||||
lastError = TRANS("Couldn't open the output device!");
|
||||
|
|
@ -1277,6 +1338,7 @@ public:
|
|||
{
|
||||
device = new WASAPIAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
|
||||
: inputDeviceName,
|
||||
getTypeName(),
|
||||
outputDeviceIds [outputIndex],
|
||||
inputDeviceIds [inputIndex],
|
||||
exclusiveMode);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue