diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.cpp new file mode 100644 index 000000000..2819ec06c --- /dev/null +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +namespace audioapi { +AndroidFileWriterBackend::AndroidFileWriterBackend( + const std::shared_ptr &audioEventHandlerRegistry, + const std::shared_ptr &fileProperties) + : AudioFileWriter(audioEventHandlerRegistry, fileProperties) { + + auto offloaderLambda = [this](WriterData data) { + taskOffloaderFunction(data); + }; + offloader_ = std::make_unique>(FILE_WRITER_CHANNEL_CAPACITY, offloaderLambda); +} + +void AndroidFileWriterBackend::writeAudioData(void *data, int numFrames) { + offloader_->getSender()->send({data, numFrames}); +} +} // namespace audioapi diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.h b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.h index 62bde4b5f..5c7056a51 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.h +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.h @@ -1,11 +1,18 @@ #pragma once #include +#include +#include #include #include #include #include +struct WriterData { + void *data; + int numFrames; +}; + namespace audioapi { class AudioFileProperties; @@ -14,11 +21,10 @@ class AndroidFileWriterBackend : public AudioFileWriter { public: explicit AndroidFileWriterBackend( const std::shared_ptr &audioEventHandlerRegistry, - const std::shared_ptr &fileProperties) - : AudioFileWriter(audioEventHandlerRegistry, fileProperties) {} + const std::shared_ptr &fileProperties); virtual OpenFileResult openFile(float streamSampleRate, int32_t streamChannelCount, int32_t streamMaxBufferSize, const std::string &fileNameOverride) = 0; - virtual bool writeAudioData(void *data, int numFrames) = 0; + void writeAudioData(void *data, int numFrames); std::string getFilePath() const override { return filePath_; } double getCurrentDuration() const override { return static_cast(framesWritten_.load(std::memory_order_acquire)) / streamSampleRate_; } @@ -27,7 +33,11 @@ class AndroidFileWriterBackend : public AudioFileWriter { float streamSampleRate_{0}; int32_t streamChannelCount_{0}; int32_t streamMaxBufferSize_{0}; - std::string filePath_{""}; + std::string filePath_; + + // delay initialization of offloader until prepare is called + std::unique_ptr> offloader_; + virtual void taskOffloaderFunction(WriterData data) = 0; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp index 83f62c4a5..1a2e3ba56 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp @@ -105,9 +105,8 @@ Result AndroidRecorderCallback::prepare( }; offloader_ = std::make_unique>( - AudioRecorderCallback::RECORDER_CALLBACK_CHANNEL_CAPACITY, offloaderLambda); + RECORDER_CALLBACK_SPSC_OVERFLOW_STRATEGY, + RECORDER_CALLBACK_SPSC_WAIT_STRATEGY>>(RECORDER_CALLBACK_CHANNEL_CAPACITY, offloaderLambda); return Result::Ok(None); } diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h index 781b2b7ba..8c4c6e81a 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h @@ -50,7 +50,7 @@ class AndroidRecorderCallback : public AudioRecorderCallback { private: // delay initialization of offloader until prepare is called - std::unique_ptr> offloader_; + std::unique_ptr> offloader_; void taskOffloaderFunction(CallbackData data); }; diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp index c713b9717..7d97acf1b 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp @@ -76,6 +76,15 @@ OpenFileResult FFmpegAudioFileWriter::openFile( return OpenFileResult::Err("Unsupported codec for the given file format"); } + auto offloaderLambda = [this](WriterData data) { + taskOffloaderFunction(data); + }; + + offloader_ = std::make_unique>(FILE_WRITER_CHANNEL_CAPACITY, offloaderLambda); + return initializeFormatContext(codec) .and_then([this, codec](auto) { return configureAndOpenCodec(codec); }) .and_then([this](auto) { return initializeStream(); }) @@ -119,31 +128,27 @@ CloseFileResult FFmpegAudioFileWriter::closeFile() { if (writeEncodedPackets() < 0) { return CloseFileResult::Err("Failed to drain encoder packets"); } + offloader_.reset(); return finalizeOutput(); } /// @brief Writes audio data to the currently opened file. -/// This method should be called only from the audio thread (or audio side-effect thread in the future). -/// @param data Pointer to the audio data buffer (interleaved float samples) as returned by Oboe stream. -/// @param numFrames Number of audio frames in the data buffer. -/// @returns True if the data was written successfully, false otherwise. -bool FFmpegAudioFileWriter::writeAudioData(void *data, int numFrames) { +void FFmpegAudioFileWriter::taskOffloaderFunction(WriterData data) { + auto [audioData, numFrames] = data; if (!isFileOpen()) { - return false; + return; } - if (!resampleAndPushToFifo(data, numFrames)) { - return false; + if (!resampleAndPushToFifo(audioData, numFrames)) { + return; } framesWritten_.fetch_add(numFrames, std::memory_order_acq_rel); if (processFifo(false) < 0) { - return false; + return; } - - return true; } /// @brief Initializes the FFmpeg format context for the output file. diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h index be44a2b8c..ff95874e7 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h @@ -32,7 +32,6 @@ class FFmpegAudioFileWriter : public AndroidFileWriterBackend { OpenFileResult openFile(float streamSampleRate, int32_t streamChannelCount, int32_t streamMaxBufferSize, const std::string &fileNameOverride) override; CloseFileResult closeFile() override; - bool writeAudioData(void *data, int numFrames) override; private: av_unique_ptr encoderCtx_{nullptr}; @@ -64,6 +63,7 @@ class FFmpegAudioFileWriter : public AndroidFileWriterBackend { // Finalization helper methods CloseFileResult finalizeOutput(); + void taskOffloaderFunction(WriterData data) override; }; } // namespace android::ffmpeg diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp index 6d205992f..d8e1fe8e3 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp @@ -53,12 +53,12 @@ MiniAudioFileWriter::~MiniAudioFileWriter() { } if (converter_ != nullptr) { - ma_data_converter_uninit(converter_.get(), NULL); + ma_data_converter_uninit(converter_.get(), nullptr); converter_.reset(); } if (processingBuffer_ != nullptr) { - ma_free(processingBuffer_, NULL); + ma_free(processingBuffer_, nullptr); processingBuffer_ = nullptr; processingBufferLength_ = 0; } @@ -103,6 +103,15 @@ OpenFileResult MiniAudioFileWriter::openFile( "Failed to initialize encoder" + std::string(ma_result_description(result))); } + auto offloaderLambda = [this](WriterData data) { + taskOffloaderFunction(data); + }; + + offloader_ = std::make_unique>(FILE_WRITER_CHANNEL_CAPACITY, offloaderLambda); + isFileOpen_.store(true, std::memory_order_release); return OpenFileResult ::Ok(filePath_); } @@ -125,12 +134,12 @@ CloseFileResult MiniAudioFileWriter::closeFile() { } if (converter_ != nullptr) { - ma_data_converter_uninit(converter_.get(), NULL); + ma_data_converter_uninit(converter_.get(), nullptr); converter_.reset(); } if (processingBuffer_ != nullptr) { - ma_free(processingBuffer_, NULL); + ma_free(processingBuffer_, nullptr); processingBuffer_ = nullptr; processingBufferLength_ = 0; } @@ -141,7 +150,7 @@ CloseFileResult MiniAudioFileWriter::closeFile() { ma_decoder decoder; - if (ma_decoder_init_file(filePath_.c_str(), NULL, &decoder) == MA_SUCCESS) { + if (ma_decoder_init_file(filePath_.c_str(), nullptr, &decoder) == MA_SUCCESS) { ma_uint64 frameCount = 0; if (ma_decoder_get_length_in_pcm_frames(&decoder, &frameCount) == MA_SUCCESS) { @@ -159,6 +168,7 @@ CloseFileResult MiniAudioFileWriter::closeFile() { fclose(file); fileSizeInMB = static_cast(fileSizeInBytes) / MB_IN_BYTES; } + offloader_.reset(); filePath_ = ""; return CloseFileResult ::Ok({fileSizeInMB, durationInSeconds}); @@ -167,33 +177,30 @@ CloseFileResult MiniAudioFileWriter::closeFile() { /// @brief Writes audio data to the file. /// If possible (sample format, channel count, and interleaving matches), /// the data is written directly, otherwise in-memory conversion is performed first -/// It should be called only on the audio thread. -/// @param data Pointer to the audio data buffer. (Interleaved float32 format - as oboe likes it) -/// @param numFrames Number of audio frames to write. -/// @return True if the write operation was successful, false otherwise. -bool MiniAudioFileWriter::writeAudioData(void *data, int numFrames) { +void MiniAudioFileWriter::taskOffloaderFunction(WriterData data) { + auto [audioData, numFrames] = data; ma_uint64 framesWritten = 0; ma_result result; if (!isFileOpen()) { - return false; + return; } if (!isConverterRequired()) { - result = ma_encoder_write_pcm_frames(encoder_.get(), data, numFrames, &framesWritten); + result = ma_encoder_write_pcm_frames(encoder_.get(), audioData, numFrames, &framesWritten); if (result != MA_SUCCESS) { invokeOnErrorCallback( "Failed to write audio data to file: " + filePath_ + std::string(ma_result_description(result))); - return false; + return; } framesWritten_.fetch_add(numFrames, std::memory_order_acq_rel); - return result == MA_SUCCESS; + return; } - ma_uint64 convertedFrameCount = convertBuffer(data, numFrames); + ma_uint64 convertedFrameCount = convertBuffer(audioData, numFrames); result = ma_encoder_write_pcm_frames( encoder_.get(), processingBuffer_, convertedFrameCount, &framesWritten); @@ -202,11 +209,10 @@ bool MiniAudioFileWriter::writeAudioData(void *data, int numFrames) { invokeOnErrorCallback( "Failed to write converted audio data to file: " + filePath_ + std::string(ma_result_description(result))); - return false; + return; } framesWritten_.fetch_add(numFrames, std::memory_order_acq_rel); - return result == MA_SUCCESS; } /// @brief Converts the audio data buffer if necessary. @@ -247,7 +253,7 @@ ma_result MiniAudioFileWriter::initializeConverterIfNeeded() { fileProperties_->sampleRate); converter_ = std::make_unique(); - result = ma_data_converter_init(&converterConfig, NULL, converter_.get()); + result = ma_data_converter_init(&converterConfig, nullptr, converter_.get()); if (result != MA_SUCCESS) { return result; @@ -258,7 +264,7 @@ ma_result MiniAudioFileWriter::initializeConverterIfNeeded() { processingBuffer_ = ma_malloc( processingBufferLength_ * fileProperties_->channelCount * ma_get_bytes_per_sample(dataFormat), - NULL); + nullptr); return MA_SUCCESS; } diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h index d49063b45..624c9ed87 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h @@ -20,7 +20,6 @@ class MiniAudioFileWriter : public AndroidFileWriterBackend { OpenFileResult openFile(float streamSampleRate, int32_t streamChannelCount, int32_t streamMaxBufferSize, const std::string &fileNameOverride) override; CloseFileResult closeFile() override; - bool writeAudioData(void *data, int numFrames) override; private: std::atomic isConverterRequired_{false}; @@ -35,6 +34,7 @@ class MiniAudioFileWriter : public AndroidFileWriterBackend { ma_uint64 convertBuffer(void *data, int numFrames); bool isConverterRequired(); + void taskOffloaderFunction(WriterData data) override; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioFileWriter.h b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioFileWriter.h index b7ad85c7f..464ac82e8 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioFileWriter.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/core/utils/AudioFileWriter.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -39,6 +40,11 @@ class AudioFileWriter { std::shared_ptr fileProperties_; std::shared_ptr audioEventHandlerRegistry_; + + static constexpr auto FILE_WRITER_SPSC_OVERFLOW_STRATEGY = + channels::spsc::OverflowStrategy::OVERWRITE_ON_FULL; + static constexpr auto FILE_WRITER_SPSC_WAIT_STRATEGY = channels::spsc::WaitStrategy::ATOMIC_WAIT; + static constexpr auto FILE_WRITER_CHANNEL_CAPACITY = 64; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSFileWriter.h b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSFileWriter.h index af34f6baa..399406511 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSFileWriter.h +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSFileWriter.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -15,6 +17,11 @@ typedef struct objc_object AudioBufferList; typedef struct objc_object AVAudioConverter; #endif // __OBJC__ +struct WriterData { + const AudioBufferList *audioBufferList; + int numFrames; +}; + namespace audioapi { class AudioFileProperties; @@ -33,7 +40,7 @@ class IOSFileWriter : public AudioFileWriter { const std::string &fileNameOverride); Result, std::string> closeFile() override; - bool writeAudioData(const AudioBufferList *audioBufferList, int numFrames); + void writeAudioData(const AudioBufferList *audioBufferList, int numFrames); double getCurrentDuration() const override; std::string getFilePath() const override; @@ -49,6 +56,15 @@ class IOSFileWriter : public AudioFileWriter { AVAudioPCMBuffer *converterInputBuffer_; AVAudioPCMBuffer *converterOutputBuffer_; + + private: + // delay initialization of offloader until prepare is called + std::unique_ptr> + offloader_; + void taskOffloaderFunction(WriterData data); }; } // namespace audioapi diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSFileWriter.mm b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSFileWriter.mm index a453e9233..e486edf2b 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSFileWriter.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSFileWriter.mm @@ -99,6 +99,15 @@ return OpenFileResult::Err("Error creating converter buffers"); } + auto offloaderLambda = [this](WriterData data) { + taskOffloaderFunction(data); + }; + + offloader_ = std::make_unique>(FILE_WRITER_CHANNEL_CAPACITY, offloaderLambda); + return OpenFileResult::Ok([[fileURL_ path] UTF8String]); } } @@ -134,6 +143,7 @@ fileURL_ = nil; framesWritten_.store(0, std::memory_order_release); + offloader_.reset(); return CloseFileResult::Ok(std::make_tuple(fileDuration, fileSizeBytesMb)); } @@ -141,16 +151,20 @@ /// @brief Writes audio data to the open audio file, performing format conversion if necessary. /// This method should be called from the audio thread. -/// @param audioBufferList Pointer to the AudioBufferList containing the audio data to write. -/// @param numFrames Number of audio frames in the audioBufferList. -/// @returns True if the write operation was successful, false otherwise. -bool IOSFileWriter::writeAudioData(const AudioBufferList *audioBufferList, int numFrames) +void IOSFileWriter::writeAudioData(const AudioBufferList *audioBufferList, int numFrames) { if (audioFile_ == nil) { invokeOnErrorCallback("Attempted to write audio data when file is not open"); - return false; + } else { + offloader_->getSender()->send({audioBufferList, numFrames}); } +} +void IOSFileWriter::taskOffloaderFunction(WriterData data) +{ + auto [audioBufferList, numFrames] = data; + if (audioBufferList == nullptr) + return; @autoreleasepool { NSError *error = nil; AVAudioFormat *fileFormat = [audioFile_ processingFormat]; @@ -173,11 +187,11 @@ invokeOnErrorCallback( std::string("Error writing audio data to file, native error: ") + [[error debugDescription] UTF8String]); - return false; + return; } framesWritten_.fetch_add(numFrames, std::memory_order_acq_rel); - return true; + return; } for (size_t i = 0; i < bufferFormat_.channelCount; ++i) { @@ -211,7 +225,7 @@ invokeOnErrorCallback( std::string("Error during audio conversion, native error: ") + [[error debugDescription] UTF8String]); - return false; + return; } [audioFile_ writeFromBuffer:converterOutputBuffer_ error:&error]; @@ -220,11 +234,10 @@ invokeOnErrorCallback( std::string("Error writing audio data to file, native error: ") + [[error debugDescription] UTF8String]); - return false; + return; } framesWritten_.fetch_add(numFrames, std::memory_order_acq_rel); - return true; } } diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSRecorderCallback.h b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSRecorderCallback.h index c02f57345..ac87f79a9 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSRecorderCallback.h +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSRecorderCallback.h @@ -51,8 +51,8 @@ class IOSRecorderCallback : public AudioRecorderCallback { private: std::unique_ptr> + RECORDER_CALLBACK_SPSC_OVERFLOW_STRATEGY, + RECORDER_CALLBACK_SPSC_WAIT_STRATEGY>> offloader_; // delay initialization of offloader until prepare is called void taskOffloaderFunction(CallbackData data); diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm index c906d9e1f..fdcf7c58d 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm @@ -93,9 +93,8 @@ }; offloader_ = std::make_unique>( - AudioRecorderCallback::RECORDER_CALLBACK_CHANNEL_CAPACITY, offloaderLambda); + RECORDER_CALLBACK_SPSC_OVERFLOW_STRATEGY, + RECORDER_CALLBACK_SPSC_WAIT_STRATEGY>>(RECORDER_CALLBACK_CHANNEL_CAPACITY, offloaderLambda); } return Result::Ok(None);