flexaudio_core/
chunk_ring.rs1use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::{Arc, Mutex};
15
16use ringbuf::traits::{Consumer, Observer, RingBuffer};
17use ringbuf::HeapRb;
18
19use crate::types::AudioChunk;
20
21type Shared = Arc<Mutex<HeapRb<AudioChunk>>>;
22
23pub fn chunk_ring(capacity_chunks: usize) -> (ChunkProducer, ChunkConsumer) {
29 let cap = capacity_chunks.max(1);
30 let rb: Shared = Arc::new(Mutex::new(HeapRb::<AudioChunk>::new(cap)));
31 let dropped = Arc::new(AtomicU64::new(0));
32 (
33 ChunkProducer {
34 rb: rb.clone(),
35 dropped: dropped.clone(),
36 },
37 ChunkConsumer { rb, dropped },
38 )
39}
40
41pub struct ChunkProducer {
43 rb: Shared,
44 dropped: Arc<AtomicU64>,
45}
46
47impl ChunkProducer {
48 pub fn push(&mut self, mut chunk: AudioChunk) -> Option<u64> {
57 let mut rb = self.rb.lock().unwrap_or_else(|e| e.into_inner());
60
61 let will_evict = rb.is_full();
63
64 if will_evict {
65 self.dropped.fetch_add(1, Ordering::Relaxed);
66 }
67 let total = self.dropped.load(Ordering::Relaxed);
69 chunk.dropped_before = u32::try_from(total).unwrap_or(u32::MAX);
70
71 let evicted = rb.push_overwrite(chunk);
72 drop(rb);
73
74 debug_assert_eq!(evicted.is_some(), will_evict);
75
76 if will_evict {
77 Some(total)
78 } else {
79 None
80 }
81 }
82
83 pub fn dropped_count(&self) -> u64 {
85 self.dropped.load(Ordering::Relaxed)
86 }
87}
88
89pub struct ChunkConsumer {
91 rb: Shared,
92 dropped: Arc<AtomicU64>,
93}
94
95impl ChunkConsumer {
96 pub fn try_pop(&mut self) -> Option<AudioChunk> {
98 let mut rb = self.rb.lock().unwrap_or_else(|e| e.into_inner());
100 rb.try_pop()
101 }
102
103 pub fn len(&self) -> usize {
105 self.rb
107 .lock()
108 .unwrap_or_else(|e| e.into_inner())
109 .occupied_len()
110 }
111
112 pub fn is_empty(&self) -> bool {
114 self.len() == 0
115 }
116
117 pub fn dropped_count(&self) -> u64 {
119 self.dropped.load(Ordering::Relaxed)
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::types::ChunkFlags;
127
128 fn chunk(seq: u64) -> AudioChunk {
129 AudioChunk {
130 data: vec![0.0; 1920],
131 frames: 960,
132 pts_ns: seq as i64 * 20_000_000,
133 seq,
134 flags: ChunkFlags::empty(),
135 dropped_before: 0,
136 peak: 0.0,
137 rms: 0.0,
138 }
139 }
140
141 #[test]
142 fn fifo_order_when_not_full() {
143 let (mut p, mut c) = chunk_ring(4);
144 assert!(c.is_empty());
145 for s in 0..3 {
146 assert_eq!(p.push(chunk(s)), None);
147 }
148 assert_eq!(c.len(), 3);
149 assert_eq!(c.try_pop().unwrap().seq, 0);
150 assert_eq!(c.try_pop().unwrap().seq, 1);
151 assert_eq!(c.try_pop().unwrap().seq, 2);
152 assert!(c.try_pop().is_none());
153 }
154
155 #[test]
156 fn drop_oldest_when_full_and_counts() {
157 let (mut p, mut c) = chunk_ring(2);
158 assert_eq!(p.push(chunk(0)), None);
160 assert_eq!(p.push(chunk(1)), None);
161 assert_eq!(p.dropped_count(), 0);
162
163 let dropped_total = p.push(chunk(2));
165 assert_eq!(dropped_total, Some(1));
166 assert_eq!(p.dropped_count(), 1);
167
168 let dropped_total = p.push(chunk(3));
170 assert_eq!(dropped_total, Some(2));
171 assert_eq!(p.dropped_count(), 2);
172
173 let first = c.try_pop().unwrap();
175 assert_eq!(first.seq, 2);
176 assert_eq!(first.dropped_before, 1);
178
179 let second = c.try_pop().unwrap();
180 assert_eq!(second.seq, 3);
181 assert_eq!(second.dropped_before, 2);
183
184 assert!(c.try_pop().is_none());
185 }
186
187 #[test]
188 fn dropped_before_is_cumulative() {
189 let (mut p, mut c) = chunk_ring(1);
190 p.push(chunk(0)); p.push(chunk(1)); c.try_pop(); let r = p.push(chunk(2)); assert_eq!(r, None);
196 let got = c.try_pop().unwrap();
197 assert_eq!(got.seq, 2);
198 assert_eq!(got.dropped_before, 1);
200 assert_eq!(p.dropped_count(), 1);
201 }
202
203 #[test]
205 fn zero_capacity_is_clamped_to_one() {
206 let (mut p, mut c) = chunk_ring(0);
207 assert!(c.is_empty());
208 assert_eq!(p.push(chunk(0)), None); assert_eq!(c.len(), 1);
210 assert_eq!(p.push(chunk(1)), Some(1));
212 let got = c.try_pop().unwrap();
213 assert_eq!(got.seq, 1);
214 }
215
216 #[test]
218 fn len_tracks_occupancy_and_is_capped() {
219 let (mut p, mut c) = chunk_ring(3);
220 assert!(c.is_empty());
221 for s in 0..3 {
222 p.push(chunk(s));
223 }
224 assert_eq!(c.len(), 3);
225 assert!(!c.is_empty());
226 p.push(chunk(3));
228 p.push(chunk(4));
229 assert_eq!(c.len(), 3, "占有数は容量 3 を超えない");
230 assert_eq!(c.try_pop().unwrap().seq, 2);
232 assert_eq!(c.try_pop().unwrap().seq, 3);
233 assert_eq!(c.try_pop().unwrap().seq, 4);
234 assert!(c.try_pop().is_none());
235 assert!(c.is_empty());
236 }
237
238 #[test]
241 fn empty_pop_is_none_and_dropped_is_shared() {
242 let (mut p, mut c) = chunk_ring(2);
243 assert!(c.try_pop().is_none());
244 p.push(chunk(0));
246 p.push(chunk(1));
247 p.push(chunk(2)); assert_eq!(p.dropped_count(), 1);
249 assert_eq!(c.dropped_count(), 1, "consumer 側も同じ dropped を観測する");
250 }
251}