视频主机方案

概述

USB 视频类(USB Video Class,UVC)协议定义了通过 USB 接口传输视频数据的工业标准。在主机(Host)模式下,Ameba 平台能够通过该协议识别并驱动外部 USB 摄像头设备。

Ameba 平台的 UVC 主机协议栈专注于 视频采集(Capture) 场景。它基于 USB Video Class(UVC)1.1 规范实现,兼容 UVC 1.5 规范,将外接的 USB 摄像头抽象为系统本地视频输入接口。该方案支持即插即用,能够与系统内置视频处理框架无缝对接,为设备提供便捷、灵活的视觉感知扩展能力。

Ameba USB UVC 主机

特性

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 (视频类基础协议)

Video Class v1.1 document set

UVC 1.5 (视频类基础协议)

Video Class v1.5 document set

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 传输层与上层视频应用或多媒体框架的解耦。

../../../_images/usb_uvc_arch.svg

组件职责

  • 应用层 (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

Compressed:
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 Stream Frame Type Descriptor

Compressed:
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)

备注

详细的字段定义请参考 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 (提交) 两个主要阶段。

流协商时序图

../../../_images/usb_uvc_probecommit.svg

核心步骤详解

  1. PROBE1: 发起协商 (SET_CUR)

    主机向视频流接口 (VS Interface) 发送 SET_CUR (PROBE) 请求,携带期望的视频参数结构体。

    • 目的:主机提出“我想要这样的分辨率、帧率和格式”。

  2. PROBE2: 获取反馈 (GET_CUR)

    主机发送 GET_CUR (PROBE) 请求,回读设备实际能够支持的参数。

    • 设备修正:如果主机请求的参数不支持,设备会返回修正后的最接近参数。

    • 关键数据:此时返回的结构体中,dwMaxPayloadTransferSize 字段由设备计算填充,这是后续选择带宽的依据。

  3. PROBE3: 最终确认 (SET_CUR)

    主机将步骤 2 中回读到的(可能被修正过的)参数,再次通过 SET_CUR (PROBE) 发送给设备。

    • 目的:双方状态同步,主机确认接受设备的修正建议,结束协商阶段。

  4. 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)

当参数协商完成后,主机需要根据设备反馈的带宽需求,选择合适的硬件接口设置来正式开启传输管道。

流带宽分配图

