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