跳过导航,直接进入主要内容跳过导航,直接进入主要内容跳过导航,直接进入主要内容

系统架构

1. 系统概览

ESP32-S3 摄像头监控系统基于 FreeRTOS 实时操作系统,在双核 ESP32-S3 上运行 27 个松耦合的 C 模块。系统采用事件驱动与轮询混合架构,摄像头采集帧数据后分两路输出:实时 HTTP MJPEG 流和录像存储。

  Camera ──→ Frame Broadcaster ──→ MJPEG Streamer (端口 81) ──→ 浏览器/客户端
          └→ Video Recorder ──→ SD Card (AVI 分段)
                             └→ NAS Uploader ──→ WebDAV / HTTP(S) (互斥)
  Config Manager ←→ NVS ←→ SD Card Override (wifi.txt / nas.txt)
  WiFi Manager (AP/STA) → Time Sync (SNTP)
  Status LED ← LED Controller (GPIO21, 低电平有效)
  Watchdog (30s TWDT, panic) → Health Monitor (60s)
  ONVIF Discovery (UDP 组播) → NVR 自动发现
  音频驱动(I2S PDM DSR_16S, 8kHz)→ DC 去除 → 频谱减法 → 噪声门 → G.711 → 音频广播器
    → RTSP 服务器(端口 554)+ Web 服务器(/api/audio HTTP 流)

核心特性

特性 说明
双核分工 Core 0: 音频采集(优先级 5),录像(优先级 3),RTSP 音频,HTTP/WebSocket;Core 1: MJPEG/RTSP 视频流,SD 写入任务,SD 监控,健康检测,ONVIF
PSRAM 依赖 摄像头帧缓冲分配在 PSRAM(双缓冲),无 PSRAM 无法工作
循环存储 SD 卡剩余空间 < 20% 自动删除最旧录像,恢复到 30%
热插拔 SD 卡拔出自动停止录像,插入自动恢复
看门狗 30s TWDT,超时触发 panic 重启
ONVIF 发现 WS-Discovery 组播,NVR 可自动发现

2. 启动流程

app_main() 中 21 步顺序初始化,任何关键步骤失败仅记录错误但继续执行(部分功能降级)。

步骤 操作 说明
1 初始化 NVS Flash 损坏时自动擦除重建
2 配置管理器 从 NVS 加载配置,无配置则使用默认值
3 状态 LED GPIO21 初始化,设为 LED_STARTING(常亮)
4 SPIFFS 挂载 加载 Web UI 静态资源,挂载失败自动格式化
5 SD 卡存储 SPI 模式,FAT32 文件系统
5a 清理不完整 AVI 删除上次异常断电时未正确关闭的录像文件
6 SD 卡配置覆盖 读取 SD 卡 /sdcard/config/wifi.txt 和 config.txt,覆盖 NVS 配置
7 WiFi 初始化 已配置 SSID → STA 模式,否则 → AP 模式
8 摄像头初始化 自动检测 OV2640/OV3660,配置分辨率/帧率/质量,测试采集验证
9 时间同步 STA 连接后启动 SNTP,阻塞等待最多 5 秒
10 NAS 上传器 创建上传队列(容量 16)和上传任务
11 帧广播器 初始化帧发布/订阅中心,供 MJPEG 订阅者使用
12 视频录像器 初始化 AVI 录像引擎,注册分段回调
13 MJPEG 流服务 初始化独立 TCP 服务器(端口 81),最大 2 个并发客户端
14 Web 服务器 端口 80,注册 URI 处理程序 + ONVIF 端点
14a OTA 更新器 初始化固件 OTA 更新能力
15 LED 状态更新 根据当前 WiFi 状态更新 LED 模式
16 ONVIF 发现 启动 WS-Discovery UDP 组播监听(WiFi 连接后)
17 等待 STA + 开始录像 最多等待 30 秒 WiFi 连接,连接后开始录像
18 BOOT 按钮监控 GPIO0,长按 5 秒触发恢复出厂设置
19 看门狗 30s TWDT,双核 idle 任务均纳入监控
20 SD 监控任务 Core 1,每 10 秒轮询 SD 卡状态,处理热插拔
21 健康监控任务 Core 1,每 60 秒输出堆/栈水位信息

RTOS 任务优先级布局

