ESP32-S2/S3 USB Host 多媒体流驱动 usb_stream 使用指南:UVC 摄像头与 UAC 音频的读、写与控制
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
导读
usb_stream是 ESP-IoT-Solution 仓库中基于 ESP32-S2/ESP32-S3 USB Host 能力的 UVC + UAC 主机驱动组件,用于从 USB 外设读取、写入和控制多媒体数据流。它同时支持一路 UVC 摄像头视频流、一路 UAC 麦克风输入流和一路 UAC 扬声器输出流。阅读本文后,你将掌握该组件的硬件选型要求、UVC/UAC 配置参数含义、完整的初始化与启动调用流程、流控(挂起/恢复、音量/静音)用法,以及 ESP32-S2 ECO0 芯片上的 SPI 屏幕抖动问题与软件规避方案。
组件概述与核心特性
usb_stream是一个位于 components/usb/usb_stream 的独立组件,通过 ESP32-S2/ESP32-S3 的 USB-OTG 外设以主机(Host)身份驱动外接 USB 设备。它面向三类多媒体设备:
- UVC(USB Video Class)摄像头:通过 UVC Stream 接口获取 MJPEG 视频流,同时支持等时(Isochronous)和批量(Bulk)两种传输模式;
- UAC(USB Audio Class)麦克风:通过 UAC Stream 接口接收音频采样数据(IN 流);
- UAC 扬声器:通过 UAC Stream 接口发送音频数据(OUT 流),并可通过 UAC Control 接口控制音量、静音等特性。
从组件源码看,其能力还包括:
- 自动解析设备的配置描述符(Configuration Descriptor),按用户参数自动匹配接口、端点与格式;
- 支持 UVC/UAC 各路数据流分别挂起(suspend)与恢复(resume);
- 提供描述符打印、USB 枚举失败重试、任务优先级/核心/栈大小可配置(见 components/usb/usb_stream/Kconfig);
- 内部采用 FreeRTOS 任务 + Ringbuffer + URB 队列的流水线架构(见 usb_stream.c)。
注意:按 README.md 的说明,对于 ESP-IDF V5.3.3 及以上版本,如果启用 UVC,建议同时开启Component config → USB-OTG → Hardware FIFO size biasing中的Bias IN选项,以确保等时传输 FIFO 容量足够。
硬件与设备选型要求
开发板
任何带有 USB Host 口的 ESP32-S2 / ESP32-S3 开发板均可使用,但该 USB 口必须能够向外输出 5V 电压,因为绝大多数 USB 摄像头、USB 耳机、USB 声卡需要总线供电。
UVC 摄像头要求
- 摄像头必须兼容USB 1.1 Full-Speed(全速)模式;
- 摄像头必须自带MJPEG硬件压缩输出(ESP32 侧不进行 JPEG 编码);
- 用户可以通过
uvc_streaming_config手动指定摄像头接口号、传输模式(等时/批量)和图像帧参数; - 等时传输模式:接口的最大包大小(Max Packet Size, MPS)应不超过 512 字节;图像数据流 USB 传输总带宽应小于4 Mbps(500 KB/s);
- 批量传输模式:图像数据流 USB 传输总带宽应小于8.8 Mbps(1100 KB/s),批量模式可支持更高的带宽上限;
- 其它特殊摄像头的兼容性要求,请参考示例程序的 README。
UAC 设备要求
- 音频功能必须兼容UAC 1.0 协议(Audio Class 1.0,即全速等时音频);
- 用户需要通过
uac_streaming_config手动指定扬声器/麦克风的采样率、位宽等参数。
UVC 与 UAC 同时使用
- UVC 和 UAC 功能可以单独启用:例如只配置 UAC 驱动一个 USB 耳机/音箱,或只配置 UVC 驱动一个 USB 摄像头;
- 如需同时启用 UVC + UAC,当前驱动仅支持同时含有摄像头与音频接口的复合设备(Composite Device),不支持分别连接两个独立设备(一台摄像头 + 一台声卡)的组合。
快速接入与示例工程
通过组件管理器添加依赖
在任意 ESP-IDF 工程中使用组件管理器添加依赖,CMake 阶段会自动下载该组件:
idf.py add-dependency "espressif/usb_stream=*"从示例模板创建工程
组件注册表提供了三个官方示例(与本仓库 examples/usb/host 下的工程对应),可用以下命令从模板创建:
# USB 摄像头 + 麦克风 + 扬声器(USB Camera + Audio,含 Web 控制台) idf.py create-project-from-example "espressif/usb_stream=*:usb_camera_mic_spk" # USB 摄像头本地 LCD 显示 idf.py create-project-from-example "espressif/usb_stream=*:usb_camera_lcd_display" # USB 音频播放器 idf.py create-project-from-example "espressif/usb_stream=*:usb_audio_player"示例下载到当前目录后,即可进入目录编译烧录。若执行create-project-from-example时报CMakeLists.txt not found in project directory错误,说明 idf-component-manager 版本过旧,请先在 ESP-IDF 环境中执行pip install -U idf-component-manager升级。
示例工程结构(以 usb_camera_mic_spk 为例)
仓库内的 examples/usb/host/usb_camera_mic_spk 是一个浏览器 Web 控制的 AV 演示工程,其main目录划分清晰:
main.c:应用入口;app_usb_host.c/.h:初始化 USB Host,并通过fifo_settings_custom自定义 DWC 硬件 FIFO 分配(解决高采样率 UAC 端点 MPS 超限问题);app_uvc_manager.c/.h:UVC 摄像头配置、分辨率选择与预览;app_uac_manager.c/.h:UAC 麦克风/扬声器格式选择、静音、音量控制;app_web.c/.h、app_wifi.c/.h:SoftAP(默认 SSIDUSB-AV-DEMO)与 Web 控制台。
编译命令为:
idf.py set-target TARGET # esp32s2 / esp32s3 / esp32p4 等 idf.py -p PORT flash monitor提示:在该示例的 README 中说明,若日志出现
EP MPS exceeds supported limit(端点 MPS 超过当前 FIFO 配置上限导致ESP_ERR_NOT_SUPPORTED),需要针对目标设备调整fifo_settings_custom或选择较低带宽的 UAC 格式。
配置参数详解
UVC 配置结构 uvc_config_t
uvc_config_t uvc_config = { .frame_width = 320, // mjpeg 图像宽度像素,例如 320 .frame_height = 240, // mjpeg 图像高度像素,例如 240 .frame_interval = FPS2INTERVAL(15), // 帧间隔(100µs 单位),例如 15fps .xfer_buffer_size = 32 * 1024, // 单帧图像大小,需按实际测试确定,320*240 一般小于 35KB .xfer_buffer_a = pointer_buffer_a, // USB 传输内部缓冲区 A .xfer_buffer_b = pointer_buffer_b, // USB 传输内部缓冲区 B(双缓冲) .frame_buffer_size = 32 * 1024, // 单帧图像缓冲大小,需按实际测试确定 .frame_buffer = pointer_frame_buffer, // 图像帧缓冲 .frame_cb = &camera_frame_cb, // 摄像头回调,可在其中阻塞 .frame_cb_arg = NULL, // 摄像头回调参数 };各字段含义与约束(依据 components/usb/usb_stream/include/usb_stream.h):
| 字段 | 含义 | 说明 |
|---|---|---|
frame_width/frame_height | 期望图像宽高(像素) | 可设为FRAME_RESOLUTION_ANY(__UINT16_MAX__)匹配任意分辨率 |
frame_interval | 帧间隔(100ns 单位) | 用宏FPS2INTERVAL(fps)转换,如FPS2INTERVAL(15)表示 15fps;源码同时提供FRAME_INTERVAL_FPS_5/10/15/20/30等预设宏 |
xfer_buffer_size | 单块传输缓冲大小 | 必须大于一帧图像大小;内部使用双缓冲(A/B)交替接收 |
xfer_buffer_a/b | 传输缓冲区指针 | 建议使用MALLOC_CAP_DMA内存(参考测试代码heap_caps_malloc(..., MALLOC_CAP_DMA)) |
frame_buffer_size/frame_buffer | 帧缓冲大小与指针 | 用于拼装完整 MJPEG 帧 |
frame_cb/frame_cb_arg | 帧回调与参数 | 新帧就绪后触发,回调运行在独立任务上下文,允许阻塞(如解码、送显) |
xfer_type(可选) | 传输模式 | UVC_XFER_ISOC(等时)或UVC_XFER_BULK(批量),多数摄像头为等时模式,批量模式带宽更高 |
format_index/frame_index(可选) | 格式索引 / 帧索引 | 用于跳过描述符解析直接指定 |
interface/interface_alt(可选) | 流接口号 / 备用接口号 | 备用接口用于选择 MPS,批量模式固定为 0 |
ep_addr/ep_mps(可选) | 端点地址 / 端点 MPS | 跳过描述符解析时手动指定 |
flags(可选) | 行为控制标志 | 支持FLAG_UVC_SUSPEND_AFTER_START(启动后立即挂起 UVC)等 |
从 test_apps/main/test_usb_stream.c 可以看到,传输缓冲区按目标芯片区分大小:ESP32-S2 为 45KB、ESP32-S3 为 55KB,且要求xfer_buffer_size >= frame_buffer_size。
UAC 配置结构 uac_config_t
uac_config_t uac_config = { .mic_bit_resolution = 16, // 麦克风采样位宽,bit .mic_samples_frequence = 16000, // 麦克风采样率,Hz .spk_bit_resolution = 16, // 扬声器采样位宽,bit .spk_samples_frequence = 16000, // 扬声器采样率,Hz .spk_buf_size = 16000, // 扬声器发送缓冲大小,需为 spk_ep_mps 的整数倍 .mic_buf_size = 0, // 麦克风接收缓冲大小,不使用为 0;否则需为 mic_min_bytes 的整数倍 .mic_cb = &mic_frame_cb, // 麦克风回调,禁止阻塞! .mic_cb_arg = NULL, // 麦克风回调参数 };关键字段补充说明(同样以头文件为准):
spk_ch_num/mic_ch_num:扬声器/麦克风通道数,可设UAC_CH_ANY(0)匹配任意通道数;mic_bit_resolution/spk_bit_resolution:位宽,可设UAC_BITS_ANY(__UINT16_MAX__)匹配任意位宽;mic_samples_frequence/spk_samples_frequence:采样率,可设UAC_FREQUENCY_ANY(__UINT32_MAX__)匹配任意采样率;spk_buf_size:扬声器发送 Ringbuffer 大小,必须为 spk 端点 MPS 的整数倍;mic_buf_size:麦克风接收缓冲大小,0 表示不使用(配合uac_mic_streaming_read轮询),否则需为mic_min_bytes的整数倍;mic_cb:麦克风数据回调,一定不能阻塞,否则影响后续帧的接收;- 可选项(
mic_interface/mic_ep_addr/mic_ep_mps、spk_interface/spk_ep_addr/spk_ep_mps、ac_interface、mic_fu_id/spk_fu_id):手动指定接口、端点与特性单元(Feature Unit)ID,用于跳过描述符解析以加快启动速度。
编程流程:配置 → 启动 → 回调 → 控制 → 停止
完整的使用流程如下(对应 usb_stream.h 的公开 API):
1. 配置驱动
- 调用
uvc_streaming_config(&uvc_config)配置 UVC 驱动(设备同时支持音频时); - 调用
uac_streaming_config(&uac_config)配置 UAC 驱动。
普通使用场景只需指定上述"必选"参数,可选项置 0,驱动会从设备描述符中自动查找正确值;如需快速启动(Quick Start),则需手动指定全部参数以跳过获取与解析描述符的步骤。两个接口在数据流已运行时返回ESP_ERR_INVALID_STATE,参数非法返回ESP_ERR_INVALID_ARG。
2. 启动数据流
esp_err_t usb_streaming_start(void);调用后驱动会创建内部任务处理 USB 数据,并响应设备连接与协议协商。返回ESP_ERR_INVALID_STATE(未配置或已在运行)、ESP_FAIL(启动失败)或ESP_OK。
可选地,可在启动前注册设备连接状态回调:
esp_err_t usb_streaming_state_register(state_callback_t cb, void *user_ptr);回调在STREAM_CONNECTED/STREAM_DISCONNECTED时被调用(仅支持注册一个回调,后注册的覆盖先前的;需在启动前注册)。此外usb_streaming_connect_wait(timeout_ms)可阻塞等待设备连接。
3. 描述符匹配与回调触发
启动后,主机根据用户参数匹配已连接设备的描述符;设备不满足配置要求时,驱动会打印警告。匹配成功后,主机持续接收 IN 流(UVC 视频与 UAC 麦克风):
- UVC 帧回调:每收到一帧完整 MJPEG 图像触发一次。回调运行在独立任务上下文(
sample_proc),可以阻塞,例如在回调中执行 JPEG 解码或写入显示缓冲。测试代码中回调会打印frame_format、sequence、width、height、data_bytes等信息; - UAC 麦克风回调:收到
mic_min_bytes字节数据后触发,禁止阻塞,否则会影响下一帧接收;如需阻塞处理,应改用uac_mic_streaming_read轮询模式替代回调模式。
回调中的实际数据结构(见头文件):mic_frame_t携带data、data_bytes、bit_resolution、samples_frequence;UVC 回调帧包含frame_format(MJPEG 等)、sequence、width、height、data_bytes。
4. 发送扬声器数据(OUT 流)
esp_err_t uac_spk_streaming_write(void *data, size_t data_bytes, size_t timeout_ms);用户将音频数据写入内部 Ringbuffer,主机在 USB 空闲时取出数据发送 OUT 流。Ringbuffer 满时返回ESP_ERR_TIMEOUT。
5. 流控与音频控制
esp_err_t usb_streaming_control(usb_stream_t stream, stream_ctrl_t ctrl_type, void *ctrl_value);stream:STREAM_UVC、STREAM_UAC_SPK、STREAM_UAC_MIC;ctrl_type:CTRL_SUSPEND/CTRL_RESUME(挂起/恢复,ctrl_value为 NULL)、CTRL_UAC_MUTE(静音,ctrl_value为 false/true)、CTRL_UAC_VOLUME(音量,ctrl_value为 0~100);- 若当前设备不支持该控制类型,返回
ESP_ERR_NOT_SUPPORTED。
音量/静音通过 UAC Control 接口的特性单元(Feature Unit)实现,源码中定义了UAC_FU_MUTE_CONTROL、UAC_FU_VOLUME_CONTROL等控制选择子,并将音量范围映射到 0~100(见 usb_stream.c 中的UAC_SPK_VOLUME_MAX/MIN/STEP)。
在测试用例 test_usb_stream.c 的状态回调中可以看到典型组合用法:设备连接后先查询分辨率列表,再依次恢复 UVC/扬声器/麦克风流,然后设置扬声器和麦克风的静音与音量(CTRL_UAC_VOLUME传 30)。
6. 动态调整参数
uvc_frame_size_list_get/uac_frame_size_list_get:查询当前已连接设备的支持分辨率/音频格式列表(传 NULL 仅获取列表大小);uvc_frame_size_reset(width, height, interval)/uac_frame_size_reset(stream, ch_num, bit_resolution, samples_frequence):在挂起状态下修改参数,恢复后生效。
7. 停止数据流
esp_err_t usb_streaming_stop(void);停止后内部任务被删除,USB 资源(URB、管道、事件队列等)被完全释放,返回ESP_ERR_TIMEOUT表示停止等待超时。
Kconfig 可调参数
组件提供丰富的 menuconfig 选项(Component config → USB Stream,见 Kconfig),常用项如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
CTRL_TRANSFER_DATA_MAX_BYTES | 1024 | 控制传输最大数据长度(64~2048 字节) |
USB_STREAM_QUICK_START | n | 快速启动模式,跳过描述符解析 |
UVC_PRINT_DESC/UVC_PRINT_DESC_VERBOSE | y / n | 枚举时打印描述符信息 / 详细模式 |
USB_PROC_TASK_PRIORITY/CORE/STACK_SIZE | 5 / 按芯片 / 3072 | USB 处理任务优先级(1~25)、核与栈大小 |
USB_WAITING_AFTER_CONN_MS | 50 | 设备连接后的枚举延时(ms) |
USB_CTRL_XFER_TIMEOUT_MS | 1000 | 控制传输超时(ms) |
USB_ENUM_FAILED_RETRY/COUNT/DELAY_MS | y / 10 / 200 | 枚举失败重试开关、次数与间隔 |
SAMPLE_PROC_TASK_PRIORITY/CORE/STACK_SIZE | 2 / 0 / 3072 | UVC 帧处理任务配置(CORE 为 -1 时不绑定核) |
UVC_CHECK_HEADER_EOF | y | 校验 payload 头 EOF 位,确认整帧接收完成 |
UVC_DROP_NO_EOF_FRAME/UVC_DROP_OVERFLOW_FRAME | n / y | 丢弃无 EOF 帧 / 溢出帧 |
NUM_BULK_STREAM_URBS/NUM_BULK_BYTES_PER_URB | 2 / 2048 | 批量模式 URB 数量与单次传输字节数 |
NUM_ISOC_UVC_URBS/NUM_PACKETS_PER_URB | 3 / 4 | 等时模式 UVC URB 数量与每 URB 包数 |
NUM_ISOC_SPK_URBS/NUM_ISOC_MIC_URBS | 3 / 3 | 扬声器/麦克风等时 URB 数量 |
UAC_MIC_CB_MIN_MS_DEFAULT | 16 | 麦克风回调最小间隔(ms,1~32) |
UAC_SPK_ST_MAX_MS_DEFAULT | 16 | 扬声器单次最大发送时长(ms,1~32) |
UAC_MIC_PACKET_COMPENSATION | n | 麦克风丢包时补数据 |
UAC_SPK_PACKET_COMPENSATION/CONTINUOUS/TIMEOUT_MS/SIZE_MS | y / y / 80 / 10 | 扬声器缓冲空时补零策略、持续补零、超时与补零时长 |
已知问题与规避方案:ESP32-S2 ECO0 SPI 屏幕抖动
在最早版本的 ESP32-S2(ECO0)芯片上,USB 传输可能污染 SPI 数据,导致 SPI 屏幕与 USB 摄像头同时工作时出现画面抖动。ESP32-S2 新版本(>=ECO1)以及 ESP32-S3 均不存在该 Bug。
软件规避方案如下(修改 IDF 内部文件,属于芯片级 workaround):
- 在
components/hal/esp32s2/include/hal/spi_ll.h中新增查询发送 FIFO 计数函数:
static inline uint32_t spi_ll_tx_get_fifo_cnt(spi_dev_t *hw) { return hw->dma_out_status.out_fifo_cnt; }- 修改
spi_new_trans实现,在真正 kick off 传输前,等待数据确实已写入发送 FIFO,再启动传输:
// 该函数用于发送新事务,运行在 ISR 或任务中。 // 配置事务相关的寄存器和 DMA(或非 DMA 的 FIFO)使用的链表。 static void SPI_MASTER_ISR_ATTR spi_new_trans(spi_device_t *dev, spi_trans_priv_t *trans_buf) { //................... spi_hal_setup_trans(hal, hal_dev, &hal_trans); spi_hal_prepare_data(hal, hal_dev, &hal_trans); //Call pre-transmission callback, if any if (dev->cfg.pre_cb) dev->cfg.pre_cb(trans); #if 1 //USB Bug workaround while (trans->length && spi_ll_tx_get_fifo_cnt(SPI_LL_GET_HW(host->id)) == 0) { __asm__ __volatile__("nop"); __asm__ __volatile__("nop"); __asm__ __volatile__("nop"); } #endif //Kick off transfer spi_hal_user_start(hal); }其原理是:在 USB 污染发生时,发送 FIFO 计数可能短暂为 0,此时忙等几个 NOP 周期让 FIFO 数据稳定后再启动 DMA 传输,从而避免 SPI 数据被 USB 传输干扰。
总结
usb_stream为 ESP32-S2/ESP32-S3 提供了一套完整的多媒体 USB Host 解决方案:一条 UVC 摄像头流 + 一条 UAC 麦克风流 + 一条 UAC 扬声器流可同时工作,支持等时/批量传输、描述符自动解析、分路挂起/恢复、音量与静音控制,并有配套的 Web 控制台示例、LCD 显示示例与音频播放示例。开发者只需配置uvc_config_t/uac_config_t结构体,按"配置 → 启动 → 回调处理 → 控制 → 停止"的流程调用 API,即可快速接入 USB 摄像头与音频设备;若使用早期 ESP32-S2 ECO0 芯片并配合 SPI 屏幕,可参考上文给出的软件规避补丁。
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考