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 物理层元数据。

Radiotap Header 结构

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>

支持的格式

  • H.264 (AVC)
  • H.265 (HEVC)

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;           // WFB_PACKET_SESSION
uint64_t session_nonce; // X25519 加密的 nonce

wsession_data_t (会话数据):

1
2
3
4
5
uint64_t epoch;         // 大端,会话纪元
uint32_t channel_id; // 大端,通道 ID
uint8_t fec_type; // FEC 类型
int8_t k, n; // FEC 参数
uint8_t session_key[32]; // ChaCha20-Poly1305 密钥

wblock_hdr_t (数据块头):

1
2
uint8_t type;           // WFB_PACKET_DATA
uint64_t data_nonce; // AEAD nonce = (block_idx << 8) | fragment_idx

wpacket_hdr_t (数据包头):

1
2
uint8_t flags;          // 标志位(WFB_PACKET_FEC_ONLY)
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