1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-16 00:34:19 +00:00

CoreAudio: Avoid data race on fifo storage

Previously, whenever the output device sample time changed from
'invalid' to 'valid', the AudioBuffer fifo in the AudioIODeviceCombiner
was cleared. This caused a data race, since the clear operation was not
mutually exclusive with writes from the input device.

This change causes the AudioIODeviceCombiner to keep track of the
timestamp of the first input device callback after the output device is
invalidated. The output device is unable to read from the fifo until its
timestamp exceeds the stored input device callback timestamp.
This commit is contained in:
reuk 2022-12-14 15:12:15 +00:00
parent 6cd2ed022d
commit 93063de28d
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C

View file

@ -1695,6 +1695,7 @@ private:
CriticalSection closeLock;
int targetLatency = 0;
std::atomic<int> xruns { -1 };
std::atomic<uint64_t> lastValidReadPosition { invalidSampleTime };
BigInteger inputChannelsRequested, outputChannelsRequested;
double sampleRateRequested = 44100;
@ -1792,8 +1793,9 @@ private:
}
auto currentWritePos = writePos.load();
const auto nextWritePos = currentWritePos + static_cast<std::uint64_t> (n);
writePos.compare_exchange_strong (currentWritePos, currentWritePos + static_cast<std::uint64_t> (n));
writePos.compare_exchange_strong (currentWritePos, nextWritePos);
if (currentWritePos == invalidSampleTime)
return;
@ -1817,6 +1819,11 @@ private:
scratchBuffer.getReadPointer (args.channel, args.inputPos),
args.nItems);
});
{
auto invalid = invalidSampleTime;
lastValidReadPosition.compare_exchange_strong (invalid, nextWritePos);
}
}
void outputAudioCallback (float* const* channels, int numChannels, int n) noexcept
@ -1839,16 +1846,29 @@ private:
}
}
accessFifo (currentReadPos, numChannels, n, [&] (const auto& args)
// If there was an xrun, we want to output zeros until we're sure that there's some valid
// input for us to read.
const auto longN = static_cast<uint64_t> (n);
const auto nextReadPos = currentReadPos + longN;
const auto validReadPos = lastValidReadPosition.load();
const auto sanitisedValidReadPos = validReadPos != invalidSampleTime ? validReadPos : nextReadPos;
const auto numZerosToWrite = sanitisedValidReadPos <= currentReadPos
? 0
: jmin (longN, sanitisedValidReadPos - currentReadPos);
for (auto i = 0; i < numChannels; ++i)
std::fill (channels[i], channels[i] + numZerosToWrite, 0.0f);
accessFifo (currentReadPos + numZerosToWrite, numChannels, static_cast<int> (longN - numZerosToWrite), [&] (const auto& args)
{
FloatVectorOperations::copy (channels[args.channel] + args.inputPos,
FloatVectorOperations::copy (channels[args.channel] + args.inputPos + numZerosToWrite,
fifo.getReadPointer (args.channel, args.fifoPos),
args.nItems);
});
// use compare exchange here as we need to avoid the case
// where we overwrite readPos being equal to invalidSampleTime
readPos.compare_exchange_strong (currentReadPos, currentReadPos + static_cast<std::uint64_t> (n));
readPos.compare_exchange_strong (currentReadPos, nextReadPos);
}
void xrun() noexcept
@ -1987,7 +2007,7 @@ private:
auto copy = invalidSampleTime;
if (sampleTime.compare_exchange_strong (copy, callbackSampleTime) && (! input))
owner.fifo.clear();
owner.lastValidReadPosition = invalidSampleTime;
}
bool isInput() const { return input; }