1. 项目概述 WFB-ng (Wireless Forwarding Bridge - Next Generation) 是一个高性能的无线视频流传输系统,专为 FPV(第一人称视角)无人机实时图传设计。它通过 UDP 在发射端和接收端之间建立加密的、具有前向纠错能力的低延迟数据通道。
核心特性
极低延迟 :端到端延迟可低至几毫秒
前向纠错 (FEC) :基于 Reed-Solomon 编码,容忍高达 30% 的数据包丢失
端到端加密 :使用 NaCl/libsodium 库的 X25519 + XSalsa20-Poly1305
多发射器支持 :单个接收端可同时聚合多个无线网卡的数据流
自适应码率 :支持动态调整 MCS 索引、带宽等射频参数
系统架构概览 1 2 3 [视频源] --> [wfb_tx] --> (UDP over WiFi) --> [wfb_rx] --> [TUN设备] --> [应用层] | | +--> [tx_cmd: 控制通道] +--> [wfb_tun: TUN代理]
2. 源文件详细设计 2.1 src/rx.cpp — 接收端聚合器 (Aggregator) 功能 : 核心接收引擎,负责从多个无线网卡接收加密数据包、解密、FEC 恢复、去重排序后输出。
类层次结构 1 2 3 4 5 6 BaseAggregator (抽象基类) | +-- Aggregator (核心实现) | +-- AggregatorUDPv4 (UDP v4 输出) +-- AggregatorUNIX (Unix Domain Socket 输出)
关键数据结构 rx_ring_item_t — FEC 环形缓冲区项 :
block_idx: 数据块索引(64位)
fragment_map[FEC_N_MAX]: 每个分片的状态映射(0=未收到,>0=已收到的字节数)
fragments[FEC_N_MAX][MAX_FEC_PAYLOAD]: 存储所有 FEC 分片的原始缓冲区
has_fragments: 当前已接收的分片数量
fragment_to_send_idx: 下一个要发送的数据分片索引
rxAntennaKey — 天线统计键 :
freq: WiFi 频率(MHz)
antenna_id: 组合了 IP 地址 + wlan 索引 + 天线 ID 的 64 位唯一标识符
mcs_index: MCS 调制编码速率索引
bandwidth: 信道带宽
rxAntStat — RSSI/噪声统计 :
维护每个天线的 RSSI 和噪声水平的指数移动平均(EMA)
使用滑动窗口计算最小值、最大值和平均值
核心工作流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1. 接收 UDP 数据包 (来自 wfb_tx) 2. 解析 radiotap header,提取天线/RSSI/MCS 等元数据 3. 根据包类型分发处理: - WFB_PACKET_SESSION (0x80): 会话密钥更新 * 验证 session nonce + channel_id + epoch * 使用 X25519 解密新的 session key * 重新初始化 FEC 参数 (k, n) - WFB_PACKET_DATA (0x81): 数据块分片 * ChaCha20-Poly1305 AEAD 解密 * 提取 block_idx 和 fragment_idx * 去重检查(std::set<uint64_t>) * 存入环形缓冲区对应位置 4. FEC 恢复: - 当收到 K 个分片(任意组合)时触发 - 如果有缺失的数据分片,调用 apply_fec() 进行 Reed-Solomon 解码 - 使用 zfex_decode_simd() SIMD 加速解码 5. 按序输出: - 检查序列号连续性,报告丢包 - 通过 send_to_socket() 发送到 TUN 设备或下游应用
FEC 环形缓冲区管理
大小:RX_RING_SIZE = 64 个块
使用 modN 运算实现循环索引
rx_ring_front: 指向最老的未处理块
rx_ring_alloc: 当前已分配的块数
优化路径:如果当前块是 front 且分片连续到达,立即发送而无需等待 FEC
加密体系 1 2 3 4 5 6 长期密钥对: X25519 (tx_publickey / rx_secretkey) | +---> 会话密钥交换: crypto_box (X25519 + XSalsa20-Poly1305) | +---> 数据加密: ChaCha20-Poly1305 AEAD nonce = block_idx << 8 | fragment_idx
IPC 消息格式 通过 Unix socket 发送统计信息:
PKT: 数据包统计(接收/解密错误/会话/FEC恢复/丢失/输出)
SESSION: 新会话建立通知
RSSI: 天线 RSSI 统计
2.2 src/tx.cpp — 发射端发送器 (Transmitter) 功能 : 从 TUN 设备或 UDP socket 读取数据,进行 FEC 编码、加密后通过多个无线网卡发送。
类层次结构 1 2 3 4 5 6 BaseTransmitter (抽象基类) | +-- Transmitter (核心实现) | +-- TransmitterUDPv4 (从 UDP v4 socket 读取数据) +-- TransmitterUNIX (从 Unix Domain Socket 读取数据)
关键数据结构 tx_ring_item_t — FEC 发送环形缓冲区 :
block_idx: 当前块的序列号
fragment_map[FEC_N_MAX]: 每个分片的发送状态(0=未发送,>0=已发送的字节数)
fragments[FEC_N_MAX][MAX_FEC_PAYLOAD]: 存储编码后的所有分片
txAntennaKey / txAntStat — 天线统计 :
与接收端类似,用于监控每个天线的发送状态和 RSSI 反馈
核心工作流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1. 从数据源读取原始数据包 (TUN/UDP socket) 2. 封装为 wpacket_hdr_t: - flags: FEC-only 标志位 - packet_size: 有效载荷大小 3. FEC 编码: - 将 K 个数据包分组为一个 block - 调用 fec_encode_simd() 生成 N-K 个校验分片 - 每个分片独立加密发送 4. 加密封装: - wblock_hdr_t (8 bytes): data_nonce = (block_idx << 8) | fragment_idx - ChaCha20-Poly1305 AEAD 加密分片数据 5. 通过无线网卡发送: - 构造 radiotap header(包含 MCS、带宽、STBC 等参数) - 发送到对应的 wlan 接口 6. 会话管理: - 定期或 epoch/channel_id 变化时生成新的 session key - 使用 X25519 加密发送 session packet
FEC 编码流程 1 2 3 4 5 6 7 8 9 原始数据包 [P0, P1, ..., P(K-1)] | +---> fec_encode_simd() ---> [P0, P1, ..., P(K-1), C(K), ..., C(N-1)] | | 数据分片 校验分片 | | ChaCha20加密 ChaCha20加密 | | UDP发送 UDP发送
控制命令处理 (tx_cmd) 监听 Unix socket,支持以下命令:
CMD_SET_FEC: 动态调整 FEC 参数 (k, n)
CMD_GET_FEC: 查询当前 FEC 参数
CMD_SET_RADIO: 修改射频参数(带宽、MCS、STBC、LDPC、GI)
CMD_GET_RADIO: 查询当前射频配置
2.3 src/wfb_tun.c — TUN 设备代理 功能 : 在 TUN 设备和 UDP socket 之间建立双向数据桥接,支持数据包聚合以减少延迟。
工作模式 1 TUN 设备 <===> wfb_tun <===> UDP Socket <===> wfb_tx/rx
关键数据结构 in_packet_buffer_t — TUN -> Socket 方向 :
data[MTU * 2]: 输入缓冲区(支持聚合多个小包)
data_size: 当前缓冲的总字节数
batch_size: 当前批次大小(<= MTU)
out_packet_buffer_t — Socket -> TUN 方向 :
data[MTU]: 输出缓冲区
data_size: 接收到的数据总量
offset: 当前写入 TUN 的偏移量
libevent 事件驱动架构 1 2 3 4 5 6 ev_ping -- 定时发送心跳包(当无数据传输时) ev_tun_read -- TUN 设备可读事件 -> 读取数据包并聚合 ev_tun_read_timeout -- 聚合超时 -> 强制刷新缓冲区 ev_socket_write -- Socket 可写事件 -> 发送聚合后的数据 ev_socket_read -- Socket 可读事件 -> 接收数据写入 TUN ev_tun_write -- TUN 设备可写事件 -> 将接收的数据注入 TUN
数据包聚合机制
agg_timeout_ms: 聚合超时(默认 5ms)
当从 TUN 读取数据包时,累积到缓冲区直到:
达到 MTU 大小 -> 立即发送
超过 agg_timeout_ms -> 强制刷新
目的:将多个小包合并为一个 UDP 包,减少协议开销
TUN 设备操作 1 2 3 4 open("/dev/net/tun" , O_RDWR | O_NONBLOCK) ioctl(fd, TUNSETIFF, { IFF_TUN | IFF_NO_PI }) ip link set up mtu <MTU> dev <name> ip addr add <addr>/24 dev <name>
2.4 src/tx_cmd.c — 控制命令行工具 功能 : 通过 UDP socket 向 wfb_tx 发送控制命令,支持动态调整 FEC 和射频参数。
命令协议 1 2 3 4 5 6 7 8 9 请求 (cmd_req_t): req_id: uint32_t (随机数,用于匹配响应) cmd_id: uint8_t (CMD_SET_FEC/GET_FEC/SET_RADIO/GET_RADIO) payload: union (根据 cmd_id 不同而不同) 响应 (cmd_resp_t): req_id: uint32_t (回显请求的 req_id) rc: uint32_t (0=成功,errno=失败) payload: union (与请求对应的数据)
支持的命令
命令
参数
说明
set_fec
-k K, -n N
设置 FEC 编码参数(默认 k=8, n=12)
get_fec
无
查询当前 FEC 参数
set_radio
-B 带宽, -G GI, -S STBC, -L LDPC, -M MCS, -N NSS, -V VHT
设置射频参数
get_radio
无
查询当前射频配置
2.5 src/radiotap.c — IEEE 802.11 Radiotap 解析器 功能 : 解析 radiotap header,提取 WiFi 物理层元数据。
1 2 3 4 5 6 +--------+--------+----------+-----------+ | version| padding| it_len | it_present| | (1B) | (3B) | (2B LE) | (4B LE +)| +--------+--------+----------+-----------+ | payload fields (variable) | +-----------------------------------------+
支持的字段
IEEE80211_RADIOTAP_TSFT: 时间戳(8字节)
IEEE80211_RADIOTAP_FLAGS: 帧控制标志(1字节)
IEEE80211_RADIOTAP_RATE: 数据速率(1字节)
IEEE80211_RADIOTAP_CHANNEL: 信道频率和标志(4字节)
IEEE80211_RADIOTAP_DBM_ANTSIGNAL: RSSI(1字节,有符号)
IEEE80211_RADIOTAP_DBM_ANTNOISE: 噪声底(1字节,有符号)
IEEE80211_RADIOTAP_MCS: MCS 信息(3字节)
IEEE80211_RADIOTAP_AMPDU_STATUS: A-MPDU 状态(8字节)
IEEE80211_RADIOTAP_VHT: VHT (802.11ac) 信息(12字节)
迭代器模式 1 2 3 4 5 6 7 ieee80211_radiotap_iterator_init(&iter, header, max_len, vns); while (ieee80211_radiotap_iterator_next(&iter) == 0 ) { switch (iter.this_arg_index) { case IEEE80211_RADIOTAP_MCS: break ; case IEEE80211_RADIOTAP_DBM_ANTSIGNAL: break ; } }
2.6 src/zfex.c — FEC Reed-Solomon 编码库 (zfex) 功能 : 基于 Vandermonde 矩阵的前向纠错编解码,支持 SIMD 加速。
核心 API
函数
说明
fec_new(k, m, &fec)
创建 FEC 编码器,k=数据块数,m=总块数
fec_free(fec)
释放 FEC 上下文
fec_encode_simd(code, inpkts, fecs, sz)
SIMD 加速编码:从 K 个输入生成 N-K 个校验块
fec_decode_simd(code, inpkts, outpkts, index, sz)
SIMD 加速解码:从任意 K 个块恢复缺失的数据块
数学基础
Galois Field GF(2^8) : 使用生成元 0x1d (primitive polynomial)
Vandermonde Matrix : 编码矩阵,保证任意 K 行线性独立
SIMD 优化 : 使用 SSE/AVX/NEON 指令集加速 Galois Field 乘法
编解码流程 1 2 3 4 5 6 7 8 编码: C = E * D E: N×K Vandermonde 编码矩阵 D: K 个数据块 C: N-K 个校验块 解码: D_missing = G_inv * D_available G_inv: 缺失块的逆矩阵 D_available: 任意 K 个可用块(数据+校验)
2.7 src/rtsp_server.c — RTSP/RTP 视频流服务器 功能 : 基于 GStreamer 的 RTSP 服务器,将 UDP RTP 流封装为 RTSP 服务。
GStreamer Pipeline 1 2 3 4 udpsrc port=<rtp_port> ! application/x-rtp,media=video,clock-rate=90000,encoding-name=H264 ! rtpjitterbuffer latency=<latency> ! rtph264depay ! rtph264pay name=pay0 pt=96 config-interval=1 aggregate-mode=zero-latency mtu=<mtu>
支持的格式
2.8 src/common.h — 公共定义与协议头 功能 : 定义 WFB-ng 私有协议的常量、数据结构和辅助函数。
协议常量
常量
值
说明
WFB_PACKET_SESSION
0x80
会话密钥包类型
WFB_PACKET_DATA
0x81
数据包类型
WFB_FEC_VDM_RS
0
Vandermonde Reed-Solomon FEC
MAX_PAYLOAD_SIZE
1452
最大有效载荷(以太网 MTU)
FEC_K_DEFAULT
8
默认数据块数
FEC_N_DEFAULT
12
默认总块数
协议数据结构 wsession_hdr_t (会话头) :
1 2 uint8_t type; uint64_t session_nonce;
wsession_data_t (会话数据) :
1 2 3 4 5 uint64_t epoch; uint32_t channel_id; uint8_t fec_type; int8_t k, n; uint8_t session_key[32 ];
wblock_hdr_t (数据块头) :
1 2 uint8_t type; uint64_t data_nonce;
wpacket_hdr_t (数据包头) :
1 2 uint8_t flags; uint16_t packet_size;
3. 数据流全景图 发射端数据流 (TX Path) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [视频源/网络] --> [TUN设备或UDP socket] | v [Transmitter::process_packet()] | +-----------+-----------+ | | 封装 wpacket_hdr_t 检查 block 是否满 | | v v 存入 fragments[0..K-1] fec_encode_simd() --> fragments[K..N-1] | | +-----------+-----------+ | v ChaCha20-Poly1305 AEAD 加密每个分片 | v 构造 radiotap header (MCS, BW, STBC...) | v [无线网卡发送] --> (WiFi over UDP)
接收端数据流 (RX Path) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 [(WiFi over UDP)] --> [多个无线网卡] | v [Aggregator::process_packet()] | +------------+------------+ | | WFB_PACKET_SESSION WFB_PACKET_DATA | | X25519 解密 session key ChaCha20-Poly1305 解密 | | 更新 FEC 参数 (k,n) 存入 rx_ring[ring_idx] | fragments[fragment_idx] | | +------------+------------+ | has_fragments >= K ? / \ yes no --> 等待更多分片 | 有缺失的数据分片? / \ yes no --> 直接发送 | fec_decode_simd() 恢复缺失分片 | v [按序输出到 TUN/UDP socket]
4. 安全架构 密钥层次 1 2 3 4 5 6 7 8 Level 0: X25519 长期密钥对 (tx_publickey / rx_secretkey) |-- crypto_box() --> Level 1: ChaCha20-Poly1305 会话密钥 (32 bytes) |-- crypto_aead_chacha20poly1305_decrypt() --> Level 2: 明文数据 Session Key 更新触发条件: - epoch 变化(手动刷新) - channel_id 不匹配 - FEC 参数变化
加密算法选择理由
组件
算法
原因
密钥交换
X25519 (Curve25519)
高性能、抗侧信道攻击
会话加密
XSalsa20-Poly1305
比 AES-GCM 更快,适合嵌入式
数据加密
ChaCha20-Poly1305 AEAD
无硬件加速时优于 AES,软件实现快
5. 性能优化策略 FEC 层
SIMD 加速的 Galois Field 运算(SSE/AVX/NEON)
环形缓冲区避免内存分配
提前发送:当前块分片连续到达时立即输出,不等待所有 K 个
网络层
UDP 无连接传输,避免 TCP 重传延迟
MSG_DONTWAIT 非阻塞 I/O
SO_SNDBUF/SO_RCVBUF 大缓冲区
数据包聚合(wfb_tun)减少协议开销
加密层
NaCl/libsodium 高度优化的实现
AEAD 模式:一次调用完成加密+认证
Nonce 复用策略:block_idx + fragment_idx 组合,避免随机数生成开销
6. 编译依赖关系图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 wfb_tx (tx.cpp) wfb_rx (rx.cpp) | | +-- libevent +-- libevent +-- libsodium +-- libsodium +-- zfex.c +-- zfex.c +-- radiotap.c +-- radiotap.c +-- common.h +-- common.h wfb_tun (wfb_tun.c) tx_cmd (tx_cmd.c) | | +-- libevent +-- common.h +-- tx_cmd.h rtsp_server (rtsp_server.c) | +-- GStreamer RTSP