视频主机方案
概述
USB 视频类(USB Video Class,UVC)协议定义了通过 USB 接口传输视频数据的工业标准。在主机(Host)模式下,Ameba 平台能够通过该协议识别并驱动外部 USB 摄像头设备。
Ameba 平台的 UVC 主机协议栈专注于 视频采集(Capture) 场景。它基于 USB Video Class(UVC)1.1 规范实现,兼容 UVC 1.5 规范,将外接的 USB 摄像头抽象为系统本地视频输入接口。该方案支持即插即用,能够与系统内置视频处理框架无缝对接,为设备提供便捷、灵活的视觉感知扩展能力。
特性
Ameba UVC Host 驱动旨在提供高效、兼容的视频输入能力,主要特性如下:
广泛的设备兼容性:支持符合 UVC 标准的 USB 摄像头设备(如 USB 网络摄像头、工业相机、USB 显微镜)。
自动化枚举配置:自动解析设备描述符,识别视频流接口与控制接口,并建立等时(Isochronous)传输通道。
主流视频格式支持:
压缩格式:MJPEG、H.264
非压缩格式:符合 UVC 规范的未压缩 YUV 视频格式(具体像素格式如 YUY2 等由摄像头的 UVC 描述符决定)
系统级深度集成:向上层应用暴露统一的 API 接口,屏蔽底层 USB 传输细节。
热插拔支持(Hot-Plug):支持 USB 外设的即插即用与动态移除,无需重启系统。
应用场景
作为 USB 主机,Ameba 负责枚举 USB 摄像头、解析视频描述符,并建立稳定的图像数据流。该方案适用于对视觉采集有需求,同时追求低功耗与快速集成的嵌入式应用,例如:
智能监控与安防:Ameba 通过 USB 摄像头实时采集画面,结合网络传输或本地存储,用于家庭监控、门铃猫眼或工业现场监视。
视觉识别终端:作为边缘计算节点的前端采集设备,获取图像数据并传递给后续的 AI 算法进行人脸识别、二维码扫描或物体检测。
视频通话设备:结合 Wi-Fi 或蜂窝网络模组,通过通用 USB 摄像头采集用户视频流,实现低成本的 VoIP 视频对讲功能。
协议简介
UVC (USB Video Class) 协议定义了在 USB 规范框架下,实现主机与视频捕获设备之间 控制管理 与 视频流传输 的标准接口。主机驱动通过此协议建立视频数据通道,实现摄像头的实时预览、录制及参数调节。
协议文档
USB-IF 官方发布了 UVC 类基础协议及多种 Payload 格式规范。开发过程中请参考以下核心文档:
规范类型 |
文档 |
|---|---|
UVC 1.1 (视频类基础协议) |
|
UVC 1.5 (视频类基础协议) |
|
Payload Specs (负载格式) |
包含在上述压缩包的 USB_Video_Payload_*.pdf 文件中。 |
术语定义
本文档涉及的通用 UVC (USB Video Class) 技术术语定义如下:
术语 |
描述 |
|---|---|
VC Interface (Video Control Interface) |
视频控制接口。作为 UVC 设备的核心控制中心,负责管理视频设备的拓扑结构(如 Unit 和 Terminal 的连接关系)。主机通过该接口向设备发送控制请求,例如调整亮度、对比度或进行云台控制(Pan/Tilt/Zoom)。 |
VS Interface (Video Streaming Interface) |
视频流接口。负责视频负载数据的实际传输,通常使用等时(Isochronous)或批量(Bulk)管道。每个 VS 接口包含具体的视频格式信息(如 YUV, MJPEG, H.264)以及相关的帧描述符。 |
Input Terminal (IT) |
输入终端。视频数据流进入 UVC 功能拓扑的入口点。常见的输入终端包括摄像头传感器(Camera Sensor)或复合视频输入接口。它代表了数据的物理源头。 |
Output Terminal (OT) |
输出终端。视频数据流离开 UVC 功能拓扑的出口点。最常见的输出终端是 USB Streaming Terminal,表示数据将通过 USB 总线发送给主机。 |
Processing Unit (PU) |
处理单元。位于输入终端之后的处理节点,用于对视频图像本身进行调整。它提供了对图像质量的控制能力,例如亮度(Brightness)、对比度(Contrast)、色调(Hue)、饱和度(Saturation)和清晰度(Sharpness)。 |
Extension Unit (XU) |
扩展单元。UVC 规范允许厂商自定义的功能模块。通过 XU,厂商可以定义标准 UVC 规范之外的特定控制指令,并在主机端通过配套驱动或应用进行访问。 |
Probe & Commit Control |
协商与提交控制。这是视频流建立过程中的关键机制。主机先发送 "Probe" 请求查询设备支持的带宽和参数,协商一致后发送 "Commit" 请求锁定配置,随后才能开启视频流传输。 |
Payload Header |
负载头。UVC 视频流数据包中的头部信息,包含了帧翻转(Frame ID)、时间戳(PTS/SCR)以及错误标志位等关键同步信息。 |
协议框架
UVC Host 协议栈采用分层架构设计,旨在实现 USB 传输层与上层视频应用或多媒体框架的解耦。
组件职责
应用层 (Application)
位于架构最顶层,负责具体的业务逻辑处理。包括视频预览、录制应用,或基于视频流的 AI 算法与网络推流服务。
视频中间件层 (Video Middleware)
起到承上启下的抽象作用。向上为应用层提供统一的数据获取接口,屏蔽底层差异;负责视频流的编解码处理、格式转换及缓冲队列管理。
UVC 类驱动 (Class Driver)
核心中间层,实现了 UVC 规范定义的行为:
拓扑解析:解析视频控制接口 (VC) 的内部拓扑(Unit/Terminal)以及视频流接口 (VS) 的格式描述符。
流协商:实现 Probe 和 Commit 流程,协商分辨率、帧率及带宽等参数。
帧重组与提交:解析 UVC 负载头 (Payload Header),处理帧起始/结束标识 (FID/EOF),将分散的 USB 包重组为完整视频帧,并以帧为单位提交给中间层。
USB Core & HCD (Host Controller Driver)
底层驱动,负责处理 USB 标准枚举、同步管道管理以及底层的物理数据传输调度。
通信机制
标准的 UVC 设备通过 接口关联描述符 (IAD) 聚合以下接口:
视频控制接口 (VC Interface) - 拓扑控制
传输机制:基于默认管道 0 (EP0) 的控制传输 (Control Transfer)。
核心职能:发送类特定请求以控制 Unit/Terminal 属性,通过 Probe (探测) 和 Commit (提交) 流程完成参数协商。
视频流接口 (VS Interface) - 数据管道
传输机制:采用同步传输 (Isochronous) 模式承载高带宽视频流。
特性:保障带宽与低延迟(无重传),适用于实时视频预览。
描述符结构
UVC 设备除遵循标准的 USB 描述符(如设备描述符、配置描述符、管道描述符)外,还定义了类特定描述符 (Class-Specific Descriptors)。
这些描述符依据其所属接口,分为 类特定视频控制接口描述符(VC) 和 类特定视频流接口描述符(VS)。
Descriptor Topology
Device Descriptor
└── Identifies basic device information (USB Version 2.00, Composite Device)
Configuration Descriptor
├── Contains total length, power supply (500mA), etc.
│
├── Interface Association Descriptor (IAD)
│ └── Groups Interface 0 (VC) and Interface 1 (VS) as a Video Function
│
├── Video Control (VC) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (AlternateSetting 0, Video Control Class)
│ ├── Class-Specific VC Header (declares UVC version, clock frequency)
│ ├── Class-Specific Descriptor Collection (Topology)
│ │ ├── Input Terminal (Camera)
│ │ ├── Processing Unit
│ │ ├── Extension Unit (Vendor Specific Controls)
│ │ └── Output Terminal (USB Streaming)
│ └── Standard Endpoint Descriptor (Interrupt IN for Status)
│
└── Video Streaming (VS) Interface Descriptor (Interface 1)
├── Alternate Setting 0: Control transfer active state (negotiation only, no data endpoint)
│ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│ ├── Class-Specific VS Header
│ ├── Format Descriptor
│ │ └── Frame Descriptor (various resolutions and frame rates)
│ ├── Format Descriptor
│ │ └── Frame Descriptor (various resolutions and frame rates)
│ ├── Still Image Frame Descriptor
│ └── Color Matching Descriptor
│
├── Alternate Setting 1: Data transfer active state (with data endpoint)
│ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│ └── Standard Endpoint Descriptor (ISO IN endpoint, e.g., Low Bandwidth)
│
├── Alternate Setting 2
│ ...... Can configure multiple different settings as needed (e.g., Medium to High Bandwidth)
│
└── ...... (Other Alternate Settings for different packet sizes)
Device Qualifier Descriptor
└── Device information while running in another speed mode (e.g., High Speed vs Full Speed capability)
Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode
│
├── Interface Association Descriptor (IAD)
│ └── Groups Interface 0 (VC) and Interface 1 (VS) as a Video Function
│
├── Video Control (VC) Interface Descriptor (Interface 0)
│ ├── Standard Interface Descriptor (AlternateSetting 0, Video Control Class)
│ └── Class-Specific Descriptor Collection (Same Topology as main configuration)
│ ├── Input Terminal
│ ├── Processing Unit
│ ├── Extension Unit
│ └── Output Terminal
│
└── Video Streaming (VS) Interface Descriptor (Interface 1)
├── Alternate Setting 0: Control transfer active state (negotiation only)
│ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│ ├── Class-Specific VS Header
│ ├── Format Descriptor
│ │ └── Frame Descriptor
│ └── Color Matching Descriptor
│
├── Alternate Setting 1: Data transfer active state
│ ├── Standard Interface Descriptor (Streaming Class)
│ └── Standard Endpoint Descriptor (ISO IN endpoint)
│
└── ...... (Other alternate settings typically available in other speed modes)
UVC Vedio Control (VC) Interface
Vedio Control Interface Descriptor
Interface Header Descriptor
├── bLength : 1 byte → Total descriptor length (13 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x01 (VC_HEADER)
├── bcdUVC : 2 bytes → Video Class Specification Release Number (0x0100 = 1.00)
├── wTotalLength : 2 bytes → Total number of bytes for all VC descriptors
├── dwClockFreq : 4 bytes → Clock frequency in Hz (e.g., 0x02DC6C00 = 48 MHz)
├── bInCollection : 1 byte → Number of VideoStreaming interfaces
└── baInterfaceNr(1) : 1 byte → Interface number of the first VideoStreaming interface (0x01)
Input Terminal Descriptor (Camera)
Input Terminal Descriptor
├── bLength : 1 byte → Total descriptor length (18 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x02 (VC_INPUT_TERMINAL)
├── bTerminalID : 1 byte → Unique ID of this Terminal (0x01)
├── wTerminalType : 2 bytes → 0x0201 (ITT_CAMERA)
├── bAssocTerminal : 1 byte → ID of associated Output Terminal (0x00 = None)
├── iTerminal : 1 byte → String descriptor index
├── wObjectiveFocalMin : 2 bytes → Min focal length (0 = not supported)
├── wObjectiveFocalMax : 2 bytes → Max focal length
├── wOcularFocalLength : 2 bytes → Ocular focal length
├── bControlSize : 1 byte → Size of bmControls (3 bytes)
└── bmControls : 3 bytes → Bitmap of supported controls
• D1: Auto-Exposure Mode
• D3: Exposure Time (Absolute)
• D5: Focus (Absolute)
• D9: Zoom (Absolute)
• D11: Pan (Absolute)
• D15: Tilt (Absolute)
Processing Unit Descriptor
Processing Unit Descriptor
├── bLength : 1 byte → Total descriptor length (11 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x05 (VC_PROCESSING_UNIT)
├── bUnitID : 1 byte → Unique ID of this Unit (0x02)
├── bSourceID : 1 byte → ID of the source connected to this unit (0x01 = Camera IT)
├── wMaxMultiplier : 2 bytes → Max digital zoom multiplier
├── bControlSize : 1 byte → Size of bmControls (2 bytes)
└── bmControls : 2 bytes → Bitmap of supported image controls
• D0: Brightness
• D1: Contrast
• D2: Hue
• D3: Saturation
• D4: Sharpness
• D6: White Balance Temperature
Extension Unit Descriptor
Extension Unit Descriptor
├── bLength : 1 byte → Total descriptor length (29 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x06 (VC_EXTENSION_UNIT)
├── bUnitID : 1 byte → Unique ID of this Unit (0x03)
├── guidExtensionCode : 16 bytes→ Vendor-specific GUID (e.g., {0FB885C3-...})
├── bNumControls : 1 byte → Number of controls in this XU (0x05)
├── bNrInPins : 1 byte → Number of input pins (0x01)
├── baSourceID[1] : 1 byte → ID of the source connected (0x02 = PU)
├── bControlSize : 1 byte → Size of bmControls (4 bytes)
└── bmControls : 4 bytes → Bitmap of supported vendor controls
Output Terminal Descriptor
Output Terminal Descriptor
├── bLength : 1 byte → Total descriptor length (9 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x03 (VC_OUTPUT_TERMINAL)
├── bTerminalID : 1 byte → Unique ID of this terminal (0x05)
├── wTerminalType : 2 bytes → 0x0101 (TT_STREAMING)
├── bAssocTerminal : 1 byte → Associated Input Terminal ID
├── bSourceID : 1 byte → ID of the connected Source (0x04 = XU)
└── iTerminal : 1 byte → String descriptor index
UVC Vedio Stream (VS) Interface
Video Stream Interface Descriptor
Class-Specific VS Input Header Descriptor
├── bLength : 1 byte → Total descriptor length (15 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x01 (VS_INPUT_HEADER)
├── bNumFormats : 1 byte → Number of video formats supported (0x02)
├── wTotalLength : 2 bytes → Total length of all VS specific descriptors
├── bEndpointAddress : 1 byte → Address of the ISO IN endpoint (0x81)
├── bmInfo : 1 byte → Capabilities (0x00)
├── bTerminalLink : 1 byte → ID of the Output Terminal in VC interface (0x05)
├── bStillCaptureMethod: 1 byte → Method of still image capture (0x02)
├── bTriggerUsage : 1 byte → Trigger usage (0x00)
├── bControlSize : 1 byte → Size of control field (1 byte)
└── bmaControls(n) : n bytes → Controls for each format
Video Stream Format Type Descriptor
Video Streaming Format Type Descriptor(MJPEG)
├── bLength : 1 byte → Total descriptor length (11 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x06 (VS_FORMAT_MJPEG)
├── bFormatIndex : 1 byte → Index of this format (0x01)
├── bNumFrameDescriptors: 1 byte → Number of frame descriptors (0x09)
├── bmFlags : 1 byte → Characteristics (0x01 = Fixed Sample Size)
├── bDefaultFrameIndex : 1 byte → Default frame index (0x01)
├── bAspectRatioX : 1 byte → X dimension of aspect ratio
├── bAspectRatioY : 1 byte → Y dimension of aspect ratio
├── bmInterlaceFlags : 1 byte → Interlace information (0x00 = Progressive)
└── bCopyProtect : 1 byte → Duplication restrictions (0x00)
Video Streaming Format Type Descriptor(Uncompressed)
├── bLength : 1 byte → Total descriptor length (27 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x04 (VS_FORMAT_UNCOMPRESSED)
├── bFormatIndex : 1 byte → Index of this format (0x02)
├── bNumFrameDescriptors: 1 byte → Number of frame descriptors
├── guidFormat : 16 bytes→ GUID identifying the format (YUY2: {32595559-...})
└── bBitsPerPixel : 1 byte → Bits per pixel (0x10 = 16 bits)
Video Stream Frame Type Descriptor
Video Streaming Frame Type Descriptor (MJPEG)
├── bLength : 1 byte → Total descriptor length (e.g., 34 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x07 (VS_FRAME_MJPEG)
├── bFrameIndex : 1 byte → Index of this frame (0x01)
├── bmCapabilities : 1 byte → Still image support, etc.
├── wWidth : 2 bytes → Frame Width (e.g., 0x0A20 = 2592)
├── wHeight : 2 bytes → Frame Height (e.g., 0x0798 = 1944)
├── dwMinBitRate : 4 bytes → Min Bit Rate (bps)
├── dwMaxBitRate : 4 bytes → Max Bit Rate (bps)
├── dwMaxVideoFrameBuf : 4 bytes → Max Frame Buffer Size (bytes)
├── dwDefaultFrameInterval: 4 bytes→ Default frame interval in 100ns units
├── bFrameIntervalType : 1 byte → 0 = Continuous, Non-0 = Discrete
└── dwFrameInterval(n) : 4×n bytes→ Discrete frame intervals supported (e.g., 33ms for 30fps)
Video Streaming Frame Type Descriptor (Uncompressed)
├── bLength : 1 byte → Total descriptor length (30 bytes)
├── bDescriptorType : 1 byte → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte → 0x05 (VS_FRAME_UNCOMPRESSED)
├── bFrameIndex : 1 byte → Index of this frame (0x01 - Default)
├── bmCapabilities : 1 byte → 0x00 (No specific capabilities set)
├── wWidth : 2 bytes → Frame Width (0x0A20 = 2592)
├── wHeight : 2 bytes → Frame Height (0x0798 = 1944)
├── dwMinBitRate : 4 bytes → Min Bit Rate (approx 25.1 MB/s)
├── dwMaxBitRate : 4 bytes → Max Bit Rate (approx 25.1 MB/s)
├── dwMaxVideoFrameBuf : 4 bytes → Max Frame Buffer Size (0x0099C600)
├── dwDefaultFrameInterval: 4 bytes→ Default interval: 400ms (2.50 fps)
├── bFrameIntervalType : 1 byte → 0x01 (1 Discrete interval supported)
└── dwFrameInterval(1) : 4 bytes → Interval 1: 400ms (2.50 fps)
备注
详细的字段定义请参考 USB-IF 官方 UVC (USB Video Class) 1.5 协议文档。
备注
注意:确认当前速度下的设备能力 (FS vs HS)
请务必注意,Camera 在全速 (Full Speed) 和高速 (High Speed) 模式下往往会呈现不同的描述符结构:
格式支持差异:由于带宽限制,某些非压缩格式(如 YUY2)可能仅在 High Speed 模式下可见。在 Full Speed 下,设备可能仅支持 MJPEG 或极低分辨率的 YUY2。
描述符变化:上述的 VS_FRAME_UNCOMPRESSED 描述符在 Full Speed 连接时可能根本不存在,或者其分辨率和帧率等参数会发生巨大变化。
类特定请求
UVC Host 驱动通过控制管道 0 发送以下请求控制设备行为。这些请求通常针对特定的 Unit ID 或 Interface。
请求名称 (Request) |
要求 |
描述 |
|---|---|---|
SET_CUR |
必选 |
设置当前属性值。用于 Probe/Commit 协商,或控制亮度、对比度等 PU 属性。 |
GET_CUR |
必选 |
获取当前属性值。读取协商后的参数或当前设备状态。 |
GET_MIN / GET_MAX |
可选 |
获取属性的可调节范围。主机驱动利用此范围限制应用层的输入值。 |
GET_RES |
可选 |
获取属性的分辨率(步进值)。 |
GET_DEF |
可选 |
获取属性的默认值。 |
GET_LEN |
可选 |
获取数据长度。 |
流协商流程 (Stream Negotiation)
在正式启动视频流传输 (Stream On) 之前,主机与设备必须 严格遵循 UVC 状态机进行参数与带宽的协商,否则设备可能拒绝启动视频流。根据官方 spec,该过程包含 Probe (三步握手) 和 Commit (提交) 两个主要阶段。
流协商时序图
核心步骤详解
PROBE1: 发起协商 (SET_CUR)
主机向视频流接口 (VS Interface) 发送
SET_CUR (PROBE)请求,携带期望的视频参数结构体。目的:主机提出“我想要这样的分辨率、帧率和格式”。
PROBE2: 获取反馈 (GET_CUR)
主机发送
GET_CUR (PROBE)请求,回读设备实际能够支持的参数。设备修正:如果主机请求的参数不支持,设备会返回修正后的最接近参数。
关键数据:此时返回的结构体中,
dwMaxPayloadTransferSize字段由设备计算填充,这是后续选择带宽的依据。
PROBE3: 最终确认 (SET_CUR)
主机将步骤 2 中回读到的(可能被修正过的)参数,再次通过
SET_CUR (PROBE)发送给设备。目的:双方状态同步,主机确认接受设备的修正建议,结束协商阶段。
COMMIT: 提交生效 (SET_CUR)
参数达成一致后,主机发送
SET_CUR (COMMIT)请求。目的:通知设备“参数已确定”。
备注
关键参数
在 Probe/Commit 流程中,主机主要依据以下三个核心字段来确定视频流配置:
bFormatIndex:格式索引 (Format Index)bFrameIndex:分辨率索引 (Frame Index)dwFrameInterval:帧率间隔 (Frame Interval)
其他参数
结构体中的其余参数(如 wCompWindowSize, wCompQuality 等)是否生效或可调,完全取决于 具体设备的实际支持情况。对于大多数标准设备,通常仅需关注上述三个核心参数,其他参数维持设备返回的默认值即可。
关于 Probe/Commit 控制结构体 (Probe and Commit Control) 的完整定义及所有字段详解,请参阅官方 spec。
流带宽匹配与开启 (Stream Activation)
当参数协商完成后,主机需要根据设备反馈的带宽需求,选择合适的硬件接口设置来正式开启传输管道。
流带宽分配图
匹配带宽 (Match Bandwidth)
驱动程序会根据协商阶段获取的 dwMaxPayloadTransferSize 值,遍历视频流接口的所有备用设置 (Alternate Settings)。
切换接口 (Set Interface)
一旦找到合适的备用设置,主机发送标准 USB 请求 SET_INTERFACE。这一步操作标志着 USB 同步传输 (Isochronous Transfer) 带宽的正式保留,视频流随即开始传输。
备注
带宽匹配原则
在同步传输模式下,带宽的选择至关重要,驱动需遵循 够用且最小 的原则:
寻找目标:在所有的 Alternate Settings 中,寻找
wMaxPacketSize(端点最大包长) 大于等于协商值dwMaxPayloadTransferSize的设置。最优选择:在满足上述条件的所有设置中,选择
wMaxPacketSize最小的那一个。
若带宽不足, 会导致数据包被截断,引起画面花屏或丢帧。若带宽过大, 会占用宝贵的 USB 总线周期性带宽,可能导致总线上其他设备(如 USB 音频)因带宽不足而无法工作。
数据传输格式
UVC 的视频数据并非纯粹的原始数据流,而是封装在带有 负载头 (Payload Header) 的数据包中。主机驱动必须首先解析并剥离此头部,才能获取有效的视频 载荷 (Payload) 。
Payload Header 详细结构
基本头部字段 (Basic Header Fields)
所有 Payload Header 起始处都包含两个核心字节:
HLE (Header Length): 1 字节。指定整个头部的长度(以字节为单位),包括 HLE 本身、BFH 以及可选的 PTS/SCR 字段。
BFH[0] (Bit Field Header): 1 字节。位域标志位,指示了后续数据的属性及可选字段的存在。
BFH[0] 位域定义
Bit
Name
描述
D0
FID
Frame Identifier (帧标识符)。 用于区分不同的视频帧或段(具体行为取决于视频格式,见下文)。
D1
EOF
End of Frame (帧结束)。 指示当前包是否为帧或段的最后一个数据包。
D2
PTS
Presentation Time Stamp (显示时间戳存在位)。 置 1 时,表示头部包含 4 字节的 PTS 字段。
D3
SCR
Source Clock Reference (源时钟参考存在位)。 置 1 时,表示头部包含 6 字节的 SCR 字段。
D4
RES
Reserved (保留位)。通常置 0。
D5
STI
Still Image (静止图像)。 置 1 表示该样本属于静态图片(Stream-based 格式通常置 0)。
D6
ERR
Error Bit (错误位)。 置 1 表示设备在流传输过程中发生了错误。
D7
EOH
End of Header (头部结束位)。 置 1 表示 BFH 字段结束(即没有扩展头部)。
可选扩展字段
PTS (4 字节): 当 BFH[0].D2 (PTS) 置位时存在。表示视频帧的显示时间。
SCR (6 字节): 当 BFH[0].D3 (SCR) 置位时存在。用于音视频同步的源时钟参考。
视频数据的传输格式
在 UVC 规范中,视频数据的传输格式主要分为两大类: 基于帧 (Frame-based) 和 基于流 (Stream-based)。这两种模式决定了主机如何解析视频数据以及如何处理 Payload Header。
Frame-based
这是最常见的模式,适用于 MJPEG、 Uncompressed (YUV/NV12) 等格式。
特点:视频数据被严格划分为一幅幅独立的图像(帧)。
传输逻辑:主机驱动关注“帧的边界”,通过检测 Payload Header 中的 FID 翻转或 EOF 标志来组装完整的一帧画面。
Stream-based
主要用于 H.264/H.265 等压缩流格式。
特点:数据被视为连续的字节流,没有严格的物理“帧”边界概念(或者边界由解码器内部处理)。
传输逻辑:主机驱动主要负责搬运数据流,通常不依赖 Payload Header 来判定帧的开始或结束,而是将数据直接交给上层应用或解码器去解析内容。
备注
工程实践:Stream-based 设备的实际行为
虽然 UVC 规范 指出 Stream-based 格式可以不使用 FID 和 EOF 来划分边界,但在实际工程应用中:
设备端 (Camera):
绝大多数厂商即使在输出 H.264/H.265 等 Stream-based 格式时,仍然会尽可能遵循 Frame-based 的规则,通过翻转 FID 或设置 EOF 来标记数据块(如 NAL Unit 或帧切片)的边界。
主机端 (Host):
大多数通用 UVC 驱动(如 Linux uvcvideo、Windows 系统驱动)为了简化逻辑,默认倾向于统一按照 FID 跳变或 EOF 标志来切分数据包。
小技巧
工程实践:主机驱动的数据处理机制
在实际工程实现中,UVC 主机驱动(Host Driver)对 Frame-based 与 Stream-based 格式采取统一的传输处理策略,并不针对流式传输做特殊区分。其核心处理逻辑如下:
传输层的格式无关性:
驱动层仅负责数据的搬运与重组,不感知具体的视频编码格式或流类型。无论设备描述符声明为何种数据类型,驱动均将其视为通用的数据载荷。
统一的组包机制:
驱动严格依赖 Payload Header 中的 FID (Frame Identifier) 状态翻转与 EOF (End of Frame) 标志位进行数据定界。驱动会将属于同一逻辑序列的数据包组装为一个完整的 Payload Buffer,并将其作为驱动层面的 一帧 提交至用户空间。
解析职责分离:
数据内容的解析由 应用层 (Application Layer) 负责。
例如,对于 H.264 等 Stream-based 格式,驱动提交的 Buffer 可能包含多个 NAL Unit 或部分帧数据,应用层需自行解析流数据结构以获取实际的视频帧。
类驱动
本节详细介绍了 USB UVC 主机驱动的内部实现细节,包括驱动架构、视频流管理、类特定请求的支持情况以及管道资源的分配方案。
具体实现
USB UVC 主机驱动栈基于模块化设计,通过分层架构实现了上层应用与 USB 硬件控制器之间的高效交互。该架构有助于稳定地捕获和处理高带宽视频数据,并提供可配置的帧缓冲管理机制。
其核心架构与数据流向如下图所示:
按功能职责划分为以下几个核心模块:
应用层接口适配(Application Adapter)
负责视频帧数据(Video Frame)的缓冲管理与提取。
作为 USB 驱动与用户应用层(User Application)的接口层,它管理着视频数据的最终输出:
参数配置:提供
usbh_uvc_set_param()接口,允许应用层设置分辨率、帧率和视频格式。数据获取:提供
usbh_uvc_get_frame()接口,应用层通过此接口从就绪队列 (Ready Queue) 中获取已组装完成的完整视频帧。流控制:提供
usbh_uvc_stream_on()和usbh_uvc_stream_off()接口,控制底层视频流传输的启停。
UVC 类驱动架构
UVC 主机端驱动由 UVC 类协议驱动(UVC Class Driver)负责标准协议的握手、描述符拓扑解析以及视频流的维护。
该模块参考 USB Video Class 1.1 规范实现核心功能,具备最小限度的 UVC 1.5 兼容性——仅识别 UVC 1.5 中必要的协商字段以满足设备互操作性要求,UVC 1.5 相对 1.1 新增的扩展字段暂不支持。实现了主机与 UVC 设备交互的核心业务逻辑。其主要职责包括:
枚举与接口绑定:负责解析视频控制接口(VC Interface)和视频流接口(VS Interface),建立 UVC 内部拓扑结构(如 Unit 和 Terminal)。
格式协商(Probe & Commit):在视频流启动前,执行 Probe/Commit 标准流程,与设备协商分辨率、帧率及最佳的带宽设置。
带宽分配:在视频流启动前,根据 Probe/Commit 协商结果选择合适的 Video Streaming 接口备用配置(Alt Setting),并在配置过程中向 USB 栈申请对应的端点带宽资源。目前版本仅支持等时传输模式,不支持 Bulk 视频流。
核心处理任务(Process Task)
负责 USB 协议栈核心逻辑与状态机管理。
状态机维护:管理 UVC 特定的状态流转,包括
UVC_STATE_CTRL(控制传输处理)、UVC_STATE_TRANSFER(数据传输处理) 以及流启动过程中的状态迁移。事件处理:响应底层传输完成事件,协调控制传输与数据流传输的时序。
动态热插拔:处理设备的连接(Attach)与移除(Detach)事件,自动释放流缓冲区与管道资源。
拼帧服务
由软件驱动负责原始 USB 数据包的解析、重组及错误检测。
URB 处理:接收底层上报的 URB (USB Request Block) 数据包,并解析 UVC 负载头 (Payload Header)。
帧组装:处理 FID (Frame ID) 翻转逻辑,剥离协议头,并将有效视频载荷填充至帧缓冲区 (Frame Buffer)。
错误检测:解析 UVC 负载头中的
bmHeaderInfo字段,检查ERR标志位等错误信息。结合 FID 状态判断帧边界,以保障视频帧数据的完整性。若开启硬件加速,此模块将直接对接硬件解码器。
由 UVC 内部硬件模块负责原始 USB 数据包的解析、重组及错误检测,可大幅降低 CPU 负载。
类特定请求实现
本驱动栈遵循 USB Video Class 规范,封装了核心类特定请求(Class-Specific Requests)的实现与发送流程。
当前驱动主要实现了与视频探针控制(Probe Control)和提交控制(Commit Control)相关的类特定请求(SET_CUR/GET_CUR)。源码路径:{SDK}/component/usb/host/uvc
类特定请求类型 |
备注 |
|---|---|
SET_CUR |
设置当前属性,主要用于 Probe 和 Commit 阶段,向设备发送期望的视频流参数(如 |
GET_CUR |
获取当前属性,读取设备当前生效的配置参数,用于校验设置或获取当前状态。 |
管道配置
UVC 主机驱动在设备枚举阶段(usbh_uvc_attach)解析配置描述符,自动识别视频控制与视频流接口,并根据 Alt Setting 申请相应的管道资源。
管道类型 |
描述 |
|---|---|
控制 IN/OUT 管道 |
默认控制管道 0(EP0),用于发送标准请求及 UVC 特定请求(如 Video Probe and Commit Control)。 |
同步 IN 管道 |
归属于视频流接口(VS Interface),用于接收高带宽的视频负载数据。驱动支持根据 USB 速度(Full/High Speed)自动选择合适的包大小(MPS)与传输模式。 |
API 说明
应用示例
应用设计
本节详细介绍 UVC(USB Video Class)主机驱动的完整开发流程,涵盖驱动初始化、热插拔管理、视频流控制与数据处理,以及不同应用场景下的示例说明。
驱动初始化
使用 UVC Host 驱动前,需定义 UVC 配置结构体(如 usbh_uvc_cfg_t)并注册回调结构体 usbh_uvc_cb_t,随后依次调用核心接口启动 USB Host 栈以及 UVC 类驱动。
步骤说明:
硬件配置:设置 USB 速度模式及相关中断优先级。
回调注册:定义
usbh_uvc_cb_t结构体,挂载各个阶段(初始化、连接、断开、参数设置完成)的处理函数。核心初始化:调用
usbh_init()初始化 USB 核心栈。类驱动加载:调用
usbh_uvc_init()初始化 UVC 类驱动。
/*
* 1. Configure USB speed, ISR priority, and main task priority.
*/
static usbh_config_t usbh_cfg = {
.speed = USB_SPEED_HIGH,
.ext_intr_enable = USBH_SOF_INTR,
.isr_priority = INT_PRI_MIDDLE,
.main_task_priority = CONFIG_USBH_UVC_MAIN_THREAD_PRIORITY,
.tick_source = USBH_SOF_TICK,
};
/*
* 2. Configure UVC user-level HW priority.
*/
static usbh_uvc_ctx_t uvc_cfg = {
#if USBH_UVC_USE_HW
.hw_isr_pri = CONFIG_USBH_UVC_HW_IRQ_PRIORITY,
#endif
};
/*
* 3. Define user callbacks for UVC events.
*/
static usbh_uvc_cb_t uvc_cb = {
.init = uvc_cb_init,
.deinit = uvc_cb_deinit,
.attach = uvc_cb_attach,
.detach = uvc_cb_detach,
.setup = uvc_cb_setup,
.setparam = uvc_cb_setparam, /* int status: check uvc_setparam_status after semaphore */
};
int ret = 0;
/*
* 4. Initialize USB host core driver with configuration.
*/
ret = usbh_init(&usbh_cfg, NULL);
if (ret != HAL_OK) {
goto free_sema_exit;
}
/*
* 5. Initialize UVC class driver.
*/
ret = usbh_uvc_init(&uvc_cfg, &uvc_cb);
if (ret != HAL_OK) {
/* If class driver init fails, clean up the core driver */
usbh_deinit();
goto usb_deinit_exit;
}
热插拔事件处理
通过注册 usbh_uvc_cb_t 中的 attach 和 detach 回调来监听 UVC 摄像头的连接与断开。
在示例代码中,利用信号量(Semaphore)机制来同步状态:
Attach:当摄像头插入并枚举成功后,触发
attach回调,释放uvc_attach_sema,通知主线程创建视频采集任务。Detach:当摄像头拔出时,触发
detach回调,释放uvc_detach_sema,触发热插拔管理线程(uvc_hotplug_thread)进行资源清理与重新初始化。
/* USB callback */
static usbh_uvc_cb_t uvc_cb = {
.attach = uvc_cb_attach,
.detach = uvc_cb_detach,
};
/* Callback executed when device is inserted */
static int uvc_cb_attach(void)
{
USB_DIAG(USB_LAYER_APP, USB_EVT_LINK, 2);
rtos_sema_give(uvc_attach_sema);
return HAL_OK;
}
/* Callback executed when device is removed */
static int uvc_cb_detach(void)
{
USB_DIAG(USB_LAYER_APP, USB_EVT_LINK, 3);
#if CONFIG_USBH_UVC_HOT_PLUG
rtos_sema_give(uvc_detach_sema);
#endif
return HAL_OK;
}
/* Thread Context: Handle the state machine for Hotplug */
static void example_usbh_uvc_hotplug_thread(void *param)
{
int ret = 0;
UNUSED(param);
for (;;) {
/* Wait for detach signal */
if (rtos_sema_take(uvc_detach_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
/* Step 1: Set exiting flag so uvc_test can exit gracefully */
uvc_task_exiting = 1;
/* Step 2: Let uvc_test finish processing the current frame */
/* (img_prepare + put_frame) before tearing down stream resources */
rtos_time_delay_ms(200);
/* Step 3: Stop streaming to wake up any blocked get_frame call.
/* After this, get_frame returns NULL (stream_state != STREAMING_ON). */
usbh_uvc_stream_off(CONFIG_USBH_UVC_IF_NUM_0);
/* Step 4: Wait for uvc_test to detect flag, break out of loop, and self-delete.*/
/* No timeout: must not deinit while uvc_test is still running,*/
/* as it may call usbh_uvc_put_frame() on already-freed memory. */
while (uvc_task != NULL) {
rtos_time_delay_ms(100);
}
RTK_LOGS(TAG, RTK_LOG_INFO, "Hotplug: uvc_test exited\n");
/* Step 5: De-init UVC Class and Host Core */
usbh_uvc_deinit();
usbh_deinit();
rtos_time_delay_ms(10);
RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap: 0x%x\n", rtos_mem_get_free_heap_size());
/* Reset exiting flag before re-init */
uvc_task_exiting = 0;
/* Step 6: Re-init Host Core and UVC Class for next attach */
ret = usbh_init(&usbh_cfg, NULL);
if (ret != HAL_OK) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Init USBH fail\n");
break;
}
ret = usbh_uvc_init(&uvc_cfg, &uvc_cb);
if (ret < 0) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Init UVC fail\n");
usbh_deinit();
break;
}
}
}
RTK_LOGS(TAG, RTK_LOG_ERROR, "Hotplug thread fail\n");
rtos_task_delete(NULL);
}
/* Main entry task to initialize USB and wait for connection */
static void example_usbh_uvc_task(void *param)
{
rtos_task_t hotplug_task;
int ret = 0;
/* ... Initialization of semaphores and mutexes ... */
/* Init USB Host Core */
ret = usbh_init(&usbh_cfg, NULL);
if (ret != HAL_OK) {
goto free_sema_exit;
}
/* Init UVC Class Driver */
ret = usbh_uvc_init(&uvc_cfg, &uvc_cb);
if (ret != HAL_OK) {
usbh_deinit();
goto usb_deinit_exit;
}
/* Create Hotplug detection thread */
ret = rtos_task_create(&hotplug_task, "example_usbh_uvc_hotplug_thread", example_usbh_uvc_hotplug_thread, NULL, 768U, CONFIG_USBH_UVC_HOTPLUG_THREAD_PRIORITY);
if (ret != RTK_SUCCESS) {
goto usbh_uvc_deinit_exit;
}
/* Wait for device attach callback to release semaphore */
while (1) {
if (rtos_sema_take(uvc_attach_sema, RTOS_SEMA_MAX_COUNT) == RTK_SUCCESS) {
if (uvc_task == NULL) {
/* Create the main UVC test/streaming thread */
ret = rtos_task_create(&uvc_task, "example_usbh_uvc_test", example_usbh_uvc_test, NULL,
768U, CONFIG_USBH_UVC_TEST_THREAD_PRIORITY);
if (ret != RTK_SUCCESS) {
goto delete_hotplug_task_exit;
}
}
}
}
goto example_exit;
/* ... Error handling labels ... */
......
}
视频流控制与数据处理
当 UVC 设备枚举成功后,主机需要配置视频参数并启动视频流,随后周期性获取图像帧数据。
帧缓冲区大小配置
图像帧缓冲区在初始化阶段由驱动统一分配,大小由 CONFIG_USBH_UVC_FRAME_BUF_SIZE 宏配置。
该宏决定了每个图像帧缓冲区的大小。正确配置该值对于稳定的视频捕获至关重要。推荐的大小取决于视频格式、分辨率、帧率和比特率(从设备 datasheet 中获得)。
平均帧大小可通过以下公式估算:
avg_frame_size (KB) = bitrate (kbps) / framerate (fps) / 8
rec (KB) = avg_frame_size x margin
其中 margin 为安全系数,因压缩格式而异:
格式 |
系数 |
说明 |
|---|---|---|
MJPEG |
3x |
峰值帧波动,3x 安全系数可容纳典型的峰值帧。 |
H264 |
5x |
峰值 I 帧较大(远大于 P/B 帧),高动态场景可增至 8x。 |
YUY2 |
无 |
无需安全系数,帧大小为固定值:width x height x 2 字节。 |
以下是常见配置的参考计算表:
格式 |
分辨率 |
比特率 |
FPS |
平均大小 |
推荐缓冲区大小 |
|---|---|---|---|---|---|
MJPEG |
720p (1280x720) |
10 Mbps |
30 |
41 KB |
125 KB (3 x 41 KB) |
MJPEG |
1080p (1920x1080) |
12 Mbps |
30 |
50 KB |
150 KB (3 x 50 KB) |
H264 |
720p (1280x720) |
8 Mbps |
30 |
33 KB |
166 KB (5 x 33 KB) |
H264 |
1080p (1920x1080) |
12 Mbps |
30 |
50 KB |
250 KB (5 x 50 KB) |
YUY2 |
480p (640x480) |
无 |
30 |
600 KB |
600 KB (固定值: 640 x 480 x 2) |
YUY2 |
720p (1280x720) |
无 |
30 |
1,800 KB |
1,800 KB (固定值: 1280 x 720 x 2) |
备注
在 USB 2.0 HS 限制内, YUY2 720p @ 30 fps(约 442 Mbps)接近极限。
如果运行时出现
Frame overflow错误,请增大CONFIG_USBH_UVC_FRAME_BUF_SIZE。确保目标硬件有足够的堆空间容纳配置的缓冲区大小。
参数配置(Set Parameters)
在启动视频流之前,通过
usbh_uvc_set_param()设置期望的格式(例如 MJPEG、H.264、YUY2 等)、分辨率和帧率。设置请求发出后,需等待uvc_setparam_sema信号量,确信该组参数已通过 Probe/Commit 协议与设备协商成功并生效。开启视频流(Stream On)
调用
usbh_uvc_stream_on()以此开启指定接口的视频传输,设备开始在 USB 总线上发送等时(Isochronous)数据包。帧数据获取与处理
应用层需在主循环中严格遵循“获取 (Get) -> 处理 (Process) -> 释放 (Put)”的流程:
获取(Get Frame):
调用
usbh_uvc_get_frame()从驱动获取一帧已填充的图像数据。RTL8730E:丢弃最旧帧(Drop Oldest)机制: 驱动维护一个帧缓冲池并提供拼帧服务。当应用层处理速度低于数据生产速度时,驱动会自动丢弃队列中尚未读取的旧帧,确保该接口始终返回最新的图像帧。
RTL8721F:硬件拼帧机制: 由底层硬件直接提供拼帧服务,该过程由硬件内部机制保证此接口始终能获取到最新的图像帧。
处理(Process):
在应用层对数据进行消费(如拷贝显示、保存文件、网络上传或仅做统计分析)。
备注
数据处理说明:
UVC 驱动作为生产者以 帧 为单位(即已聚合构成一帧完整图像的所有 UVC 负载(payload))将数据交付给消费者。驱动层不解析 UVC payload 内部的具体编码数据内容(如 MJPEG/H.264 比特流),因此应用层需根据具体视频格式自行实现 payload 的解码或处理逻辑。
归还(Put Frame):
无论应用层如何处理该帧(即使只是统计数据大小或者决定丢弃该帧),处理完毕后 必须 调用
usbh_uvc_put_frame()。备注
RTL8730E:此函数用于将缓冲区归还给驱动内部的帧池。若不及时归还,会导致帧缓冲池耗尽,从而无法继续获取新的图像数据。
RTL8721F:此函数在该平台上实际上不执行具体操作。如果上层应用的消费者处理速度过慢,HW UVC 内部会使用新的帧覆盖旧帧的数据。
停止视频流(Stream Off)
当不再需要视频数据或准备断开连接时,调用
usbh_uvc_stream_off()。这将通知设备停止 ISOC 传输,释放等时端点带宽,并复位驱动内部的流控制状态。
/* Define user callbacks for UVC events */
static usbh_uvc_cb_t uvc_cb = {
.setup = uvc_cb_setup,
.setparam = uvc_cb_setparam,
};
/* Define USB user-level setup callback */
static int uvc_cb_setup(void)
{
USB_DIAG(USB_LAYER_APP, USB_EVT_LINK, 4);
rtos_sema_give(uvc_start_sema);
return HAL_OK;
}
/* Define USB user-level setparam callback */
static int uvc_cb_setparam(int status)
{
USB_DIAG(USB_LAYER_APP, USB_EVT_LINK, 5);
uvc_setparam_status = status;
rtos_sema_give(uvc_setparam_sema);
return HAL_OK;
}
/* Main UVC Test Thread */
static void example_usbh_uvc_test(void *param)
{
......
/* Wait for the device to be ready (Enumeration Complete) */
while (!uvc_task_exiting) {
if (rtos_sema_take(uvc_start_sema, RTOS_SEMA_MAX_COUNT) != RTK_SUCCESS) {
continue;
}
/* 1. Configure UVC Parameters */
/* Set the desired format, resolution, and frame rate */
uvc_s_ctx.fmt_type = CONFIG_USBH_UVC_FORMAT_TYPE; // e.g., MJPEG
uvc_s_ctx.width = CONFIG_USBH_UVC_WIDTH; // e.g., 1080
uvc_s_ctx.height = CONFIG_USBH_UVC_HEIGHT; // e.g., 720
uvc_s_ctx.frame_rate = CONFIG_USBH_UVC_FRAME_RATE;// e.g., 30
uvc_s_ctx.frame_buf_size = CONFIG_USBH_UVC_FRAME_BUF_SIZE;
if (uvc_s_ctx.fmt_type == USBH_UVC_FORMAT_MJPEG) {
fmt_name = "MJPEG";
} else if (uvc_s_ctx.fmt_type == USBH_UVC_FORMAT_H264) {
fmt_name = "H264";
} else if (uvc_s_ctx.fmt_type == USBH_UVC_FORMAT_YUV) {
fmt_name = "YUV";
} else {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Unsupport type %d\n", uvc_s_ctx.fmt_type);
goto exit;
}
/* Trigger the UVC parameter setting process */
ret = usbh_uvc_set_param(&uvc_s_ctx, CONFIG_USBH_UVC_IF_NUM_0);
if (ret != RTK_SUCCESS) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Set param req failed: %d\n", ret);
goto exit;
}
/* Wait for the semaphore indicating parameter setting is actually completed */
if (rtos_sema_take(uvc_setparam_sema, 5000) == RTK_SUCCESS) {
/* Check the status returned by the setparam callback */
if (uvc_setparam_status != HAL_OK) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Set paras err: %s %d*%d@%dfps status=%d\n",
fmt_name, uvc_s_ctx.width, uvc_s_ctx.height, uvc_s_ctx.frame_rate, uvc_setparam_status);
goto exit;
}
RTK_LOGS(TAG, RTK_LOG_INFO, "Set paras ok: %s %d*%d@%dfps\n",
fmt_name, uvc_s_ctx.width, uvc_s_ctx.height, uvc_s_ctx.frame_rate);
} else {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Set paras timeout: %s %d*%d@%dfps\n",
fmt_name, uvc_s_ctx.width, uvc_s_ctx.height, uvc_s_ctx.frame_rate);
goto exit;
}
/* ... Initialize consumer tasks (e.g., VFS thread or HTTPC thread) ... */
/* 2. Start Video Stream */
RTK_LOGS(TAG, RTK_LOG_INFO, "Stream on\n");
ret = usbh_uvc_stream_on(&uvc_s_ctx, CONFIG_USBH_UVC_IF_NUM_0);
if (ret) goto exit;
/* 3. Main Capture Loop */
while (img_cnt < CONFIG_USBH_UVC_LOOP && !uvc_task_exiting) {
// 3.1 Get Frame from USB Stack
/* Retrieve a filled frame buffer from the UVC driver */
buf = usbh_uvc_get_frame(CONFIG_USBH_UVC_IF_NUM_0);
if (buf == NULL) {
/* Frame not ready yet, fail_cnt ++ , wait and retry */
rtos_time_delay_ms(1);
continue;
}
// 3.2 Process the Frame Data
/* CRITICAL: Buffer overflow detected! */
/* This means the camera sent a frame larger than our allocated buffer. */
/* ACTION: Please increase 'CONFIG_USBH_UVC_FRAME_BUF_SIZE' in example_usbh_uvc.c */
/* to match the camera's actual output size for the current resolution/format. */
len = buf->byteused;
/* Even on error, we must return the frame buffer */
if (len > CONFIG_USBH_UVC_FRAME_BUF_SIZE) {
if (usbh_uvc_put_frame(buf, CONFIG_USBH_UVC_IF_NUM_0) != HAL_OK) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Put frame fail\n");
}
RTK_LOGS(TAG, RTK_LOG_ERROR, "Frame %d overflow %d > %d\n", img_cnt, len, CONFIG_USBH_UVC_FRAME_BUF_SIZE);
goto exit;
}
/* CONSUMER LOGIC: */
/* The actual data processing happens here. */
/* - Simple Mode: Just count bytes and drop data. */
/* - VFS Mode: Write `buf->buf` to SD Card. */
/* - HTTPC Mode: Send `buf->buf` to Network. */
if (len > 0) {
RTK_LOGS(TAG, RTK_LOG_INFO, "Captured frame %d, len=%d\n", img_cnt, len);
usbh_uvc_img_prepare(buf);
}
// 3.3 Put Frame back to USB Stack
/* CRITICAL: Must return the buffer to driver for the next capture */
if (usbh_uvc_put_frame(buf, CONFIG_USBH_UVC_IF_NUM_0) != HAL_OK) {
RTK_LOGS(TAG, RTK_LOG_ERROR, "Put frame fail\n");
}
img_cnt ++;
}
/* 4. Calculate throughput */
uvc_calculate_tp(img_cnt);
/* 5. Release source when detech*/
if (uvc_task_exiting) {
......
goto exit;
}
}
/* exit labels */
}
驱动卸载
当设备断开或需要关闭 USB 主机功能时,需按顺序卸载类驱动和主机核心驱动,并释放相关系统资源。
/* 1. Deinitialize UVC class driver. */
usbh_uvc_deinit();
/* 2. Deinitialize USB host core driver */
usbh_deinit();
运行方式
本示例演示了 Ameba 作为 USB UVC 主机如何捕获摄像头视频帧。为了满足不同的应用场景,示例提供了三种工作模式:
宏定义 |
说明 |
帧存储 / 上传方式 |
|---|---|---|
|
基础测试模式 |
仅捕获视频帧用于统计吞吐量,不进行后处理;
每会话采集 |
|
SD 卡存储模式 |
通过 VFS 将视频帧写入 SD 卡(FAT32);
MJPEG:逐帧保存为 |
|
网络上传模式 |
通过 HTTP POST 上传视频帧: Ringbuffer(RTL8730E,SW UVC)
MJPEG:逐帧上传;
H264:分块流式上传;
采集 PSRAM(RTL8721F,HW UVC) 采集完成后批量上传(约 2 秒视频)。 |
用户可以通过修改代码中的 CONFIG_USBH_UVC_APP 宏定义来选择当前生效的模式。
配置与编译
软件配置
打开
example_usbh_uvc.c文件,根据测试需求修改CONFIG_USBH_UVC_APP宏定义等:/* Supported application example: USBH_UVC_APP_SIMPLE, USBH_UVC_APP_VFS, USBH_UVC_APP_HTTPC */ #define CONFIG_USBH_UVC_APP USBH_UVC_APP_SIMPLE /* Supported formats: USBH_UVC_FORMAT_MJPEG, USBH_UVC_FORMAT_YUV, USBH_UVC_FORMAT_H264 * Note: Users must verify which formats their specific camera supports and * adjust the definition below accordingly. */ #define CONFIG_USBH_UVC_FORMAT_TYPE USBH_UVC_FORMAT_MJPEG /* Target resolution and compression ratio. * If the specific camera device does not support * these values, the host stack will automatically select the closest match. * Always check the logs to confirm the actual parameters applied. */ #define CONFIG_USBH_UVC_WIDTH 1280 #define CONFIG_USBH_UVC_HEIGHT 720 #define CONFIG_USBH_UVC_FRAME_RATE 30 /* Frame buffer size in bytes * Size depends on format, resolution, and scene complexity. * Formula: * avg_frame_size (KB) = bitrate (kbps) / framerate (fps) / 8 * rec = avg x margin * * Margin by format: * MJPEG: 3 x avg (peak frame fluctuation) * H264: 5 x avg (peak I-frame; increase to 8x for high-motion) * YUY2: no margin needed, set to width x height x 2 (uncompressed, fixed frame size) * * Examples: * MJPEG @ 720p 30 fps, 10 Mbps : avg = 41 KB, rec = 125 KB * MJPEG @ 1080p 30 fps, 12 Mbps : avg = 50 KB, rec = 150 KB * H264 @ 720p 30 fps, 8 Mbps : avg = 33 KB, rec = 166 KB * H264 @ 1080p 30 fps, 12 Mbps : avg = 50 KB, rec = 250 KB * YUY2 @ 480p 30 fps : frame = 640 x 480 x 2 = 600 KB (USB 2.0 bandwidth: 147 Mbps) * YUY2 @ 720p 30 fps : frame = 1280 x 720 x 2 = 1,800 KB (USB 2.0 bandwidth: 442 Mbps) * YUY2 @ 1080p 30 fps : not listed - exceeds USB 2.0 HS bandwidth (~480 Mbps theoretical); * 1080p YUY2 @ 30 fps requires ~950 Mbps; use MJPEG or H264 instead. * Note: Increase this value if an oversize error occurs; Ensure the target hardware has * sufficient heap memory for this buffer. */ #define CONFIG_USBH_UVC_FRAME_BUF_SIZE (150 * 1024) /* Most cameras have a single video stream interface, so use default 0. * If the camera supports dual streams, set this to 1. * Note: Current protocol stack supports a maximum of 2 video stream interfaces. */ #define CONFIG_USBH_UVC_IF_NUM_0 0 /* Hot plug / memory leak test */ #define CONFIG_USBH_UVC_HOT_PLUG 1 /* Check image data validity (0: Disable, 1: Enable) */ #define CONFIG_USBH_UVC_CHECK_MJEPG_DATA 1 /* Number of frames to capture in the loop */ #define CONFIG_USBH_UVC_LOOP 200 /* Maximum continuous error count before stopping */ #define CONFIG_USBH_UVC_MAX_FAIL_COUNT 5
根据所选模式,还需要关注以下配置项:
若使用 HTTPC 模式,需配置目标服务器的 IP 地址和端口,并确保 Wi-Fi 连接信息正确。
#define USBH_UVC_HTTPC_SERVER "xx.xx.xx.xx" #define USBH_UVC_HTTPC_PORT 5090
编译与烧录
在 SDK 根目录下执行以下命令以配置环境,选择目标 SoC,编译工程并将生成
Image文件烧录至开发板:# Initialize environment (required for every new terminal) source env.sh or env.bat(Windows system) # Select Target SoC (replace xxx with your specific SoCs) ameba.py soc xxx ameba.py build -a usbh_uvc -p
确认 Menuconfig 配置
若编译失败,请执行
ameba.py menuconfig,确认已选择USBH UVC。- Choose `CONFIG USB --->`: [*] Enable USB USB Mode (Host) ---> [*] UVC
结果验证
通用测试步骤:
将 USB 摄像头连接至开发板 USB 接口。
复位开发板,观察串口日志。
确保无 USB 相关报错信息(如枚举失败)。
根据所选模式,查看下方的具体验证步骤与预期日志:
测试说明:
此模式下,系统识别摄像头后将周期性捕获图像(默认 200 帧),仅计算吞吐量,不保存数据。
预期日志:
[UVC-I] Set paras ok: MJPEG 1280*720@60fps
[UVC-I] Stream on
[UVC-I] Captured frame 0, len=20832
[UVC-I] Captured frame 1, len=20912
...
[UVC-I] Captured frame 199, len=108264
[UVC-I] TP 4126 KB/s @ 4953 ms, fps 40/s
[UVC-I] TP 4.0 MB/s-40 (0_20930888/200)
[UVC-I] Stream off
[UVC-I] Test done
测试说明:
请务必在复位前插入格式化为 FAT32 的 SD 卡。
测试完成后,将 SD 卡插入电脑,检查是否生成
imgX.jpeg文件且能正常打开。
预期日志:
[UVC-I] Set paras ok: MJPEG 1280*720@60fps
[UVC-I] Start vfs service
...
[UVC-I] VFS-SDcard Init Success
[UVC-I] Stream on
[UVC-I] Captured frame 0, len=25952
[UVC-I] Create image file: sdcard:img0.jpeg
[UVC-I] fwrite() ok, w 25946
[UVC-I] Captured frame 1, len=24904
[UVC-I] Create image file: sdcard:img1.jpeg
缓冲区模式说明:
HTTPC 模式提供两种缓冲区模式,由 USBH_UVC_HTTPC_BUFFER_MODE 配置:
模式 |
存储位置 |
行为 |
适用 SoC |
内存占用 |
|---|---|---|---|---|
0 |
环形缓冲区 (SRAM) |
帧数据存入内部 SRAM 的环形缓冲区,HTTP 客户端实时上传 (MJPEG:逐帧上传;H264:流式上传)。 |
RTL8730E(软件 UVC) |
SRAM: |
1 |
PSRAM |
帧数据写入外部 PSRAM 缓冲区,捕获完成后 HTTP 客户端 将整个 PSRAM 内容作为单次批处理上传。 |
RTL8721F(硬件 UVC) |
PSRAM: 2 MB |
模式根据 SoC 自动选择:
RTL8730E(SW UVC):
USBH_UVC_HTTPC_BUFFER_MODE= 0,使用 SRAM 环形缓冲区实时上传。RTL8721F(HW UVC):
USBH_UVC_HTTPC_BUFFER_MODE= 1,使用 PSRAM 缓冲区捕获完成后批量上传。
运行时 CLI 命令:
HTTPC 服务器的 IP 地址可在运行时通过 uvch_setip CLI 命令设置,无需重新编译:
# 设置 HTTPC 服务器 IP(需在 WiFi 连接前执行)
uvch_setip xx.xx.xx.xx
环境搭建:
测试前需在 PC 端搭建 Apache 服务器用于接收图片。
安装 Apache:下载并解压 Apache24,将
conf/httpd.conf中的SRVROOT修改为解压路径,并将Listen端口配置为 5090。配置上传脚本:在
Apache24/cgi-bin/下创建submit.py(确保第一行 Python 路径正确):
#!C:\Programs\Python\Python312\python.exe
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import cgi, os
import cgitb
cgitb.enable()
form = cgi.FieldStorage()
# get filename
fileitem = form['filename']
# check
if fileitem.filename:
fn = os.path.basename(fileitem.filename)
open('../uploads/' + fn, 'wb').write(fileitem.file.read())
message = 'file "' + fn + '" upload success'
else:
message = 'file upload fail'
print("Content-Type: text/html")
print()
print("""
<html>
<head>
<meta charset="utf-8">
<title>rtk.upload.com</title>
</head>
<body>
<p>%s</p>
</body>
</html>
""" % message)
启动服务:运行
Apache24/bin/httpd.exe。
预期日志:
[UVC-I] Set paras ok: MJPEG 1280*720@60fps
[UVC-I] Start HTTP client
[UVC-I] Try to conn
[UVC-I] Start httpc
[UVC-I] Stream on
[UVC-I] Captured frame 0, len=20448
[UVC-I] Captured frame 1, len=19000
[UVC-I] Send image0 request ok
[UVC-I] Captured frame 2, len=19008
[UVC-I] Captured frame 3, len=24104
[UVC-I] Captured frame 4, len=24056
[UVC-I] Captured frame 5, len=71792
[UVC-I] Captured frame 6, len=72032
[UVC-I] Captured frame 7, len=71480
[UVC-I] Captured frame 8, len=71536
[UVC-I] Captured frame 9, len=71376
[UVC-I] Send image1 request ok
结果检查:
查看 PC 端 Apache24/uploads 目录(由上传脚本 submit.py 写入),确认图片已成功上传。
查看捕获的媒体文件:
JPEG 图片:可直接用 PC 上的图片查看器打开。
JPEG 序列合成 MP4:可使用
ffmpeg将 JPEG 帧序列转换为可播放的 MP4 视频,请将<framerate>替换为实际帧率,<drive>:替换为 SD 卡盘符:ffmpeg -framerate <framerate> -i <drive>:/img%d.jpeg -c:v libx264 -pix_fmt yuv420p output.mp4
H.264 裸流:可使用
ffplay直接播放 H.264 原始码流:ffplay -f h264 stream.h264
YUY2 raw 视频:可使用 VLC 或
ffplay播放,需指定 rawvideo 参数,如像素格式、分辨率、帧率等:ffplay -f rawvideo -pixel_format yuyv422 -video_size <resolution> -framerate <framerate> raw.yuv
备注
以上命令仅供参考,完整命令语法和可用选项请参考 FFmpeg 文档 。