Browse Source

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.
v7.0.9
reuk 3 years ago
parent
commit
93063de28d
No known key found for this signature in database GPG Key ID: FCB43929F012EE5C
1 changed files with 25 additions and 5 deletions
  1. +25
    -5
      modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp

+ 25
- 5
modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp 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; }


Loading…
Cancel
Save