mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +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()
|
void allocateTempBuffers()
|
||||||
{
|
{
|
||||||
auto tempBufSize = (size_t) bufferSize + 4;
|
const auto tempBufSize = (size_t) bufferSize + 4;
|
||||||
|
|
||||||
auto streams = getStreams();
|
auto streams = getStreams();
|
||||||
const auto total = std::accumulate (streams.begin(), streams.end(), (size_t) 0,
|
const auto total = std::accumulate (streams.begin(), streams.end(), (size_t) 0,
|
||||||
[] (auto n, const auto& s) { return n + (s != nullptr ? s->channels : 0); });
|
[] (auto n, const auto& s) { return n + (s != nullptr ? s->channels : 0); });
|
||||||
audioBuffer.clear();
|
audioBuffer.clear();
|
||||||
audioBuffer.resize (total * tempBufSize);
|
audioBuffer.resize (total * tempBufSize);
|
||||||
|
audioBufferLengthInSamples = (size_t) bufferSize;
|
||||||
|
|
||||||
size_t channels = 0;
|
size_t channels = 0;
|
||||||
for (auto* stream : streams)
|
for (auto* stream : streams)
|
||||||
|
|
@ -777,60 +778,52 @@ public:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto numInputChans = (int) getChannels (inStream);
|
const auto actualBufferSizeSamples = std::invoke ([&]
|
||||||
const auto numOutputChans = (int) getChannels (outStream);
|
{
|
||||||
|
size_t result = 0;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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)
|
if (callback != nullptr)
|
||||||
{
|
{
|
||||||
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;
|
|
||||||
|
|
||||||
if (stride != 0) // if this is zero, info is invalid
|
|
||||||
{
|
|
||||||
for (int j = bufferSize; --j >= 0;)
|
|
||||||
{
|
|
||||||
*dest++ = *src;
|
|
||||||
src += stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto* stream : getStreams())
|
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,
|
const auto timeStamp = stream == inStream.get() ? inputTimestamp : outputTimestamp;
|
||||||
getTempBuffers (outStream), numOutputChans,
|
owner.hadDiscontinuity |= stream->checkTimestampsForDiscontinuity (timeStamp);
|
||||||
bufferSize,
|
|
||||||
context);
|
|
||||||
|
|
||||||
for (int i = numOutputChans; --i >= 0;)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (size_t offset = 0; offset < actualBufferSizeSamples;)
|
||||||
|
{
|
||||||
|
const auto numSamplesInChunk = jmin (actualBufferSizeSamples - offset, audioBufferLengthInSamples);
|
||||||
|
processBufferChunk (offset, numSamplesInChunk, inputTimestamp, outputTimestamp, inInputData, outOutputData);
|
||||||
|
offset += numSamplesInChunk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -842,7 +835,7 @@ public:
|
||||||
|
|
||||||
for (auto* stream : getStreams())
|
for (auto* stream : getStreams())
|
||||||
if (stream != nullptr)
|
if (stream != nullptr)
|
||||||
stream->previousSampleTime += static_cast<Float64> (bufferSize);
|
stream->previousSampleTime += static_cast<Float64> (actualBufferSizeSamples);
|
||||||
}
|
}
|
||||||
|
|
||||||
// called by callbacks (possibly off the main thread)
|
// called by callbacks (possibly off the main thread)
|
||||||
|
|
@ -1065,6 +1058,111 @@ public:
|
||||||
AudioWorkgroup audioWorkgroup;
|
AudioWorkgroup audioWorkgroup;
|
||||||
|
|
||||||
private:
|
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
|
class ScopedAudioDeviceIOProcID
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -1117,6 +1215,7 @@ private:
|
||||||
double sampleRate = 0;
|
double sampleRate = 0;
|
||||||
int bufferSize = 0;
|
int bufferSize = 0;
|
||||||
std::vector<float> audioBuffer;
|
std::vector<float> audioBuffer;
|
||||||
|
size_t audioBufferLengthInSamples = 0;
|
||||||
Atomic<int> callbacksAllowed { 1 };
|
Atomic<int> callbacksAllowed { 1 };
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue