use std::sync::Arc; use crossbeam::queue::ArrayQueue; /// Fixed-capacity PCM handoff from the Android render producer task to /// the Oboe output callback. pub(crate) struct AndroidRenderRing { frames: Arc>, } impl AndroidRenderRing { pub(crate) fn new(capacity: usize) -> Self { Self { frames: Arc::new(ArrayQueue::new((capacity / 2).max(1))), } } pub(crate) fn producer(&self) -> AndroidRenderRingProducer { AndroidRenderRingProducer { frames: Arc::clone(&self.frames), } } pub(crate) fn consumer(&self) -> AndroidRenderRingConsumer { AndroidRenderRingConsumer { frames: Arc::clone(&self.frames), } } } pub(crate) struct AndroidRenderRingProducer { frames: Arc>, } impl AndroidRenderRingProducer { pub(crate) fn push_frame_lossy(&self, samples: &[f32]) { for frame in samples.chunks_exact(2) { let stereo_frame = [frame[0], frame[1]]; if self.frames.push(stereo_frame).is_err() { let _ = self.frames.pop(); let _ = self.frames.push(stereo_frame); } } } } pub(crate) struct AndroidRenderRingConsumer { frames: Arc>, } impl AndroidRenderRingConsumer { #[cfg(test)] pub(crate) fn drain_into_zero_filling(&self, out: &mut [f32]) { let mut chunks = out.chunks_exact_mut(2); for frame_out in &mut chunks { let frame = self.frames.pop().unwrap_or([0.0, 0.0]); frame_out.copy_from_slice(&frame); } for sample in chunks.into_remainder() { *sample = 0.0; } } pub(crate) fn drain_stereo_into_zero_filling(&self, out: &mut [(f32, f32)]) { for frame_out in out { let frame = self.frames.pop().unwrap_or([0.0, 0.0]); *frame_out = (frame[0], frame[1]); } } } #[cfg(test)] mod tests { use super::*; #[test] fn producer_drops_oldest_samples_when_ring_is_full() { let ring = AndroidRenderRing::new(4); let producer = ring.producer(); let consumer = ring.consumer(); producer.push_frame_lossy(&[1.0, 2.0, 3.0, 4.0]); producer.push_frame_lossy(&[5.0, 6.0]); let mut out = [0.0; 4]; consumer.drain_into_zero_filling(&mut out); assert_eq!(out, [3.0, 4.0, 5.0, 6.0]); } #[test] fn overflow_after_partial_consumer_drain_preserves_stereo_pairing() { let ring = AndroidRenderRing::new(4); let producer = ring.producer(); let consumer = ring.consumer(); producer.push_frame_lossy(&[1.0, 10.0, 2.0, 20.0]); let mut odd_out = [9.0]; consumer.drain_into_zero_filling(&mut odd_out); assert_eq!(odd_out, [0.0]); producer.push_frame_lossy(&[3.0, 30.0]); let mut out = [0.0; 4]; consumer.drain_into_zero_filling(&mut out); assert_eq!(out, [2.0, 20.0, 3.0, 30.0]); } #[test] fn consumer_zero_fills_tail_on_underrun() { let ring = AndroidRenderRing::new(4); let producer = ring.producer(); let consumer = ring.consumer(); producer.push_frame_lossy(&[0.25, -0.25]); let mut out = [9.0; 4]; consumer.drain_into_zero_filling(&mut out); assert_eq!(out, [0.25, -0.25, 0.0, 0.0]); } }