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 @@ -7,6 +7,7 @@
#include <audioapi/utils/CircularArray.hpp>

#include <algorithm>
#include <cstring>
#include <memory>
#include <string>
#include <unordered_map>
Expand Down Expand Up @@ -35,20 +36,7 @@ AndroidRecorderCallback::AndroidRecorderCallback(
callbackId) {}

AndroidRecorderCallback::~AndroidRecorderCallback() {
if (converter_ != nullptr) {
ma_data_converter_uninit(converter_.get(), nullptr);
converter_.reset();
}

if (processingBuffer_ != nullptr) {
ma_free(processingBuffer_, nullptr);
processingBuffer_ = nullptr;
processingBufferLength_ = 0;
}

for (size_t i = 0; i < circularBuffer_.size(); ++i) {
circularBuffer_[i]->zero();
}
cleanup();
}

/// @brief Prepares the recorder callback by initializing the data converter and allocating necessary buffers.
Expand Down Expand Up @@ -111,6 +99,7 @@ Result<NoneType, std::string> AndroidRecorderCallback::prepare(
}

void AndroidRecorderCallback::cleanup() {
std::scoped_lock lock(callbackMutex_);
if (circularBuffer_[0]->getNumberOfAvailableFrames() > 0) {
emitAudioData(true);
}
Expand Down Expand Up @@ -141,7 +130,18 @@ void AndroidRecorderCallback::receiveAudioData(void *data, int numFrames) {
return;
}

offloader_->getSender()->send({data, numFrames});
// Oboe owns `data` only for the duration of this synchronous callback.
// Copy into an owned buffer before handing off to the worker thread; the
// consumer in taskOffloaderFunction frees it.
size_t bytes =
static_cast<size_t>(numFrames) * streamChannelCount_ * ma_get_bytes_per_sample(ma_format_f32);
void *owned = ma_malloc(bytes, nullptr);
if (owned == nullptr) {
return;
}
std::memcpy(owned, data, bytes);

offloader_->getSender()->send({owned, numFrames});
}

