125 lines
3.3 KiB
Rust
125 lines
3.3 KiB
Rust
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<ArrayQueue<[f32; 2]>>,
|
|
}
|
|
|
|
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<ArrayQueue<[f32; 2]>>,
|
|
}
|
|
|
|
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<ArrayQueue<[f32; 2]>>,
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|