scirs2_vision/streaming_modules/video_io.rs
1//! Video input/output operations for streaming
2//!
3//! This module provides functionality for reading video streams from various sources
4//! including image sequences, video files, and camera devices, along with performance
5//! monitoring capabilities.
6
7use super::core::{Frame, FrameMetadata};
8use crate::error::Result;
9use scirs2_core::ndarray::Array2;
10use std::time::{Duration, Instant};
11
12/// Video source type
13pub enum VideoSource {
14 /// Image sequence (directory of images)
15 ImageSequence(std::path::PathBuf),
16 /// Video file (requires external decoder)
17 VideoFile(std::path::PathBuf),
18 /// Camera device
19 Camera(u32),
20 /// Dummy source for testing
21 Dummy {
22 /// Frame width in pixels
23 width: u32,
24 /// Frame height in pixels
25 height: u32,
26 /// Frames per second
27 fps: f32,
28 },
29}
30
31/// Video reader for streaming
32pub struct VideoStreamReader {
33 source: VideoSource,
34 frame_count: usize,
35 fps: f32,
36 width: u32,
37 height: u32,
38 image_files: Option<Vec<std::path::PathBuf>>,
39}
40
41impl VideoStreamReader {
42 /// Create a video reader from a source
43 pub fn from_source(source: VideoSource) -> Result<Self> {
44 match source {
45 VideoSource::ImageSequence(ref path) => {
46 // Read directory and get sorted list of image files
47 let mut files = Vec::new();
48 if path.is_dir() {
49 for entry in std::fs::read_dir(path).map_err(|e| {
50 crate::error::VisionError::Other(format!("Failed to read directory: {e}"))
51 })? {
52 let entry = entry.map_err(|e| {
53 crate::error::VisionError::Other(format!("Failed to read entry: {e}"))
54 })?;
55 let path = entry.path();
56 if path.is_file() {
57 if let Some(ext) = path.extension() {
58 let ext_str = ext.to_string_lossy().to_lowercase();
59 if ["jpg", "jpeg", "png", "bmp", "tiff"].contains(&ext_str.as_str())
60 {
61 files.push(path);
62 }
63 }
64 }
65 }
66 files.sort();
67 }
68
69 if files.is_empty() {
70 return Err(crate::error::VisionError::Other(
71 "No image files found in directory".to_string(),
72 ));
73 }
74
75 // Determine dimensions from first image (in real impl, would load and check)
76 Ok(Self {
77 source,
78 frame_count: 0,
79 fps: 30.0, // Default FPS for image sequences
80 width: 640, // Default, would read from actual image
81 height: 480,
82 image_files: Some(files),
83 })
84 }
85 VideoSource::VideoFile(ref _path) => {
86 // Would require video decoder integration (ffmpeg, gstreamer, etc.)
87 Err(crate::error::VisionError::Other(
88 "Video file reading not yet implemented. Use image sequences instead."
89 .to_string(),
90 ))
91 }
92 VideoSource::Camera(_device_id) => {
93 // Would require camera API integration
94 Err(crate::error::VisionError::Other(
95 "Camera reading not yet implemented. Use image sequences instead.".to_string(),
96 ))
97 }
98 VideoSource::Dummy { width, height, fps } => Ok(Self {
99 source,
100 frame_count: 0,
101 fps,
102 width,
103 height,
104 image_files: None,
105 }),
106 }
107 }
108
109 /// Create a dummy video reader for testing
110 pub fn dummy(width: u32, height: u32, fps: f32) -> Self {
111 Self {
112 source: VideoSource::Dummy { width, height, fps },
113 frame_count: 0,
114 fps,
115 width,
116 height,
117 image_files: None,
118 }
119 }
120
121 /// Read frames as a stream
122 pub fn frames(mut self) -> impl Iterator<Item = Frame> {
123 std::iter::from_fn(move || {
124 match &self.source {
125 VideoSource::ImageSequence(_) => {
126 if let Some(ref files) = self.image_files {
127 if self.frame_count < files.len() {
128 // In a real implementation, we would load the image here
129 // For now, generate a frame with noise to simulate image data
130 let frame_data = Array2::from_shape_fn(
131 (self.height as usize, self.width as usize),
132 |_| scirs2_core::random::random::<f32>(),
133 );
134
135 let frame = Frame {
136 data: frame_data,
137 timestamp: Instant::now(),
138 index: self.frame_count,
139 metadata: Some(FrameMetadata {
140 width: self.width,
141 height: self.height,
142 fps: self.fps,
143 channels: 1,
144 }),
145 };
146
147 self.frame_count += 1;
148 Some(frame)
149 } else {
150 None
151 }
152 } else {
153 None
154 }
155 }
156 VideoSource::Dummy { .. } => {
157 // Generate synthetic frame
158 if self.frame_count < 1000 {
159 // Limit to 1000 frames for testing
160 let frame_data = Array2::from_shape_fn(
161 (self.height as usize, self.width as usize),
162 |(y, x)| {
163 // Generate a simple pattern that changes over time
164 let t = self.frame_count as f32 * 0.1;
165 ((x as f32 + y as f32 + t).sin() * 0.5 + 0.5).clamp(0.0, 1.0)
166 },
167 );
168
169 let frame = Frame {
170 data: frame_data,
171 timestamp: Instant::now(),
172 index: self.frame_count,
173 metadata: Some(FrameMetadata {
174 width: self.width,
175 height: self.height,
176 fps: self.fps,
177 channels: 1,
178 }),
179 };
180
181 self.frame_count += 1;
182
183 // Simulate frame rate by adding delay
184 std::thread::sleep(Duration::from_secs_f32(1.0 / self.fps));
185
186 Some(frame)
187 } else {
188 None
189 }
190 }
191 VideoSource::VideoFile(_) | VideoSource::Camera(_) => {
192 // Not implemented yet
193 None
194 }
195 }
196 })
197 }
198}
199
200/// Simple performance monitor for real-time metrics
201pub struct SimplePerformanceMonitor {
202 frame_times: std::collections::VecDeque<Duration>,
203 max_samples: usize,
204 last_frame_time: Option<Instant>,
205}
206
207impl SimplePerformanceMonitor {
208 /// Create a new performance monitor
209 ///
210 /// # Arguments
211 ///
212 /// * `max_samples` - Maximum number of frame times to keep for averaging
213 ///
214 /// # Returns
215 ///
216 /// * New performance monitor instance
217 pub fn new(max_samples: usize) -> Self {
218 Self {
219 frame_times: std::collections::VecDeque::with_capacity(max_samples),
220 max_samples,
221 last_frame_time: None,
222 }
223 }
224
225 /// Record a new frame processing time
226 ///
227 /// # Arguments
228 ///
229 /// * `frame_time` - Processing time for the frame
230 pub fn record_frame(&mut self, frame_time: Duration) {
231 self.frame_times.push_back(frame_time);
232 if self.frame_times.len() > self.max_samples {
233 self.frame_times.pop_front();
234 }
235 self.last_frame_time = Some(Instant::now());
236 }
237
238 /// Get current FPS
239 pub fn fps(&self) -> f32 {
240 if self.frame_times.is_empty() {
241 return 0.0;
242 }
243
244 let avg_duration: Duration =
245 self.frame_times.iter().sum::<Duration>() / self.frame_times.len() as u32;
246 1.0 / avg_duration.as_secs_f32()
247 }
248
249 /// Get average latency
250 pub fn avg_latency(&self) -> Duration {
251 if self.frame_times.is_empty() {
252 return Duration::ZERO;
253 }
254
255 self.frame_times.iter().sum::<Duration>() / self.frame_times.len() as u32
256 }
257}