/// @brief Deinterleaves the audio data and pushes it into the circular buffer.
Expand All @@ -160,6 +160,14 @@ void AndroidRecorderCallback::deinterleaveAndPushAudioData(void *data, int numFr
/// processes it (resampling and deinterleaving if necessary), and pushes it into the circular buffer.
void AndroidRecorderCallback::taskOffloaderFunction(CallbackData callbackData) {
auto [data, numFrames] = callbackData;

// The TaskOffloader destructor sends a default-constructed CallbackData
// (data == nullptr) to unblock the receiver; ignore it here.
if (data == nullptr) {
return;
}
std::scoped_lock lock(callbackMutex_);

ma_uint64 inputFrameCount = numFrames;
ma_uint64 outputFrameCount = 0;

Expand All @@ -170,6 +178,7 @@ void AndroidRecorderCallback::taskOffloaderFunction(CallbackData callbackData) {
if (circularBuffer_[0]->getNumberOfAvailableFrames() >= bufferLength_) {
emitAudioData();
}
ma_free(data, nullptr);
return;
}

Expand All @@ -184,6 +193,8 @@ void AndroidRecorderCallback::taskOffloaderFunction(CallbackData callbackData) {
if (circularBuffer_[0]->getNumberOfAvailableFrames() >= bufferLength_) {
emitAudioData();
}

ma_free(data, nullptr);
}

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class AndroidRecorderCallback : public AudioRecorderCallback {
RECORDER_CALLBACK_SPSC_WAIT_STRATEGY>>
offloader_;
void taskOffloaderFunction(CallbackData data);
std::mutex callbackMutex_;
};

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class IOSRecorderCallback : public AudioRecorderCallback {
offloader_;
// delay initialization of offloader until prepare is called
void taskOffloaderFunction(CallbackData data);
std::mutex callbackMutex_;
};

} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <audioapi/utils/CircularArray.hpp>
#include <audioapi/utils/Result.hpp>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <utility>

namespace audioapi {
Expand All @@ -32,17 +34,7 @@

IOSRecorderCallback::~IOSRecorderCallback()
{
@autoreleasepool {
converter_ = nil;
bufferFormat_ = nil;
callbackFormat_ = nil;
converterInputBuffer_ = nil;
converterOutputBuffer_ = nil;

for (size_t i = 0; i < channelCount_; ++i) {
circularBuffer_[i]->zero();
}
}
cleanup();
}

/// @brief Prepares the IOSRecorderCallback for receiving audio data.
Expand Down Expand Up @@ -104,6 +96,7 @@
/// This method should be called from the JS thread only.
void IOSRecorderCallback::cleanup()
{
std::scoped_lock lock(callbackMutex_);
@autoreleasepool {
if (circularBuffer_[0]->getNumberOfAvailableFrames() > 0) {
emitAudioData(true);
Expand All @@ -115,7 +108,7 @@
converterInputBuffer_ = nil;
converterOutputBuffer_ = nil;

for (size_t i = 0; i < channelCount_; ++i) {
for (int i = 0; i < channelCount_; ++i) {
circularBuffer_[i]->zero();
}
offloader_.reset();
Expand All @@ -132,15 +125,56 @@
if (!isInitialized_.load(std::memory_order_acquire)) {
return;
}
offloader_->getSender()->send({inputBuffer, numFrames});

// CoreAudio owns `inputBuffer` only for the duration of this synchronous
// callback. Copy into an owned AudioBufferList before handing off to the
// worker thread; the consumer in taskOffloaderFunction frees it.
UInt32 bufferCount = inputBuffer->mNumberBuffers;
size_t headerSize = offsetof(AudioBufferList, mBuffers) + sizeof(AudioBuffer) * bufferCount;
AudioBufferList *owned = static_cast<AudioBufferList *>(std::malloc(headerSize));
if (owned == nullptr) {
return;
}
owned->mNumberBuffers = bufferCount;
for (UInt32 i = 0; i < bufferCount; ++i) {
UInt32 byteSize = inputBuffer->mBuffers[i].mDataByteSize;
owned->mBuffers[i].mNumberChannels = inputBuffer->mBuffers[i].mNumberChannels;
owned->mBuffers[i].mDataByteSize = byteSize;
void *channelData = std::malloc(byteSize);
if (channelData == nullptr) {
for (UInt32 j = 0; j < i; ++j) {
std::free(owned->mBuffers[j].mData);
}
std::free(owned);
return;
}
std::memcpy(channelData, inputBuffer->mBuffers[i].mData, byteSize);
owned->mBuffers[i].mData = channelData;
}

offloader_->getSender()->send({owned, numFrames});
}

static inline void freeOwnedAudioBufferList(const AudioBufferList *bufferList)
{
if (bufferList == nullptr) {
return;
}
for (UInt32 i = 0; i < bufferList->mNumberBuffers; ++i) {
std::free(bufferList->mBuffers[i].mData);
}
std::free(const_cast<AudioBufferList *>(bufferList));
}

void IOSRecorderCallback::taskOffloaderFunction(CallbackData data)
{
auto [inputBuffer, numFrames] = data;
// dummy data to wake up thread after cleanup, skip processing it

// The TaskOffloader destructor sends a default-constructed CallbackData
// (data == nullptr) to unblock the receiver; ignore it here.
if (inputBuffer == nullptr)
return;
std::scoped_lock lock(callbackMutex_);
@autoreleasepool {
NSError *error = nil;

Expand All @@ -152,6 +186,7 @@
circularBuffer_[i]->push_back(data, numFrames);
}

freeOwnedAudioBufferList(inputBuffer);
inputBuffer = nullptr;
if (circularBuffer_[0]->getNumberOfAvailableFrames() >= bufferLength_) {
emitAudioData();
Expand All @@ -168,6 +203,7 @@
inputBuffer->mBuffers[i].mDataByteSize);
}

freeOwnedAudioBufferList(inputBuffer);
inputBuffer = nullptr;
converterInputBuffer_.frameLength = numFrames;

Expand Down
Loading