☰
ESP32-CAM实现MJPEG视频流写入SD卡:本地录像方案详解
2026/10/22 13:56:06 网站建设 项目流程

简介:这份资源围绕ESP32-CAM模块实现MJPEG视频流传输到服务器,适合物联网开发者、嵌入式爱好者和智能监控项目学习者。项目以OV2640摄像头采集图像,涉及Wi-Fi与SD卡接口,通过连续捕获JPEG帧并经HTTP传输,服务器端按时间戳解码播放,构成视频流;同时支持运动检测、AVI录制、FTP上传及DS18B20温度读取等扩展功能。压缩包共16个文件,主要包含7个cpp源码、4个头文件、1个ino工程文件及说明文档,涵盖摄像头驱动、JPEG编码、Wi-Fi配置、网络传输、运动检测与存储模块,源码包含网络服务、运动检测、FTP上传、温度采集等独立模块,结构清晰,便于直接编译调试与二次开发。已有2469人学习下载,对于想深入理解ESP32-CAM、MJPEG编码和物联网视频传输全流程的开发者,这份资料能提供完整的项目框架、模块化代码结构和可参考的实现思路,帮助快速上手实际项目。

1. 标题里的 MJPEG2SD 到底在解决什么问题

拿到“ESP32-CAM_MJPEG2SD-master”这个压缩包名字,多数第一次接触的人会以为它只是个普通摄像头示例代码。其实它解决的是一个很现实的需求:让 ESP32-CAM 脱离电脑和手机,自己把视频流一段一段写进 SD 卡,变成一个低成本的可移动录像设备。MJPEG2SD 这个名字把核心路径说得很直白——MJPEG 格式的 JPEG 帧流,直接落地到 SD 卡上。

这个方向适合谁?手头有 ESP32-CAM 模块、不想每次调试都开串口监视器的人;需要做长时间图像采集但不想依赖 Wi-Fi 传输的场景;或者单纯想给小车、气象站、家庭安防加一个“黑匣子”功能的开发者。它不需要额外硬件,模块自带的 microSD 卡槽就是存储介质,代码跑通之后,拔卡就能读数据。

我看到这个标题的第一反应是:它不是一个开箱即用的成品固件,而是一个可迁移的工程模板。真正值钱的部分不是“能不能录像”,而是帧率怎么控制、文件怎么切分、卡写满之后怎么办。下面这几章,我把从原理到落地的完整路径拆开讲,最后把最容易翻车的几个坑也列出来。

2. 从硬件引脚到 MJPEG 流:ESP32-CAM 录像方案的基本盘

2.1 OV2640 与 ESP32-CAM 的引脚分配:为什么说这板子天生适合做录像

ESP32-CAM 是某公司出的低成本开发板,核心是 ESP32 芯片加一颗 OV2640 摄像头传感器。OV2640 支持最高 1600x1200 分辨率,内置 JPEG 压缩引擎,能直接输出压缩后的 JPEG 数据,不需要 ESP32 做软件编码。这一点对录像方案至关重要,因为 ESP32 的 CPU 主频虽然能跑到 240MHz,但内存和算力都不足以做实时软件视频编码,硬编码是一条必然路径。

板上还集成了一个 microSD 卡槽,走的是 SPI 模式。这里有一个 ESP32-CAM 的经典坑位:板载的 microSD 卡槽和摄像头共用部分 GPIO,SD 卡的 CS 引脚默认接在 GPIO 4,而摄像头的数据引脚是 GPIO 2、GPIO 13 等,具体映射与板子版本有关。换句话说,你不做任何引脚配置直接调用 SD 库,大概率初始化失败。这也是很多初学者拿到 MJPEG2SD 相关代码后第一步就卡住的原因。

引脚分工大致如下:摄像头用 SCCB 接口(类似 I2C)传输控制指令,D0-D7 并行数据线传输图像数据;SD 卡用 SPI 模式,MOSI、MISO、SCLK、CS 四根线。ESP32-CAM 模块本身没有 USB 转串口芯片,所以下载程序需要外接 USB-TTL,这个细节决定了整个开发流程的接线方式。

2.2 MJPEG 不是“一种视频编码”,而是“一串 JPEG 图片”

MJPEG 全称 Motion JPEG,本质上是把每帧图像独立压缩成 JPEG,然后按时间顺序连续存储或传输。它与 H.264、H.265 这些用帧间预测的编码方式有本质区别:没有 I 帧、P 帧、B 帧的概念,每一帧都是完整的、可独立解码的 JPEG 图片。

