开始前适用开发板:ESP32-S3 Korvo · 开发框架:ESP-IDF
适用开发板:Korvo S3 · 工程
04.advanced.camera_webserver· targetesp32s3
前置:Wi-Fi Station + DVP 摄像头
学习时长:约 25 分钟 · 难度:★★★☆☆
学习目标
完成本节后,你将能够:
- 理解 MJPEG over HTTP 的推流原理
- 掌握 HTTP multipart 响应的实现方式
- 了解帧率、分辨率与网络带宽的关系
- 设计一个简单的远程视频监控方案
1. 这个例子在干嘛
Wi-Fi 联网后启动 HTTP 视频流服务器,手机/电脑浏览器访问板子 IP 即可看到摄像头实时画面。这是「远程监控/图传」的基础模板。
生活类比:就像一个最简单的网络摄像头——插电联网,打开浏览器就能看画面。
┌──────────┐ DVP ┌──────────┐ Wi-Fi ┌──────────────┐
│ 摄像头 │──────────→│ ESP32-S3 │───────────→│ 浏览器 │
│ Sensor │ RGB/JPEG │ HTTP 推流 │ MJPEG帧 │ <img> 或 │
└──────────┘ │ Server │ │ /stream 路径 │
└──────────┘ └──────────────┘
2. 编译烧录
cd Korvo_Firmware\04.advanced.camera_webserver
idf.py set-target esp32s3
idf.py menuconfig # 配置 Wi-Fi SSID/密码
idf.py build flash monitor
3. 使用步骤(详细版)
3.1 启动并获取 IP
- 烧录后等待串口显示
GOT IP - 记住 IP 地址(如
192.168.1.50)
3.2 浏览器访问
- 手机/PC 连接 同一 Wi-Fi 网络
- 浏览器打开
http://<板子IP>/(或串口提示的路径,常见/stream) - 应看到实时画面
3.3 测试多客户端
- 同时在手机和电脑打开——观察帧率变化(连接越多越卡)
- 这是因为每个客户端都需要 ESP32 单独推帧
4. 运行现象
| 阶段 | 串口输出 | 浏览器 |
|---|---|---|
| 启动 | Wi-Fi 连接日志、camera init | — |
| 就绪 | HTTP stream server started + IP | 访问 IP 可看到页面 |
| 推流中 | 帧率日志(若启用) | 实时画面 |
| 多客户端 | 连接数增加日志 | 帧率下降 |
5. MJPEG over HTTP 原理
5.1 什么是 MJPEG 推流?
不是把整个视频压缩后发送(那是 H.264 流),而是 每帧单独压缩成 JPEG,依次发送。浏览器逐帧显示,连起来就是"视频"。
5.2 HTTP Multipart 响应
HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame
--frame
Content-Type: image/jpeg
Content-Length: 12345
<JPEG 数据帧 1>
--frame
Content-Type: image/jpeg
Content-Length: 12346
<JPEG 数据帧 2>
... (持续不断)
关键点:
multipart/x-mixed-replace告诉浏览器"后面的每个 part 替换前一个"- 浏览器用
<img>标签就能直接显示 - 连接 不会主动关闭——服务器持续推帧直到客户端断开
5.3 与 camera_lvgl_display 的区别
| 对比项 | camera_lvgl_display | camera_webserver |
|---|---|---|
| 显示方式 | 本地 LVGL 屏幕 | 远程浏览器 |
| 网络需求 | 不需要 | 需要 Wi-Fi |
| 格式 | RGB565(直接刷屏) | JPEG(压缩后发送) |
| 观看设备 | 板子屏幕 | 手机/电脑 |
| 帧率 | 受 SPI 带宽限制 | 受 Wi-Fi 带宽限制 |
6. 关键文件与代码解析
| 文件 | 作用 | 重点关注 |
|---|---|---|
main/app_main.c | 总入口 | 启动顺序 |
main/app_wifi.c | Wi-Fi Station 连接 | 事件处理 |
main/app_camera.c | 摄像头初始化 | camera_config_t |
main/app_http_stream.c | 核心:HTTP 推流 | multipart handler |
推流 handler 核心逻辑
esp_err_t stream_handler(httpd_req_t *req) {
// 1. 设置 Content-Type 为 multipart
httpd_resp_set_type(req, "multipart/x-mixed-replace; boundary=frame");
while (1) {
// 2. 从摄像头获取一帧 JPEG
camera_fb_t *fb = esp_camera_fb_get();
// 3. 写 boundary + Content-Type + Content-Length
httpd_resp_send_chunk(req, "--frame\r\n...", ...);
// 4. 写 JPEG 数据
httpd_resp_send_chunk(req, fb->buf, fb->len);
// 5. 归还帧缓冲
esp_camera_fb_return(fb);
// 6. 如果客户端断开 → 退出循环
if (httpd_req_to_sockfd(req) < 0) break;
}
}
7. 性能调优指南
| 调优项 | 方法 | 效果 |
|---|---|---|
| 分辨率 | 降低 frame_size(如 QVGA) | 每帧 JPEG 更小,传输更快 |
| JPEG 质量 | camera_config.jpeg_quality(值越大质量越低) | 文件更小,牺牲清晰度 |
| Wi-Fi 带宽 | 5GHz 路由器(手机侧)+ 靠近路由器 | 减少延迟和丢帧 |
| 帧缓冲数 | fb_count = 2(双缓冲) | 减少等待 |
| 并发限制 | 限制最大客户端数 | 避免 OOM |
典型性能参考(QVGA, WiFi 2.4G):
- 1 个客户端:15~20 fps
- 2 个客户端:8~12 fps
- 3+ 个客户端:帧率明显下降或崩溃
8. 常见问题与排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 浏览器打不开 | IP 不对或不在同一网段 | 核对串口输出的 IP |
| 浏览器打不开 | PC 防火墙 | 临时关闭防火墙 |
| 有页面无图 | URL 路径不对 | 查看 app_http_stream.c 中注册的路径 |
| 有页面无图 | 摄像头初始化失败 | 串口检查 camera init 日志 |
| 画面很卡 | 分辨率/质量太高 | 降低分辨率或提高 jpeg_quality 数值 |
| 画面很卡 | Wi-Fi 信号差 | 靠近路由器 |
| 内存不足崩溃 | 帧缓冲 + HTTP socket 占用大 | 减少 fb_count;限制并发 |
| 画面延迟高 | 网络缓冲 | 正常现象,可降分辨率改善 |
9. 动手改造建议
- HTTP 认证:加 Basic Auth,防止局域网内被随意访问
- 单帧快照:新增
/capture.jpg路径,只返回当前一帧 JPEG(不推流) - MQTT 联动:订阅 MQTT Topic,收到命令才开启/关闭推流
- 运动检测:帧差法检测画面变化,变化大时 MQTT 报警
- HTML 界面:返回一个 HTML 页面,内嵌
<img src="/stream">+ 控制按钮
10. 知识延伸
-
MJPEG vs H.264 vs WebRTC:
方案 优点 缺点 ESP32 可行性 MJPEG over HTTP 简单、浏览器直接支持 带宽大 推荐 H.264 压缩比高 编码复杂 S3 不支持硬编码 WebRTC 低延迟、P2P 复杂 有社区方案 -
multipart 的历史:这个 Content-Type 最初是为邮件附件设计的,后来被网络摄像头复用来做视频推流
-
帧率 vs 延迟:降低分辨率能同时提高帧率和降低延迟,因为每帧数据量更小
11. 下一步
| 方向 | 推荐例程 | 说明 |
|---|---|---|
| 本地预览 | camera_lvgl_display | 不需要 Wi-Fi |
| HTTP Server 基础 | HTTP Server | 理解 handler 注册 |
| 人脸识别 | face_recognition | 摄像头 + AI |
| 通用网页图传教程 | 网页图传教程 | 深入原理 |
酷世DIY · Kevincoooool