oximedia_graph/filters/video/
null.rs1use crate::error::{GraphError, GraphResult};
7use crate::frame::FilterFrame;
8use crate::node::{Node, NodeId, NodeState, NodeType};
9use crate::port::{InputPort, OutputPort, PortFormat, PortId, PortType, VideoPortFormat};
10
11pub struct NullSink {
18 id: NodeId,
19 name: String,
20 state: NodeState,
21 inputs: Vec<InputPort>,
22 outputs: Vec<OutputPort>,
23 frames_received: u64,
24}
25
26impl NullSink {
27 #[must_use]
29 pub fn new(id: NodeId, name: impl Into<String>) -> Self {
30 let video_format = PortFormat::Video(VideoPortFormat::any());
31
32 Self {
33 id,
34 name: name.into(),
35 state: NodeState::Idle,
36 inputs: vec![
37 InputPort::new(PortId(0), "input", PortType::Video).with_format(video_format)
38 ],
39 outputs: Vec::new(),
40 frames_received: 0,
41 }
42 }
43
44 #[must_use]
46 pub fn frames_received(&self) -> u64 {
47 self.frames_received
48 }
49
50 pub fn reset_counter(&mut self) {
52 self.frames_received = 0;
53 }
54}
55
56impl Node for NullSink {
57 fn id(&self) -> NodeId {
58 self.id
59 }
60
61 fn name(&self) -> &str {
62 &self.name
63 }
64
65 fn node_type(&self) -> NodeType {
66 NodeType::Sink
67 }
68
69 fn state(&self) -> NodeState {
70 self.state
71 }
72
73 fn set_state(&mut self, state: NodeState) -> GraphResult<()> {
74 if !self.state.can_transition_to(state) {
75 return Err(GraphError::InvalidStateTransition {
76 node: self.id,
77 from: self.state.to_string(),
78 to: state.to_string(),
79 });
80 }
81 self.state = state;
82 Ok(())
83 }
84
85 fn inputs(&self) -> &[InputPort] {
86 &self.inputs
87 }
88
89 fn outputs(&self) -> &[OutputPort] {
90 &self.outputs
91 }
92
93 fn process(&mut self, input: Option<FilterFrame>) -> GraphResult<Option<FilterFrame>> {
94 if let Some(frame) = input {
95 if frame.is_video() {
96 self.frames_received += 1;
97 }
98 }
99 Ok(None)
101 }
102
103 fn reset(&mut self) -> GraphResult<()> {
104 self.frames_received = 0;
105 self.set_state(NodeState::Idle)
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use oximedia_codec::VideoFrame;
113 use oximedia_core::PixelFormat;
114
115 #[test]
116 fn test_null_sink_creation() {
117 let sink = NullSink::new(NodeId(0), "null");
118
119 assert_eq!(sink.id(), NodeId(0));
120 assert_eq!(sink.name(), "null");
121 assert_eq!(sink.node_type(), NodeType::Sink);
122 assert_eq!(sink.frames_received(), 0);
123 }
124
125 #[test]
126 fn test_null_sink_ports() {
127 let sink = NullSink::new(NodeId(0), "null");
128
129 assert_eq!(sink.inputs().len(), 1);
131 assert_eq!(sink.outputs().len(), 0);
132 }
133
134 #[test]
135 fn test_null_sink_process() {
136 let mut sink = NullSink::new(NodeId(0), "null");
137
138 for _ in 0..5 {
140 let video = VideoFrame::new(PixelFormat::Yuv420p, 1920, 1080);
141 let frame = FilterFrame::Video(video);
142 let result = sink.process(Some(frame)).expect("process should succeed");
143 assert!(result.is_none());
145 }
146
147 assert_eq!(sink.frames_received(), 5);
148 }
149
150 #[test]
151 fn test_null_sink_reset() {
152 let mut sink = NullSink::new(NodeId(0), "null");
153
154 let video = VideoFrame::new(PixelFormat::Yuv420p, 1920, 1080);
156 sink.process(Some(FilterFrame::Video(video)))
157 .expect("operation should succeed");
158 assert_eq!(sink.frames_received(), 1);
159
160 sink.reset().expect("reset should succeed");
162 assert_eq!(sink.frames_received(), 0);
163 assert_eq!(sink.state(), NodeState::Idle);
164 }
165
166 #[test]
167 fn test_null_sink_no_input() {
168 let mut sink = NullSink::new(NodeId(0), "null");
169
170 let result = sink.process(None).expect("process should succeed");
171 assert!(result.is_none());
172 assert_eq!(sink.frames_received(), 0);
173 }
174}