# 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 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 = '