pluginbridge / Source /Shared /SharedAudioBuffer.h
RAM2118's picture
Out-of-process rebuild: Add Shared/SharedAudioBuffer.h — lock-free shared memory audio transport
a4a5519 verified
Raw
History Blame Contribute Delete
6.66 kB
#pragma once
#include <cstdint>
#include <cstring>
#include <atomic>
#include <string>
#ifdef __APPLE__
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <dispatch/dispatch.h>
#endif
// Shared memory layout for audio transport between Plugin and Helper
// Zero-copy: both processes mmap the same region
struct SharedAudioHeader {
std::atomic<uint32_t> sampleRate{44100};
std::atomic<uint32_t> blockSize{512};
std::atomic<uint32_t> numChannels{2};
std::atomic<uint32_t> state{0}; // 0=idle, 1=input_ready, 2=output_ready
std::atomic<uint32_t> helperAlive{0}; // heartbeat from helper
uint32_t padding[3]; // align to 32 bytes
};
// State machine:
// Parent: writes input → sets state=1 → signals helper
// Helper: reads input → processBlock → writes output → sets state=2 → signals parent
// Parent: reads output → sets state=0
static constexpr uint32_t kStateIdle = 0;
static constexpr uint32_t kStateInputReady = 1;
static constexpr uint32_t kStateOutputReady = 2;
// Max supported: stereo, 4096 samples per block
static constexpr size_t kMaxChannels = 2;
static constexpr size_t kMaxBlockSize = 4096;
static constexpr size_t kAudioDataSize = kMaxChannels * kMaxBlockSize * sizeof(float);
static constexpr size_t kSharedMemSize = sizeof(SharedAudioHeader) + (kAudioDataSize * 2); // input + output
class SharedAudioBuffer {
public:
SharedAudioBuffer() = default;
~SharedAudioBuffer() { destroy(); }
// Create (parent) or open (helper) shared memory
bool create(const std::string& name)
{
shmName = "/" + name;
// Remove if exists from previous crash
shm_unlink(shmName.c_str());
fd = shm_open(shmName.c_str(), O_CREAT | O_RDWR, 0666);
if (fd < 0) return false;
if (ftruncate(fd, kSharedMemSize) != 0) {
close(fd);
return false;
}
ptr = (uint8_t*)mmap(nullptr, kSharedMemSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
close(fd);
return false;
}
// Zero the whole region
memset(ptr, 0, kSharedMemSize);
header = reinterpret_cast<SharedAudioHeader*>(ptr);
inputBuffer = reinterpret_cast<float*>(ptr + sizeof(SharedAudioHeader));
outputBuffer = reinterpret_cast<float*>(ptr + sizeof(SharedAudioHeader) + kAudioDataSize);
isOwner = true;
return true;
}
bool open(const std::string& name)
{
shmName = "/" + name;
fd = shm_open(shmName.c_str(), O_RDWR, 0666);
if (fd < 0) return false;
ptr = (uint8_t*)mmap(nullptr, kSharedMemSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
close(fd);
return false;
}
header = reinterpret_cast<SharedAudioHeader*>(ptr);
inputBuffer = reinterpret_cast<float*>(ptr + sizeof(SharedAudioHeader));
outputBuffer = reinterpret_cast<float*>(ptr + sizeof(SharedAudioHeader) + kAudioDataSize);
isOwner = false;
return true;
}
void destroy()
{
if (ptr && ptr != MAP_FAILED) {
munmap(ptr, kSharedMemSize);
ptr = nullptr;
}
if (fd >= 0) {
close(fd);
fd = -1;
}
if (isOwner && !shmName.empty()) {
shm_unlink(shmName.c_str());
}
}
// --- Audio data access ---
SharedAudioHeader* getHeader() { return header; }
void writeInput(const float* const* channelData, int numChannels, int numSamples)
{
int ch = std::min(numChannels, (int)kMaxChannels);
int ns = std::min(numSamples, (int)kMaxBlockSize);
for (int c = 0; c < ch; ++c)
memcpy(inputBuffer + c * kMaxBlockSize, channelData[c], ns * sizeof(float));
}
void readOutput(float* const* channelData, int numChannels, int numSamples)
{
int ch = std::min(numChannels, (int)kMaxChannels);
int ns = std::min(numSamples, (int)kMaxBlockSize);
for (int c = 0; c < ch; ++c)
memcpy(channelData[c], outputBuffer + c * kMaxBlockSize, ns * sizeof(float));
}
void readInput(float* const* channelData, int numChannels, int numSamples)
{
int ch = std::min(numChannels, (int)kMaxChannels);
int ns = std::min(numSamples, (int)kMaxBlockSize);
for (int c = 0; c < ch; ++c)
memcpy(channelData[c], inputBuffer + c * kMaxBlockSize, ns * sizeof(float));
}
void writeOutput(const float* const* channelData, int numChannels, int numSamples)
{
int ch = std::min(numChannels, (int)kMaxChannels);
int ns = std::min(numSamples, (int)kMaxBlockSize);
for (int c = 0; c < ch; ++c)
memcpy(outputBuffer + c * kMaxBlockSize, channelData[c], ns * sizeof(float));
}
bool isValid() const { return ptr != nullptr && ptr != MAP_FAILED; }
private:
uint8_t* ptr = nullptr;
int fd = -1;
bool isOwner = false;
std::string shmName;
SharedAudioHeader* header = nullptr;
float* inputBuffer = nullptr;
float* outputBuffer = nullptr;
};
// --- Semaphore wrapper (macOS uses dispatch semaphores since POSIX named sems are deprecated) ---
class SharedSemaphore {
public:
SharedSemaphore() = default;
~SharedSemaphore() { destroy(); }
bool create(const std::string& name)
{
semName = name;
// macOS: use named POSIX semaphore (still works despite deprecation warning)
sem = sem_open(("/" + name).c_str(), O_CREAT, 0666, 0);
return sem != SEM_FAILED;
}
bool open(const std::string& name)
{
semName = name;
sem = sem_open(("/" + name).c_str(), 0);
return sem != SEM_FAILED;
}
void signal()
{
if (sem != SEM_FAILED)
sem_post(sem);
}
bool wait(int timeoutMs = 100)
{
if (sem == SEM_FAILED) return false;
// macOS doesn't support sem_timedwait, use polling
for (int i = 0; i < timeoutMs; ++i) {
if (sem_trywait(sem) == 0)
return true;
usleep(1000); // 1ms
}
return false;
}
bool tryWait()
{
if (sem == SEM_FAILED) return false;
return sem_trywait(sem) == 0;
}
void destroy()
{
if (sem != SEM_FAILED) {
sem_close(sem);
sem_unlink(("/" + semName).c_str());
sem = SEM_FAILED;
}
}
private:
sem_t* sem = SEM_FAILED;
std::string semName;
};