mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
BufferingAudioSource: Fix AudioBuffer data race
This commit is contained in:
parent
832deb9ad7
commit
80602649a2
2 changed files with 101 additions and 82 deletions
|
|
@ -65,6 +65,8 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne
|
|||
buffer.setSize (numberOfChannels, bufferSizeNeeded);
|
||||
buffer.clear();
|
||||
|
||||
const ScopedLock sl (bufferRangeLock);
|
||||
|
||||
bufferValidStart = 0;
|
||||
bufferValidEnd = 0;
|
||||
|
||||
|
|
@ -72,6 +74,8 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne
|
|||
|
||||
do
|
||||
{
|
||||
const ScopedUnlock ul (bufferRangeLock);
|
||||
|
||||
backgroundThread.moveToFrontOfQueue (this);
|
||||
Thread::sleep (5);
|
||||
}
|
||||
|
|
@ -96,100 +100,97 @@ void BufferingAudioSource::releaseResources()
|
|||
|
||||
void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
|
||||
{
|
||||
const ScopedLock sl (bufferStartPosLock);
|
||||
const auto bufferRange = getValidBufferRange (info.numSamples);
|
||||
|
||||
auto start = bufferValidStart.load();
|
||||
auto end = bufferValidEnd.load();
|
||||
auto pos = nextPlayPos.load();
|
||||
|
||||
auto validStart = (int) (jlimit (start, end, pos) - pos);
|
||||
auto validEnd = (int) (jlimit (start, end, pos + info.numSamples) - pos);
|
||||
|
||||
if (validStart == validEnd)
|
||||
if (bufferRange.isEmpty())
|
||||
{
|
||||
// total cache miss
|
||||
info.clearActiveBufferRegion();
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
const auto validStart = bufferRange.getStart();
|
||||
const auto validEnd = bufferRange.getEnd();
|
||||
|
||||
const ScopedLock sl (callbackLock);
|
||||
|
||||
if (validStart > 0)
|
||||
info.buffer->clear (info.startSample, validStart); // partial cache miss at start
|
||||
|
||||
if (validEnd < info.numSamples)
|
||||
info.buffer->clear (info.startSample + validEnd,
|
||||
info.numSamples - validEnd); // partial cache miss at end
|
||||
|
||||
if (validStart < validEnd)
|
||||
{
|
||||
if (validStart > 0)
|
||||
info.buffer->clear (info.startSample, validStart); // partial cache miss at start
|
||||
|
||||
if (validEnd < info.numSamples)
|
||||
info.buffer->clear (info.startSample + validEnd,
|
||||
info.numSamples - validEnd); // partial cache miss at end
|
||||
|
||||
if (validStart < validEnd)
|
||||
for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;)
|
||||
{
|
||||
for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;)
|
||||
jassert (buffer.getNumSamples() > 0);
|
||||
|
||||
const auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples());
|
||||
const auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples());
|
||||
|
||||
if (startBufferIndex < endBufferIndex)
|
||||
{
|
||||
jassert (buffer.getNumSamples() > 0);
|
||||
auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples());
|
||||
auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples());
|
||||
info.buffer->copyFrom (chan, info.startSample + validStart,
|
||||
buffer,
|
||||
chan, startBufferIndex,
|
||||
validEnd - validStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto initialSize = buffer.getNumSamples() - startBufferIndex;
|
||||
|
||||
if (startBufferIndex < endBufferIndex)
|
||||
{
|
||||
info.buffer->copyFrom (chan, info.startSample + validStart,
|
||||
buffer,
|
||||
chan, startBufferIndex,
|
||||
validEnd - validStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto initialSize = buffer.getNumSamples() - startBufferIndex;
|
||||
info.buffer->copyFrom (chan, info.startSample + validStart,
|
||||
buffer,
|
||||
chan, startBufferIndex,
|
||||
initialSize);
|
||||
|
||||
info.buffer->copyFrom (chan, info.startSample + validStart,
|
||||
buffer,
|
||||
chan, startBufferIndex,
|
||||
initialSize);
|
||||
|
||||
info.buffer->copyFrom (chan, info.startSample + validStart + initialSize,
|
||||
buffer,
|
||||
chan, 0,
|
||||
(validEnd - validStart) - initialSize);
|
||||
}
|
||||
info.buffer->copyFrom (chan, info.startSample + validStart + initialSize,
|
||||
buffer,
|
||||
chan, 0,
|
||||
(validEnd - validStart) - initialSize);
|
||||
}
|
||||
}
|
||||
|
||||
nextPlayPos += info.numSamples;
|
||||
}
|
||||
|
||||
nextPlayPos += info.numSamples;
|
||||
}
|
||||
|
||||
bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, uint32 timeout)
|
||||
{
|
||||
if (!source || source->getTotalLength() <= 0)
|
||||
if (source == nullptr || source->getTotalLength() <= 0)
|
||||
return false;
|
||||
|
||||
if (nextPlayPos + info.numSamples < 0)
|
||||
if ((nextPlayPos + info.numSamples < 0)
|
||||
|| (! isLooping() && nextPlayPos > getTotalLength()))
|
||||
return true;
|
||||
|
||||
if (! isLooping() && nextPlayPos > getTotalLength())
|
||||
return true;
|
||||
|
||||
auto now = Time::getMillisecondCounter();
|
||||
auto startTime = now;
|
||||
const auto startTime = Time::getMillisecondCounter();
|
||||
auto now = startTime;
|
||||
|
||||
auto elapsed = (now >= startTime ? now - startTime
|
||||
: (std::numeric_limits<uint32>::max() - startTime) + now);
|
||||
|
||||
while (elapsed <= timeout)
|
||||
{
|
||||
const auto bufferRange = getValidBufferRange (info.numSamples);
|
||||
|
||||
const auto validStart = bufferRange.getStart();
|
||||
const auto validEnd = bufferRange.getEnd();
|
||||
|
||||
if (validStart <= 0
|
||||
&& validStart < validEnd
|
||||
&& validEnd >= info.numSamples)
|
||||
{
|
||||
const ScopedLock sl (bufferStartPosLock);
|
||||
|
||||
auto start = bufferValidStart.load();
|
||||
auto end = bufferValidEnd.load();
|
||||
auto pos = nextPlayPos.load();
|
||||
|
||||
auto validStart = static_cast<int> (jlimit (start, end, pos) - pos);
|
||||
auto validEnd = static_cast<int> (jlimit (start, end, pos + info.numSamples) - pos);
|
||||
|
||||
if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples)
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed))))
|
||||
if (elapsed < timeout
|
||||
&& ! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
now = Time::getMillisecondCounter();
|
||||
elapsed = (now >= startTime ? now - startTime
|
||||
|
|
@ -202,7 +203,7 @@ bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelI
|
|||
int64 BufferingAudioSource::getNextReadPosition() const
|
||||
{
|
||||
jassert (source->getTotalLength() > 0);
|
||||
auto pos = nextPlayPos.load();
|
||||
const auto pos = nextPlayPos.load();
|
||||
|
||||
return (source->isLooping() && nextPlayPos > 0)
|
||||
? pos % source->getTotalLength()
|
||||
|
|
@ -211,18 +212,28 @@ int64 BufferingAudioSource::getNextReadPosition() const
|
|||
|
||||
void BufferingAudioSource::setNextReadPosition (int64 newPosition)
|
||||
{
|
||||
const ScopedLock sl (bufferStartPosLock);
|
||||
const ScopedLock sl (bufferRangeLock);
|
||||
|
||||
nextPlayPos = newPosition;
|
||||
backgroundThread.moveToFrontOfQueue (this);
|
||||
}
|
||||
|
||||
Range<int> BufferingAudioSource::getValidBufferRange (int numSamples) const
|
||||
{
|
||||
const ScopedLock sl (bufferRangeLock);
|
||||
|
||||
const auto pos = nextPlayPos.load();
|
||||
|
||||
return { (int) (jlimit (bufferValidStart, bufferValidEnd, pos) - pos),
|
||||
(int) (jlimit (bufferValidStart, bufferValidEnd, pos + numSamples) - pos) };
|
||||
}
|
||||
|
||||
bool BufferingAudioSource::readNextBufferChunk()
|
||||
{
|
||||
int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd;
|
||||
|
||||
{
|
||||
const ScopedLock sl (bufferStartPosLock);
|
||||
const ScopedLock sl (bufferRangeLock);
|
||||
|
||||
if (wasSourceLooping != isLooping())
|
||||
{
|
||||
|
|
@ -236,7 +247,7 @@ bool BufferingAudioSource::readNextBufferChunk()
|
|||
sectionToReadStart = 0;
|
||||
sectionToReadEnd = 0;
|
||||
|
||||
const int maxChunkSize = 2048;
|
||||
constexpr int maxChunkSize = 2048;
|
||||
|
||||
if (newBVS < bufferValidStart || newBVS >= bufferValidEnd)
|
||||
{
|
||||
|
|
@ -257,7 +268,7 @@ bool BufferingAudioSource::readNextBufferChunk()
|
|||
sectionToReadEnd = newBVE;
|
||||
|
||||
bufferValidStart = newBVS;
|
||||
bufferValidEnd = jmin (bufferValidEnd.load(), newBVE);
|
||||
bufferValidEnd = jmin (bufferValidEnd, newBVE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,8 +276,9 @@ bool BufferingAudioSource::readNextBufferChunk()
|
|||
return false;
|
||||
|
||||
jassert (buffer.getNumSamples() > 0);
|
||||
auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples());
|
||||
auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples());
|
||||
|
||||
const auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples());
|
||||
const auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples());
|
||||
|
||||
if (bufferIndexStart < bufferIndexEnd)
|
||||
{
|
||||
|
|
@ -276,7 +288,7 @@ bool BufferingAudioSource::readNextBufferChunk()
|
|||
}
|
||||
else
|
||||
{
|
||||
auto initialSize = buffer.getNumSamples() - bufferIndexStart;
|
||||
const auto initialSize = buffer.getNumSamples() - bufferIndexStart;
|
||||
|
||||
readBufferSection (sectionToReadStart,
|
||||
initialSize,
|
||||
|
|
@ -288,7 +300,7 @@ bool BufferingAudioSource::readNextBufferChunk()
|
|||
}
|
||||
|
||||
{
|
||||
const ScopedLock sl2 (bufferStartPosLock);
|
||||
const ScopedLock sl2 (bufferRangeLock);
|
||||
|
||||
bufferValidStart = newBVS;
|
||||
bufferValidEnd = newBVE;
|
||||
|
|
@ -304,6 +316,8 @@ void BufferingAudioSource::readBufferSection (int64 start, int length, int buffe
|
|||
source->setNextReadPosition (start);
|
||||
|
||||
AudioSourceChannelInfo info (&buffer, bufferOffset, length);
|
||||
|
||||
const ScopedLock sl (callbackLock);
|
||||
source->getNextAudioBlock (info);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,20 +99,25 @@ public:
|
|||
|
||||
private:
|
||||
//==============================================================================
|
||||
OptionalScopedPointer<PositionableAudioSource> source;
|
||||
TimeSliceThread& backgroundThread;
|
||||
int numberOfSamplesToBuffer, numberOfChannels;
|
||||
AudioBuffer<float> buffer;
|
||||
CriticalSection bufferStartPosLock;
|
||||
WaitableEvent bufferReadyEvent;
|
||||
std::atomic<int64> bufferValidStart { 0 }, bufferValidEnd { 0 }, nextPlayPos { 0 };
|
||||
double sampleRate = 0;
|
||||
bool wasSourceLooping = false, isPrepared = false, prefillBuffer;
|
||||
|
||||
Range<int> getValidBufferRange (int numSamples) const;
|
||||
bool readNextBufferChunk();
|
||||
void readBufferSection (int64 start, int length, int bufferOffset);
|
||||
int useTimeSlice() override;
|
||||
|
||||
//==============================================================================
|
||||
OptionalScopedPointer<PositionableAudioSource> source;
|
||||
TimeSliceThread& backgroundThread;
|
||||
int numberOfSamplesToBuffer, numberOfChannels;
|
||||
AudioBuffer<float> buffer;
|
||||
CriticalSection callbackLock, bufferRangeLock;
|
||||
WaitableEvent bufferReadyEvent;
|
||||
int64 bufferValidStart = 0, bufferValidEnd = 0;
|
||||
std::atomic<int64> nextPlayPos { 0 };
|
||||
double sampleRate = 0;
|
||||
bool wasSourceLooping = false, isPrepared = false;
|
||||
const bool prefillBuffer;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource)
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue