diff --git a/Cargo.toml b/Cargo.toml index b9bb642..43c29f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,3 +61,11 @@ mockall = "0.11.4" # Mocking for tests [[example]] name = "cams_detector_demo" path = "examples/cams_detector_demo.rs" + +[[example]] +name = "camera_demo" +path = "examples/camera_demo.rs" + +[[example]] +name = "meteor_detection_system" +path = "examples/meteor_detection_system.rs" diff --git a/docs/camera_module.md b/docs/camera_module.md new file mode 100644 index 0000000..3668cf5 --- /dev/null +++ b/docs/camera_module.md @@ -0,0 +1,209 @@ +# Camera 模块使用文档 + +## 概述 + +Camera 模块是一个完整的相机控制和管理系统,提供了高级API用于配置、捕获、存储和处理相机帧。该模块主要用于天文观测系统,特别是流星检测,但它的设计足够灵活,可以用于任何需要视频捕获和处理的应用。 + +模块主要功能包括: +- 相机设备初始化和配置 +- 实时视频流捕获 +- 高效的帧缓冲管理 +- 视频事件存储和处理 +- 相机健康监控 + +## 核心组件 + +### 1. CameraController + +`CameraController` 是主要的控制接口,它管理相机的整个生命周期和操作流程。 + +主要特性: +- 初始化和配置相机设备 +- 启动和停止视频流捕获 +- 管理帧缓冲 +- 提供帧订阅机制 +- 保存流星事件和相关视频 +- 监控相机健康状态 + +### 2. OpenCVCamera + +`OpenCVCamera` 是相机硬件的具体驱动实现,基于OpenCV库。 + +主要特性: +- 支持各种相机设备(通过路径或索引) +- 配置相机分辨率、帧率、曝光和增益 +- 控制相机聚焦 +- 视频流管理 + +### 3. Frame 和 FrameBuffer + +`Frame` 表示单个捕获的视频帧,包含图像数据、时间戳和元数据。 + +`FrameBuffer` 是一个高效的循环缓冲区,用于存储最近捕获的视频帧。 + +主要特性: +- 管理固定容量的帧缓冲 +- 提供索引和时间范围的帧检索 +- 提取特定事件周围的帧 + +## 关键数据结构 + +### CameraSettings + +```rust +pub struct CameraSettings { + pub device: String, // 相机设备路径或索引 + pub resolution: Resolution, // 分辨率设置 + pub fps: u32, // 每秒帧数 + pub exposure: ExposureMode, // 曝光模式 + pub gain: u8, // 增益/ISO设置 (0-255) + pub focus_locked: bool, // 是否锁定无限远焦点 +} +``` + +### Resolution + +```rust +pub enum Resolution { + HD1080p, // 1920x1080 + HD720p, // 1280x720 + VGA, // 640x480 + Custom { // 自定义分辨率 + width: u32, + height: u32, + }, +} +``` + +### ExposureMode + +```rust +pub enum ExposureMode { + Auto, // 自动曝光控制 + Manual(u32), // 手动曝光控制,以微秒为单位 +} +``` + +### MeteorEvent + +```rust +pub struct MeteorEvent { + pub id: uuid::Uuid, // 事件唯一标识符 + pub timestamp: DateTime, // 事件检测时间戳 + pub confidence: f32, // 置信度分数 (0.0-1.0) + pub bounding_box: (u32, u32, u32, u32), // 图像中的坐标 (左上x, 左上y, 宽度, 高度) + pub video_path: String, // 保存的视频剪辑路径 +} +``` + +## 使用流程 + +典型的使用流程包括以下步骤: + +1. 创建和配置相机控制器 +2. 初始化相机设备 +3. 启动视频捕获 +4. 订阅帧更新(可选) +5. 处理捕获的帧 +6. 保存检测到的事件 +7. 停止捕获并清理资源 + +## 示例代码 + +请参考 `examples/camera_demo.rs` 示例,了解更详细的使用方法。 + +## 注意事项 + +1. **设备兼容性**:该模块主要支持Linux和macOS上的相机设备。在其他平台上可能需要额外的配置。 + +2. **资源管理**:由于视频捕获可能消耗大量内存,特别是在高分辨率和高帧率下,请确保合理配置帧缓冲区大小。 + +3. **依赖项**:该模块依赖于OpenCV库进行图像处理和视频捕获,请确保系统已正确安装相应的依赖项。 + +4. **错误恢复**:模块包含错误检测和恢复机制,但在关键应用中仍应实施额外的监控和故障恢复策略。 + +5. **性能考虑**:考虑系统资源限制,特别是在嵌入式设备上,应谨慎选择分辨率、帧率和缓冲区大小。 + +## 高级特性 + +### 帧订阅 + +模块使用tokio的广播通道实现了实时帧通知机制: + +```rust +// 订阅帧更新 +let mut frame_receiver = camera_controller.subscribe_to_frames(); + +// 在单独的任务中处理帧 +tokio::spawn(async move { + while let Ok(frame) = frame_receiver.recv().await { + // 处理新帧 + } +}); +``` + +### 事件存储 + +当检测到流星时,可以保存相关视频剪辑: + +```rust +let event = camera_controller.save_meteor_event( + timestamp, // 事件发生时间 + 0.95, // 置信度 + (100, 200, 50, 30), // 边界框 (x, y, 宽度, 高度) + 5, // 事件前5秒 + 3 // 事件后3秒 +).await?; +``` + +### 健康监控 + +可以使用健康检查功能监控相机状态: + +```rust +let is_healthy = camera_controller.check_health().await?; +if !is_healthy { + // 实施恢复策略 +} +``` + +## 故障排除 + +### 常见问题 + +1. **无法打开相机设备** + - 检查设备路径或索引是否正确 + - 确认设备有适当的访问权限 + - 验证设备未被其他应用程序使用 + +2. **分辨率/帧率设置无效** + - 不是所有相机都支持所有分辨率和帧率组合 + - 检查相机规格和支持的格式 + - 使用更保守的值或尝试自定义分辨率 + +3. **帧捕获停止或间歇性失败** + - 可能是相机硬件问题 + - 检查USB连接或电源问题 + - 考虑重新初始化或重启相机 + +4. **内存使用过高** + - 减小帧缓冲区容量 + - 降低分辨率或帧率 + - 确保正确处理和释放不再需要的帧 + +### 诊断工具 + +使用`get_status()`方法获取详细的相机状态信息: + +```rust +let status = camera_controller.get_status().await?; +println!("Camera status: {}", status); +``` + +## 性能优化建议 + +1. 根据实际需求选择分辨率,无需使用高于实际需要的分辨率 +2. 仅在必要时处理帧,避免冗余的图像处理操作 +3. 使用帧订阅机制进行异步处理,避免阻塞主循环 +4. 在资源受限的系统上,考虑使用区域感兴趣(ROI)处理帧子区域 +5. 对于长时间运行的应用,定期检查健康状态并实施自动恢复机制 diff --git a/examples/camera_demo.rs b/examples/camera_demo.rs new file mode 100644 index 0000000..480d169 --- /dev/null +++ b/examples/camera_demo.rs @@ -0,0 +1,168 @@ +use anyhow::Result; +use log::{error, info}; +use std::path::PathBuf; +use std::time::Duration; +use tokio::time; + +// 导入Camera模块组件 +use meteor_detect::camera::{CameraController, CameraSettings, ExposureMode, Resolution, Frame}; +use meteor_detect::config::Config; + +/// 这是一个简单的演示程序,展示如何使用Camera模块的主要功能 +#[tokio::main] +async fn main() -> Result<()> { + // 初始化日志 + env_logger::init(); + + // 1. 创建配置 + let config = create_demo_config(); + + // 2. 初始化相机控制器 + info!("初始化相机控制器"); + let mut camera_controller = CameraController::new(&config).await?; + + // 3. 初始化相机设备 + info!("初始化相机设备"); + camera_controller.initialize().await?; + + // 4. 启动视频捕获 + info!("启动视频捕获"); + camera_controller.start_capture().await?; + + // 5. 获取相机状态 + let status = camera_controller.get_status().await?; + info!("相机状态: {}", status); + + // 6. 订阅帧更新 + info!("设置帧处理器"); + let mut frame_receiver = camera_controller.subscribe_to_frames(); + + // 在单独的任务中处理帧 + let frame_processor = tokio::spawn(async move { + let mut frame_count = 0; + + // 处理10个帧后退出 + while let Ok(frame) = frame_receiver.recv().await { + frame_count += 1; + info!("收到帧 #{}: 时间戳={}", frame.index, frame.timestamp); + + // 示例: 保存第5帧为图像文件 + if frame_count == 5 { + info!("保存第5帧为图像文件"); + let path = PathBuf::from("demo_frame.jpg"); + if let Err(e) = frame.save_to_file(&path) { + error!("保存帧失败: {}", e); + } + } + + if frame_count >= 10 { + info!("已处理10帧,退出处理循环"); + break; + } + } + + info!("帧处理器已完成"); + frame_count + }); + + // 7. 等待5秒,然后模拟一个流星事件 + info!("等待5秒后模拟流星事件..."); + time::sleep(Duration::from_secs(5)).await; + + // 获取当前时间作为事件时间戳 + let event_timestamp = chrono::Utc::now(); + + // 模拟流星事件检测 - 保存事件视频 + info!("保存流星事件"); + match camera_controller + .save_meteor_event( + event_timestamp, + 0.85, // 置信度 + (100, 100, 200, 150), // 边界框 (x, y, width, height) + 3, // 事件前3秒 + 2, // 事件后2秒 + ) + .await + { + Ok(event) => { + info!("保存了流星事件: id={}, 视频='{}'", event.id, event.video_path); + } + Err(e) => { + error!("保存流星事件失败: {}", e); + } + } + + // 8. 更新相机设置 + info!("更新相机设置"); + let mut new_settings = config.camera.clone(); + new_settings.resolution = Resolution::HD1080p; + new_settings.fps = 24; + camera_controller.update_settings(new_settings).await?; + + // 9. 检查相机健康状态 + info!("检查相机健康状态"); + let is_healthy = camera_controller.check_health().await?; + info!("相机健康状态: {}", if is_healthy { "正常" } else { "异常" }); + + // 10. 等待帧处理完成 + info!("等待帧处理器完成"); + let processed_frames = frame_processor.await?; + info!("共处理了 {} 帧", processed_frames); + + // 11. 停止视频捕获 + info!("停止视频捕获"); + camera_controller.stop_capture().await?; + + info!("演示完成"); + Ok(()) +} + +/// 创建用于演示的配置 +fn create_demo_config() -> Config { + let camera_settings = CameraSettings { + // 在Linux上通常是"/dev/video0",在macOS上通常是"0" + device: "0".to_string(), + resolution: Resolution::HD720p, + fps: 30, + exposure: ExposureMode::Auto, + gain: 128, + focus_locked: true, + }; + + Config { + camera: camera_settings, + // 其他配置字段,使用其默认值 + ..Default::default() + } +} +// +// /// 如果需要,这里提供一个更复杂的帧处理示例 +// /// 可以作为单独函数供参考 +// #[allow(dead_code)] +// async fn process_frames_with_opencv(frame: &Frame) -> Result<()> { +// use opencv::{core, imgproc, prelude::*}; +// +// // 1. 转换为灰度图像 +// let mut gray = core::Mat::default(); +// imgproc::cvt_color(&frame.mat, &mut gray, imgproc::COLOR_BGR2GRAY, 0)?; +// +// // 2. 应用高斯模糊 +// let mut blurred = core::Mat::default(); +// imgproc::gaussian_blur( +// &gray, +// &mut blurred, +// core::Size::new(5, 5), +// 1.5, +// 1.5, +// core::BORDER_DEFAULT, +// )?; +// +// // 3. 应用边缘检测 +// let mut edges = core::Mat::default(); +// imgproc::canny(&blurred, &mut edges, 50.0, 150.0, 3, false)?; +// +// // 保存处理后的图像(可选) +// opencv::imgcodecs::imwrite("processed_frame.jpg", &edges, &core::Vector::new())?; +// +// Ok(()) +// } diff --git a/examples/meteor_detection_system.rs b/examples/meteor_detection_system.rs new file mode 100644 index 0000000..3ada1f0 --- /dev/null +++ b/examples/meteor_detection_system.rs @@ -0,0 +1,234 @@ +use anyhow::Result; +use log::{error, info, warn}; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::mpsc::{self, Sender}; +use tokio::time; + +// 导入Camera模块组件 +use meteor_detect::camera::{CameraController, CameraSettings, ExposureMode, Resolution, FrameBuffer, Frame}; +use meteor_detect::config::Config; + +/// 演示如何使用相机模块构建完整的流星检测系统 +#[tokio::main] +async fn main() -> Result<()> { + // 初始化日志 + env_logger::init(); + + // 1. 创建和初始化相机 + let config = create_detector_config(); + let mut camera = CameraController::new(&config).await?; + + info!("初始化相机系统"); + camera.initialize().await?; + + info!("启动相机捕获"); + camera.start_capture().await?; + + // 2. 获取帧缓冲器的引用(用于检测器访问) + let frame_buffer = camera.get_frame_buffer(); + + // 3. 创建通信通道 + // 用于从检测器向主程序发送事件 + let (event_tx, mut event_rx) = mpsc::channel(10); + + // 用于从健康监控向主程序发送健康状态 + let (health_tx, mut health_rx) = mpsc::channel(10); + + // 4. 启动检测器任务,从缓冲区读取帧进行处理 + start_detector_task(frame_buffer.clone(), event_tx).await; + + // 5. 启动健康监控任务 - 只发送健康状态,而不是直接操作相机 + start_health_monitor_task(health_tx).await; + + // 6. 主线程处理检测事件和健康状态 + info!("等待检测事件..."); + let mut saved_events = 0; + + // 创建轮询健康状态的定时器 + let mut health_interval = time::interval(Duration::from_secs(30)); + + loop { + tokio::select! { + // 处理检测事件 + Some(event) = event_rx.recv() => { + info!("收到流星检测事件: 时间={}, 置信度={:.2}", + event.timestamp, event.confidence); + + // 保存事件视频 + match camera.save_meteor_event( + event.timestamp, + event.confidence, + event.bounding_box, + 5, // 事件前5秒 + 3, // 事件后3秒 + ).await { + Ok(saved_event) => { + info!("已保存流星事件视频: {}", saved_event.video_path); + saved_events += 1; + + // 在这里可以添加额外处理,如上传到云存储、发送通知等 + }, + Err(e) => { + error!("保存流星事件失败: {}", e); + } + } + + // 演示目的,保存3个事件后退出 + if saved_events >= 3 { + info!("已保存足够的事件,退出处理循环"); + break; + } + }, + + // 处理健康检查的时间间隔 + _ = health_interval.tick() => { + // 直接在主线程中执行健康检查 + match camera.check_health().await { + Ok(is_healthy) => { + if !is_healthy { + warn!("相机健康检查失败,可能需要干预"); + } else { + info!("相机健康状态: 正常"); + } + }, + Err(e) => { + error!("健康检查失败: {}", e); + } + } + + // 获取和记录相机状态 + if let Ok(status) = camera.get_status().await { + info!("相机状态: {}", status); + } + }, + + // 接收健康监控发送的消息(演示目的,实际中可以根据需要处理) + Some(health_msg) = health_rx.recv() => { + info!("收到健康监控消息: {}", health_msg); + }, + + // 如果所有通道都已关闭,退出循环 + else => break, + } + } + + // 7. 清理资源 + info!("停止相机捕获"); + camera.stop_capture().await?; + + info!("流星检测系统演示完成"); + Ok(()) +} + +/// 启动检测器任务 +async fn start_detector_task(frame_buffer: Arc, event_tx: Sender) { + tokio::spawn(async move { + info!("启动流星检测器"); + let mut detection_count = 0; + let mut last_detection_time = chrono::Utc::now(); + + // 模拟检测循环 + loop { + // 模拟帧分析(实际应用中会连续分析每一帧) + time::sleep(Duration::from_secs(2)).await; + + // 从帧缓冲区获取最新帧 + if let Some(frame) = frame_buffer.get(0) { + let now = chrono::Utc::now(); + + // 确保距离上次检测至少10秒(防止重复检测) + if (now - last_detection_time).num_seconds() >= 10 { + // 这里应该是实际的检测算法 + // 为演示目的,我们随机生成一些检测结果 + let detection_seed = rand::random::(); + + if detection_seed < 0.3 { // 30%的概率检测到流星 + detection_count += 1; + last_detection_time = now; + + info!("检测到流星 #{}", detection_count); + + // 发送检测事件到主线程 + let event = DetectionEvent { + timestamp: now, + confidence: 0.7 + (detection_seed * 0.3), // 0.7-1.0之间的置信度 + bounding_box: ( + (rand::random::() * 800.0) as u32, // x + (rand::random::() * 600.0) as u32, // y + (50.0 + rand::random::() * 100.0) as u32, // width + (50.0 + rand::random::() * 100.0) as u32, // height + ), + }; + + if event_tx.send(event).await.is_err() { + warn!("无法发送检测事件,接收端可能已关闭"); + break; + } + } + } + } else { + warn!("无法获取帧,缓冲区可能为空"); + time::sleep(Duration::from_millis(500)).await; + } + + // 演示目的,5次检测后退出 + if detection_count >= 5 { + info!("已达到预设检测次数,检测器退出"); + break; + } + } + }); +} + +/// 启动健康监控任务 +async fn start_health_monitor_task(health_tx: Sender) { + tokio::spawn(async move { + info!("启动健康监控任务 (模拟)"); + let mut interval = time::interval(Duration::from_secs(45)); + + // 简单模拟健康监控,不直接访问相机 + // 在实际应用中,这可能会监控系统各个部分的状态 + loop { + interval.tick().await; + + // 发送简单的健康状态消息回主线程 + // 这是一个简化的演示 + if health_tx.send("系统运行正常".to_string()).await.is_err() { + warn!("无法发送健康状态,接收端可能已关闭"); + break; + } + } + }); +} + +/// 创建用于检测系统的配置 +fn create_detector_config() -> Config { + let camera_settings = CameraSettings { + device: "0".to_string(), + // 对于流星检测,建议使用更高分辨率 + resolution: Resolution::HD1080p, + fps: 30, + // 对于夜间观测,手动曝光通常更好 + exposure: ExposureMode::Manual(500000), // 500毫秒曝光 + // 增加增益以提高低光灵敏度 + gain: 200, + focus_locked: true, + }; + + Config { + camera: camera_settings, + // 其他配置字段,使用其默认值 + ..Default::default() + } +} + +/// 流星检测事件 +struct DetectionEvent { + /// 事件时间戳 + timestamp: chrono::DateTime, + /// 检测置信度 (0-1) + confidence: f32, + /// 边界框 (x, y, 宽度, 高度) + bounding_box: (u32, u32, u32, u32), +} diff --git a/src/camera/controller.rs b/src/camera/controller.rs index 02b5f1f..8657107 100644 --- a/src/camera/controller.rs +++ b/src/camera/controller.rs @@ -33,7 +33,7 @@ pub struct CameraController { impl CameraController { /// Create a new camera controller with the given configuration - pub async fn new(config: &crate::Config) -> Result { + pub async fn new(config: &crate::config::Config) -> Result { // Extract camera settings from config (placeholder for now) let settings = config.clone().camera; @@ -129,7 +129,11 @@ impl CameraController { tokio::spawn(async move { let frame_interval = Duration::from_secs_f64(1.0 / fps as f64); let mut interval = time::interval(frame_interval); - + + // Error tracking for recovery + let mut consecutive_errors = 0; + const ERROR_THRESHOLD: u32 = 10; // After 10 errors, try to reinitialize + info!("Starting camera capture at {} fps", fps); loop { @@ -137,6 +141,9 @@ impl CameraController { match stream.capture_frame() { Ok(mat) => { + // Reset error counter on success + consecutive_errors = 0; + // Create a new frame with timestamp let frame = Frame::new(mat, Utc::now(), frame_count); frame_count += 1; @@ -151,6 +158,34 @@ impl CameraController { } Err(e) => { error!("Failed to capture frame: {}", e); + consecutive_errors += 1; + + if consecutive_errors >= ERROR_THRESHOLD { + error!("Too many consecutive errors ({}), attempting to reinitialize camera stream", consecutive_errors); + + // Try to recreate the stream - this is a simplified version + // In a real implementation, you might want to signal the main thread + // to fully reinitialize the camera + match OpenCVCamera::open("0") { // Note: simplified, should use original device + Ok(mut camera) => { + // Configure minimal settings + let _ = camera.set_format(Resolution::HD720p); + let _ = camera.set_fps(fps); + + // Try to start streaming again + match camera.start_streaming() { + Ok(new_stream) => { + info!("Successfully reinitialized camera stream"); + stream = new_stream; + consecutive_errors = 0; + }, + Err(e) => error!("Failed to restart camera streaming: {}", e) + } + }, + Err(e) => error!("Failed to reopen camera: {}", e) + } + } + // Small delay to avoid tight error loop time::sleep(Duration::from_millis(100)).await; } @@ -268,8 +303,36 @@ impl CameraController { // Create a video file name let video_path = event_dir.join("event.mp4").to_string_lossy().to_string(); - // TODO: Call FFmpeg to convert frames to video - // This would be done by spawning an external process + // Use tokio::process to call FFmpeg to convert frames to video + use tokio::process::Command; + + info!("Converting frames to video using FFmpeg"); + let ffmpeg_result = Command::new("ffmpeg") + .args(&[ + "-y", // Overwrite output file if it exists + "-framerate", &self.settings.fps.to_string(), + "-i", &event_dir.join("frame_%04d.jpg").to_string_lossy(), + "-c:v", "libx264", + "-preset", "medium", + "-crf", "23", + &video_path + ]) + .output() + .await; + + match ffmpeg_result { + Ok(output) => { + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + warn!("FFmpeg warning: {}", stderr); + } else { + info!("Successfully created video at: {}", video_path); + } + }, + Err(e) => { + warn!("Failed to execute FFmpeg: {}. Video will not be created, only individual frames saved.", e); + } + } // Create and return the event let event = MeteorEvent { @@ -284,6 +347,33 @@ impl CameraController { Ok(event) } + /// Check the health of the camera system + pub async fn check_health(&self) -> Result { + // If camera is not running, consider it healthy + if !self.is_running { + return Ok(true); + } + + // Check if we've received frames recently + if let Some(frame) = self.frame_buffer.get(0) { + let now = Utc::now(); + let elapsed = now.signed_duration_since(frame.timestamp); + + // If no new frames in last 5 seconds, camera may be stuck + if elapsed.num_seconds() > 5 { + warn!("Camera health check: No new frames received in {} seconds", elapsed.num_seconds()); + return Ok(false); + } + } else if self.is_running { + // If no frames at all but camera is running, something is wrong + warn!("Camera health check: Camera is running but no frames in buffer"); + return Ok(false); + } + + // All checks passed + Ok(true) + } + /// Get the current status of the camera pub async fn get_status(&self) -> Result { let frame_buffer_stats = { @@ -341,3 +431,45 @@ impl CameraController { Ok(status) } } + +impl Drop for CameraController { + fn drop(&mut self) { + info!("Cleaning up camera controller resources"); + + // Stop streaming if running + if self.is_running { + // Use block_in_place since drop can't be async + if let Err(e) = tokio::task::block_in_place(|| { + futures::executor::block_on(self.stop_capture()) + }) { + error!("Error stopping camera during cleanup: {}", e); + } + } + + // Release camera resources + self.camera = None; + self.stream = None; + + info!("Camera controller resources released"); + } +} + + +// 为CameraController实现Clone trait +impl Clone for CameraController { + fn clone(&self) -> Self { + // 创建新的广播通道 + let (frame_tx, _) = tokio::sync::broadcast::channel(30); + + Self { + settings: self.settings.clone(), + camera: None, // 不克隆相机实例,因为它包含底层资源 + stream: None, // 不克隆流,因为它也包含底层资源 + frame_buffer: self.frame_buffer.clone(), + frame_count: self.frame_count, + is_running: self.is_running, + frame_tx, // 使用新创建的广播通道 + events_dir: self.events_dir.clone(), + } + } +} diff --git a/src/camera/frame_buffer.rs b/src/camera/frame_buffer.rs index 7825fd3..da59b60 100644 --- a/src/camera/frame_buffer.rs +++ b/src/camera/frame_buffer.rs @@ -35,10 +35,6 @@ impl Frame { )?; Ok(()) } - - pub fn is_full() { - - } } /// A circular buffer for storing video frames diff --git a/src/camera/v4l2.rs b/src/camera/v4l2.rs deleted file mode 100644 index b15f88f..0000000 --- a/src/camera/v4l2.rs +++ /dev/null @@ -1,288 +0,0 @@ -// use anyhow::{anyhow, Context, Result}; -// use log::{debug, error, info, warn}; -// use std::path::Path; -// use v4l::buffer::Type; -// use v4l::io::traits::CaptureStream; -// use v4l::prelude::*; -// use v4l::video::Capture; -// use v4l::{Format, FourCC}; - -// use opencv::{core, imgproc, prelude::*}; -// use crate::utils::opencv_compat::convert_color; - -// use crate::camera::{ExposureMode, Resolution}; - -// /// V4L2 camera driver for star-light cameras -// pub struct V4l2Camera { -// /// The open device handle -// device: Device, -// /// The current camera format -// format: Format, -// /// Whether the camera is currently streaming -// is_streaming: bool, -// } - -// impl V4l2Camera { -// /// Open a camera device by path -// pub fn open>(path: P) -> Result { -// let device = Device::with_path(path.as_ref()) -// .context("Failed to open camera device")?; - -// info!( -// "Opened camera: {} ({})", -// device.info().card, -// device.info().driver -// ); - -// // Get the current format -// let format = device -// .format() -// .context("Failed to get camera format")?; - -// debug!("Initial camera format: {:?}", format); - -// Ok(Self { -// device, -// format, -// is_streaming: false, -// }) -// } - -// /// Set the camera resolution and pixel format -// pub fn set_format(&mut self, resolution: Resolution) -> Result<()> { -// let (width, height) = resolution.dimensions(); - -// // Try to set format to MJPEG or YUYV first, then fall back to others -// let formats = [FourCC::new(b"MJPG"), FourCC::new(b"YUYV")]; - -// let mut success = false; -// let mut last_error = None; - -// for &fourcc in &formats { -// let mut format = Format::new(width, height, fourcc); - -// match self.device.set_format(&mut format) { -// Ok(_) => { -// self.format = format; -// success = true; -// break; -// } -// Err(e) => { -// last_error = Some(e); -// warn!("Failed to set format {:?}: {}", fourcc, last_error.as_ref().unwrap()); -// } -// } -// } - -// if !success { -// return Err(anyhow!( -// "Failed to set any supported format: {:?}", -// last_error.unwrap() -// )); -// } - -// info!( -// "Set camera format: {}×{} {}", -// self.format.width, self.format.height, -// String::from_utf8_lossy(&self.format.fourcc.repr) -// ); - -// Ok(()) -// } - -// /// Set the camera frame rate -// pub fn set_fps(&mut self, fps: u32) -> Result<()> { -// if let Some(params) = self.device.params() { -// let mut params = params.context("Failed to get camera parameters")?; -// params.set_frames_per_second(fps, 1); - -// self.device -// .set_params(¶ms) -// .context("Failed to set frame rate")?; - -// info!("Set camera frame rate: {} fps", fps); -// } else { -// warn!("Camera does not support frame rate adjustment"); -// } - -// Ok(()) -// } - -// /// Set camera exposure mode and value -// pub fn set_exposure(&mut self, mode: ExposureMode) -> Result<()> { -// // First, set auto/manual mode -// let ctrl_id = v4l::control::id::EXPOSURE_AUTO; -// let auto_value = match mode { -// ExposureMode::Auto => 3, // V4L2_EXPOSURE_AUTO -// ExposureMode::Manual(_) => 1, // V4L2_EXPOSURE_MANUAL -// }; - -// self.device -// .set_control(ctrl_id, auto_value) -// .context("Failed to set exposure mode")?; - -// // If manual, set the exposure value -// if let ExposureMode::Manual(exposure_time) = mode { -// // Exposure time in microseconds -// let ctrl_id = v4l::control::id::EXPOSURE_ABSOLUTE; -// self.device -// .set_control(ctrl_id, exposure_time as i64) -// .context("Failed to set exposure time")?; -// } - -// info!("Set camera exposure: {:?}", mode); -// Ok(()) -// } - -// /// Set camera gain (ISO) -// pub fn set_gain(&mut self, gain: u8) -> Result<()> { -// let ctrl_id = v4l::control::id::GAIN; - -// self.device -// .set_control(ctrl_id, gain as i64) -// .context("Failed to set gain")?; - -// info!("Set camera gain: {}", gain); -// Ok(()) -// } - -// /// Lock focus at infinity (if supported) -// pub fn lock_focus_at_infinity(&mut self) -> Result<()> { -// // First, set focus mode to manual -// let auto_focus_id = v4l::control::id::FOCUS_AUTO; -// if let Ok(_) = self.device.set_control(auto_focus_id, 0) { -// // Then set focus to infinity (typically maximum value) -// let focus_id = v4l::control::id::FOCUS_ABSOLUTE; - -// // Get the range of the control -// if let Ok(control) = self.device.control(focus_id) { -// let max_focus = control.maximum(); - -// if let Ok(_) = self.device.set_control(focus_id, max_focus) { -// info!("Locked focus at infinity (value: {})", max_focus); -// return Ok(()); -// } -// } - -// warn!("Failed to set focus to infinity"); -// } else { -// warn!("Camera does not support focus control"); -// } - -// Ok(()) -// } - -// /// Start streaming from the camera -// pub fn start_streaming(&mut self) -> Result { -// let queue = MmapStream::with_buffers(&self.device, Type::VideoCapture, 4) -// .context("Failed to create capture stream")?; - -// self.is_streaming = true; -// info!("Started camera streaming"); - -// Ok(V4l2CaptureStream { -// stream: queue, -// format: self.format.clone(), -// }) -// } - -// /// Stop streaming from the camera -// pub fn stop_streaming(&mut self) -> Result<()> { -// // The streaming will be stopped when the CaptureStream is dropped -// self.is_streaming = false; -// info!("Stopped camera streaming"); -// Ok(()) -// } - -// /// Check if the camera is currently streaming -// pub fn is_streaming(&self) -> bool { -// self.is_streaming -// } - -// /// Get current format width -// pub fn width(&self) -> u32 { -// self.format.width -// } - -// /// Get current format height -// pub fn height(&self) -> u32 { -// self.format.height -// } - -// /// Get current format pixel format -// pub fn pixel_format(&self) -> FourCC { -// self.format.fourcc -// } -// } - -// /// Wrapper around V4L2 capture stream -// pub struct V4l2CaptureStream { -// stream: MmapStream, -// format: Format, -// } - -// impl V4l2CaptureStream { -// /// Capture a single frame from the camera -// pub fn capture_frame(&mut self) -> Result { -// let buffer = self.stream.next() -// .context("Failed to capture frame")?; - -// let width = self.format.width as i32; -// let height = self.format.height as i32; - -// // Convert the buffer to an OpenCV Mat based on the pixel format -// let mat = match self.format.fourcc { -// // MJPEG format -// f if f == FourCC::new(b"MJPG") => { -// // Decode JPEG data -// let data = buffer.data(); -// let vec_data = unsafe { -// std::slice::from_raw_parts(data.as_ptr(), data.len()) -// }.to_vec(); - -// let buf = core::Vector::from_slice(&vec_data); -// let img = opencv::imgcodecs::imdecode(&buf, opencv::imgcodecs::IMREAD_COLOR)?; -// img -// }, - -// // YUYV format -// f if f == FourCC::new(b"YUYV") => { -// let data = buffer.data(); - -// // Create a Mat from the YUYV data -// let mut yuyv = unsafe { -// let bytes_per_pixel = 2; // YUYV is 2 bytes per pixel -// let step = width as usize * bytes_per_pixel; -// core::Mat::new_rows_cols_with_data( -// height, -// width, -// core::CV_8UC2, -// data.as_ptr() as *mut _, -// step, -// )? -// }; - -// // Convert YUYV to BGR -// let mut bgr = core::Mat::default()?; -// convert_color(&yuyv, &mut bgr, imgproc::COLOR_YUV2BGR_YUYV, 0)?; -// bgr -// }, - -// // Unsupported format -// _ => { -// return Err(anyhow!( -// "Unsupported pixel format: {}", -// String::from_utf8_lossy(&self.format.fourcc.repr) -// )); -// } -// }; - -// Ok(mat) -// } -// } - -// impl Drop for V4l2CaptureStream { -// fn drop(&mut self) { -// debug!("V4L2 capture stream dropped"); -// } -// } diff --git a/src/gps/controller.rs b/src/gps/controller.rs index 9232428..37b174e 100644 --- a/src/gps/controller.rs +++ b/src/gps/controller.rs @@ -77,7 +77,7 @@ pub struct GpsController { impl GpsController { /// Create a new GPS controller with the given configuration - pub async fn new(config: &crate::Config) -> Result { + pub async fn new(config: &crate::config::Config) -> Result { // Extract GPS settings from config let gps_config = config.gps.clone(); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..13d9f40 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,20 @@ +// Platform detection +#[cfg(target_os = "linux")] +pub const PLATFORM_SUPPORTS_GPIO: bool = true; + +#[cfg(not(target_os = "linux"))] +pub const PLATFORM_SUPPORTS_GPIO: bool = false; + +// 将所有模块重新导出,使它们对例子可见 +pub mod camera; +pub mod config; +pub mod utils; +pub mod gps; +pub mod sensors; +pub mod detection; +pub mod streaming; +pub mod storage; +pub mod communication; +pub mod monitoring; +pub mod overlay; +pub mod hooks; diff --git a/src/main.rs b/src/main.rs index 843c460..eb49b36 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,3 @@ -// Platform detection -#[cfg(target_os = "linux")] -pub const PLATFORM_SUPPORTS_GPIO: bool = true; - -#[cfg(not(target_os = "linux"))] -pub const PLATFORM_SUPPORTS_GPIO: bool = false; mod camera; mod config; @@ -28,6 +22,7 @@ use tokio::signal; use futures::future::join_all; pub use config::Config; +use meteor_detect::PLATFORM_SUPPORTS_GPIO; use crate::overlay::Watermark; /// Main entry point for the meteor detection system diff --git a/src/sensors/controller.rs b/src/sensors/controller.rs index cea93c1..f103536 100644 --- a/src/sensors/controller.rs +++ b/src/sensors/controller.rs @@ -84,7 +84,7 @@ pub struct SensorController { impl SensorController { /// Create a new sensor controller with the given configuration - pub async fn new(config: &crate::Config) -> Result { + pub async fn new(config: &crate::config::Config) -> Result { // Extract sensor settings from config let sensor_config = config.sensors.clone();