news 2026/9/20 8:38:20

使用 esp-iot-solution 的 usb_device_uvc 组件将 ESP32 打造为 USB UVC 摄像头设备

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用 esp-iot-solution 的 usb_device_uvc 组件将 ESP32 打造为 USB UVC 摄像头设备

使用 esp-iot-solution 的 usb_device_uvc 组件将 ESP32 打造为 USB UVC 摄像头设备

【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution

usb_device_uvc是 esp-iot-solution 仓库中面向 ESP32-S2 / ESP32-S3 / ESP32-P4 的 USB UVC(USB Video Class)设备驱动组件,它让 MCU 能够以标准摄像头设备的身份出现在 USB 主机(PC、平板等)上,并通过回调函数将相机或任意图像源包装成符合 UVC 规范的设备。读完本文,你将掌握如何添加该组件、理解四个核心回调函数与uvc_device_config()/uvc_device_init()的完整用法、配置分辨率与帧率等 Kconfig 选项,并能参照仓库示例将 ESP32-S3-EYE 变成即插即用的 USB 网络摄像头。

组件概览与能力边界

usb_device_uvc组件位于 components/usb/usb_device_uvc,其核心实现为 usb_device_uvc.c,对外 API 声明在 include/usb_device_uvc.h。组件底层基于 TinyUSB 协议栈,由 tusb 目录下的tusb_config.husb_descriptors.cuvc_frame_config.h提供描述符和帧配置支持。

根据 usb_device_uvc.rst 与 README.md,该组件支持:

  1. 通过 UVC 流接口进行视频流传输(Stream 接口);
  2. 同时支持 Isochronous(等时)与 Bulk(批量)两种传输模式;
  3. 支持多种分辨率和帧率(默认支持 QVGA/HVGA/VGA/SVGA/HD/FHD 多档,并允许自定义多帧尺寸列表);
  4. 在开启CONFIG_UVC_SUPPORT_TWO_CAM后支持双摄像头同时出流。

需要说明的是:组件当前不提供deinitAPI 的公开承诺(文档明确指出"该组件仅提供一个 API 用于配置 UVC 设备。由于驱动基于 TinyUSB 堆栈,因此未提供 deinit API"),不过在较新版本源码中已额外实现了uvc_device_deinit()(见 usb_device_uvc.c),可用于停止任务、拆除 USB 栈与释放 PHY,读者可按需使用。

将组件添加到项目

与仓库中其他组件一致,usb_device_uvc通过 ESP-IDF 组件管理器进行依赖管理。在项目根目录执行:

idf.py add-dependency "espressif/usb_device_uvc=*"

该命令会把组件写入项目的idf_component.yml,在后续 CMake 配置步骤中自动从组件仓库下载,无需手动拷贝源码。

用户参考:配置结构与四个回调函数

组件对外只要求用户完成两件事:填充uvc_device_config_t配置结构并调用uvc_device_config()注册,然后调用uvc_device_init()启动设备。由于图像数据完全由用户提供,组件本身并不关心画面来源——它可以是真实摄像头,也可以是从 Flash/内存中读取的 JPEG 图片(测试用例就是这么做的)。

帧缓冲区结构 uvc_fb_t

uvc_fb_t(定义于 usb_device_uvc.h)描述一帧待发送的数据:

字段类型含义
bufuint8_t *帧数据指针
lensize_t帧数据字节数
widthsize_t图像宽度(像素)
heightsize_t图像高度(像素)
formatuvc_format_t帧格式(JPEG 或 H264)
timestampstruct timeval帧的时间戳(自启动起)

uvc_format_t枚举支持UVC_FORMAT_JPEGUVC_FORMAT_H264两种格式(见 usb_device_uvc.h)。

四个回调函数

组件通过四个回调把"什么时候启动相机、什么时候取帧、什么时候还帧、什么时候停止"完全交给用户实现(回调类型定义见 usb_device_uvc.h):

