oximedia_graph/filters/audio/
passthrough.rs1use crate::error::{GraphError, GraphResult};
7use crate::frame::FilterFrame;
8use crate::node::{Node, NodeId, NodeState, NodeType};
9use crate::port::{AudioPortFormat, InputPort, OutputPort, PortFormat, PortId, PortType};
10
11pub struct AudioPassthrough {
18 id: NodeId,
19 name: String,
20 state: NodeState,
21 node_type: NodeType,
22 inputs: Vec<InputPort>,
23 outputs: Vec<OutputPort>,
24}
25
26impl AudioPassthrough {
27 #[must_use]
29 pub fn new(id: NodeId, name: impl Into<String>) -> Self {
30 let audio_format = PortFormat::Audio(AudioPortFormat::any());
31
32 Self {
33 id,
34 name: name.into(),
35 state: NodeState::Idle,
36 node_type: NodeType::Filter,
37 inputs: vec![InputPort::new(PortId(0), "input", PortType::Audio)
38 .with_format(audio_format.clone())],
39 outputs: vec![
40 OutputPort::new(PortId(0), "output", PortType::Audio).with_format(audio_format)
41 ],
42 }
43 }
44
45 #[must_use]
47 pub fn new_source(id: NodeId, name: impl Into<String>) -> Self {
48 let audio_format = PortFormat::Audio(AudioPortFormat::any());
49
50 Self {
51 id,
52 name: name.into(),
53 state: NodeState::Idle,
54 node_type: NodeType::Source,
55 inputs: vec![InputPort::new(PortId(0), "input", PortType::Audio)
56 .with_format(audio_format.clone())
57 .optional()],
58 outputs: vec![
59 OutputPort::new(PortId(0), "output", PortType::Audio).with_format(audio_format)
60 ],
61 }
62 }
63
64 #[must_use]
66 pub fn new_sink(id: NodeId, name: impl Into<String>) -> Self {
67 let audio_format = PortFormat::Audio(AudioPortFormat::any());
68
69 Self {
70 id,
71 name: name.into(),
72 state: NodeState::Idle,
73 node_type: NodeType::Sink,
74 inputs: vec![
75 InputPort::new(PortId(0), "input", PortType::Audio).with_format(audio_format)
76 ],
77 outputs: Vec::new(),
78 }
79 }
80}
81
82impl Node for AudioPassthrough {
83 fn id(&self) -> NodeId {
84 self.id
85 }
86
87 fn name(&self) -> &str {
88 &self.name
89 }
90
91 fn node_type(&self) -> NodeType {
92 self.node_type
93 }
94
95 fn state(&self) -> NodeState {
96 self.state
97 }
98
99 fn set_state(&mut self, state: NodeState) -> GraphResult<()> {
100 if !self.state.can_transition_to(state) {
101 return Err(GraphError::InvalidStateTransition {
102 node: self.id,
103 from: self.state.to_string(),
104 to: state.to_string(),
105 });
106 }
107 self.state = state;
108 Ok(())
109 }
110
111 fn inputs(&self) -> &[InputPort] {
112 &self.inputs
113 }
114
115 fn outputs(&self) -> &[OutputPort] {
116 &self.outputs
117 }
118
119 fn process(&mut self, input: Option<FilterFrame>) -> GraphResult<Option<FilterFrame>> {
120 match input {
121 Some(frame) if frame.is_audio() => {
122 if self.node_type == NodeType::Sink {
124 Ok(None)
125 } else {
126 Ok(Some(frame))
127 }
128 }
129 Some(_) => Err(GraphError::PortTypeMismatch {
130 expected: "Audio".to_string(),
131 actual: "Video".to_string(),
132 }),
133 None => Ok(None),
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use oximedia_audio::{AudioFrame, ChannelLayout};
142 use oximedia_core::SampleFormat;
143
144 #[test]
145 fn test_audio_passthrough_creation() {
146 let filter = AudioPassthrough::new(NodeId(42), "test_filter");
147
148 assert_eq!(filter.id(), NodeId(42));
149 assert_eq!(filter.name(), "test_filter");
150 assert_eq!(filter.node_type(), NodeType::Filter);
151 assert_eq!(filter.state(), NodeState::Idle);
152 }
153
154 #[test]
155 fn test_audio_passthrough_source() {
156 let filter = AudioPassthrough::new_source(NodeId(0), "source");
157
158 assert_eq!(filter.node_type(), NodeType::Source);
159 assert!(!filter.inputs()[0].required);
160 }
161
162 #[test]
163 fn test_audio_passthrough_sink() {
164 let filter = AudioPassthrough::new_sink(NodeId(0), "sink");
165
166 assert_eq!(filter.node_type(), NodeType::Sink);
167 assert!(filter.outputs().is_empty());
168 }
169
170 #[test]
171 fn test_audio_passthrough_ports() {
172 let filter = AudioPassthrough::new(NodeId(0), "test");
173
174 assert_eq!(filter.inputs().len(), 1);
175 assert_eq!(filter.outputs().len(), 1);
176
177 let input = &filter.inputs()[0];
178 assert_eq!(input.port_type, PortType::Audio);
179
180 let output = &filter.outputs()[0];
181 assert_eq!(output.port_type, PortType::Audio);
182 }
183
184 #[test]
185 fn test_audio_passthrough_process() {
186 let mut filter = AudioPassthrough::new(NodeId(0), "test");
187
188 let audio = AudioFrame::new(SampleFormat::F32, 48000, ChannelLayout::Stereo);
190 let frame = FilterFrame::Audio(audio);
191
192 let result = filter.process(Some(frame)).expect("process should succeed");
194 assert!(result.is_some());
195 assert!(result.expect("value should be valid").is_audio());
196 }
197
198 #[test]
199 fn test_audio_passthrough_sink_process() {
200 let mut filter = AudioPassthrough::new_sink(NodeId(0), "sink");
201
202 let audio = AudioFrame::new(SampleFormat::F32, 48000, ChannelLayout::Stereo);
204 let frame = FilterFrame::Audio(audio);
205
206 let result = filter.process(Some(frame)).expect("process should succeed");
208 assert!(result.is_none());
209 }
210
211 #[test]
212 fn test_audio_passthrough_no_input() {
213 let mut filter = AudioPassthrough::new(NodeId(0), "test");
214
215 let result = filter.process(None).expect("process should succeed");
216 assert!(result.is_none());
217 }
218
219 #[test]
220 fn test_state_transitions() {
221 let mut filter = AudioPassthrough::new(NodeId(0), "test");
222
223 assert!(filter.set_state(NodeState::Processing).is_ok());
224 assert_eq!(filter.state(), NodeState::Processing);
225
226 assert!(filter.set_state(NodeState::Done).is_ok());
227 assert_eq!(filter.state(), NodeState::Done);
228 }
229}