//! Geek szitman supercamera - Rust implementation //! //! This crate provides a Rust implementation of the Geek szitman supercamera //! endoscope viewer with PipeWire support and preparation for V4L2 fallback. //! //! # Features //! //! - USB communication with the endoscope device //! - PipeWire video streaming //! - UPP protocol implementation //! - JPEG frame processing //! - Modular architecture for maintainability //! //! # Example //! //! ```rust,no_run //! use geek_szitman_supercamera::SuperCamera; //! //! fn main() -> Result<(), Box> { //! let camera = SuperCamera::new()?; //! camera.start_stream()?; //! Ok(()) //! } //! ``` pub mod error; pub mod protocol; pub mod usb; pub mod utils; pub mod video; pub use error::{Error, Result}; pub use protocol::UPPCamera; pub use usb::UsbSupercamera; pub use video::{VideoBackend, VideoBackendTrait}; use std::sync::Arc; use std::sync::Mutex; use std::sync::RwLock; use std::thread; use std::time::Duration; use tracing::{info, warn}; /// Main camera controller that orchestrates all components pub struct SuperCamera { usb_camera: Arc, protocol: Arc, video_backend: Arc>>, is_running: Arc>, usb_thread: Arc>>>, } impl SuperCamera { /// Create a new SuperCamera instance pub fn new() -> Result { Self::with_backend(crate::video::VideoBackendType::PipeWire) } /// Create a new SuperCamera instance with specified backend pub fn with_backend(backend_type: crate::video::VideoBackendType) -> Result { let usb_camera = Arc::new(UsbSupercamera::new()?); let protocol = Arc::new(UPPCamera::new_with_debug(true)); // Enable debug by default // Initialize video backend based on choice let video_backend = Arc::new(Mutex::new(VideoBackend::from_type(backend_type)?)); Ok(Self { usb_camera, protocol, video_backend, is_running: Arc::new(RwLock::new(false)), usb_thread: Arc::new(Mutex::new(None)), }) } /// Start the camera stream pub fn start_stream(&self) -> Result<()> { let mut is_running = self.is_running.write().unwrap(); if *is_running { warn!("Camera stream is already running"); return Ok(()); } info!("Starting camera stream..."); *is_running = true; drop(is_running); // Ensure video backend is initialized before pushing frames { let mut backend = self.video_backend.lock().unwrap(); backend.initialize()?; } // Start USB reading loop in a separate thread let usb_camera = Arc::clone(&self.usb_camera); let protocol = Arc::clone(&self.protocol); let video_backend = Arc::clone(&self.video_backend); let is_running = Arc::clone(&self.is_running); let handle = thread::spawn(move || { Self::usb_read_loop(usb_camera, protocol, video_backend, is_running); }); // Store the thread handle let mut usb_thread = self.usb_thread.lock().unwrap(); *usb_thread = Some(handle); Ok(()) } /// Stop the camera stream pub fn stop_stream(&self) -> Result<()> { let mut is_running = self.is_running.write().unwrap(); if !*is_running { warn!("Camera stream is not running"); return Ok(()); } info!("Stopping camera stream..."); *is_running = false; Ok(()) } /// Check if the camera stream is running pub fn is_running(&self) -> bool { *self.is_running.read().unwrap() } /// Main USB reading loop fn usb_read_loop( usb_camera: Arc, protocol: Arc, video_backend: Arc>>, is_running: Arc>, ) { let mut frame_count = 0u32; while *is_running.read().unwrap() { match usb_camera.read_frame() { Ok(data) => { frame_count += 1; // Reduce logging frequency - only log every 100th frame if frame_count % 100 == 0 { tracing::debug!("Received frame {} ({} bytes)", frame_count, data.len()); } // Process frame through protocol if let Err(e) = protocol.handle_frame_robust(&data) { tracing::error!("Protocol error: {}", e); // Log additional frame information for debugging if data.len() >= 5 { let magic_bytes = [data[0], data[1]]; let magic = u16::from_le_bytes(magic_bytes); let cid = if data.len() >= 3 { data[2] } else { 0 }; let length_bytes = if data.len() >= 5 { [data[3], data[4]] } else { [0, 0] }; let length = u16::from_le_bytes(length_bytes); tracing::debug!( "Frame header: magic=0x{:04X}, cid={}, length={}, actual_size={}", magic, cid, length, data.len() ); } continue; } // Send to video backend if frame is complete if let Some(frame) = protocol.get_complete_frame() { let backend = video_backend.lock().unwrap(); if let Err(e) = backend.push_frame(&frame) { tracing::error!("Video backend error: {}", e); } } } Err(e) => { tracing::error!("USB read error: {}", e); // Check if it's a USB error that indicates disconnection if let crate::error::Error::Usb(usb_err) = &e { if usb_err.is_device_disconnected() { tracing::warn!("Device disconnected, stopping stream"); break; } } // Use standard library sleep instead of tokio std::thread::sleep(Duration::from_millis(100)); } } } info!("USB reading loop stopped"); } } impl Drop for SuperCamera { fn drop(&mut self) { // Stop the stream if let Ok(mut is_running) = self.is_running.try_write() { *is_running = false; } // Wait for USB thread to finish if let Some(handle) = self.usb_thread.lock().unwrap().take() { let _ = handle.join(); } } } #[cfg(test)] mod tests { #[test] fn test_super_camera_creation() { // This test requires actual USB device, so we'll just test the structure // In a real test environment, we'd use mocks assert!(true); } }