【数字图传第二步】esp32 80211 raw发送视频数据

前言

在无人机FPV(第一人称视角)飞行中,图传系统的延迟和距离直接决定了飞行体验。传统的WiFi UDP图传虽然简单,但存在协议栈开销大、重传机制导致延迟抖动等问题。本文将带你从零实现一个基于802.11 RAW帧的FPV图传系统,通过绕过TCP/IP协议栈,结合FEC前向纠错编码,实现低延迟、高可靠性的视频传输。

为什么选择802.11 RAW帧?

标准WiFi传输使用TCP/IP协议栈,数据需要经过多层封装,带来以下问题:

  • 协议开销大:UDP/IP头就占42字节

  • 重传机制:丢包后重传会引入不可预测的延迟

  • 竞争接入:与普通网络流量争抢信道

而802.11 RAW帧直接操作MAC层,我们可以:

  • 自定义数据格式,去除无用头部

  • 主动丢包,不等待重传,用FEC修复丢失数据

  • 独占信道,获得稳定延迟

系统整体架构

┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐

│ OV2640 │───▶│ JPEG │───▶│ FEC │───▶│ 802.11 │

│ 摄像头 │ │ 编码 │ │ 编码器 │ │ RAW帧 │

└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘

┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐

│ 显示 │◀───│ JPEG │◀───│ FEC │◀───│ 802.11 │

│ │ │ 解码 │ │ 解码器 │ │ RAW帧 │

└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘

核心代码实现

1. 摄像头初始化

首先配置OV2640摄像头,使用JPEG格式输出以节省带宽:

camera_config_t esp32cam_config = {

.pin_pwdn = CAMERA_PIN_PWDN,

.pin_reset = CAMERA_PIN_RESET,

.pin_xclk = CAMERA_PIN_XCLK,

.pin_sscb_sda = CAMERA_PIN_SIOD,

.pin_sscb_scl = CAMERA_PIN_SIOC,

// ... 省略引脚配置

.pixel_format = PIXFORMAT_JPEG, // JPEG硬件编码

.frame_size = FRAMESIZE_XGA, // 1024x768

.jpeg_quality = 30, // 画质与压缩比平衡

.fb_count = 2, // 双缓冲

.fb_location = CAMERA_FB_IN_PSRAM, // 使用PSRAM

};

esp_err_t err = esp_camera_init(&config);

2. 自定义数据包格式

为了高效传输,我们设计紧凑的数据包结构:

#pragma pack(push, 1)

struct Air2Ground_Video_Packet {

enum class Type : uint8_t {

Video, // 视频数据

Telemetry // 遥测数据

};

Type type; // 包类型 (1字节)

uint32_t size; // 包大小 (4字节)

uint8_t pong; // 延迟测量 (1字节)

uint8_t crc; // 校验 (1字节)

Resolution resolution; // 分辨率 (1字节)

uint8_t part_index : 7; // 分片索引 (7位)

uint8_t last_part : 1; // 最后一包标识 (1位)

uint32_t frame_index; // 帧序号 (4字节)

// 后面跟实际视频数据

};

#pragma pack(pop)

static_assert(sizeof(Air2Ground_Video_Packet) == 13, "精确控制包大小");

3. 802.11 RAW帧发送

这是整个系统最核心的部分------构造并发送自定义WiFi帧:

void send_raw_data(const uint8_t *target_mac, const uint8_t *data, uint16_t len) {

static uint8_t packet[1500];

uint8_t my_mac[6];

esp_read_mac(my_mac, ESP_MAC_WIFI_STA);

// 构造802.11数据帧头

packet[0] = 0x40; // 帧控制:Version=0, Type=Data, Subtype=Data

packet[1] = 0x00; // 标志位

packet[2] = 0x00; // Duration

packet[3] = 0x00;

memcpy(&packet[4], target_mac, 6); // 目标MAC地址 (Receiver Address)

memcpy(&packet[10], my_mac, 6); // 源MAC地址 (Transmitter Address)

memcpy(&packet[16], CUSTOM_BSSID, 6);// BSSID,自定义标识

// 序列号,用于区分帧

static uint16_t seq = 0;

packet[22] = (seq & 0x0F) << 4;

packet[23] = (seq >> 4) & 0xFF;

seq++;

// 复制数据负载

memcpy(&packet[24], data, len);

// 发送RAW帧

esp_wifi_80211_tx(WIFI_IF_STA, packet, 24 + len, true);

}

关键技术点

  • esp_wifi_80211_tx 是ESP-IDF提供的底层发送接口

  • 帧头只占24字节,比UDP/IP的42字节节省近一半

  • 不需要ARP、IP、UDP层层封装

4. FEC前向纠错编码

为解决无线丢包问题,我们引入Reed-Solomon风格的FEC编码:

class Fec_Codec {

public:

static const uint8_t MAX_CODING_K = 16;

static const uint8_t MAX_CODING_N = 32;

struct Descriptor {

uint8_t coding_k = 8; // 原始数据包数量

uint8_t coding_n = 12; // 总包数(原始+冗余)

size_t mtu = 1330; // 单包大小

uint8_t priority = configMAX_PRIORITIES - 1;

};

bool init_encoder(const Descriptor& descriptor);

bool encode_data(const void* data, size_t size, bool block);

bool flush_encode_packet(bool block);

void set_data_encoded_cb(void (*cb)(void* data, size_t size));

};