#include "usb_device_uvc.h" static esp_err_t camera_start_cb(uvc_format_t format, int width, int height, int rate, void *cb_ctx) { // 用户可以在这里初始化相机 // 相机应根据给定的格式、宽度、高度和帧率进行初始化 return ESP_OK; } static void camera_stop_cb(void *cb_ctx) { // 用户代码(停止相机采集) return; } static uvc_fb_t* camera_fb_get_cb(void *cb_ctx) { // 用户代码以返回图像帧缓冲区 // 相机应准备下一帧,并返回帧缓冲区 return uvc_fb; } static void camera_fb_return_cb(uvc_fb_t *fb, void *cb_ctx) { // 在帧缓冲区被复制到传输缓冲区后返回 // 用户代码以回收帧缓冲区 return; }

各回调的语义与触发时机如下:

  • start_cb:主机(Host)以特定格式/分辨率/帧率打开 UVC 设备时被调用,返回值非ESP_OK会被组件视为相机初始化失败,从而向主机返回 UVC 错误(见 usb_device_uvc.c 中tud_video_commit_cb的实现);
  • stop_cb:主机关闭设备、USB 总线挂起(tud_suspend_cb)或重新提交流参数(re-commit)时被调用,用于停止相机采集,避免传感器重复初始化;
  • fb_get_cb:视频任务按帧间隔向用户索要下一帧,返回NULL表示取帧失败(组件会丢弃该帧并继续);
  • fb_return_cb:帧数据已被复制进组件内部传输缓冲区后调用,用户在此处归还/释放帧缓冲区(如调用esp_camera_fb_return)。

配置结构 uvc_device_config_t 与启动流程

// 缓冲区用于存储要发送到主机的数据 const size_t buff_size = 30 * 1024; uint8_t *uvc_buffer = (uint8_t *)heap_caps_malloc(buff_size, MALLOC_CAP_DEFAULT); assert(uvc_buffer != NULL); uvc_device_config_t config = { .uvc_buffer = uvc_buffer, .uvc_buffer_size = 40 * 1024, .start_cb = camera_start_cb, .fb_get_cb = camera_fb_get_cb, .fb_return_cb = camera_fb_return_cb, .stop_cb = camera_stop_cb, .cb_ctx = NULL, }; ESP_ERROR_CHECK(uvc_device_config(0, &config)); ESP_ERROR_CHECK(uvc_device_init());

uvc_device_config_t完整字段(见 usb_device_uvc.h):

字段类型说明
uvc_bufferuint8_t *UVC 传输缓冲区,存放待发送帧数据
uvc_buffer_sizeuint32_t传输缓冲区大小,必须大于一帧的大小,否则大帧会被直接丢弃
start_cbuvc_input_start_cb_t主机以特定格式/分辨率打开设备时的回调
fb_get_cbuvc_input_fb_get_cb_t主机请求新帧时的回调
fb_return_cbuvc_input_fb_return_cb_t帧缓冲区不再使用时的回调
stop_cbuvc_input_stop_cb_t主机关闭设备时的回调
cb_ctxvoid *回调上下文,用户自定义数据,原样传给所有回调

uvc_device_config(int index, uvc_device_config_t *config)index是 UVC 设备编号([0,1]0对应 Cam1,1对应 Cam2,仅在开启双摄像头时可用)。从源码看,该函数会做严格的参数校验:index越界、config为 NULL、四个回调缺失、缓冲区为空或大小为 0 都会返回ESP_ERR_INVALID_ARG(见 usb_device_uvc.c)。

uvc_device_init()则完成真正的工作:

  1. 根据 Kconfig 中 Cam1/Cam2 的格式选项确定UVC_FORMAT_JPEGUVC_FORMAT_H264
  2. 创建事件组,初始化 USB PHY(usb_phy_init(),配置为 OTG 设备模式);
  3. 调用tusb_init()启动 TinyUSB 协议栈;
  4. 创建 TinyUSB 任务与 UVC 视频任务(任务优先级、绑核均可通过 Kconfig 配置),随后设备即可被主机枚举为 UVC 摄像头。