任务 核心 优先级 职责
audio_capture 0 5 I2S PDM 麦克风采集,G.711 编码,音频广播(Core 0 上最高优先级用户任务)
recorder 0 3 摄像头采集,运动检测,帧缓冲分配/发布,入队到 sd_writer
mjpeg_cli 1 2 MJPEG HTTP 流客户端任务(端口 81)
rtsp_video 1 2 RTSP 视频流(espp)
rtsp_audio 0 2 RTSP 音频流(G.711)— Core 0 以降低 I2S 采集的延迟
sd_writer 1 2 专用 SD 写入任务。从录像器消耗写队列,执行异步 write_avi_frame + 音频复用
WebSocket/httpd 0/1 1 HTTP 服务器 + WebSocket 处理程序

设计原因:

  • 音频采集优先级 5 是最高用户任务 — 不会被 SD 写入抢占。
  • 录像器优先级 3(原为 5)— 保持在 Core 0,低于音频以避免抢占 Core 1 上的流媒体任务。
  • sd_writer 在 Core 1 — I/O 密集型,无需与摄像头保持 CPU 邻近性。

异步 SD 写入架构 (v0.4.0)

录像器采集帧并将 frame_buf 引用入队到深度为 2 的队列。Core 1 上专用的 sd_writer_task 消耗队列并异步执行 write_avi_frame + 音频复用。

关键设计点:

  • 队列深度 2:允许录像器在 SD 写入进行时提前采集 1-2 帧
  • 哨兵屏障:NULL frame_buf + xTaskNotifyGive 确保分段旋转等待所有排队帧刷新后才执行 close_segment
  • 保持帧同步回退:如果队列已满,录像器同步写入帧(背压)
  • 音频复用:音频帧(160 字节 G.711)在 SD 写入期间交错到 AVI 容器中

这消除了 SD I/O 上的采集循环阻塞,以前这会在录制期间导致 MJPEG 流卡顿和音频丢帧。


3. 模块说明

系统共 27 个模块,全部位于 main/ 目录,每个模块一个 .c/.h 文件对。

模块 文件 职责 关键函数
主入口 main.c 启动流程,任务创建 app_main(), on_segment_complete()
摄像头驱动 camera_driver.c OV2640/OV3660 自动检测,帧采集 camera_init(), camera_capture()
视频录像器 video_recorder.c AVI MJPEG 分段录像,状态机,延时摄影模式 recorder_start(), recorder_stop()
帧广播器 frame_broadcaster.c 帧发布/订阅中心,向 MJPEG 订阅者分发 JPEG 帧 fbroadcast_publish(), fbroadcast_subscribe()
MJPEG 流服务 mjpeg_streamer.c MJPEG 实时流,独立 TCP 服务器(端口 81) mjpeg_streamer_init()
Web 服务器 web_server.c HTTP 服务器 + REST API + ONVIF 端点 web_server_start(), web_server_get_handle()
WiFi 管理 wifi_manager.c AP/STA 双模式,自动选择 wifi_init(), wifi_scan()
配置管理 config_manager.c NVS 持久化,SD 卡覆盖 config_init(), config_save(), config_reset()
存储管理 storage_manager.c SD 卡挂载/卸载,循环清理 storage_init(), storage_cleanup()
NAS 上传 nas_uploader.c 互斥上传调度(WebDAV 或 HTTP/HTTPS) nas_uploader_init(), nas_uploader_enqueue()
HTTP 上传客户端 http_upload_client.c HTTP/HTTPS PUT 流式上传 —
WebDAV 客户端 webdav_client.c WebDAV 协议上传实现 —
ONVIF 发现 onvif_discovery.c WS-Discovery UDP 组播,NVR 自动发现 onvif_discovery_init()
ONVIF 服务 onvif_service.c SOAP 处理:GetDeviceInformation, GetCapabilities, GetProfiles, GetStreamUri onvif_register_handlers()
状态 LED status_led.c 5 种 LED 模式控制 led_init(), led_set_status()
时间同步 time_sync.c SNTP 同步,手动设置时间 time_sync_init(), time_is_synced()
运动检测 motion_detector.c 帧差分析,运动评分,告警触发 motion_detector_init(), motion_detector_process()
OTA 更新器 ota_updater.c 固件升级,版本管理 ota_updater_init(), ota_update_start()
Webhook webhook.c HTTP 事件通知 webhook_send(), webhook_init()
WebSocket 服务器 ws_server.c 实时推送更新到 WebUI 客户端 ws_server_init(), ws_broadcast()
SHA-256 哈希 sha256.c 文件完整性校验 sha256_hash(), sha256_verify()
JSON 解析器 cJSON.c 第三方 JSON 库(IDF v6.0 内置) —
音频驱动 audio_driver.c I2S PDM 麦克风采集,DSR_16S(1.024 MHz),8kHz + DC 去除 + 噪声门 audio_driver_init(), audio_driver_start()
频谱降噪器 audio_ns.c 256 点 FFT 频谱减法,Wiener 增益,最小统计量噪声估计 audio_ns_init(), audio_ns_process()
音频广播器 audio_broadcaster.c 音频帧发布/订阅中心(镜像帧广播器) abroadcast_publish(), abroadcast_subscribe()
G.711 编解码 g711_codec.c G.711 μ-law 编解码器(ITU-T 标准) g711_encode(), g711_decode()
RTSP 服务器 rtsp_server.cpp RTSP 服务器(MJPEG+G.711 双轨道,无认证,端口 554) rtsp_server_init(), rtsp_server_start()
SD 日志 sd_log.c SD 卡结构化事件日志,自动轮转 sd_log_init(), sd_log_write()

