use rusty_esp_core::error::{Error, Result};
use rusty_esp_core::pcm::PcmFormat;
#[derive(Debug)]
pub struct RingBuffer<'m> {
buf: &'m mut [u8],
frame_bytes: usize,
cap: usize,
head: usize,
len: usize,
pub dropped_frames: u64,
pub high_water_frames: usize,
}
impl<'m> RingBuffer<'m> {
pub fn new(buf: &'m mut [u8], format: PcmFormat) -> Result<Self> {
let frame_bytes = format.frame_bytes();
let cap = (buf.len() / frame_bytes) * frame_bytes;
if cap == 0 {
return Err(Error::BufferTooSmall {
needed: frame_bytes,
});
}
Ok(RingBuffer {
buf,
frame_bytes,
cap,
head: 0,
len: 0,
dropped_frames: 0,
high_water_frames: 0,
})
}
#[must_use]
pub fn capacity_frames(&self) -> usize {
self.cap / self.frame_bytes
}
#[must_use]
pub fn available_frames(&self) -> usize {
self.len / self.frame_bytes
}
#[must_use]
pub fn free_frames(&self) -> usize {
(self.cap - self.len) / self.frame_bytes
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn clear(&mut self) {
self.head = 0;
self.len = 0;
}
fn check_frames(&self, data: &[u8]) -> Result<()> {
if data.len() % self.frame_bytes != 0 {
return Err(Error::InvalidGeometry);
}
if data.len() > self.cap {
return Err(Error::BufferTooSmall { needed: data.len() });
}
Ok(())
}
fn write_at_tail(&mut self, data: &[u8]) {
let tail = (self.head + self.len) % self.cap;
let first = (self.cap - tail).min(data.len());
self.buf[tail..tail + first].copy_from_slice(&data[..first]);
let rest = data.len() - first;
if rest > 0 {
self.buf[..rest].copy_from_slice(&data[first..]);
}
self.len += data.len();
let frames = self.len / self.frame_bytes;
if frames > self.high_water_frames {
self.high_water_frames = frames;
}
}
pub fn push(&mut self, data: &[u8]) -> Result<()> {
self.check_frames(data)?;
if data.len() > self.cap - self.len {
self.dropped_frames += (data.len() / self.frame_bytes) as u64;
return Err(Error::Busy);
}
self.write_at_tail(data);
Ok(())
}
pub fn push_overwrite(&mut self, data: &[u8]) -> Result<usize> {
self.check_frames(data)?;
let need = data.len().saturating_sub(self.cap - self.len);
if need > 0 {
self.head = (self.head + need) % self.cap;
self.len -= need;
let frames = need / self.frame_bytes;
self.dropped_frames += frames as u64;
self.write_at_tail(data);
return Ok(frames);
}
self.write_at_tail(data);
Ok(0)
}
pub fn pop(&mut self, out: &mut [u8]) -> usize {
let want = (out.len() / self.frame_bytes) * self.frame_bytes;
let n = want.min(self.len);
if n == 0 {
return 0;
}
let first = (self.cap - self.head).min(n);
out[..first].copy_from_slice(&self.buf[self.head..self.head + first]);
if n > first {
out[first..n].copy_from_slice(&self.buf[..n - first]);
}
self.head = (self.head + n) % self.cap;
self.len -= n;
n
}
pub fn pop_exact(&mut self, out: &mut [u8]) -> bool {
if out.len() % self.frame_bytes != 0 || out.len() > self.len || out.is_empty() {
return false;
}
self.pop(out);
true
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fmt() -> PcmFormat {
PcmFormat::PCM16_48K_STEREO }
#[test]
fn wraps_and_counts() {
let mut mem = [0u8; 18]; let mut r = RingBuffer::new(&mut mem, fmt()).unwrap();
assert_eq!(r.capacity_frames(), 4);
r.push(&[1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]).unwrap();
let mut out = [0u8; 8];
assert_eq!(r.pop(&mut out), 8);
assert_eq!(out, [1, 1, 1, 1, 2, 2, 2, 2]);
r.push(&[4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6]).unwrap();
assert_eq!(r.available_frames(), 4);
assert_eq!(r.free_frames(), 0);
assert_eq!(r.high_water_frames, 4);
assert_eq!(r.push(&[9, 9, 9, 9]).err(), Some(Error::Busy));
assert_eq!(r.dropped_frames, 1);
let mut all = [0u8; 16];
assert_eq!(r.pop(&mut all), 16);
assert_eq!(all, [3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6]);
assert!(r.is_empty());
assert_eq!(r.pop(&mut all), 0);
}
#[test]
fn overwrite_discards_oldest() {
let mut mem = [0u8; 12];
let mut r = RingBuffer::new(&mut mem, fmt()).unwrap();
r.push(&[1, 1, 1, 1, 2, 2, 2, 2]).unwrap();
assert_eq!(r.push_overwrite(&[3, 3, 3, 3, 4, 4, 4, 4]).unwrap(), 1);
assert_eq!(r.dropped_frames, 1);
let mut out = [0u8; 12];
assert_eq!(r.pop(&mut out), 12);
assert_eq!(out, [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4]);
}
#[test]
fn pop_exact_waits_for_a_block() {
let mut mem = [0u8; 16];
let mut r = RingBuffer::new(&mut mem, fmt()).unwrap();
r.push(&[7; 4]).unwrap();
let mut out = [0u8; 8];
assert!(!r.pop_exact(&mut out));
r.push(&[8; 4]).unwrap();
assert!(r.pop_exact(&mut out));
assert_eq!(out, [7, 7, 7, 7, 8, 8, 8, 8]);
}
#[test]
fn rejects_misaligned_and_oversize() {
let mut mem = [0u8; 16];
let mut r = RingBuffer::new(&mut mem, fmt()).unwrap();
assert_eq!(r.push(&[0; 6]).err(), Some(Error::InvalidGeometry));
assert_eq!(
r.push(&[0; 20]).err(),
Some(Error::BufferTooSmall { needed: 20 })
);
let mut tiny = [0u8; 3];
assert_eq!(
RingBuffer::new(&mut tiny, fmt()).err(),
Some(Error::BufferTooSmall { needed: 4 })
);
}
}