这个特性对 ESP32-CAM 极其友好,原因有三个。第一,OV2640 传感器直接输出 JPEG 数据,ESP32 不需要做任何编码计算,只需要把数据搬运到 SD 卡;第二,单帧损坏不会影响其他帧,录像文件即使中间有几帧写坏了,前后帧依然能正常打开;第三,解码端极其简单,任何支持 JPEG 解码的软件都能逐帧读取,不需要专门的播放器。

但代价也很明显:文件体积巨大。一帧 640x480 的 JPEG 按质量 80 压缩大约需要 30-60KB,如果按 10 帧每秒来录,一分钟的数据量就在 20-35MB 之间。这个体积决定了 SD 卡的容量和文件的组织方式必须提前规划。MJPEG2SD 这类方案通常会自带文件名轮转逻辑,比如每分钟生成一个新文件,卡写满后自动覆盖最早的录像文件,形成“行车记录仪”式的循环记录。

2.3 抓帧存储的核心链路:DMA、帧缓冲与 SD 写入时序

代码层面的核心链路是:OV2640 产生一帧 JPEG 数据 -> ESP32 的摄像头驱动把数据放入帧缓冲 -> 程序从帧缓冲读取数据 -> 逐块写入 SD 卡。整个过程中最容易出问题的是缓冲区大小和写入时序。

ESP32 的摄像头上层驱动库(esp_camera)通过camera_fb_t结构体返回帧数据。这个结构体里有一个len字段记录当前帧的字节数,一个buf指针指向数据首地址。在 Arduino 环境中,获取一帧的代码通常是:

#include "esp_camera.h" camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); return; } // fb->buf 指向 JPEG 数据,fb->len 是字节数 // 将 fb->buf 写入 SD 卡 write_frame_to_sd(fb->buf, fb->len); esp_camera_fb_return(fb);

esp_camera_fb_get()会阻塞等待一帧图像采集完成,返回后需要尽快处理完数据并调用esp_camera_fb_return(fb)释放帧缓冲,否则内部缓冲池耗尽后摄像头就无法继续出帧。帧缓冲默认由 PSRAM 提供,所以选模块时一定要选带 PSRAM 的版本,这是基本前提。

写入 SD 卡时,我通常的做法是分块写而不是一次性写入。fb->buf指向的数据是一次采集完成的完整 JPEG 帧,但 SD 卡的 SPI 模式下,单次写入过大容易出现总线超时。用File.write()内部也会分块,不过显式控制块大小(比如每次 512 字节)在出问题时更容易定位。

File file = SD.open("/record/0001.jpg", FILE_WRITE); if (!file) { Serial.println("Failed to open file"); return; } size_t remaining = fb->len; uint8_t *p = fb->buf; while (remaining > 0) { size_t chunk = remaining > 512 ? 512 : remaining; file.write(p, chunk); p += chunk; remaining -= chunk; } file.close();

这里把一帧完整的 JPEG 写成一个独立的 JPG 文件,适合“定时抓拍”场景,也是 MJPEG2SD 最基础的存储方式——画质清晰、文件可独立打开。代价是文件数量多,目录管理压力大。后面会讲怎么把它升级成真正的连续视频流。

3. 本地跑通 MJPEG2SD:最小代码与三条必跑命令

3.1 准备工程目录与配置文件:环境选型直接影响调试效率

开始写代码之前,先决定开发环境。常见做法有两种:Arduino IDE 加 ESP32 扩展包,或者 PlatformIO。我个人倾向于用 PlatformIO,因为它的platformio.ini可以固化板型、Flash 大小、波特率等配置,换电脑后不用重新设置。

工程目录结构按下面这样组织,代码和配置分开,后续维护更容易。创建一个新项目,文件夹名称可以叫mjpeg2sd_local,在platformio.ini中指定板型和分区方案。ESP32-CAM 的 Flash 通常是 4MB,但摄像头缓冲区需要 PSRAM,所以板型要选esp32cam。

[env:esp32cam] platform = espressif32 board = esp32cam framework = arduino board_build.filesystem = littlefs monitor_speed = 115200 build_flags = -DBOARD_HAS_PSRAM -DCONFIG_ESP32_SPIRAM_SUPPORT