4. 数据流

录像数据流

摄像头采集的 JPEG 帧由录像器写入 AVI 文件,分段完成后触发回调链:

camera_capture()
    │
    ▼
    视频录像器 (recording_task, Core 0)
    │  ← 帧数据写入 AVI 文件
    │  ← 按 segment_sec 时长分段(连续 vs 延时模式)
    │  ← 写入 AVI idx1 索引
    │
    ▼  分段完成
    on_segment_complete(filepath, size)
    │
    ├──→ nas_uploader_enqueue(filepath)
    │       │
    │       ▼
    │    上传任务 (Core 1)
    │       ├── upload_method=1 → WebDAV 上传
    │       ├── upload_method=2 → HTTP(S) 上传
    │       └── upload_method=0 → 跳过上传
    │       失败重试 3 次,连续失败 10 次后暂停 5 分钟
    │
    └──→ storage_cleanup()
            │
            ▼
         检查剩余空间 < cleanup_low_pct?(默认 20%)
            ├── 是 → 删除最旧录像文件,直到 ≥ cleanup_high_pct(默认 30%)
            │        写入失败时也尝试清理+重试
            └── 否 → 无操作

延时摄影模式:当 timelapse_interval_sec > 0 时,按指定间隔(1-300秒)采集帧而非连续采集。文件使用 TLM_ 前缀,以 15fps 播放。

MJPEG 实时流数据流

MJPEG 流服务运行在端口 81 作为独立 TCP 服务器,与端口 80 的主 httpd 完全分离。详见端口分离设计。

浏览器 → GET /stream (端口 81)
    │
    ▼
mjpeg_listen_task (Core 1, 优先级 3)
    └── accept() → 为每个客户端生成 mjpeg_client_task
         │
         ▼
frame_broadcaster → fbroadcast_receive()
         │
         ▼
video_recorder(帧生产者)→ fbroadcast_publish()
         │  ← recording_task 中的 camera_capture()
         │  ← PSRAM 副本 → 与摄像头缓冲区解耦
         │
         ▼
HTTP 分块传输 (multipart/x-mixed-replace)
    │  Content-Type: image/jpeg
    │  Boundary: --frame
    │
    ▼
浏览器实时渲染

RTSP 服务器数据流

RTSP Server (端口 554) ← frame_broadcaster + audio_broadcaster → MJPEG 视频轨道(RTP payload type 26) → G.711 μ-law 音频轨道(RTP payload type 0, PCMU)


5. FreeRTOS 任务表

任务名 函数 优先级 核心 栈大小 周期/触发 文件
recorder recording_task 5 (configMAX-2) Core 0 4096 B 连续循环(帧采集) video_recorder.c
upload upload_task 3 Core 1 6144 B 队列阻塞等待 nas_uploader.c
mjpeg_listen mjpeg_listen_task 3 Core 1 4096 B 连接接受循环 mjpeg_streamer.c
mjpeg_cli_N mjpeg_client_task 2 Core 1 4096 B 帧接收+发送循环 mjpeg_streamer.c
onvif_disc onvif_discovery_task 2 Core 1 6144 B UDP 组播监听 onvif_discovery.c
sd_monitor sd_monitor_task 2 Core 1 3072 B 10 秒轮询 main.c
boot_btn boot_button_monitor 1 任意 2048 B 200ms 轮询 main.c
health_mon health_monitor_task 1 Core 1 3072 B 60 秒轮询 main.c
ws_server websocket_server_task 3 Core 1 2048 B 客户端事件驱动 ws_server.c
httpd ESP-IDF 内置 默认 — 16384 B 事件驱动 web_server.c
main app_main 1 Core 0 8192 B 初始化后返回 main.c

