Skip to main content

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}