简介:本资源是基于ESP32-CAM模块实现MJPEG视频流实时传输并支持SD卡本地缓存的完整嵌入式开发项目,面向物联网初学者、智能硬件开发者及嵌入式图像传输实践者,解决低功耗设备端视频采集、压缩编码与网络推流的核心技术问题。压缩包共16个文件(150KB),含7个C++源码(如mjpeg2sd.cpp、app_httpd.cpp)、4个头文件(camera_pins.h、myConfig.h等)用于硬件配置与功能模块划分,2张PNG示意图辅助理解系统架构,另有README.md说明文档、主程序INO文件及LICENSE授权文件,结构清晰、模块职责明确,便于快速上手与二次开发。已有2451人学习下载,项目覆盖Wi-Fi连接、OV2640摄像头驱动、JPEG帧编码、HTTP流式传输及SD卡缓冲机制等关键环节,附带OTA升级与运动检测扩展代码,提供从硬件初始化到服务器端呈现的全链路参考实现,是深入理解ESP32-CAM视频应用的高价值实践样本。
1. 用 ESP32-CAM 把 MJPEG 流直接存到 SD 卡:不是“拍照存图”,而是持续录像级的本地缓存方案
你手上的 ESP32-CAM 开发板,摄像头能拍、Wi-Fi 能连、SD 卡槽也焊好了——但默认示例里,要么是 Web Server 实时推 MJPEG 流给浏览器看,要么是单张 JPEG 拍完存卡。而ESP32-CAM_MJPEG2SD-master这个开源项目干了一件更硬核的事:不依赖 PC 或云服务,让 ESP32-CAM 自己把连续的 MJPEG 视频流(非逐帧 JPEG)原样切片、打包、写入 SD 卡,生成可被 VLC/FFmpeg 直接识别的.mjpeg文件。它不是“拍照存图”,而是轻量级嵌入式录像机——适合无人值守的工地监控、农业温棚记录、设备运行状态抓取等场景。对新手,这是理解 ESP32-CAM 存储层与视频流协议协同的关键入口;对有经验的开发者,它暴露了 IDF v4.4+ 中 SDMMC 驱动与 UVC/MJPEG 流解析的边界条件。本文不讲 Blinker App 配置或手机远程预览,只聚焦:如何让这块板子真正“自己录下来”。
2. 为什么选 MJPEG 流直存而非 JPEG 单帧?从协议层看 ESP32-CAM 的存储瓶颈
2.1 MJPEG 流的本质:不是“视频文件”,而是 HTTP 分块传输的 JPEG 序列
MJPEG(Motion JPEG)在 ESP32-CAM 上并非编码后的.avi或.mp4,而是服务器通过 HTTP 响应头Content-Type: multipart/x-mixed-replace; boundary=frame,持续推送多个以--frame\r\nContent-Type: image/jpeg\r\n\r\n[JPEG_DATA]\r\n包裹的 JPEG 数据块。每个块含完整 JPEG 头(SOI)、DHT/DQT 表、SOS、图像数据和 EOI。关键点在于:这些块之间无时间戳、无帧率元数据,且长度可变。若按单帧 JPEG 存储,需逐块解包、校验、写文件,I/O 开销极大;而MJPEG2SD的设计哲学是——跳过解码,原样截取并拼接。
提示:ESP32-CAM 的 PSRAM 仅 4MB,无法缓存整段流;SD 卡写入速度(尤其 Class 4 卡)约 2–5 MB/s,但 MJPEG 流峰值带宽常达 1–3 MB/s(QVGA@15fps)。必须控制写入粒度,避免 FAT32 文件系统频繁分配簇导致卡顿。
2.2 对比三种常见存储策略:为何 MJPEG2SD 是折中解
| 方案 | 帧处理方式 | 存储格式 | CPU 占用 | SD 卡寿命影响 | 适用场景 |
|---|---|---|---|---|---|
| 单帧 JPEG 存储 | 每帧 decode → encode → write | .jpg | 高(需 JPEG 解码+重编码) | 高(小文件多,FAT32 碎片化严重) | 定时抓拍、AI 推理前采样 |
| Raw YUV 存储 | DMA 直接捕获 sensor 输出 | .yuv | 极低 | 中(大文件,但无文件系统开销) | 调试 sensor 时序、后续 PC 端转码 |
| MJPEG 流直存(本项目) | 截取 HTTP boundary 块,原样写入 | .mjpeg | 低(仅字符串匹配+memcpy) | 低(单大文件,顺序写入) | 长时本地录像、离线回放 |
MJPEG2SD选择第三条路:它不碰 JPEG 数据内容,只识别--frame边界,将每个块(含\r\n\r\n后的 JPEG 二进制)追加到 SD 卡文件末尾。最终生成的.mjpeg文件,可用ffplay -f mjpeg input.mjpeg直接播放,或用ffmpeg -i input.mjpeg -c:v libx264 output.mp4转为标准视频。
2.3 ESP32-CAM 硬件约束下的关键参数取舍
- 摄像头配置:必须启用
PIXFORMAT_JPEG(不可用 RGB565/YUV),否则无 MJPEG 流输出; - SD 卡接口:使用
SDMMC模式(非 SPI),因 SPI 带宽上限约 20 Mbps,而 QVGA@10fps MJPEG 流常超 25 Mbps; - PSRAM 使用:开启
CONFIG_ESP32_SPIRAM_SUPPORT,但MJPEG2SD实际不依赖 PSRAM 缓存流——它用双缓冲区(各 8KB)交替接收 HTTP chunk,避免内存碎片; - FreeRTOS 任务优先级:
camera_task设为CONFIG_CAMERA_TASK_PRIORITY=10,sd_write_task设为9,确保图像采集不被 I/O 阻塞。
3. 从零编译烧录:ESP32-CAM 刷机程序 + MJPEG2SD 固件下载与配置
3.1 开发环境准备:ESP-IDF v4.4.6 是当前最稳版本
ESP32-CAM_MJPEG2SD-master仓库未声明 IDF 版本,但实测在 IDF v5.1 上因sdmmc_host_t结构体变更导致编译失败。强烈建议锁定 v4.4.6(2022 年 10 月 LTS 版):
# 清理旧环境 rm -rf $HOME/esp mkdir -p $HOME/esp && cd $HOME/esp # 下载并安装 IDF v4.4.6 git clone -b release/v4.4.6 --recursive https://github.com/espressif/esp-idf.git cd esp-idf ./install.sh . ./export.sh注意:不要用
idf.py set-target esp32s2等命令切换芯片——ESP32-CAM 是 ESP32-D0WDQ6,必须保持esp32目标。
3.2 获取源码并修正关键引脚定义
从 GitHub 克隆项目(注意分支):
cd $HOME/esp git clone https://github.com/username/ESP32-CAM_MJPEG2SD.git cd ESP32-CAM_MJPEG2SD git checkout master # 确认是原始 master 分支ESP32-CAM 开发板存在多种 PCB 版本(AI-Thinker、TTL 模块、第三方兼容板),SD 卡引脚定义极易出错。打开main/camera_pins.h,确认以下宏匹配你的板子:
// AI-Thinker ESP32-CAM 标准版(最常见) #define SD_MISO_GPIO 13 #define SD_MOSI_GPIO 15 #define SD_SCLK_GPIO 14 #define SD_CS_GPIO 2 // 注意:不是 4!部分教程误写为 GPIO4,会导致初始化失败若使用 TTL 转 USB 模块烧录,还需设置UART0引脚(通常 GPIO1/TX, GPIO3/RX),并在sdkconfig中启用:
CONFIG_ESPTOOLPY_PORT="/dev/ttyUSB0" CONFIG_ESPTOOLPY_BAUD_921600=y CONFIG_ESPTOOLPY_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_40M=y3.3 编译与烧录:两步命令搞定 ESP32-CAM 刷机程序
# 生成项目配置(会启动 menuconfig) idf.py menuconfig在menuconfig中重点检查:
Serial flasher config→Default serial port设为你的串口(如/dev/ttyUSB0);Component config→Camera Configuration→Camera pinout选AI Thinker ESP32-CAM;SDMMC Configuration→SDMMC Host选SDMMC(非 SPI);MJPEG2SD Configuration→Output filename prefix设为rec_(生成rec_0001.mjpeg);MJPEG2SD Configuration→Max file size (MB)设为100(防 SD 卡写满)。
保存退出后编译烧录:
# 编译(耗时约 2 分钟) idf.py build # 烧录(自动复位,无需手动按 BOOT 键) idf.py -p /dev/ttyUSB0 flash # 查看日志(观察 SD 卡是否 mount 成功) idf.py -p /dev/ttyUSB0 monitor成功日志关键行:
I (234) MJPEG2SD: SD card mounted successfully I (235) MJPEG2SD: Creating new file: /sdcard/rec_0001.mjpeg I (236) MJPEG2SD: HTTP server started on http://192.168.4.1/stream此时开发板会创建 AP 热点ESP32-CAM-XXXX,手机连接后访问http://192.168.4.1/stream即可见实时流——同时 SD 卡已开始写入.mjpeg文件。
4. 参数调优与排错:当 ESP32-CAM 固件下载后不录像的 5 类典型问题
4.1 SD 卡无法识别:硬件层排查清单
| 现象 | 检查项 | 解决方案 |
|---|---|---|
SD card mount failed | SD 卡是否为 FAT32 格式? | 用mkfs.fat -F32 /dev/sdX重格式化(Linux);Windows 用磁盘管理工具设为 FAT32(非 exFAT) |
SD card mount failed | GPIO2 是否被其他外设占用? | 拔掉所有扩展模块,确认SD_CS_GPIO未接 LED 或传感器 |
SD card mount failed | SD 卡是否 >32GB? | ESP32 SDMMC 驱动对 >32GB 卡支持不稳定,优先用 16GB Class 10 卡 |
SD card mount failed | 供电是否不足? | ESP32-CAM 拍照+SD 写入瞬时电流达 500mA,必须用 ≥1A 电源,禁用 USB 2.0 口直供 |
提示:在
monitor日志中搜索sdmmc_init,若出现SDMMC_ERR_TIMEOUT,90% 是供电或接触问题;若出现SDMMC_ERR_INVALID_ARG,则是引脚配置错误。
4.2 录像文件为空或中断:流协议与缓冲区协同调试
MJPEG2SD默认每 10 秒新建一个文件(防止单文件过大)。若rec_0001.mjpeg只有几 KB,说明流未正确捕获。检查:
- HTTP 流是否真实存在?在 PC 上用
curl -s http://192.168.4.1/stream | head -c 1000 | hexdump -C,应看到--frame\r\nContent-Type: image/jpeg\r\n\r\n\xff\xd8\xff\xe0(JPEG SOI 标记); - 缓冲区是否溢出?修改
main/mjpeg2sd.c中BUFFER_SIZE(默认 8192)为16384,重新编译; - 写入速率是否超限?在
sd_write_task中添加计时:
若单次写入 >50000 μs(50ms),说明 SD 卡性能不足,需降帧率。int64_t start = esp_timer_get_time(); ret = f_write(&fil, buf, len, &bw); int64_t end = esp_timer_get_time(); ESP_LOGI(TAG, "Write %d bytes in %lld us", len, end - start);
4.3 帧率与画质平衡:ESP32-CAM 开发板管理地址中的关键配置
在main/app_camera.c中调整摄像头参数:
// 关键参数表(实测稳定值) config.xclk_freq_hz = 10000000; // XCLK 降低至 10MHz(原 20MHz 易丢帧) config.pixel_format = PIXFORMAT_JPEG; // 必须 JPEG config.frame_size = FRAMESIZE_QVGA; // QVGA(320x240) 最稳;VGA(640x480) 需 PSRAM 且易卡顿 config.jpeg_quality = 12; // 10~15 间权衡:12 为画质/体积最佳点 config.fb_count = 2; // 双帧缓冲,防采集阻塞注意:
FRAMESIZE_UXGA(1600x1200)在 ESP32-CAM 上不可用——sensor OV2640 不支持该分辨率 JPEG 输出,强行设置会导致CAMERA_FAIL_TO_SET_FRAMESIZE错误。
4.4 文件系统损坏恢复:SD 卡异常断电后的修复流程
若开发板意外断电,SD 卡 FAT32 表可能损坏,表现为f_mount返回FR_NO_FILESYSTEM。此时:
- 将 SD 卡插入 PC,用
chkdsk /f X:(Windows)或sudo fsck.fat -a /dev/sdX1(Linux)修复; - 删除根目录下所有
rec_*.mjpeg和lost+found/; - 重启 ESP32-CAM,它会自动创建新文件。
禁止在录像中拔卡!MJPEG2SD无 journaling 机制,强制拔卡必损文件。
5. 进阶技巧:用 FFmpeg 验证录像完整性与提取关键帧
5.1 一键验证.mjpeg文件是否可播
将 SD 卡取出,插入 PC,执行:
# 检查文件头是否合法(必须以 --frame 开头) head -c 50 rec_0001.mjpeg | strings | head -n 1 # 输出应为:--frame # 检查是否含 JPEG 数据(搜索 ff d8 ff) xxd -l 100 rec_0001.mjpeg | grep "ff d8 ff" # 用 FFmpeg 探测流信息(确认帧率/分辨率) ffmpeg -v quiet -i rec_0001.mjpeg -show_entries stream=width,height,r_frame_rate -of default=nw=1 # 正常输出:width=320, height=240, r_frame_rate=15/15.2 从.mjpeg提取指定时间点的 JPEG 图像
MJPEG2SD不存时间戳,但可通过字节偏移估算。假设帧率 12fps,想提取第 60 秒的帧(即第 720 帧):
# 计算平均帧大小(单位:字节) FILE_SIZE=$(stat -c "%s" rec_0001.mjpeg) TOTAL_FRAMES=$(ffprobe -v quiet -show_entries stream=nb_read_frames -of default=nw=1 rec_0001.mjpeg | cut -d= -f2) AVG_FRAME_SIZE=$((FILE_SIZE / TOTAL_FRAMES)) # 定位第 720 帧起始位置(跳过前 719 帧) OFFSET=$((719 * AVG_FRAME_SIZE)) # 提取该帧(从 --frame 开始到下一个 --frame 前) dd if=rec_0001.mjpeg of=frame_720.jpg bs=1 skip=$OFFSET count=100000 2>/dev/null | \ sed -n '/\xff\xd8\xff/,/\xff\xd9/p' > frame_720.jpg5.3 生成可分享的 MP4:用 H.264 压缩降低体积
原始.mjpeg文件体积巨大(1 小时约 4–6 GB)。转为 H.264:
ffmpeg -f mjpeg -i rec_0001.mjpeg \ -c:v libx264 -preset fast -crf 23 \ -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2" \ -r 15 \ rec_0001.mp4参数说明:
-crf 23:质量恒定,18~28 间调节(值越小质量越高);-vf scale=...:保持宽高比并填充黑边,适配手机播放;-r 15:强制输出帧率为 15fps,匹配原始流。
生成的rec_0001.mp4体积可降至原.mjpeg的 1/10,且兼容所有设备。
本文还有配套的精品资源,点击获取