diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp index 547b734fa..a88c634c7 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp @@ -31,6 +31,7 @@ bool AudioPlayer::openAudioStream() { ->setPerformanceMode(PerformanceMode::None) ->setChannelCount(channelCount_) ->setSampleRateConversionQuality(SampleRateConversionQuality::Medium) + ->setFramesPerDataCallback(RENDER_QUANTUM_SIZE) ->setDataCallback(this) ->setSampleRate(static_cast(sampleRate_)) ->setErrorCallback(this); diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp index d7ba8eedb..fa659999e 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.cpp @@ -88,21 +88,15 @@ void ConvolverNode::onInputDisabled() { } } -std::shared_ptr ConvolverNode::processInputs( - const std::shared_ptr &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 ConvolverNode::processNode( const std::shared_ptr &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"); + } if (signalledToStop_) { if (remainingSegments_ > 0) { remainingSegments_--; diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h index be84e97bf..832dd5c57 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/effects/ConvolverNode.h @@ -41,10 +41,6 @@ class ConvolverNode : public AudioNode { int framesToProcess) override; private: - std::shared_ptr processInputs( - const std::shared_ptr &outputBuffer, - int framesToProcess, - bool checkIsAlreadyProcessed) override; void onInputDisabled() override; const float gainCalibrationSampleRate_; size_t remainingSegments_; diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.h b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.h index b15c69af2..ced8ce26a 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.h +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.h @@ -4,11 +4,14 @@ #import #else // when compiled as C++ typedef struct objc_object NativeAudioPlayer; +typedef struct objc_object AudioBufferList; #endif // __OBJC__ #include -#include +#include +#include +#include namespace audioapi { class AudioContext; @@ -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 audioBuffer_; NativeAudioPlayer *audioPlayer_; std::function, int)> renderAudio_; int channelCount_; std::atomic isRunning_; + /// Set from main thread on start/resume; consumed on audio thread to drop stale pending audio. + std::atomic flushOverflowNextPull_{false}; + /// Frames valid at the front of each `pendingSaved_[ch]` (0 … RENDER_QUANTUM_SIZE). + int pendingSavedCount_{0}; + DSPAudioBuffer pendingSaved_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.mm b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.mm index 500d58f3a..6567731bf 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioPlayer.mm @@ -1,10 +1,11 @@ #import +#include +#include + #include -#include #include #include -#include #include namespace audioapi { @@ -13,35 +14,20 @@ const std::function, 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(RENDER_QUANTUM_SIZE, channelCount_, sampleRate); } @@ -50,6 +36,80 @@ 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(outputData->mBuffers[channel].mData); + std::memset(outputChannel, 0, static_cast(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(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); + + // 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(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(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()) { @@ -57,6 +117,9 @@ } bool success = [audioPlayer_ start]; + if (success) { + flushOverflowNextPull_.store(true, std::memory_order_release); + } isRunning_.store(success, std::memory_order_release); return success; } @@ -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; }