
简介本资源是基于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 序列MJPEGMotion JPEG在 ESP32-CAM 上并非编码后的.avi或.mp4而是服务器通过 HTTP 响应头Content-Type: multipart/x-mixed-replace; boundaryframe持续推送多个以--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/sQVGA15fps。必须控制写入粒度避免 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而 QVGA10fps MJPEG 流常超 25 MbpsPSRAM 使用开启CONFIG_ESP32_SPIRAM_SUPPORT但MJPEG2SD实际不依赖 PSRAM 缓存流——它用双缓冲区各 8KB交替接收 HTTP chunk避免内存碎片FreeRTOS 任务优先级camera_task设为CONFIG_CAMERA_TASK_PRIORITY10sd_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.62022 年 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_921600y CONFIG_ESPTOOLPY_FLASHMODE_QIOy CONFIG_ESPTOOLPY_FLASHFREQ_40My3.3 编译与烧录两步命令搞定 ESP32-CAM 刷机程序# 生成项目配置会启动 menuconfig idf.py menuconfig在menuconfig中重点检查Serial flasher config→Default serial port设为你的串口如/dev/ttyUSB0Component config→Camera Configuration→Camera pinout选AI Thinker ESP32-CAMSDMMC Configuration→SDMMC Host选SDMMC非 SPIMJPEG2SD Configuration→Output filename prefix设为rec_生成rec_0001.mjpegMJPEG2SD 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 failedSD 卡是否为 FAT32 格式用mkfs.fat -F32 /dev/sdX重格式化LinuxWindows 用磁盘管理工具设为 FAT32非 exFATSD card mount failedGPIO2 是否被其他外设占用拔掉所有扩展模块确认SD_CS_GPIO未接 LED 或传感器SD card mount failedSD 卡是否 32GBESP32 SDMMC 驱动对 32GB 卡支持不稳定优先用 16GB Class 10 卡SD card mount failed供电是否不足ESP32-CAM 拍照SD 写入瞬时电流达 500mA必须用 ≥1A 电源禁用 USB 2.0 口直供提示在monitor日志中搜索sdmmc_init若出现SDMMC_ERR_TIMEOUT90% 是供电或接触问题若出现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\xe0JPEG SOI 标记缓冲区是否溢出修改main/mjpeg2sd.c中BUFFER_SIZE默认 8192为16384重新编译写入速率是否超限在sd_write_task中添加计时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);若单次写入 50000 μs50ms说明 SD 卡性能不足需降帧率。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_UXGA1600x1200在 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/sdX1Linux修复删除根目录下所有rec_*.mjpeg和lostfound/重启 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 streamwidth,height,r_frame_rate -of defaultnw1 # 正常输出width320, height240, r_frame_rate15/15.2 从.mjpeg提取指定时间点的 JPEG 图像MJPEG2SD不存时间戳但可通过字节偏移估算。假设帧率 12fps想提取第 60 秒的帧即第 720 帧# 计算平均帧大小单位字节 FILE_SIZE$(stat -c %s rec_0001.mjpeg) TOTAL_FRAMES$(ffprobe -v quiet -show_entries streamnb_read_frames -of defaultnw1 rec_0001.mjpeg | cut -d -f2) AVG_FRAME_SIZE$((FILE_SIZE / TOTAL_FRAMES)) # 定位第 720 帧起始位置跳过前 719 帧 OFFSET$((719 * AVG_FRAME_SIZE)) # 提取该帧从 --frame 开始到下一个 --frame 前 dd ifrec_0001.mjpeg offrame_720.jpg bs1 skip$OFFSET count100000 2/dev/null | \ sed -n /\xff\xd8\xff/,/\xff\xd9/p frame_720.jpg5.3 生成可分享的 MP4用 H.264 压缩降低体积原始.mjpeg文件体积巨大1 小时约 4–6 GB。转为 H.264ffmpeg -f mjpeg -i rec_0001.mjpeg \ -c:v libx264 -preset fast -crf 23 \ -vf scale640:480:force_original_aspect_ratiodecrease,pad640: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且兼容所有设备。本文还有配套的精品资源点击获取