Files
flyLink/PROTOCOL.md
2026-02-02 17:06:20 +08:00

237 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SkyLink 协议规范文档
## 概述
SkyLink 是一个多平台实时视频传输系统的通信协议设计用于在机载端、PC 地面站和移动端之间进行高效的实时视频传输。
## 📋 协议版本
- **版本**: 1.0
- **最后更新**: 2026-01-19
- **维护者**: flowzl
---
## 🔧 数据包结构
### UDP 包头 (PacketHeader)
```
总大小: 50 字节
┌─────────┬──────────┬────────┬──────────┬────────┬──────────┬──────┬──────┬──────┬────────┬─────────┐
│ Magic │ FrameID │ Chunks │ ChunkIdx │ DataLen│Timestamp │ Lat │ Lon │ Alt │ CRC16 │ Reserve │
│ 2 bytes │ 4 bytes │ 2 byte │ 2 bytes │ 2bytes │ 8 bytes │ 8by │ 8by │ 8by │ 2bytes │ 4 bytes │
└─────────┴──────────┴────────┴──────────┴────────┴──────────┴──────┴──────┴──────┴────────┴─────────┘
```
### 字段详解
| 字段 | 大小 | 类型 | 说明 |
|------|------|------|------|
| **Magic** | 2 | uint16 | 魔数 `0xAA55`,用于识别合法数据包 |
| **FrameID** | 4 | uint32 | 唯一的帧编号,从 0 开始递增 |
| **TotalChunks** | 2 | uint16 | 该帧总共被分成多少个 UDP 包 |
| **ChunkIndex** | 2 | uint16 | 当前包是第几个分片 (0-based) |
| **DataLen** | 2 | uint16 | 当前包的有效载荷长度(单位:字节) |
| **Timestamp** | 8 | double | GPS 时间戳或系统时间(秒数) |
| **Latitude** | 8 | double | 纬度 (WGS84 坐标系) |
| **Longitude** | 8 | double | 经度 (WGS84 坐标系) |
| **Altitude** | 8 | double | 海拔高度 (单位:米) |
| **CRC16** | 2 | uint16 | CRC16 校验码(可选,当前未使用) |
| **Reserve** | 4 | uint32 | 保留字段,用于将来扩展 |
---
## 📦 完整数据包结构
```
┌─────────────────────────────┐
│ UDP Package Header (50B) │ <- PacketHeader 结构
├─────────────────────────────┤
│ Payload (0 ~ 1422 bytes) │ <- JPEG 图像数据片段
└─────────────────────────────┘
Total: 50 ~ 1472 bytes
```
### 最大包大小
- **以太网 MTU**: 1500 字节
- **IP 头**: 20 字节
- **UDP 头**: 8 字节
- **可用容量**: 1500 - 20 - 8 = 1472 字节
- **PacketHeader**: 50 字节
- **最大载荷**: 1472 - 50 = 1422 字节
---
## 🔄 帧重组流程
### 发送端 (ROS 2 机载端)
```
1. 采集图像
2. 编码为 JPEG (质量 1-100)
3. 计算所需分片数
Total = ceil(JPEG_SIZE / 1422)
4. 逐片段创建 UDP 包头
- magic = 0xAA55
- frame_id = 帧计数器
- total_chunks = 计算出的总片数
- chunk_index = 0, 1, 2, ...
- data_len = 当前片段大小
- timestamp = 当前时间戳
- lat, lon, alt = 当前 GPS 位置
5. 发送 UDP 广播包
通过 UDP/IP 广播到网络
```
### 接收端 (PC 地面站 / Android 移动端)
```
1. 监听 UDP 端口 9999
2. 接收数据包
3. 检查魔数是否为 0xAA55
✗ 否 → 丢弃该包
是 → 继续
4. 根据 frame_id 找到对应的帧缓冲区
如果不存在 → 创建新的缓冲区
5. 将当前分片存储到 chunks[chunk_index]
6. 检查是否所有分片都已接收
if (chunks.count() == total_chunks)
将所有分片合并为完整 JPEG 数据
解码 JPEG 为图像
显示到界面
发出 frameDecoded() 信号
7. 清理超时未完成的帧
(超过 5 秒)
```
---
## ⏱️ 时序示例
假设一个 480x360 分辨率的图像JPEG 压缩后 50 KB
```
Frame#0:
TotalChunks = ceil(50000 / 1422) = 36 个分片
Packet 0: magic=0xAA55, frame_id=0, chunk_index=0/36, data_len=1422
Packet 1: magic=0xAA55, frame_id=0, chunk_index=1/36, data_len=1422
...
Packet 35: magic=0xAA55, frame_id=0, chunk_index=35/36, data_len=340
-> 接收器接收到全部 36 个分片后,合并为 50KB JPEG
-> 解码并显示图像
Frame#1:
frame_id=1, 重复上述过程
```
---
## 📊 带宽计算
### 示例配置
- **分辨率**: 640x480
- **JPEG 质量**: 70
- **帧率**: 30 FPS
- **压缩后大小**: ~40 KB/帧
### 带宽需求
```
总体数据量 = (JPEG_SIZE + Header_Overhead) × FPS
= (40000 + 50×36) × 30
≈ 1.2 MB/s
≈ 9.6 Mbps
```
### 网络适应性
| 网络环境 | 推荐分辨率 | 推荐帧率 | 推荐质量 |
|---------|----------|--------|--------|
| WiFi 5G (100+ Mbps) | 1920x1080 | 30 | 85 |
| WiFi 2.4G (50 Mbps) | 1280x720 | 20 | 70 |
| 4G (10 Mbps) | 640x480 | 15 | 50 |
| 局域网 (1000 Mbps) | 4K | 60 | 95 |
---
## 🔒 安全性考虑
### 当前缺陷
- 无加密UDP 明文传输)
- 无身份验证
- 无完整性校验CRC16 未启用)
### 改进建议(后续版本)
1. **加密**: 添加 AES-128-CBC 加密层
2. **认证**: HMAC-SHA256 签名
3. **校验**: 启用 CRC16 检验
4. **防重放**: 添加序列号和时间戳验证
---
## 💾 编程实现参考
### C++ 解析包头
```cpp
// 读取包头
PacketHeader header;
socket.read((uint8_t*)&header, sizeof(PacketHeader));
// 验证
if (header.magic != 0xAA55) {
// 无效包
return;
}
// 读取载荷
std::vector<uint8_t> payload(header.data_len);
socket.read(payload.data(), header.data_len);
```
### Kotlin 解析包头
```kotlin
val buffer = ByteBuffer.wrap(data)
buffer.order(ByteOrder.LITTLE_ENDIAN)
val header = SkyLinkPacketHeader.fromByteArray(data)
if (header?.isValid() == true) {
// 处理有效包
}
```
### Python 解析包头(参考)
```python
import struct
# 解析包头
fmt = '<HHHHQQQQHDI' # little-endian 格式字符串
magic, frame_id, total_chunks, chunk_idx, data_len, \
timestamp, lat, lon, alt, crc16, reserve = \
struct.unpack(fmt, header_bytes[:50])
if magic == 0xAA55:
# 有效包
pass
```
---