Files
chanora/crates/chanora_audio/src/android_render_ring.rs
T

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]);
}
}