BOARD_HAS_PSRAM这个宏是摄像头初始化的前提,没有它,OV2640 的高分辨率模式会直接崩溃。monitor_speed设为 115200,和Serial.begin的参数保持一致,这个串口输出是排查问题的主要窗口。

3.2 摄像头与 SD 卡的初始化顺序:先卡后摄还是先摄后卡

初始化顺序是 MJPEG2SD 方案里最容易出怪问题的环节。按我的经验,正确顺序是:先初始化 SD 卡,再初始化摄像头。因为 SD 卡初始化如果失败,后续录像文件无处可写,你能立刻在串口看到报错;而如果先初始化摄像头,卡初始化失败时摄像头已经在工作了,处理中途报错的分支逻辑会复杂很多。

初始化代码写在setup()里,第一步挂载 SD 卡文件系统,第二步初始化摄像头,第三步单独测试一次抓帧确认链路通畅。

#include "FS.h" #include "SD_MMC.h" #include "esp_camera.h" void setup() { Serial.begin(115200); delay(1000); // 1. 初始化 SD 卡,ESP32-CAM 板载卡槽走 SD_MMC 库的 1-bit 模式 if (!SD_MMC.begin()) { Serial.println("Card Mount Failed"); return; } // 2. 初始化摄像头,配置结构体参数见下方说明 camera_config_t config; config.ledc_channel = LEDC_CHANNEL_0; config.ledc_timer = LEDC_TIMER_0; config.pin_d0 = 5; config.pin_d1 = 18; config.pin_d2 = 19; config.pin_d3 = 21; config.pin_d4 = 36; config.pin_d5 = 39; config.pin_d6 = 34; config.pin_d7 = 35; config.pin_xclk = 0; config.pin_pclk = 22; config.pin_vsync = 25; config.pin_href = 23; config.pin_sscb_sda = 26; config.pin_sscb_scl = 27; config.pin_pwdn = 32; config.pin_reset = -1; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; config.frame_size = FRAMESIZE_VGA; config.jpeg_quality = 10; config.fb_count = 2; esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } // 3. 抓一帧测试链路 camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); } else { Serial.printf("Test frame captured, size: %u bytes\n", fb->len); esp_camera_fb_return(fb); } }

pixel_format必须设为PIXFORMAT_JPEG,这样才能拿到 OV2640 硬件编码后的 JPEG 数据,而不是 RGB565 原始数据。frame_size用FRAMESIZE_VGA即 640x480,这是画质和速度的均衡点。jpeg_quality的范围是 0-63,数值越小画质越高、文件越大,10 是录像场景的常用起点。

fb_count设为 2 表示使用双帧缓冲,一帧在写入 SD 卡时,另一帧可以被摄像头继续填充,减少帧间等待。这个参数在低速 SD 卡上尤为重要,单缓冲会明显掉帧。

3.3 一个可以跑起来的最小循环:连拍写到 SD 卡

主循环的逻辑不复杂:抓帧、写文件、释放缓冲。但有几个决定录像质量的细节要处理。文件名不能重复,我用millis()生成时间戳;写入完成后关闭文件,避免文件句柄泄漏;每次循环间隔通过主动延迟控制,不要让抓帧堵死整个系统。

