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 alloc_buffer(tx_buffers(), &key, srate);
375
376 let key_thread = key.clone();
377 let thread = match std::thread::Builder::new()
378 .name("ringo-ausrc".into())
379 .spawn(move || render_loop(key_thread, srate, ch, ptime, fmt, run_thread, cb))
380 {
381 Ok(t) => t,
382 Err(e) => {
383 crate::rlog!(Error, "ringo ausrc: spawn render thread failed: {e}");
386 unsafe { mem_deref(cell) };
387 return ENOMEM;
388 }
389 };
390
391 let state = Box::new(SrcState {
392 key,
393 run,
394 thread: Some(thread),
395 });
396 unsafe {
397 *(cell as *mut *mut SrcState) = Box::into_raw(state);
398 *stp = cell as *mut ausrc_st;
399 }
400 0
401}
402
403fn render_stream(q: Option<&mut VecDeque<i16>>, pos: &mut f64, step: f64, mono: &mut [i16]) {
409 let Some(q) = q else {
410 mono.iter_mut().for_each(|s| *s = 0);
411 return;
412 };
413 for s in mono.iter_mut() {
414 *s = q.get(*pos as usize).copied().unwrap_or(0);
415 *pos += step;
416 }
417 let consumed = (*pos as usize).min(q.len());
420 for _ in 0..consumed {
421 q.pop_front();
422 }
423 *pos -= consumed as f64;
424 if q.is_empty() {
425 *pos = 0.0; }
427}
428
429fn render_loop(
432 key: String,
433 srate: u32,
434 ch: u8,
435 ptime: u32,
436 fmt: i32,
437 run: Arc<AtomicBool>,
438 cb: ReadCb,
439) {
440 let is_float = fmt == aufmt::AUFMT_FLOAT as i32;
441 let sample_size = if is_float { 4 } else { 2 };
442 let frames = (srate as usize * ptime as usize / 1000).max(1);
443 let sampc = frames * ch as usize;
444
445 let mut sampv = vec![0u8; sampc * sample_size];
446 let mut mono = vec![0i16; frames];
447
448 let mut cur_version = u64::MAX;
450 let mut cur_spec = GenSpec::Silence;
451 let mut phase = 0.0f64; let mut file_pos = 0.0f64; let mut stream_pos = 0.0f64; let mut start = Instant::now();
456 let mut frame_idx: u64 = 0;
457
458 while run.load(Ordering::Acquire) {
459 let target = start + Duration::from_millis(frame_idx * ptime as u64);
461 let now = Instant::now();
462 if target > now {
463 std::thread::sleep(target - now);
464 } else if now - target > Duration::from_millis(ptime as u64 * 4) {
465 start = now;
469 frame_idx = 0;
470 }
471 if !run.load(Ordering::Acquire) {
472 break;
473 }
474
475 let present = {
477 let map = registry().lock().unwrap_or_else(|e| e.into_inner());
478 map.get(&key).map(|e| (e.version, e.spec.clone()))
479 };
480 match &present {
481 Some((version, spec)) => {
482 if *version != cur_version {
483 cur_version = *version;
484 cur_spec = spec.clone();
485 phase = 0.0;
486 file_pos = 0.0;
487 stream_pos = 0.0;
488 crate::rlog!(
489 Info,
490 "ringo ausrc: key={key} spec={} (v{version})",
491 spec_name(spec)
492 );
493 }
494 }
495 None => {
496 if cur_version != u64::MAX {
499 crate::rlog!(Warn, "ringo ausrc: key={key} no registry entry, silence");
500 cur_version = u64::MAX;
501 cur_spec = GenSpec::Silence;
502 }
503 }
504 }
505
506 match &cur_spec {
508 GenSpec::Silence => mono.iter_mut().for_each(|s| *s = 0),
509 GenSpec::Tone(freq) => {
510 let step = std::f64::consts::TAU * (*freq as f64) / (srate as f64);
511 for s in mono.iter_mut() {
512 *s = (phase.sin() * AMPLITUDE * 32767.0) as i16;
513 phase += step;
514 if phase >= std::f64::consts::TAU {
515 phase -= std::f64::consts::TAU;
516 }
517 }
518 }
519 GenSpec::File(samples, file_srate) => {
520 let step = *file_srate as f64 / srate as f64;
521 let len = samples.len() as f64;
522 for s in mono.iter_mut() {
523 while file_pos >= len {
526 file_pos -= len;
527 }
528 let i = file_pos as usize;
529 *s = samples.get(i).copied().unwrap_or(0);
530 file_pos += step;
531 }
532 }
533 GenSpec::Stream(stream_srate) => {
534 let step = (*stream_srate as f64 / srate as f64).max(f64::MIN_POSITIVE);
535 match stream_queue(&key) {
538 Some(q) => {
539 let mut q = q.lock().unwrap_or_else(|e| e.into_inner());
540 render_stream(Some(&mut q), &mut stream_pos, step, &mut mono);
541 }
542 None => render_stream(None, &mut stream_pos, step, &mut mono),
543 }
544 }
545 }
546
547 if FULL_CAPTURE.load(Ordering::Acquire) {
549 capture_mono(tx_buffers(), &key, &mono);
550 }
551
552 if is_float {
554 let out =
555 unsafe { std::slice::from_raw_parts_mut(sampv.as_mut_ptr() as *mut f32, sampc) };
556 for (f, &m) in out.chunks_mut(ch as usize).zip(mono.iter()) {
557 let v = m as f32 / 32768.0;
558 f.iter_mut().for_each(|x| *x = v);
559 }
560 } else {
561 let out =
562 unsafe { std::slice::from_raw_parts_mut(sampv.as_mut_ptr() as *mut i16, sampc) };
563 for (f, &m) in out.chunks_mut(ch as usize).zip(mono.iter()) {
564 f.iter_mut().for_each(|x| *x = m);
565 }
566 }
567
568 let mut af: auframe = unsafe { std::mem::zeroed() };
570 unsafe {
571 auframe_init(
572 &mut af,
573 if is_float {
574 aufmt::AUFMT_FLOAT
575 } else {
576 aufmt::AUFMT_S16LE
577 },
578 sampv.as_mut_ptr() as *mut c_void,
579 sampc,
580 srate,
581 ch,
582 );
583 }
584 af.timestamp = frame_idx * ptime as u64 * 1000; if let Some(rh) = cb.rh {
587 unsafe { rh(&mut af, cb.arg as *mut c_void) };
588 }
589
590 frame_idx += 1;
591 }
592}
593
594static AUPLAY: OnceLock<usize> = OnceLock::new();
608
609struct PlayState {
610 run: Arc<AtomicBool>,
611 thread: Option<std::thread::JoinHandle<()>>,
612}
613
614impl Drop for PlayState {
615 fn drop(&mut self) {
616 self.run.store(false, Ordering::Release);
617 if let Some(t) = self.thread.take() {
618 let _ = t.join();
619 }
620 }
621}
622
623struct WriteCb {
624 wh: auplay_write_h,
625 arg: usize,
626}
627unsafe impl Send for WriteCb {}
633
634struct AudioBuf {
639 srate: u32,
640 samples: VecDeque<i16>,
641 frozen: bool,
645}
646
647const VERIFY_RETAIN_SECS: usize = 3;
649pub const RECORDING_MAX_SECS: u64 = 600;
654const FULL_RETAIN_SECS: usize = RECORDING_MAX_SECS as usize;
657
658static FULL_CAPTURE: AtomicBool = AtomicBool::new(false);
661
662static RX_BUFFERS: OnceLock<Mutex<HashMap<String, AudioBuf>>> = OnceLock::new();
663static TX_BUFFERS: OnceLock<Mutex<HashMap<String, AudioBuf>>> = OnceLock::new();
664
665fn rx_buffers() -> &'static Mutex<HashMap<String, AudioBuf>> {
666 RX_BUFFERS.get_or_init(|| Mutex::new(HashMap::new()))
667}
668fn tx_buffers() -> &'static Mutex<HashMap<String, AudioBuf>> {
669 TX_BUFFERS.get_or_init(|| Mutex::new(HashMap::new()))
670}
671
672pub(super) fn set_full_capture(on: bool) {
675 FULL_CAPTURE.store(on, Ordering::Release);
676}
677
678pub(super) fn arm_capture(key: &str, full: bool) {
683 let was_full = FULL_CAPTURE.swap(full, Ordering::AcqRel);
684 for buffers in [tx_buffers(), rx_buffers()] {
685 if let Some(buf) = buffers
686 .lock()
687 .unwrap_or_else(|e| e.into_inner())
688 .get_mut(key)
689 {
690 if full {
691 buf.samples.clear();
692 buf.frozen = false;
693 } else if was_full {
694 buf.frozen = true;
695 }
696 }
697 }
698}
699
700fn retain_secs() -> usize {
701 if FULL_CAPTURE.load(Ordering::Acquire) {
702 FULL_RETAIN_SECS
703 } else {
704 VERIFY_RETAIN_SECS
705 }
706}
707
708fn reset_buffer(buffers: &Mutex<HashMap<String, AudioBuf>>, key: &str, srate: u32) {
711 buffers.lock().unwrap_or_else(|e| e.into_inner()).insert(
712 key.to_string(),
713 AudioBuf {
714 srate,
715 samples: VecDeque::new(),
716 frozen: false,
717 },
718 );
719}
720
721fn prepare_buffer(buffers: &Mutex<HashMap<String, AudioBuf>>, key: &str, srate: u32) {
725 let mut map = buffers.lock().unwrap_or_else(|e| e.into_inner());
726 match map.get_mut(key) {
727 None => {
728 map.insert(
729 key.to_string(),
730 AudioBuf {
731 srate,
732 samples: VecDeque::new(),
733 frozen: false,
734 },
735 );
736 }
737 Some(buf) if buf.srate != srate => {
738 crate::rlog!(
739 Info,
740 "ringo ausrc: key={key} srate {} -> {srate}, resetting buffer",
741 buf.srate
742 );
743 buf.srate = srate;
744 buf.samples.clear();
745 buf.frozen = false;
746 }
747 _ => {}
748 }
749}
750
751fn alloc_buffer(buffers: &Mutex<HashMap<String, AudioBuf>>, key: &str, srate: u32) {
756 if FULL_CAPTURE.load(Ordering::Acquire) {
757 prepare_buffer(buffers, key, srate);
758 } else {
759 reset_buffer(buffers, key, srate);
760 }
761}
762
763pub(super) fn received_window(key: &str) -> Option<(Vec<i16>, u32)> {
765 buffer_window(rx_buffers(), key)
766}
767
768pub(super) fn sent_window(key: &str) -> Option<(Vec<i16>, u32)> {
771 buffer_window(tx_buffers(), key)
772}
773
774fn buffer_window(buffers: &Mutex<HashMap<String, AudioBuf>>, key: &str) -> Option<(Vec<i16>, u32)> {
775 let map = buffers.lock().unwrap_or_else(|e| e.into_inner());
776 let buf = map.get(key)?;
777 Some((buf.samples.iter().copied().collect(), buf.srate))
778}
779
780fn capture_mono(buffers: &Mutex<HashMap<String, AudioBuf>>, key: &str, samples: &[i16]) {
782 let mut map = buffers.lock().unwrap_or_else(|e| e.into_inner());
783 let Some(buf) = map.get_mut(key) else {
784 return;
785 };
786 if buf.frozen {
787 return;
788 }
789 buf.samples.extend(samples.iter().copied());
790 let cap = (buf.srate as usize * retain_secs()).max(1);
791 while buf.samples.len() > cap {
792 buf.samples.pop_front();
793 }
794}
795
796fn capture_rx(key: &str, sampv: &[u8], is_float: bool, ch: usize, srate: u32) {
800 let ch = ch.max(1);
801 let mono: Vec<i16> = if is_float {
802 let f =
803 unsafe { std::slice::from_raw_parts(sampv.as_ptr() as *const f32, sampv.len() / 4) };
804 f.chunks_exact(ch)
805 .map(|fr| (fr[0] * 32767.0) as i16)
806 .collect()
807 } else {
808 let s =
809 unsafe { std::slice::from_raw_parts(sampv.as_ptr() as *const i16, sampv.len() / 2) };
810 s.chunks_exact(ch).map(|fr| fr[0]).collect()
811 };
812 capture_mono(rx_buffers(), key, &mono);
813 let mut taps = rx_taps().lock().unwrap_or_else(|e| e.into_inner());
816 if let Some(tx) = taps.get(key) {
817 let frame = AudioFrame {
818 samples: mono,
819 rate: srate,
820 };
821 if tx.send(frame).is_err() {
822 taps.remove(key);
823 }
824 }
825}
826
827unsafe extern "C" fn play_destructor(arg: *mut c_void) {
828 let cell = arg as *mut *mut PlayState;
829 let state = unsafe { *cell };
830 if !state.is_null() {
831 drop(unsafe { Box::from_raw(state) });
832 unsafe { *cell = std::ptr::null_mut() };
833 }
834}
835
836unsafe extern "C" fn play_alloc_handler(
837 stp: *mut *mut auplay_st,
838 _ap: *const auplay,
839 prm: *mut auplay_prm,
840 dev: *const c_char,
841 wh: auplay_write_h,
842 arg: *mut c_void,
843) -> i32 {
844 const EINVAL: i32 = 22;
845 const ENOMEM: i32 = 12;
846
847 if stp.is_null() || prm.is_null() || wh.is_none() {
848 return EINVAL;
849 }
850
851 let prm = unsafe { &*prm };
852 let srate = prm.srate;
853 let ptime = if prm.ptime == 0 { 20 } else { prm.ptime };
854 let fmt = prm.fmt;
855
856 if srate == 0 || prm.ch == 0 {
857 crate::rlog!(
858 Warn,
859 "ringo auplay: invalid prm srate={srate} ch={}",
860 prm.ch
861 );
862 return EINVAL;
863 }
864 let ch = prm.ch;
865
866 let key = if dev.is_null() {
870 String::new()
871 } else {
872 unsafe { std::ffi::CStr::from_ptr(dev) }
873 .to_string_lossy()
874 .into_owned()
875 };
876 if !key.is_empty() {
877 alloc_buffer(rx_buffers(), &key, srate);
878 }
879
880 let cell = unsafe { mem_zalloc(std::mem::size_of::<*mut PlayState>(), Some(play_destructor)) };
881 if cell.is_null() {
882 return ENOMEM;
883 }
884
885 let run = Arc::new(AtomicBool::new(true));
886 let run_thread = run.clone();
887 let cb = WriteCb {
888 wh,
889 arg: arg as usize,
890 };
891
892 let thread = match std::thread::Builder::new()
893 .name("ringo-auplay".into())
894 .spawn(move || play_loop(key, srate, ch, ptime, fmt, run_thread, cb))
895 {
896 Ok(t) => t,
897 Err(e) => {
898 crate::rlog!(Error, "ringo auplay: spawn play thread failed: {e}");
901 unsafe { mem_deref(cell) };
902 return ENOMEM;
903 }
904 };
905
906 let state = Box::new(PlayState {
907 run,
908 thread: Some(thread),
909 });
910 unsafe {
911 *(cell as *mut *mut PlayState) = Box::into_raw(state);
912 *stp = cell as *mut auplay_st;
913 }
914 0
915}
916
917fn play_loop(
921 key: String,
922 srate: u32,
923 ch: u8,
924 ptime: u32,
925 fmt: i32,
926 run: Arc<AtomicBool>,
927 cb: WriteCb,
928) {
929 let is_float = fmt == aufmt::AUFMT_FLOAT as i32;
930 let sample_size = if is_float { 4 } else { 2 };
931 let frames = (srate as usize * ptime as usize / 1000).max(1);
932 let sampc = frames * ch as usize;
933 let mut sampv = vec![0u8; sampc * sample_size];
934
935 let af_fmt = if is_float {
936 aufmt::AUFMT_FLOAT
937 } else {
938 aufmt::AUFMT_S16LE
939 };
940
941 let mut start = Instant::now();
942 let mut frame_idx: u64 = 0;
943
944 while run.load(Ordering::Acquire) {
945 let target = start + Duration::from_millis(frame_idx * ptime as u64);
946 let now = Instant::now();
947 if target > now {
948 std::thread::sleep(target - now);
949 } else if now - target > Duration::from_millis(ptime as u64 * 4) {
950 start = now;
951 frame_idx = 0;
952 }
953 if !run.load(Ordering::Acquire) {
954 break;
955 }
956
957 let mut af: auframe = unsafe { std::mem::zeroed() };
958 unsafe {
959 auframe_init(
960 &mut af,
961 af_fmt,
962 sampv.as_mut_ptr() as *mut c_void,
963 sampc,
964 srate,
965 ch,
966 );
967 }
968 af.timestamp = frame_idx * ptime as u64 * 1000;
969
970 if let Some(wh) = cb.wh {
971 unsafe { wh(&mut af, cb.arg as *mut c_void) };
974 if !key.is_empty() {
975 capture_rx(&key, &sampv, is_float, ch as usize, srate);
976 }
977 }
978
979 frame_idx += 1;
980 }
981}
982
983pub(super) fn register_module() -> Result<(), String> {
990 let mut asp: *mut ausrc = std::ptr::null_mut();
991 let rc = unsafe {
992 ausrc_register(
993 &mut asp,
994 baresip_ausrcl(),
995 c"ringo".as_ptr(),
996 Some(alloc_handler),
997 )
998 };
999 if rc != 0 {
1000 return Err(format!("ausrc_register(ringo) failed (rc={rc})"));
1001 }
1002 let _ = AUSRC.set(asp as usize);
1003
1004 let mut pp: *mut auplay = std::ptr::null_mut();
1005 let rc = unsafe {
1006 auplay_register(
1007 &mut pp,
1008 baresip_auplayl(),
1009 c"ringo".as_ptr(),
1010 Some(play_alloc_handler),
1011 )
1012 };
1013 if rc != 0 {
1014 return Err(format!("auplay_register(ringo) failed (rc={rc})"));
1015 }
1016 let _ = AUPLAY.set(pp as usize);
1017 Ok(())
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022 use super::*;
1023
1024 #[test]
1025 fn render_stream_same_rate_drains_in_order() {
1026 let mut q: VecDeque<i16> = (1..=6).collect();
1027 let mut pos = 0.0;
1028 let mut out = [0i16; 4];
1029 render_stream(Some(&mut q), &mut pos, 1.0, &mut out);
1030 assert_eq!(out, [1, 2, 3, 4]);
1031 assert_eq!(q.iter().copied().collect::<Vec<_>>(), vec![5, 6]); let mut out2 = [0i16; 2];
1034 render_stream(Some(&mut q), &mut pos, 1.0, &mut out2);
1035 assert_eq!(out2, [5, 6]);
1036 assert!(q.is_empty());
1037 }
1038
1039 #[test]
1040 fn render_stream_underrun_is_silence() {
1041 let mut q: VecDeque<i16> = VecDeque::from(vec![7, 8]);
1042 let mut pos = 0.0;
1043 let mut out = [0i16; 4];
1044 render_stream(Some(&mut q), &mut pos, 1.0, &mut out);
1045 assert_eq!(out, [7, 8, 0, 0]); assert!(q.is_empty());
1047 assert_eq!(pos, 0.0); let mut out2 = [9i16; 3];
1050 render_stream(None, &mut pos, 1.0, &mut out2);
1051 assert_eq!(out2, [0, 0, 0]);
1052 }
1053
1054 #[test]
1055 fn render_stream_downsamples_2to1() {
1056 let mut q: VecDeque<i16> = (0..8).collect();
1058 let mut pos = 0.0;
1059 let mut out = [0i16; 4];
1060 render_stream(Some(&mut q), &mut pos, 2.0, &mut out);
1061 assert_eq!(out, [0, 2, 4, 6]);
1062 assert!(q.is_empty()); }
1064
1065 #[test]
1066 fn push_audio_is_noop_before_start_then_caps() {
1067 let key = "ringo-test-stream-cap"; push_audio(key, &[1, 2, 3]);
1070 assert!(stream_queue(key).is_none());
1071
1072 start_audio_stream(key, 8000);
1074 push_audio(key, &vec![7i16; STREAM_IN_CAP_SAMPLES + 100]);
1075 let q = stream_queue(key).expect("queue exists after start");
1076 assert_eq!(
1077 q.lock().unwrap_or_else(|e| e.into_inner()).len(),
1078 STREAM_IN_CAP_SAMPLES
1079 );
1080
1081 remove_generator(key);
1083 assert!(stream_queue(key).is_none());
1084 }
1085
1086 #[test]
1087 fn prepare_buffer_creates_keeps_and_resets_on_rate_change() {
1088 let key = "ringo-test-prepare";
1089 prepare_buffer(rx_buffers(), key, 8000);
1091 assert_eq!(buffer_window(rx_buffers(), key), Some((vec![], 8000)));
1092 capture_mono(rx_buffers(), key, &[1, 2, 3]);
1093 prepare_buffer(rx_buffers(), key, 8000);
1095 assert_eq!(
1096 buffer_window(rx_buffers(), key),
1097 Some((vec![1, 2, 3], 8000))
1098 );
1099 prepare_buffer(rx_buffers(), key, 16000);
1101 assert_eq!(buffer_window(rx_buffers(), key), Some((vec![], 16000)));
1102 remove_generator(key);
1103 }
1104
1105 #[test]
1108 fn arm_capture_scopes_retention_and_allocs() {
1109 let key = "ringo-test-arm";
1110 let srate = 8000u32;
1111 let sec = srate as usize;
1112
1113 arm_capture(key, true);
1116 prepare_buffer(tx_buffers(), key, srate);
1117 prepare_buffer(rx_buffers(), key, srate);
1118 capture_mono(tx_buffers(), key, &vec![7i16; sec * 601]);
1119 assert_eq!(
1120 buffer_window(tx_buffers(), key).unwrap().0.len(),
1121 sec * FULL_RETAIN_SECS
1122 );
1123 arm_capture(key, true);
1124 assert_eq!(
1125 (
1126 buffer_window(tx_buffers(), key),
1127 buffer_window(rx_buffers(), key)
1128 ),
1129 (Some((vec![], srate)), Some((vec![], srate)))
1130 );
1131
1132 capture_mono(rx_buffers(), key, &[3]);
1134 alloc_buffer(rx_buffers(), key, srate);
1135 assert_eq!(buffer_window(rx_buffers(), key), Some((vec![3], srate)));
1136
1137 let long = vec![3i16; sec * 10];
1140 capture_mono(rx_buffers(), key, &long[1..]);
1141 arm_capture(key, false);
1142 capture_mono(rx_buffers(), key, &vec![5i16; sec * 4]);
1143 assert_eq!(
1144 buffer_window(rx_buffers(), key),
1145 Some((long.clone(), srate))
1146 );
1147 arm_capture(key, false);
1149 assert_eq!(
1150 buffer_window(rx_buffers(), key).unwrap().0.len(),
1151 long.len()
1152 );
1153 alloc_buffer(rx_buffers(), key, srate);
1155 assert_eq!(buffer_window(rx_buffers(), key), Some((vec![], srate)));
1156 capture_mono(rx_buffers(), key, &vec![5i16; sec * 4]);
1157 assert_eq!(
1158 buffer_window(rx_buffers(), key).unwrap().0.len(),
1159 sec * VERIFY_RETAIN_SECS
1160 );
1161 arm_capture(key, false);
1163 arm_capture(key, true);
1164 assert_eq!(buffer_window(rx_buffers(), key), Some((vec![], srate)));
1165
1166 arm_capture(key, false);
1168 remove_generator(key);
1169 }
1170}