任务调度策略

  • Core 0:运行录像任务(高优先级),确保帧采集不被抢占导致丢帧
  • Core 1:运行上传、MJPEG 流、ONVIF 发现、SD 监控、健康监控、WebSocket 服务器等非实时任务
  • BOOT 按钮监控:不绑定核心,低优先级轮询
  • Web 服务器:由 ESP-IDF httpd 库管理,16384 字节栈
  • WebSocket 服务器:运行在 Core 1,向连接的客户端广播状态更新

6. 分区表

来自 partitions.csv,使用自定义双 OTA 分区表:

名称 类型 子类型 偏移 大小 说明
nvs data nvs 0x9000 24 KB (0x6000) 非易失性存储(配置持久化)
phy_init data phy 0xF000 4 KB (0x1000) PHY 校准数据
ota_0 app ota_0 0x10000 1792 KB (0x1D0000) OTA 插槽 A
ota_1 app ota_1 0x1E0000 1792 KB (0x1D0000) OTA 插槽 B
otadata data ota 0x3B0000 8 KB (0x2000) OTA 选择数据
spiffs data spiffs 0x3B2000 256 KB (0x40000) Web UI 静态资源

Flash 总大小:8 MB

注意:双 OTA 插槽支持无缝固件更新。esp_https_ota 自动处理插槽切换。


7. Web API 端点

服务器运行在端口 80,另含 ONVIF SOAP 端点:

方法 路径 说明
GET /api/status 否
GET /api/config 否
POST /api/config
GET /api/files 否
POST /api/files/batch
DELETE /api/files
GET /api/download?name=xxx 否
GET /api/scan 否
POST /api/time
POST /api/record?action=start|stop
POST /api/ota
POST /api/format
POST /api/reset
GET /metrics 否
POST /onvif/device_service 否
POST /onvif/media_service 否
OPTIONS /* 否
GET /* 否

认证:无——契约 v1.9(2026-09-18)已移除设备级 Web 密码,端点在可信局域网内开放(边界为路由器的 WPA2)。


8. Web UI

6 个 HTML 页面,烧录到 SPIFFS 分区,由通配符 GET 处理程序服务:

页面 文件 功能
首页 index.html 状态概览
配置 config.html WiFi / NAS / 摄像头参数配置(手风琴布局)
文件 files.html 录像文件浏览、下载、批量操作,可折叠日期分组
预览 preview.html MJPEG 实时流预览,支持全屏和截图
OTA ota.html 固件更新界面,URL 输入和进度显示
设置 setup.html 首次 WiFi 配置向导

Prometheus 集成:/metrics 端点以文本格式提供 15+ 项系统指标。

温度监控:ESP32-S3 芯片温度通过 /api/status 获取,仪表盘颜色编码指示。

性能优化 (v0.4.0)

优化 影响
异步 SD 写入(sd_writer_task) 消除 SD I/O 上的采集循环阻塞
音频帧使用内部 RAM 160 字节 G.711 帧使用 MALLOC_CAP_DMA,避免 PSRAM 缓存与 JPEG DMA 的争用
MJPEG TCP_NODELAY + 8KB 分块 降低小块 part-headers 的延迟,提高吞吐量
循环相对帧延时 实际交付的 FPS 与配置匹配(以前因发送时间加到延迟上而较低)
Gzip 压缩的 HTML 5 个网页作为 .html.gz 提供,负载减少 60%
WebSocket 互斥锁快照模式 防止慢客户端发送期间的连接/断开饥饿
运动检测预分配池缓冲区 预分配 388800 字节解码缓冲区,避免每帧分配
TCP 缓冲区 16KB + 接收邮箱 24 更大的 TCP 窗口以提高流媒体吞吐量
可访问性增强功能已加载,支持焦点管理、键盘导航和屏幕阅读器
页面已加载完成,可访问性功能已启用