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}