mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-28 16:41:52 +08:00
feat: 添加 RustDesk 协议支持和项目文档
- 新增 RustDesk 模块,支持与 RustDesk 客户端连接 - 实现会合服务器协议和 P2P 连接 - 支持 NaCl 加密和密钥交换 - 添加视频帧和 HID 事件适配器 - 添加 Protobuf 协议定义 (message.proto, rendezvous.proto) - 新增完整项目文档 - 各功能模块文档 (video, hid, msd, otg, webrtc 等) - hwcodec 和 RustDesk 协议技术报告 - 系统架构和技术栈文档 - 更新 Web 前端 RustDesk 配置界面和 API
This commit is contained in:
484
docs/modules/atx.md
Normal file
484
docs/modules/atx.md
Normal file
@@ -0,0 +1,484 @@
|
||||
# ATX 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
ATX 模块提供电源控制功能,通过 GPIO 或 USB 继电器控制目标计算机的电源和重置按钮。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- 电源按钮控制
|
||||
- 重置按钮控制
|
||||
- 电源 LED 状态监视
|
||||
- Wake-on-LAN 支持
|
||||
- 多后端支持 (GPIO/USB 继电器)
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/atx/
|
||||
├── mod.rs # 模块导出
|
||||
├── controller.rs # AtxController (11KB)
|
||||
├── executor.rs # 动作执行器 (10KB)
|
||||
├── types.rs # 类型定义 (7KB)
|
||||
├── led.rs # LED 监视 (5KB)
|
||||
└── wol.rs # Wake-on-LAN (5KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ATX Architecture │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Web API
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ AtxController │
|
||||
│ (controller.rs) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌────────┐ ┌────────┐
|
||||
│ Power │ │ Reset │ │ LED │
|
||||
│Executor│ │Executor│ │Monitor │
|
||||
└───┬────┘ └───┬────┘ └───┬────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌────────┐ ┌────────┐
|
||||
│ GPIO │ │ GPIO │ │ GPIO │
|
||||
│ or USB │ │ or USB │ │ Input │
|
||||
│ Relay │ │ Relay │ │ │
|
||||
└───┬────┘ └───┬────┘ └───┬────┘
|
||||
│ │ │
|
||||
└───────────┼─────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ Target PC │
|
||||
│ (ATX Header) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 AtxController (controller.rs)
|
||||
|
||||
```rust
|
||||
pub struct AtxController {
|
||||
/// 电源按钮配置
|
||||
power: Arc<AtxButton>,
|
||||
|
||||
/// 重置按钮配置
|
||||
reset: Arc<AtxButton>,
|
||||
|
||||
/// LED 监视器
|
||||
led_monitor: Arc<RwLock<Option<LedMonitor>>>,
|
||||
|
||||
/// WoL 控制器
|
||||
wol: Arc<RwLock<Option<WolController>>>,
|
||||
|
||||
/// 当前状态
|
||||
state: Arc<RwLock<AtxState>>,
|
||||
|
||||
/// 事件总线
|
||||
events: Arc<EventBus>,
|
||||
}
|
||||
|
||||
impl AtxController {
|
||||
/// 创建控制器
|
||||
pub fn new(config: &AtxConfig, events: Arc<EventBus>) -> Result<Self>;
|
||||
|
||||
/// 短按电源按钮 (开机/正常关机)
|
||||
pub async fn power_short_press(&self) -> Result<()>;
|
||||
|
||||
/// 长按电源按钮 (强制关机)
|
||||
pub async fn power_long_press(&self) -> Result<()>;
|
||||
|
||||
/// 按重置按钮
|
||||
pub async fn reset_press(&self) -> Result<()>;
|
||||
|
||||
/// 获取电源状态
|
||||
pub fn power_state(&self) -> PowerState;
|
||||
|
||||
/// 发送 WoL 魔术包
|
||||
pub async fn wake_on_lan(&self, mac: &str) -> Result<()>;
|
||||
|
||||
/// 获取状态
|
||||
pub fn state(&self) -> AtxState;
|
||||
|
||||
/// 重新加载配置
|
||||
pub async fn reload(&self, config: &AtxConfig) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct AtxState {
|
||||
/// 是否可用
|
||||
pub available: bool,
|
||||
|
||||
/// 电源是否开启
|
||||
pub power_on: bool,
|
||||
|
||||
/// 最后操作时间
|
||||
pub last_action: Option<DateTime<Utc>>,
|
||||
|
||||
/// 错误信息
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
pub enum PowerState {
|
||||
On,
|
||||
Off,
|
||||
Unknown,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 AtxButton (executor.rs)
|
||||
|
||||
```rust
|
||||
pub struct AtxButton {
|
||||
/// 按钮名称
|
||||
name: String,
|
||||
|
||||
/// 驱动类型
|
||||
driver: AtxDriverType,
|
||||
|
||||
/// GPIO 句柄
|
||||
gpio: Option<LineHandle>,
|
||||
|
||||
/// USB 继电器句柄
|
||||
relay: Option<UsbRelay>,
|
||||
|
||||
/// 配置
|
||||
config: AtxKeyConfig,
|
||||
}
|
||||
|
||||
impl AtxButton {
|
||||
/// 创建按钮
|
||||
pub fn new(name: &str, config: &AtxKeyConfig) -> Result<Self>;
|
||||
|
||||
/// 短按 (100ms)
|
||||
pub async fn short_press(&self) -> Result<()>;
|
||||
|
||||
/// 长按 (3000ms)
|
||||
pub async fn long_press(&self) -> Result<()>;
|
||||
|
||||
/// 自定义按压时间
|
||||
pub async fn press(&self, duration: Duration) -> Result<()>;
|
||||
|
||||
/// 设置输出状态
|
||||
fn set_output(&self, high: bool) -> Result<()>;
|
||||
}
|
||||
|
||||
pub enum AtxDriverType {
|
||||
/// GPIO 直连
|
||||
Gpio,
|
||||
|
||||
/// USB 继电器
|
||||
UsbRelay,
|
||||
|
||||
/// 禁用
|
||||
None,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 LedMonitor (led.rs)
|
||||
|
||||
```rust
|
||||
pub struct LedMonitor {
|
||||
/// GPIO 引脚
|
||||
pin: u32,
|
||||
|
||||
/// GPIO 句柄
|
||||
line: LineHandle,
|
||||
|
||||
/// 当前状态
|
||||
state: Arc<AtomicBool>,
|
||||
|
||||
/// 监视任务
|
||||
monitor_task: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl LedMonitor {
|
||||
/// 创建监视器
|
||||
pub fn new(config: &AtxLedConfig) -> Result<Self>;
|
||||
|
||||
/// 启动监视
|
||||
pub fn start(&mut self, events: Arc<EventBus>) -> Result<()>;
|
||||
|
||||
/// 停止监视
|
||||
pub fn stop(&mut self);
|
||||
|
||||
/// 获取当前状态
|
||||
pub fn state(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 WolController (wol.rs)
|
||||
|
||||
```rust
|
||||
pub struct WolController {
|
||||
/// 网络接口
|
||||
interface: String,
|
||||
|
||||
/// 广播地址
|
||||
broadcast_addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl WolController {
|
||||
/// 创建控制器
|
||||
pub fn new(interface: Option<&str>) -> Result<Self>;
|
||||
|
||||
/// 发送 WoL 魔术包
|
||||
pub async fn wake(&self, mac: &str) -> Result<()>;
|
||||
|
||||
/// 构建魔术包
|
||||
fn build_magic_packet(mac: &[u8; 6]) -> [u8; 102];
|
||||
|
||||
/// 解析 MAC 地址
|
||||
fn parse_mac(mac: &str) -> Result<[u8; 6]>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct AtxConfig {
|
||||
/// 是否启用
|
||||
pub enabled: bool,
|
||||
|
||||
/// 电源按钮配置
|
||||
pub power: AtxKeyConfig,
|
||||
|
||||
/// 重置按钮配置
|
||||
pub reset: AtxKeyConfig,
|
||||
|
||||
/// LED 监视配置
|
||||
pub led: AtxLedConfig,
|
||||
|
||||
/// WoL 配置
|
||||
pub wol: WolConfig,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct AtxKeyConfig {
|
||||
/// 驱动类型
|
||||
pub driver: AtxDriverType,
|
||||
|
||||
/// GPIO 芯片 (如 /dev/gpiochip0)
|
||||
pub gpio_chip: Option<String>,
|
||||
|
||||
/// GPIO 引脚号
|
||||
pub gpio_pin: Option<u32>,
|
||||
|
||||
/// USB 继电器设备
|
||||
pub relay_device: Option<String>,
|
||||
|
||||
/// 继电器通道
|
||||
pub relay_channel: Option<u8>,
|
||||
|
||||
/// 激活电平
|
||||
pub active_level: ActiveLevel,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct AtxLedConfig {
|
||||
/// 是否启用
|
||||
pub enabled: bool,
|
||||
|
||||
/// GPIO 芯片
|
||||
pub gpio_chip: Option<String>,
|
||||
|
||||
/// GPIO 引脚号
|
||||
pub gpio_pin: Option<u32>,
|
||||
|
||||
/// 激活电平
|
||||
pub active_level: ActiveLevel,
|
||||
}
|
||||
|
||||
pub enum ActiveLevel {
|
||||
High,
|
||||
Low,
|
||||
}
|
||||
|
||||
impl Default for AtxConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
power: AtxKeyConfig::default(),
|
||||
reset: AtxKeyConfig::default(),
|
||||
led: AtxLedConfig::default(),
|
||||
wol: WolConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 端点
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/atx/status` | GET | 获取 ATX 状态 |
|
||||
| `/api/atx/power/short` | POST | 短按电源 |
|
||||
| `/api/atx/power/long` | POST | 长按电源 |
|
||||
| `/api/atx/reset` | POST | 按重置 |
|
||||
| `/api/atx/wol` | POST | 发送 WoL |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```json
|
||||
// GET /api/atx/status
|
||||
{
|
||||
"available": true,
|
||||
"power_on": true,
|
||||
"last_action": "2024-01-15T10:30:00Z",
|
||||
"error": null
|
||||
}
|
||||
|
||||
// POST /api/atx/wol
|
||||
// Request: { "mac": "00:11:22:33:44:55" }
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 硬件连接
|
||||
|
||||
### 6.1 GPIO 直连
|
||||
|
||||
```
|
||||
One-KVM Device Target PC
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ GPIO Pin │───────────────│ Power SW │
|
||||
│ (Output) │ │ │
|
||||
└─────────────┘ └─────────────┘
|
||||
|
||||
接线说明:
|
||||
- GPIO 引脚连接到 ATX 电源按钮
|
||||
- 使用光耦或继电器隔离 (推荐)
|
||||
- 注意电平匹配
|
||||
```
|
||||
|
||||
### 6.2 USB 继电器
|
||||
|
||||
```
|
||||
One-KVM Device USB Relay Target PC
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ USB │───────────────│ Relay │──────────│ Power SW │
|
||||
│ │ │ │ │ │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
|
||||
优点:
|
||||
- 完全隔离
|
||||
- 无需担心电平问题
|
||||
- 更安全
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 事件
|
||||
|
||||
```rust
|
||||
pub enum SystemEvent {
|
||||
AtxStateChanged {
|
||||
power_on: bool,
|
||||
last_action: Option<String>,
|
||||
error: Option<String>,
|
||||
},
|
||||
|
||||
AtxActionPerformed {
|
||||
action: String, // "power_short" | "power_long" | "reset" | "wol"
|
||||
success: bool,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 错误处理
|
||||
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AtxError {
|
||||
#[error("ATX not available")]
|
||||
NotAvailable,
|
||||
|
||||
#[error("GPIO error: {0}")]
|
||||
GpioError(String),
|
||||
|
||||
#[error("Relay error: {0}")]
|
||||
RelayError(String),
|
||||
|
||||
#[error("WoL error: {0}")]
|
||||
WolError(String),
|
||||
|
||||
#[error("Invalid MAC address: {0}")]
|
||||
InvalidMac(String),
|
||||
|
||||
#[error("Operation in progress")]
|
||||
Busy,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 使用示例
|
||||
|
||||
```rust
|
||||
let atx = AtxController::new(&config, events)?;
|
||||
|
||||
// 开机
|
||||
atx.power_short_press().await?;
|
||||
|
||||
// 检查状态
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
if atx.power_state() == PowerState::On {
|
||||
println!("PC is now on");
|
||||
}
|
||||
|
||||
// 强制关机
|
||||
atx.power_long_press().await?;
|
||||
|
||||
// 重置
|
||||
atx.reset_press().await?;
|
||||
|
||||
// Wake-on-LAN
|
||||
atx.wake_on_lan("00:11:22:33:44:55").await?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 常见问题
|
||||
|
||||
### Q: GPIO 无法控制?
|
||||
|
||||
1. 检查引脚配置
|
||||
2. 检查权限 (`/dev/gpiochip*`)
|
||||
3. 检查接线
|
||||
|
||||
### Q: LED 状态不正确?
|
||||
|
||||
1. 检查 active_level 配置
|
||||
2. 检查 GPIO 输入模式
|
||||
|
||||
### Q: WoL 不工作?
|
||||
|
||||
1. 检查目标 PC BIOS 设置
|
||||
2. 检查网卡支持
|
||||
3. 检查网络广播
|
||||
463
docs/modules/audio.md
Normal file
463
docs/modules/audio.md
Normal file
@@ -0,0 +1,463 @@
|
||||
# Audio 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
Audio 模块负责音频采集和编码,支持 ALSA 采集和 Opus 编码。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- ALSA 音频采集
|
||||
- Opus 编码
|
||||
- 多质量配置
|
||||
- WebSocket/WebRTC 传输
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/audio/
|
||||
├── mod.rs # 模块导出
|
||||
├── controller.rs # AudioController (15KB)
|
||||
├── capture.rs # ALSA 采集 (12KB)
|
||||
├── encoder.rs # Opus 编码 (8KB)
|
||||
├── shared_pipeline.rs # 共享管道 (15KB)
|
||||
├── monitor.rs # 健康监视 (11KB)
|
||||
└── device.rs # 设备枚举 (8KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Audio Architecture │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
ALSA Device (hw:0,0)
|
||||
│
|
||||
│ PCM 48kHz/16bit/Stereo
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ AudioCapturer │
|
||||
│ (capture.rs) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ SharedAudioPipeline │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ Opus Encoder │ │
|
||||
│ │ 48kHz → 24-96 kbps │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
┌─────────┴─────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ WebSocket │ │ WebRTC │
|
||||
│ Stream │ │ Audio Track │
|
||||
└─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 AudioController (controller.rs)
|
||||
|
||||
```rust
|
||||
pub struct AudioController {
|
||||
/// 采集器
|
||||
capturer: Arc<RwLock<Option<AudioCapturer>>>,
|
||||
|
||||
/// 共享管道
|
||||
pipeline: Arc<SharedAudioPipeline>,
|
||||
|
||||
/// 配置
|
||||
config: Arc<RwLock<AudioConfig>>,
|
||||
|
||||
/// 状态
|
||||
state: Arc<RwLock<AudioState>>,
|
||||
|
||||
/// 事件总线
|
||||
events: Arc<EventBus>,
|
||||
}
|
||||
|
||||
impl AudioController {
|
||||
/// 创建控制器
|
||||
pub fn new(config: &AudioConfig, events: Arc<EventBus>) -> Result<Self>;
|
||||
|
||||
/// 启动音频
|
||||
pub async fn start(&self) -> Result<()>;
|
||||
|
||||
/// 停止音频
|
||||
pub async fn stop(&self) -> Result<()>;
|
||||
|
||||
/// 订阅音频帧
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<AudioFrame>;
|
||||
|
||||
/// 获取状态
|
||||
pub fn status(&self) -> AudioStatus;
|
||||
|
||||
/// 设置质量
|
||||
pub fn set_quality(&self, quality: AudioQuality) -> Result<()>;
|
||||
|
||||
/// 列出设备
|
||||
pub fn list_devices(&self) -> Vec<AudioDeviceInfo>;
|
||||
|
||||
/// 重新加载配置
|
||||
pub async fn reload(&self, config: &AudioConfig) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct AudioStatus {
|
||||
pub enabled: bool,
|
||||
pub streaming: bool,
|
||||
pub device: Option<String>,
|
||||
pub sample_rate: u32,
|
||||
pub channels: u16,
|
||||
pub bitrate: u32,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 AudioCapturer (capture.rs)
|
||||
|
||||
```rust
|
||||
pub struct AudioCapturer {
|
||||
/// PCM 句柄
|
||||
pcm: PCM,
|
||||
|
||||
/// 设备名
|
||||
device: String,
|
||||
|
||||
/// 采样率
|
||||
sample_rate: u32,
|
||||
|
||||
/// 通道数
|
||||
channels: u16,
|
||||
|
||||
/// 帧大小
|
||||
frame_size: usize,
|
||||
|
||||
/// 运行状态
|
||||
running: AtomicBool,
|
||||
}
|
||||
|
||||
impl AudioCapturer {
|
||||
/// 打开设备
|
||||
pub fn open(device: &str, config: &CaptureConfig) -> Result<Self>;
|
||||
|
||||
/// 读取音频帧
|
||||
pub fn read_frame(&self) -> Result<Vec<i16>>;
|
||||
|
||||
/// 启动采集
|
||||
pub fn start(&self) -> Result<()>;
|
||||
|
||||
/// 停止采集
|
||||
pub fn stop(&self);
|
||||
|
||||
/// 是否运行中
|
||||
pub fn is_running(&self) -> bool;
|
||||
}
|
||||
|
||||
pub struct CaptureConfig {
|
||||
pub sample_rate: u32, // 48000
|
||||
pub channels: u16, // 2
|
||||
pub frame_size: usize, // 960 (20ms)
|
||||
pub buffer_size: usize, // 4800
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 OpusEncoder (encoder.rs)
|
||||
|
||||
```rust
|
||||
pub struct OpusEncoder {
|
||||
/// Opus 编码器
|
||||
encoder: audiopus::Encoder,
|
||||
|
||||
/// 采样率
|
||||
sample_rate: u32,
|
||||
|
||||
/// 通道数
|
||||
channels: u16,
|
||||
|
||||
/// 帧大小
|
||||
frame_size: usize,
|
||||
|
||||
/// 码率
|
||||
bitrate: u32,
|
||||
}
|
||||
|
||||
impl OpusEncoder {
|
||||
/// 创建编码器
|
||||
pub fn new(quality: AudioQuality) -> Result<Self>;
|
||||
|
||||
/// 编码 PCM 数据
|
||||
pub fn encode(&mut self, pcm: &[i16]) -> Result<Vec<u8>>;
|
||||
|
||||
/// 设置码率
|
||||
pub fn set_bitrate(&mut self, bitrate: u32) -> Result<()>;
|
||||
|
||||
/// 获取码率
|
||||
pub fn bitrate(&self) -> u32;
|
||||
|
||||
/// 重置编码器
|
||||
pub fn reset(&mut self) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 SharedAudioPipeline (shared_pipeline.rs)
|
||||
|
||||
```rust
|
||||
pub struct SharedAudioPipeline {
|
||||
/// 采集器
|
||||
capturer: Arc<RwLock<Option<AudioCapturer>>>,
|
||||
|
||||
/// 编码器
|
||||
encoder: Arc<Mutex<OpusEncoder>>,
|
||||
|
||||
/// 广播通道
|
||||
tx: broadcast::Sender<AudioFrame>,
|
||||
|
||||
/// 采集任务
|
||||
capture_task: Arc<RwLock<Option<JoinHandle<()>>>>,
|
||||
|
||||
/// 配置
|
||||
config: Arc<RwLock<AudioConfig>>,
|
||||
}
|
||||
|
||||
impl SharedAudioPipeline {
|
||||
/// 创建管道
|
||||
pub fn new(config: &AudioConfig) -> Result<Self>;
|
||||
|
||||
/// 启动管道
|
||||
pub async fn start(&self) -> Result<()>;
|
||||
|
||||
/// 停止管道
|
||||
pub async fn stop(&self) -> Result<()>;
|
||||
|
||||
/// 订阅音频帧
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<AudioFrame>;
|
||||
|
||||
/// 获取统计
|
||||
pub fn stats(&self) -> PipelineStats;
|
||||
}
|
||||
|
||||
pub struct AudioFrame {
|
||||
/// Opus 数据
|
||||
pub data: Bytes,
|
||||
|
||||
/// 时间戳
|
||||
pub timestamp: u64,
|
||||
|
||||
/// 帧序号
|
||||
pub sequence: u64,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 音频质量
|
||||
|
||||
```rust
|
||||
pub enum AudioQuality {
|
||||
/// 24 kbps - 最低带宽
|
||||
VeryLow,
|
||||
|
||||
/// 48 kbps - 低带宽
|
||||
Low,
|
||||
|
||||
/// 64 kbps - 中等
|
||||
Medium,
|
||||
|
||||
/// 96 kbps - 高质量
|
||||
High,
|
||||
}
|
||||
|
||||
impl AudioQuality {
|
||||
pub fn bitrate(&self) -> u32 {
|
||||
match self {
|
||||
Self::VeryLow => 24000,
|
||||
Self::Low => 48000,
|
||||
Self::Medium => 64000,
|
||||
Self::High => 96000,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct AudioConfig {
|
||||
/// 是否启用
|
||||
pub enabled: bool,
|
||||
|
||||
/// 设备名
|
||||
pub device: Option<String>,
|
||||
|
||||
/// 音频质量
|
||||
pub quality: AudioQuality,
|
||||
|
||||
/// 自动启动
|
||||
pub auto_start: bool,
|
||||
}
|
||||
|
||||
impl Default for AudioConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
device: None, // 使用默认设备
|
||||
quality: AudioQuality::Medium,
|
||||
auto_start: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API 端点
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/audio/status` | GET | 获取音频状态 |
|
||||
| `/api/audio/start` | POST | 启动音频 |
|
||||
| `/api/audio/stop` | POST | 停止音频 |
|
||||
| `/api/audio/devices` | GET | 列出设备 |
|
||||
| `/api/audio/quality` | GET | 获取质量 |
|
||||
| `/api/audio/quality` | POST | 设置质量 |
|
||||
| `/api/ws/audio` | WS | 音频流 |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```json
|
||||
// GET /api/audio/status
|
||||
{
|
||||
"enabled": true,
|
||||
"streaming": true,
|
||||
"device": "hw:0,0",
|
||||
"sample_rate": 48000,
|
||||
"channels": 2,
|
||||
"bitrate": 64000,
|
||||
"error": null
|
||||
}
|
||||
|
||||
// GET /api/audio/devices
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"name": "hw:0,0",
|
||||
"description": "USB Audio Device",
|
||||
"is_default": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. WebSocket 音频流
|
||||
|
||||
```javascript
|
||||
// 连接 WebSocket
|
||||
const ws = new WebSocket('/api/ws/audio');
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
// 初始化 Opus 解码器
|
||||
const decoder = new OpusDecoder();
|
||||
|
||||
// 接收音频帧
|
||||
ws.onmessage = (event) => {
|
||||
const frame = new Uint8Array(event.data);
|
||||
const pcm = decoder.decode(frame);
|
||||
audioContext.play(pcm);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 事件
|
||||
|
||||
```rust
|
||||
pub enum SystemEvent {
|
||||
AudioStateChanged {
|
||||
enabled: bool,
|
||||
streaming: bool,
|
||||
device: Option<String>,
|
||||
error: Option<String>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 错误处理
|
||||
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AudioError {
|
||||
#[error("Device not found: {0}")]
|
||||
DeviceNotFound(String),
|
||||
|
||||
#[error("Device busy: {0}")]
|
||||
DeviceBusy(String),
|
||||
|
||||
#[error("ALSA error: {0}")]
|
||||
AlsaError(String),
|
||||
|
||||
#[error("Encoder error: {0}")]
|
||||
EncoderError(String),
|
||||
|
||||
#[error("Not streaming")]
|
||||
NotStreaming,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 使用示例
|
||||
|
||||
```rust
|
||||
let controller = AudioController::new(&config, events)?;
|
||||
|
||||
// 启动音频
|
||||
controller.start().await?;
|
||||
|
||||
// 订阅音频帧
|
||||
let mut rx = controller.subscribe();
|
||||
while let Ok(frame) = rx.recv().await {
|
||||
// 处理 Opus 数据
|
||||
send_to_client(frame.data);
|
||||
}
|
||||
|
||||
// 停止
|
||||
controller.stop().await?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 常见问题
|
||||
|
||||
### Q: 找不到音频设备?
|
||||
|
||||
1. 检查 ALSA 配置
|
||||
2. 运行 `arecord -l`
|
||||
3. 检查权限
|
||||
|
||||
### Q: 音频延迟高?
|
||||
|
||||
1. 减小帧大小
|
||||
2. 降低质量
|
||||
3. 检查网络
|
||||
|
||||
### Q: 音频断断续续?
|
||||
|
||||
1. 增大缓冲区
|
||||
2. 检查 CPU 负载
|
||||
3. 使用更低质量
|
||||
340
docs/modules/auth.md
Normal file
340
docs/modules/auth.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# Auth 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
Auth 模块提供用户认证和会话管理功能。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- 用户管理
|
||||
- 密码哈希 (Argon2)
|
||||
- 会话管理
|
||||
- 认证中间件
|
||||
- 权限控制
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/auth/
|
||||
├── mod.rs # 模块导出
|
||||
├── user.rs # 用户管理 (5KB)
|
||||
├── session.rs # 会话管理 (4KB)
|
||||
├── password.rs # 密码哈希 (1KB)
|
||||
└── middleware.rs # 中间件 (4KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心组件
|
||||
|
||||
### 2.1 UserStore (user.rs)
|
||||
|
||||
```rust
|
||||
pub struct UserStore {
|
||||
db: Pool<Sqlite>,
|
||||
}
|
||||
|
||||
impl UserStore {
|
||||
/// 创建存储
|
||||
pub async fn new(db: Pool<Sqlite>) -> Result<Self>;
|
||||
|
||||
/// 创建用户
|
||||
pub async fn create_user(&self, user: &CreateUser) -> Result<User>;
|
||||
|
||||
/// 获取用户
|
||||
pub async fn get_user(&self, id: &str) -> Result<Option<User>>;
|
||||
|
||||
/// 按用户名获取
|
||||
pub async fn get_by_username(&self, username: &str) -> Result<Option<User>>;
|
||||
|
||||
/// 更新用户
|
||||
pub async fn update_user(&self, id: &str, update: &UpdateUser) -> Result<()>;
|
||||
|
||||
/// 删除用户
|
||||
pub async fn delete_user(&self, id: &str) -> Result<()>;
|
||||
|
||||
/// 列出用户
|
||||
pub async fn list_users(&self) -> Result<Vec<User>>;
|
||||
|
||||
/// 验证密码
|
||||
pub async fn verify_password(&self, username: &str, password: &str) -> Result<Option<User>>;
|
||||
|
||||
/// 更新密码
|
||||
pub async fn update_password(&self, id: &str, new_password: &str) -> Result<()>;
|
||||
|
||||
/// 检查是否需要初始化
|
||||
pub async fn needs_setup(&self) -> Result<bool>;
|
||||
}
|
||||
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub role: UserRole,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub enum UserRole {
|
||||
Admin,
|
||||
User,
|
||||
}
|
||||
|
||||
pub struct CreateUser {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub role: UserRole,
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 SessionStore (session.rs)
|
||||
|
||||
```rust
|
||||
pub struct SessionStore {
|
||||
/// 会话映射
|
||||
sessions: RwLock<HashMap<String, Session>>,
|
||||
|
||||
/// 会话超时
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
impl SessionStore {
|
||||
/// 创建存储
|
||||
pub fn new(timeout: Duration) -> Self;
|
||||
|
||||
/// 创建会话
|
||||
pub fn create_session(&self, user: &User) -> String;
|
||||
|
||||
/// 获取会话
|
||||
pub fn get_session(&self, token: &str) -> Option<Session>;
|
||||
|
||||
/// 删除会话
|
||||
pub fn delete_session(&self, token: &str);
|
||||
|
||||
/// 清理过期会话
|
||||
pub fn cleanup_expired(&self);
|
||||
|
||||
/// 刷新会话
|
||||
pub fn refresh_session(&self, token: &str) -> bool;
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
pub token: String,
|
||||
pub user_id: String,
|
||||
pub username: String,
|
||||
pub role: UserRole,
|
||||
pub created_at: Instant,
|
||||
pub last_active: Instant,
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 密码哈希 (password.rs)
|
||||
|
||||
```rust
|
||||
/// 哈希密码
|
||||
pub fn hash_password(password: &str) -> Result<String> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let argon2 = Argon2::default();
|
||||
let hash = argon2
|
||||
.hash_password(password.as_bytes(), &salt)?
|
||||
.to_string();
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
/// 验证密码
|
||||
pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
|
||||
let parsed_hash = PasswordHash::new(hash)?;
|
||||
Ok(Argon2::default()
|
||||
.verify_password(password.as_bytes(), &parsed_hash)
|
||||
.is_ok())
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 认证中间件 (middleware.rs)
|
||||
|
||||
```rust
|
||||
pub async fn auth_middleware(
|
||||
State(state): State<Arc<AppState>>,
|
||||
cookies: Cookies,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
// 获取 session token
|
||||
let token = cookies
|
||||
.get("session_id")
|
||||
.map(|c| c.value().to_string());
|
||||
|
||||
// 验证会话
|
||||
let session = token
|
||||
.and_then(|t| state.sessions.get_session(&t));
|
||||
|
||||
if let Some(session) = session {
|
||||
// 将用户信息注入请求
|
||||
let mut request = request;
|
||||
request.extensions_mut().insert(session);
|
||||
next.run(request).await
|
||||
} else {
|
||||
StatusCode::UNAUTHORIZED.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn admin_middleware(
|
||||
session: Extension<Session>,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
if session.role == UserRole::Admin {
|
||||
next.run(request).await
|
||||
} else {
|
||||
StatusCode::FORBIDDEN.into_response()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. API 端点
|
||||
|
||||
| 端点 | 方法 | 权限 | 描述 |
|
||||
|------|------|------|------|
|
||||
| `/api/auth/login` | POST | Public | 登录 |
|
||||
| `/api/auth/logout` | POST | User | 登出 |
|
||||
| `/api/auth/check` | GET | User | 检查认证 |
|
||||
| `/api/auth/password` | POST | User | 修改密码 |
|
||||
| `/api/users` | GET | Admin | 列出用户 |
|
||||
| `/api/users` | POST | Admin | 创建用户 |
|
||||
| `/api/users/:id` | DELETE | Admin | 删除用户 |
|
||||
| `/api/setup/init` | POST | Public | 初始化设置 |
|
||||
|
||||
### 请求/响应格式
|
||||
|
||||
```json
|
||||
// POST /api/auth/login
|
||||
// Request:
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "password123"
|
||||
}
|
||||
|
||||
// Response:
|
||||
{
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"username": "admin",
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/auth/check
|
||||
{
|
||||
"authenticated": true,
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"username": "admin",
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct AuthConfig {
|
||||
/// 会话超时 (秒)
|
||||
pub session_timeout_secs: u64,
|
||||
|
||||
/// 是否启用认证
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for AuthConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
session_timeout_secs: 86400, // 24 小时
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 安全特性
|
||||
|
||||
### 5.1 密码存储
|
||||
|
||||
- Argon2id 哈希
|
||||
- 随机盐值
|
||||
- 不可逆
|
||||
|
||||
### 5.2 会话安全
|
||||
|
||||
- 随机 token (UUID v4)
|
||||
- HTTPOnly Cookie
|
||||
- 会话超时
|
||||
- 自动清理
|
||||
|
||||
### 5.3 权限控制
|
||||
|
||||
- 两级权限: Admin / User
|
||||
- 中间件检查
|
||||
- 敏感操作需 Admin
|
||||
|
||||
---
|
||||
|
||||
## 6. 使用示例
|
||||
|
||||
```rust
|
||||
// 创建用户
|
||||
let user = users.create_user(&CreateUser {
|
||||
username: "admin".to_string(),
|
||||
password: "password123".to_string(),
|
||||
role: UserRole::Admin,
|
||||
}).await?;
|
||||
|
||||
// 验证密码
|
||||
if let Some(user) = users.verify_password("admin", "password123").await? {
|
||||
// 创建会话
|
||||
let token = sessions.create_session(&user);
|
||||
|
||||
// 设置 Cookie
|
||||
cookies.add(Cookie::build("session_id", token)
|
||||
.http_only(true)
|
||||
.path("/")
|
||||
.finish());
|
||||
}
|
||||
|
||||
// 获取会话
|
||||
if let Some(session) = sessions.get_session(&token) {
|
||||
println!("User: {}", session.username);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AuthError {
|
||||
#[error("Invalid credentials")]
|
||||
InvalidCredentials,
|
||||
|
||||
#[error("User not found")]
|
||||
UserNotFound,
|
||||
|
||||
#[error("User already exists")]
|
||||
UserExists,
|
||||
|
||||
#[error("Session expired")]
|
||||
SessionExpired,
|
||||
|
||||
#[error("Permission denied")]
|
||||
PermissionDenied,
|
||||
|
||||
#[error("Setup required")]
|
||||
SetupRequired,
|
||||
}
|
||||
```
|
||||
297
docs/modules/config.md
Normal file
297
docs/modules/config.md
Normal file
@@ -0,0 +1,297 @@
|
||||
# Config 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
Config 模块提供配置管理功能,所有配置存储在 SQLite 数据库中。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- SQLite 配置存储
|
||||
- 类型安全的配置结构
|
||||
- 热重载支持
|
||||
- TypeScript 类型生成
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/config/
|
||||
├── mod.rs # 模块导出
|
||||
├── schema.rs # 配置结构定义 (12KB)
|
||||
└── store.rs # SQLite 存储 (8KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心组件
|
||||
|
||||
### 2.1 ConfigStore (store.rs)
|
||||
|
||||
```rust
|
||||
pub struct ConfigStore {
|
||||
db: Pool<Sqlite>,
|
||||
}
|
||||
|
||||
impl ConfigStore {
|
||||
/// 创建存储
|
||||
pub async fn new(db_path: &Path) -> Result<Self>;
|
||||
|
||||
/// 获取完整配置
|
||||
pub async fn get_config(&self) -> Result<AppConfig>;
|
||||
|
||||
/// 更新配置
|
||||
pub async fn update_config(&self, config: &AppConfig) -> Result<()>;
|
||||
|
||||
/// 获取单个配置项
|
||||
pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>>;
|
||||
|
||||
/// 设置单个配置项
|
||||
pub async fn set<T: Serialize>(&self, key: &str, value: &T) -> Result<()>;
|
||||
|
||||
/// 删除配置项
|
||||
pub async fn delete(&self, key: &str) -> Result<()>;
|
||||
|
||||
/// 重置为默认
|
||||
pub async fn reset_to_default(&self) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 AppConfig (schema.rs)
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
#[typeshare]
|
||||
pub struct AppConfig {
|
||||
/// 视频配置
|
||||
pub video: VideoConfig,
|
||||
|
||||
/// 流配置
|
||||
pub stream: StreamConfig,
|
||||
|
||||
/// HID 配置
|
||||
pub hid: HidConfig,
|
||||
|
||||
/// MSD 配置
|
||||
pub msd: MsdConfig,
|
||||
|
||||
/// ATX 配置
|
||||
pub atx: AtxConfig,
|
||||
|
||||
/// 音频配置
|
||||
pub audio: AudioConfig,
|
||||
|
||||
/// 认证配置
|
||||
pub auth: AuthConfig,
|
||||
|
||||
/// Web 配置
|
||||
pub web: WebConfig,
|
||||
|
||||
/// RustDesk 配置
|
||||
pub rustdesk: RustDeskConfig,
|
||||
|
||||
/// 扩展配置
|
||||
pub extensions: ExtensionsConfig,
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 各模块配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct VideoConfig {
|
||||
pub device: Option<String>,
|
||||
pub format: Option<String>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub fps: u32,
|
||||
pub quality: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct StreamConfig {
|
||||
pub mode: StreamMode,
|
||||
pub bitrate_kbps: u32,
|
||||
pub gop_size: u32,
|
||||
pub encoder: EncoderType,
|
||||
pub stun_server: Option<String>,
|
||||
pub turn_server: Option<String>,
|
||||
pub turn_username: Option<String>,
|
||||
pub turn_password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct HidConfig {
|
||||
pub backend: HidBackendType,
|
||||
pub ch9329_device: Option<String>,
|
||||
pub ch9329_baud_rate: Option<u32>,
|
||||
pub default_mouse_mode: MouseMode,
|
||||
}
|
||||
|
||||
// ... 其他配置结构
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. TypeScript 类型生成
|
||||
|
||||
使用 `#[typeshare]` 属性自动生成 TypeScript 类型:
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct VideoConfig {
|
||||
pub device: Option<String>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
```
|
||||
|
||||
生成的 TypeScript:
|
||||
|
||||
```typescript
|
||||
export interface VideoConfig {
|
||||
device?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
```
|
||||
|
||||
生成命令:
|
||||
|
||||
```bash
|
||||
./scripts/generate-types.sh
|
||||
# 或
|
||||
typeshare src --lang=typescript --output-file=web/src/types/generated.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. API 端点
|
||||
|
||||
| 端点 | 方法 | 权限 | 描述 |
|
||||
|------|------|------|------|
|
||||
| `/api/config` | GET | Admin | 获取完整配置 |
|
||||
| `/api/config` | PATCH | Admin | 更新配置 |
|
||||
| `/api/config/video` | GET | Admin | 获取视频配置 |
|
||||
| `/api/config/video` | PATCH | Admin | 更新视频配置 |
|
||||
| `/api/config/stream` | GET | Admin | 获取流配置 |
|
||||
| `/api/config/stream` | PATCH | Admin | 更新流配置 |
|
||||
| `/api/config/hid` | GET | Admin | 获取 HID 配置 |
|
||||
| `/api/config/hid` | PATCH | Admin | 更新 HID 配置 |
|
||||
| `/api/config/reset` | POST | Admin | 重置为默认 |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```json
|
||||
// GET /api/config/video
|
||||
{
|
||||
"device": "/dev/video0",
|
||||
"format": "MJPEG",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"fps": 30,
|
||||
"quality": 80
|
||||
}
|
||||
|
||||
// PATCH /api/config/video
|
||||
// Request:
|
||||
{
|
||||
"width": 1280,
|
||||
"height": 720
|
||||
}
|
||||
|
||||
// Response: 更新后的完整配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置热重载
|
||||
|
||||
配置更改后自动重载相关组件:
|
||||
|
||||
```rust
|
||||
// 更新配置
|
||||
config_store.update_config(&new_config).await?;
|
||||
|
||||
// 发布配置变更事件
|
||||
events.publish(SystemEvent::ConfigChanged {
|
||||
section: "video".to_string(),
|
||||
});
|
||||
|
||||
// 各组件监听事件并重载
|
||||
// VideoStreamManager::on_config_changed()
|
||||
// HidController::reload()
|
||||
// etc.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据库结构
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
配置以 JSON 格式存储:
|
||||
|
||||
```
|
||||
key: "app_config"
|
||||
value: { "video": {...}, "hid": {...}, ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 使用示例
|
||||
|
||||
```rust
|
||||
// 获取配置
|
||||
let config = config_store.get_config().await?;
|
||||
println!("Video device: {:?}", config.video.device);
|
||||
|
||||
// 更新配置
|
||||
let mut config = config_store.get_config().await?;
|
||||
config.video.width = 1280;
|
||||
config.video.height = 720;
|
||||
config_store.update_config(&config).await?;
|
||||
|
||||
// 获取单个配置项
|
||||
let video: Option<VideoConfig> = config_store.get("video").await?;
|
||||
|
||||
// 设置单个配置项
|
||||
config_store.set("video", &video_config).await?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 默认配置
|
||||
|
||||
```rust
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
video: VideoConfig {
|
||||
device: None,
|
||||
format: None,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
fps: 30,
|
||||
quality: 80,
|
||||
},
|
||||
stream: StreamConfig {
|
||||
mode: StreamMode::Mjpeg,
|
||||
bitrate_kbps: 2000,
|
||||
gop_size: 60,
|
||||
encoder: EncoderType::H264,
|
||||
..Default::default()
|
||||
},
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
353
docs/modules/events.md
Normal file
353
docs/modules/events.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# Events 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
Events 模块提供事件总线功能,用于模块间通信和状态广播。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- 事件发布/订阅
|
||||
- 多订阅者广播
|
||||
- WebSocket 事件推送
|
||||
- 状态变更通知
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/events/
|
||||
└── mod.rs # EventBus 实现
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Event System │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
|
||||
│ Video │ │ HID │ │ Audio │
|
||||
│ Module │ │ Module │ │ Module │
|
||||
└───────┬────────┘ └───────┬────────┘ └───────┬────────┘
|
||||
│ │ │
|
||||
│ publish() │ publish() │ publish()
|
||||
└──────────────────┼──────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ EventBus │
|
||||
│ (broadcast channel) │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌─────────────────┼─────────────────┐
|
||||
│ │ │
|
||||
│ subscribe() │ subscribe() │
|
||||
▼ ▼ ▼
|
||||
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
|
||||
│ WebSocket │ │ DeviceInfo │ │ Internal │
|
||||
│ Handler │ │ Broadcaster │ │ Tasks │
|
||||
└────────────────┘ └────────────────┘ └────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 EventBus
|
||||
|
||||
```rust
|
||||
pub struct EventBus {
|
||||
/// 广播发送器
|
||||
tx: broadcast::Sender<SystemEvent>,
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
/// 创建事件总线
|
||||
pub fn new() -> Self {
|
||||
let (tx, _) = broadcast::channel(1024);
|
||||
Self { tx }
|
||||
}
|
||||
|
||||
/// 发布事件
|
||||
pub fn publish(&self, event: SystemEvent) {
|
||||
let _ = self.tx.send(event);
|
||||
}
|
||||
|
||||
/// 订阅事件
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<SystemEvent> {
|
||||
self.tx.subscribe()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 SystemEvent
|
||||
|
||||
```rust
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub enum SystemEvent {
|
||||
// 视频事件
|
||||
StreamStateChanged {
|
||||
state: String,
|
||||
device: Option<String>,
|
||||
resolution: Option<Resolution>,
|
||||
fps: Option<f32>,
|
||||
},
|
||||
|
||||
VideoDeviceChanged {
|
||||
added: Vec<String>,
|
||||
removed: Vec<String>,
|
||||
},
|
||||
|
||||
// HID 事件
|
||||
HidStateChanged {
|
||||
backend: String,
|
||||
initialized: bool,
|
||||
keyboard_connected: bool,
|
||||
mouse_connected: bool,
|
||||
mouse_mode: String,
|
||||
error: Option<String>,
|
||||
},
|
||||
|
||||
// MSD 事件
|
||||
MsdStateChanged {
|
||||
mode: String,
|
||||
connected: bool,
|
||||
image: Option<String>,
|
||||
error: Option<String>,
|
||||
},
|
||||
|
||||
MsdDownloadProgress {
|
||||
download_id: String,
|
||||
downloaded: u64,
|
||||
total: u64,
|
||||
speed: u64,
|
||||
},
|
||||
|
||||
// ATX 事件
|
||||
AtxStateChanged {
|
||||
power_on: bool,
|
||||
last_action: Option<String>,
|
||||
error: Option<String>,
|
||||
},
|
||||
|
||||
// 音频事件
|
||||
AudioStateChanged {
|
||||
enabled: bool,
|
||||
streaming: bool,
|
||||
device: Option<String>,
|
||||
error: Option<String>,
|
||||
},
|
||||
|
||||
// 配置事件
|
||||
ConfigChanged {
|
||||
section: String,
|
||||
},
|
||||
|
||||
// 设备信息汇总
|
||||
DeviceInfo {
|
||||
video: VideoInfo,
|
||||
hid: HidInfo,
|
||||
msd: MsdInfo,
|
||||
atx: AtxInfo,
|
||||
audio: AudioInfo,
|
||||
},
|
||||
|
||||
// 系统错误
|
||||
SystemError {
|
||||
module: String,
|
||||
severity: String,
|
||||
message: String,
|
||||
},
|
||||
|
||||
// RustDesk 事件
|
||||
RustDeskStatusChanged {
|
||||
status: String,
|
||||
device_id: Option<String>,
|
||||
error: Option<String>,
|
||||
},
|
||||
|
||||
RustDeskConnectionOpened {
|
||||
connection_id: String,
|
||||
peer_id: String,
|
||||
},
|
||||
|
||||
RustDeskConnectionClosed {
|
||||
connection_id: String,
|
||||
peer_id: String,
|
||||
reason: String,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 设备信息广播器
|
||||
|
||||
在 `main.rs` 中启动的后台任务:
|
||||
|
||||
```rust
|
||||
pub fn spawn_device_info_broadcaster(
|
||||
state: Arc<AppState>,
|
||||
events: Arc<EventBus>,
|
||||
) -> JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
let mut rx = events.subscribe();
|
||||
let mut debounce = tokio::time::interval(Duration::from_millis(100));
|
||||
let mut pending = false;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// 收到事件
|
||||
result = rx.recv() => {
|
||||
if result.is_ok() {
|
||||
pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 防抖定时器
|
||||
_ = debounce.tick() => {
|
||||
if pending {
|
||||
pending = false;
|
||||
// 收集设备信息
|
||||
let device_info = state.get_device_info().await;
|
||||
// 广播
|
||||
events.publish(SystemEvent::DeviceInfo(device_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. WebSocket 事件推送
|
||||
|
||||
```rust
|
||||
pub async fn ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(|socket| handle_ws(socket, state))
|
||||
}
|
||||
|
||||
async fn handle_ws(mut socket: WebSocket, state: Arc<AppState>) {
|
||||
let mut rx = state.events.subscribe();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// 发送事件给客户端
|
||||
result = rx.recv() => {
|
||||
if let Ok(event) = result {
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
if socket.send(Message::Text(json)).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 接收客户端消息
|
||||
msg = socket.recv() => {
|
||||
match msg {
|
||||
Some(Ok(Message::Close(_))) | None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 使用示例
|
||||
|
||||
### 6.1 发布事件
|
||||
|
||||
```rust
|
||||
// 视频模块发布状态变更
|
||||
events.publish(SystemEvent::StreamStateChanged {
|
||||
state: "streaming".to_string(),
|
||||
device: Some("/dev/video0".to_string()),
|
||||
resolution: Some(Resolution { width: 1920, height: 1080 }),
|
||||
fps: Some(30.0),
|
||||
});
|
||||
|
||||
// HID 模块发布状态变更
|
||||
events.publish(SystemEvent::HidStateChanged {
|
||||
backend: "otg".to_string(),
|
||||
initialized: true,
|
||||
keyboard_connected: true,
|
||||
mouse_connected: true,
|
||||
mouse_mode: "absolute".to_string(),
|
||||
error: None,
|
||||
});
|
||||
```
|
||||
|
||||
### 6.2 订阅事件
|
||||
|
||||
```rust
|
||||
let mut rx = events.subscribe();
|
||||
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(SystemEvent::StreamStateChanged { state, .. }) => {
|
||||
println!("Stream state: {}", state);
|
||||
}
|
||||
Ok(SystemEvent::HidStateChanged { backend, .. }) => {
|
||||
println!("HID backend: {}", backend);
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 前端事件处理
|
||||
|
||||
```typescript
|
||||
// 连接 WebSocket
|
||||
const ws = new WebSocket('/api/ws');
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
switch (data.type) {
|
||||
case 'StreamStateChanged':
|
||||
updateStreamStatus(data);
|
||||
break;
|
||||
case 'HidStateChanged':
|
||||
updateHidStatus(data);
|
||||
break;
|
||||
case 'MsdStateChanged':
|
||||
updateMsdStatus(data);
|
||||
break;
|
||||
case 'DeviceInfo':
|
||||
updateAllDevices(data);
|
||||
break;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 最佳实践
|
||||
|
||||
### 8.1 事件粒度
|
||||
|
||||
- 使用细粒度事件便于精确更新
|
||||
- DeviceInfo 用于初始化和定期同步
|
||||
|
||||
### 8.2 防抖
|
||||
|
||||
- 使用 100ms 防抖避免事件风暴
|
||||
- 合并多个快速变更
|
||||
|
||||
### 8.3 错误处理
|
||||
|
||||
- 发布失败静默忽略 (fire-and-forget)
|
||||
- 订阅者断开自动清理
|
||||
850
docs/modules/hid.md
Normal file
850
docs/modules/hid.md
Normal file
@@ -0,0 +1,850 @@
|
||||
# HID 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
HID (Human Interface Device) 模块负责将键盘和鼠标事件转发到目标计算机,是 One-KVM 实现远程控制的核心模块。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- 键盘事件处理 (按键、修饰键)
|
||||
- 鼠标事件处理 (移动、点击、滚轮)
|
||||
- 支持绝对和相对鼠标模式
|
||||
- 多后端支持 (OTG、CH9329)
|
||||
- WebSocket 和 DataChannel 输入
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/hid/
|
||||
├── mod.rs # HidController (16KB)
|
||||
├── backend.rs # 后端抽象
|
||||
├── otg.rs # OTG 后端 (33KB)
|
||||
├── ch9329.rs # CH9329 串口后端 (46KB)
|
||||
├── keymap.rs # 按键映射 (14KB)
|
||||
├── types.rs # 类型定义
|
||||
├── monitor.rs # 健康监视 (14KB)
|
||||
├── datachannel.rs # DataChannel 适配 (8KB)
|
||||
└── websocket.rs # WebSocket 适配 (6KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ HID Architecture │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Browser Input Events
|
||||
│
|
||||
┌─────────┴─────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ WebSocket │ │ DataChannel │
|
||||
│ Handler │ │ Handler │
|
||||
│ (websocket.rs) │ │(datachannel.rs) │
|
||||
└────────┬────────┘ └────────┬────────┘
|
||||
│ │
|
||||
└──────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ HidController │
|
||||
│ (mod.rs) │
|
||||
│ - send_keyboard() │
|
||||
│ - send_mouse() │
|
||||
│ - select_backend() │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌──────────┼──────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ OTG Backend│ │ CH9329 │ │ None │
|
||||
│ (otg.rs) │ │ Backend │ │ (dummy) │
|
||||
└──────┬──────┘ └────┬─────┘ └──────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────┐ ┌──────────┐
|
||||
│ /dev/hidg* │ │ Serial │
|
||||
│ USB Gadget │ │ Port │
|
||||
└─────────────┘ └──────────┘
|
||||
│ │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Target PC │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### 2.2 后端选择
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Backend Selection │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
HidBackendType::Otg
|
||||
│
|
||||
├── 检查 OtgService 是否可用
|
||||
│
|
||||
├── 请求 HID 函数 (3个设备)
|
||||
│ ├── /dev/hidg0 (键盘)
|
||||
│ ├── /dev/hidg1 (相对鼠标)
|
||||
│ └── /dev/hidg2 (绝对鼠标)
|
||||
│
|
||||
└── 创建 OtgHidBackend
|
||||
|
||||
HidBackendType::Ch9329 { port, baud_rate }
|
||||
│
|
||||
├── 打开串口设备
|
||||
│
|
||||
├── 初始化 CH9329 芯片
|
||||
│
|
||||
└── 创建 Ch9329HidBackend
|
||||
|
||||
HidBackendType::None
|
||||
│
|
||||
└── 创建空后端 (丢弃所有事件)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 HidController (mod.rs)
|
||||
|
||||
HID 控制器主类,统一管理所有 HID 操作。
|
||||
|
||||
```rust
|
||||
pub struct HidController {
|
||||
/// 当前后端
|
||||
backend: Arc<RwLock<Box<dyn HidBackend>>>,
|
||||
|
||||
/// 后端类型
|
||||
backend_type: Arc<RwLock<HidBackendType>>,
|
||||
|
||||
/// OTG 服务引用
|
||||
otg_service: Arc<OtgService>,
|
||||
|
||||
/// 健康监视器
|
||||
monitor: Arc<HidHealthMonitor>,
|
||||
|
||||
/// 配置
|
||||
config: Arc<RwLock<HidConfig>>,
|
||||
|
||||
/// 事件总线
|
||||
events: Arc<EventBus>,
|
||||
|
||||
/// 鼠标模式
|
||||
mouse_mode: Arc<RwLock<MouseMode>>,
|
||||
}
|
||||
|
||||
impl HidController {
|
||||
/// 初始化控制器
|
||||
pub async fn init(
|
||||
otg_service: Arc<OtgService>,
|
||||
config: &HidConfig,
|
||||
events: Arc<EventBus>,
|
||||
) -> Result<Arc<Self>>;
|
||||
|
||||
/// 发送键盘事件
|
||||
pub async fn send_keyboard(&self, event: &KeyboardEvent) -> Result<()>;
|
||||
|
||||
/// 发送鼠标事件
|
||||
pub async fn send_mouse(&self, event: &MouseEvent) -> Result<()>;
|
||||
|
||||
/// 设置鼠标模式
|
||||
pub fn set_mouse_mode(&self, mode: MouseMode);
|
||||
|
||||
/// 获取鼠标模式
|
||||
pub fn get_mouse_mode(&self) -> MouseMode;
|
||||
|
||||
/// 重新加载配置
|
||||
pub async fn reload(&self, config: &HidConfig) -> Result<()>;
|
||||
|
||||
/// 重置 HID 状态
|
||||
pub async fn reset(&self) -> Result<()>;
|
||||
|
||||
/// 获取状态信息
|
||||
pub fn info(&self) -> HidInfo;
|
||||
}
|
||||
|
||||
pub struct HidInfo {
|
||||
pub backend: String,
|
||||
pub initialized: bool,
|
||||
pub keyboard_connected: bool,
|
||||
pub mouse_connected: bool,
|
||||
pub mouse_mode: MouseMode,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 HidBackend Trait (backend.rs)
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait HidBackend: Send + Sync {
|
||||
/// 发送键盘事件
|
||||
async fn send_keyboard(&self, event: &KeyboardEvent) -> Result<()>;
|
||||
|
||||
/// 发送鼠标事件
|
||||
async fn send_mouse(&self, event: &MouseEvent, mode: MouseMode) -> Result<()>;
|
||||
|
||||
/// 重置状态
|
||||
async fn reset(&self) -> Result<()>;
|
||||
|
||||
/// 获取后端信息
|
||||
fn info(&self) -> HidBackendInfo;
|
||||
|
||||
/// 检查连接状态
|
||||
fn is_connected(&self) -> bool;
|
||||
}
|
||||
|
||||
pub struct HidBackendInfo {
|
||||
pub name: String,
|
||||
pub backend_type: HidBackendType,
|
||||
pub keyboard_connected: bool,
|
||||
pub mouse_connected: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HidBackendType {
|
||||
/// USB OTG gadget 模式
|
||||
Otg,
|
||||
|
||||
/// CH9329 串口 HID 控制器
|
||||
Ch9329 {
|
||||
port: String,
|
||||
baud_rate: u32,
|
||||
},
|
||||
|
||||
/// 禁用 HID
|
||||
None,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 OTG 后端 (otg.rs)
|
||||
|
||||
通过 Linux USB OTG gadget 模拟 HID 设备。
|
||||
|
||||
```rust
|
||||
pub struct OtgHidBackend {
|
||||
/// HID 设备路径
|
||||
paths: HidDevicePaths,
|
||||
|
||||
/// 键盘设备文件
|
||||
keyboard_fd: RwLock<Option<File>>,
|
||||
|
||||
/// 相对鼠标设备文件
|
||||
mouse_rel_fd: RwLock<Option<File>>,
|
||||
|
||||
/// 绝对鼠标设备文件
|
||||
mouse_abs_fd: RwLock<Option<File>>,
|
||||
|
||||
/// 当前键盘状态
|
||||
keyboard_state: Mutex<KeyboardState>,
|
||||
|
||||
/// OTG 服务引用
|
||||
otg_service: Arc<OtgService>,
|
||||
}
|
||||
|
||||
impl OtgHidBackend {
|
||||
/// 创建 OTG 后端
|
||||
pub async fn new(otg_service: Arc<OtgService>) -> Result<Self>;
|
||||
|
||||
/// 打开 HID 设备
|
||||
async fn open_devices(&self) -> Result<()>;
|
||||
|
||||
/// 关闭 HID 设备
|
||||
async fn close_devices(&self);
|
||||
|
||||
/// 写入键盘报告
|
||||
fn write_keyboard_report(&self, report: &KeyboardReport) -> Result<()>;
|
||||
|
||||
/// 写入鼠标报告
|
||||
fn write_mouse_report(&self, report: &[u8], absolute: bool) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct HidDevicePaths {
|
||||
pub keyboard: PathBuf, // /dev/hidg0
|
||||
pub mouse_relative: PathBuf, // /dev/hidg1
|
||||
pub mouse_absolute: PathBuf, // /dev/hidg2
|
||||
}
|
||||
```
|
||||
|
||||
#### HID 报告格式
|
||||
|
||||
```rust
|
||||
/// 键盘报告 (8 字节)
|
||||
#[repr(C, packed)]
|
||||
pub struct KeyboardReport {
|
||||
pub modifiers: u8, // Ctrl, Shift, Alt, GUI
|
||||
pub reserved: u8, // 保留
|
||||
pub keys: [u8; 6], // 最多 6 个按键 scancode
|
||||
}
|
||||
|
||||
/// 相对鼠标报告 (4 字节)
|
||||
#[repr(C, packed)]
|
||||
pub struct MouseRelativeReport {
|
||||
pub buttons: u8, // 按钮状态
|
||||
pub x: i8, // X 移动 (-127 ~ 127)
|
||||
pub y: i8, // Y 移动 (-127 ~ 127)
|
||||
pub wheel: i8, // 滚轮 (-127 ~ 127)
|
||||
}
|
||||
|
||||
/// 绝对鼠标报告 (6 字节)
|
||||
#[repr(C, packed)]
|
||||
pub struct MouseAbsoluteReport {
|
||||
pub buttons: u8, // 按钮状态
|
||||
pub x: u16, // X 坐标 (0 ~ 32767)
|
||||
pub y: u16, // Y 坐标 (0 ~ 32767)
|
||||
pub wheel: i8, // 滚轮 (-127 ~ 127)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 CH9329 后端 (ch9329.rs)
|
||||
|
||||
通过 CH9329 芯片(串口转 HID)实现 HID 功能。
|
||||
|
||||
```rust
|
||||
pub struct Ch9329HidBackend {
|
||||
/// 串口设备
|
||||
port: Mutex<Box<dyn SerialPort>>,
|
||||
|
||||
/// 设备路径
|
||||
device_path: String,
|
||||
|
||||
/// 波特率
|
||||
baud_rate: u32,
|
||||
|
||||
/// 当前键盘状态
|
||||
keyboard_state: Mutex<KeyboardState>,
|
||||
|
||||
/// 连接状态
|
||||
connected: AtomicBool,
|
||||
}
|
||||
|
||||
impl Ch9329HidBackend {
|
||||
/// 创建 CH9329 后端
|
||||
pub fn new(device: &str, baud_rate: u32) -> Result<Self>;
|
||||
|
||||
/// 发送命令
|
||||
fn send_command(&self, cmd: &[u8]) -> Result<Vec<u8>>;
|
||||
|
||||
/// 发送键盘数据包
|
||||
fn send_keyboard_packet(&self, report: &KeyboardReport) -> Result<()>;
|
||||
|
||||
/// 发送鼠标数据包
|
||||
fn send_mouse_packet(&self, report: &[u8], absolute: bool) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
#### CH9329 协议
|
||||
|
||||
```
|
||||
帧格式:
|
||||
┌──────┬──────┬──────┬──────────┬──────────┬──────┐
|
||||
│ HEAD │ ADDR │ CMD │ LEN │ DATA │ SUM │
|
||||
│ 0x57 │ 0xAB │ 0xXX │ data_len │ payload │ csum │
|
||||
└──────┴──────┴──────┴──────────┴──────────┴──────┘
|
||||
|
||||
命令码:
|
||||
0x02 - 发送键盘数据
|
||||
0x04 - 发送绝对鼠标数据
|
||||
0x05 - 发送相对鼠标数据
|
||||
0x0E - 获取芯片信息
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 事件类型
|
||||
|
||||
### 4.1 键盘事件 (types.rs)
|
||||
|
||||
```rust
|
||||
pub struct KeyboardEvent {
|
||||
/// 按下的键列表
|
||||
pub keys: Vec<KeyCode>,
|
||||
|
||||
/// 修饰键状态
|
||||
pub modifiers: KeyboardModifiers,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct KeyboardModifiers {
|
||||
pub left_ctrl: bool,
|
||||
pub left_shift: bool,
|
||||
pub left_alt: bool,
|
||||
pub left_gui: bool,
|
||||
pub right_ctrl: bool,
|
||||
pub right_shift: bool,
|
||||
pub right_alt: bool,
|
||||
pub right_gui: bool,
|
||||
}
|
||||
|
||||
impl KeyboardModifiers {
|
||||
/// 转换为 USB HID 修饰符字节
|
||||
pub fn to_byte(&self) -> u8 {
|
||||
let mut byte = 0u8;
|
||||
if self.left_ctrl { byte |= 0x01; }
|
||||
if self.left_shift { byte |= 0x02; }
|
||||
if self.left_alt { byte |= 0x04; }
|
||||
if self.left_gui { byte |= 0x08; }
|
||||
if self.right_ctrl { byte |= 0x10; }
|
||||
if self.right_shift { byte |= 0x20; }
|
||||
if self.right_alt { byte |= 0x40; }
|
||||
if self.right_gui { byte |= 0x80; }
|
||||
byte
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 鼠标事件 (types.rs)
|
||||
|
||||
```rust
|
||||
pub struct MouseEvent {
|
||||
/// 按钮
|
||||
pub button: Option<MouseButton>,
|
||||
|
||||
/// 事件类型
|
||||
pub event_type: MouseEventType,
|
||||
|
||||
/// 相对移动 X
|
||||
pub dx: i16,
|
||||
|
||||
/// 相对移动 Y
|
||||
pub dy: i16,
|
||||
|
||||
/// 绝对位置 X (0-32767)
|
||||
pub x: u32,
|
||||
|
||||
/// 绝对位置 Y (0-32767)
|
||||
pub y: u32,
|
||||
|
||||
/// 滚轮移动
|
||||
pub wheel: i8,
|
||||
}
|
||||
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Button4,
|
||||
Button5,
|
||||
}
|
||||
|
||||
pub enum MouseEventType {
|
||||
Press,
|
||||
Release,
|
||||
Move,
|
||||
Wheel,
|
||||
}
|
||||
|
||||
pub enum MouseMode {
|
||||
/// 相对模式 (用于普通操作)
|
||||
Relative,
|
||||
|
||||
/// 绝对模式 (用于 BIOS/精确定位)
|
||||
Absolute,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 按键映射
|
||||
|
||||
### 5.1 KeyCode 枚举 (keymap.rs)
|
||||
|
||||
```rust
|
||||
pub enum KeyCode {
|
||||
// 字母键
|
||||
KeyA, KeyB, KeyC, /* ... */ KeyZ,
|
||||
|
||||
// 数字键
|
||||
Digit1, Digit2, /* ... */ Digit0,
|
||||
|
||||
// 功能键
|
||||
F1, F2, /* ... */ F12,
|
||||
|
||||
// 控制键
|
||||
Escape, Tab, CapsLock, Space, Enter, Backspace,
|
||||
Insert, Delete, Home, End, PageUp, PageDown,
|
||||
|
||||
// 方向键
|
||||
ArrowUp, ArrowDown, ArrowLeft, ArrowRight,
|
||||
|
||||
// 修饰键
|
||||
ShiftLeft, ShiftRight,
|
||||
ControlLeft, ControlRight,
|
||||
AltLeft, AltRight,
|
||||
MetaLeft, MetaRight,
|
||||
|
||||
// 小键盘
|
||||
Numpad0, Numpad1, /* ... */ Numpad9,
|
||||
NumpadAdd, NumpadSubtract, NumpadMultiply, NumpadDivide,
|
||||
NumpadEnter, NumpadDecimal, NumLock,
|
||||
|
||||
// 其他
|
||||
PrintScreen, ScrollLock, Pause,
|
||||
/* ... */
|
||||
}
|
||||
|
||||
impl KeyCode {
|
||||
/// 转换为 USB HID scancode
|
||||
pub fn to_scancode(&self) -> u8;
|
||||
|
||||
/// 从 JavaScript keyCode 转换
|
||||
pub fn from_js_code(code: &str) -> Option<Self>;
|
||||
|
||||
/// 是否为修饰键
|
||||
pub fn is_modifier(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 JavaScript 键码映射
|
||||
|
||||
```javascript
|
||||
// 前端发送的格式
|
||||
{
|
||||
"type": "keyboard",
|
||||
"keys": ["KeyA", "KeyB"],
|
||||
"modifiers": {
|
||||
"ctrl": false,
|
||||
"shift": true,
|
||||
"alt": false,
|
||||
"meta": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 输入处理器
|
||||
|
||||
### 6.1 WebSocket Handler (websocket.rs)
|
||||
|
||||
```rust
|
||||
pub struct WsHidHandler {
|
||||
hid: Arc<HidController>,
|
||||
}
|
||||
|
||||
impl WsHidHandler {
|
||||
pub fn new(hid: Arc<HidController>) -> Self;
|
||||
|
||||
/// 处理 WebSocket 消息
|
||||
pub async fn handle_message(&self, msg: &str) -> Result<()> {
|
||||
let event: HidMessage = serde_json::from_str(msg)?;
|
||||
|
||||
match event {
|
||||
HidMessage::Keyboard(kb) => {
|
||||
self.hid.send_keyboard(&kb).await?;
|
||||
}
|
||||
HidMessage::Mouse(mouse) => {
|
||||
self.hid.send_mouse(&mouse).await?;
|
||||
}
|
||||
HidMessage::SetMouseMode(mode) => {
|
||||
self.hid.set_mouse_mode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum HidMessage {
|
||||
#[serde(rename = "keyboard")]
|
||||
Keyboard(KeyboardEvent),
|
||||
|
||||
#[serde(rename = "mouse")]
|
||||
Mouse(MouseEvent),
|
||||
|
||||
#[serde(rename = "mouse_mode")]
|
||||
SetMouseMode(MouseMode),
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 DataChannel Handler (datachannel.rs)
|
||||
|
||||
用于 WebRTC 模式下的 HID 事件处理。
|
||||
|
||||
```rust
|
||||
pub struct HidDataChannelHandler {
|
||||
hid: Arc<HidController>,
|
||||
}
|
||||
|
||||
impl HidDataChannelHandler {
|
||||
pub fn new(hid: Arc<HidController>) -> Self;
|
||||
|
||||
/// 处理 DataChannel 消息
|
||||
pub async fn handle_message(&self, data: &[u8]) -> Result<()>;
|
||||
|
||||
/// 创建 DataChannel 配置
|
||||
pub fn datachannel_config() -> RTCDataChannelInit;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 健康监视
|
||||
|
||||
### 7.1 HidHealthMonitor (monitor.rs)
|
||||
|
||||
```rust
|
||||
pub struct HidHealthMonitor {
|
||||
/// 错误计数
|
||||
error_count: AtomicU32,
|
||||
|
||||
/// 连续错误计数
|
||||
consecutive_errors: AtomicU32,
|
||||
|
||||
/// 最后错误时间
|
||||
last_error: RwLock<Option<Instant>>,
|
||||
|
||||
/// 最后错误消息
|
||||
last_error_msg: RwLock<Option<String>>,
|
||||
|
||||
/// 重试配置
|
||||
config: MonitorConfig,
|
||||
}
|
||||
|
||||
impl HidHealthMonitor {
|
||||
/// 记录错误
|
||||
pub fn record_error(&self, error: &str);
|
||||
|
||||
/// 记录成功
|
||||
pub fn record_success(&self);
|
||||
|
||||
/// 是否应该重试
|
||||
pub fn should_retry(&self) -> bool;
|
||||
|
||||
/// 是否需要重新初始化
|
||||
pub fn needs_reinit(&self) -> bool;
|
||||
|
||||
/// 获取健康状态
|
||||
pub fn health_status(&self) -> HealthStatus;
|
||||
}
|
||||
|
||||
pub enum HealthStatus {
|
||||
Healthy,
|
||||
Degraded { error_rate: f32 },
|
||||
Unhealthy { consecutive_errors: u32 },
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 配置
|
||||
|
||||
### 8.1 HID 配置结构
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct HidConfig {
|
||||
/// 后端类型
|
||||
pub backend: HidBackendType,
|
||||
|
||||
/// CH9329 设备路径 (如果使用 CH9329)
|
||||
pub ch9329_device: Option<String>,
|
||||
|
||||
/// CH9329 波特率
|
||||
pub ch9329_baud_rate: Option<u32>,
|
||||
|
||||
/// 默认鼠标模式
|
||||
pub default_mouse_mode: MouseMode,
|
||||
|
||||
/// 鼠标灵敏度 (1-10)
|
||||
pub mouse_sensitivity: u8,
|
||||
|
||||
/// 启用滚轮
|
||||
pub enable_wheel: bool,
|
||||
}
|
||||
|
||||
impl Default for HidConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
backend: HidBackendType::Otg,
|
||||
ch9329_device: None,
|
||||
ch9329_baud_rate: Some(9600),
|
||||
default_mouse_mode: MouseMode::Absolute,
|
||||
mouse_sensitivity: 5,
|
||||
enable_wheel: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. API 端点
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/hid/status` | GET | 获取 HID 状态 |
|
||||
| `/api/hid/reset` | POST | 重置 HID 状态 |
|
||||
| `/api/hid/keyboard` | POST | 发送键盘事件 |
|
||||
| `/api/hid/mouse` | POST | 发送鼠标事件 |
|
||||
| `/api/hid/mouse/mode` | GET | 获取鼠标模式 |
|
||||
| `/api/hid/mouse/mode` | POST | 设置鼠标模式 |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```json
|
||||
// GET /api/hid/status
|
||||
{
|
||||
"backend": "otg",
|
||||
"initialized": true,
|
||||
"keyboard_connected": true,
|
||||
"mouse_connected": true,
|
||||
"mouse_mode": "absolute",
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 事件
|
||||
|
||||
```rust
|
||||
pub enum SystemEvent {
|
||||
HidStateChanged {
|
||||
backend: String,
|
||||
initialized: bool,
|
||||
keyboard_connected: bool,
|
||||
mouse_connected: bool,
|
||||
mouse_mode: String,
|
||||
error: Option<String>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 错误处理
|
||||
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum HidError {
|
||||
#[error("Backend not initialized")]
|
||||
NotInitialized,
|
||||
|
||||
#[error("Device not found: {0}")]
|
||||
DeviceNotFound(String),
|
||||
|
||||
#[error("Device busy: {0}")]
|
||||
DeviceBusy(String),
|
||||
|
||||
#[error("Write error: {0}")]
|
||||
WriteError(String),
|
||||
|
||||
#[error("Serial port error: {0}")]
|
||||
SerialError(String),
|
||||
|
||||
#[error("Invalid key code: {0}")]
|
||||
InvalidKeyCode(String),
|
||||
|
||||
#[error("OTG service error: {0}")]
|
||||
OtgError(String),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 使用示例
|
||||
|
||||
### 12.1 初始化 HID 控制器
|
||||
|
||||
```rust
|
||||
let otg_service = Arc::new(OtgService::new()?);
|
||||
let events = Arc::new(EventBus::new());
|
||||
|
||||
let hid = HidController::init(
|
||||
otg_service,
|
||||
&HidConfig::default(),
|
||||
events,
|
||||
).await?;
|
||||
```
|
||||
|
||||
### 12.2 发送键盘事件
|
||||
|
||||
```rust
|
||||
// 按下 Ctrl+C
|
||||
hid.send_keyboard(&KeyboardEvent {
|
||||
keys: vec![KeyCode::KeyC],
|
||||
modifiers: KeyboardModifiers {
|
||||
left_ctrl: true,
|
||||
..Default::default()
|
||||
},
|
||||
}).await?;
|
||||
|
||||
// 释放所有键
|
||||
hid.send_keyboard(&KeyboardEvent {
|
||||
keys: vec![],
|
||||
modifiers: KeyboardModifiers::default(),
|
||||
}).await?;
|
||||
```
|
||||
|
||||
### 12.3 发送鼠标事件
|
||||
|
||||
```rust
|
||||
// 移动鼠标到绝对位置
|
||||
hid.send_mouse(&MouseEvent {
|
||||
button: None,
|
||||
event_type: MouseEventType::Move,
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
x: 16384, // 屏幕中心
|
||||
y: 16384,
|
||||
wheel: 0,
|
||||
}).await?;
|
||||
|
||||
// 点击左键
|
||||
hid.send_mouse(&MouseEvent {
|
||||
button: Some(MouseButton::Left),
|
||||
event_type: MouseEventType::Press,
|
||||
..Default::default()
|
||||
}).await?;
|
||||
|
||||
hid.send_mouse(&MouseEvent {
|
||||
button: Some(MouseButton::Left),
|
||||
event_type: MouseEventType::Release,
|
||||
..Default::default()
|
||||
}).await?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. 常见问题
|
||||
|
||||
### Q: OTG 模式下键盘/鼠标不工作?
|
||||
|
||||
1. 检查 `/dev/hidg*` 设备是否存在
|
||||
2. 检查 USB gadget 是否正确配置
|
||||
3. 检查目标 PC 是否识别 USB 设备
|
||||
4. 查看 `dmesg` 日志
|
||||
|
||||
### Q: CH9329 无法初始化?
|
||||
|
||||
1. 检查串口设备路径
|
||||
2. 检查波特率设置
|
||||
3. 使用 `minicom` 测试串口连接
|
||||
|
||||
### Q: 鼠标定位不准确?
|
||||
|
||||
1. 使用绝对鼠标模式
|
||||
2. 校准屏幕分辨率
|
||||
3. 检查缩放设置
|
||||
|
||||
### Q: 按键有延迟?
|
||||
|
||||
1. 检查网络延迟
|
||||
2. 使用 WebRTC 模式
|
||||
3. 减少中间代理
|
||||
617
docs/modules/msd.md
Normal file
617
docs/modules/msd.md
Normal file
@@ -0,0 +1,617 @@
|
||||
# MSD 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
MSD (Mass Storage Device) 模块提供虚拟存储设备功能,允许将 ISO/IMG 镜像作为 USB 存储设备挂载到目标计算机。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- ISO/IMG 镜像挂载
|
||||
- 镜像下载管理
|
||||
- Ventoy 多 ISO 启动盘
|
||||
- 热插拔支持
|
||||
- 下载进度追踪
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/msd/
|
||||
├── mod.rs # 模块导出
|
||||
├── controller.rs # MsdController (20KB)
|
||||
├── image.rs # 镜像管理 (21KB)
|
||||
├── ventoy_drive.rs # Ventoy 驱动 (24KB)
|
||||
├── monitor.rs # 健康监视 (9KB)
|
||||
└── types.rs # 类型定义 (6KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ MSD Architecture │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Web API
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ MsdController │
|
||||
│ (controller.rs) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌───────────┐ ┌───────────┐
|
||||
│ Image │ │ Ventoy │ │ OTG │
|
||||
│ Manager │ │ Drive │ │ Service │
|
||||
│ (image.rs) │ │(ventoy.rs)│ │ │
|
||||
└──────┬──────┘ └─────┬─────┘ └─────┬─────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌───────────┐ ┌───────────┐
|
||||
│ /data/ │ │ exFAT │ │ MSD │
|
||||
│ images/ │ │ Drive │ │ Function │
|
||||
└─────────────┘ └───────────┘ └───────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────┐
|
||||
│ Target PC │
|
||||
│ (USB Drive) │
|
||||
└───────────────┘
|
||||
```
|
||||
|
||||
### 2.2 MSD 模式
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ MSD Modes │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Image Mode │
|
||||
│ ┌───────────┐ │
|
||||
│ │ ISO/IMG │ ──► MSD LUN ──► Target PC sees single drive │
|
||||
│ │ File │ │
|
||||
│ └───────────┘ │
|
||||
│ 特点: │
|
||||
│ - 单个镜像文件 │
|
||||
│ - 直接挂载 │
|
||||
│ - 适合系统安装 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Ventoy Mode │
|
||||
│ ┌───────────┐ │
|
||||
│ │ ISO 1 │ │
|
||||
│ ├───────────┤ ┌───────────┐ │
|
||||
│ │ ISO 2 │ ──► │ Ventoy │ ──► Target PC sees bootable drive │
|
||||
│ ├───────────┤ │ Drive │ with ISO selection menu │
|
||||
│ │ ISO 3 │ └───────────┘ │
|
||||
│ └───────────┘ │
|
||||
│ 特点: │
|
||||
│ - 多个 ISO 文件 │
|
||||
│ - exFAT 文件系统 │
|
||||
│ - 启动菜单选择 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 MsdController (controller.rs)
|
||||
|
||||
MSD 控制器主类。
|
||||
|
||||
```rust
|
||||
pub struct MsdController {
|
||||
/// 当前状态
|
||||
state: Arc<RwLock<MsdState>>,
|
||||
|
||||
/// 镜像管理器
|
||||
image_manager: Arc<ImageManager>,
|
||||
|
||||
/// Ventoy 驱动器
|
||||
ventoy_drive: Arc<RwLock<Option<VentoyDrive>>>,
|
||||
|
||||
/// OTG 服务
|
||||
otg_service: Arc<OtgService>,
|
||||
|
||||
/// MSD 函数句柄
|
||||
msd_function: Arc<RwLock<Option<MsdFunction>>>,
|
||||
|
||||
/// 事件总线
|
||||
events: Arc<EventBus>,
|
||||
|
||||
/// 数据目录
|
||||
data_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl MsdController {
|
||||
/// 创建控制器
|
||||
pub async fn new(
|
||||
otg_service: Arc<OtgService>,
|
||||
data_dir: PathBuf,
|
||||
events: Arc<EventBus>,
|
||||
) -> Result<Arc<Self>>;
|
||||
|
||||
/// 获取状态
|
||||
pub fn state(&self) -> MsdState;
|
||||
|
||||
/// 连接 MSD
|
||||
pub async fn connect(&self) -> Result<()>;
|
||||
|
||||
/// 断开 MSD
|
||||
pub async fn disconnect(&self) -> Result<()>;
|
||||
|
||||
/// 切换到镜像模式
|
||||
pub async fn set_image(&self, image_id: &str) -> Result<()>;
|
||||
|
||||
/// 切换到 Ventoy 模式
|
||||
pub async fn set_ventoy(&self) -> Result<()>;
|
||||
|
||||
/// 清除当前挂载
|
||||
pub async fn clear(&self) -> Result<()>;
|
||||
|
||||
/// 列出镜像
|
||||
pub fn list_images(&self) -> Vec<ImageInfo>;
|
||||
|
||||
/// 上传镜像
|
||||
pub async fn upload_image(&self, name: &str, data: Bytes) -> Result<ImageInfo>;
|
||||
|
||||
/// 从 URL 下载镜像
|
||||
pub async fn download_image(&self, url: &str) -> Result<String>;
|
||||
|
||||
/// 删除镜像
|
||||
pub async fn delete_image(&self, image_id: &str) -> Result<()>;
|
||||
|
||||
/// 获取下载进度
|
||||
pub fn get_download_progress(&self, download_id: &str) -> Option<DownloadProgress>;
|
||||
}
|
||||
|
||||
pub struct MsdState {
|
||||
/// 是否可用
|
||||
pub available: bool,
|
||||
|
||||
/// 当前模式
|
||||
pub mode: MsdMode,
|
||||
|
||||
/// 是否已连接
|
||||
pub connected: bool,
|
||||
|
||||
/// 当前镜像信息
|
||||
pub current_image: Option<ImageInfo>,
|
||||
|
||||
/// 驱动器信息
|
||||
pub drive_info: Option<DriveInfo>,
|
||||
|
||||
/// 错误信息
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
pub enum MsdMode {
|
||||
/// 未激活
|
||||
None,
|
||||
|
||||
/// 单镜像模式
|
||||
Image,
|
||||
|
||||
/// Ventoy 模式
|
||||
Drive,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 ImageManager (image.rs)
|
||||
|
||||
镜像文件管理器。
|
||||
|
||||
```rust
|
||||
pub struct ImageManager {
|
||||
/// 镜像目录
|
||||
images_dir: PathBuf,
|
||||
|
||||
/// 镜像列表缓存
|
||||
images: RwLock<HashMap<String, ImageInfo>>,
|
||||
|
||||
/// 下载任务
|
||||
downloads: RwLock<HashMap<String, DownloadTask>>,
|
||||
|
||||
/// HTTP 客户端
|
||||
http_client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl ImageManager {
|
||||
/// 创建管理器
|
||||
pub fn new(images_dir: PathBuf) -> Result<Self>;
|
||||
|
||||
/// 扫描镜像目录
|
||||
pub fn scan_images(&self) -> Result<Vec<ImageInfo>>;
|
||||
|
||||
/// 获取镜像信息
|
||||
pub fn get_image(&self, id: &str) -> Option<ImageInfo>;
|
||||
|
||||
/// 添加镜像
|
||||
pub async fn add_image(&self, name: &str, data: Bytes) -> Result<ImageInfo>;
|
||||
|
||||
/// 删除镜像
|
||||
pub fn delete_image(&self, id: &str) -> Result<()>;
|
||||
|
||||
/// 开始下载
|
||||
pub async fn start_download(&self, url: &str) -> Result<String>;
|
||||
|
||||
/// 取消下载
|
||||
pub fn cancel_download(&self, download_id: &str) -> Result<()>;
|
||||
|
||||
/// 获取下载进度
|
||||
pub fn get_download_progress(&self, download_id: &str) -> Option<DownloadProgress>;
|
||||
|
||||
/// 验证镜像文件
|
||||
fn validate_image(path: &Path) -> Result<ImageFormat>;
|
||||
}
|
||||
|
||||
pub struct ImageInfo {
|
||||
/// 唯一 ID
|
||||
pub id: String,
|
||||
|
||||
/// 文件名
|
||||
pub name: String,
|
||||
|
||||
/// 文件大小
|
||||
pub size: u64,
|
||||
|
||||
/// 格式
|
||||
pub format: ImageFormat,
|
||||
|
||||
/// 创建时间
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
||||
/// 下载状态
|
||||
pub download_status: Option<DownloadStatus>,
|
||||
}
|
||||
|
||||
pub enum ImageFormat {
|
||||
/// ISO 光盘镜像
|
||||
Iso,
|
||||
|
||||
/// 原始磁盘镜像
|
||||
Img,
|
||||
|
||||
/// 未知格式
|
||||
Unknown,
|
||||
}
|
||||
|
||||
pub struct DownloadProgress {
|
||||
/// 已下载字节
|
||||
pub downloaded: u64,
|
||||
|
||||
/// 总字节数
|
||||
pub total: u64,
|
||||
|
||||
/// 下载速度 (bytes/sec)
|
||||
pub speed: u64,
|
||||
|
||||
/// 预计剩余时间
|
||||
pub eta_secs: u64,
|
||||
|
||||
/// 状态
|
||||
pub status: DownloadStatus,
|
||||
}
|
||||
|
||||
pub enum DownloadStatus {
|
||||
Pending,
|
||||
Downloading,
|
||||
Completed,
|
||||
Failed(String),
|
||||
Cancelled,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 VentoyDrive (ventoy_drive.rs)
|
||||
|
||||
Ventoy 可启动驱动器管理。
|
||||
|
||||
```rust
|
||||
pub struct VentoyDrive {
|
||||
/// 驱动器路径
|
||||
drive_path: PathBuf,
|
||||
|
||||
/// 镜像路径
|
||||
images: Vec<PathBuf>,
|
||||
|
||||
/// 容量
|
||||
capacity: u64,
|
||||
|
||||
/// 已用空间
|
||||
used: u64,
|
||||
}
|
||||
|
||||
impl VentoyDrive {
|
||||
/// 创建 Ventoy 驱动器
|
||||
pub fn create(drive_path: PathBuf, capacity: u64) -> Result<Self>;
|
||||
|
||||
/// 添加 ISO
|
||||
pub fn add_iso(&mut self, iso_path: &Path) -> Result<()>;
|
||||
|
||||
/// 移除 ISO
|
||||
pub fn remove_iso(&mut self, name: &str) -> Result<()>;
|
||||
|
||||
/// 列出 ISO
|
||||
pub fn list_isos(&self) -> Vec<String>;
|
||||
|
||||
/// 获取驱动器信息
|
||||
pub fn info(&self) -> DriveInfo;
|
||||
|
||||
/// 获取驱动器路径
|
||||
pub fn path(&self) -> &Path;
|
||||
}
|
||||
|
||||
pub struct DriveInfo {
|
||||
/// 容量
|
||||
pub capacity: u64,
|
||||
|
||||
/// 已用空间
|
||||
pub used: u64,
|
||||
|
||||
/// 可用空间
|
||||
pub available: u64,
|
||||
|
||||
/// ISO 列表
|
||||
pub isos: Vec<String>,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 类型定义
|
||||
|
||||
### 4.1 MSD 配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct MsdConfig {
|
||||
/// 是否启用 MSD
|
||||
pub enabled: bool,
|
||||
|
||||
/// 镜像目录
|
||||
pub images_dir: Option<String>,
|
||||
|
||||
/// 默认模式
|
||||
pub default_mode: MsdMode,
|
||||
|
||||
/// Ventoy 容量 (MB)
|
||||
pub ventoy_capacity_mb: u32,
|
||||
}
|
||||
|
||||
impl Default for MsdConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
images_dir: None,
|
||||
default_mode: MsdMode::None,
|
||||
ventoy_capacity_mb: 4096, // 4GB
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 端点
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/msd/status` | GET | 获取 MSD 状态 |
|
||||
| `/api/msd/connect` | POST | 连接 MSD |
|
||||
| `/api/msd/disconnect` | POST | 断开 MSD |
|
||||
| `/api/msd/images` | GET | 列出镜像 |
|
||||
| `/api/msd/images` | POST | 上传镜像 |
|
||||
| `/api/msd/images/:id` | DELETE | 删除镜像 |
|
||||
| `/api/msd/images/download` | POST | 从 URL 下载 |
|
||||
| `/api/msd/images/download/:id` | GET | 获取下载进度 |
|
||||
| `/api/msd/images/download/:id` | DELETE | 取消下载 |
|
||||
| `/api/msd/set-image` | POST | 设置当前镜像 |
|
||||
| `/api/msd/set-ventoy` | POST | 设置 Ventoy 模式 |
|
||||
| `/api/msd/clear` | POST | 清除挂载 |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```json
|
||||
// GET /api/msd/status
|
||||
{
|
||||
"available": true,
|
||||
"mode": "image",
|
||||
"connected": true,
|
||||
"current_image": {
|
||||
"id": "abc123",
|
||||
"name": "ubuntu-22.04.iso",
|
||||
"size": 4700000000,
|
||||
"format": "iso"
|
||||
},
|
||||
"drive_info": null,
|
||||
"error": null
|
||||
}
|
||||
|
||||
// GET /api/msd/images
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"id": "abc123",
|
||||
"name": "ubuntu-22.04.iso",
|
||||
"size": 4700000000,
|
||||
"format": "iso",
|
||||
"created_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// POST /api/msd/images/download
|
||||
// Request: { "url": "https://example.com/image.iso" }
|
||||
// Response: { "download_id": "xyz789" }
|
||||
|
||||
// GET /api/msd/images/download/xyz789
|
||||
{
|
||||
"downloaded": 1234567890,
|
||||
"total": 4700000000,
|
||||
"speed": 12345678,
|
||||
"eta_secs": 280,
|
||||
"status": "downloading"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 事件
|
||||
|
||||
```rust
|
||||
pub enum SystemEvent {
|
||||
MsdStateChanged {
|
||||
mode: MsdMode,
|
||||
connected: bool,
|
||||
image: Option<String>,
|
||||
error: Option<String>,
|
||||
},
|
||||
|
||||
MsdDownloadProgress {
|
||||
download_id: String,
|
||||
progress: DownloadProgress,
|
||||
},
|
||||
|
||||
MsdDownloadComplete {
|
||||
download_id: String,
|
||||
image_id: String,
|
||||
success: bool,
|
||||
error: Option<String>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MsdError {
|
||||
#[error("MSD not available")]
|
||||
NotAvailable,
|
||||
|
||||
#[error("Already connected")]
|
||||
AlreadyConnected,
|
||||
|
||||
#[error("Not connected")]
|
||||
NotConnected,
|
||||
|
||||
#[error("Image not found: {0}")]
|
||||
ImageNotFound(String),
|
||||
|
||||
#[error("Invalid image format: {0}")]
|
||||
InvalidFormat(String),
|
||||
|
||||
#[error("Download failed: {0}")]
|
||||
DownloadFailed(String),
|
||||
|
||||
#[error("Storage full")]
|
||||
StorageFull,
|
||||
|
||||
#[error("OTG error: {0}")]
|
||||
OtgError(String),
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 使用示例
|
||||
|
||||
### 8.1 挂载 ISO 镜像
|
||||
|
||||
```rust
|
||||
let msd = MsdController::new(otg_service, data_dir, events).await?;
|
||||
|
||||
// 列出镜像
|
||||
let images = msd.list_images();
|
||||
println!("Available images: {:?}", images);
|
||||
|
||||
// 设置镜像
|
||||
msd.set_image("abc123").await?;
|
||||
|
||||
// 连接到目标 PC
|
||||
msd.connect().await?;
|
||||
|
||||
// 目标 PC 现在可以看到 USB 驱动器...
|
||||
|
||||
// 断开连接
|
||||
msd.disconnect().await?;
|
||||
```
|
||||
|
||||
### 8.2 从 URL 下载
|
||||
|
||||
```rust
|
||||
// 开始下载
|
||||
let download_id = msd.download_image("https://example.com/ubuntu.iso").await?;
|
||||
|
||||
// 监控进度
|
||||
loop {
|
||||
if let Some(progress) = msd.get_download_progress(&download_id) {
|
||||
println!("Progress: {}%", progress.downloaded * 100 / progress.total);
|
||||
|
||||
if matches!(progress.status, DownloadStatus::Completed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 使用 Ventoy 模式
|
||||
|
||||
```rust
|
||||
// 切换到 Ventoy 模式
|
||||
msd.set_ventoy().await?;
|
||||
|
||||
// 获取驱动器信息
|
||||
let state = msd.state();
|
||||
if let Some(drive_info) = state.drive_info {
|
||||
println!("Capacity: {} MB", drive_info.capacity / 1024 / 1024);
|
||||
println!("ISOs: {:?}", drive_info.isos);
|
||||
}
|
||||
|
||||
// 连接
|
||||
msd.connect().await?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 常见问题
|
||||
|
||||
### Q: 镜像无法挂载?
|
||||
|
||||
1. 检查镜像文件完整性
|
||||
2. 确认文件格式正确
|
||||
3. 检查存储空间
|
||||
|
||||
### Q: 目标 PC 不识别?
|
||||
|
||||
1. 检查 USB 连接
|
||||
2. 尝试重新连接
|
||||
3. 查看目标 PC 的设备管理器
|
||||
|
||||
### Q: 下载速度慢?
|
||||
|
||||
1. 检查网络连接
|
||||
2. 使用更近的镜像源
|
||||
3. 检查磁盘 I/O
|
||||
|
||||
### Q: Ventoy 启动失败?
|
||||
|
||||
1. 检查目标 PC BIOS 设置
|
||||
2. 尝试不同的启动模式
|
||||
3. 确认 ISO 文件支持 Ventoy
|
||||
667
docs/modules/otg.md
Normal file
667
docs/modules/otg.md
Normal file
@@ -0,0 +1,667 @@
|
||||
# OTG 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
OTG (On-The-Go) 模块负责管理 Linux USB Gadget,为 HID 和 MSD 功能提供统一的 USB 设备管理。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- USB Gadget 生命周期管理
|
||||
- HID 函数配置 (键盘、鼠标)
|
||||
- MSD 函数配置 (虚拟存储)
|
||||
- ConfigFS 操作
|
||||
- UDC 绑定/解绑
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/otg/
|
||||
├── mod.rs # 模块导出
|
||||
├── service.rs # OtgService (17KB)
|
||||
├── manager.rs # OtgGadgetManager (12KB)
|
||||
├── hid.rs # HID Function (7KB)
|
||||
├── msd.rs # MSD Function (14KB)
|
||||
├── configfs.rs # ConfigFS 操作 (4KB)
|
||||
├── endpoint.rs # 端点分配 (2KB)
|
||||
└── report_desc.rs # HID 报告描述符 (6KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 设计目标
|
||||
|
||||
解决 HID 和 MSD 共享同一个 USB Gadget 的所有权问题:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ OTG Ownership Model │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────┐
|
||||
│ OtgService │ ◄── 唯一所有者
|
||||
│ (service.rs) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
enable_hid() enable_msd() 状态查询
|
||||
│ │
|
||||
└──────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│OtgGadgetManager │
|
||||
│ (manager.rs) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────┐ ┌───────┐ ┌───────┐
|
||||
│ HID │ │ MSD │ │ UDC │
|
||||
│ Func │ │ Func │ │ Bind │
|
||||
└───────┘ └───────┘ └───────┘
|
||||
```
|
||||
|
||||
### 2.2 ConfigFS 结构
|
||||
|
||||
```
|
||||
/sys/kernel/config/usb_gadget/one-kvm/
|
||||
├── idVendor # 0x05ac (Apple)
|
||||
├── idProduct # 0x0001
|
||||
├── bcdDevice # 0x0100
|
||||
├── bcdUSB # 0x0200
|
||||
├── bMaxPacketSize0 # 64
|
||||
│
|
||||
├── strings/
|
||||
│ └── 0x409/ # English
|
||||
│ ├── manufacturer # "One-KVM"
|
||||
│ ├── product # "KVM Device"
|
||||
│ └── serialnumber # UUID
|
||||
│
|
||||
├── configs/
|
||||
│ └── c.1/
|
||||
│ ├── MaxPower # 500
|
||||
│ ├── strings/
|
||||
│ │ └── 0x409/
|
||||
│ │ └── configuration # "Config 1"
|
||||
│ └── (function symlinks)
|
||||
│
|
||||
├── functions/
|
||||
│ ├── hid.usb0/ # 键盘
|
||||
│ │ ├── protocol # 1 (keyboard)
|
||||
│ │ ├── subclass # 1 (boot)
|
||||
│ │ ├── report_length # 8
|
||||
│ │ └── report_desc # (binary)
|
||||
│ │
|
||||
│ ├── hid.usb1/ # 相对鼠标
|
||||
│ │ ├── protocol # 2 (mouse)
|
||||
│ │ ├── subclass # 1 (boot)
|
||||
│ │ ├── report_length # 4
|
||||
│ │ └── report_desc # (binary)
|
||||
│ │
|
||||
│ ├── hid.usb2/ # 绝对鼠标
|
||||
│ │ ├── protocol # 2 (mouse)
|
||||
│ │ ├── subclass # 0 (none)
|
||||
│ │ ├── report_length # 6
|
||||
│ │ └── report_desc # (binary)
|
||||
│ │
|
||||
│ └── mass_storage.usb0/ # 虚拟存储
|
||||
│ ├── stall # 1
|
||||
│ └── lun.0/
|
||||
│ ├── cdrom # 1 (ISO mode)
|
||||
│ ├── ro # 1 (read-only)
|
||||
│ ├── removable # 1
|
||||
│ ├── nofua # 1
|
||||
│ └── file # /path/to/image.iso
|
||||
│
|
||||
└── UDC # UDC 设备名
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 OtgService (service.rs)
|
||||
|
||||
OTG 服务主类,提供统一的 USB Gadget 管理接口。
|
||||
|
||||
```rust
|
||||
pub struct OtgService {
|
||||
/// Gadget 管理器
|
||||
manager: Arc<Mutex<OtgGadgetManager>>,
|
||||
|
||||
/// 当前状态
|
||||
state: Arc<RwLock<OtgServiceState>>,
|
||||
|
||||
/// HID 函数句柄
|
||||
hid_function: Arc<RwLock<Option<HidFunction>>>,
|
||||
|
||||
/// MSD 函数句柄
|
||||
msd_function: Arc<RwLock<Option<MsdFunction>>>,
|
||||
|
||||
/// 请求计数器 (lock-free)
|
||||
pending_requests: AtomicU8,
|
||||
}
|
||||
|
||||
impl OtgService {
|
||||
/// 创建服务
|
||||
pub fn new() -> Result<Self>;
|
||||
|
||||
/// 启用 HID 功能
|
||||
pub async fn enable_hid(&self) -> Result<HidDevicePaths>;
|
||||
|
||||
/// 禁用 HID 功能
|
||||
pub async fn disable_hid(&self) -> Result<()>;
|
||||
|
||||
/// 启用 MSD 功能
|
||||
pub async fn enable_msd(&self) -> Result<MsdFunction>;
|
||||
|
||||
/// 禁用 MSD 功能
|
||||
pub async fn disable_msd(&self) -> Result<()>;
|
||||
|
||||
/// 获取状态
|
||||
pub fn state(&self) -> OtgServiceState;
|
||||
|
||||
/// 检查 HID 是否启用
|
||||
pub fn is_hid_enabled(&self) -> bool;
|
||||
|
||||
/// 检查 MSD 是否启用
|
||||
pub fn is_msd_enabled(&self) -> bool;
|
||||
}
|
||||
|
||||
pub struct OtgServiceState {
|
||||
/// Gadget 是否激活
|
||||
pub gadget_active: bool,
|
||||
|
||||
/// HID 是否启用
|
||||
pub hid_enabled: bool,
|
||||
|
||||
/// MSD 是否启用
|
||||
pub msd_enabled: bool,
|
||||
|
||||
/// HID 设备路径
|
||||
pub hid_paths: Option<HidDevicePaths>,
|
||||
|
||||
/// 错误信息
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
pub struct HidDevicePaths {
|
||||
pub keyboard: PathBuf, // /dev/hidg0
|
||||
pub mouse_relative: PathBuf, // /dev/hidg1
|
||||
pub mouse_absolute: PathBuf, // /dev/hidg2
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 OtgGadgetManager (manager.rs)
|
||||
|
||||
Gadget 生命周期管理器。
|
||||
|
||||
```rust
|
||||
pub struct OtgGadgetManager {
|
||||
/// Gadget 路径
|
||||
gadget_path: PathBuf,
|
||||
|
||||
/// UDC 设备名
|
||||
udc_name: Option<String>,
|
||||
|
||||
/// 是否已创建
|
||||
created: bool,
|
||||
|
||||
/// 是否已绑定
|
||||
bound: bool,
|
||||
|
||||
/// 端点分配器
|
||||
endpoint_allocator: EndpointAllocator,
|
||||
}
|
||||
|
||||
impl OtgGadgetManager {
|
||||
/// 创建管理器
|
||||
pub fn new() -> Result<Self>;
|
||||
|
||||
/// 创建 Gadget
|
||||
pub fn create_gadget(&mut self, config: &GadgetConfig) -> Result<()>;
|
||||
|
||||
/// 销毁 Gadget
|
||||
pub fn destroy_gadget(&mut self) -> Result<()>;
|
||||
|
||||
/// 绑定 UDC
|
||||
pub fn bind_udc(&mut self) -> Result<()>;
|
||||
|
||||
/// 解绑 UDC
|
||||
pub fn unbind_udc(&mut self) -> Result<()>;
|
||||
|
||||
/// 添加函数
|
||||
pub fn add_function(&mut self, func: &dyn GadgetFunction) -> Result<()>;
|
||||
|
||||
/// 移除函数
|
||||
pub fn remove_function(&mut self, func: &dyn GadgetFunction) -> Result<()>;
|
||||
|
||||
/// 链接函数到配置
|
||||
pub fn link_function(&self, func: &dyn GadgetFunction) -> Result<()>;
|
||||
|
||||
/// 取消链接函数
|
||||
pub fn unlink_function(&self, func: &dyn GadgetFunction) -> Result<()>;
|
||||
|
||||
/// 检测可用 UDC
|
||||
fn detect_udc() -> Result<String>;
|
||||
}
|
||||
|
||||
pub struct GadgetConfig {
|
||||
pub name: String, // "one-kvm"
|
||||
pub vendor_id: u16, // 0x05ac
|
||||
pub product_id: u16, // 0x0001
|
||||
pub manufacturer: String, // "One-KVM"
|
||||
pub product: String, // "KVM Device"
|
||||
pub serial: String, // UUID
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 HID Function (hid.rs)
|
||||
|
||||
```rust
|
||||
pub struct HidFunction {
|
||||
/// 键盘函数
|
||||
keyboard: HidFunctionConfig,
|
||||
|
||||
/// 相对鼠标函数
|
||||
mouse_relative: HidFunctionConfig,
|
||||
|
||||
/// 绝对鼠标函数
|
||||
mouse_absolute: HidFunctionConfig,
|
||||
}
|
||||
|
||||
pub struct HidFunctionConfig {
|
||||
/// 函数名
|
||||
pub name: String, // "hid.usb0"
|
||||
|
||||
/// 协议
|
||||
pub protocol: u8, // 1=keyboard, 2=mouse
|
||||
|
||||
/// 子类
|
||||
pub subclass: u8, // 1=boot, 0=none
|
||||
|
||||
/// 报告长度
|
||||
pub report_length: u8,
|
||||
|
||||
/// 报告描述符
|
||||
pub report_desc: Vec<u8>,
|
||||
}
|
||||
|
||||
impl HidFunction {
|
||||
/// 创建 HID 函数
|
||||
pub fn new() -> Self;
|
||||
|
||||
/// 获取键盘报告描述符
|
||||
pub fn keyboard_report_desc() -> Vec<u8>;
|
||||
|
||||
/// 获取相对鼠标报告描述符
|
||||
pub fn mouse_relative_report_desc() -> Vec<u8>;
|
||||
|
||||
/// 获取绝对鼠标报告描述符
|
||||
pub fn mouse_absolute_report_desc() -> Vec<u8>;
|
||||
}
|
||||
|
||||
impl GadgetFunction for HidFunction {
|
||||
fn name(&self) -> &str;
|
||||
fn function_type(&self) -> &str; // "hid"
|
||||
fn configure(&self, path: &Path) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 MSD Function (msd.rs)
|
||||
|
||||
```rust
|
||||
pub struct MsdFunction {
|
||||
/// 函数名
|
||||
name: String,
|
||||
|
||||
/// LUN 配置
|
||||
luns: Vec<MsdLun>,
|
||||
}
|
||||
|
||||
pub struct MsdLun {
|
||||
/// LUN 编号
|
||||
pub lun_id: u8,
|
||||
|
||||
/// 镜像文件路径
|
||||
pub file: Option<PathBuf>,
|
||||
|
||||
/// 是否 CD-ROM 模式
|
||||
pub cdrom: bool,
|
||||
|
||||
/// 是否只读
|
||||
pub readonly: bool,
|
||||
|
||||
/// 是否可移除
|
||||
pub removable: bool,
|
||||
}
|
||||
|
||||
impl MsdFunction {
|
||||
/// 创建 MSD 函数
|
||||
pub fn new() -> Self;
|
||||
|
||||
/// 设置镜像文件
|
||||
pub fn set_image(&mut self, path: &Path, cdrom: bool) -> Result<()>;
|
||||
|
||||
/// 清除镜像
|
||||
pub fn clear_image(&mut self) -> Result<()>;
|
||||
|
||||
/// 弹出介质
|
||||
pub fn eject(&mut self) -> Result<()>;
|
||||
}
|
||||
|
||||
impl GadgetFunction for MsdFunction {
|
||||
fn name(&self) -> &str;
|
||||
fn function_type(&self) -> &str; // "mass_storage"
|
||||
fn configure(&self, path: &Path) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 ConfigFS 操作 (configfs.rs)
|
||||
|
||||
```rust
|
||||
pub struct ConfigFs;
|
||||
|
||||
impl ConfigFs {
|
||||
/// ConfigFS 根路径
|
||||
const ROOT: &'static str = "/sys/kernel/config/usb_gadget";
|
||||
|
||||
/// 创建目录
|
||||
pub fn mkdir(path: &Path) -> Result<()>;
|
||||
|
||||
/// 删除目录
|
||||
pub fn rmdir(path: &Path) -> Result<()>;
|
||||
|
||||
/// 写入文件
|
||||
pub fn write_file(path: &Path, content: &str) -> Result<()>;
|
||||
|
||||
/// 写入二进制文件
|
||||
pub fn write_binary(path: &Path, data: &[u8]) -> Result<()>;
|
||||
|
||||
/// 读取文件
|
||||
pub fn read_file(path: &Path) -> Result<String>;
|
||||
|
||||
/// 创建符号链接
|
||||
pub fn symlink(target: &Path, link: &Path) -> Result<()>;
|
||||
|
||||
/// 删除符号链接
|
||||
pub fn unlink(path: &Path) -> Result<()>;
|
||||
|
||||
/// 列出目录
|
||||
pub fn list_dir(path: &Path) -> Result<Vec<String>>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 端点分配 (endpoint.rs)
|
||||
|
||||
```rust
|
||||
pub struct EndpointAllocator {
|
||||
/// 已使用的端点
|
||||
used_endpoints: HashSet<u8>,
|
||||
|
||||
/// 最大端点数
|
||||
max_endpoints: u8,
|
||||
}
|
||||
|
||||
impl EndpointAllocator {
|
||||
/// 创建分配器
|
||||
pub fn new(max_endpoints: u8) -> Self;
|
||||
|
||||
/// 分配端点
|
||||
pub fn allocate(&mut self, count: u8) -> Result<Vec<u8>>;
|
||||
|
||||
/// 释放端点
|
||||
pub fn release(&mut self, endpoints: &[u8]);
|
||||
|
||||
/// 检查可用端点数
|
||||
pub fn available(&self) -> u8;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.7 报告描述符 (report_desc.rs)
|
||||
|
||||
```rust
|
||||
pub struct ReportDescriptor;
|
||||
|
||||
impl ReportDescriptor {
|
||||
/// 标准键盘报告描述符
|
||||
pub fn keyboard() -> Vec<u8> {
|
||||
vec![
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x06, // Usage (Keyboard)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x05, 0x07, // Usage Page (Key Codes)
|
||||
0x19, 0xE0, // Usage Minimum (224)
|
||||
0x29, 0xE7, // Usage Maximum (231)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x08, // Report Count (8)
|
||||
0x81, 0x02, // Input (Data, Variable, Absolute)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x81, 0x01, // Input (Constant)
|
||||
0x95, 0x06, // Report Count (6)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x65, // Logical Maximum (101)
|
||||
0x05, 0x07, // Usage Page (Key Codes)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x29, 0x65, // Usage Maximum (101)
|
||||
0x81, 0x00, // Input (Data, Array)
|
||||
0xC0, // End Collection
|
||||
]
|
||||
}
|
||||
|
||||
/// 相对鼠标报告描述符
|
||||
pub fn mouse_relative() -> Vec<u8>;
|
||||
|
||||
/// 绝对鼠标报告描述符
|
||||
pub fn mouse_absolute() -> Vec<u8>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 生命周期管理
|
||||
|
||||
### 4.1 初始化流程
|
||||
|
||||
```
|
||||
OtgService::new()
|
||||
│
|
||||
├── 检测 UDC 设备
|
||||
│ └── 读取 /sys/class/udc/
|
||||
│
|
||||
├── 创建 OtgGadgetManager
|
||||
│
|
||||
└── 初始化状态
|
||||
|
||||
enable_hid()
|
||||
│
|
||||
├── 检查 Gadget 是否存在
|
||||
│ └── 如不存在,创建 Gadget
|
||||
│
|
||||
├── 创建 HID 函数
|
||||
│ ├── hid.usb0 (键盘)
|
||||
│ ├── hid.usb1 (相对鼠标)
|
||||
│ └── hid.usb2 (绝对鼠标)
|
||||
│
|
||||
├── 配置函数
|
||||
│ └── 写入报告描述符
|
||||
│
|
||||
├── 链接函数到配置
|
||||
│
|
||||
├── 绑定 UDC (如未绑定)
|
||||
│
|
||||
└── 等待设备节点出现
|
||||
└── /dev/hidg0, hidg1, hidg2
|
||||
```
|
||||
|
||||
### 4.2 清理流程
|
||||
|
||||
```
|
||||
disable_hid()
|
||||
│
|
||||
├── 检查是否有其他函数使用
|
||||
│
|
||||
├── 如果只有 HID,解绑 UDC
|
||||
│
|
||||
├── 取消链接 HID 函数
|
||||
│
|
||||
└── 删除 HID 函数目录
|
||||
|
||||
disable_msd()
|
||||
│
|
||||
├── 同上...
|
||||
│
|
||||
└── 如果没有任何函数,销毁 Gadget
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置
|
||||
|
||||
### 5.1 OTG 配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct OtgConfig {
|
||||
/// 是否启用 OTG
|
||||
pub enabled: bool,
|
||||
|
||||
/// 厂商 ID
|
||||
pub vendor_id: u16,
|
||||
|
||||
/// 产品 ID
|
||||
pub product_id: u16,
|
||||
|
||||
/// 厂商名称
|
||||
pub manufacturer: String,
|
||||
|
||||
/// 产品名称
|
||||
pub product: String,
|
||||
}
|
||||
|
||||
impl Default for OtgConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
vendor_id: 0x05ac, // Apple
|
||||
product_id: 0x0001,
|
||||
manufacturer: "One-KVM".to_string(),
|
||||
product: "KVM Device".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 错误处理
|
||||
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum OtgError {
|
||||
#[error("No UDC device found")]
|
||||
NoUdcDevice,
|
||||
|
||||
#[error("Gadget already exists")]
|
||||
GadgetExists,
|
||||
|
||||
#[error("Gadget not found")]
|
||||
GadgetNotFound,
|
||||
|
||||
#[error("Function already exists: {0}")]
|
||||
FunctionExists(String),
|
||||
|
||||
#[error("UDC busy")]
|
||||
UdcBusy,
|
||||
|
||||
#[error("ConfigFS error: {0}")]
|
||||
ConfigFsError(String),
|
||||
|
||||
#[error("Permission denied: {0}")]
|
||||
PermissionDenied(String),
|
||||
|
||||
#[error("Device node not found: {0}")]
|
||||
DeviceNodeNotFound(String),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 使用示例
|
||||
|
||||
### 7.1 启用 HID
|
||||
|
||||
```rust
|
||||
let otg = OtgService::new()?;
|
||||
|
||||
// 启用 HID
|
||||
let paths = otg.enable_hid().await?;
|
||||
println!("Keyboard: {:?}", paths.keyboard);
|
||||
println!("Mouse relative: {:?}", paths.mouse_relative);
|
||||
println!("Mouse absolute: {:?}", paths.mouse_absolute);
|
||||
|
||||
// 使用设备...
|
||||
|
||||
// 禁用 HID
|
||||
otg.disable_hid().await?;
|
||||
```
|
||||
|
||||
### 7.2 启用 MSD
|
||||
|
||||
```rust
|
||||
let otg = OtgService::new()?;
|
||||
|
||||
// 启用 MSD
|
||||
let mut msd = otg.enable_msd().await?;
|
||||
|
||||
// 挂载 ISO
|
||||
msd.set_image(Path::new("/data/ubuntu.iso"), true)?;
|
||||
|
||||
// 弹出
|
||||
msd.eject()?;
|
||||
|
||||
// 禁用 MSD
|
||||
otg.disable_msd().await?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 常见问题
|
||||
|
||||
### Q: 找不到 UDC 设备?
|
||||
|
||||
1. 检查内核是否支持 USB Gadget
|
||||
2. 加载必要的内核模块:
|
||||
```bash
|
||||
modprobe libcomposite
|
||||
modprobe usb_f_hid
|
||||
modprobe usb_f_mass_storage
|
||||
```
|
||||
3. 检查 `/sys/class/udc/` 目录
|
||||
|
||||
### Q: 权限错误?
|
||||
|
||||
1. 以 root 运行
|
||||
2. 或配置 udev 规则
|
||||
|
||||
### Q: 设备节点不出现?
|
||||
|
||||
1. 检查 UDC 是否正确绑定
|
||||
2. 查看 `dmesg` 日志
|
||||
3. 检查 ConfigFS 配置
|
||||
|
||||
### Q: 目标 PC 不识别?
|
||||
|
||||
1. 检查 USB 线缆
|
||||
2. 检查报告描述符
|
||||
3. 使用 `lsusb` 确认设备
|
||||
776
docs/modules/rustdesk.md
Normal file
776
docs/modules/rustdesk.md
Normal file
@@ -0,0 +1,776 @@
|
||||
# RustDesk 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
RustDesk 模块实现 RustDesk 协议集成,允许使用标准 RustDesk 客户端访问 One-KVM 设备。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- RustDesk 协议实现
|
||||
- 渲染服务器 (hbbs) 通信
|
||||
- 中继服务器 (hbbr) 通信
|
||||
- 视频/音频/HID 转换
|
||||
- 端到端加密
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/rustdesk/
|
||||
├── mod.rs # RustDeskService (21KB)
|
||||
├── connection.rs # 连接管理 (49KB)
|
||||
├── rendezvous.rs # 渲染服务器 (32KB)
|
||||
├── crypto.rs # NaCl 加密 (16KB)
|
||||
├── config.rs # 配置 (7KB)
|
||||
├── hid_adapter.rs # HID 适配 (14KB)
|
||||
├── frame_adapters.rs # 帧转换 (9KB)
|
||||
├── protocol.rs # 协议包装 (6KB)
|
||||
└── bytes_codec.rs # 帧编码 (8KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 RustDesk 网络架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ RustDesk Network Architecture │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ RustDesk │ │ One-KVM │
|
||||
│ Client │ │ Device │
|
||||
└──────┬──────┘ └──────┬──────┘
|
||||
│ │
|
||||
│ 1. 查询设备地址 │
|
||||
│─────────────────────►┌─────────────┐◄──────────────│
|
||||
│ │ hbbs │ │
|
||||
│ │ (Rendezvous)│ │
|
||||
│◄─────────────────────└─────────────┘ │
|
||||
│ 2. 返回地址 │
|
||||
│ │
|
||||
│ 3a. 直接连接 (如果可达) │
|
||||
│────────────────────────────────────────────────────│
|
||||
│ │
|
||||
│ 3b. 中继连接 (如果 NAT) │
|
||||
│─────────────────────►┌─────────────┐◄──────────────│
|
||||
│ │ hbbr │ │
|
||||
│ │ (Relay) │ │
|
||||
│◄─────────────────────└─────────────┘───────────────│
|
||||
│ │
|
||||
│ 4. 建立加密通道 │
|
||||
│◄───────────────────────────────────────────────────│
|
||||
│ │
|
||||
│ 5. 传输视频/音频/HID │
|
||||
│◄───────────────────────────────────────────────────│
|
||||
```
|
||||
|
||||
### 2.2 模块内部架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ RustDesk Module Architecture │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────┐
|
||||
│ RustDeskService │
|
||||
│ (mod.rs) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌───────────────────┼───────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Rendezvous │ │ Connection │ │ Crypto │
|
||||
│ (rendezvous) │ │ (connection) │ │ (crypto) │
|
||||
└────────┬────────┘ └────────┬────────┘ └─────────────────┘
|
||||
│ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────────────────────────┐
|
||||
│ hbbs Server │ │ Adapters │
|
||||
│ Connection │ │ ┌──────────┐ ┌──────────────────┐ │
|
||||
└─────────────────┘ │ │ HID │ │ Frame │ │
|
||||
│ │ Adapter │ │ Adapters │ │
|
||||
│ └──────────┘ └──────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────┼─────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────┐ ┌───────────┐ ┌───────────┐
|
||||
│ HID │ │ Video │ │ Audio │
|
||||
│ Controller│ │ Pipeline │ │ Pipeline │
|
||||
└───────────┘ └───────────┘ └───────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 RustDeskService (mod.rs)
|
||||
|
||||
RustDesk 服务主类。
|
||||
|
||||
```rust
|
||||
pub struct RustDeskService {
|
||||
/// 服务配置
|
||||
config: Arc<RwLock<RustDeskConfig>>,
|
||||
|
||||
/// 渲染连接
|
||||
rendezvous: Arc<RwLock<Option<RendezvousConnection>>>,
|
||||
|
||||
/// 客户端连接
|
||||
connections: Arc<RwLock<HashMap<String, Arc<ClientConnection>>>>,
|
||||
|
||||
/// 加密密钥
|
||||
keys: Arc<RustDeskKeys>,
|
||||
|
||||
/// 视频管道
|
||||
video_pipeline: Arc<SharedVideoPipeline>,
|
||||
|
||||
/// 音频管道
|
||||
audio_pipeline: Arc<SharedAudioPipeline>,
|
||||
|
||||
/// HID 控制器
|
||||
hid: Arc<HidController>,
|
||||
|
||||
/// 服务状态
|
||||
status: Arc<RwLock<ServiceStatus>>,
|
||||
|
||||
/// 事件总线
|
||||
events: Arc<EventBus>,
|
||||
}
|
||||
|
||||
impl RustDeskService {
|
||||
/// 创建服务
|
||||
pub async fn new(
|
||||
config: RustDeskConfig,
|
||||
video_pipeline: Arc<SharedVideoPipeline>,
|
||||
audio_pipeline: Arc<SharedAudioPipeline>,
|
||||
hid: Arc<HidController>,
|
||||
events: Arc<EventBus>,
|
||||
) -> Result<Arc<Self>>;
|
||||
|
||||
/// 启动服务
|
||||
pub async fn start(&self) -> Result<()>;
|
||||
|
||||
/// 停止服务
|
||||
pub async fn stop(&self) -> Result<()>;
|
||||
|
||||
/// 获取设备 ID
|
||||
pub fn device_id(&self) -> String;
|
||||
|
||||
/// 获取状态
|
||||
pub fn status(&self) -> ServiceStatus;
|
||||
|
||||
/// 更新配置
|
||||
pub async fn update_config(&self, config: RustDeskConfig) -> Result<()>;
|
||||
|
||||
/// 获取连接列表
|
||||
pub fn connections(&self) -> Vec<ConnectionInfo>;
|
||||
|
||||
/// 断开连接
|
||||
pub async fn disconnect(&self, connection_id: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
pub enum ServiceStatus {
|
||||
Stopped,
|
||||
Starting,
|
||||
Running,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
pub struct ConnectionInfo {
|
||||
pub id: String,
|
||||
pub peer_id: String,
|
||||
pub connected_at: DateTime<Utc>,
|
||||
pub ip: String,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 RendezvousConnection (rendezvous.rs)
|
||||
|
||||
渲染服务器连接管理。
|
||||
|
||||
```rust
|
||||
pub struct RendezvousConnection {
|
||||
/// 服务器地址
|
||||
server_addr: SocketAddr,
|
||||
|
||||
/// TCP 连接
|
||||
stream: TcpStream,
|
||||
|
||||
/// 设备 ID
|
||||
device_id: String,
|
||||
|
||||
/// 公钥
|
||||
public_key: [u8; 32],
|
||||
|
||||
/// 注册状态
|
||||
registered: AtomicBool,
|
||||
|
||||
/// 心跳任务
|
||||
heartbeat_task: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl RendezvousConnection {
|
||||
/// 连接到渲染服务器
|
||||
pub async fn connect(
|
||||
server: &str,
|
||||
device_id: &str,
|
||||
keys: &RustDeskKeys,
|
||||
) -> Result<Self>;
|
||||
|
||||
/// 注册设备
|
||||
pub async fn register(&self) -> Result<()>;
|
||||
|
||||
/// 发送心跳
|
||||
async fn heartbeat(&self) -> Result<()>;
|
||||
|
||||
/// 接收消息
|
||||
pub async fn recv_message(&mut self) -> Result<RendezvousMessage>;
|
||||
|
||||
/// 处理穿孔请求
|
||||
pub async fn handle_punch_request(&self, peer_id: &str) -> Result<SocketAddr>;
|
||||
}
|
||||
|
||||
pub enum RendezvousMessage {
|
||||
RegisterOk,
|
||||
PunchRequest { peer_id: String, socket_addr: SocketAddr },
|
||||
Heartbeat,
|
||||
Error(String),
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 ClientConnection (connection.rs)
|
||||
|
||||
客户端连接处理。
|
||||
|
||||
```rust
|
||||
pub struct ClientConnection {
|
||||
/// 连接 ID
|
||||
id: String,
|
||||
|
||||
/// 对端 ID
|
||||
peer_id: String,
|
||||
|
||||
/// 加密通道
|
||||
channel: EncryptedChannel,
|
||||
|
||||
/// 帧适配器
|
||||
frame_adapter: FrameAdapter,
|
||||
|
||||
/// HID 适配器
|
||||
hid_adapter: HidAdapter,
|
||||
|
||||
/// 状态
|
||||
state: Arc<RwLock<ConnectionState>>,
|
||||
}
|
||||
|
||||
impl ClientConnection {
|
||||
/// 创建连接
|
||||
pub async fn new(
|
||||
stream: TcpStream,
|
||||
keys: &RustDeskKeys,
|
||||
peer_public_key: &[u8],
|
||||
) -> Result<Self>;
|
||||
|
||||
/// 处理连接
|
||||
pub async fn handle(
|
||||
&self,
|
||||
video_rx: broadcast::Receiver<EncodedFrame>,
|
||||
audio_rx: broadcast::Receiver<AudioFrame>,
|
||||
hid: Arc<HidController>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// 发送视频帧
|
||||
async fn send_video_frame(&self, frame: &EncodedFrame) -> Result<()>;
|
||||
|
||||
/// 发送音频帧
|
||||
async fn send_audio_frame(&self, frame: &AudioFrame) -> Result<()>;
|
||||
|
||||
/// 处理输入事件
|
||||
async fn handle_input(&self, msg: &InputMessage) -> Result<()>;
|
||||
|
||||
/// 关闭连接
|
||||
pub async fn close(&self) -> Result<()>;
|
||||
}
|
||||
|
||||
pub enum ConnectionState {
|
||||
Handshaking,
|
||||
Authenticating,
|
||||
Connected,
|
||||
Closing,
|
||||
Closed,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 RustDeskKeys (crypto.rs)
|
||||
|
||||
加密密钥管理。
|
||||
|
||||
```rust
|
||||
pub struct RustDeskKeys {
|
||||
/// 设备 ID
|
||||
pub device_id: String,
|
||||
|
||||
/// Curve25519 公钥
|
||||
pub public_key: [u8; 32],
|
||||
|
||||
/// Curve25519 私钥
|
||||
secret_key: [u8; 32],
|
||||
|
||||
/// Ed25519 签名公钥
|
||||
pub sign_public_key: [u8; 32],
|
||||
|
||||
/// Ed25519 签名私钥
|
||||
sign_secret_key: [u8; 64],
|
||||
}
|
||||
|
||||
impl RustDeskKeys {
|
||||
/// 生成新密钥
|
||||
pub fn generate() -> Self;
|
||||
|
||||
/// 从配置加载
|
||||
pub fn from_config(config: &KeyConfig) -> Result<Self>;
|
||||
|
||||
/// 保存到配置
|
||||
pub fn to_config(&self) -> KeyConfig;
|
||||
|
||||
/// 计算共享密钥
|
||||
pub fn shared_secret(&self, peer_public_key: &[u8; 32]) -> [u8; 32];
|
||||
|
||||
/// 签名消息
|
||||
pub fn sign(&self, message: &[u8]) -> [u8; 64];
|
||||
|
||||
/// 验证签名
|
||||
pub fn verify(public_key: &[u8; 32], message: &[u8], signature: &[u8; 64]) -> bool;
|
||||
}
|
||||
|
||||
pub struct EncryptedChannel {
|
||||
/// 发送密钥
|
||||
send_key: [u8; 32],
|
||||
|
||||
/// 接收密钥
|
||||
recv_key: [u8; 32],
|
||||
|
||||
/// 发送 nonce
|
||||
send_nonce: AtomicU64,
|
||||
|
||||
/// 接收 nonce
|
||||
recv_nonce: AtomicU64,
|
||||
}
|
||||
|
||||
impl EncryptedChannel {
|
||||
/// 加密消息
|
||||
pub fn encrypt(&self, plaintext: &[u8]) -> Vec<u8>;
|
||||
|
||||
/// 解密消息
|
||||
pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 HidAdapter (hid_adapter.rs)
|
||||
|
||||
RustDesk HID 事件转换。
|
||||
|
||||
```rust
|
||||
pub struct HidAdapter {
|
||||
hid: Arc<HidController>,
|
||||
}
|
||||
|
||||
impl HidAdapter {
|
||||
/// 创建适配器
|
||||
pub fn new(hid: Arc<HidController>) -> Self;
|
||||
|
||||
/// 处理键盘事件
|
||||
pub async fn handle_keyboard(&self, event: &RdKeyboardEvent) -> Result<()>;
|
||||
|
||||
/// 处理鼠标事件
|
||||
pub async fn handle_mouse(&self, event: &RdMouseEvent) -> Result<()>;
|
||||
|
||||
/// 转换键码
|
||||
fn convert_keycode(rd_key: u32) -> Option<KeyCode>;
|
||||
|
||||
/// 转换鼠标按钮
|
||||
fn convert_button(rd_button: u32) -> Option<MouseButton>;
|
||||
}
|
||||
|
||||
/// RustDesk 键盘事件
|
||||
pub struct RdKeyboardEvent {
|
||||
pub keycode: u32,
|
||||
pub down: bool,
|
||||
pub modifiers: u32,
|
||||
}
|
||||
|
||||
/// RustDesk 鼠标事件
|
||||
pub struct RdMouseEvent {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub mask: u32,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 FrameAdapter (frame_adapters.rs)
|
||||
|
||||
帧格式转换。
|
||||
|
||||
```rust
|
||||
pub struct FrameAdapter;
|
||||
|
||||
impl FrameAdapter {
|
||||
/// 转换视频帧到 RustDesk 格式
|
||||
pub fn to_rd_video_frame(frame: &EncodedFrame) -> RdVideoFrame;
|
||||
|
||||
/// 转换音频帧到 RustDesk 格式
|
||||
pub fn to_rd_audio_frame(frame: &AudioFrame) -> RdAudioFrame;
|
||||
}
|
||||
|
||||
/// RustDesk 视频帧
|
||||
pub struct RdVideoFrame {
|
||||
pub data: Vec<u8>,
|
||||
pub key_frame: bool,
|
||||
pub pts: i64,
|
||||
pub format: RdVideoFormat,
|
||||
}
|
||||
|
||||
pub enum RdVideoFormat {
|
||||
H264,
|
||||
H265,
|
||||
VP8,
|
||||
VP9,
|
||||
}
|
||||
|
||||
/// RustDesk 音频帧
|
||||
pub struct RdAudioFrame {
|
||||
pub data: Vec<u8>,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.7 协议消息 (protocol.rs)
|
||||
|
||||
Protobuf 消息包装。
|
||||
|
||||
```rust
|
||||
/// 使用 prost 生成的 protobuf 消息
|
||||
pub mod proto {
|
||||
include!(concat!(env!("OUT_DIR"), "/rendezvous.rs"));
|
||||
include!(concat!(env!("OUT_DIR"), "/message.rs"));
|
||||
}
|
||||
|
||||
pub struct MessageCodec;
|
||||
|
||||
impl MessageCodec {
|
||||
/// 编码消息
|
||||
pub fn encode<M: prost::Message>(msg: &M) -> Vec<u8>;
|
||||
|
||||
/// 解码消息
|
||||
pub fn decode<M: prost::Message + Default>(data: &[u8]) -> Result<M>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.8 帧编码 (bytes_codec.rs)
|
||||
|
||||
变长帧协议。
|
||||
|
||||
```rust
|
||||
pub struct BytesCodec {
|
||||
state: DecodeState,
|
||||
buffer: BytesMut,
|
||||
}
|
||||
|
||||
impl BytesCodec {
|
||||
/// 编码帧
|
||||
pub fn encode_frame(data: &[u8]) -> Vec<u8> {
|
||||
let mut buf = Vec::with_capacity(4 + data.len());
|
||||
buf.extend_from_slice(&(data.len() as u32).to_be_bytes());
|
||||
buf.extend_from_slice(data);
|
||||
buf
|
||||
}
|
||||
|
||||
/// 解码帧
|
||||
pub fn decode_frame(&mut self, src: &mut BytesMut) -> Result<Option<Bytes>>;
|
||||
}
|
||||
|
||||
enum DecodeState {
|
||||
Length,
|
||||
Data(usize),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 协议详解
|
||||
|
||||
### 4.1 Protobuf 定义
|
||||
|
||||
```protobuf
|
||||
// protos/rendezvous.proto
|
||||
message RegisterPeer {
|
||||
string id = 1;
|
||||
bytes public_key = 2;
|
||||
}
|
||||
|
||||
message RegisterPeerResponse {
|
||||
bool ok = 1;
|
||||
string error = 2;
|
||||
}
|
||||
|
||||
message PunchHoleRequest {
|
||||
string id = 1;
|
||||
string nat_type = 2;
|
||||
}
|
||||
|
||||
// protos/message.proto
|
||||
message VideoFrame {
|
||||
bytes data = 1;
|
||||
bool key = 2;
|
||||
int64 pts = 3;
|
||||
VideoCodec codec = 4;
|
||||
}
|
||||
|
||||
message AudioFrame {
|
||||
bytes data = 1;
|
||||
int64 timestamp = 2;
|
||||
}
|
||||
|
||||
message KeyboardEvent {
|
||||
uint32 keycode = 1;
|
||||
bool down = 2;
|
||||
uint32 modifiers = 3;
|
||||
}
|
||||
|
||||
message MouseEvent {
|
||||
int32 x = 1;
|
||||
int32 y = 2;
|
||||
uint32 mask = 3;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 连接握手
|
||||
|
||||
```
|
||||
1. TCP 连接
|
||||
Client ────► Device
|
||||
|
||||
2. 公钥交换
|
||||
Client ◄───► Device
|
||||
|
||||
3. DH 密钥协商
|
||||
shared_secret = X25519(my_private, peer_public)
|
||||
|
||||
4. 密钥派生
|
||||
send_key = HKDF(shared_secret, "send")
|
||||
recv_key = HKDF(shared_secret, "recv")
|
||||
|
||||
5. 认证 (可选)
|
||||
Client ────► Device: encrypted(password)
|
||||
Client ◄──── Device: encrypted(ok/fail)
|
||||
|
||||
6. 开始传输
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct RustDeskConfig {
|
||||
/// 是否启用
|
||||
pub enabled: bool,
|
||||
|
||||
/// 渲染服务器地址
|
||||
pub rendezvous_server: String,
|
||||
|
||||
/// 中继服务器地址
|
||||
pub relay_server: Option<String>,
|
||||
|
||||
/// 设备 ID (自动生成)
|
||||
pub device_id: Option<String>,
|
||||
|
||||
/// 访问密码
|
||||
pub password: Option<String>,
|
||||
|
||||
/// 允许的客户端 ID
|
||||
pub allowed_clients: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for RustDeskConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
rendezvous_server: "rs-ny.rustdesk.com:21116".to_string(),
|
||||
relay_server: None,
|
||||
device_id: None,
|
||||
password: None,
|
||||
allowed_clients: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API 端点
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/rustdesk/status` | GET | 获取服务状态 |
|
||||
| `/api/rustdesk/start` | POST | 启动服务 |
|
||||
| `/api/rustdesk/stop` | POST | 停止服务 |
|
||||
| `/api/rustdesk/config` | GET | 获取配置 |
|
||||
| `/api/rustdesk/config` | PATCH | 更新配置 |
|
||||
| `/api/rustdesk/device-id` | GET | 获取设备 ID |
|
||||
| `/api/rustdesk/connections` | GET | 获取连接列表 |
|
||||
| `/api/rustdesk/connections/:id` | DELETE | 断开连接 |
|
||||
|
||||
### 响应格式
|
||||
|
||||
```json
|
||||
// GET /api/rustdesk/status
|
||||
{
|
||||
"status": "running",
|
||||
"device_id": "123456789",
|
||||
"rendezvous_connected": true,
|
||||
"active_connections": 1
|
||||
}
|
||||
|
||||
// GET /api/rustdesk/connections
|
||||
{
|
||||
"connections": [
|
||||
{
|
||||
"id": "conn-abc",
|
||||
"peer_id": "987654321",
|
||||
"connected_at": "2024-01-15T10:30:00Z",
|
||||
"ip": "192.168.1.100"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 事件
|
||||
|
||||
```rust
|
||||
pub enum SystemEvent {
|
||||
RustDeskStatusChanged {
|
||||
status: String,
|
||||
device_id: Option<String>,
|
||||
error: Option<String>,
|
||||
},
|
||||
|
||||
RustDeskConnectionOpened {
|
||||
connection_id: String,
|
||||
peer_id: String,
|
||||
},
|
||||
|
||||
RustDeskConnectionClosed {
|
||||
connection_id: String,
|
||||
peer_id: String,
|
||||
reason: String,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 错误处理
|
||||
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RustDeskError {
|
||||
#[error("Service not running")]
|
||||
NotRunning,
|
||||
|
||||
#[error("Already running")]
|
||||
AlreadyRunning,
|
||||
|
||||
#[error("Rendezvous connection failed: {0}")]
|
||||
RendezvousFailed(String),
|
||||
|
||||
#[error("Authentication failed")]
|
||||
AuthFailed,
|
||||
|
||||
#[error("Connection refused")]
|
||||
ConnectionRefused,
|
||||
|
||||
#[error("Encryption error: {0}")]
|
||||
EncryptionError(String),
|
||||
|
||||
#[error("Protocol error: {0}")]
|
||||
ProtocolError(String),
|
||||
|
||||
#[error("Timeout")]
|
||||
Timeout,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 使用示例
|
||||
|
||||
### 9.1 启动服务
|
||||
|
||||
```rust
|
||||
let config = RustDeskConfig {
|
||||
enabled: true,
|
||||
rendezvous_server: "rs-ny.rustdesk.com:21116".to_string(),
|
||||
password: Some("mypassword".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let service = RustDeskService::new(
|
||||
config,
|
||||
video_pipeline,
|
||||
audio_pipeline,
|
||||
hid,
|
||||
events,
|
||||
).await?;
|
||||
|
||||
service.start().await?;
|
||||
|
||||
println!("Device ID: {}", service.device_id());
|
||||
```
|
||||
|
||||
### 9.2 客户端连接
|
||||
|
||||
```
|
||||
1. 打开 RustDesk 客户端
|
||||
2. 输入设备 ID
|
||||
3. 输入密码 (如果设置)
|
||||
4. 连接成功后即可控制
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 常见问题
|
||||
|
||||
### Q: 无法连接到渲染服务器?
|
||||
|
||||
1. 检查网络连接
|
||||
2. 检查服务器地址
|
||||
3. 检查防火墙
|
||||
|
||||
### Q: 客户端连接失败?
|
||||
|
||||
1. 检查设备 ID
|
||||
2. 检查密码
|
||||
3. 检查 NAT 穿透
|
||||
|
||||
### Q: 视频延迟高?
|
||||
|
||||
1. 使用更近的中继服务器
|
||||
2. 检查网络带宽
|
||||
3. 降低视频质量
|
||||
|
||||
### Q: 如何自建服务器?
|
||||
|
||||
参考 RustDesk Server 部署文档:
|
||||
- hbbs: 渲染服务器
|
||||
- hbbr: 中继服务器
|
||||
895
docs/modules/video.md
Normal file
895
docs/modules/video.md
Normal file
@@ -0,0 +1,895 @@
|
||||
# Video 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
Video 模块负责视频采集、编码和流传输,是 One-KVM 的核心功能模块。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- V4L2 视频设备采集
|
||||
- 多格式像素转换
|
||||
- 硬件/软件视频编码
|
||||
- MJPEG 和 WebRTC 流传输
|
||||
- 帧去重和质量控制
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/video/
|
||||
├── mod.rs # 模块导出
|
||||
├── capture.rs # V4L2 视频采集 (22KB)
|
||||
├── streamer.rs # 视频流服务 (34KB)
|
||||
├── stream_manager.rs # 流管理器 (24KB)
|
||||
├── shared_video_pipeline.rs # 共享视频管道 (35KB)
|
||||
├── h264_pipeline.rs # H264 编码管道 (22KB)
|
||||
├── format.rs # 像素格式定义 (9KB)
|
||||
├── frame.rs # 视频帧结构 (6KB)
|
||||
├── convert.rs # 格式转换 (21KB)
|
||||
└── encoder/ # 编码器
|
||||
├── mod.rs
|
||||
├── traits.rs # Encoder trait
|
||||
├── h264.rs # H264 编码
|
||||
├── h265.rs # H265 编码
|
||||
├── vp8.rs # VP8 编码
|
||||
├── vp9.rs # VP9 编码
|
||||
├── jpeg.rs # JPEG 编码
|
||||
└── registry.rs # 编码器注册表
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 数据流
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Video Data Flow │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
V4L2 Device (/dev/video0)
|
||||
│
|
||||
│ Raw frames (MJPEG/YUYV/NV12)
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ VideoCapturer │ ◄─── capture.rs
|
||||
│ - open_device() │
|
||||
│ - read_frame() │
|
||||
│ - set_format() │
|
||||
└─────────┬─────────┘
|
||||
│ VideoFrame
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ Streamer │ ◄─── streamer.rs
|
||||
│ - start() │
|
||||
│ - stop() │
|
||||
│ - get_info() │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
┌─────┴─────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────┐ ┌────────────────────────────┐
|
||||
│ MJPEG │ │ SharedVideoPipeline │
|
||||
│ Mode │ │ - Decode (MJPEG→YUV) │
|
||||
│ │ │ - Convert (YUV→target) │
|
||||
│ │ │ - Encode (H264/H265/VP8) │
|
||||
└────────┘ └─────────────┬──────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────┐ ┌────────┐
|
||||
│ HTTP │ │ WebRTC │
|
||||
│ Stream │ │ RTP │
|
||||
└────────┘ └────────┘
|
||||
```
|
||||
|
||||
### 2.2 组件关系
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Component Relationships │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
VideoStreamManager (stream_manager.rs)
|
||||
│
|
||||
├──► Streamer (MJPEG mode)
|
||||
│ └──► VideoCapturer
|
||||
│
|
||||
└──► WebRtcStreamer (WebRTC mode)
|
||||
└──► SharedVideoPipeline
|
||||
├──► VideoCapturer
|
||||
├──► MjpegDecoder
|
||||
├──► YuvConverter
|
||||
└──► Encoders[]
|
||||
├── H264Encoder
|
||||
├── H265Encoder
|
||||
├── VP8Encoder
|
||||
└── VP9Encoder
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 VideoCapturer (capture.rs)
|
||||
|
||||
V4L2 视频采集器,负责从摄像头/采集卡读取视频帧。
|
||||
|
||||
#### 主要接口
|
||||
|
||||
```rust
|
||||
pub struct VideoCapturer {
|
||||
device: Device,
|
||||
stream: Option<MmapStream<'static>>,
|
||||
config: CaptureConfig,
|
||||
format: PixelFormat,
|
||||
resolution: Resolution,
|
||||
}
|
||||
|
||||
impl VideoCapturer {
|
||||
/// 打开视频设备
|
||||
pub fn open(device_path: &str) -> Result<Self>;
|
||||
|
||||
/// 设置视频格式
|
||||
pub fn set_format(&mut self, config: &CaptureConfig) -> Result<()>;
|
||||
|
||||
/// 开始采集
|
||||
pub fn start(&mut self) -> Result<()>;
|
||||
|
||||
/// 停止采集
|
||||
pub fn stop(&mut self) -> Result<()>;
|
||||
|
||||
/// 读取一帧
|
||||
pub fn read_frame(&mut self) -> Result<VideoFrame>;
|
||||
|
||||
/// 列出设备支持的格式
|
||||
pub fn list_formats(&self) -> Vec<FormatInfo>;
|
||||
|
||||
/// 列出支持的分辨率
|
||||
pub fn list_resolutions(&self, format: PixelFormat) -> Vec<Resolution>;
|
||||
}
|
||||
```
|
||||
|
||||
#### 采集配置
|
||||
|
||||
```rust
|
||||
pub struct CaptureConfig {
|
||||
pub device: String, // /dev/video0
|
||||
pub width: u32, // 1920
|
||||
pub height: u32, // 1080
|
||||
pub fps: u32, // 30
|
||||
pub format: Option<PixelFormat>, // 优先格式
|
||||
pub buffer_count: u32, // 4
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```rust
|
||||
// 打开设备
|
||||
let mut capturer = VideoCapturer::open("/dev/video0")?;
|
||||
|
||||
// 设置格式
|
||||
capturer.set_format(&CaptureConfig {
|
||||
device: "/dev/video0".to_string(),
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
fps: 30,
|
||||
format: Some(PixelFormat::Mjpeg),
|
||||
buffer_count: 4,
|
||||
})?;
|
||||
|
||||
// 开始采集
|
||||
capturer.start()?;
|
||||
|
||||
// 读取帧
|
||||
loop {
|
||||
let frame = capturer.read_frame()?;
|
||||
process_frame(frame);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 VideoFrame (frame.rs)
|
||||
|
||||
视频帧数据结构,支持零拷贝和帧去重。
|
||||
|
||||
```rust
|
||||
pub struct VideoFrame {
|
||||
/// 帧数据 (引用计数)
|
||||
data: Arc<Bytes>,
|
||||
|
||||
/// xxHash64 缓存 (用于去重)
|
||||
hash: Arc<OnceLock<u64>>,
|
||||
|
||||
/// 分辨率
|
||||
resolution: Resolution,
|
||||
|
||||
/// 像素格式
|
||||
format: PixelFormat,
|
||||
|
||||
/// 行步长
|
||||
stride: u32,
|
||||
|
||||
/// 是否关键帧
|
||||
key_frame: bool,
|
||||
|
||||
/// 帧序号
|
||||
sequence: u64,
|
||||
|
||||
/// 采集时间戳
|
||||
capture_ts: Instant,
|
||||
|
||||
/// 是否有信号
|
||||
online: bool,
|
||||
}
|
||||
|
||||
impl VideoFrame {
|
||||
/// 创建新帧
|
||||
pub fn new(data: Bytes, resolution: Resolution, format: PixelFormat) -> Self;
|
||||
|
||||
/// 获取帧数据
|
||||
pub fn data(&self) -> &[u8];
|
||||
|
||||
/// 计算帧哈希 (懒加载)
|
||||
pub fn hash(&self) -> u64;
|
||||
|
||||
/// 检查帧是否相同 (用于去重)
|
||||
pub fn is_same_as(&self, other: &Self) -> bool;
|
||||
|
||||
/// 克隆帧 (零拷贝)
|
||||
pub fn clone_ref(&self) -> Self;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 PixelFormat (format.rs)
|
||||
|
||||
支持的像素格式定义。
|
||||
|
||||
```rust
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum PixelFormat {
|
||||
// 压缩格式
|
||||
Mjpeg, // Motion JPEG (优先级: 100)
|
||||
Jpeg, // Static JPEG (优先级: 99)
|
||||
|
||||
// YUV 4:2:2 打包格式
|
||||
Yuyv, // YUYV/YUY2 (优先级: 80)
|
||||
Yvyu, // YVYU (优先级: 64)
|
||||
Uyvy, // UYVY (优先级: 65)
|
||||
|
||||
// YUV 半平面格式
|
||||
Nv12, // NV12 (优先级: 75)
|
||||
Nv16, // NV16 (优先级: 60)
|
||||
Nv24, // NV24 (优先级: 55)
|
||||
|
||||
// YUV 平面格式
|
||||
Yuv420, // I420/YU12 (优先级: 70)
|
||||
Yvu420, // YV12 (优先级: 63)
|
||||
|
||||
// RGB 格式
|
||||
Rgb565, // RGB565 (优先级: 40)
|
||||
Rgb24, // RGB24 (优先级: 50)
|
||||
Bgr24, // BGR24 (优先级: 49)
|
||||
|
||||
// 灰度
|
||||
Grey, // 8-bit grayscale (优先级: 10)
|
||||
}
|
||||
|
||||
impl PixelFormat {
|
||||
/// 获取格式优先级 (越高越好)
|
||||
pub fn priority(&self) -> u32;
|
||||
|
||||
/// 计算帧大小
|
||||
pub fn frame_size(&self, width: u32, height: u32) -> usize;
|
||||
|
||||
/// 转换为 V4L2 FourCC
|
||||
pub fn to_fourcc(&self) -> u32;
|
||||
|
||||
/// 从 V4L2 FourCC 转换
|
||||
pub fn from_fourcc(fourcc: u32) -> Option<Self>;
|
||||
|
||||
/// 是否压缩格式
|
||||
pub fn is_compressed(&self) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 SharedVideoPipeline (shared_video_pipeline.rs)
|
||||
|
||||
多会话共享的视频编码管道。
|
||||
|
||||
```rust
|
||||
pub struct SharedVideoPipeline {
|
||||
/// 视频采集器
|
||||
capturer: Arc<Mutex<VideoCapturer>>,
|
||||
|
||||
/// MJPEG 解码器
|
||||
decoder: MjpegDecoder,
|
||||
|
||||
/// YUV 转换器
|
||||
converter: YuvConverter,
|
||||
|
||||
/// 编码器实例
|
||||
encoders: HashMap<VideoCodec, Box<dyn Encoder>>,
|
||||
|
||||
/// 活跃会话
|
||||
sessions: Arc<RwLock<Vec<SessionSender>>>,
|
||||
|
||||
/// 配置
|
||||
config: PipelineConfig,
|
||||
}
|
||||
|
||||
impl SharedVideoPipeline {
|
||||
/// 创建管道
|
||||
pub async fn new(config: PipelineConfig) -> Result<Self>;
|
||||
|
||||
/// 启动管道
|
||||
pub async fn start(&self) -> Result<()>;
|
||||
|
||||
/// 停止管道
|
||||
pub async fn stop(&self) -> Result<()>;
|
||||
|
||||
/// 添加会话订阅
|
||||
pub fn subscribe(&self, codec: VideoCodec) -> Receiver<EncodedFrame>;
|
||||
|
||||
/// 移除会话订阅
|
||||
pub fn unsubscribe(&self, session_id: &str);
|
||||
|
||||
/// 编码单帧 (多编码器)
|
||||
async fn encode_frame(&self, frame: VideoFrame) -> Result<()>;
|
||||
}
|
||||
```
|
||||
|
||||
#### 编码流程
|
||||
|
||||
```
|
||||
Input: VideoFrame (MJPEG)
|
||||
│
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ MJPEG Decode │ turbojpeg / VAAPI
|
||||
│ MJPEG → YUV420 │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ YUV Convert │ libyuv (SIMD)
|
||||
│ YUV420 → target │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
┌─────┴─────┬─────────┬─────────┐
|
||||
│ │ │ │
|
||||
▼ ▼ ▼ ▼
|
||||
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
|
||||
│ H264 │ │ H265 │ │ VP8 │ │ VP9 │
|
||||
│Encoder│ │Encoder│ │Encoder│ │Encoder│
|
||||
└───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘
|
||||
│ │ │ │
|
||||
└──────────┴──────────┴──────────┘
|
||||
│
|
||||
▼
|
||||
EncodedFrame[]
|
||||
(distribute to sessions)
|
||||
```
|
||||
|
||||
### 3.5 Streamer (streamer.rs)
|
||||
|
||||
高层视频流服务,管理采集和分发。
|
||||
|
||||
```rust
|
||||
pub struct Streamer {
|
||||
/// 采集器
|
||||
capturer: Option<Arc<Mutex<VideoCapturer>>>,
|
||||
|
||||
/// 采集任务句柄
|
||||
capture_task: Option<JoinHandle<()>>,
|
||||
|
||||
/// 帧广播通道
|
||||
frame_tx: broadcast::Sender<VideoFrame>,
|
||||
|
||||
/// 状态
|
||||
state: Arc<RwLock<StreamerState>>,
|
||||
|
||||
/// 配置
|
||||
config: StreamerConfig,
|
||||
|
||||
/// 事件总线
|
||||
events: Arc<EventBus>,
|
||||
}
|
||||
|
||||
impl Streamer {
|
||||
/// 创建流服务
|
||||
pub fn new(events: Arc<EventBus>) -> Self;
|
||||
|
||||
/// 启动流
|
||||
pub async fn start(&self, config: StreamerConfig) -> Result<()>;
|
||||
|
||||
/// 停止流
|
||||
pub async fn stop(&self) -> Result<()>;
|
||||
|
||||
/// 订阅帧
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<VideoFrame>;
|
||||
|
||||
/// 获取状态
|
||||
pub fn state(&self) -> StreamerState;
|
||||
|
||||
/// 获取信息
|
||||
pub fn get_info(&self) -> StreamerInfo;
|
||||
|
||||
/// 应用配置
|
||||
pub async fn apply_config(&self, config: StreamerConfig) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct StreamerState {
|
||||
pub status: StreamStatus,
|
||||
pub device: Option<String>,
|
||||
pub resolution: Option<Resolution>,
|
||||
pub format: Option<PixelFormat>,
|
||||
pub fps: f32,
|
||||
pub frame_count: u64,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
pub enum StreamStatus {
|
||||
Idle,
|
||||
Starting,
|
||||
Streaming,
|
||||
Stopping,
|
||||
Error,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 VideoStreamManager (stream_manager.rs)
|
||||
|
||||
统一管理 MJPEG 和 WebRTC 流模式。
|
||||
|
||||
```rust
|
||||
pub struct VideoStreamManager {
|
||||
/// MJPEG 流服务
|
||||
mjpeg_streamer: Arc<Streamer>,
|
||||
|
||||
/// WebRTC 流服务
|
||||
webrtc_streamer: Arc<RwLock<Option<WebRtcStreamer>>>,
|
||||
|
||||
/// 当前模式
|
||||
mode: Arc<RwLock<StreamMode>>,
|
||||
|
||||
/// 配置存储
|
||||
config_store: ConfigStore,
|
||||
|
||||
/// 事件总线
|
||||
events: Arc<EventBus>,
|
||||
}
|
||||
|
||||
impl VideoStreamManager {
|
||||
/// 创建管理器
|
||||
pub fn new(config_store: ConfigStore, events: Arc<EventBus>) -> Self;
|
||||
|
||||
/// 启动流
|
||||
pub async fn start(&self) -> Result<()>;
|
||||
|
||||
/// 停止流
|
||||
pub async fn stop(&self) -> Result<()>;
|
||||
|
||||
/// 切换模式
|
||||
pub async fn set_mode(&self, mode: StreamMode) -> Result<()>;
|
||||
|
||||
/// 获取当前模式
|
||||
pub fn get_mode(&self) -> StreamMode;
|
||||
|
||||
/// 获取设备列表
|
||||
pub fn list_devices(&self) -> Vec<DeviceInfo>;
|
||||
|
||||
/// 获取统计信息
|
||||
pub fn get_stats(&self) -> StreamStats;
|
||||
|
||||
/// 获取 MJPEG 订阅
|
||||
pub fn subscribe_mjpeg(&self) -> broadcast::Receiver<VideoFrame>;
|
||||
|
||||
/// 创建 WebRTC 会话
|
||||
pub async fn create_webrtc_session(&self, params: SessionParams) -> Result<Session>;
|
||||
}
|
||||
|
||||
pub enum StreamMode {
|
||||
Mjpeg,
|
||||
Webrtc,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 编码器系统
|
||||
|
||||
### 4.1 Encoder Trait (encoder/traits.rs)
|
||||
|
||||
```rust
|
||||
pub trait Encoder: Send + Sync {
|
||||
/// 编码一帧
|
||||
fn encode(&mut self, frame: &VideoFrame) -> Result<EncodedFrame>;
|
||||
|
||||
/// 获取编码器类型
|
||||
fn codec(&self) -> VideoCodec;
|
||||
|
||||
/// 获取当前码率
|
||||
fn bitrate(&self) -> u32;
|
||||
|
||||
/// 设置码率
|
||||
fn set_bitrate(&mut self, bitrate: u32) -> Result<()>;
|
||||
|
||||
/// 获取 GOP 大小
|
||||
fn gop_size(&self) -> u32;
|
||||
|
||||
/// 强制关键帧
|
||||
fn force_keyframe(&mut self);
|
||||
|
||||
/// 重置编码器
|
||||
fn reset(&mut self) -> Result<()>;
|
||||
|
||||
/// 获取编码器信息
|
||||
fn info(&self) -> EncoderInfo;
|
||||
}
|
||||
|
||||
pub struct EncodedFrame {
|
||||
pub data: Bytes,
|
||||
pub codec: VideoCodec,
|
||||
pub key_frame: bool,
|
||||
pub pts: u64,
|
||||
pub dts: u64,
|
||||
}
|
||||
|
||||
pub enum VideoCodec {
|
||||
H264,
|
||||
H265,
|
||||
VP8,
|
||||
VP9,
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 编码器优先级
|
||||
|
||||
```
|
||||
H264 编码器选择顺序:
|
||||
1. VAAPI (Intel/AMD GPU)
|
||||
2. RKMPP (Rockchip)
|
||||
3. V4L2 M2M
|
||||
4. x264 (Software)
|
||||
|
||||
H265 编码器选择顺序:
|
||||
1. VAAPI
|
||||
2. RKMPP
|
||||
(无软件后备)
|
||||
|
||||
VP8/VP9 编码器:
|
||||
1. VAAPI only
|
||||
```
|
||||
|
||||
### 4.3 EncoderRegistry (encoder/registry.rs)
|
||||
|
||||
```rust
|
||||
pub struct EncoderRegistry {
|
||||
/// 已注册的编码器工厂
|
||||
factories: HashMap<VideoCodec, Vec<EncoderFactory>>,
|
||||
}
|
||||
|
||||
impl EncoderRegistry {
|
||||
/// 创建注册表
|
||||
pub fn new() -> Self;
|
||||
|
||||
/// 注册编码器工厂
|
||||
pub fn register(&mut self, codec: VideoCodec, factory: EncoderFactory);
|
||||
|
||||
/// 创建最佳编码器
|
||||
pub fn create_encoder(&self, codec: VideoCodec, config: EncoderConfig) -> Result<Box<dyn Encoder>>;
|
||||
|
||||
/// 列出可用编码器
|
||||
pub fn list_available(&self, codec: VideoCodec) -> Vec<EncoderInfo>;
|
||||
|
||||
/// 探测硬件能力
|
||||
pub fn probe_hardware() -> HardwareCapabilities;
|
||||
}
|
||||
|
||||
pub struct EncoderFactory {
|
||||
pub name: String,
|
||||
pub priority: u32,
|
||||
pub create: Box<dyn Fn(EncoderConfig) -> Result<Box<dyn Encoder>>>,
|
||||
pub probe: Box<dyn Fn() -> bool>,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 格式转换
|
||||
|
||||
### 5.1 MjpegDecoder (convert.rs)
|
||||
|
||||
```rust
|
||||
pub struct MjpegDecoder {
|
||||
/// turbojpeg 解压缩器
|
||||
decompressor: Decompressor,
|
||||
|
||||
/// 输出缓冲区
|
||||
output_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl MjpegDecoder {
|
||||
/// 创建解码器
|
||||
pub fn new() -> Result<Self>;
|
||||
|
||||
/// 解码 MJPEG 到 YUV420
|
||||
pub fn decode(&mut self, jpeg_data: &[u8]) -> Result<YuvFrame>;
|
||||
|
||||
/// 获取图像信息
|
||||
pub fn get_info(jpeg_data: &[u8]) -> Result<ImageInfo>;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 YuvConverter (convert.rs)
|
||||
|
||||
使用 libyuv 进行高性能格式转换。
|
||||
|
||||
```rust
|
||||
pub struct YuvConverter;
|
||||
|
||||
impl YuvConverter {
|
||||
/// YUYV → YUV420
|
||||
pub fn yuyv_to_yuv420(src: &[u8], dst: &mut [u8], width: u32, height: u32);
|
||||
|
||||
/// NV12 → YUV420
|
||||
pub fn nv12_to_yuv420(src: &[u8], dst: &mut [u8], width: u32, height: u32);
|
||||
|
||||
/// RGB24 → YUV420
|
||||
pub fn rgb24_to_yuv420(src: &[u8], dst: &mut [u8], width: u32, height: u32);
|
||||
|
||||
/// YUV420 → NV12
|
||||
pub fn yuv420_to_nv12(src: &[u8], dst: &mut [u8], width: u32, height: u32);
|
||||
|
||||
/// 缩放 YUV420
|
||||
pub fn scale_yuv420(
|
||||
src: &[u8], src_width: u32, src_height: u32,
|
||||
dst: &mut [u8], dst_width: u32, dst_height: u32,
|
||||
filter: ScaleFilter,
|
||||
);
|
||||
}
|
||||
|
||||
pub enum ScaleFilter {
|
||||
None, // 最近邻
|
||||
Linear, // 双线性
|
||||
Bilinear, // 双线性 (同 Linear)
|
||||
Box, // 盒式滤波
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 配置说明
|
||||
|
||||
### 6.1 视频配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct VideoConfig {
|
||||
/// 设备路径 (/dev/video0)
|
||||
pub device: Option<String>,
|
||||
|
||||
/// 像素格式 (MJPEG/YUYV/NV12)
|
||||
pub format: Option<String>,
|
||||
|
||||
/// 宽度
|
||||
pub width: u32,
|
||||
|
||||
/// 高度
|
||||
pub height: u32,
|
||||
|
||||
/// 帧率
|
||||
pub fps: u32,
|
||||
|
||||
/// JPEG 质量 (1-100)
|
||||
pub quality: u32,
|
||||
}
|
||||
|
||||
impl Default for VideoConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
device: None,
|
||||
format: None,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
fps: 30,
|
||||
quality: 80,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 流配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct StreamConfig {
|
||||
/// 流模式
|
||||
pub mode: StreamMode,
|
||||
|
||||
/// 码率 (kbps)
|
||||
pub bitrate_kbps: u32,
|
||||
|
||||
/// GOP 大小
|
||||
pub gop_size: u32,
|
||||
|
||||
/// 编码器类型
|
||||
pub encoder: EncoderType,
|
||||
|
||||
/// STUN 服务器
|
||||
pub stun_server: Option<String>,
|
||||
|
||||
/// TURN 服务器
|
||||
pub turn_server: Option<String>,
|
||||
|
||||
/// TURN 用户名
|
||||
pub turn_username: Option<String>,
|
||||
|
||||
/// TURN 密码
|
||||
pub turn_password: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. API 端点
|
||||
|
||||
### 7.1 流控制
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/stream/status` | GET | 获取流状态 |
|
||||
| `/api/stream/start` | POST | 启动流 |
|
||||
| `/api/stream/stop` | POST | 停止流 |
|
||||
| `/api/stream/mode` | GET | 获取流模式 |
|
||||
| `/api/stream/mode` | POST | 设置流模式 |
|
||||
| `/api/stream/mjpeg` | GET | MJPEG 流 |
|
||||
| `/api/stream/snapshot` | GET | 获取快照 |
|
||||
|
||||
### 7.2 设备管理
|
||||
|
||||
| 端点 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/api/devices/video` | GET | 列出视频设备 |
|
||||
| `/api/devices/video/:id/formats` | GET | 列出设备格式 |
|
||||
| `/api/devices/video/:id/resolutions` | GET | 列出分辨率 |
|
||||
|
||||
### 7.3 响应格式
|
||||
|
||||
```json
|
||||
// GET /api/stream/status
|
||||
{
|
||||
"status": "streaming",
|
||||
"device": "/dev/video0",
|
||||
"resolution": { "width": 1920, "height": 1080 },
|
||||
"format": "MJPEG",
|
||||
"fps": 30.0,
|
||||
"frame_count": 12345,
|
||||
"mode": "mjpeg"
|
||||
}
|
||||
|
||||
// GET /api/devices/video
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"path": "/dev/video0",
|
||||
"name": "USB Capture",
|
||||
"driver": "uvcvideo",
|
||||
"bus": "usb-0000:00:14.0-1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 事件
|
||||
|
||||
视频模块发布的事件:
|
||||
|
||||
```rust
|
||||
pub enum SystemEvent {
|
||||
/// 流状态变化
|
||||
StreamStateChanged {
|
||||
state: String, // "idle" | "starting" | "streaming" | "stopping" | "error"
|
||||
device: Option<String>,
|
||||
resolution: Option<Resolution>,
|
||||
fps: Option<f32>,
|
||||
},
|
||||
|
||||
/// 设备变化
|
||||
VideoDeviceChanged {
|
||||
added: Vec<String>,
|
||||
removed: Vec<String>,
|
||||
},
|
||||
|
||||
/// 编码器变化
|
||||
EncoderChanged {
|
||||
codec: String,
|
||||
hardware: bool,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 错误处理
|
||||
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum VideoError {
|
||||
#[error("Device not found: {0}")]
|
||||
DeviceNotFound(String),
|
||||
|
||||
#[error("Device busy: {0}")]
|
||||
DeviceBusy(String),
|
||||
|
||||
#[error("Format not supported: {0:?}")]
|
||||
FormatNotSupported(PixelFormat),
|
||||
|
||||
#[error("Resolution not supported: {0}x{1}")]
|
||||
ResolutionNotSupported(u32, u32),
|
||||
|
||||
#[error("Capture error: {0}")]
|
||||
CaptureError(String),
|
||||
|
||||
#[error("Encoder error: {0}")]
|
||||
EncoderError(String),
|
||||
|
||||
#[error("No signal")]
|
||||
NoSignal,
|
||||
|
||||
#[error("Device lost")]
|
||||
DeviceLost,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 性能优化
|
||||
|
||||
### 10.1 零拷贝
|
||||
|
||||
- `Arc<Bytes>` 共享帧数据
|
||||
- 引用计数避免复制
|
||||
|
||||
### 10.2 帧去重
|
||||
|
||||
- xxHash64 快速哈希
|
||||
- 相同帧跳过编码
|
||||
|
||||
### 10.3 硬件加速
|
||||
|
||||
- VAAPI 优先
|
||||
- 自动后备软件编码
|
||||
|
||||
### 10.4 内存池
|
||||
|
||||
- 预分配帧缓冲区
|
||||
- 复用编码器缓冲区
|
||||
|
||||
---
|
||||
|
||||
## 11. 常见问题
|
||||
|
||||
### Q: 如何添加新的视频格式?
|
||||
|
||||
1. 在 `format.rs` 添加枚举值
|
||||
2. 实现 `to_fourcc()` 和 `from_fourcc()`
|
||||
3. 在 `convert.rs` 添加转换函数
|
||||
|
||||
### Q: 如何添加新的编码器?
|
||||
|
||||
1. 实现 `Encoder` trait
|
||||
2. 创建 `EncoderFactory`
|
||||
3. 在 `EncoderRegistry` 注册
|
||||
|
||||
### Q: 帧率不稳定怎么办?
|
||||
|
||||
1. 检查 USB 带宽
|
||||
2. 降低分辨率
|
||||
3. 使用 MJPEG 格式
|
||||
4. 启用硬件编码
|
||||
428
docs/modules/web.md
Normal file
428
docs/modules/web.md
Normal file
@@ -0,0 +1,428 @@
|
||||
# Web 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
Web 模块提供 HTTP API 和静态文件服务。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- REST API
|
||||
- WebSocket
|
||||
- 静态文件服务
|
||||
- 认证中间件
|
||||
- CORS 支持
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/web/
|
||||
├── mod.rs # 模块导出
|
||||
├── routes.rs # 路由定义 (9KB)
|
||||
├── ws.rs # WebSocket (8KB)
|
||||
├── audio_ws.rs # 音频 WebSocket (8KB)
|
||||
├── static_files.rs # 静态文件 (6KB)
|
||||
└── handlers/ # API 处理器
|
||||
├── mod.rs
|
||||
└── config/
|
||||
├── mod.rs
|
||||
├── apply.rs
|
||||
├── types.rs
|
||||
└── rustdesk.rs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 路由结构
|
||||
|
||||
### 2.1 公共路由 (无认证)
|
||||
|
||||
| 路由 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/health` | GET | 健康检查 |
|
||||
| `/auth/login` | POST | 登录 |
|
||||
| `/setup` | GET | 获取设置状态 |
|
||||
| `/setup/init` | POST | 初始化设置 |
|
||||
|
||||
### 2.2 用户路由 (需认证)
|
||||
|
||||
| 路由 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/info` | GET | 系统信息 |
|
||||
| `/devices` | GET | 设备列表 |
|
||||
| `/stream/*` | * | 流控制 |
|
||||
| `/webrtc/*` | * | WebRTC 信令 |
|
||||
| `/hid/*` | * | HID 控制 |
|
||||
| `/audio/*` | * | 音频控制 |
|
||||
| `/ws` | WS | 事件 WebSocket |
|
||||
| `/ws/audio` | WS | 音频 WebSocket |
|
||||
|
||||
### 2.3 管理员路由 (需 Admin)
|
||||
|
||||
| 路由 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/config/*` | * | 配置管理 |
|
||||
| `/msd/*` | * | MSD 操作 |
|
||||
| `/atx/*` | * | ATX 控制 |
|
||||
| `/extensions/*` | * | 扩展管理 |
|
||||
| `/rustdesk/*` | * | RustDesk |
|
||||
| `/users/*` | * | 用户管理 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 路由定义
|
||||
|
||||
```rust
|
||||
pub fn create_router(state: Arc<AppState>) -> Router {
|
||||
Router::new()
|
||||
// 公共路由
|
||||
.route("/health", get(handlers::health))
|
||||
.route("/auth/login", post(handlers::login))
|
||||
.route("/setup", get(handlers::setup_status))
|
||||
.route("/setup/init", post(handlers::setup_init))
|
||||
|
||||
// 用户路由
|
||||
.nest("/api", user_routes())
|
||||
|
||||
// 管理员路由
|
||||
.nest("/api/admin", admin_routes())
|
||||
|
||||
// 静态文件
|
||||
.fallback(static_files::serve)
|
||||
|
||||
// 中间件
|
||||
.layer(CorsLayer::permissive())
|
||||
.layer(CompressionLayer::new())
|
||||
.layer(TraceLayer::new_for_http())
|
||||
|
||||
// 状态
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
fn user_routes() -> Router {
|
||||
Router::new()
|
||||
.route("/info", get(handlers::system_info))
|
||||
.route("/devices", get(handlers::list_devices))
|
||||
|
||||
// 流控制
|
||||
.route("/stream/status", get(handlers::stream_status))
|
||||
.route("/stream/start", post(handlers::stream_start))
|
||||
.route("/stream/stop", post(handlers::stream_stop))
|
||||
.route("/stream/mjpeg", get(handlers::mjpeg_stream))
|
||||
|
||||
// WebRTC
|
||||
.route("/webrtc/session", post(handlers::webrtc_create_session))
|
||||
.route("/webrtc/offer", post(handlers::webrtc_offer))
|
||||
.route("/webrtc/ice", post(handlers::webrtc_ice))
|
||||
.route("/webrtc/close", post(handlers::webrtc_close))
|
||||
|
||||
// HID
|
||||
.route("/hid/status", get(handlers::hid_status))
|
||||
.route("/hid/reset", post(handlers::hid_reset))
|
||||
|
||||
// WebSocket
|
||||
.route("/ws", get(handlers::ws_handler))
|
||||
.route("/ws/audio", get(handlers::audio_ws_handler))
|
||||
|
||||
// 认证中间件
|
||||
.layer(middleware::from_fn(auth_middleware))
|
||||
}
|
||||
|
||||
fn admin_routes() -> Router {
|
||||
Router::new()
|
||||
// 配置
|
||||
.route("/config", get(handlers::config::get_config))
|
||||
.route("/config", patch(handlers::config::update_config))
|
||||
|
||||
// MSD
|
||||
.route("/msd/status", get(handlers::msd_status))
|
||||
.route("/msd/connect", post(handlers::msd_connect))
|
||||
|
||||
// ATX
|
||||
.route("/atx/status", get(handlers::atx_status))
|
||||
.route("/atx/power/short", post(handlers::atx_power_short))
|
||||
|
||||
// 认证中间件
|
||||
.layer(middleware::from_fn(auth_middleware))
|
||||
.layer(middleware::from_fn(admin_middleware))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 静态文件服务
|
||||
|
||||
```rust
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "web/dist"]
|
||||
#[include = "*.html"]
|
||||
#[include = "*.js"]
|
||||
#[include = "*.css"]
|
||||
#[include = "assets/*"]
|
||||
struct Assets;
|
||||
|
||||
pub async fn serve(uri: Uri) -> impl IntoResponse {
|
||||
let path = uri.path().trim_start_matches('/');
|
||||
|
||||
// 尝试获取文件
|
||||
if let Some(content) = Assets::get(path) {
|
||||
let mime = mime_guess::from_path(path)
|
||||
.first_or_octet_stream();
|
||||
|
||||
return (
|
||||
[(header::CONTENT_TYPE, mime.as_ref())],
|
||||
content.data.into_owned(),
|
||||
).into_response();
|
||||
}
|
||||
|
||||
// SPA 回退到 index.html
|
||||
if let Some(content) = Assets::get("index.html") {
|
||||
return (
|
||||
[(header::CONTENT_TYPE, "text/html")],
|
||||
content.data.into_owned(),
|
||||
).into_response();
|
||||
}
|
||||
|
||||
StatusCode::NOT_FOUND.into_response()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. WebSocket 处理
|
||||
|
||||
### 5.1 事件 WebSocket (ws.rs)
|
||||
|
||||
```rust
|
||||
pub async fn ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(|socket| handle_ws(socket, state))
|
||||
}
|
||||
|
||||
async fn handle_ws(mut socket: WebSocket, state: Arc<AppState>) {
|
||||
// 发送初始设备信息
|
||||
let device_info = state.get_device_info().await;
|
||||
let json = serde_json::to_string(&device_info).unwrap();
|
||||
let _ = socket.send(Message::Text(json)).await;
|
||||
|
||||
// 订阅事件
|
||||
let mut rx = state.events.subscribe();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// 发送事件
|
||||
result = rx.recv() => {
|
||||
if let Ok(event) = result {
|
||||
let json = serde_json::to_string(&event).unwrap();
|
||||
if socket.send(Message::Text(json)).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 接收消息 (心跳/关闭)
|
||||
msg = socket.recv() => {
|
||||
match msg {
|
||||
Some(Ok(Message::Ping(data))) => {
|
||||
let _ = socket.send(Message::Pong(data)).await;
|
||||
}
|
||||
Some(Ok(Message::Close(_))) | None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 音频 WebSocket (audio_ws.rs)
|
||||
|
||||
```rust
|
||||
pub async fn audio_ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
ws.on_upgrade(|socket| handle_audio_ws(socket, state))
|
||||
}
|
||||
|
||||
async fn handle_audio_ws(mut socket: WebSocket, state: Arc<AppState>) {
|
||||
// 订阅音频帧
|
||||
let mut rx = state.audio.subscribe();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// 发送音频帧
|
||||
result = rx.recv() => {
|
||||
if let Ok(frame) = result {
|
||||
if socket.send(Message::Binary(frame.data.to_vec())).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理关闭
|
||||
msg = socket.recv() => {
|
||||
match msg {
|
||||
Some(Ok(Message::Close(_))) | None => break,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. MJPEG 流
|
||||
|
||||
```rust
|
||||
pub async fn mjpeg_stream(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> impl IntoResponse {
|
||||
let boundary = "frame";
|
||||
|
||||
// 订阅视频帧
|
||||
let rx = state.stream_manager.subscribe_mjpeg();
|
||||
|
||||
// 创建流
|
||||
let stream = async_stream::stream! {
|
||||
let mut rx = rx;
|
||||
while let Ok(frame) = rx.recv().await {
|
||||
let header = format!(
|
||||
"--{}\r\nContent-Type: image/jpeg\r\nContent-Length: {}\r\n\r\n",
|
||||
boundary,
|
||||
frame.data.len()
|
||||
);
|
||||
yield Ok::<_, std::io::Error>(Bytes::from(header));
|
||||
yield Ok(frame.data.clone());
|
||||
yield Ok(Bytes::from("\r\n"));
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
format!("multipart/x-mixed-replace; boundary={}", boundary),
|
||||
)],
|
||||
Body::from_stream(stream),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
```rust
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, message) = match self {
|
||||
AppError::AuthError => (StatusCode::UNAUTHORIZED, "Authentication failed"),
|
||||
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
|
||||
AppError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden"),
|
||||
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.as_str()),
|
||||
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.as_str()),
|
||||
AppError::Internal(err) => {
|
||||
tracing::error!("Internal error: {:?}", err);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
|
||||
}
|
||||
// ...
|
||||
};
|
||||
|
||||
(status, Json(json!({ "error": message }))).into_response()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 请求提取器
|
||||
|
||||
```rust
|
||||
// 从 Cookie 获取会话
|
||||
pub struct AuthUser(pub Session);
|
||||
|
||||
#[async_trait]
|
||||
impl<S> FromRequestParts<S> for AuthUser
|
||||
where
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = AppError;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let cookies = Cookies::from_request_parts(parts, state).await?;
|
||||
let token = cookies
|
||||
.get("session_id")
|
||||
.map(|c| c.value().to_string())
|
||||
.ok_or(AppError::Unauthorized)?;
|
||||
|
||||
let state = parts.extensions.get::<Arc<AppState>>().unwrap();
|
||||
let session = state.sessions
|
||||
.get_session(&token)
|
||||
.ok_or(AppError::Unauthorized)?;
|
||||
|
||||
Ok(AuthUser(session))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 中间件
|
||||
|
||||
### 9.1 认证中间件
|
||||
|
||||
```rust
|
||||
pub async fn auth_middleware(
|
||||
State(state): State<Arc<AppState>>,
|
||||
cookies: Cookies,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
let token = cookies
|
||||
.get("session_id")
|
||||
.map(|c| c.value().to_string());
|
||||
|
||||
if let Some(session) = token.and_then(|t| state.sessions.get_session(&t)) {
|
||||
request.extensions_mut().insert(session);
|
||||
next.run(request).await
|
||||
} else {
|
||||
StatusCode::UNAUTHORIZED.into_response()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Admin 中间件
|
||||
|
||||
```rust
|
||||
pub async fn admin_middleware(
|
||||
Extension(session): Extension<Session>,
|
||||
request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
if session.role == UserRole::Admin {
|
||||
next.run(request).await
|
||||
} else {
|
||||
StatusCode::FORBIDDEN.into_response()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. HTTPS 支持
|
||||
|
||||
```rust
|
||||
// 使用 axum-server 提供 TLS
|
||||
let tls_config = RustlsConfig::from_pem_file(cert_path, key_path).await?;
|
||||
|
||||
axum_server::bind_rustls(addr, tls_config)
|
||||
.serve(app.into_make_service())
|
||||
.await?;
|
||||
|
||||
// 或自动生成自签名证书
|
||||
let (cert, key) = generate_self_signed_cert()?;
|
||||
let tls_config = RustlsConfig::from_pem(cert, key).await?;
|
||||
```
|
||||
731
docs/modules/webrtc.md
Normal file
731
docs/modules/webrtc.md
Normal file
@@ -0,0 +1,731 @@
|
||||
# WebRTC 模块文档
|
||||
|
||||
## 1. 模块概述
|
||||
|
||||
WebRTC 模块提供低延迟的实时音视频流传输,支持多种视频编码格式和 DataChannel HID 控制。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- WebRTC 会话管理
|
||||
- 多编码器支持 (H264/H265/VP8/VP9)
|
||||
- 音频轨道 (Opus)
|
||||
- DataChannel HID
|
||||
- ICE/STUN/TURN 支持
|
||||
|
||||
### 1.2 文件结构
|
||||
|
||||
```
|
||||
src/webrtc/
|
||||
├── mod.rs # 模块导出
|
||||
├── webrtc_streamer.rs # 统一管理器 (34KB)
|
||||
├── universal_session.rs # 会话管理 (32KB)
|
||||
├── video_track.rs # 视频轨道 (19KB)
|
||||
├── rtp.rs # RTP 打包 (24KB)
|
||||
├── h265_payloader.rs # H265 RTP (15KB)
|
||||
├── peer.rs # PeerConnection (17KB)
|
||||
├── config.rs # 配置 (3KB)
|
||||
├── signaling.rs # 信令 (5KB)
|
||||
└── track.rs # 轨道基类 (11KB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构设计
|
||||
|
||||
### 2.1 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ WebRTC Architecture │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Browser
|
||||
│
|
||||
│ HTTP Signaling
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ WebRtcStreamer │
|
||||
│(webrtc_streamer)│
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌────────┐ ┌────────┐
|
||||
│Session │ │Session │ │Session │
|
||||
│ 1 │ │ 2 │ │ N │
|
||||
└───┬────┘ └───┬────┘ └───┬────┘
|
||||
│ │ │
|
||||
├───────────┼─────────────┤
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ SharedVideoPipeline │
|
||||
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
|
||||
│ │H264 │ │H265 │ │VP8 │ │VP9 │ │
|
||||
│ └─────┘ └─────┘ └─────┘ └─────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ VideoCapturer │
|
||||
└────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 会话生命周期
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Session Lifecycle │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
1. 创建会话
|
||||
POST /webrtc/session
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Create Session │
|
||||
│ Generate ID │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
{ session_id: "..." }
|
||||
|
||||
2. 发送 Offer
|
||||
POST /webrtc/offer
|
||||
{ session_id, codec, offer_sdp }
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Process Offer │
|
||||
│ Create Answer │
|
||||
│ Setup Tracks │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
{ answer_sdp, ice_candidates }
|
||||
|
||||
3. ICE 候选
|
||||
POST /webrtc/ice
|
||||
{ session_id, candidate }
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Add ICE │
|
||||
│ Candidate │
|
||||
└─────────────────┘
|
||||
|
||||
4. 连接建立
|
||||
┌─────────────────┐
|
||||
│ DTLS Handshake │
|
||||
│ SRTP Setup │
|
||||
│ DataChannel │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
开始传输视频/音频
|
||||
|
||||
5. 关闭会话
|
||||
POST /webrtc/close
|
||||
{ session_id }
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Cleanup │
|
||||
│ Release │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 WebRtcStreamer (webrtc_streamer.rs)
|
||||
|
||||
WebRTC 服务主类。
|
||||
|
||||
```rust
|
||||
pub struct WebRtcStreamer {
|
||||
/// 会话映射
|
||||
sessions: Arc<RwLock<HashMap<String, Arc<UniversalSession>>>>,
|
||||
|
||||
/// 共享视频管道
|
||||
video_pipeline: Arc<SharedVideoPipeline>,
|
||||
|
||||
/// 共享音频管道
|
||||
audio_pipeline: Arc<SharedAudioPipeline>,
|
||||
|
||||
/// HID 控制器
|
||||
hid: Arc<HidController>,
|
||||
|
||||
/// 配置
|
||||
config: WebRtcConfig,
|
||||
|
||||
/// 事件总线
|
||||
events: Arc<EventBus>,
|
||||
}
|
||||
|
||||
impl WebRtcStreamer {
|
||||
/// 创建流服务
|
||||
pub async fn new(
|
||||
video_pipeline: Arc<SharedVideoPipeline>,
|
||||
audio_pipeline: Arc<SharedAudioPipeline>,
|
||||
hid: Arc<HidController>,
|
||||
config: WebRtcConfig,
|
||||
events: Arc<EventBus>,
|
||||
) -> Result<Self>;
|
||||
|
||||
/// 创建会话
|
||||
pub async fn create_session(&self) -> Result<String>;
|
||||
|
||||
/// 处理 Offer
|
||||
pub async fn process_offer(
|
||||
&self,
|
||||
session_id: &str,
|
||||
offer: &str,
|
||||
codec: VideoCodec,
|
||||
) -> Result<OfferResponse>;
|
||||
|
||||
/// 添加 ICE 候选
|
||||
pub async fn add_ice_candidate(
|
||||
&self,
|
||||
session_id: &str,
|
||||
candidate: &str,
|
||||
) -> Result<()>;
|
||||
|
||||
/// 关闭会话
|
||||
pub async fn close_session(&self, session_id: &str) -> Result<()>;
|
||||
|
||||
/// 获取会话列表
|
||||
pub fn list_sessions(&self) -> Vec<SessionInfo>;
|
||||
|
||||
/// 获取统计信息
|
||||
pub fn get_stats(&self) -> WebRtcStats;
|
||||
}
|
||||
|
||||
pub struct OfferResponse {
|
||||
pub answer_sdp: String,
|
||||
pub ice_candidates: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct WebRtcStats {
|
||||
pub active_sessions: usize,
|
||||
pub total_bytes_sent: u64,
|
||||
pub avg_bitrate: u32,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 UniversalSession (universal_session.rs)
|
||||
|
||||
单个 WebRTC 会话。
|
||||
|
||||
```rust
|
||||
pub struct UniversalSession {
|
||||
/// 会话 ID
|
||||
id: String,
|
||||
|
||||
/// PeerConnection
|
||||
peer: Arc<RTCPeerConnection>,
|
||||
|
||||
/// 视频轨道
|
||||
video_track: Arc<UniversalVideoTrack>,
|
||||
|
||||
/// 音频轨道
|
||||
audio_track: Option<Arc<dyn TrackLocal>>,
|
||||
|
||||
/// HID DataChannel
|
||||
hid_channel: Arc<RwLock<Option<Arc<RTCDataChannel>>>>,
|
||||
|
||||
/// HID 处理器
|
||||
hid_handler: Arc<HidDataChannelHandler>,
|
||||
|
||||
/// 状态
|
||||
state: Arc<RwLock<SessionState>>,
|
||||
|
||||
/// 编码器类型
|
||||
codec: VideoCodec,
|
||||
}
|
||||
|
||||
impl UniversalSession {
|
||||
/// 创建会话
|
||||
pub async fn new(
|
||||
id: String,
|
||||
config: &WebRtcConfig,
|
||||
video_pipeline: Arc<SharedVideoPipeline>,
|
||||
audio_pipeline: Arc<SharedAudioPipeline>,
|
||||
hid_handler: Arc<HidDataChannelHandler>,
|
||||
codec: VideoCodec,
|
||||
) -> Result<Self>;
|
||||
|
||||
/// 处理 Offer SDP
|
||||
pub async fn handle_offer(&self, offer_sdp: &str) -> Result<String>;
|
||||
|
||||
/// 添加 ICE 候选
|
||||
pub async fn add_ice_candidate(&self, candidate: &str) -> Result<()>;
|
||||
|
||||
/// 获取 ICE 候选
|
||||
pub fn get_ice_candidates(&self) -> Vec<String>;
|
||||
|
||||
/// 关闭会话
|
||||
pub async fn close(&self) -> Result<()>;
|
||||
|
||||
/// 获取状态
|
||||
pub fn state(&self) -> SessionState;
|
||||
|
||||
/// 获取统计
|
||||
pub fn stats(&self) -> SessionStats;
|
||||
}
|
||||
|
||||
pub enum SessionState {
|
||||
New,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected,
|
||||
Failed,
|
||||
Closed,
|
||||
}
|
||||
|
||||
pub struct SessionStats {
|
||||
pub bytes_sent: u64,
|
||||
pub packets_sent: u64,
|
||||
pub bitrate: u32,
|
||||
pub frame_rate: f32,
|
||||
pub round_trip_time: Duration,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 VideoTrack (video_track.rs)
|
||||
|
||||
视频轨道封装。
|
||||
|
||||
```rust
|
||||
pub struct UniversalVideoTrack {
|
||||
/// 轨道 ID
|
||||
id: String,
|
||||
|
||||
/// 编码类型
|
||||
codec: VideoCodec,
|
||||
|
||||
/// RTP 发送器
|
||||
rtp_sender: Arc<RtpSender>,
|
||||
|
||||
/// 帧计数
|
||||
frame_count: AtomicU64,
|
||||
|
||||
/// 统计
|
||||
stats: Arc<RwLock<TrackStats>>,
|
||||
}
|
||||
|
||||
impl UniversalVideoTrack {
|
||||
/// 创建轨道
|
||||
pub fn new(id: &str, codec: VideoCodec) -> Result<Self>;
|
||||
|
||||
/// 发送编码帧
|
||||
pub async fn send_frame(&self, frame: &EncodedFrame) -> Result<()>;
|
||||
|
||||
/// 获取 RTP 参数
|
||||
pub fn rtp_params(&self) -> RtpParameters;
|
||||
|
||||
/// 获取统计
|
||||
pub fn stats(&self) -> TrackStats;
|
||||
}
|
||||
|
||||
pub struct TrackStats {
|
||||
pub frames_sent: u64,
|
||||
pub bytes_sent: u64,
|
||||
pub packets_sent: u64,
|
||||
pub packet_loss: f32,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 RTP 打包 (rtp.rs)
|
||||
|
||||
RTP 协议实现。
|
||||
|
||||
```rust
|
||||
pub struct RtpPacketizer {
|
||||
/// SSRC
|
||||
ssrc: u32,
|
||||
|
||||
/// 序列号
|
||||
sequence: u16,
|
||||
|
||||
/// 时间戳
|
||||
timestamp: u32,
|
||||
|
||||
/// 负载类型
|
||||
payload_type: u8,
|
||||
|
||||
/// 时钟频率
|
||||
clock_rate: u32,
|
||||
}
|
||||
|
||||
impl RtpPacketizer {
|
||||
/// 创建打包器
|
||||
pub fn new(codec: VideoCodec) -> Self;
|
||||
|
||||
/// 打包 H264 帧
|
||||
pub fn packetize_h264(&mut self, frame: &[u8], keyframe: bool) -> Vec<Vec<u8>>;
|
||||
|
||||
/// 打包 VP8 帧
|
||||
pub fn packetize_vp8(&mut self, frame: &[u8], keyframe: bool) -> Vec<Vec<u8>>;
|
||||
|
||||
/// 打包 VP9 帧
|
||||
pub fn packetize_vp9(&mut self, frame: &[u8], keyframe: bool) -> Vec<Vec<u8>>;
|
||||
|
||||
/// 打包 Opus 帧
|
||||
pub fn packetize_opus(&mut self, frame: &[u8]) -> Vec<u8>;
|
||||
}
|
||||
|
||||
/// H264 NAL 单元分片
|
||||
pub struct H264Fragmenter;
|
||||
|
||||
impl H264Fragmenter {
|
||||
/// 分片大于 MTU 的 NAL
|
||||
pub fn fragment(nal: &[u8], mtu: usize) -> Vec<Vec<u8>>;
|
||||
|
||||
/// 创建 STAP-A 聚合
|
||||
pub fn aggregate(nals: &[&[u8]]) -> Vec<u8>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 H265 打包器 (h265_payloader.rs)
|
||||
|
||||
H265/HEVC RTP 打包。
|
||||
|
||||
```rust
|
||||
pub struct H265Payloader {
|
||||
/// MTU 大小
|
||||
mtu: usize,
|
||||
}
|
||||
|
||||
impl H265Payloader {
|
||||
/// 创建打包器
|
||||
pub fn new(mtu: usize) -> Self;
|
||||
|
||||
/// 打包 H265 帧
|
||||
pub fn packetize(&self, frame: &[u8]) -> Vec<Vec<u8>>;
|
||||
|
||||
/// 分析 NAL 单元类型
|
||||
fn get_nal_type(nal: &[u8]) -> u8;
|
||||
|
||||
/// 是否需要分片
|
||||
fn needs_fragmentation(&self, nal: &[u8]) -> bool;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 信令协议
|
||||
|
||||
### 4.1 创建会话
|
||||
|
||||
```
|
||||
POST /api/webrtc/session
|
||||
Content-Type: application/json
|
||||
|
||||
{}
|
||||
|
||||
Response:
|
||||
{
|
||||
"session_id": "abc123-def456"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 发送 Offer
|
||||
|
||||
```
|
||||
POST /api/webrtc/offer
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"session_id": "abc123-def456",
|
||||
"video_codec": "h264",
|
||||
"enable_audio": true,
|
||||
"offer_sdp": "v=0\r\no=- ..."
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"answer_sdp": "v=0\r\no=- ...",
|
||||
"ice_candidates": [
|
||||
"candidate:1 1 UDP ...",
|
||||
"candidate:2 1 TCP ..."
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 ICE 候选
|
||||
|
||||
```
|
||||
POST /api/webrtc/ice
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"session_id": "abc123-def456",
|
||||
"candidate": "candidate:1 1 UDP ..."
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 关闭会话
|
||||
|
||||
```
|
||||
POST /api/webrtc/close
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"session_id": "abc123-def456"
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置
|
||||
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[typeshare]
|
||||
pub struct WebRtcConfig {
|
||||
/// STUN 服务器
|
||||
pub stun_servers: Vec<String>,
|
||||
|
||||
/// TURN 服务器
|
||||
pub turn_servers: Vec<TurnServer>,
|
||||
|
||||
/// 默认编码器
|
||||
pub default_codec: VideoCodec,
|
||||
|
||||
/// 码率 (kbps)
|
||||
pub bitrate_kbps: u32,
|
||||
|
||||
/// GOP 大小
|
||||
pub gop_size: u32,
|
||||
|
||||
/// 启用音频
|
||||
pub enable_audio: bool,
|
||||
|
||||
/// 启用 DataChannel HID
|
||||
pub enable_datachannel_hid: bool,
|
||||
}
|
||||
|
||||
pub struct TurnServer {
|
||||
pub url: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl Default for WebRtcConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stun_servers: vec!["stun:stun.l.google.com:19302".to_string()],
|
||||
turn_servers: vec![],
|
||||
default_codec: VideoCodec::H264,
|
||||
bitrate_kbps: 2000,
|
||||
gop_size: 60,
|
||||
enable_audio: true,
|
||||
enable_datachannel_hid: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. DataChannel HID
|
||||
|
||||
### 6.1 消息格式
|
||||
|
||||
```javascript
|
||||
// 键盘事件
|
||||
{
|
||||
"type": "keyboard",
|
||||
"keys": ["KeyA", "KeyB"],
|
||||
"modifiers": {
|
||||
"ctrl": false,
|
||||
"shift": true,
|
||||
"alt": false,
|
||||
"meta": false
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标事件
|
||||
{
|
||||
"type": "mouse",
|
||||
"x": 16384,
|
||||
"y": 16384,
|
||||
"button": "left",
|
||||
"event": "press"
|
||||
}
|
||||
|
||||
// 鼠标模式
|
||||
{
|
||||
"type": "mouse_mode",
|
||||
"mode": "absolute"
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 处理流程
|
||||
|
||||
```
|
||||
DataChannel Message
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│Parse JSON Event │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│HidDataChannel │
|
||||
│ Handler │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ HidController │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
USB/Serial
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 支持的编码器
|
||||
|
||||
| 编码器 | RTP 负载类型 | 时钟频率 | 硬件加速 |
|
||||
|--------|-------------|---------|---------|
|
||||
| H264 | 96 (动态) | 90000 | VAAPI/RKMPP/V4L2 |
|
||||
| H265 | 97 (动态) | 90000 | VAAPI |
|
||||
| VP8 | 98 (动态) | 90000 | VAAPI |
|
||||
| VP9 | 99 (动态) | 90000 | VAAPI |
|
||||
| Opus | 111 (动态) | 48000 | 无 (软件) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 错误处理
|
||||
|
||||
```rust
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum WebRtcError {
|
||||
#[error("Session not found: {0}")]
|
||||
SessionNotFound(String),
|
||||
|
||||
#[error("Session already exists")]
|
||||
SessionExists,
|
||||
|
||||
#[error("Invalid SDP: {0}")]
|
||||
InvalidSdp(String),
|
||||
|
||||
#[error("Codec not supported: {0}")]
|
||||
CodecNotSupported(String),
|
||||
|
||||
#[error("ICE failed")]
|
||||
IceFailed,
|
||||
|
||||
#[error("DTLS failed")]
|
||||
DtlsFailed,
|
||||
|
||||
#[error("Track error: {0}")]
|
||||
TrackError(String),
|
||||
|
||||
#[error("Connection closed")]
|
||||
ConnectionClosed,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 使用示例
|
||||
|
||||
### 9.1 创建会话
|
||||
|
||||
```rust
|
||||
let streamer = WebRtcStreamer::new(
|
||||
video_pipeline,
|
||||
audio_pipeline,
|
||||
hid,
|
||||
WebRtcConfig::default(),
|
||||
events,
|
||||
).await?;
|
||||
|
||||
// 创建会话
|
||||
let session_id = streamer.create_session().await?;
|
||||
|
||||
// 处理 Offer
|
||||
let response = streamer.process_offer(
|
||||
&session_id,
|
||||
&offer_sdp,
|
||||
VideoCodec::H264,
|
||||
).await?;
|
||||
|
||||
println!("Answer: {}", response.answer_sdp);
|
||||
```
|
||||
|
||||
### 9.2 前端连接
|
||||
|
||||
```javascript
|
||||
// 创建 PeerConnection
|
||||
const pc = new RTCPeerConnection({
|
||||
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
||||
});
|
||||
|
||||
// 创建 DataChannel
|
||||
const hidChannel = pc.createDataChannel('hid');
|
||||
|
||||
// 创建 Offer
|
||||
const offer = await pc.createOffer();
|
||||
await pc.setLocalDescription(offer);
|
||||
|
||||
// 发送到服务器
|
||||
const response = await fetch('/api/webrtc/offer', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
session_id,
|
||||
video_codec: 'h264',
|
||||
offer_sdp: offer.sdp
|
||||
})
|
||||
});
|
||||
|
||||
const { answer_sdp, ice_candidates } = await response.json();
|
||||
|
||||
// 设置 Answer
|
||||
await pc.setRemoteDescription({ type: 'answer', sdp: answer_sdp });
|
||||
|
||||
// 添加 ICE 候选
|
||||
for (const candidate of ice_candidates) {
|
||||
await pc.addIceCandidate({ candidate });
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 常见问题
|
||||
|
||||
### Q: 连接超时?
|
||||
|
||||
1. 检查 STUN/TURN 配置
|
||||
2. 检查防火墙设置
|
||||
3. 尝试使用 TURN 中继
|
||||
|
||||
### Q: 视频卡顿?
|
||||
|
||||
1. 降低分辨率/码率
|
||||
2. 检查网络带宽
|
||||
3. 使用硬件编码
|
||||
|
||||
### Q: 音频不同步?
|
||||
|
||||
1. 检查时间戳同步
|
||||
2. 调整缓冲区大小
|
||||
3. 使用 NTP 同步
|
||||
Reference in New Issue
Block a user