use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
use audio_opus_bsd::{AudioEncoder, OpusDecodeWorker, OpusEncoder};
struct CountingAllocator;
static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
static COUNTING_ENABLED: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if COUNTING_ENABLED.load(Ordering::Relaxed) == 1 {
ALLOC_COUNT.fetch_add(1, Ordering::Relaxed);
}
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout);
}
}
#[global_allocator]
static A: CountingAllocator = CountingAllocator;
fn count_allocs<F: FnOnce()>(f: F) -> usize {
ALLOC_COUNT.store(0, Ordering::Relaxed);
COUNTING_ENABLED.store(1, Ordering::Relaxed);
f();
COUNTING_ENABLED.store(0, Ordering::Relaxed);
ALLOC_COUNT.load(Ordering::Relaxed)
}
const FRAME_SIZE: usize = 960;
const SAMPLE_RATE: u32 = 48_000;
const POLL_CAP: usize = 2000;
const POLL_SLEEP: std::time::Duration = std::time::Duration::from_millis(1);
fn sine_440_mono() -> Vec<f32> {
let step = 2.0 * std::f32::consts::PI * 440.0 / SAMPLE_RATE as f32;
(0..FRAME_SIZE)
.map(|i| 0.5 * (i as f32 * step).sin())
.collect()
}
fn drain_n<T>(cons: &mut rtrb::Consumer<T>, want: usize) -> Vec<T> {
let mut out = Vec::with_capacity(want);
for _ in 0..POLL_CAP {
while let Ok(item) = cons.pop() {
out.push(item);
if out.len() == want {
return out;
}
}
std::thread::sleep(POLL_SLEEP);
}
while let Ok(item) = cons.pop() {
out.push(item);
}
assert_eq!(
out.len(),
want,
"expected {want} items from consumer, got {}",
out.len()
);
out
}
#[test]
fn rt_consumer_pop_is_alloc_free() {
let mut enc =
OpusEncoder::new(SAMPLE_RATE, 1, opus::Application::Audio).expect("encoder constructs");
enc.set_bitrate_checked(64_000).expect("bitrate");
let pcm = sine_440_mono();
let (mut worker, mut rt_cons) =
OpusDecodeWorker::new(SAMPLE_RATE, 1, 64).expect("decode worker spawns");
for _ in 0..40 {
let packet = enc.encode(&pcm).expect("encode");
assert!(
worker.submit_packet(packet),
"submit must succeed while the ring has capacity"
);
}
std::thread::sleep(std::time::Duration::from_millis(50));
let _ = drain_n(&mut rt_cons, 40);
let allocs = count_allocs(|| {
for _ in 0..32 {
let _ = rt_cons.pop();
}
});
assert_eq!(
allocs, 0,
"{allocs} allocations on the RT path — RT-safety violation \
(alloc must be 0 on rtrb::Consumer::pop)"
);
}
#[test]
fn construction_allocates_but_isolated_from_rt_path() {
let allocs = count_allocs(|| {
let (_worker, _rt_cons) =
OpusDecodeWorker::new(SAMPLE_RATE, 1, 16).expect("decode worker spawns");
});
assert!(
allocs > 0,
"construction must allocate (it is not RT-safe) — measurement gate check"
);
}