源码级工作原理:从取帧到 USB 传输

从 usb_device_uvc.c 的实现可以梳理出整条数据通路:

  1. 参数协商:主机发起 UVC 流参数协商(Probe/Commit)时,TinyUSB 调用tud_video_commit_cb,组件打印bFrameIndexdwFrameInterval,将帧间隔(100ns 单位)换算为毫秒存入interval_ms,并按帧索引从UVC_FRAMES_INFO查表得到宽/高/帧率,调用start_cb让用户初始化相机;
  2. 帧节奏控制:视频任务(video_task)通过tud_video_n_streaming(0, 0)判断当前是否处于流传输状态;未传输时重置内部状态并等待;传输时按interval_ms精确节流,实现目标帧率;
  3. 取帧与拷贝:到达帧间隔后调用fb_get_cb取帧,若pic->len > uvc_buffer_size则记录frame size is too big, dropping frame并归还帧、丢弃本帧;否则memcpy到传输缓冲区后立即调用fb_return_cb归还用户帧缓冲;
  4. 异步发送:调用tud_video_n_frame_xfer()发起 USB 传输,并通过任务通知(xTaskNotifyGive)在tud_video_frame_xfer_complete_cb中获知上一帧发送完成,从而保证同一时刻只有一个在途传输,避免缓冲区竞争。

可见组件对"缓冲不足"的处理是主动丢帧而非阻塞,因此uvc_buffer_size的取值直接影响高分辨率下的实际帧率表现:若缓冲区小于最大帧尺寸,超大帧将被持续丢弃,表现为花屏或掉帧。

Kconfig 配置详解

组件的所有可调参数集中在 Kconfig 中,通过idf.py menuconfig进入USB Device UVC菜单配置。需要说明的是:这些配置在 menuconfig 中体现为CONFIG_UVC_CAM1_*/CONFIG_UVC_CAM2_*系列符号,由 Kconfig 中的变量(如UVC_CAM1_FRAMESIZE)经选择后生成。

USB 身份信息

配置项默认值说明
TUSB_VID0x303A厂商 ID(Espressif 的 USB VID)
TUSB_PID0x8000产品 ID
TUSB_MANUFACTUREREspressif厂商字符串
TUSB_PRODUCTESP UVC Device产品字符串
TUSB_SERIAL_NUM12345678序列号字符串

这些值会写入 USB 设备/字符串描述符(见 usb_descriptors.c),决定主机设备管理器中显示的名称。

传输模式与 USB 速度

配置项默认值说明
UVC_SUPPORT_TWO_CAMn是否支持双摄像头(Cam2 全部配置依赖此项)
TINYUSB_RHPORTHS(P4/S31)/ FS(其余)USB PHY 控制器速度:HS 对应高速(USB OTG 2.0 PHY),仅 ESP32-P4 / ESP32-S31 支持
UVC_CAM1_XFER_MODE/UVC_CAM2_XFER_MODEIsochronousCam1/Cam2 的传输模式(Isochronous 或 Bulk)

传输模式的选择有实际性能与兼容性影响。以仓库示例 examples/usb/device/usb_webcam/README.md 给出的实测参考:Isochronous 模式吞吐约 512KB/s、兼容 Windows/Linux/macOS;Bulk 模式吞吐约 1216KB/s、兼容 Windows/macOS,但部分 Linux 平台可能遇到兼容性问题。此外 README.md 特别提示:若开启UVC_SUPPORT_TWO_CAM且需要多摄像头切换,请将两个摄像头的传输模式都设为 Isochronous。

分辨率、帧率与多帧列表

Cam1 的默认分辨率(UVC_CAM1_FRAMESIZE)与配套的默认帧率、宽高如下:

分辨率选项宽×高默认帧率
QVGA320×24030 fps
HVGA480×32030 fps
VGA640×48015 fps
SVGA800×60015 fps
HD(默认)1280×72015 fps
FHD1920×108015 fps

