嵌入式RTSP流转发与图像分析基于嵌入式设备实现RTSP视频流转发到后端PC,并识别视频流中的无人机
一、项目背景与目标
原项目是基于 ESP-IDF 框架开发的 ESP32-S3 AI 智能摄像头(项目名 XR-AI-CAM),运行在深圳小二极客科技(XiaoRGEEK)定制的 ESP32-S3 开发板上。核心功能包括:OV2640 摄像头视频采集、ST7789 LCD 本地显示、HTTP MJPEG 视频流(multipart/x-mixed-replace,端口 81)、以及基于 esp-dl 深度学习库的人脸检测与人脸识别功能。WiFi 默认工作于 AP 模式(SSID: ESP32-Camera),用户连接后通过浏览器访问 192.168.4.1 查看视频和控制相机参数。
本项目的目标是将原 ESP-IDF 项目迁移到 PlatformIO 构建系统,同时用 RTSP(Real-Time Streaming Protocol)协议替代原有的 HTTP MJPEG 视频流传输方式,移除 AI 人脸识别功能以简化代码,保留并优化核心的视频推流能力。要求保持所有硬件引脚配置不变(因原项目已在该板子上验证可用),WiFi 热点名改为"111",密码设为"wsq86317321"。
硬件平台核心参数:
芯片:ESP32-S3 (Xtensa LX7) @ 240MHz, 40MHz晶振
Flash:16MB Quad SPI (QIO模式, 80MHz)
PSRAM:8MB Octal SPI (80MHz, "qio_opi" 配置)
摄像头:OV2640 (DVP 8-bit 并行接口, XCLK 12MHz)
LCD:ST7789V (SPI3_HOST, 320×240, 16-bit RGB565)
WiFi:STA 模式连接手机热点
二、开发过程与关键困难及解决方案
困难 1:ESP-IDF → PlatformIO 框架迁移的编译链接错误
初次尝试直接使用 PlatformIO 的 ESP-IDF 框架(framework = espidf),遇到两个层面的编译问题。第一,esp32-camera 库的 sccb.c 引用了 Kconfig 宏 CONFIG_SCCB_CLK_FREQ,但 PlatformIO 不会为第三方库自动运行 Kconfig,该宏未定义导致编译失败。第二,PlatformIO registry 上的 esp32-camera 库实际上是 Arduino 版本,其 cam_hal.c 调用了 ESP-IDF 的 ll_cam_* 系列 HAL 层函数,在纯 ESP-IDF 框架下这些符号链接失败。同时 Arduino 版本的 xclk.c(负责生成摄像头时钟信号)依赖框架内部符号 xclk_timer_conf,在 ESP-IDF 框架下同样未定义。
解决方案:切换为 Arduino 框架(framework = arduino)。这是 PlatformIO 上使用 esp32-camera 的标准方式。Arduino 框架内置了完整的 ESP-IDF 子系统(包括 lwIP、WiFi、camera HAL 等),组件依赖关系已被正确配置。同时在 platformio.ini 的 build_flags 中手动补充缺失的 Kconfig 宏(CONFIG_SCCB_CLK_FREQ=100000、CONFIG_OV2640_SUPPORT=1 等共 9 个宏),以及删除原项目中 ESP-IDF v5.x 特有的 API 调用(esp_lcd_panel_dev_config_t 中的 rgb_ele_order 字段在 Arduino 框架的 ESP-IDF v4.4 中不存在)。
困难 2:LCD 花屏 → PSRAM 未正确启用的隐蔽错误
首次成功编译烧录后,LCD 显示完全花屏(全屏随机彩色噪点,而非黑屏或无显示)。当时有多种可能的根因:SPI 时钟过高、DMA 配置错误、PSRAM 缓冲区失效等。关键线索来自 PlatformIO 编译日志中的一行信息:
PLATFORM: Espressif 32 > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
PlatformIO 自动选择了"N8"板子变体——8MB Flash、无 PSRAM。而实际硬件是 16MB Flash + 8MB Octal PSRAM。无 PSRAM 意味着 camera_config_t 中的 fb_location = CAMERA_FB_IN_PSRAM 分配失败,摄像头帧缓冲区实际分配在内部 SRAM 中(内部 SRAM 仅 512KB,无法容纳多个 VGA 帧缓冲),DMA 写入的是未初始化或已被覆盖的内存区域,显示到 LCD 上即为随机噪点(花屏)。
解决方案:在项目 boards/ 目录下创建自定义板子定义文件 esp32s3-custom.json,核心配置:
"memory_type": "qio_opi" // Quad Flash + Octal PSRAM(关键!)
"flash_size": "16MB"
"maximum_ram_size": 8388608 // 8MB
"partitions": "default_16MB.csv"
值得记录的错误:最初将 memory_type 错误设为 "opi_opi"(Octal Flash + Octal PSRAM)。该板子的 Flash 实际是 Quad 模式,错误配置导致第二级 bootloader 无法正确读取固件,设备完全无法启动(LCD 黑屏、无 WiFi 热点、无串口输出)。修正为 "qio_opi" 后系统正常启动,PSRAM 被正确识别为 8MB。
困难 3:自写 RTSP 服务器导致 WiFi 反复断连
在完成 RTSP 服务器自写实现后(约 400 行 C++ 代码,实现了 TCP socket 服务器 + RTSP 五命令 + RTP over TCP 交错传输 + JPEG 分片),系统出现反复断连现象。手机热点显示 ESP32 一会连接一会断开。排查采用逐步简化法:
步骤一:纯 WiFi 连接(去掉摄像头和 RTSP)→ WiFi 稳定。步骤二:WiFi + 摄像头抓帧(不加 RTSP)→ WiFi 稳定。步骤三:WiFi + 摄像头 + TCP 端口 554 监听(仅响应 RTSP 命令,不推流)→ WiFi 稳定。步骤四:WiFi + 摄像头 + 完整 RTSP 推流 → WiFi 断连。定位到:问题出在 RTP 推流代码中。
根因分析:摄像头 DMA 操作和 WiFi ISR 在同一个 CPU 核心(Core 0)上竞争。send_rtp_frame() 函数在一个 while 循环中逐分片发送 JPEG 数据(每帧 5-15 个 RTP 分片),循环内 send() 系统调用可能阻塞等待 TCP 发送缓冲区。在此期间 WiFi 栈的 ISR 无法及时处理 WiFi Beacon 帧和管理帧,导致 AP 侧认为 STA 已断开连接。参考原项目架构后发现:原项目将摄像头抓帧任务 task_process_camera 钉在 Core 1 (priority 5),HTTP 服务和 WiFi 运行在 Core 0。Camera DMA 和 WiFi ISR 被隔离到不同的核心。
修复尝试:拆分 cam_task(Core 1, prio 5)和 rtsp_srv(Core 0, prio 4),通过 FreeRTOS 队列通信;client socket 设为 O_NONBLOCK + send() 使用 MSG_DONTWAIT;每次循环 vTaskDelay(1) 主动让出 CPU。这些措施有所改善但仍不够稳定。最终决定放弃自写 RTSP 实现。
困难 4:采用 Micro-RTSP 开源库(关键转折点)
在分析两个成熟的参考项目(ESP32-RTSPServer by rjsachse 和 esp32cam-rtsp by rzeldent)后,发现它们都使用了 geeksville/Micro-RTSP 库处理 RTSP 协议栈,而非自写。两个项目的分析还揭示了以下最佳实践:
① 关闭欠压检测(WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0)):WiFi 发射时的瞬间电压下降可能触发欠压复位,导致设备不断重启,表现完全符合"反复断连"的症状。此设置是参考项目可靠性的 #1 因素。② CAMERA_GRAB_LATEST:总是取最新帧,丢弃未处理的旧帧,避免帧堆积导致延迟发散。③ 帧率控制(~10fps 定时器驱动):限制 WiFi 带宽占用,避免 TCP 拥塞风暴。④ 非阻塞 socket(O_NONBLOCK):所有 socket 操作不阻塞 FreeRTOS 调度,WiFi 栈始终能得到 CPU 时间。⑤ RTP 分片 ≤ 1438 字节:低于 MTU(1500 - IP头 - UDP头),避免 IP 层分片(WiFi 环境下 IP 分片极易丢失)。⑥ JPEG 编码直接复用摄像头帧(无需额外 memcpy)。
解决方案:将 Micro-RTSP 库源码复制到项目的 src/ 目录(便于修改访问权限),创建自定义的 Esp32CamStreamer 类继承 CStreamer,桥接 esp32-camera 的帧获取与 Micro-RTSP 的 RTP 推流。同时将 CStreamer.h 中的 m_width、m_height、SendRtpPacket() 从 private 改为 protected,使子类可以动态更新帧尺寸并直接调用 RTP 分片函数。WiFi 从此稳定连接,问题解决。
困难 5:RTSP 连接成功但无画面 —— 摄像头 JPEG 模式配置不兼容
Micro-RTSP 集成后,WiFi 连接稳定,telnet 可成功连接 554 端口,ffplay 可以完成 RTSP握手(看到 DESCRIBE 返回的 SDP 信息),但客户端始终无法显示视频画面。ffplay 打印:
Failed to parse interval end specification
nan : 0.000 fd=0 aq=0KB vq=0KB sq=0B
vq=0KB 表示视频队列中没有任何数据——服务器根本没有发送 RTP 视频包。为排查摄像头是否正常,在 ESP32 上添加了一个简易 HTTP 诊断服务器(端口 80),提供 /snapshot 端点返回摄像头当前帧的 JPEG 快照。浏览器访问 /snapshot 返回"Camera not ready",确认 esp_camera_fb_get() 返回 NULL——摄像头不产帧。
根因:之前为简化 RTSP 推流流程,将摄像头 pixel_format 从原项目的 PIXFORMAT_RGB565 改为 PIXFORMAT_JPEG,期望摄像头直接输出 JPEG 数据无需软件编码。但该配置在当前 OV2640 + ESP32-S3 组合下不工作,esp_camera_fb_get() 永远返回 NULL。具体原因可能是 JPEG 模式需要特定的寄存器配置或时钟设置,在当前板子上这些条件未被满足。
解决方案:将摄像头配置完全恢复为原项目的验证配置(PIXFORMAT_RGB565 + FRAMESIZE_240X240 + CAMERA_GRAB_WHEN_EMPTY + jpeg_quality=12 + fb_count=2)。在 RTSP 推流时,通过 frame2jpg(fb, 30, &jpg_buf, &jpg_len) 将 RGB565 实时转换为 JPEG。恢复后 HTTP snapshot 立即出现 240×240 的清晰画面,RTSP 流也随即正常工作。
困难 6:播放端优化 —— 延迟、闪烁与画质平衡
RTSP 流联通后,出现两个播放体验问题:VLC 画面不时闪烁、时有时无;ffplay 画面有约 3 秒延迟。通过 ffplay 调试日志分析:默认使用 UDP 传输时,手机热点 WiFi 环境下 UDP 丢包率较高,RTP 分片丢失导致 JPEG 帧不完整,VLC 呈现闪烁或黑屏。TCP 传输虽然可靠但初始配置下帧率过低(10fps),JPEG 编码质量过高(80)导致单帧体积大、编码时间长,进一步降低了有效帧率。
优化措施:① 强制 TCP 传输(ffplay -rtsp_transport tcp / vlc --rtsp-tcp)—— 消除 UDP 丢包导致的花屏;② JPEG 质量从 80 降至 30 —— 单帧约缩小 60%,编码时间缩短约 40%;③ 帧率从 10fps 提升到约 20fps(帧间隔 100ms→50ms);④ 抓帧模式改为 CAMERA_GRAB_LATEST —— 客户端取帧慢时自动丢弃旧帧;⑤ ffplay 低延迟参数(-fflags nobuffer -flags low_delay -framedrop)—— 客户端侧减少缓冲区。优化后 TCP 模式画面清晰无闪烁,延迟从约 3 秒降至约 1-2 秒(受限于 ESP32 软件 JPEG 编码开销)。
三、RTSP 流转发逻辑架构
下图为整个系统的数据流架构,展示从摄像头硬件到播放器的完整链路。
┌─────────────────────────────────────────────────────────────────────────┐
│ ESP32-S3 固件架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ OV2640 │ DVP │ cam_task │ Queue │ xFrameQueue │ │
│ │ 摄像头 │───────▶│ (Core 1, Prio 5) │───────▶│ (FreeRTOS │ │
│ │ (DVP 8-bit) │ │ esp_camera_ │ │ Queue │ │
│ │ 12MHz XCLK │ │ fb_get() 循环 │ │ depth = 2 │ │
│ │ RGB565 │ │ 240×240 RGB565 │ │ 帧指针传递 │ │
│ └─────────────┘ └──────────────────┘ └──────┬───────┘ │
│ │ │
│ ┌───────────────────────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ RTSP 服务 (Core 0, Prio 4) │ │
│ │ │ │
│ │ ┌────────────────┐ ┌──────────────────────────┐ │ │
│ │ │ CRtspSession │ │ Esp32CamStreamer │ │ │
│ │ │ (Micro-RTSP) │ │ (继承 CStreamer) │ │ │
│ │ │ │ │ │ │ │
│ │ │ OPTIONS ── 200 │ │ xQueueReceive(fb) │ │ │
│ │ │ DESCRIBE ─ SDP│ │ ↓ │ │ │
│ │ │ SETUP ──── TCP│ │ frame2jpg(fb, 30) │ │ │
│ │ │ PLAY ──── 开始│ │ RGB565 → JPEG (~8KB) │ │ │
│ │ │ │ │ ↓ │ │ │
│ │ │ TCP :554 │ │ SendRtpPacket() │ │ │
│ │ │ RTSP协议状态机 │ │ 12B RTP头 + 8B JPEG头 │ │ │
│ │ │ │ │ + JPEG数据 分片 ≤1460B │ │ │
│ │ └───────┬────────┘ └───────────┬──────────────┘ │ │
│ │ │ RTSP 命令(TCP) │ RTP 数据(TCP) │ │
│ └──────────┼─────────────────────────┼──────────────────┘ │
│ │ │ │
│ ┌──────────▼─────────────────────────▼──────────────────┐ │
│ │ WiFi Station (Core 0) │ │
│ │ lwIP TCP/IP Stack + 802.11b/g/n PHY │ │
│ │ TCP :554 (RTSP+RTP) + TCP :80 (HTTP诊断) │ │
│ └──────────────────────────┬────────────────────────────┘ │
│ │ │
└──────────────────────────────┼───────────────────────────────────────────┘
│
┌───────────▼───────────┐
│ 手机热点 "111" │
│ WiFi AP / DHCP │
│ 10.128.247.x/24 │
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ PC 播放器 │
│ ffplay / VLC │
│ rtsp://IP:554/ │
│ /mjpeg/1 │
└───────────────────────┘
数据流分步说明
【第1步:摄像头捕获】cam_task 运行在 Core 1(priority 5),循环调用 esp_camera_fb_get() 从 OV2640 传感器读取一帧 RGB565 格式的 240×240 图像(每个像素 2 字节,每帧约 115KB)。摄像头通过 DVP(Digital Video Port)8-bit 并行接口与 ESP32-S3 连接,XCLK 主频 12MHz。帧缓冲区分配在 PSRAM 中(fb_location = CAMERA_FB_IN_PSRAM),双缓冲配置(fb_count=2)确保摄像头 DMA 始终有可用缓冲区。获取帧后通过 FreeRTOS 队列(xQueueSend)发送给 RTSP 任务,队列深度为 2,仅传递帧指针(不拷贝数据),内存零开销。
【第2步:RTSP 协议协商】rtsp_srv 任务运行在 Core 0(priority 4)。CRtspSession(Micro-RTSP 库)通过 TCP 端口 554 监听客户端连接,处理标准 RTSP 五命令序列:OPTIONS → 返回支持的 RTSP 方法列表(DESCRIBE, SETUP, PLAY, TEARDOWN, PAUSE);DESCRIBE → 返回 SDP(Session Description Protocol)描述,包含视频格式(JPEG)、RTP 载荷类型(26)、IP 地址等媒体信息;SETUP → 建立 RTP 传输通道,返回 TCP 交错模式(RTP/AVP/TCP;interleaved=0-1),RTP 和 RTCP 数据通过同一 TCP 连接的不同逻辑通道(channel 0 和 channel 1)传输;PLAY → 开始推送视频流,设置 m_streaming = true。
【第3步:视频编码与 RTP 封装】CRtspSession::broadcastCurrentFrame() 被定时调用(~20fps),内部调用 Esp32CamStreamer::streamImage():从队列接收 RGB565 帧 → frame2jpg(fb, 30, &jpg_buf, &jpg_len) 将 RGB565 转换为 JPEG(quality=30,240×240 约 4-8KB)→ 逐分片调用 SendRtpPacket()。每个 RTP 包的格式:4 字节 TCP 交错头($ + channel + 2B长度)+ 12 字节 RTP 头(V=2,P=0,X=0,CC=0 + M+PT=26 + SequenceNum + Timestamp + SSRC)+ 8 字节 JPEG 载荷头(RFC 2435: type=0 4:2:2, Q=0x5e, width/8, height/8)+ JPEG 数据(≤1460 字节/分片)。所有 RTP 包通过 TCP 顺序发送,保证接收端按序完整重组 JPEG 帧。
【第4步:客户端解码显示】客户端(ffplay/VLC)连接到 TCP 端口 554,完成 RTSP 握手后进入 PLAY 状态。TCP 流中交替出现 RTSP 命令响应(明文文本)和 RTP 数据($ + channel 前缀的二进制数据)。客户端解析 TCP 交错帧,提取 RTP 包;多个 RTP 分片按 SequenceNum 排序重组为完整 JPEG 帧;JPEG 解码器将帧渲染到屏幕。TCP 模式确保数据有序完整到达(不丢包),画面清晰无闪烁,代价是端到端延迟约 1-2 秒(主要瓶颈在 ESP32 软件 JPEG 编码 ~50ms/帧 + TCP 确认往返时间)。
四、提问质量分析与改进建议
4.1 提供了关键思路的有效提问
✓ "显示器一直花屏怎么回事啊"
直接指向 PSRAM 配置错误的核心症状。"花屏"而非"黑屏"暗示硬件在工作但数据有误(而非初始化失败),这个精确的症状描述驱动了 PlatformIO 板子变体选择的排查方向,最终发现"No PSRAM"配置错误。
✓ "能不能改成这个设备和我的电脑都连上手机热点...然后在同一网段下传输rtsp视频"
跳出了"修复 ESP32 AP 模式"的思维定势。从 AP 切换到 STA 同时解决了两个问题:ESP32 Arduino AP 模式的客户端兼容性问题(可见但连不上),以及 ESP32 与 PC 不在同一网段的问题。这个网络架构调整是 WiFi 功能稳定化的关键转折点。
✓ "要不然你参考一下 C:\Users\28400\Desktop\嵌入式\web 这个项目吧"
原项目已在该硬件上验证可用,是最可靠的"真值参考"。分析原项目架构直接揭示了核心隔离设计(cam_task Core 1 + WiFi Core 0),这是自写 RTSP 服务器不稳定的根因。"回头看已知能用的东西"是嵌入式开发中最高效的调试策略。
✓ "以及github上面还有一个项目:https://github.com/rzeldent/esp32cam-rtsp.git"
引用这个开源项目是决定性的。它展示了 Micro-RTSP 库的正确用法,以及关欠压检测、CAMERA_GRAB_LATEST 等关键工程实践。"不要重新发明轮子"的思维直接解决了自写 RTSP 不稳定问题。
✓ "用串口监视器也没有接收到数据" + "热点有没有显示它的接入"
这两个观察形成了多维度交叉验证:LCD 黑屏 + 串口无输出 + WiFi 热点不出现 → 三重证据强烈指向固件根本没有正常启动,而非某个单一模块的故障。这推动了 PlatformIO 板子定义的深入排查,发现了 opi_opi vs qio_opi 的致命配置错误。
✓ "为什么可以稳定连接了,telnet好像也可以连上...但是vlc还是回显"
精确区分了 TCP 连接层(L4)vs RTSP 协议层(L7)。"telnet 能连但 VLC 不行"直接定位到 Micro-RTSP 的 URL 路径匹配问题(DESCRIBE 需 /mjpeg/1 路径),为后续的 ffplay 调试(UDP vs TCP 传输)和摄像头 JPEG 模式排查划定了正确的排查范围。
✓ "加载不出照片。点snapshot回显:Camera not ready"
HTTP 诊断接口将黑盒问题变成白盒。"Camera not ready" 的精确反馈直接定位根因:摄像头不产帧。这推动了关键决策:放弃 PIXFORMAT_JPEG 模式,恢复验证可用的 PIXFORMAT_RGB565 + frame2jpg 路径。
4.2 可以更精确的提问方式
△ "还是报错"——仅提供结论性描述,未附完整的错误内容
编译错误日志中的每一行都可能包含根因线索。例如 HARDWARE 行显示的板子变体型号直接暴露了 PSRAM 配置问题。建议:在提问时附上完整的编译输出或运行日志。可以使用重定向保存:pio run 2>&1 | tee build.log,然后分享完整日志文件或关键片段。完整日志可大幅减少"盲猜"环节。
△ "还是在一直重连"——未描述重连的周期和模式
WiFi 的重连行为包含了重要诊断信息:高频重连(1-2 秒间隔)≈ 代码崩溃导致设备重启;中频重连(5-10 秒)≈ WiFi 认证失败或信号弱;低频重连(30 秒以上)≈ DHCP 续约或省电模式唤醒。建议:观察并记录重连间隔、手机上显示的是"已连接"还是"正在获取IP",这些信息可以大幅缩小排查范围。
△ "好像前面那个稍微好一点?而且画面好像更清晰,为什么"——模糊定性描述
A/B 对比测试时,建议具体说明差异:"TCP 模式延迟约 1 秒但画面完整,UDP 模式延迟约 0.5 秒但偶尔花屏"。定量的对比数据比定性的"好像好一点"更有助于精确调参。可以使用 ffplay 的 stats 信息获取帧率、丢帧数等客观指标。
△ 初期未一次性提供完整硬件规格
建议在项目启动时一次性提供:芯片型号(ESP32-S3)、Flash(16MB Quad QIO)、PSRAM(8MB Octal 80MHz)、摄像头型号(OV2640)、LCD 型号(ST7789)、烧录串口号(COM8)、板子品牌和型号(XiaoRGEEK)。这些信息是 PlatformIO 板子选择的基础,缺失任何一个都会导致数小时的无效调试。建议在首次提问时附上一张硬件规格清单。
五、最终项目文件结构
esp32-rtsp-Claude/
├── platformio.ini # PlatformIO 项目配置
│ └── board = esp32s3-custom # 自定义板子 (qio_opi)
│ └── framework = arduino # Arduino 框架
│ └── lib_deps = esp32-camera # 唯一外部依赖
├── boards/
│ └── esp32s3-custom.json # 自定义板子:16MB Flash + 8MB Octal PSRAM
├── src/
│ ├── main.cpp # 主入口:WiFi STA + 摄像头 + RTSP + HTTP诊断
│ ├── xr_board.h # 引脚定义 (原项目完全保留)
│ ├── xr_board.c # 摄像头初始化 (RGB565, 240×240, 12MHz)
│ ├── CRtspSession.h / .cpp # Micro-RTSP: RTSP 协议状态机
│ ├── CStreamer.h / .cpp # Micro-RTSP: RTP 分片与 JPEG 封装 (已修改)
│ ├── platglue.h # Micro-RTSP: 跨平台抽象接口
│ └── platglue-esp32.h # Micro-RTSP: ESP32 Arduino 平台适配
└── ESP32-RTSP开发日志.docx # 本文档
六、未来改进方向
▶ 1. 串口调试能力恢复
当前 Arduino SDK 将控制台输出固定到 UART0(GPIO43/44),但本板子使用 ESP32-S3 内置 USB Serial/JTAG (COM8)进行通信。需修改 Arduino 框架的 sdkconfig 或手动初始化 USB Serial/JTAG 驱动,使 printf / ESP_LOGI 输出到 COM8。这将极大提升后续调试效率(目前所有日志都不可见)。
▶ 2. 摄像头 JPEG 直出模式修复
当前使用 RGB565 → frame2jpg() 软件编码路径,每次编码约耗时 50ms(占 20fps 帧预算的 100%)。如果能让 OV2640 直接输出 JPEG 格式(排查 PIXFORMAT_JPEG 配置不工作的具体原因:可能是 时钟配置、DVP 时序或寄存器设置问题),可将帧率从 ~20fps 提升到 30fps+,同时大幅降低 CPU 负载和端到端延迟。
▶ 3. 多人同时观看(多播/多会话)
当前 Micro-RTSP 为单客户端模式(新连接替换旧连接)。Micro-RTSP 库已内置 UDP 多播支持,启用后一次 RTP 发送即可服务多个客户端(类似于 IPTV 直播模式)。对于 TCP 模式,可通过会话管理支持多个客户端同时接收同一视频流。
▶ 4. Web 配置管理页面
参考 esp32cam-rtsp 项目的 IotWebConf 实现,添加 WiFi 配网页面和摄像头参数调节页面。用户可通过浏览器动态调整分辨率、JPEG 质量、帧率等参数,无需重新编译烧录。也解决了"换一个 WiFi 环境就要重新烧录"的问题。
▶ 5. WiFi AP+STA 双模共存
ESP32 同时运行 AP 模式(用于首次配网,SSID 可固定为"ESP32-Config")和 STA 模式(连接目标网络)。配网完成后自动关闭 AP 节省功耗和信道资源。这是 IoT 设备的标准配网方案。
▶ 6. OTA 无线固件升级
通过 ArduinoOTA 库或 ESP-IDF OTA 功能实现 WiFi 无线固件更新,避免每次调试都需要 USB 数据线物理连接。需要配置支持 OTA 的分区表(factory + ota_0 + ota_1)。
▶ 7. AI 功能按需回添
原项目的人脸检测/识别功能可以在当前稳定的 RTSP 流基础上按需回添。人脸检测模型(HumanFaceDetectMSR01/MNP01)可运行在 Core 1 空闲时段,检测结果(人脸框坐标、关键点)通过 WebSocket 或自定义 RTSP 扩展通道推送到客户端。实现了"稳定推流 + 可选AI"的模块化架构。
七、关键技术决策总结
● 构建框架 Arduino 框架(非 ESP-IDF):PlatformIO 上 esp32-camera 库的标准使用方式。避免了 ESP-IDF 框架下的 Kconfig 配置缺失和组件依赖问题。代价是失去了 ESP-IDF menuconfig 的精细控制能力和 UART 调试日志。
● 板子定义 自定义 boards/esp32s3-custom.json,memory_type = "qio_opi"(Quad Flash + Octal PSRAM)。注意区分 qio_opi 和 opi_opi:前者 Flash 为 Quad 模式,后者 Flash 为 Octal 模式。选错会导致 bootloader 无法读取固件、设备完全无法启动。
● RTSP 协议栈 Micro-RTSP(geeksville/Micro-RTSP v0.1.6):成熟的轻量级 RTSP 库,处理协议状态机和 RTP 封装。需修改 CStreamer.h 的访问权限(private→protected)以支持自定义子类。自写 RTSP 协议的工程成本远高于预期(协议边界情况、RTP 分片、TCP/UDP 双模等)。
● 视频编码路径 RGB565 → frame2jpg(quality=30) 软件编码。OV2640 的 PIXFORMAT_JPEG 直出模式在当前硬件/驱动组合下不工作(esp_camera_fb_get() 返回 NULL),软件编码是唯一可行路径。Quality=30 在画质和编码速度间取得平衡:单帧约 4-8KB,编码 < 50ms。
● 传输层协议 RTP/AVP/TCP 交错模式(TCP 端口 554)。TCP 保证数据完整有序到达,消除 UDP 丢包导致的画面闪烁。延迟代价(1-2 秒)在当前应用场景下可接受。如需更低延迟,可启用 Micro-RTSP 的 UDP 多播模式。
● CPU 核心分配 摄像头抓帧 Core 1 (prio 5) + RTSP/WiFi Core 0 (prio 4)。核心隔离确保 WiFi ISR 不被摄像头 DMA 中断阻塞,是 WiFi 稳定性的基石。这是从原项目中逆向分析得到的最关键架构设计。
● 网络拓扑 STA 模式连接手机热点。相比 ESP32 自建 AP,STA 模式的优势:可使用手机现有网络、无需切换 WiFi、客户端兼容性好、DHCP 自动分配 IP。代价是无法独立运行(依赖手机热点)。
八、第二阶段:RTSP 流调试与 YOLO 模型自主训练
在第一阶段(5月26日)完成了从 ESP-IDF MJPEG HTTP 到 PlatformIO RTSP 的基础迁移后,第二阶段(5月29日)的工作重点转向两个方向:RTSP 视频流的稳定性调试,以及基于自主采集数据的 YOLOv8 无人机检测模型训练。期间经历了固件代码丢失、WiFi 信号质量排查、OpenCV RTSP 兼容性、LabelImg 标注工具链、模型训练路径对齐等多个技术障碍,最终实现了从采集到训练到实时检测的完整闭环。
困难7:固件版本覆盖导致 RTSP 服务丢失
在调试 Python 显示卡顿问题的过程中,main.cpp 被错误地重写为纯 HTTP MJPEG 版本(去掉了所有 RTSP 相关代码),导致 ESP32 烧录后 RTSP 端口 554 完全不响应。更严重的是,Desktop 上的唯一备份也被同步覆盖,RTSP 版本的完整源代码一度完全丢失。ffplay 连接超时、ffmpeg 管道无数据、Python 脚本超时报错——所有症状指向"RTSP 服务不存在"。
解决方案:从残存的 Micro-RTSP 库文件(CRtspSession.h/cpp、CStreamer.h/cpp、platglue*.h)出发,重新手写了完整的 RTSP 版 main.cpp(约 140 行),包括 Esp32CamStreamer 自定义子类、HTTP 诊断端点(/ 和 /snapshot)、RTSP 服务启动逻辑、帧率控制(100ms 间隔)等。重新编译烧录后 RTSP 服务恢复。关键的教训是:在修改任何已验证可用的代码前,必须提前创建带时间戳的备份文件。事后已将完整项目备份至 Desktop\嵌入式\esp32-rtsp-Claude备份\ 目录。
困难8:Python 侧 RTSP 读取的五种方案对比
在确认 RTSP 固件正常(ffplay 能播放)的前提下,Python 脚本却反复无法获取视频帧。先后尝试了五种方案,每种都暴露了不同层面的兼容性问题:
方案一:cv2.VideoCapture 直读 RTSP。OpenCV 内置的 FFmpeg 后端无法正确解码 Micro-RTSP 发出的 JPEG/RTP 载荷(payload type 26),表现是 30 秒超时后返回 "Stream timeout"。方案二:ffmpeg 子进程管道(subprocess.Popen + stdout.read)。管道阻塞式读取导致主线程 UI 冻结,OpenCV 的 imshow 窗口无法响应键盘事件。方案三:ffmpeg + 后台线程读取。reader 线程持续从管道拉帧,主线程只负责显示和 YOLO 推理。但当 RTSP 固件异常时,后台线程静默失败,主线程永远等不到第一帧,用户看到"连接成功"但无画面。方案四:HTTP snapshot 轮询(urllib + cv2.imdecode)。完全不依赖 RTSP/RTP 协议栈,每次 HTTP GET 获取一个独立 JPEG 帧。可靠性最高(snapshot 端点在所有故障场景下都能正常工作),代价是帧率较低(受限于 HTTP 往返时间,约 5-8 fps)。此方案最终用于 collect_images.py 拍照脚本。方案五(最终):确认 RTSP 固件正常后,回到 cv2.VideoCapture 方案。之前失败是因为固件中 RTSP 服务根本没在运行,修复固件后 VideoCapture 能正常连接并持续拉流。此方案用于 3.py 实时检测脚本。
核心经验:Python 访问 RTSP 流的最可靠方案是 OpenCV VideoCapture + 确认固件 RTSP 服务正常。诊断优先级应为:ffplay 能播 → RTSP 端口正常 → Python 再连接。ffplay 是 RTSP 连通性的"金标准"测试工具。
困难9:WiFi 热点子网变更导致的间歇性卡顿
ESP32 每次连接手机热点后分配的 IP 在不同子网间漂移(10.128.x.x、10.64.x.x、10.182.x.x、10.20.x.x、10.51.x.x 等)。部分子网下 RTSP 推流流畅稳定,部分子网下出现大量 "Missing packets; dropping frame" 和 "RTP timestamps don't match" 错误,画面严重卡顿。但 HTTP snapshot 端点在所有子网下都表现正常(连续 10 次请求均在 0.5 秒内返回 4-5KB 数据),说明 TCP 层面连接正常,问题局限在 RTSP RTP 分片的实时性上。
排查方法:用 Python 一行脚本连续拉取 snapshot,统计每次响应大小和耗时。HTTP 始终秒级响应 = WiFi 正常 + 摄像头正常。RTP 丢包 = 手机热点在不同子网/信道上的 QoS 差异。尝试了降低帧率(20fps→10fps)和增加主循环延迟(10ms→20ms),效果有限。将手机放置在 ESP32 30cm 以内可明显改善。
困难10:LabelImg 标注工具兼容性 + makesense.ai 在线工具
在准备自主训练数据集时,首先尝试安装 LabelImg 图形标注工具。LabelImg 与新版本 PyQt5 存在不兼容问题(drawLine 参数的 float/int 类型错误),使用 pip install labelImg 后按 W 键创建标注框即崩溃退出。尝试降级到 labelImg==1.8.6 仍然失败。
解决方案:改用在线标注工具 makesense.ai(浏览器直接访问,无需安装)。操作流程:Get Started → 拖入图片文件夹 → 选 Object Detection → 添加标签 drone → 框选目标 → Actions → Export Annotations → 选择 YOLO 格式导出。导出后需将 labels 文件夹中的 .txt 标注文件移动到与 images 同级的 labels/ 目录,YOLOv8 训练脚本才能正确读取。
困难11:自主模型训练与效果评估
使用 collect_images.py(HTTP snapshot 方案)在无人机不同距离(5-50 米)和角度下采集了 41 张 RGB565 240×240 训练图片,经 makesense.ai 标注后,以网上的公开无人机检测模型 best.pt(1359 张图片训练,mAP50=0.995)为基座,进行迁移学习(fine-tune)。训练配置:80 epochs、imgsz=640、batch=8、AdamW 优化器、余弦退火学习率、数据增强(旋转/翻转/HSV 色域变换)、EarlyStopping patience=20。
训练在 Intel Core i9-14900HX CPU 上完成,65 轮后自动早停(最佳模型在第 45 轮),总耗时约 5 分钟。最终模型(6.3MB)在自己的 41 张验证集上达到 mAP50=0.995、mAP50-95=0.508。但在实际 RTSP 视频流测试中,自主训练的模型检测效果不如网上 1359 张图片训练的模型。分析原因:(1) 41 张图片覆盖的场景远少于 1359 张;(2) 远距离小目标的样本偏少;(3) 标注可能不够精确。结论:需要继续扩充训练数据集(目标 100+ 张),特别增加远距离(30-50m)样本。
两个模型路径:
网上模型(当前 3.py 使用):
C:\Users\28400\Real-time-detection-of-drone-based-on-YOLO8\drone_detection\runs\detect\drone\drone_train\weights\best.pt
自主训练模型:
C:\Users\28400\Desktop\嵌入式\runs\detect\my_drone_train\drone_v1\weights\best.pt
九、中期报告整合:硬件验证与模型方案探索
9.1 ESP32-S3 硬件引脚配置验证
在将原 ESP-IDF 项目迁移到 PlatformIO 前,首先对 XiaoRGEEK 定制 ESP32-S3 开发板的引脚配置进行了全面审查。原项目在 xr_board.h 中定义的摄像头 DVP 引脚和 LCD SPI 引脚已经过验证,在本项目中完全保留不变:
/* 摄像头 DVP 8-bit 并行接口 */
#define CAMERA_PIN_D0 7 #define CAMERA_PIN_D1 5
#define CAMERA_PIN_D2 4 #define CAMERA_PIN_D3 6
#define CAMERA_PIN_D4 15 #define CAMERA_PIN_D5 17
#define CAMERA_PIN_D6 18 #define CAMERA_PIN_D7 3
#define CAMERA_PIN_XCLK 8 // 12MHz 主时钟
#define CAMERA_PIN_PCLK 16 // 像素时钟
#define CAMERA_PIN_VSYNC 10 // 帧同步
#define CAMERA_PIN_HREF 46 // 行参考
#define CAMERA_PIN_SIOD 1 // I2C 数据 (与 I2C 总线共用)
#define CAMERA_PIN_SIOC 2 // I2C 时钟 (与 I2C 总线共用)
#define CAMERA_PIN_PWDN 9 // 电源控制
#define CAMERA_PIN_RESET -1 // 复位 (不使用)
/* LCD ST7789 SPI */
#define BSP_LCD_SPI_MOSI 42 #define BSP_LCD_SPI_CLK 41
#define BSP_LCD_SPI_CS 11 #define BSP_LCD_DC 40
#define BSP_LCD_BACKLIGHT 39
#define BSP_LCD_SPI_NUM SPI3_HOST // ESP32-S3 的 SPI3
#define BSP_LCD_PIXEL_CLOCK_HZ (80 * 1000 * 1000) // 80MHz
摄像头通过 DVP(Digital Video Port)8-bit 并行接口与 ESP32-S3 连接,XCLK 主频 12MHz。I2C 总线(SDA=GPIO1, SCL=GPIO2)同时用于摄像头 SCCB 配置和板级外设通信。需要注意 GPIO1/2 被 I2C 占用后,ESP32 默认的 UART0 串口输出(也使用 GPIO1/2)不可用,在本项目中直接导致串口调试输出完全不可见(Phase 1 中所有 ESP_LOGI/printf 输出均丢失)。LCD 使用 SPI3_HOST(ESP32-S3 的第三路 SPI),80MHz 时钟,SPI Mode 2(CPOL=1, CPHA=0),16-bit RGB565 颜色格式。
9.2 RTSP 协议原理研究
在确定采用 RTSP 方案替代 HTTP MJPEG 后,首先对 RTSP 协议原理进行了系统学习。RTSP(Real-Time Streaming Protocol,RFC 2326)是一个应用层协议,用于控制实时媒体流的传输。它本身不传输媒体数据,而是通过 SDP(Session Description Protocol)描述媒体格式,通过 SETUP 协商 RTP(Real-Time Transport Protocol)的传输参数(UDP 端口或 TCP 交错通道),PLAY 命令触发媒体流推送。
标准的 RTSP 交互流程为五步握手:OPTIONS(查询服务器能力)→ DESCRIBE(获取 SDP 媒体描述)→ SETUP(建立 RTP 传输通道,指定 TCP 交错或 UDP 单播/多播)→ PLAY(开始推流)→ TEARDOWN(结束会话)。本项目的 Micro-RTSP 库(geeksville/Micro-RTSP v0.1.6)完整实现了这套状态机,同时支持 TCP 交错模式(RTP/AVP/TCP;interleaved=0-1)和 UDP 单播模式。
9.3 无人机检测模型方案探索
在完成基础视频流联通后,开始探索无人机检测方案。初始方案是直接使用 YOLOv8 官方预训练模型 (yolov8n.pt / yolov8s.pt),但 COCO 数据集的 80 个类别中不包含"drone",无法直接用于无人机检测。因此需要寻找专门的无人机检测模型或数据集。
通过网络检索,在 GitHub 上找到了 WissalHILEL/Drone-vs-Bird-Detection 项目,该项目提供了基于 YOLOv8 的无人机 vs 鸟类检测完整训练流程和 1359 张标注数据集。将该仓库克隆到本地后,使用其提供的 drone.yaml 配置文件和预标注数据集进行训练。训练在 CPU(Intel i9-14900HX)上耗时较长(初始训练约 8-9 小时),后来发现是因为 PyTorch 默认只用单线程 CPU,未充分利用多核性能。通过调整 DataLoader 的 workers 参数和 batch size,后续训练缩短到了可接受的范围。
网上模型在静态图片上的检测效果良好,能够准确识别画面中的无人机并标注边界框。但在接入实时 RTSP 视频流后,发现两个问题:(1) 240×240 低分辨率视频帧中,远距离(30m+)无人机的像素占比极小,模型检出率急剧下降;(2) 模型是在"其他无人机"的照片上训练的,对用户自己这台无人机的特定外观和涂装不够敏感。这促使了第二阶段自主采集标注训练数据。
十、今日关键问题速查表
● RTSP 固件被覆盖 main.cpp 被覆盖为纯 HTTP 版本 → RTSP 服务丢失 → 从 Micro-RTSP 残余文件重建 main.cpp。教训:改代码前务必备份。
● Python RTSP 五种方案 VideoCapture / ffmpeg pipe / 线程pipe / HTTP轮询 / 修复后VideoCapture。最终方案:ffplay 验证连通性 → VideoCapture 拉流。
● WiFi 子网间歇卡顿 不同子网下 RTP 丢包率差异大。HTTP snapshot 始终正常。排查方法:Python 连续拉取 snapshot 统计。
● LabelImg 崩溃 PyQt 版本不兼容。改用 makesense.ai 在线标注工具。导出的 txt 需放入 labels/ 目录。
● 模型训练效果对比 自主 41 张图不如网上 1359 张。需要 100+ 张多距离角度样本。两个模型路径已记录。
● 项目备份 完整项目已备份至 Desktop\嵌入式\esp32-rtsp-Claude备份\。
十一、第二阶段提问质量回顾
11.1 有效的提问策略
"你之前把我的脚本改成啥了。为什么采集不了相片了"——在被多次修改后,主动要求回归到已知可用的版本,而不是继续在问题版本上修补。这种"回到基线"的思维在嵌入式调试中非常有效。
"手机放ESP32旁边(30cm内)"和"换用不同子网的重开热点"——通过物理距离和网络环境的对比实验,成功将问题定位为 WiFi 信号质量而非代码逻辑错误,避免了无效的代码修改。
"帮我看看现在这个3.py用的模型是哪个"——在发现检测效果变差时,首先检查模型路径是否正确,而非盲目调整参数。这种"先验证配置再调参"的排查顺序非常正确。
"一定要记得备份啊"——在经历了固件代码丢失后,主动要求创建备份。这是本次开发中最重要的教训之一。
11.2 仍需改进的提问习惯
"特别卡""不行""加载不出来"——这些描述过于模糊,同一词汇可能指代完全不同的问题。"卡"可能是帧率低(<5fps)、画面冻结、延迟大(>3秒)或完全黑屏。建议:描述具体现象(例如"画面约2秒更新一次,动作不连续"或"画面完全静止不动")。
"帮我烧录回去"未明确指定版本——在多个版本同时存在的情况下(Desktop 备份、工作目录、历史 git 状态),仅说"烧录回去"无法确定要哪个版本,导致 MJPEG 版本被错误烧录。建议:明确指定路径或时间(例如"烧录 Desktop 备份里的版本"或"恢复到昨天能用的版本")。
多次要求改架构后反悔——这暴露了需求不够清晰的深层问题。建议:在提出架构变更前,先评估"是否必须"和"风险多大",避免在"HTTP vs RTSP"之间反复跳跃。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
— 第二阶段完 · 2026年5月29日 —
更多推荐
所有评论(0)