FEC工作流程

  1. 收集8个原始视频包(coding_k=8)

  2. 生成4个冗余包(coding_n - coding_k = 4)

  3. 共12个包发送出去

  4. 接收端只需收到任意8个包即可恢复全部数据

这样即使丢失33%的包,依然可以完整恢复视频帧!

5. 视频帧分片与发送

将摄像头采集的JPEG帧分片后送入FEC编码器:

void send_video_frame(uint8_t *fb_buf, size_t fb_len) {

static uint32_t current_frame_id = 0;

size_t offset = 0;

int packets_sent_this_frame = 0;

// 单包有效载荷 = MTU - 包头

size_t max_video_payload = 1330 - sizeof(Air2Ground_Video_Packet);

while (offset < fb_len) {

size_t chunk_size = min(max_video_payload, fb_len - offset);

// 从FEC编码器获取数据缓冲区

uint8_t *packet_buffer = my_fec_encoder.get_encode_packet_data(true);

// 填充视频包头

Air2Ground_Video_Packet *header = (Air2Ground_Video_Packet*)packet_buffer;

header->type = Air2Ground_Header::Type::Video;

header->size = chunk_size + sizeof(Air2Ground_Video_Packet);

header->frame_index = current_frame_id;

header->last_part = (offset + chunk_size >= fb_len) ? 1 : 0;

// 复制视频数据

memcpy(packet_buffer + sizeof(Air2Ground_Video_Packet),

fb_buf + offset, chunk_size);

// 提交到FEC编码器

my_fec_encoder.flush_encode_packet(true);

offset += chunk_size;

packets_sent_this_frame++;

}

current_frame_id++;

}

6. 完整主程序

extern "C" void app_main(void) {

// 提高任务优先级

vTaskPrioritySet(NULL, 10);

// 初始化WiFi

nvs_flash_init();

esp_netif_init();

esp_event_loop_create_default();

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

esp_wifi_init(&cfg);

esp_wifi_set_mode(WIFI_MODE_STA);

esp_wifi_start();

// 关键:锁定WiFi信道并开启混杂模式

esp_wifi_set_promiscuous(true);

esp_wifi_config_80211_tx_rate(WIFI_IF_STA, WIFI_PHY_RATE_11M_S);

esp_wifi_set_channel(WIFI_CHANNEL, WIFI_SECOND_CHAN_NONE);

// 初始化FEC编码器

Fec_Codec::Descriptor fec_desc;

fec_desc.coding_k = 8;

fec_desc.coding_n = 12;

fec_desc.mtu = 1330;

my_fec_encoder.init_encoder(fec_desc);

my_fec_encoder.set_data_encoded_cb(on_fec_encoded);

// 初始化摄像头

OVCamera cam;

cam.init(esp32cam_config);

// 主循环

while (1) {

cam.run();

if (cam.fb != NULL) {

send_video_frame(cam.fb->buf, cam.fb->len);

cam.done();

vTaskDelay(pdMS_TO_TICKS(5)); // 喂狗

}

}

}

项目文件结构

fpv_sender/

├── CMakeLists.txt # 编译配置

├── idf_component.yml # 依赖管理

├── main/

│ ├── fpv_sender.cpp # 主程序

│ ├── fec_codec.cpp # FEC编解码

│ ├── fec.cpp # FEC数学实现

│ ├── crc.cpp # CRC校验

│ ├── OVCamera.cpp # 摄像头驱动

│ ├── packets.h # 数据包定义

│ └── structures.h # 通用结构

相关推荐
Groundwork Explorer3 天前
W5500 CircuitPython 驱动测试知多少?
socket·w5500·esp32s3·circuitpython·socketpool
qdprobot14 天前
ESP32S3 AiTall V3 Mixly 图形化编程开发AI小智 MCP AIOT大模型对话开发视频教程Micropython小智AI系统
人工智能·micropython·esp32s3·图形化编程·mcp·mixly小智ai·大模型对话
crazin1 个月前
MimiClaw网络教程踩坑记录
esp32s3·嵌入式ai·小龙虾·mimiclaw
jianqiang.xue2 个月前
ESP32-S3 运行 Linux 全指南:从 RISC-V 模拟器移植到 8 秒快速启动
linux·stm32·单片机·mongodb·risc-v·esp32s3
风痕天际3 个月前
ESP32-S3开发教程6:硬件定时器
单片机·嵌入式硬件·嵌入式·esp32·freertos·esp32s3
风痕天际3 个月前
ESP32-S3开发教程五-按键中断2(使用FreeRTOS)
单片机·嵌入式硬件·esp32·vs code·esp32s3·esp-idf
容沁风4 个月前
挑选合适的esp32 s3-cam模块
esp32s3·esp32相机模块
风痕天际4 个月前
ESP32-S3开发教程三:蜂鸣器与FreeRTOS多任务协同
单片机·嵌入式·freertos·esp32s3
Lxinccode5 个月前
ESP32-S3(2) : 安装ESP-IDF
esp32·esp32s3·esp-idf安装