mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-08 23:24:19 +00:00
WASAPI: Fix bug where an external device sample rate change could permanently stop audio processing
This commit is contained in:
parent
e26c69e7bf
commit
fd1c29e56b
1 changed files with 98 additions and 65 deletions
|
|
@ -412,12 +412,25 @@ static bool supportsSampleRateConversion (WASAPIDeviceMode deviceMode) noexcept
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
struct WASAPIDeviceBaseDelegate
|
||||
{
|
||||
virtual ~WASAPIDeviceBaseDelegate() = default;
|
||||
virtual void deviceOpened (IMMDevice*) = 0;
|
||||
virtual void deviceSampleRateChanged (IMMDevice*) = 0;
|
||||
virtual void deviceSessionBecameInactive (IMMDevice*) = 0;
|
||||
virtual void deviceSessionExpired (IMMDevice*) = 0;
|
||||
virtual void deviceSessionBecameActive (IMMDevice*) = 0;
|
||||
};
|
||||
|
||||
class WASAPIDeviceBase
|
||||
{
|
||||
public:
|
||||
WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
|
||||
WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d,
|
||||
WASAPIDeviceMode mode,
|
||||
WASAPIDeviceBaseDelegate& delegateIn)
|
||||
: device (d),
|
||||
deviceMode (mode)
|
||||
deviceMode (mode),
|
||||
delegate (delegateIn)
|
||||
{
|
||||
clientEvent = CreateEvent (nullptr, false, false, nullptr);
|
||||
|
||||
|
|
@ -468,8 +481,7 @@ public:
|
|||
if (client == nullptr || ! tryInitialisingWithBufferSize (bufferSizeSamples))
|
||||
return false;
|
||||
|
||||
sampleRateHasChanged = false;
|
||||
shouldShutdown = false;
|
||||
delegate.deviceOpened (device);
|
||||
|
||||
channelMaps.clear();
|
||||
|
||||
|
|
@ -503,22 +515,22 @@ public:
|
|||
|
||||
void deviceSampleRateChanged()
|
||||
{
|
||||
sampleRateHasChanged = true;
|
||||
delegate.deviceSampleRateChanged (device);
|
||||
}
|
||||
|
||||
void deviceSessionBecameInactive()
|
||||
{
|
||||
isActive = false;
|
||||
delegate.deviceSessionBecameInactive (device);
|
||||
}
|
||||
|
||||
void deviceSessionExpired()
|
||||
{
|
||||
shouldShutdown = true;
|
||||
delegate.deviceSessionExpired (device);
|
||||
}
|
||||
|
||||
void deviceSessionBecameActive()
|
||||
{
|
||||
isActive = true;
|
||||
delegate.deviceSessionBecameActive (device);
|
||||
}
|
||||
|
||||
std::optional<BigInteger> getDefaultLayout() const
|
||||
|
|
@ -537,6 +549,8 @@ public:
|
|||
|
||||
WASAPIDeviceMode deviceMode;
|
||||
|
||||
WASAPIDeviceBaseDelegate& delegate;
|
||||
|
||||
double sampleRate = 0, defaultSampleRate = 0;
|
||||
int numChannels = 0, actualNumChannels = 0, maxNumChannels = 0, defaultNumChannels = 0;
|
||||
int minBufferSize = 0, defaultBufferSize = 0, latencySamples = 0;
|
||||
|
|
@ -548,7 +562,6 @@ public:
|
|||
Array<int> channelMaps;
|
||||
UINT32 actualBufferSize = 0;
|
||||
int bytesPerSample = 0, bytesPerFrame = 0;
|
||||
std::atomic<bool> sampleRateHasChanged { false }, shouldShutdown { false }, isActive { true };
|
||||
|
||||
virtual void updateFormat (bool isFloat) = 0;
|
||||
|
||||
|
|
@ -919,10 +932,7 @@ private:
|
|||
class WASAPIInputDevice final : public WASAPIDeviceBase
|
||||
{
|
||||
public:
|
||||
WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
|
||||
: WASAPIDeviceBase (d, mode)
|
||||
{
|
||||
}
|
||||
using WASAPIDeviceBase::WASAPIDeviceBase;
|
||||
|
||||
~WASAPIInputDevice() override
|
||||
{
|
||||
|
|
@ -971,7 +981,7 @@ public:
|
|||
return false;
|
||||
|
||||
purgeInputBuffers();
|
||||
isActive = true;
|
||||
delegate.deviceSessionBecameActive (device);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1067,10 +1077,7 @@ private:
|
|||
class WASAPIOutputDevice final : public WASAPIDeviceBase
|
||||
{
|
||||
public:
|
||||
WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode)
|
||||
: WASAPIDeviceBase (d, mode)
|
||||
{
|
||||
}
|
||||
using WASAPIDeviceBase::WASAPIDeviceBase;
|
||||
|
||||
~WASAPIOutputDevice() override
|
||||
{
|
||||
|
|
@ -1116,7 +1123,7 @@ public:
|
|||
if (! check (client->Start()))
|
||||
return false;
|
||||
|
||||
isActive = true;
|
||||
delegate.deviceSessionBecameActive (device);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1193,6 +1200,7 @@ private:
|
|||
//==============================================================================
|
||||
class WASAPIAudioIODevice final : public AudioIODevice,
|
||||
public Thread,
|
||||
private WASAPIDeviceBaseDelegate,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
|
|
@ -1402,8 +1410,8 @@ public:
|
|||
if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent);
|
||||
if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent);
|
||||
|
||||
// No lock here; background thread is not running
|
||||
flags &= ~(flagShutdown | flagSampleRateChanged);
|
||||
flags.fetch_and (~(flagShutdown | flagInputSampleRateDidChange | flagOutputSampleRateDidChange),
|
||||
std::memory_order_acq_rel);
|
||||
|
||||
startThread (Priority::high);
|
||||
Thread::sleep (5);
|
||||
|
|
@ -1432,7 +1440,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
flags |= flagOpen;
|
||||
flags.fetch_or (flagOpen, std::memory_order_acq_rel);
|
||||
|
||||
return lastError;
|
||||
}
|
||||
|
|
@ -1451,17 +1459,17 @@ public:
|
|||
if (outputDevice != nullptr) outputDevice->close();
|
||||
|
||||
// Background thread has stopped at this point
|
||||
flags &= ~flagOpen;
|
||||
flags.fetch_and (~flagOpen, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
bool isOpen() override
|
||||
{
|
||||
return ((flags & flagOpen) != 0) && isThreadRunning();
|
||||
return ((flags.load (std::memory_order_acquire) & flagOpen) != 0) && isThreadRunning();
|
||||
}
|
||||
|
||||
bool isPlaying() override
|
||||
{
|
||||
return ((flags & (flagOpen | flagStarted)) == (flagOpen | flagStarted)) && isThreadRunning();
|
||||
return ((flags.load (std::memory_order_acquire) & (flagOpen | flagStarted)) == (flagOpen | flagStarted)) && isThreadRunning();
|
||||
}
|
||||
|
||||
void start (AudioIODeviceCallback* call) override
|
||||
|
|
@ -1469,13 +1477,13 @@ public:
|
|||
{
|
||||
const ScopedLock sl (startStopLock);
|
||||
|
||||
if ((flags & (flagOpen | flagStarted)) != flagOpen || call == nullptr)
|
||||
if ((flags.load (std::memory_order_acquire) & (flagOpen | flagStarted)) != flagOpen || call == nullptr)
|
||||
return;
|
||||
|
||||
if (! isThreadRunning())
|
||||
{
|
||||
// something's gone wrong and the thread's stopped
|
||||
flags &= ~flagOpen;
|
||||
flags.fetch_and (~flagOpen, std::memory_order_acq_rel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1486,7 +1494,7 @@ public:
|
|||
const ScopedLock sl (startStopLock);
|
||||
|
||||
callback = call;
|
||||
flags |= flagStarted;
|
||||
flags.fetch_or (flagStarted, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1496,7 +1504,7 @@ public:
|
|||
{
|
||||
const ScopedLock sl (startStopLock);
|
||||
|
||||
const auto wasStarted = (flags.fetch_and (~flagStarted) & flagStarted) != 0;
|
||||
const auto wasStarted = (flags.fetch_and (~flagStarted, std::memory_order_acq_rel) & flagStarted) != 0;
|
||||
return wasStarted ? callback : nullptr;
|
||||
});
|
||||
|
||||
|
|
@ -1534,17 +1542,16 @@ public:
|
|||
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
if ((outputDevice != nullptr && outputDevice->shouldShutdown)
|
||||
|| (inputDevice != nullptr && inputDevice->shouldShutdown))
|
||||
{
|
||||
flags |= flagShutdown;
|
||||
triggerAsyncUpdate();
|
||||
const auto loadedFlags = flags.load (std::memory_order_acquire);
|
||||
|
||||
if ((loadedFlags & (flagShutdown | flagOutputSampleRateDidChange | flagInputSampleRateDidChange)) != 0)
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
break;
|
||||
}
|
||||
|
||||
auto inputDeviceActive = (inputDevice != nullptr && inputDevice->isActive);
|
||||
auto outputDeviceActive = (outputDevice != nullptr && outputDevice->isActive);
|
||||
const auto inputDeviceActive = (loadedFlags & flagInputIsActive) != 0;
|
||||
const auto outputDeviceActive = (loadedFlags & flagOutputIsActive) != 0;
|
||||
|
||||
if (! inputDeviceActive && ! outputDeviceActive)
|
||||
continue;
|
||||
|
|
@ -1568,20 +1575,12 @@ public:
|
|||
}
|
||||
|
||||
inputDevice->copyBuffersFromReservoir (ins.getArrayOfWritePointers(), numInputBuffers, bufferSize);
|
||||
|
||||
if (inputDevice->sampleRateHasChanged)
|
||||
{
|
||||
flags |= flagSampleRateChanged;
|
||||
triggerAsyncUpdate();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const ScopedTryLock sl (startStopLock);
|
||||
|
||||
if (sl.isLocked() && (flags & flagStarted) != 0)
|
||||
if (sl.isLocked() && (loadedFlags & flagStarted) != 0)
|
||||
{
|
||||
callback->audioDeviceIOCallbackWithContext (ins.getArrayOfReadPointers(),
|
||||
numInputBuffers,
|
||||
|
|
@ -1601,14 +1600,6 @@ public:
|
|||
// Note that this function is handed the input device so it can check for the event and make sure
|
||||
// the input reservoir is filled up correctly even when bufferSize > device actualBufferSize
|
||||
outputDevice->copyBuffers (outs.getArrayOfReadPointers(), numOutputBuffers, bufferSize, inputDevice.get(), *this);
|
||||
|
||||
if (outputDevice->sampleRateHasChanged)
|
||||
{
|
||||
flags |= flagSampleRateChanged;
|
||||
triggerAsyncUpdate();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1618,6 +1609,45 @@ public:
|
|||
String lastError;
|
||||
|
||||
private:
|
||||
void deviceOpened (IMMDevice* d) override
|
||||
{
|
||||
const auto newFlags = inputDevice == nullptr || inputDevice->device != d
|
||||
? ~flagOutputSampleRateDidChange
|
||||
: ~flagInputSampleRateDidChange;
|
||||
flags.fetch_and (newFlags, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void deviceSampleRateChanged (IMMDevice* d) override
|
||||
{
|
||||
const auto newFlags = inputDevice == nullptr || inputDevice->device != d
|
||||
? flagOutputSampleRateDidChange
|
||||
: flagInputSampleRateDidChange;
|
||||
flags.fetch_or (newFlags, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void deviceSessionBecameInactive (IMMDevice* d) override
|
||||
{
|
||||
const auto newFlags = inputDevice == nullptr || inputDevice->device != d
|
||||
? flagOutputIsActive
|
||||
: flagInputIsActive;
|
||||
flags.fetch_and (~newFlags, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void deviceSessionExpired (IMMDevice*) override
|
||||
{
|
||||
flags.fetch_or (flagShutdown, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void deviceSessionBecameActive (IMMDevice* d) override
|
||||
{
|
||||
const auto newFlags = inputDevice == nullptr || inputDevice->device != d
|
||||
? flagOutputIsActive
|
||||
: flagInputIsActive;
|
||||
flags.fetch_or (newFlags, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
std::atomic<int> flags { 0 };
|
||||
|
||||
// Device stats...
|
||||
std::unique_ptr<WASAPIInputDevice> inputDevice;
|
||||
std::unique_ptr<WASAPIOutputDevice> outputDevice;
|
||||
|
|
@ -1629,15 +1659,17 @@ private:
|
|||
Array<double> sampleRates;
|
||||
Array<int> bufferSizes;
|
||||
|
||||
// Active state...
|
||||
std::atomic<int> flags { 0 };
|
||||
|
||||
enum Flags
|
||||
{
|
||||
flagOpen = 1 << 0,
|
||||
flagStarted = 1 << 1,
|
||||
flagShutdown = 1 << 2,
|
||||
flagSampleRateChanged = 1 << 3,
|
||||
flagOpen = 1 << 0,
|
||||
flagStarted = 1 << 1,
|
||||
flagShutdown = 1 << 2,
|
||||
|
||||
flagInputSampleRateDidChange = 1 << 3,
|
||||
flagInputIsActive = 1 << 4,
|
||||
|
||||
flagOutputSampleRateDidChange = 1 << 5,
|
||||
flagOutputIsActive = 1 << 6,
|
||||
};
|
||||
|
||||
int currentBufferSizeSamples = 0;
|
||||
|
|
@ -1680,9 +1712,9 @@ private:
|
|||
auto flow = getDataFlow (device);
|
||||
|
||||
if (deviceId == inputDeviceId && flow == eCapture)
|
||||
inputDevice.reset (new WASAPIInputDevice (device, deviceMode));
|
||||
inputDevice.reset (new WASAPIInputDevice (device, deviceMode, *this));
|
||||
else if (deviceId == outputDeviceId && flow == eRender)
|
||||
outputDevice.reset (new WASAPIOutputDevice (device, deviceMode));
|
||||
outputDevice.reset (new WASAPIOutputDevice (device, deviceMode, *this));
|
||||
}
|
||||
|
||||
return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk()))
|
||||
|
|
@ -1700,7 +1732,8 @@ private:
|
|||
inputDevice = nullptr;
|
||||
};
|
||||
|
||||
const auto prevFlags = flags.fetch_and (~(flagShutdown | flagSampleRateChanged));
|
||||
const auto prevFlags = flags.fetch_and (~(flagShutdown | flagInputSampleRateDidChange | flagOutputSampleRateDidChange),
|
||||
std::memory_order_acq_rel);
|
||||
|
||||
if ((prevFlags & flagShutdown) != 0)
|
||||
{
|
||||
|
|
@ -1708,10 +1741,10 @@ private:
|
|||
return;
|
||||
}
|
||||
|
||||
if ((prevFlags & flagSampleRateChanged) == 0)
|
||||
if ((prevFlags & (flagInputSampleRateDidChange | flagOutputSampleRateDidChange)) == 0)
|
||||
return;
|
||||
|
||||
auto sampleRateChangedByInput = (inputDevice != nullptr && inputDevice->sampleRateHasChanged);
|
||||
const auto sampleRateChangedByInput = (prevFlags & flagInputSampleRateDidChange) != 0;
|
||||
|
||||
closeDevices();
|
||||
initialise();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue