1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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);
}
}
|