Skip to main content

rusty_esp_audio_core/
ring.rs

1//! `RingBuffer`: ESP-ADF's `ringbuf` remade as a whole-frame ring over a
2//! caller-owned slice.
3//!
4//! The ring never splits a sample frame, counts every frame it had to drop,
5//! and remembers its high-water mark, so a firmware can print the three
6//! numbers that matter (capacity, peak fill, drops) before anyone reaches for
7//! a clock. It is single-owner: a Track A firmware that needs a DMA task and a
8//! network task shares it behind a `Mutex`; Track B hands it to one Embassy
9//! task and signals the other.
10
11use rusty_esp_core::error::{Error, Result};
12use rusty_esp_core::pcm::PcmFormat;
13
14/// A whole-frame byte ring over borrowed memory.
15#[derive(Debug)]
16pub struct RingBuffer<'m> {
17    buf: &'m mut [u8],
18    frame_bytes: usize,
19    /// Usable bytes: a whole number of frames.
20    cap: usize,
21    /// Read position in bytes.
22    head: usize,
23    /// Bytes stored.
24    len: usize,
25    /// Frames refused or overwritten because the ring was full.
26    pub dropped_frames: u64,
27    /// Most frames ever stored at once.
28    pub high_water_frames: usize,
29}
30
31impl<'m> RingBuffer<'m> {
32    /// A ring over `buf` for frames of `format`. `buf` must hold at least one
33    /// frame; a trailing partial frame is unused.
34    pub fn new(buf: &'m mut [u8], format: PcmFormat) -> Result<Self> {
35        let frame_bytes = format.frame_bytes();
36        let cap = (buf.len() / frame_bytes) * frame_bytes;
37        if cap == 0 {
38            return Err(Error::BufferTooSmall {
39                needed: frame_bytes,
40            });
41        }
42        Ok(RingBuffer {
43            buf,
44            frame_bytes,
45            cap,
46            head: 0,
47            len: 0,
48            dropped_frames: 0,
49            high_water_frames: 0,
50        })
51    }
52
53    /// Frames the ring can hold.
54    #[must_use]
55    pub fn capacity_frames(&self) -> usize {
56        self.cap / self.frame_bytes
57    }
58
59    /// Frames stored right now.
60    #[must_use]
61    pub fn available_frames(&self) -> usize {
62        self.len / self.frame_bytes
63    }
64
65    /// Frames that can still be pushed without dropping.
66    #[must_use]
67    pub fn free_frames(&self) -> usize {
68        (self.cap - self.len) / self.frame_bytes
69    }
70
71    /// True when nothing is stored.
72    #[must_use]
73    pub fn is_empty(&self) -> bool {
74        self.len == 0
75    }
76
77    /// Forget the contents (counters are kept).
78    pub fn clear(&mut self) {
79        self.head = 0;
80        self.len = 0;
81    }
82
83    fn check_frames(&self, data: &[u8]) -> Result<()> {
84        if data.len() % self.frame_bytes != 0 {
85            return Err(Error::InvalidGeometry);
86        }
87        if data.len() > self.cap {
88            return Err(Error::BufferTooSmall { needed: data.len() });
89        }
90        Ok(())
91    }
92
93    fn write_at_tail(&mut self, data: &[u8]) {
94        let tail = (self.head + self.len) % self.cap;
95        let first = (self.cap - tail).min(data.len());
96        self.buf[tail..tail + first].copy_from_slice(&data[..first]);
97        let rest = data.len() - first;
98        if rest > 0 {
99            self.buf[..rest].copy_from_slice(&data[first..]);
100        }
101        self.len += data.len();
102        let frames = self.len / self.frame_bytes;
103        if frames > self.high_water_frames {
104            self.high_water_frames = frames;
105        }
106    }
107
108    /// Append whole frames. When they do not all fit, nothing is stored, the
109    /// frames count as dropped and `Err(Busy)` comes back — the producer
110    /// keeps running and the drop is on the record.
111    pub fn push(&mut self, data: &[u8]) -> Result<()> {
112        self.check_frames(data)?;
113        if data.len() > self.cap - self.len {
114            self.dropped_frames += (data.len() / self.frame_bytes) as u64;
115            return Err(Error::Busy);
116        }
117        self.write_at_tail(data);
118        Ok(())
119    }
120
121    /// Append whole frames, discarding the oldest stored frames to make room.
122    /// Returns how many frames were discarded (also added to
123    /// `dropped_frames`). Use it when latency matters more than continuity.
124    pub fn push_overwrite(&mut self, data: &[u8]) -> Result<usize> {
125        self.check_frames(data)?;
126        let need = data.len().saturating_sub(self.cap - self.len);
127        if need > 0 {
128            self.head = (self.head + need) % self.cap;
129            self.len -= need;
130            let frames = need / self.frame_bytes;
131            self.dropped_frames += frames as u64;
132            self.write_at_tail(data);
133            return Ok(frames);
134        }
135        self.write_at_tail(data);
136        Ok(0)
137    }
138
139    /// Move up to `out.len()` bytes of whole frames out; returns bytes moved
140    /// (0 when the ring is empty).
141    pub fn pop(&mut self, out: &mut [u8]) -> usize {
142        let want = (out.len() / self.frame_bytes) * self.frame_bytes;
143        let n = want.min(self.len);
144        if n == 0 {
145            return 0;
146        }
147        let first = (self.cap - self.head).min(n);
148        out[..first].copy_from_slice(&self.buf[self.head..self.head + first]);
149        if n > first {
150            out[first..n].copy_from_slice(&self.buf[..n - first]);
151        }
152        self.head = (self.head + n) % self.cap;
153        self.len -= n;
154        n
155    }
156
157    /// Like [`pop`](Self::pop) but only when `out.len()` bytes are stored;
158    /// otherwise nothing moves and `false` comes back. This is how a consumer
159    /// waits for one whole block.
160    pub fn pop_exact(&mut self, out: &mut [u8]) -> bool {
161        if out.len() % self.frame_bytes != 0 || out.len() > self.len || out.is_empty() {
162            return false;
163        }
164        self.pop(out);
165        true
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    fn fmt() -> PcmFormat {
174        PcmFormat::PCM16_48K_STEREO // 4-byte frames
175    }
176
177    #[test]
178    fn wraps_and_counts() {
179        let mut mem = [0u8; 18]; // 4 frames usable, 2 bytes spare
180        let mut r = RingBuffer::new(&mut mem, fmt()).unwrap();
181        assert_eq!(r.capacity_frames(), 4);
182        r.push(&[1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]).unwrap();
183        let mut out = [0u8; 8];
184        assert_eq!(r.pop(&mut out), 8);
185        assert_eq!(out, [1, 1, 1, 1, 2, 2, 2, 2]);
186        // Now head is at frame 2; pushing 3 frames wraps around the end.
187        r.push(&[4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6]).unwrap();
188        assert_eq!(r.available_frames(), 4);
189        assert_eq!(r.free_frames(), 0);
190        assert_eq!(r.high_water_frames, 4);
191        // Full: a push drops and reports Busy without storing anything.
192        assert_eq!(r.push(&[9, 9, 9, 9]).err(), Some(Error::Busy));
193        assert_eq!(r.dropped_frames, 1);
194        let mut all = [0u8; 16];
195        assert_eq!(r.pop(&mut all), 16);
196        assert_eq!(all, [3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6]);
197        assert!(r.is_empty());
198        assert_eq!(r.pop(&mut all), 0);
199    }
200
201    #[test]
202    fn overwrite_discards_oldest() {
203        let mut mem = [0u8; 12];
204        let mut r = RingBuffer::new(&mut mem, fmt()).unwrap();
205        r.push(&[1, 1, 1, 1, 2, 2, 2, 2]).unwrap();
206        assert_eq!(r.push_overwrite(&[3, 3, 3, 3, 4, 4, 4, 4]).unwrap(), 1);
207        assert_eq!(r.dropped_frames, 1);
208        let mut out = [0u8; 12];
209        assert_eq!(r.pop(&mut out), 12);
210        assert_eq!(out, [2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4]);
211    }
212
213    #[test]
214    fn pop_exact_waits_for_a_block() {
215        let mut mem = [0u8; 16];
216        let mut r = RingBuffer::new(&mut mem, fmt()).unwrap();
217        r.push(&[7; 4]).unwrap();
218        let mut out = [0u8; 8];
219        assert!(!r.pop_exact(&mut out));
220        r.push(&[8; 4]).unwrap();
221        assert!(r.pop_exact(&mut out));
222        assert_eq!(out, [7, 7, 7, 7, 8, 8, 8, 8]);
223    }
224
225    #[test]
226    fn rejects_misaligned_and_oversize() {
227        let mut mem = [0u8; 16];
228        let mut r = RingBuffer::new(&mut mem, fmt()).unwrap();
229        assert_eq!(r.push(&[0; 6]).err(), Some(Error::InvalidGeometry));
230        assert_eq!(
231            r.push(&[0; 20]).err(),
232            Some(Error::BufferTooSmall { needed: 20 })
233        );
234        let mut tiny = [0u8; 3];
235        assert_eq!(
236            RingBuffer::new(&mut tiny, fmt()).err(),
237            Some(Error::BufferTooSmall { needed: 4 })
238        );
239    }
240}