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

CoreAudio: Respect buffer size passed to audio callback

We now query the incoming buffers to see how many samples are available.

If the callback's buffers will fit into our preallocated buffer (i.e. the
length in samples is smaller or equal to the preallocated buffer), then
we perform an audio callback with the provided data, even if the number
of samples is smaller than expected.

If the callback's buffers are larger than expected, we split the
incoming buffer into chunks that are no larger than the prepared
buffer-size.
This commit is contained in:
reuk 2025-05-28 13:00:35 +01:00
parent ff99341179
commit f6df3e3ce1
No known key found for this signature in database

View file

@ -353,13 +353,14 @@ public:
void allocateTempBuffers()
{
auto tempBufSize = (size_t) bufferSize + 4;
const auto tempBufSize = (size_t) bufferSize + 4;
auto streams = getStreams();
const auto total = std::accumulate (streams.begin(), streams.end(), (size_t) 0,
[] (auto n, const auto& s) { return n + (s != nullptr ? s->channels : 0); });
audioBuffer.clear();
audioBuffer.resize (total * tempBufSize);
audioBufferLengthInSamples = (size_t) bufferSize;
size_t channels = 0;
for (auto* stream : streams)
@ -777,60 +778,52 @@ public:
return;
}
const auto numInputChans = (int) getChannels (inStream);
const auto numOutputChans = (int) getChannels (outStream);
if (callback != nullptr)
const auto actualBufferSizeSamples = std::invoke ([&]
{
for (int i = numInputChans; --i >= 0;)
{
auto& info = inStream->channelInfo.getReference (i);
auto dest = inStream->tempBuffers[(size_t) i];
auto src = ((const float*) inInputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples;
auto stride = info.dataStrideSamples;
size_t result = 0;
if (stride != 0) // if this is zero, info is invalid
for (auto [streamPtr, data] : { std::tuple (&inStream, static_cast<const AudioBufferList*> (inInputData)),
std::tuple (&outStream, static_cast<const AudioBufferList*> (outOutputData)) })
{
auto& stream = *streamPtr;
const auto numChannels = (int) getChannels (stream);
for (auto i = 0; i < numChannels; ++i)
{
for (int j = bufferSize; --j >= 0;)
{
*dest++ = *src;
src += stride;
}
const auto info = stream->channelInfo.getReference (i);
const auto stride = (size_t) info.dataStrideSamples;
if (stride == 0)
continue;
const auto bufSizeSamples = data->mBuffers[info.streamNum].mDataByteSize / (sizeof (float) * stride);
// Not all stream buffer sizes are equal!
jassert (result == 0 || result == bufSizeSamples);
result = bufSizeSamples;
}
}
return result;
});
if (callback != nullptr)
{
for (auto* stream : getStreams())
if (stream != nullptr)
owner.hadDiscontinuity |= stream->checkTimestampsForDiscontinuity (stream == inStream.get() ? inputTimestamp
: outputTimestamp);
const auto* timeStamp = numOutputChans > 0 ? outputTimestamp : inputTimestamp;
const auto nanos = timeStamp != nullptr ? timeConversions.hostTimeToNanos (timeStamp->mHostTime) : 0;
const AudioIODeviceCallbackContext context
{
timeStamp != nullptr ? &nanos : nullptr,
};
if (stream == nullptr)
continue;
callback->audioDeviceIOCallbackWithContext (getTempBuffers (inStream), numInputChans,
getTempBuffers (outStream), numOutputChans,
bufferSize,
context);
const auto timeStamp = stream == inStream.get() ? inputTimestamp : outputTimestamp;
owner.hadDiscontinuity |= stream->checkTimestampsForDiscontinuity (timeStamp);
}
for (int i = numOutputChans; --i >= 0;)
for (size_t offset = 0; offset < actualBufferSizeSamples;)
{
auto& info = outStream->channelInfo.getReference (i);
auto src = outStream->tempBuffers[(size_t) i];
auto dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples;
auto stride = info.dataStrideSamples;
if (stride != 0) // if this is zero, info is invalid
{
for (int j = bufferSize; --j >= 0;)
{
*dest = *src++;
dest += stride;
}
}
const auto numSamplesInChunk = jmin (actualBufferSizeSamples - offset, audioBufferLengthInSamples);
processBufferChunk (offset, numSamplesInChunk, inputTimestamp, outputTimestamp, inInputData, outOutputData);
offset += numSamplesInChunk;
}
}
else
@ -842,7 +835,7 @@ public:
for (auto* stream : getStreams())
if (stream != nullptr)
stream->previousSampleTime += static_cast<Float64> (bufferSize);
stream->previousSampleTime += static_cast<Float64> (actualBufferSizeSamples);
}
// called by callbacks (possibly off the main thread)
@ -1065,6 +1058,111 @@ public:
AudioWorkgroup audioWorkgroup;
private:
template <typename Iterator>
struct StrideIterator
{
StrideIterator (Iterator iteratorIn, ptrdiff_t strideIn)
: iterator (std::move (iteratorIn)), stride (strideIn) {}
StrideIterator& operator++()
{
iterator += stride;
return *this;
}
StrideIterator operator++ (int)
{
auto copy = *this;
operator++();
return copy;
}
// decltype (auto) here because the return types may be references
decltype (auto) operator* () const { return *iterator; }
bool operator== (const StrideIterator& other) const { return iterator == other.iterator; }
bool operator!= (const StrideIterator& other) const { return iterator != other.iterator; }
StrideIterator& operator+= (ptrdiff_t x)
{
iterator += stride * x;
return *this;
}
StrideIterator operator+ (ptrdiff_t x) const
{
return StrideIterator { *this } += x;
}
Iterator iterator;
ptrdiff_t stride;
};
void processBufferChunk (size_t sampleOffset,
size_t numSamplesInChunk,
const AudioTimeStamp* inputTimestamp,
const AudioTimeStamp* outputTimestamp,
const AudioBufferList* inInputData,
AudioBufferList* outOutputData)
{
// precondition
jassert (callback != nullptr);
const auto numInputChans = (int) getChannels (inStream);
const auto numOutputChans = (int) getChannels (outStream);
// copy from input buffer to temporary buffer
for (auto index = 0; index < numInputChans; ++index)
{
const auto info = inStream->channelInfo.getReference (index);
const auto src = StrideIterator { ((const float*) inInputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples,
info.dataStrideSamples }
+ (ptrdiff_t) sampleOffset;
if (src.stride == 0) // if this is zero, info is invalid
continue;
std::copy (src, src + (ptrdiff_t) numSamplesInChunk, inStream->tempBuffers[(size_t) index]);
}
// only pass a timestamp for the first chunk of each buffer
const auto* timeStamp = std::invoke ([&]() -> const AudioTimeStamp*
{
if (sampleOffset != 0)
return nullptr;
return numOutputChans > 0 ? outputTimestamp : inputTimestamp;
});
const auto nanos = timeStamp != nullptr ? timeConversions.hostTimeToNanos (timeStamp->mHostTime) : 0;
const AudioIODeviceCallbackContext context
{
timeStamp != nullptr ? &nanos : nullptr,
};
callback->audioDeviceIOCallbackWithContext (getTempBuffers (inStream),
numInputChans,
getTempBuffers (outStream),
numOutputChans,
(int) numSamplesInChunk,
context);
// copy from temporary buffer to output buffer
for (auto index = 0; index < numOutputChans; ++index)
{
const auto info = outStream->channelInfo.getReference (index);
const auto dest = StrideIterator { ((float*) outOutputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples,
info.dataStrideSamples }
+ (ptrdiff_t) sampleOffset;
if (dest.stride == 0) // if this is zero, info is invalid
continue;
const auto* src = outStream->tempBuffers[(size_t) index];
std::copy (src, src + numSamplesInChunk, dest);
}
}
class ScopedAudioDeviceIOProcID
{
public:
@ -1117,6 +1215,7 @@ private:
double sampleRate = 0;
int bufferSize = 0;
std::vector<float> audioBuffer;
size_t audioBufferLengthInSamples = 0;
Atomic<int> callbacksAllowed { 1 };
//==============================================================================