../../../_images/usb_uvc_select_bw.svg
  • 匹配带宽 (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 详细结构

../../../_images/usb_uvc_payloadheader.png
  • 基本头部字段 (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 硬件控制器之间的高效交互。该架构有助于稳定地捕获和处理高带宽视频数据,并提供可配置的帧缓冲管理机制。

其核心架构与数据流向如下图所示:

RTL8730E:
../../../_images/usb_host_swuvc_flow.svg

按功能职责划分为以下几个核心模块:

应用层接口适配(Application Adapter)

负责视频帧数据(Video Frame)的缓冲管理与提取。

作为 USB 驱动与用户应用层(User Application)的接口层,它管理着视频数据的最终输出:

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)事件,自动释放流缓冲区与管道资源。

拼帧服务

RTL8730E:

由软件驱动负责原始 USB 数据包的解析、重组及错误检测。

  • URB 处理:接收底层上报的 URB (USB Request Block) 数据包,并解析 UVC 负载头 (Payload Header)。

  • 帧组装:处理 FID (Frame ID) 翻转逻辑,剥离协议头,并将有效视频载荷填充至帧缓冲区 (Frame Buffer)。

  • 错误检测:解析 UVC 负载头中的 bmHeaderInfo 字段,检查 ERR 标志位等错误信息。结合 FID 状态判断帧边界,以保障视频帧数据的完整性。若开启硬件加速,此模块将直接对接硬件解码器。

类特定请求实现

本驱动栈遵循 USB Video Class 规范,封装了核心类特定请求(Class-Specific Requests)的实现与发送流程。

当前驱动主要实现了与视频探针控制(Probe Control)和提交控制(Commit Control)相关的类特定请求(SET_CUR/GET_CUR)。源码路径:{SDK}/component/usb/host/uvc

类特定请求类型

备注

SET_CUR

设置当前属性,主要用于 Probe 和 Commit 阶段,向设备发送期望的视频流参数(如 dwFrameIntervalbFormatIndex)。

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 说明

驱动 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 中的 attachdetach 回调来监听 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 设备枚举成功后,主机需要配置视频参数并启动视频流,随后周期性获取图像帧数据。

  1. 帧缓冲区大小配置

图像帧缓冲区在初始化阶段由驱动统一分配,大小由 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

  • 确保目标硬件有足够的堆空间容纳配置的缓冲区大小。

  1. 参数配置(Set Parameters)

    在启动视频流之前,通过 usbh_uvc_set_param() 设置期望的格式(例如 MJPEG、H.264、YUY2 等)、分辨率和帧率。设置请求发出后,需等待 uvc_setparam_sema 信号量,确信该组参数已通过 Probe/Commit 协议与设备协商成功并生效。

  2. 开启视频流(Stream On)

    调用 usbh_uvc_stream_on() 以此开启指定接口的视频传输,设备开始在 USB 总线上发送等时(Isochronous)数据包。

  3. 帧数据获取与处理

应用层需在主循环中严格遵循“获取 (Get) -> 处理 (Process) -> 释放 (Put)”的流程:

  • 获取(Get Frame)

    调用 usbh_uvc_get_frame() 从驱动获取一帧已填充的图像数据。

    RTL8730E:

    丢弃最旧帧(Drop Oldest)机制: 驱动维护一个帧缓冲池并提供拼帧服务。当应用层处理速度低于数据生产速度时,驱动会自动丢弃队列中尚未读取的旧帧,确保该接口始终返回最新的图像帧。

  • 处理(Process)

    在应用层对数据进行消费(如拷贝显示、保存文件、网络上传或仅做统计分析)。

    备注

    数据处理说明:

    UVC 驱动作为生产者以 为单位(即已聚合构成一帧完整图像的所有 UVC 负载(payload))将数据交付给消费者。驱动层不解析 UVC payload 内部的具体编码数据内容(如 MJPEG/H.264 比特流),因此应用层需根据具体视频格式自行实现 payload 的解码或处理逻辑。

  • 归还(Put Frame)

    无论应用层如何处理该帧(即使只是统计数据大小或者决定丢弃该帧),处理完毕后 必须 调用 usbh_uvc_put_frame()

    备注

    RTL8730E:

    此函数用于将缓冲区归还给驱动内部的帧池。若不及时归还,会导致帧缓冲池耗尽,从而无法继续获取新的图像数据。

  1. 停止视频流(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 主机如何捕获摄像头视频帧。为了满足不同的应用场景,示例提供了三种工作模式:

宏定义

说明

帧存储 / 上传方式

USBH_UVC_APP_SIMPLE

基础测试模式

仅捕获视频帧用于统计吞吐量,不进行后处理; 每会话采集 CONFIG_USBH_UVC_LOOP (200)帧。

USBH_UVC_APP_VFS

SD 卡存储模式

通过 VFS 将视频帧写入 SD 卡(FAT32); MJPEG:逐帧保存为 imgX.jpegH264:保存为 stream.h264 (示例上限 2 MB,可按硬件调整)。

USBH_UVC_APP_HTTPC

网络上传模式

通过 HTTP POST 上传视频帧:

Ringbuffer(RTL8730E,SW UVC) MJPEG:逐帧上传; H264:分块流式上传; 采集 CONFIG_USBH_UVC_LOOP (200)帧。

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
    

结果验证

通用测试步骤

  1. 将 USB 摄像头连接至开发板 USB 接口。

  2. 复位开发板,观察串口日志。

  3. 确保无 USB 相关报错信息(如枚举失败)。

根据所选模式,查看下方的具体验证步骤与预期日志:

Simple 模式:

测试说明

此模式下,系统识别摄像头后将周期性捕获图像(默认 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

查看捕获的媒体文件

  • 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 文档