Cam2(UVC_CAM2_*)拥有完全相同的选项结构,仅默认分辨率为 HD。另有两个重要开关:

  • UVC_CAM1_MULTI_FRAMESIZE/UVC_CAM2_MULTI_FRAMESIZE(默认y):启用后向主机暴露额外的多帧尺寸列表;
  • UVC_MULTI_FRAME_CONFIG菜单下的FRAME_SIZE_1/2/3:自定义三个附加帧尺寸,默认分别为 640×480@15、480×320@30、320×240@30。

这些帧配置最终汇聚到 uvc_frame_config.h 中的UVC_FRAMES_INFO[][4]静态表(每路摄像头一张 4 项的表:1 个默认帧 + 3 个附加帧),USB 描述符会依据该表向主机声明可协商的帧格式。示例日志中打印的Frame(1)=1280*720@15fpsFrame(2)=640*480@15fpsFrame(3)=480*320@30fps正是这张表的实际内容(见 usb_webcam_main.c)。

任务优先级与核绑定

配置项默认值范围说明
UVC_TINYUSB_TASK_PRIORITY51–15TinyUSB 任务优先级
UVC_TINYUSB_TASK_CORE-1-1–1TinyUSB 任务绑核(-1 为不绑定)
UVC_CAM1_TASK_PRIORITY41–15Cam1 视频任务优先级
UVC_CAM1_TASK_CORE-1-1–1Cam1 视频任务绑核
UVC_CAM2_TASK_PRIORITY/UVC_CAM2_TASK_CORE4 / -1同上Cam2 任务配置(依赖双摄像头开关)

在多核芯片上,将 TinyUSB 任务与视频任务分配到不同核心有助于避免帧传输抖动。

双摄像头模式

CONFIG_UVC_SUPPORT_TWO_CAM开启后,组件通过CONFIG_UVC_CAM_NUM = 2扩展内部状态,为两路摄像头各维护一份uvc_device_config_t、格式、帧间隔与视频任务(video_task/video_task2,见 usb_device_uvc.c)。此时需要分别调用两次uvc_device_config()

uvc_device_config(0, &config_cam1); uvc_device_config(1, &config_cam2); uvc_device_init();

uvc_device_init()会校验两路都已完成配置,否则返回ESP_ERR_INVALID_STATE。USB 描述符中也会相应出现两组 Video Control / Video Streaming 接口(字符串描述符中的 "UVC CAM1" / "UVC CAM2"),主机可同时打开两个摄像头设备。双摄像头实现在 examples/usb/device/usb_dual_uvc_device 中有完整参考。

运行示例:将 ESP32-S3-EYE 变成 USB 摄像头

仓库提供了可直接运行的示例 examples/usb/device/usb_webcam,它把 ESP32-S3-EYE 开发板包装成一个标准的 USB WebCam 设备(仅支持 MJPEG 格式,IDF v5.0 及以上版本支持 LCD 动画显示)。

硬件要求

  • 任意带摄像头与 USB 接口的 ESP32-S2 / ESP32-S3 开发板(示例默认 ESP32-S3-EYE,也可通过 menuconfig 中USB WebCam config → Camera Pin Configuration → Select Camera Pinout切换板型,或选择Custom Camera Pinout逐引脚配置);
  • USB 连接:GPIO19 接 D-,GPIO20 接 D+;
  • 摄像头需支持 JPEG 硬件压缩(如 OV2640、OV3660 等,可参考 esp32-camera 支持列表)。

构建与烧录

. $HOME/esp/esp-idf/export.sh # 设置 ESP-IDF 环境变量 idf.py set-target esp32s3 # 或 esp32s2 idf.py build flash monitor

示例运行后会打印格式/帧列表,并在主机打开设备时输出bFrameIndexdwFrameInterval与相机初始化日志(参考 usb_webcam/README.md 的 Example Output)。此时在 PC 的摄像头应用(如 OBS、浏览器 WebRTC 或系统相机)中即可看到 ESP32 的画面。

