1use std::collections::{HashMap, VecDeque};
20use std::os::raw::{c_char, c_void};
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::{Arc, Mutex, OnceLock};
23use std::time::{Duration, Instant};
24
25use super::bindings::*;
26
27const AMPLITUDE: f64 = 0.25;
28
29#[derive(Clone)]
31enum GenSpec {
32 Silence,
33 Tone(u32),
35 File(Arc<Vec<i16>>, u32),
37 Stream(u32),
40}
41
42struct Entry {
45 version: u64,
46 spec: GenSpec,
47}
48
49static REGISTRY: OnceLock<Mutex<HashMap<String, Entry>>> = OnceLock::new();
52
53fn registry() -> &'static Mutex<HashMap<String, Entry>> {
54 REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
55}
56
57static AUSRC: OnceLock<usize> = OnceLock::new();
59
60#[derive(Debug, Clone)]
63pub struct AudioFrame {
64 pub samples: Vec<i16>,
66 pub rate: u32,
68}
69
70type StreamQueue = Arc<Mutex<VecDeque<i16>>>;
74
75static STREAM_IN: OnceLock<Mutex<HashMap<String, StreamQueue>>> = OnceLock::new();
79
80fn stream_in() -> &'static Mutex<HashMap<String, StreamQueue>> {
81 STREAM_IN.get_or_init(|| Mutex::new(HashMap::new()))
82}
83
84fn stream_queue(key: &str) -> Option<StreamQueue> {
86 stream_in()
87 .lock()
88 .unwrap_or_else(|e| e.into_inner())
89 .get(key)
90 .map(Arc::clone)
91}
92
93static RX_TAPS: OnceLock<Mutex<HashMap<String, std::sync::mpsc::Sender<AudioFrame>>>> =
96 OnceLock::new();
97
98fn rx_taps() -> &'static Mutex<HashMap<String, std::sync::mpsc::Sender<AudioFrame>>> {
99 RX_TAPS.get_or_init(|| Mutex::new(HashMap::new()))
100}
101
102const STREAM_IN_CAP_SAMPLES: usize = 48_000;
105
106pub(super) fn set_generator(key: &str, spec: &str) {
111 let parsed = parse_spec(spec);
112 let mut map = registry().lock().unwrap_or_else(|e| e.into_inner());
113 let version = map.get(key).map(|e| e.version + 1).unwrap_or(0);
114 map.insert(
115 key.to_string(),
116 Entry {
117 version,
118 spec: parsed,
119 },
120 );
121}
122
123pub(super) fn start_audio_stream(key: &str, rate: u32) {
127 stream_in()
128 .lock()
129 .unwrap_or_else(|e| e.into_inner())
130 .entry(key.to_string())
131 .or_default();
132 let mut map = registry().lock().unwrap_or_else(|e| e.into_inner());
133 let version = map.get(key).map(|e| e.version + 1).unwrap_or(0);
134 map.insert(
135 key.to_string(),
136 Entry {
137 version,
138 spec: GenSpec::Stream(rate.max(1)),
139 },
140 );
141}
142
143pub(super) fn push_audio(key: &str, samples: &[i16]) {
146 let Some(q) = stream_queue(key) else {
147 return;
148 };
149 let mut q = q.lock().unwrap_or_else(|e| e.into_inner());
150 q.extend(samples.iter().copied());
151 while q.len() > STREAM_IN_CAP_SAMPLES {
152 q.pop_front();
153 }
154}
155
156pub(super) fn subscribe_received_audio(key: &str) -> std::sync::mpsc::Receiver<AudioFrame> {
159 let (tx, rx) = std::sync::mpsc::channel();
160 rx_taps()
161 .lock()
162 .unwrap_or_else(|e| e.into_inner())
163 .insert(key.to_string(), tx);
164 rx
165}
166
167pub(super) fn init_generator(key: &str) {
170 let mut map = registry().lock().unwrap_or_else(|e| e.into_inner());
171 map.entry(key.to_string()).or_insert(Entry {
172 version: 0,
173 spec: GenSpec::Silence,
174 });
175}
176
177pub(super) fn remove_generator(key: &str) {
180 registry()
181 .lock()
182 .unwrap_or_else(|e| e.into_inner())
183 .remove(key);
184 rx_buffers()
185 .lock()
186 .unwrap_or_else(|e| e.into_inner())
187 .remove(key);
188 tx_buffers()
189 .lock()
190 .unwrap_or_else(|e| e.into_inner())
191 .remove(key);
192 stream_in()
193 .lock()
194 .unwrap_or_else(|e| e.into_inner())
195 .remove(key);
196 rx_taps()
197 .lock()
198 .unwrap_or_else(|e| e.into_inner())
199 .remove(key);
200}
201
202fn spec_name(spec: &GenSpec) -> String {
203 match spec {
204 GenSpec::Silence => "silence".into(),
205 GenSpec::Tone(f) => format!("tone({f}Hz)"),
206 GenSpec::File(s, sr) => format!("file({} samples @{sr}Hz)", s.len()),
207 GenSpec::Stream(sr) => format!("stream(@{sr}Hz)"),
208 }
209}
210
211fn parse_spec(spec: &str) -> GenSpec {
212 let (driver, device) = spec.split_once(',').unwrap_or((spec, ""));
213 match driver {
214 "ausine" => match device.split(',').next().unwrap_or("").parse::<u32>() {
215 Ok(f) if (10..=20000).contains(&f) => GenSpec::Tone(f),
216 _ => {
217 crate::rlog!(
218 Warn,
219 "ringo ausrc: invalid tone freq in '{spec}', using silence"
220 );
221 GenSpec::Silence
222 }
223 },
224 "aufile" => match load_wav_mono(device) {
225 Some((samples, srate)) => GenSpec::File(Arc::new(samples), srate),
226 None => {
227 crate::rlog!(
228 Warn,
229 "ringo ausrc: failed to load '{device}', using silence"
230 );
231 GenSpec::Silence
232 }
233 },
234 _ => GenSpec::Silence,
235 }
236}
237
238fn load_wav_mono(path: &str) -> Option<(Vec<i16>, u32)> {
240 let data = std::fs::read(path).ok()?;
241 let pcm = super::sounds::parse_wav(&data)?;
242 let i16s: Vec<i16> = pcm
244 .samples
245 .chunks_exact(2)
246 .map(|b| i16::from_le_bytes([b[0], b[1]]))
247 .collect();
248 let mono = if pcm.channels >= 2 {
249 i16s.chunks_exact(pcm.channels as usize)
250 .map(|frame| {
251 let sum: i32 = frame.iter().take(2).map(|&s| s as i32).sum();
252 (sum / 2) as i16
253 })
254 .collect()
255 } else {
256 i16s
257 };
258 if mono.is_empty() {
259 return None;
260 }
261 Some((mono, pcm.srate))
262}
263
264struct SrcState {
269 key: String,
270 run: Arc<AtomicBool>,
271 thread: Option<std::thread::JoinHandle<()>>,
272}
273
274impl Drop for SrcState {
275 fn drop(&mut self) {
276 crate::rlog!(Info, "ringo ausrc: FREE key={}", self.key);
277 self.run.store(false, Ordering::Release);
278 if let Some(t) = self.thread.take() {
279 let _ = t.join();
280 }
281 }
282}
283
284struct ReadCb {
288 rh: ausrc_read_h,
289 arg: usize,
290}
291unsafe impl Send for ReadCb {}
298
299unsafe extern "C" fn destructor(arg: *mut c_void) {
301 let cell = arg as *mut *mut SrcState;
303 let state = unsafe { *cell };
304 if !state.is_null() {
305 drop(unsafe { Box::from_raw(state) });
307 unsafe { *cell = std::ptr::null_mut() };
308 }
309}
310
311unsafe extern "C" fn alloc_handler(
312 stp: *mut *mut ausrc_st,
313 _as: *const ausrc,
314 prm: *mut ausrc_prm,
315 dev: *const c_char,
316 rh: ausrc_read_h,
317 _errh: ausrc_error_h,
318 arg: *mut c_void,
319) -> i32 {
320 const EINVAL: i32 = 22;
321 const ENOMEM: i32 = 12;
322 const ENOTSUP: i32 = 95;
323
324 if stp.is_null() || prm.is_null() || dev.is_null() || rh.is_none() {
325 return EINVAL;
326 }
327
328 let prm = unsafe { &*prm };
329 let key = unsafe { std::ffi::CStr::from_ptr(dev) }
330 .to_string_lossy()
331 .into_owned();
332
333 let srate = prm.srate;
334 let ptime = if prm.ptime == 0 { 20 } else { prm.ptime };
335 let fmt = prm.fmt;
336
337 if srate == 0 || prm.ch == 0 {
341 crate::rlog!(Warn, "ringo ausrc: invalid prm srate={srate} ch={}", prm.ch);
342 return EINVAL;
343 }
344 let ch = prm.ch;
345
346 if fmt != aufmt::AUFMT_S16LE as i32 && fmt != aufmt::AUFMT_FLOAT as i32 {
350 crate::rlog!(Warn, "ringo ausrc: unsupported sample format {fmt}");
351 return ENOTSUP;
352 }
353
354 let cell = unsafe { mem_zalloc(std::mem::size_of::<*mut SrcState>(), Some(destructor)) };
356 if cell.is_null() {
357 return ENOMEM;
358 }
359
360 crate::rlog!(
361 Info,
362 "ringo ausrc: ALLOC key={key} srate={srate} ch={ch} ptime={ptime} fmt={fmt}"
363 );
364
365 let run = Arc::new(AtomicBool::new(true));
366 let run_thread = run.clone();
367 let cb = ReadCb {
368 rh,
369 arg: arg as usize,
370 };
371
372 reset_buffer(tx_buffers(), &key, srate);
374
375 let key_thread = key.clone();
376 let thread = match std::thread::Builder::new()
377 .name("ringo-ausrc".into())
378 .spawn(move || render_loop(key_thread, srate, ch, ptime, fmt, run_thread, cb))
379 {
380 Ok(t) => t,
381 Err(e) => {
382 crate::rlog!(Error, "ringo ausrc: spawn render thread failed: {e}");
385 unsafe { mem_deref(cell) };
386 return ENOMEM;
387 }
388 };
389
390 let state = Box::new(SrcState {
391 key,
392 run,
393 thread: Some(thread),
394 });
395 unsafe {
396 *(cell as *mut *mut SrcState) = Box::into_raw(state);
397 *stp = cell as *mut ausrc_st;
398 }
399 0
400}
401
402fn render_stream(q: Option<&mut VecDeque<i16>>, pos: &mut f64, step: f64, mono: &mut [i16]) {
408 let Some(q) = q else {
409 mono.iter_mut().for_each(|s| *s = 0);
410 return;
411 };
412 for s in mono.iter_mut() {
413 *s = q.get(*pos as usize).copied().unwrap_or(0);
414 *pos += step;
415 }
416 let consumed = (*pos as usize).min(q.len());
419 for _ in 0..consumed {
420 q.pop_front();
421 }
422 *pos -= consumed as f64;
423 if q.is_empty() {
424 *pos = 0.0; }
426}
427
428fn render_loop(
431 key: String,
432 srate: u32,
433 ch: u8,
434 ptime: u32,
435 fmt: i32,
436 run: Arc<AtomicBool>,
437 cb: ReadCb,
438) {
439 let is_float = fmt == aufmt::AUFMT_FLOAT as i32;
440 let sample_size = if is_float { 4 } else { 2 };
441 let frames = (srate as usize * ptime as usize / 1000).max(1);
442 let sampc = frames * ch as usize;
443
444 let mut sampv = vec![0u8; sampc * sample_size];
445 let mut mono = vec![0i16; frames];
446
447 let mut cur_version = u64::MAX;
449 let mut cur_spec = GenSpec::Silence;
450 let mut phase = 0.0f64; let mut file_pos = 0.0f64; let mut stream_pos = 0.0f64; let mut start = Instant::now();
455 let mut frame_idx: u64 = 0;
456
457 while run.load(Ordering::Acquire) {
458 let target = start + Duration::from_millis(frame_idx * ptime as u64);
460 let now = Instant::now();
461 if target > now {
462 std::thread::sleep(target - now);
463 } else if now - target > Duration::from_millis(ptime as u64 * 4) {
464 start = now;
468 frame_idx = 0;
469 }
470 if !run.load(Ordering::Acquire) {
471 break;
472 }
473
474 let present = {
476 let map = registry().lock().unwrap_or_else(|e| e.into_inner());
477 map.get(&key).map(|e| (e.version, e.spec.clone()))
478 };
479 match &present {
480 Some((version, spec)) => {
481 if *version != cur_version {
482 cur_version = *version;
483 cur_spec = spec.clone();
484 phase = 0.0;
485 file_pos = 0.0;
486 stream_pos = 0.0;
487 crate::rlog!(
488 Info,
489 "ringo ausrc: key={key} spec={} (v{version})",
490 spec_name(spec)
491 );
492 }
493 }
494 None => {
495 if cur_version != u64::MAX {
498 crate::rlog!(Warn, "ringo ausrc: key={key} no registry entry, silence");
499 cur_version = u64::MAX;
500 cur_spec = GenSpec::Silence;
501 }
502 }
503 }
504
505 match &cur_spec {
507 GenSpec::Silence => mono.iter_mut().for_each(|s| *s = 0),
508 GenSpec::Tone(freq) => {
509 let step = std::f64::consts::TAU * (*freq as f64) / (srate as f64);
510 for s in mono.iter_mut() {
511 *s = (phase.sin() * AMPLITUDE * 32767.0) as i16;
512 phase += step;
513 if phase >= std::f64::consts::TAU {
514 phase -= std::f64::consts::TAU;
515 }
516 }
517 }
518 GenSpec::File(samples, file_srate) => {
519 let step = *file_srate as f64 / srate as f64;
520 let len = samples.len() as f64;
521 for s in mono.iter_mut() {
522 while file_pos >= len {
525 file_pos -= len;
526 }
527 let i = file_pos as usize;
528 *s = samples.get(i).copied().unwrap_or(0);
529 file_pos += step;
530 }
531 }
532 GenSpec::Stream(stream_srate) => {
533 let step = (*stream_srate as f64 / srate as f64).max(f64::MIN_POSITIVE);
534 match stream_queue(&key) {
537 Some(q) => {
538 let mut q = q.lock().unwrap_or_else(|e| e.into_inner());
539 render_stream(Some(&mut q), &mut stream_pos, step, &mut mono);
540 }
541 None => render_stream(None, &mut stream_pos, step, &mut mono),
542 }
543 }
544 }
545
546 if FULL_CAPTURE.load(Ordering::Acquire) {
548 capture_mono(tx_buffers(), &key, &mono);
549 }
550
551 if is_float {
553 let out =
554 unsafe { std::slice::from_raw_parts_mut(sampv.as_mut_ptr() as *mut f32, sampc) };
555 for (f, &m) in out.chunks_mut(ch as usize).zip(mono.iter()) {
556 let v = m as f32 / 32768.0;
557 f.iter_mut().for_each(|x| *x = v);
558 }
559 } else {
560 let out =
561 unsafe { std::slice::from_raw_parts_mut(sampv.as_mut_ptr() as *mut i16, sampc) };
562 for (f, &m) in out.chunks_mut(ch as usize).zip(mono.iter()) {
563 f.iter_mut().for_each(|x| *x = m);
564 }
565 }
566
567 let mut af: auframe = unsafe { std::mem::zeroed() };
569 unsafe {
570 auframe_init(
571 &mut af,
572 if is_float {
573 aufmt::AUFMT_FLOAT
574 } else {
575 aufmt::AUFMT_S16LE
576 },
577 sampv.as_mut_ptr() as *mut c_void,
578 sampc,
579 srate,
580 ch,
581 );
582 }
583 af.timestamp = frame_idx * ptime as u64 * 1000; if let Some(rh) = cb.rh {
586 unsafe { rh(&mut af, cb.arg as *mut c_void) };
587 }
588
589 frame_idx += 1;
590 }
591}
592
593static AUPLAY: OnceLock<usize> = OnceLock::new();
607
608struct PlayState {
609 run: Arc<AtomicBool>,
610 thread: Option<std::thread::JoinHandle<()>>,
611}
612
613impl Drop for PlayState {
614 fn drop(&mut self) {
615 self.run.store(false, Ordering::Release);
616 if let Some(t) = self.thread.take() {
617 let _ = t.join();
618 }
619 }
620}
621
622struct WriteCb {
623 wh: auplay_write_h,
624 arg: usize,
625}
626unsafe impl Send for WriteCb {}
632
633struct AudioBuf {
638 srate: u32,
639 samples: VecDeque<i16>,
640}
641
642const VERIFY_RETAIN_SECS: usize = 3;
644const FULL_RETAIN_SECS: usize = 600;
647
648static FULL_CAPTURE: AtomicBool = AtomicBool::new(false);
651
652static RX_BUFFERS: OnceLock<Mutex<HashMap<String, AudioBuf>>> = OnceLock::new();
653static TX_BUFFERS: OnceLock<Mutex<HashMap<String, AudioBuf>>> = OnceLock::new();
654
655fn rx_buffers() -> &'static Mutex<HashMap<String, AudioBuf>> {
656 RX_BUFFERS.get_or_init(|| Mutex::new(HashMap::new()))
657}
658fn tx_buffers() -> &'static Mutex<HashMap<String, AudioBuf>> {
659 TX_BUFFERS.get_or_init(|| Mutex::new(HashMap::new()))
660}
661
662pub(super) fn set_full_capture(on: bool) {
665 FULL_CAPTURE.store(on, Ordering::Release);
666}
667
668fn retain_secs() -> usize {
669 if FULL_CAPTURE.load(Ordering::Acquire) {
670 FULL_RETAIN_SECS
671 } else {
672 VERIFY_RETAIN_SECS
673 }
674}
675
676fn reset_buffer(buffers: &Mutex<HashMap<String, AudioBuf>>, key: &str, srate: u32) {
679 buffers.lock().unwrap_or_else(|e| e.into_inner()).insert(
680 key.to_string(),
681 AudioBuf {
682 srate,
683 samples: VecDeque::new(),
684 },
685 );
686}
687
688pub(super) fn received_window(key: &str) -> Option<(Vec<i16>, u32)> {
690 buffer_window(rx_buffers(), key)
691}
692
693pub(super) fn sent_window(key: &str) -> Option<(Vec<i16>, u32)> {
696 buffer_window(tx_buffers(), key)
697}
698
699fn buffer_window(buffers: &Mutex<HashMap<String, AudioBuf>>, key: &str) -> Option<(Vec<i16>, u32)> {
700 let map = buffers.lock().unwrap_or_else(|e| e.into_inner());
701 let buf = map.get(key)?;
702 Some((buf.samples.iter().copied().collect(), buf.srate))
703}
704
705fn capture_mono(buffers: &Mutex<HashMap<String, AudioBuf>>, key: &str, samples: &[i16]) {
707 let mut map = buffers.lock().unwrap_or_else(|e| e.into_inner());
708 let Some(buf) = map.get_mut(key) else {
709 return;
710 };
711 buf.samples.extend(samples.iter().copied());
712 let cap = (buf.srate as usize * retain_secs()).max(1);
713 while buf.samples.len() > cap {
714 buf.samples.pop_front();
715 }
716}
717
718fn capture_rx(key: &str, sampv: &[u8], is_float: bool, ch: usize, srate: u32) {
722 let ch = ch.max(1);
723 let mono: Vec<i16> = if is_float {
724 let f =
725 unsafe { std::slice::from_raw_parts(sampv.as_ptr() as *const f32, sampv.len() / 4) };
726 f.chunks_exact(ch)
727 .map(|fr| (fr[0] * 32767.0) as i16)
728 .collect()
729 } else {
730 let s =
731 unsafe { std::slice::from_raw_parts(sampv.as_ptr() as *const i16, sampv.len() / 2) };
732 s.chunks_exact(ch).map(|fr| fr[0]).collect()
733 };
734 capture_mono(rx_buffers(), key, &mono);
735 let mut taps = rx_taps().lock().unwrap_or_else(|e| e.into_inner());
738 if let Some(tx) = taps.get(key) {
739 let frame = AudioFrame {
740 samples: mono,
741 rate: srate,
742 };
743 if tx.send(frame).is_err() {
744 taps.remove(key);
745 }
746 }
747}
748
749unsafe extern "C" fn play_destructor(arg: *mut c_void) {
750 let cell = arg as *mut *mut PlayState;
751 let state = unsafe { *cell };
752 if !state.is_null() {
753 drop(unsafe { Box::from_raw(state) });
754 unsafe { *cell = std::ptr::null_mut() };
755 }
756}
757
758unsafe extern "C" fn play_alloc_handler(
759 stp: *mut *mut auplay_st,
760 _ap: *const auplay,
761 prm: *mut auplay_prm,
762 dev: *const c_char,
763 wh: auplay_write_h,
764 arg: *mut c_void,
765) -> i32 {
766 const EINVAL: i32 = 22;
767 const ENOMEM: i32 = 12;
768
769 if stp.is_null() || prm.is_null() || wh.is_none() {
770 return EINVAL;
771 }
772
773 let prm = unsafe { &*prm };
774 let srate = prm.srate;
775 let ptime = if prm.ptime == 0 { 20 } else { prm.ptime };
776 let fmt = prm.fmt;
777
778 if srate == 0 || prm.ch == 0 {
779 crate::rlog!(
780 Warn,
781 "ringo auplay: invalid prm srate={srate} ch={}",
782 prm.ch
783 );
784 return EINVAL;
785 }
786 let ch = prm.ch;
787
788 let key = if dev.is_null() {
791 String::new()
792 } else {
793 unsafe { std::ffi::CStr::from_ptr(dev) }
794 .to_string_lossy()
795 .into_owned()
796 };
797 if !key.is_empty() {
798 reset_buffer(rx_buffers(), &key, srate);
799 }
800
801 let cell = unsafe { mem_zalloc(std::mem::size_of::<*mut PlayState>(), Some(play_destructor)) };
802 if cell.is_null() {
803 return ENOMEM;
804 }
805
806 let run = Arc::new(AtomicBool::new(true));
807 let run_thread = run.clone();
808 let cb = WriteCb {
809 wh,
810 arg: arg as usize,
811 };
812
813 let thread = match std::thread::Builder::new()
814 .name("ringo-auplay".into())
815 .spawn(move || play_loop(key, srate, ch, ptime, fmt, run_thread, cb))
816 {
817 Ok(t) => t,
818 Err(e) => {
819 crate::rlog!(Error, "ringo auplay: spawn play thread failed: {e}");
822 unsafe { mem_deref(cell) };
823 return ENOMEM;
824 }
825 };
826
827 let state = Box::new(PlayState {
828 run,
829 thread: Some(thread),
830 });
831 unsafe {
832 *(cell as *mut *mut PlayState) = Box::into_raw(state);
833 *stp = cell as *mut auplay_st;
834 }
835 0
836}
837
838fn play_loop(
842 key: String,
843 srate: u32,
844 ch: u8,
845 ptime: u32,
846 fmt: i32,
847 run: Arc<AtomicBool>,
848 cb: WriteCb,
849) {
850 let is_float = fmt == aufmt::AUFMT_FLOAT as i32;
851 let sample_size = if is_float { 4 } else { 2 };
852 let frames = (srate as usize * ptime as usize / 1000).max(1);
853 let sampc = frames * ch as usize;
854 let mut sampv = vec![0u8; sampc * sample_size];
855
856 let af_fmt = if is_float {
857 aufmt::AUFMT_FLOAT
858 } else {
859 aufmt::AUFMT_S16LE
860 };
861
862 let mut start = Instant::now();
863 let mut frame_idx: u64 = 0;
864
865 while run.load(Ordering::Acquire) {
866 let target = start + Duration::from_millis(frame_idx * ptime as u64);
867 let now = Instant::now();
868 if target > now {
869 std::thread::sleep(target - now);
870 } else if now - target > Duration::from_millis(ptime as u64 * 4) {
871 start = now;
872 frame_idx = 0;
873 }
874 if !run.load(Ordering::Acquire) {
875 break;
876 }
877
878 let mut af: auframe = unsafe { std::mem::zeroed() };
879 unsafe {
880 auframe_init(
881 &mut af,
882 af_fmt,
883 sampv.as_mut_ptr() as *mut c_void,
884 sampc,
885 srate,
886 ch,
887 );
888 }
889 af.timestamp = frame_idx * ptime as u64 * 1000;
890
891 if let Some(wh) = cb.wh {
892 unsafe { wh(&mut af, cb.arg as *mut c_void) };
895 if !key.is_empty() {
896 capture_rx(&key, &sampv, is_float, ch as usize, srate);
897 }
898 }
899
900 frame_idx += 1;
901 }
902}
903
904pub(super) fn register_module() -> Result<(), String> {
911 let mut asp: *mut ausrc = std::ptr::null_mut();
912 let rc = unsafe {
913 ausrc_register(
914 &mut asp,
915 baresip_ausrcl(),
916 c"ringo".as_ptr(),
917 Some(alloc_handler),
918 )
919 };
920 if rc != 0 {
921 return Err(format!("ausrc_register(ringo) failed (rc={rc})"));
922 }
923 let _ = AUSRC.set(asp as usize);
924
925 let mut pp: *mut auplay = std::ptr::null_mut();
926 let rc = unsafe {
927 auplay_register(
928 &mut pp,
929 baresip_auplayl(),
930 c"ringo".as_ptr(),
931 Some(play_alloc_handler),
932 )
933 };
934 if rc != 0 {
935 return Err(format!("auplay_register(ringo) failed (rc={rc})"));
936 }
937 let _ = AUPLAY.set(pp as usize);
938 Ok(())
939}
940
941#[cfg(test)]
942mod tests {
943 use super::*;
944
945 #[test]
946 fn render_stream_same_rate_drains_in_order() {
947 let mut q: VecDeque<i16> = (1..=6).collect();
948 let mut pos = 0.0;
949 let mut out = [0i16; 4];
950 render_stream(Some(&mut q), &mut pos, 1.0, &mut out);
951 assert_eq!(out, [1, 2, 3, 4]);
952 assert_eq!(q.iter().copied().collect::<Vec<_>>(), vec![5, 6]); let mut out2 = [0i16; 2];
955 render_stream(Some(&mut q), &mut pos, 1.0, &mut out2);
956 assert_eq!(out2, [5, 6]);
957 assert!(q.is_empty());
958 }
959
960 #[test]
961 fn render_stream_underrun_is_silence() {
962 let mut q: VecDeque<i16> = VecDeque::from(vec![7, 8]);
963 let mut pos = 0.0;
964 let mut out = [0i16; 4];
965 render_stream(Some(&mut q), &mut pos, 1.0, &mut out);
966 assert_eq!(out, [7, 8, 0, 0]); assert!(q.is_empty());
968 assert_eq!(pos, 0.0); let mut out2 = [9i16; 3];
971 render_stream(None, &mut pos, 1.0, &mut out2);
972 assert_eq!(out2, [0, 0, 0]);
973 }
974
975 #[test]
976 fn render_stream_downsamples_2to1() {
977 let mut q: VecDeque<i16> = (0..8).collect();
979 let mut pos = 0.0;
980 let mut out = [0i16; 4];
981 render_stream(Some(&mut q), &mut pos, 2.0, &mut out);
982 assert_eq!(out, [0, 2, 4, 6]);
983 assert!(q.is_empty()); }
985
986 #[test]
987 fn push_audio_is_noop_before_start_then_caps() {
988 let key = "ringo-test-stream-cap"; push_audio(key, &[1, 2, 3]);
991 assert!(stream_queue(key).is_none());
992
993 start_audio_stream(key, 8000);
995 push_audio(key, &vec![7i16; STREAM_IN_CAP_SAMPLES + 100]);
996 let q = stream_queue(key).expect("queue exists after start");
997 assert_eq!(
998 q.lock().unwrap_or_else(|e| e.into_inner()).len(),
999 STREAM_IN_CAP_SAMPLES
1000 );
1001
1002 remove_generator(key);
1004 assert!(stream_queue(key).is_none());
1005 }
1006}