#pragma once #include #include #include #include /* Кольцевой буфер кадров переменной длины (SPSC, mutex-protected). * * Producer (uasync-поток) и consumer (аудио-поток). Память предвыделена одним * блоком — ноль аллокаций на кадр. При переполнении дропается старейший кадр * (семантика джиттер-буфера). */ class AudioFrameRing { public: AudioFrameRing(int capacity, int slotSize) : cap_(capacity), slotSize_(slotSize), buf_((size_t)capacity * (size_t)slotSize, 0), lens_((size_t)capacity, 0) {} /* Producer: положить кадр. Возвращает false если len > slotSize. * При переполнении дропает старейший кадр (успех). */ bool push(const uint8_t* data, int len) { if (len <= 0 || len > slotSize_) return false; std::lock_guard lk(mtx_); if (count_ == cap_) { head_ = (head_ + 1) % cap_; /* drop oldest */ count_--; } std::memcpy(buf_.data() + (size_t)tail_ * slotSize_, data, (size_t)len); lens_[(size_t)tail_] = (uint16_t)len; tail_ = (tail_ + 1) % cap_; count_++; return true; } /* Consumer: взять старейший кадр. Возвращает длину (0 = пусто). */ int pop(uint8_t* out, int outCap) { std::lock_guard lk(mtx_); if (count_ == 0) return 0; int len = (int)lens_[(size_t)head_]; if (out && len > 0 && len <= outCap) std::memcpy(out, buf_.data() + (size_t)head_ * slotSize_, (size_t)len); head_ = (head_ + 1) % cap_; count_--; return len; } void clear() { std::lock_guard lk(mtx_); head_ = tail_ = count_ = 0; } int size() const { std::lock_guard lk(mtx_); return count_; } int capacity() const { return cap_; } private: int cap_, slotSize_; int head_ = 0, tail_ = 0, count_ = 0; std::vector buf_; std::vector lens_; mutable std::mutex mtx_; };