uint32_t frame_count = 0; void loop() { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Capture failed, skipping frame"); delay(100); return; } char filename[32]; snprintf(filename, sizeof(filename), "/rec_%06u.jpg", frame_count++); File file = SD_MMC.open(filename, FILE_WRITE); if (!file) { Serial.printf("Failed to open %s\n", filename); esp_camera_fb_return(fb); return; } size_t written = file.write(fb->buf, fb->len); file.close(); esp_camera_fb_return(fb); if (written != fb->len) { Serial.printf("Incomplete write: %u/%u\n", written, fb->len); } else { Serial.printf("Saved %s, %u bytes\n", filename, written); } delay(50); }

snprintf生成六位序号文件名,超过 999999 文件时会回到零,实际做产品需要换成时间戳命名。delay(50)把写入节奏稳定在每分钟大约 600 帧以下,具体帧率取决于分辨率、JPEG 质量和 SD 卡速度。

串口里每保存一帧就打印一行,帧率信息就藏在打印间隔里。如果发现输出的帧大小远高于预期(比如 VGA 分辨率下单帧超过 100KB),说明jpeg_quality设得太低,在 NVMe 上都可能卡顿,更别说 SPI 模式的 SD 卡了。拿到这个最小可运行版本,MJPEG2SD 的核心机制就跑通了。

4. 把“抓拍”升级成“连续录像”:参数、缓冲与文件轮转的三层改造

4.1 影响录像总时长的三个参数:分辨率、质量和帧率

MJPEG 录像是一个“预算游戏”:SD 卡总容量除以单帧大小,再除以每秒帧数,就是可录制时长。单帧大小由分辨率和 JPEG 质量共同决定,而帧率受限于抓帧速度和 SD 卡写入速度。

分辨率上,QVGA(320x240)单帧约 10-20KB,VGA(640x480)单帧约 30-60KB,XGA(1024x768)单帧约 80-150KB。帧率上,ESP32-CAM 在 QVGA 下能跑 25fps 左右,VGA 下大概 10-15fps,再高分辨率就只能按帧率低限跑了。这个帧率不是摄像头能力不够,而是 SD 卡 SPI 写入速度成为瓶颈。

jpeg_quality是调节杠杆:值越小画质越好、体积越大;值越大画质越差、体积越小。经验值是 10-15 之间,画质足够辨识细节,单帧体积也不会爆炸。如果把质量调到 20,画面会出现明显的马赛克感,但录制时长会大幅增加。做长时间无人值守录像时,我一般用 VGA + 质量 12 + 7fps,一张 16GB 卡可以录约 4 小时。

帧率控制不要用裸delay(),不同 SD 卡的写入速度差异很大,写卡耗时本身可能占掉帧间隔的一半以上。用时间差判断是更稳妥的做法,这样可以保证每帧的曝光间隔稳定,录像的连贯性不会因为写入波动而忽快忽慢。

const unsigned long frame_interval_us = 1000000 / 7; // 目标 7fps unsigned long last_capture_us = 0; void loop() { unsigned long now = esp_timer_get_time(); if (now - last_capture_us < frame_interval_us) { return; } last_capture_us = now; camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { return; } // 写入逻辑与第 3 章相同 }

esp_timer_get_time()返回微秒级时间戳,比millis()精度高很多。frame_interval_us用 100 万微秒除以目标帧率得到,避免用毫秒做除法带来的精度损失。

4.2 帧缓冲管理:双缓冲和三缓冲的取舍

fb_count参数影响的是“摄像头采集”和“应用层处理”之间的并行度。单缓冲意味着摄像头采集的数据必须完整被应用层消费完才能开始下一帧,期间摄像头处于等待状态;双缓冲允许硬件在应用层处理上一帧时,继续把当前帧写入另一个缓冲;三缓冲进一步降低突发写入造成的丢帧。

代价在内存上。VGA 的 JPEG 单帧约 50KB,三缓冲就是 150KB 的 PSRAM 占用。在 PSRAM 足够的时候,我倾向于fb_count = 3,这是连续录像场景下的经验值,丢帧率明显低于双缓冲。

处理完帧之后必须立即调用esp_camera_fb_return(fb),这一点没有商量的余地。如果你发现录像一段时间后抓帧失败率上升,先检查是不是有路径漏掉了fb_return调用,帧缓冲泄漏是这类代码最常见的隐性 bug。

4.3 文件轮转与卡满覆盖:行车记录仪式录像的落地写法

长时间录像不能只有一个文件一写到底。文件系统写超大单个文件时,FAT 碎片化和掉电后文件损坏的概率都会上升。更合理的组织方式是每 N 秒生成一个新文件,文件名携带时间信息,卡满后删除最老的文件继续写。

文件名的排序直接决定删除逻辑的复杂度。用YYYYMMDD_HHMMSS.jpg这类字典序排列的文件名,从头到尾扫描删除最早的文件即可,不需要解析文件修改时间属性。这里用 RTC 时间源,ESP32 的time.h库通过 NTP 同步后获取标准时间。

#include "time.h" void setup_time() { configTime(8 * 3600, 0, "pool.ntp.org"); } void loop_capture() { struct tm timeinfo; if (!getLocalTime(&timeinfo)) { // 时间未同步时回退到帧计数文件名 snprintf(path, sizeof(path), "/rec_%06u.jpg", frame_count++); } else { strftime(path, sizeof(path), "/rec_%Y%m%d_%H%M%S.jpg", &timeinfo); } // 写入逻辑 }

卡满覆盖的判断放在写入循环的外部。每次写完文件后检查一次 SD 卡剩余空间,低于阈值就遍历根目录,找出字典序最小的文件删除。删除操作放在写入循环里会影响帧率,所以更好的做法是等到两个文件切换的空隙再执行。

覆盖删除是最容易被做砸的地方——如果删错了文件或者删得太频繁,前面的录像数据就可能被新数据覆盖掉。阈值设得保守一点,比如剩余空间低于总容量 10% 时再开始删,宁可稍微多留一点空间,也不要因为删文件影响正在进行的写入。

5. 避坑与常见排查:ESP32-CAM 录像方案的七个真实陷阱

5.1 摄像头初始化失败:PSRAM 没开导致的高分辨率内存申请失败

现象是esp_camera_init返回错误0x20004之类的内存分配失败码,串口同时打印类似 “SPIRAM not enabled” 的提示。原因基本是板型定义里没有启用 PSRAM,或者模块本身是阉割版没有 PSRAM 芯片。

解决方法是先确认硬件:手指摸摄像头模块下方的屏蔽罩,真 PSRAM 版在板子上有一颗 PSRAM 芯片。软件层面检查platformio.ini里的BOARD_HAS_PSRAM宏是否生效,Arduino 环境下需要在“工具 -> Board”里选带ESP32CAM字样的板型。

5.2 SD 卡挂载失败:为什么别人能跑就我不能

现象是SD_MMC.begin()永远返回 false,换了三张卡都不行。原因是 ESP32-CAM 板载卡槽在某些批次走的是 1-bit SD 模式,和标准 SPI 模式的初始化方式不一致;另外把SD_MMC.begin()和SD.begin()两种库混用也会出问题。

解决方法是统一使用SD_MMC库,并且把SD_MMC.begin("/sdcard", true)中的mode1bit参数设为 true,强制使用 1-bit 模式。大部分 ESP32-CAM 的卡槽在 1-bit 模式下兼容性最好。同时检查卡槽的机械接触,痛点往往是卡没有完全推到位。

5.3 写入速度不稳,录像帧率忽高忽低

现象是串口打印的帧间隔波动很大,有时一帧耗时 50ms,下一帧变成 300ms。原因是 SD 卡的磨损老化或者 FAT 文件系统碎片化,长时间反复写入后,卡内闪存页映射变化导致写入速度大幅波动,这是 SPI 模式 SD 卡卡片的常见特点。

解决方法是换一张全新的大品牌 Class 10 卡,不要用手头不知道用了多少年的旧卡。另外每写 1000 帧可以主动关一次文件重新创建,减少文件系统内碎片累积。此外,避免在录像过程中用手机拉取 WiFi 推流,Wi-Fi 和 SD 写卡共用系统资源,会互相拖累。

5.4 JPEG 帧数据不完整,保存出来的图片花屏

现象是图片能打开但下半部分是花的,或者整个画面被撕裂。原因是帧缓冲里的一帧数据还在被 DMA 填充时,esp_camera_fb_get就返回了,或者fb->len与实际写入的字节数不匹配。更常见的是frame_size和fb->len不匹配——高分辨率模式下 OV2640 本身输出超时。

解决办法是把frame_size降到FRAMESIZE_SVGA或FRAMESIZE_VGA测试,排查是否高分辨率模式绑定出现问题。帧损坏率高于 1% 时,优先检查电源,ESP32-CAM 在抓帧瞬间电流尖峰接近 500mA,劣质 USB-TTL 的 3.3V 输出根本带不动。

5.5 录像文件打不开:掉电造成 FAT 目录项损坏

现象是拔电后 SD 卡插电脑,某些.jpg文件显示 0 字节或者文件系统提示需要修复。原因是写文件过程中突然断电,文件系统没有来得及更新目录项和长度信息,这个和 SD 卡质量关系不大,是嵌入式断电写入的固有问题。

解决方法是设计上添加一个“文件关闭完整性检查”:每写完一个文件后重新打开读一次首尾字节,确认没有提前 EOF;同时在代码里加入优雅关机流程,检测到按键按下后先关闭所有句柄再断电,减少损坏窗口。量产级做法是每帧写入完成后调用file.sync(),代价是每个文件多几次 SPI 写入操作。

5.6 为什么换上 32GB 大卡反而报错

现象是 8GB 卡正常,换成 32GB 或 64GB 卡初始化失败。原因是很多大容量卡出场是 exFAT 格式,ESP32 的 SD_MMC 库默认不支持 exFAT,只支持 FAT32。

解决方法是把新的 SD 卡在电脑上格式化为 FAT32,簇大小选 32KB 或更小。ESP32 对 exFAT 的支持在较新的 SDK 中已有,但在 Arduino 环境下踩的人很多,直接用 FAT32 最稳妥。

5.7 长时间录像后摄像头发热严重导致画面模糊

现象是录像跑了 30 分钟后画面开始模糊发白。原因是 OV2640 传感器和 ESP32 芯片在长时间高负载下发热,传感器温度升高后暗电流增大,画质劣化,这叫温度漂移。

解决方法是整体录像节奏加入“冷却帧”:每录 60 秒停 2 秒,让摄像头吃一下凉气,代价是丢几帧数据,换来长时间稳定。如果要连续录几小时,物理上加一片散热片在芯片背面是性价比最高的解法,我用一片 8x8mm 的铝片贴在 ESP32 背面就能明显改善。

6. 进阶验证技巧:用 MJPEG 编码容器把单帧 JPG 串成 AVI

跑通上面的 JPG 连拍之后,录像数据量一大,管理文件就很费劲。一个值得做的进阶是把单帧 JPG 包进 AVI 容器。AVI 对 MJPEG 编码的 JPEG 帧支持非常宽松:它本质上是一个 RIFF 格式的容器,把 JPEG 帧按顺序写入movi块,加上strh、strf描述流的编码格式、帧率和分辨率信息,就能被电脑上的标准播放器识别。这个做法的价值在于:不需要任何视频编码运算,ESP32 侧只是把多个 JPEG 帧按 AVI 的规则写入一个文件,播放时由本地播放器负责解码。

核心步骤是先写一个只包含一帧的 AVI 头,记录分辨率、帧率、总帧数和编码器标识;后续每抓一帧 JPEG,先写00dc的 chunk 标识和帧长度,再把完整 JPEG 数据追加进去。最后一帧写完后回填头部中记录的帧数和文件大小。关键参数是dwScale、dwRate——这两个值决定播放帧率,dwRate是帧率分子,dwScale是分母,比如 7fps 就设dwRate=7, dwScale=1。我用 640x480、质量 12、7fps 的参数组合,录制 10 分钟生成一个约 180MB 的 AVI 文件,在所有主流播放器里都能正常打开和抽帧。

代码层面不需要引入额外库,手写一个文件头结构体就够了。在初始化时创建文件、写入RIFF头、预留movi块的偏移位置,每帧数据按00dcchunk 格式追加。文件关闭前回到文件头回填RIFF大小和avih帧计数字段。

// AVI 写入核心:每帧 JPEG 前加 4 字节标识和 4 字节长度 file.write((const uint8_t*)"00dc", 4); file.write((const uint8_t*)&fb->len, sizeof(uint32_t)); file.write(fb->buf, fb->len); // 帧计数器 frame_count++; // 所有帧写完后回填文件头 file.seek(4, SeekSet); uint32_t riff_size = file.size() - 8; file.write((const uint8_t*)&riff_size, sizeof(uint32_t));

00dc是 AVI 规范中 MJPEG 流的标准 chunk 标识,长度字段必须是小端序存储,ESP32 本身是小端处理器,不需要额外做字节序转换,这一点省了不少心。确保seek在文件关闭之前执行,否则文件头保持全零,播放器会提示AVI 损坏。

这个技巧的边界和坑我也踩过一轮:一是 AVI 头的avih结构里dwTotFrames必须从 0 开始记,我第一版从 1 开始记导致播放器总认为最后一帧缺失;二是width和height必须写在strf的biWidth和biHeight字段里,而不是BITMAPINFOHEADER偶数字节对齐之外的字节——有一次写错了位置,播放器能识别画面但显示的分辨率是错的。最后补充一句我的习惯:任何录像方案做完,都要验证三个字——“拔电再开”。连续录像时拔掉电源,插回电脑看最后一个文件能不能正常拖进度条。如果每次都损坏,说明sync()没有及时刷到物理卡上,这个问题不解决,任何保存策略都是纸面可靠。

希望这篇一步步走下来的方案能帮你把 MJPEG2SD 跑成真正能用的项目,少陪我走那些夜里盯着串口输出发愣的路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询