diff options
| author | Dawid Rycerz <dawid@rycerz.xyz> | 2026-02-08 12:44:10 +0100 |
|---|---|---|
| committer | Dawid Rycerz <dawid@rycerz.xyz> | 2026-02-08 12:44:10 +0100 |
| commit | 0c20fb86633104744dbccf30ad732296694fff1b (patch) | |
| tree | 02ffb8494086960b4a84decf3bdc2c8c61bfc4f6 /src/lib.rs | |
Initial pipewiremain
Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ab42e1c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,217 @@ +//! 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<dyn std::error::Error>> { +//! 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<UsbSupercamera>, + protocol: Arc<UPPCamera>, + video_backend: Arc<Mutex<Box<dyn VideoBackendTrait>>>, + is_running: Arc<RwLock<bool>>, + usb_thread: Arc<Mutex<Option<thread::JoinHandle<()>>>>, +} + +impl SuperCamera { + /// Create a new SuperCamera instance + pub fn new() -> Result<Self> { + 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<Self> { + 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<UsbSupercamera>, + protocol: Arc<UPPCamera>, + video_backend: Arc<Mutex<Box<dyn VideoBackendTrait>>>, + is_running: Arc<RwLock<bool>>, + ) { + 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); + } +} |
