Skip to main content

flexaudio_core/
backend.rs

1//! OS バックエンドが実装する [`CaptureBackend`] トレイトと、バックエンドが
2//! 生フレームをコアへ渡すための [`RawSink`] ハンドル。
3//!
4//! 配線(backend → [`RawRing`](mod@crate::raw_ring) → [`Normalizer`](crate::normalizer)
5//! → [`ChunkRing`](mod@crate::chunk_ring))は facade 層が後で行う。ここではバックエンド
6//! 契約の型のみを定義する。
7
8use crate::raw_ring::RawProducer;
9use crate::types::Result;
10
11/// バックエンドが生 interleaved f32 フレームをコアへ渡すためのシンク。
12///
13/// 内部に [`RawProducer`] を持ち、[`push`](Self::push) は RT 安全な非ブロッキング
14/// 書き込み(満杯時 DROP)を行う。SPSC の producer 側で、バックエンドの RT
15/// コールバックスレッドからのみ `push` する想定。
16pub struct RawSink {
17    producer: RawProducer,
18    native_rate: u32,
19    native_channels: u16,
20}
21
22impl RawSink {
23    /// バックエンドのネイティブフォーマットと共に生フレームシンクを作る。
24    pub fn new(producer: RawProducer, native_rate: u32, native_channels: u16) -> Self {
25        Self {
26            producer,
27            native_rate,
28            native_channels,
29        }
30    }
31
32    /// 生 interleaved f32 フレームを非ブロッキングに渡す。
33    ///
34    /// `pts_ns` はデバイス由来のプレゼンテーションタイムスタンプ。決してブロックせず、
35    /// 満杯時は内部 overflow カウンタを増やしてドロップする。
36    ///
37    /// PTS の正規化と 20ms チャンク化は取り込みスレッドの責務で、ここでは生フレーム
38    /// だけを渡す。生リングはサンプルしか運ばないため `pts_ns` は配線層が別途取り回す
39    /// (将来フレームへ対応付ける用途で、用意できる backend は渡しておく)。
40    ///
41    /// `push` は backend の RT コールバックから呼ばれる想定。独自 backend では、push
42    /// を呼ぶ経路でヒープ確保・ロック・ブロッキング・システムコールをしないこと。
43    pub fn push(&mut self, interleaved: &[f32], pts_ns: i64) -> usize {
44        let _ = pts_ns;
45        self.producer.push_slice(interleaved)
46    }
47
48    /// バックエンドのネイティブサンプルレート(Hz)。
49    pub fn native_rate(&self) -> u32 {
50        self.native_rate
51    }
52
53    /// バックエンドのネイティブチャンネル数。
54    pub fn native_channels(&self) -> u16 {
55        self.native_channels
56    }
57
58    /// これまでに(満杯で)ドロップした累計サンプル数。
59    pub fn overflow_count(&self) -> u64 {
60        self.producer.overflow_count()
61    }
62}
63
64/// OS 固有キャプチャバックエンドが実装するトレイト。
65///
66/// facade は [`native_format`](Self::native_format) でネイティブフォーマットを
67/// 取得して [`Normalizer`](crate::normalizer) を構成し、[`start`](Self::start) に
68/// [`RawSink`] を渡してキャプチャを開始する。バックエンドは自身の RT コールバック
69/// 内で `sink.push(...)` を呼ぶ。
70///
71/// `Stream::open` に `Box<dyn CaptureBackend>` を渡せば独自 backend を差し込める。
72///
73/// 独自 backend を実装するときに守ること:
74/// - キャプチャスレッド / RT コールバックで panic させない(panic するとキャプチャが
75///   静かに止まりうる)。
76/// - RT コールバックからの [`RawSink::push`] は RT 安全に呼ぶ(ヒープ確保・ロック・
77///   ブロッキング・システムコールなし。詳細は [`RawSink::push`] を参照)。
78/// - `start` / `stop` を冪等にする(動作中の二重 `start` は no-op で `Ok`、未起動の
79///   `stop` も no-op)。
80pub trait CaptureBackend: Send {
81    /// バックエンドのネイティブフォーマット `(sample_rate, channels)`。
82    fn native_format(&self) -> (u32, u16);
83
84    /// 指定シンクへ生フレームを流し始める。
85    fn start(&mut self, sink: RawSink) -> Result<()>;
86
87    /// キャプチャを停止する。
88    fn stop(&mut self);
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use crate::raw_ring::raw_ring;
95
96    /// テスト用の極小バックエンド。`start` で 1 ブロック push する。
97    struct DummyBackend {
98        sink: Option<RawSink>,
99    }
100
101    impl CaptureBackend for DummyBackend {
102        fn native_format(&self) -> (u32, u16) {
103            (44_100, 2)
104        }
105        fn start(&mut self, mut sink: RawSink) -> Result<()> {
106            assert_eq!(sink.native_rate(), 44_100);
107            assert_eq!(sink.native_channels(), 2);
108            sink.push(&[0.1, 0.2, 0.3, 0.4], 0);
109            self.sink = Some(sink);
110            Ok(())
111        }
112        fn stop(&mut self) {
113            self.sink = None;
114        }
115    }
116
117    #[test]
118    fn backend_pushes_into_raw_ring() {
119        let (prod, mut cons) = raw_ring(16);
120        let sink = RawSink::new(prod, 44_100, 2);
121        let mut be = DummyBackend { sink: None };
122        assert_eq!(be.native_format(), (44_100, 2));
123        be.start(sink).unwrap();
124
125        let mut out = [0.0f32; 4];
126        let got = cons.pop_slice(&mut out);
127        assert_eq!(got, 4);
128        assert_eq!(out, [0.1, 0.2, 0.3, 0.4]);
129        be.stop();
130    }
131}