Skip to main content

flexaudio_core/
backend.rs

1//! The [`CaptureBackend`] trait implemented by OS backends, and the [`RawSink`] handle used
2//! by backends to pass raw frames to the core.
3//!
4//! The facade layer wires the path (backend → [`RawRing`](mod@crate::raw_ring) → [`Normalizer`](crate::normalizer)
5//! → [`ChunkRing`](mod@crate::chunk_ring)). This module only defines backend contract types.
6
7use crate::raw_ring::RawProducer;
8use crate::types::Result;
9
10/// Sink for passing raw interleaved f32 frames from a backend to the core.
11///
12/// Holds a [`RawProducer`] internally. [`push`](Self::push) writes without blocking and is RT-safe
13/// (drops data when full). This is the SPSC producer side; call `push` only from the backend's
14/// RT callback thread.
15pub struct RawSink {
16    producer: RawProducer,
17    native_rate: u32,
18    native_channels: u16,
19}
20
21impl RawSink {
22    /// Create a raw-frame sink with the backend native format.
23    pub fn new(producer: RawProducer, native_rate: u32, native_channels: u16) -> Self {
24        Self {
25            producer,
26            native_rate,
27            native_channels,
28        }
29    }
30
31    /// Send raw interleaved f32 frames without blocking.
32    ///
33    /// `pts_ns` is a device-provided presentation timestamp. Never block;
34    /// increment the internal overflow counter and drop data when full.
35    ///
36    /// PTS normalization and 20 ms chunking belong to the ingest thread. This sends only raw frames;
37    /// the raw ring carries samples only, so the wiring layer handles `pts_ns` separately
38    /// (backends that can provide it should do so for future frame association).
39    ///
40    /// `push` is intended for backend RT callbacks. Custom backends must not allocate, lock, block,
41    /// or make system calls along the path that calls push.
42    pub fn push(&mut self, interleaved: &[f32], pts_ns: i64) -> usize {
43        let _ = pts_ns;
44        self.producer.push_slice(interleaved)
45    }
46
47    /// Backend native sample rate (Hz).
48    pub fn native_rate(&self) -> u32 {
49        self.native_rate
50    }
51
52    /// Backend native channel count.
53    pub fn native_channels(&self) -> u16 {
54        self.native_channels
55    }
56
57    /// Cumulative samples dropped because the buffer was full.
58    pub fn overflow_count(&self) -> u64 {
59        self.producer.overflow_count()
60    }
61}
62
63/// Trait implemented by OS-specific capture backends.
64///
65/// The facade reads the native format via [`native_format`](Self::native_format), configures a
66/// [`Normalizer`](crate::normalizer), then passes a [`RawSink`] to [`start`](Self::start) to begin
67/// capture. The backend calls `sink.push(...)` from its RT callback.
68///
69/// Pass a custom backend as `Box<dyn CaptureBackend>` to `Stream::open`.
70///
71/// Requirements for custom backends:
72/// - Never panic on the capture thread / RT callback (a panic can silently stop capture).
73///
74/// - Call [`RawSink::push`] from RT callbacks in an RT-safe way (no allocation, locks, blocking,
75///   or system calls; see [`RawSink::push`] for details).
76/// - Make `start` / `stop` idempotent: calling `start` while running returns `Ok(())` without
77///   doing anything; calling `stop` before startup does nothing.
78pub trait CaptureBackend: Send {
79    /// Backend native format `(sample_rate, channels)`.
80    fn native_format(&self) -> (u32, u16);
81
82    /// Start sending raw frames to the given sink.
83    fn start(&mut self, sink: RawSink) -> Result<()>;
84
85    /// Stop capture.
86    fn stop(&mut self);
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use crate::raw_ring::raw_ring;
93
94    /// Tiny backend for tests. Pushes one block from `start`.
95    struct DummyBackend {
96        sink: Option<RawSink>,
97    }
98
99    impl CaptureBackend for DummyBackend {
100        fn native_format(&self) -> (u32, u16) {
101            (44_100, 2)
102        }
103        fn start(&mut self, mut sink: RawSink) -> Result<()> {
104            assert_eq!(sink.native_rate(), 44_100);
105            assert_eq!(sink.native_channels(), 2);
106            sink.push(&[0.1, 0.2, 0.3, 0.4], 0);
107            self.sink = Some(sink);
108            Ok(())
109        }
110        fn stop(&mut self) {
111            self.sink = None;
112        }
113    }
114
115    #[test]
116    fn backend_pushes_into_raw_ring() {
117        let (prod, mut cons) = raw_ring(16);
118        let sink = RawSink::new(prod, 44_100, 2);
119        let mut be = DummyBackend { sink: None };
120        assert_eq!(be.native_format(), (44_100, 2));
121        be.start(sink).unwrap();
122
123        let mut out = [0.0f32; 4];
124        let got = cons.pop_slice(&mut out);
125        assert_eq!(got, 4);
126        assert_eq!(out, [0.1, 0.2, 0.3, 0.4]);
127        be.stop();
128    }
129}