示例中的关键实现

示例的四个回调展示了与esp_camera组件的标准配合方式(见 usb_webcam_main.c):

  • camera_start_cb根据主机请求的宽高映射到esp_cameraframesize_t与 JPEG 质量参数,再调用camera_init()初始化传感器;初始化完成后还会设置vflip等图像方向参数;
  • camera_fb_get_cb通过esp_camera_fb_get()获取相机帧,将其包装为uvc_fb_t返回,并检查帧长是否超过UVC_MAX_FRAMESIZE_SIZE(S3 上为 75KB);
  • camera_fb_return_cb在组件拷贝完数据后调用esp_camera_fb_return()归还帧缓冲;
  • camera_stop_cb在主机关闭设备时收到通知(示例中用于关闭 S3-EYE 的 LCD 眼睛动画)。

组件级测试验证

组件自带测试用例 test_apps/main/usb_device_uvc_test.c,它不使用真实摄像头,而是把内嵌在固件中的 JPEG 图片(esp_1280_720.jpg,通过_binary_esp_1280_720_jpg_start符号引用)作为帧来源,在camera_fb_get_cb中按当前配置的宽高填充uvc_fb_t并返回。该测试同样走完整的uvc_device_configuvc_device_init流程,验证了组件"数据源无关"的设计——只要回调能提供合法的 JPEG/H264 帧,任何来源都可以成为 UVC 设备,这对调试与 CI 自动化验证非常有价值。

常见问题与注意事项

  • 帧缓冲尺寸uvc_buffer_size必须大于最大一帧的字节数。示例中 S3 使用 75KB、S2 使用 60KB(见 usb_webcam_main.c)。帧过大时组件会打印frame size is too big, dropping frame并丢弃该帧;
  • 内存分配:传输缓冲区建议通过heap_caps_malloc/malloc从可用堆分配;真实相机示例将帧缓冲放在 PSRAM(CAMERA_FB_IN_PSRAM),以缓解片内内存压力;
  • Bulk 模式的 Linux 兼容性:部分 Linux 平台对 Bulk 模式的 UVC 摄像头支持不完善,若无法枚举请切回 Isochronous 模式;
  • 双摄像头切换:启用UVC_SUPPORT_TWO_CAM后如需要多摄像头切换,两个摄像头的传输模式都应保持 Isochronous;
  • 回调必须完整uvc_device_config会校验四个回调与缓冲区均非空,缺一不可;
  • deinit 语义:早期文档说明基于 TinyUSB 的驱动不提供deinit;当前源码已实现uvc_device_deinit(),可完成任务回收、tusb_teardown()与 PHY 释放,具体以所用版本头文件声明为准。

【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 8:36:58

BrewUI:给Homebrew装上可视化仪表盘,让命令行包管理更直观

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 8:36:30

ROS暑期学校与AI融合:系统学习路径与实战避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 8:36:28

开放式Code Review实践:流程、工具与踩坑全记录

这两年我在团队里一直在推一件事:把code review从"合并前的必要关卡"变成"团队知识流动的主干道"。折腾了一圈工具和流程之后,我觉得真正值得沉淀下来的不是某个插件或脚本,而是"开放式评审"这一整套思路。本文…

作者头像 李华
网站建设 2026/9/20 8:36:00

大模型API成本全解析:一块钱能买多少Token?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 8:35:36

Atlas 300V Pro 24G推理卡实战:YOLO模型部署全解析

先说一个很多人在选型时都会困惑的问题:Atlas 300V 24G到底算不算“运算加速卡”?我去年接了一个边缘视频分析项目,要在机房部署几十路摄像头的实时目标检测,客户指定了Atlas 300V Pro 24G跑YOLO系列模型,一开始团队里…

作者头像 李华
网站建设 2026/9/20 8:35:27

Windows PowerShell 5.1升级到7.x完整指南:安装配置与脚本迁移实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华