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:
parent
ff99341179
commit
f6df3e3ce1
1 changed files with 144 additions and 45 deletions
|
|
@ -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 };
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue