
Arduino-ESP32 ESP_Video 库实战指南基于 MIPI-CSI 与 DVP 接口的 V4L2 风格摄像头采集【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32ESP_Video 是 arduino-esp32 核心中面向 ESP32-S3 与 ESP32-P4 的摄像头采集库它封装了 ESP-IDF 的esp_video组件以 V4L2 风格 API 屏蔽 MIPI-CSI含 ISP与 DVP 两种底层接口的差异。读完本文你将掌握视频设备节点的选择、像素格式与分辨率的映射关系、MIPI-CSI / DVP 两套完整初始化与取帧流程以及如何通过传感器控制接口调节增益、曝光与镜像翻转可直接基于 示例工程 与 DVP 示例 快速搭建预览、抓帧或 JPEG 采集应用。库定位与适用前提该库封装 ESP-IDFesp_video组件对外暴露 V4L2 风格的采集 API用于受支持 SoC 上的摄像头传感器。它完成MIPI-CSI带 ISP与DVP两种接口的底层初始化后让应用可以打开视频设备节点、分配缓冲区并取出帧数据。典型用途包括预览流水线、面向计算机视觉的抓帧、JPEG 采集以及传感器调参增益、曝光、翻转等控制。使用该库有三个硬性前提见 ESP_Video.h 头部的条件编译ESP-IDF 5.4.0 或更高版本且目标芯片为ESP32-S3 或 ESP32-P4IDF 配置中至少开启CONFIG_ESP_VIDEO_ENABLE_MIPI_CSI_VIDEO_DEVICE或CONFIG_ESP_VIDEO_ENABLE_DVP_VIDEO_DEVICE其中之一否则ESP_Video.h不暴露任何公共 API示例分别面向ESP32-P4MIPI-CSI DVP与ESP32-S3DVP。在草图中包含头文件即可#include ESP_Video.h从源码结构看ESP_Video.h在最外层用#if (CONFIG_ESP_VIDEO_ENABLE_MIPI_CSI_VIDEO_DEVICE || CONFIG_ESP_VIDEO_ENABLE_DVP_VIDEO_DEVICE)和#if ESP_IDF_VERSION ESP_IDF_VERSION_VAL(5, 4, 0)双重条件包裹全部类定义这正是文档所述无配置则无 API的代码级体现。库的元信息见 library.properties当前版本 3.3.11类别 Signal Input/Output。视频设备节点用文件路径操作摄像头硬件初始化完成后采集通过esp_video_device.h中定义的标准设备路径进行设备路径宏实际节点含义ESP_VIDEO_MIPI_CSI_DEVICE_NAME/dev/video0MIPI-CSI 摄像头ESP_VIDEO_ISP_DVP_DEVICE_NAME/dev/video1ISP DVPESP_VIDEO_DVP_DEVICE_NAME/dev/video2无 ISP 路径的 DVP其他节点—在 IDF 配置中启用 SPI、USB UVC、JPEG/H264 编解码、ISP 设备时追加节点路径只是入口真正的工作由ESPVideoCaptureDevClass完成它以open()打开设备经VIDIOC_G_FMT读取驱动当前格式、VIDIOC_S_FMT重新应用格式再请求 MMAP 捕获缓冲区对应源码 open()/applyFormat()。API 总览七层设计Arduino-ESP32 的 ESP Video API 组织为七层从像素格式到传感器控制逐层递进像素格式——esp_video_format_t与ESPVideoFormatClass分辨率——ESPVideoSolutionClass帧宽高硬件配置——ESPVideoCamConfigClass、ESPVideoCSIConfigClass、ESPVideoDVPPinsConfigClass、ESPVideoDVPConfigClass硬件初始化——ESPVideoClass::begin()采集设备——ESPVideoCaptureDevClass继承格式与分辨率基类缓冲区——ESPVideoBufferClass同样继承格式与分辨率基类传感器控制——ESPVideoCaptureDevClass::setSensor*()系列方法值得注意的设计细节均有源码佐证配置对象是值类型只保存引脚与总线设置可复制、可赋值引脚通过periman的占用/释放由ESPVideoClass::begin()完成在end()或析构函数中释放。CSI 与 DVP 可共存例如 ESP32-P4每种接口由内部标志位独立跟踪并在end()或析构时释放。采集设备打开是统一的begin()打开设备、读取并重放当前 V4L2 格式VIDIOC_G_FMT/VIDIOC_S_FMT、保存宽高并请求捕获缓冲区。格式与分辨率是共享基类采集设备和每一帧缓冲区都携带元数据。像素格式是 Arduino 枚举setFormat()在startCapture()前调用若缓冲区分配后格式改变驱动缓冲区会被自动重新请求见setFormat()中requestBuffer(buf_count_)的回调逻辑。资源持有类不可复制ESPVideoClass、ESPVideoCaptureDevClassESPVideoBufferClass仅可移动。像素格式与 V4L2 fourcc 映射esp_video_format_t是 Arduino 侧的像素格式标识符ESPVideoCaptureDevClass::setFormat()在发出VIDIOC_S_FMT前将每个枚举值映射为 V4L2 fourcc格式元数据存放在ESPVideoFormatClass中被采集设备与每个缓冲区继承。枚举定义见 ESP_Video.htypedef enum { ESP_VIDEO_FORMAT_UNKNOWN 0, ESP_VIDEO_FORMAT_RAW8, ESP_VIDEO_FORMAT_RAW10, ESP_VIDEO_FORMAT_RAW12, ESP_VIDEO_FORMAT_RGB565, ESP_VIDEO_FORMAT_RGB888, ESP_VIDEO_FORMAT_YUV420, ESP_VIDEO_FORMAT_YUV422_YUYV, ESP_VIDEO_FORMAT_YUV422_UYVY, ESP_VIDEO_FORMAT_GRAY8, ESP_VIDEO_FORMAT_JPEG, ESP_VIDEO_FORMAT_MAX } esp_video_format_t;支持的格式及其 V4L2 映射映射函数video_format_to_v4l2_format()见 ESP_Video.cppesp_video_format_t名称字符串V4L2 fourccESP_VIDEO_FORMAT_RAW8RAW8V4L2_PIX_FMT_SRGGB8ESP_VIDEO_FORMAT_RAW10RAW10V4L2_PIX_FMT_SGRBG10ESP_VIDEO_FORMAT_RAW12RAW12V4L2_PIX_FMT_SGRBG12ESP_VIDEO_FORMAT_RGB565RGB565V4L2_PIX_FMT_RGB565ESP_VIDEO_FORMAT_RGB888RGB888V4L2_PIX_FMT_RGB24ESP_VIDEO_FORMAT_YUV420YUV420V4L2_PIX_FMT_YUV420ESP_VIDEO_FORMAT_YUV422_YUYVYUV422_YUYVV4L2_PIX_FMT_YUYVESP_VIDEO_FORMAT_YUV422_UYVYYUV422_UYVYV4L2_PIX_FMT_UYVYESP_VIDEO_FORMAT_GRAY8GRAY8V4L2_PIX_FMT_GREYESP_VIDEO_FORMAT_JPEGJPEGV4L2_PIX_FMT_JPEGESP_VIDEO_FORMAT_UNKNOWN与ESP_VIDEO_FORMAT_MAX是哨兵值不能传给setFormat()。实际可用格式取决于传感器驱动与当前视频设备若驱动在begin()时报告无法识别的 fourccgetFormat()会返回ESP_VIDEO_FORMAT_UNKNOWN对应源码v4l2_format_to_video_format()的 default 分支。分辨率与格式元数据基类ESPVideoSolutionClass分辨率存储当前帧宽高被ESPVideoCaptureDevClassbegin()期间经VIDIOC_G_FMT填充与ESPVideoBufferClass取帧时从采集设备复制继承ESPVideoSolutionClass(); // 空对象宽高为 0 ESPVideoSolutionClass(const ESPVideoSolutionClass config); // 复制宽高 ESPVideoSolutionClass operator(const ESPVideoSolutionClass config); void begin(uint32_t width, uint32_t height); // 存储帧尺寸 uint32_t getWidth() const; uint32_t getHeight() const;ESPVideoFormatClass格式元数据存储当前像素格式类型及其可读名称同样被采集设备与缓冲区继承ESPVideoFormatClass(); // 空对象格式为 UNKNOWN ESPVideoFormatClass(const ESPVideoFormatClass config); ESPVideoFormatClass operator(const ESPVideoFormatClass config); void begin(esp_video_format_t format_type, const std::string format_name ); esp_video_format_t getFormat() const; const char *getFormatName() const;当begin()传入空format_name时getFormatName()会根据format_type推导名称并在首次访问时缓存源码中format_name_被声明为mutable以支持惰性填充。摄像头配置SCCB / I2CESPVideoCamConfigClass保存 CSI 与 DVP 共用的 SCCBI2C设置。创建实例后调用begin()填充设置再传给ESPVideoCSIConfigClass或ESPVideoDVPConfigClass。默认构造创建空对象复制构造/赋值复制 SCCB 端口或句柄、引脚号与 I2C 频率但不保留 GPIO引脚所有权归ESPVideoClass::begin()。beginArduino 引脚号版本bool begin(i2c_port_num_t port, int8_t scl_pin, int8_t sda_pin, uint32_t i2c_freq i2c_freq_hz, int8_t reset_pin -1, int8_t pwdn_pin -1);port—— 库初始化总线时用于 SCCB 的 I2C 端口scl_pin/sda_pin—— SCCB 时钟与数据引脚i2c_freq—— SCCB 频率Hz默认 100 kHz源码中i2c_freq_hz 100 * 1000reset_pin/pwdn_pin—— 可选的传感器复位与掉电 GPIO不用时传-1。begin复用已有 I2C 主总线bool begin(i2c_master_bus_handle_t i2c_handle, uint32_t i2c_freq i2c_freq_hz, int8_t reset_pin -1, int8_t pwdn_pin -1);当 SCCB 复用应用已创建的 I2C 总线时使用此重载此时库不会为 SCCB 初始化新的 I2C 驱动源码通过needInitI2C()判断i2c_handle nullptr来决定是否自建总线并在getSccbConfig()中相应设置init_sccb标志。其他成员esp_video_init_sccb_config_t getSccbConfig() const—— 返回供 ESP-IDFesp_video初始化结构使用的 SCCB 段gpio_num_t getResetPin() const/gpio_num_t getPwdnPin() const—— 可选传感器控制 GPIO 的只读访问bool acquirePins(void *bus)/bool releasePins()—— 通过periman保留或释放 SCCB 及复位/掉电 GPIO由ESPVideoClass::begin()/end()自动调用应用通常无需直接调用。DVP 场景下periman的占用类型包括ESP32_BUS_TYPE_VIDEO_CAM_RESET、ESP32_BUS_TYPE_VIDEO_SCCB_SCL/SDA以及ESP32_BUS_TYPE_LCDCAM_CAM_*系列见 setPinsBus/clearPinsBus 实现。MIPI-CSI 配置ESPVideoCSIConfigClass继承ESPVideoCamConfigClass用于 MIPI-CSI ISP 初始化仅在启用CONFIG_ESP_VIDEO_ENABLE_MIPI_CSI_VIDEO_DEVICE时可用。复制构造会同时复制基类摄像头配置与 LDO 初始化标志。bool begin(const ESPVideoCamConfigClass config, bool dont_init_ldo false);config—— 基础摄像头 / SCCB 配置dont_init_ldo—— 为true时跳过 CSI 驱动内的 LDO 初始化外部供电时使用。getCsiInitConfig()从存储的配置构建 ESP-IDF CSI 初始化结构getDontInitLdo()返回 LDO 跳过标志。DVP 引脚配置与 DVP 配置ESPVideoDVPPinsConfigClass并行引脚描述 DVP 并行数据与同步引脚仅在启用CONFIG_ESP_VIDEO_ENABLE_DVP_VIDEO_DEVICE时可用begin()固定配置 8 位数据宽度D0–D7。bool begin(int8_t vsync_pin, int8_t de_pin, int8_t pclk_pin, int8_t xclk_pin, int8_t data0_pin, int8_t data1_pin, int8_t data2_pin, int8_t data3_pin, int8_t data4_pin, int8_t data5_pin, int8_t data6_pin, int8_t data7_pin);vsync_pin/de_pin/pclk_pin/xclk_pin—— 同步与时钟引脚data0_pin…data7_pin—— 8 条并行数据线。源码中该begin()会将dvp_pin.data_width固定为CAM_CTLR_DATA_WIDTH_8并把各引脚填入esp_cam_ctlr_dvp_pin_config_t。getDvpPin()返回原生 ESP-IDF DVP 引脚结构。ESPVideoDVPConfigClass组合配置同时继承ESPVideoCamConfigClass与ESPVideoDVPPinsConfigClass单个实例携带完整 DVP 设置SCCB 引脚 XCLK 频率bool begin(const ESPVideoCamConfigClass config, const ESPVideoDVPPinsConfigClass dvp_pin, uint32_t xclk_freq);config—— 基础摄像头 / SCCB 配置dvp_pin—— DVP 数据、同步与时钟引脚映射参见 ESP-IDF camera controller 文档xclk_freq—— 传感器外部时钟XCLK频率Hz。getDvpInitConfig()构建 ESP-IDF DVP 初始化结构getXclkFreq()返回 XCLK 频率。其acquirePins()/releasePins()依次调用基类实现同时管理 SCCB、复位/掉电与 DVP 并行引脚。ESPVideoClass硬件初始化与清理ESPVideoClass初始化 CSI 或 DVP 的esp_video栈每个应用创建一个实例或使用静态/全局实例。析构函数调用end()停止已初始化的子系统并释放保留的引脚。void end();通过esp_video_deinit_with_flags()停止子系统、释放begin()期间保留的引脚并清空存储的配置。beginMIPI-CSIbool begin(const ESPVideoCSIConfigClass config);初始化 MIPI-CSI 与 ISPESP_VIDEO_INIT_FLAGS_MIPI_CSI | ESP_VIDEO_INIT_FLAGS_ISP。流程为通过periman保留引脚 → 调用esp_video_init_with_flags()→ 保存配置供清理。初始化成功或已完成时返回true失败时在返回false前释放已保留引脚源码中的回滚逻辑见 begin(CSI)。isCSIInitialized()返回 CSI 与 ISP 标志是否生效。beginDVPbool begin(const ESPVideoDVPConfigClass config);初始化 DVP 视频设备ESP_VIDEO_INIT_FLAGS_DVP遵循与 CSI 相同的引脚保留与回滚规则。isDVPInitialized()返回 DVP 子系统是否已初始化。isActive()则返回是否有任何子系统标志处于活动状态。ESPVideoCaptureDevClass采集设备与帧循环该类打开 V4L2 采集设备、管理缓冲区并取出帧继承ESPVideoSolutionClass与ESPVideoFormatClass提供分辨率与格式元数据。类本身不可复制、不可移动请使用单一长生命周期实例或显式管理生命周期。析构函数调用end()停止采集、解除 MMAP 映射、释放驱动缓冲区并关闭文件描述符。begin / endbool begin(const char *path, size_t buf_count 2); void end();begin()打开path指定的设备若未打开读取驱动格式VIDIOC_G_FMT、重新应用VIDIOC_S_FMT、保存宽高与像素格式并请求buf_count个 MMAP 捕获缓冲区检测到的格式与分辨率可通过getFormat()、getFormatName()、getWidth()、getHeight()获取。设备已打开时直接返回true而不改变路径任何失败都会关闭设备并使实例保持未打开状态源码 begin()。requestBuffer / startCapture / stopCapturebool requestBuffer(size_t buf_count); bool startCapture(); bool stopCapture();requestBuffer(buf_count)—— 释放既有缓冲区向驱动请求新的 MMAP 缓冲区并映射到用户空间buf_count通常取 2 以实现双缓冲失败时回滚部分已分配缓冲区。源码中会依次执行VIDIOC_REQBUFS→ 逐个VIDIOC_QUERYBUFmmap()见 requestBuffer()。startCapture()—— 将所有缓冲区入队并启动视频流VIDIOC_STREAMON要求先完成缓冲区设置。stopCapture()—— 若采集已启动则停止流VIDIOC_STREAMOFF未启动或已停止时返回false。captureBuffer 与状态查询ESPVideoBufferClass captureBuffer(); bool isOpened() const; bool isCaptureStarted() const;captureBuffer()取出一个已填充缓冲区VIDIOC_DQBUF采集未运行或取帧失败时返回无效缓冲区。isOpened()判断设备文件描述符是否有效isCaptureStarted()判断startCapture()是否成功且流未停止。setFormatbool setFormat(esp_video_format_t format);在采集开始前选择像素格式或在设备打开后更改格式。内部以format对应的 V4L2 fourcc 执行VIDIOC_G_FMT/VIDIOC_S_FMT若格式改变且已分配捕获缓冲区会释放既有 MMAP 缓冲区并按上次begin()/requestBuffer()的缓冲区数量重新请求。对不支持的枚举值、I/O 错误或缓冲区重分配失败返回false。典型应用在begin()之后、startCapture()之前调用。注意源码中若采集已启动capture_started_setFormat()会直接拒绝执行。传感器控制以下方法映射到 V4L2 扩展控制VIDIOC_S_EXT_CTRLS可用性取决于传感器驱动与当前格式每个控制方法在驱动接受值时才返回true方法对应的 V4L2 控制bool setSensorGain(int32_t gain)V4L2_CID_GAINbool setSensorExposure(int32_t exposure)V4L2_CID_EXPOSUREbool setSensorExposureTime(int32_t exposure_time)V4L2_CID_EXPOSURE_ABSOLUTEbool setSensorAETargetLevel(int32_t target_level)V4L2_CID_CAMERA_AE_LEVELbool setSensorJPEGQuality(int32_t quality)V4L2_CID_JPEG_COMPRESSION_QUALITYbool setSensorVFlip(bool vflip)V4L2_CID_VFLIPbool setSensorHFlip(bool hflip)V4L2_CID_HFLIPbool setSensorTestPattern(bool test_pattern)V4L2_CID_TEST_PATTERN所有方法统一经setExtCtrlValue()构造v4l2_ext_control/v4l2_ext_controls并下发ioctl(fd, VIDIOC_S_EXT_CTRLS, ...)见 ESP_Video.cpp。ESPVideoBufferClassRAII 帧缓冲一帧已取出数据的 RAII 包装。继承ESPVideoSolutionClass与ESPVideoFormatClass因此每帧自带格式与分辨率元数据。对象被销毁、被移动或调用end()时缓冲区会被重新入队VIDIOC_QBUF以便采集继续。该类仅可移动复制已删除。ESPVideoBufferClass(ESPVideoBufferClass other) noexcept; // 转移所有权源对象清空 ESPVideoBufferClass operator(ESPVideoBufferClass other) noexcept; bool valid() const; // data() 非空且 size() 0 uint8_t *data() const; // MMAP 模式下的像素数据指针 size_t size() const; // V4L2 的 bytesused有效字节数 int index() const; // 驱动缓冲区索引重新入队后为 -1 uint32_t getWidth() const; uint32_t getHeight() const; // 取帧时从采集设备复制的宽高 const char *formatName() const; // getFormatName() 的别名 esp_video_format_t formatType() const; // getFormat() 的别名 void end(); // 立即将缓冲区归还驱动重要提示只在处理一帧期间持有ESPVideoBufferClass。销毁、移动或调用end()都会把缓冲区归还驱动不及时释放缓冲区会导致captureBuffer()停滞缓冲区耗尽。同时data()返回的指针不可超过ESPVideoBufferClass对象的生命周期保存。端到端示例MIPI-CSI 采集ESP32-P4完整示例见 mipi_csi_camera.ino核心流程如下#include esp_video_device.h ESPVideoClass video; ESPVideoCamConfigClass cam_config; cam_config.begin(port, scl, sda); ESPVideoCSIConfigClass csi_config; csi_config.begin(cam_config); if (!video.begin(csi_config)) { // handle error } ESPVideoCaptureDevClass capture; if (!capture.begin(ESP_VIDEO_MIPI_CSI_DEVICE_NAME, 2)) { // handle error } if (!capture.setFormat(ESP_VIDEO_FORMAT_RGB565)) { // handle error } if (!capture.startCapture()) { // handle error } ESPVideoBufferClass frame capture.captureBuffer(); if (frame.valid()) { uint8_t *pixels frame.data(); size_t nbytes frame.size(); const char *format_name frame.formatName(); // e.g. JPEG, RGB565 uint32_t width frame.getWidth(); uint32_t height frame.getHeight(); // use frame; buffer is returned to the driver when frame is destroyed }示例工程的引脚定义以 ESP32-P4-Function-EV-Board V1.5 为默认SCCB I2C 端口 0、SCL GPIO 8、SDA GPIO 7并保护性地用#if ESP_IDF_VERSION ESP_IDF_VERSION_VAL(5, 4, 0)与#if CONFIG_IDF_TARGET_ESP32P4包裹版本或芯片不符时给出明确编译错误或运行提示。示例 CI 配置ci.yml还要求PartitionSchemeesp_sr_16, FlashSize16M与CONFIG_ESP_VIDEO_ENABLE_MIPI_CSI_VIDEO_DEVICEy可作为你配置开发环境时的参考。端到端示例DVP 采集ESP32-S3 / ESP32-P4完整示例见 dvp_camera.ino核心流程如下#include esp_video_device.h ESPVideoClass video; ESPVideoCamConfigClass cam_config; cam_config.begin(port, scl, sda); ESPVideoDVPPinsConfigClass dvp_pins; dvp_pins.begin(vsync, de, pclk, xclk, d0, d1, d2, d3, d4, d5, d6, d7); ESPVideoDVPConfigClass dvp_config; dvp_config.begin(cam_config, dvp_pins, xclk_freq_hz); if (!video.begin(dvp_config)) { // handle error } ESPVideoCaptureDevClass capture; if (!capture.begin(ESP_VIDEO_DVP_DEVICE_NAME, 2)) { // handle error } if (!capture.startCapture()) { // handle error } ESPVideoBufferClass frame capture.captureBuffer(); if (frame.valid()) { uint8_t *pixels frame.data(); size_t nbytes frame.size(); const char *format_name frame.formatName(); uint32_t width frame.getWidth(); uint32_t height frame.getHeight(); // use frame; buffer is returned to the driver when frame is destroyed }示例为两种芯片提供了可直接套用的默认引脚与 XCLK 频率ESP32-S3ESP32-S3-EYESCCB I2C 端口 0、SCL GPIO 5、SDA GPIO 4XCLK GPIO 15、PCLK GPIO 13、VSYNC GPIO 6、DE GPIO 7、D0–D7 为 GPIO 11/9/8/10/12/18/17/16XCLK 频率10 MHz。ESP32-P4ESP32-P4-Function-EV-Board V1.5SCCB I2C 端口 0、SCL GPIO 8、SDA GPIO 7XCLK GPIO 20、PCLK GPIO 4、VSYNC GPIO 37、DE GPIO 22、D0–D7 为 GPIO 2/32/33/23/3/6/5/21XCLK 频率20 MHz。两个示例的loop()都采用取帧 → 校验 → 打印指针/大小/格式/宽高 → 帧对象析构归还缓冲区的循环模式Serial 波特率 115200kCaptureBufferCount 2即双缓冲可避免帧丢失。示例同样要求CONFIG_ESP_VIDEO_ENABLE_DVP_VIDEO_DEVICEy。配置 Kconfig 与开发环境要点库整体在 library.properties 中声明为architecturesesp32的标准 Arduino 库安装后通过#include ESP_Video.h使用。必须按接口类型开启对应 KconfigMIPI-CSI 用CONFIG_ESP_VIDEO_ENABLE_MIPI_CSI_VIDEO_DEVICEyDVP 用CONFIG_ESP_VIDEO_ENABLE_DVP_VIDEO_DEVICEy两者可同时开启ESP32-P4 支持 CSI 与 DVP 共存。示例 CI 额外使用PartitionSchemeesp_sr_16, FlashSize16M在资源紧张的工程中可作为分区间隔的参考。若板级引脚与示例不同只需改写EXAMPLE_*引脚宏引脚冲突会在periman保留阶段被检测并打印日志源码中videoDetachBus会提示先调用video.end()再复用摄像头引脚。【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考