Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ bool AudioPlayer::openAudioStream() {
->setPerformanceMode(PerformanceMode::None)
->setChannelCount(channelCount_)
->setSampleRateConversionQuality(SampleRateConversionQuality::Medium)
->setFramesPerDataCallback(RENDER_QUANTUM_SIZE)
->setDataCallback(this)
->setSampleRate(static_cast<int>(sampleRate_))
->setErrorCallback(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,15 @@ void ConvolverNode::onInputDisabled() {
}
}

std::shared_ptr<DSPAudioBuffer> ConvolverNode::processInputs(
const std::shared_ptr<DSPAudioBuffer> &outputBuffer,
int framesToProcess,
bool checkIsAlreadyProcessed) {
if (internalBufferIndex_ < framesToProcess) {
return AudioNode::processInputs(outputBuffer, RENDER_QUANTUM_SIZE, false);
}
return AudioNode::processInputs(outputBuffer, 0, false);
}

// processing pipeline: processingBuffer -> intermediateBuffer_ -> audioBuffer_ (mixing
// with intermediateBuffer_)
std::shared_ptr<DSPAudioBuffer> ConvolverNode::processNode(
const std::shared_ptr<DSPAudioBuffer> &processingBuffer,
int framesToProcess) {
if (processingBuffer->getSize() != RENDER_QUANTUM_SIZE) {
printf(
"[AUDIOAPI WARN] convolver requires 128 buffer size for each render quantum, otherwise quality of convolution is very poor\n");
}
Comment thread
mdydek marked this conversation as resolved.
if (signalledToStop_) {
if (remainingSegments_ > 0) {
remainingSegments_--;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ class ConvolverNode : public AudioNode {
int framesToProcess) override;

private:
std::shared_ptr<DSPAudioBuffer> processInputs(
const std::shared_ptr<DSPAudioBuffer> &outputBuffer,
int framesToProcess,
bool checkIsAlreadyProcessed) override;
void onInputDisabled() override;
const float gainCalibrationSampleRate_;
size_t remainingSegments_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
#import <NativeAudioPlayer.h>
#else // when compiled as C++
typedef struct objc_object NativeAudioPlayer;
typedef struct objc_object AudioBufferList;
Comment thread
mdydek marked this conversation as resolved.
#endif // __OBJC__

#include <audioapi/utils/AudioBuffer.hpp>
#include <functional>

#include <atomic>
#include <cstddef>
#include <functional>
namespace audioapi {

class AudioContext;
Expand All @@ -29,12 +32,23 @@ class IOSAudioPlayer {

bool isRunning() const;

protected:
private:
void clearPendingSaved();
/// Audio-thread only. Always pulls the graph in steps of RENDER_QUANTUM_SIZE; if the system
/// buffer size is not a multiple of 128, the unused tail of the last quantum is kept (max 128
/// frames) and played at the start of the next callback.
void deliverOutputBuffers(AudioBufferList *outputData, int numFrames);

std::shared_ptr<DSPAudioBuffer> audioBuffer_;
NativeAudioPlayer *audioPlayer_;
std::function<void(std::shared_ptr<DSPAudioBuffer>, int)> renderAudio_;
int channelCount_;
std::atomic<bool> isRunning_;
/// Set from main thread on start/resume; consumed on audio thread to drop stale pending audio.
std::atomic<bool> flushOverflowNextPull_{false};
/// Frames valid at the front of each `pendingSaved_[ch]` (0 … RENDER_QUANTUM_SIZE).
int pendingSavedCount_{0};
DSPAudioBuffer pendingSaved_;
};

} // namespace audioapi
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#import <AVFoundation/AVFoundation.h>

#include <algorithm>
#include <cstring>

#include <audioapi/core/utils/Constants.h>
#include <audioapi/dsp/VectorMath.h>
#include <audioapi/ios/core/IOSAudioPlayer.h>
#include <audioapi/ios/system/AudioEngine.h>
#include <audioapi/utils/AudioArray.hpp>
#include <audioapi/utils/AudioBuffer.hpp>

namespace audioapi {
Expand All @@ -13,35 +14,20 @@
const std::function<void(std::shared_ptr<DSPAudioBuffer>, int)> &renderAudio,
float sampleRate,
int channelCount)
: renderAudio_(renderAudio), channelCount_(channelCount), audioBuffer_(0), isRunning_(false)
: audioBuffer_(nullptr),
audioPlayer_(nullptr),
renderAudio_(renderAudio),
channelCount_(channelCount),
isRunning_(false),
pendingSaved_(RENDER_QUANTUM_SIZE, channelCount_, sampleRate)
{
RenderAudioBlock renderAudioBlock = ^(AudioBufferList *outputData, int numFrames) {
int processedFrames = 0;

while (processedFrames < numFrames) {
int framesToProcess = std::min(numFrames - processedFrames, RENDER_QUANTUM_SIZE);

if (isRunning_.load(std::memory_order_acquire)) {
renderAudio_(audioBuffer_, framesToProcess);
} else {
audioBuffer_->zero();
}

for (size_t channel = 0; channel < channelCount_; channel += 1) {
float *outputChannel = (float *)outputData->mBuffers[channel].mData;

audioBuffer_->getChannel(channel)->copyTo(
outputChannel, 0, processedFrames, framesToProcess);
}

processedFrames += framesToProcess;
}
deliverOutputBuffers(outputData, numFrames);
};

audioPlayer_ = [[NativeAudioPlayer alloc] initWithRenderAudio:renderAudioBlock
sampleRate:sampleRate
channelCount:channelCount_];

audioBuffer_ = std::make_shared<DSPAudioBuffer>(RENDER_QUANTUM_SIZE, channelCount_, sampleRate);
}

Expand All @@ -50,13 +36,90 @@
cleanup();
}

void IOSAudioPlayer::clearPendingSaved()
{
pendingSavedCount_ = 0;
pendingSaved_.zero();
}

void IOSAudioPlayer::deliverOutputBuffers(AudioBufferList *outputData, int numFrames)
{
// If requested, clear any saved overflow before continuing normal rendering.
if (flushOverflowNextPull_.exchange(false, std::memory_order_acq_rel)) {
clearPendingSaved();
}

// if not running, set output to 0
if (!isRunning_.load(std::memory_order_acquire)) {
for (int channel = 0; channel < channelCount_; ++channel) {
auto *outputChannel = static_cast<float *>(outputData->mBuffers[channel].mData);
std::memset(outputChannel, 0, static_cast<size_t>(numFrames) * sizeof(float));
}
return;
}

int outPos = 0;
while (outPos < numFrames) {
const int need = numFrames - outPos;

if (pendingSavedCount_ > 0) {
const int fromPending = std::min(need, pendingSavedCount_);

// populate output with pendingSaved
for (int ch = 0; ch < channelCount_; ++ch) {
float *dst = static_cast<float *>(outputData->mBuffers[ch].mData) + outPos;
const float *src = pendingSaved_[ch].begin();
std::memcpy(dst, src, fromPending * sizeof(float));

// move the remaining samples to the beginning of the pendingSaved buffer
const int remain = pendingSavedCount_ - fromPending;
if (remain > 0) {
float *buf = pendingSaved_[ch].begin();
std::memmove(buf, buf + fromPending, remain * sizeof(float));
}
}

pendingSavedCount_ -= fromPending;
outPos += fromPending;
continue;
}

renderAudio_(audioBuffer_, RENDER_QUANTUM_SIZE);

Comment thread
mdydek marked this conversation as resolved.
// normal rendering - take RENDER_QUANTUM_SIZE frames from the graph and copy to output
const int stillNeed = numFrames - outPos;
if (stillNeed >= RENDER_QUANTUM_SIZE) {
for (int ch = 0; ch < channelCount_; ++ch) {
auto *src = (*audioBuffer_)[ch].begin();
float *dst = static_cast<float *>(outputData->mBuffers[ch].mData) + outPos;
std::memcpy(dst, src, RENDER_QUANTUM_SIZE * sizeof(float));
}
outPos += RENDER_QUANTUM_SIZE;
} else {
// when output will be sliced, copy the remaining frames to pendingSaved
const int tail = RENDER_QUANTUM_SIZE - stillNeed;
for (int ch = 0; ch < channelCount_; ++ch) {
auto *src = (*audioBuffer_)[ch].begin();
float *dst = static_cast<float *>(outputData->mBuffers[ch].mData) + outPos;
std::memcpy(dst, src, stillNeed * sizeof(float));
}
pendingSaved_.copy(*audioBuffer_, stillNeed, 0, tail);
pendingSavedCount_ = tail;
outPos += stillNeed;
}
}
}

bool IOSAudioPlayer::start()
{
if (isRunning()) {
return true;
}

bool success = [audioPlayer_ start];
if (success) {
flushOverflowNextPull_.store(true, std::memory_order_release);
}
isRunning_.store(success, std::memory_order_release);
return success;
}
Expand All @@ -74,6 +137,9 @@
}

bool success = [audioPlayer_ resume];
if (success) {
flushOverflowNextPull_.store(true, std::memory_order_release);
}
isRunning_.store(success, std::memory_order_release);
return success;
}
Expand Down
Loading