diff --git a/examples/Demo/JuceDemo.jucer b/examples/Demo/JuceDemo.jucer index 4864348849..e1cd233563 100644 --- a/examples/Demo/JuceDemo.jucer +++ b/examples/Demo/JuceDemo.jucer @@ -290,7 +290,7 @@ - + diff --git a/examples/Demo/JuceLibraryCode/AppConfig.h b/examples/Demo/JuceLibraryCode/AppConfig.h index af5e76555e..11fa1455f1 100644 --- a/examples/Demo/JuceLibraryCode/AppConfig.h +++ b/examples/Demo/JuceLibraryCode/AppConfig.h @@ -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 diff --git a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp index 2eac2fb3cc..bb334babad 100644 --- a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -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);