本文提到的entity和unit实际上是一个东西,都代指uvc设备中的某个单元,只不过uvc驱动中把它叫做eneity实体
本文仅仅做了对uvc注册和框架的研究,至于音视频类的内容没有深究
usb摄像头描述信息概述
bash
[ USB Device: 4a54:5232 Generic Web Camera (USB 2.0 / Bus Powered 500mA) ]
│
├─► [ IAD 0: Video Function (Class 14, SubClass 3) ] ──────────────────────┐
│ │ │
│ ├─► Interface 0: Video Control (VC, 接口号 0) │
│ │ │ │
│ │ ├─► 包含硬件 Entity 拓扑 (IT 1 -> PU 7 -> OT 3, XU 6) ├─► 绑定 uvcvideo 驱动
│ │ └─► Endpoint 0x82 (EP 2 IN, Interrupt, 64B) ── 状态事件上报 │ 生成 /dev/video*
│ │ │
│ └─► Interface 1: Video Streaming (VS, 接口号 1) │
│ │ │
│ ├─► AltSetting 0: 零带宽空闲态 (0 Endpoints) │
│ ├─► AltSetting 1~7: 等时传输态 (Isochronous, EP 4 IN 0x84) │
│ │ * 最大带宽包达 3x1024 = 3072 字节/微帧 │
│ └─► 支持格式: │
│ * Format 1: MJPEG (1080P/720P/544P/480P/360P @ 30fps) │
│ * Format 2: YUY2 (Uncompressed, 1080P@1fps, 720P@1.5fps) │
│ │
└─► [ IAD 1: Audio Function (Class 1, SubClass 0) ] ───────────────────────┼─────────────────────────┐
│ │ │
├─► Interface 2: Audio Control (AC, 接口号 2) │ │
│ │ │ │
│ ├─► 包含音频拓扑 (IT 3 Mic -> FU 5 Feature -> OT 4 USB Stream) │ ├─► 绑定 snd-usb-audio 驱动
│ └─► 支持控制: Mute(静音), Volume(音量) │ 生成 ALSA 声卡 (pcmC*D*c)
│ │ │
└─► Interface 3: Audio Streaming (AS, 接口号 3) │ │
│ │ │
├─► AltSetting 0: 空闲态 (0 Endpoints) │ │
└─► AltSetting 5: 工作态 (EP 3 IN 0x83, Isochronous, 64B) │ │
* PCM 单声道 (Mono), 16-bit, 32000 Hz 采样率 ┘ ┘
uvc_driver.c
uvc设备即usb video class设备,其将isp等功能直接封装在了设备上,通过usb来传输图像和控制信号
linux内核中针对uvc设备写了一套完整的驱动程序,能够通过uvc设备的usb的Video Control的interface进行自匹配
uvc_ids中定义了很多uvc设备的interface信息


对于不在上面这些特定厂商的产品中的uvc设备,则通过最后这两条,不匹配vid和pid而是

通过上面这个宏拼接起来的uvc设备类型进行匹配,只看interface的描述符是不是usb video class
其中sc是interface的subclass,这里是1,就有如下对应关系
bInterfaceSubClass 1 Video Control
然后将其id_table以及probe函数等注册到usb总线上

此时这个id_table实际上匹配的是subClass 为 Video Control的usb interface
设备中所有eneity即unit是怎么接的,分别是干嘛的,都在Video Control的interface中进行了描述
uvc_probe函数
对于绝大多数linux驱动来说,最关键的地方就在probe函数

上面这段代码主要是处理初始化一些用到的信息
其中1995行-1997行初始化的三个链表
是uvc设备独有的
-entities 是uvc设备内部的功能单元的静态描述信息
-chains 是视频数据通路的静态描述信息,即从output unit->input unit
-streams 是对chain的使用说明书,其中包含了用哪条chain,开多大的urb,设置输出什么分辨率等等,有几个stream就对应着/dev下有几个video设备节点
一般可能会有video0和video1两个节点,video0是结构体数据,video1是纯纯的元数据,一般也用不到video1

2017行的uvc_parse_control()函数就是负责解析video control interface并按照其描述填充entites链表
同时内部也会解析usb video streaming中对于stream的描述信息,比如支持哪些分辨率,帧率以及通过哪个端口协商这些,填充到streams链表上
stream按照支持的格式,比如mjpeg,yuyv进行分开

这段代码是将uvc设备注册到linux中的media controller子系统框架中
Media Controller(MC)框架 则负责向用户态提供整个硬件内部的拓扑结构、实体(Entity)与数据链路(Pad / Link)关系
其通常会被注册为/dev/mediax节点
使用media-ctl工具,即可查看设备内部entities的拓扑图
media-ctl -d /dev/media0 -p
bash
Device topology
- entity 1: Web Camera: Web Camera (1 pad, 1 link)
type Node subtype V4L flags 1
device node name /dev/video0
pad0: Sink
<- "Extension 6":1 [ENABLED,IMMUTABLE]
- entity 4: Web Camera: Web Camera (0 pad, 0 link)
type Node subtype V4L flags 0
device node name /dev/video1
- entity 8: Extension 6 (2 pads, 2 links)
type V4L2 subdev subtype Unknown flags 0
pad0: Sink
<- "Processing 7":1 [ENABLED,IMMUTABLE]
pad1: Source
-> "Web Camera: Web Camera":0 [ENABLED,IMMUTABLE]
- entity 11: Processing 7 (2 pads, 2 links)
type V4L2 subdev subtype Unknown flags 0
pad0: Sink
<- "Camera 1":0 [ENABLED,IMMUTABLE]
pad1: Source
-> "Extension 6":0 [ENABLED,IMMUTABLE]
- entity 14: Camera 1 (1 pad, 1 link)
type V4L2 subdev subtype Sensor flags 0
pad0: Source
-> "Processing 7":0 [ENABLED,IMMUTABLE]
pad是数据引脚,source表示流出,sink表示流入
ENABLE表示开启通路,IMMUTABLE表示不可更改(因为我这个usb摄像头很便宜,内部实体之间的数据流向固定死了,也就是chain是固定的)

2050行是将当前uvc设备作为一个总设备注册成为v4l2设备,可以理解为将其注册成一个资源管理器
后面所有被注册成video_device的stream就都是它的子设备了
2054行,遍历所有前面得到的entities链表并且按照entities描述信息,前面uvc_parse_control是给每个entity分配了一个支持功能的位图
这里将位图中 为真 的项,实例化成一个个control挂到entity上
2058行,从output 实体开始往回遍历entities,每找到一条完整的从output到 input实体的chain,就添加一条chain
此时chain就是包含完整control信息的从input的eneity到output的eneity的一条拓扑路径
2062行,是真正注册video_device的地方,可以理解为其将每个stream注册为了一个/dev下面的video设备
其先会调用
c
uvc_register_chains(struct uvc_device *dev) --->
uvc_register_terms(struct uvc_device *dev,struct uvc_video_chain *chain)

uvc_register_terms函数中会调用uvc_stream_by_id
uvc_stream_by_id内部最终会调用uvc_register_video函数,通过bTerminalLink和bTerminalID匹配后,将每个stream和每条chain中的output实体进行匹配,只有匹配成功的stream才会走向后面被注册
所以这里又引出一个知识点,那么就是一个chain可以对应多个stream


然后接下来走到uvc_register_video函数

uvc_video_init函数是给stream设置默认的分辨率帧率等,剩下的那些调节白平衡呀啥的,会在iotcl中实现
这里v4l2为了兼容性,和看门狗子系统一样,都是在硬件操作的fops上套了一层变成了字符设备给用户空间交互
这里vdev指向的ops函数结构体中,实际上操作的是vdev私有数据,即stream
之后所有设置控制参数操作,就都是通过stream找到对应的chain上的eneity进行操作从而实现的
设置分辨率和帧率则是通过video streaming interface中的EP0进行选择

至此,一个uvc设备就被成功从usb设备注册成了一个v4l2下的一个或者多个video_device
总结
最后我让ai帮我总结了一下
bash
[ USB 设备插入 / 枚举识别 (4a54:5232) ]
│
▼
1. 总线驱动匹配 (Probe)
uvc_probe(struct usb_interface *intf)
(仅绑定 Class 14, SubClass 1 的 VC 接口)
│
┌───────────────────────┴───────────────────────┐
▼ ▼
2. 控制端解析 (VC Interface) 3. 数据流端解析 (VS Interface)
uvc_parse_control(dev) uvc_parse_streaming(dev)
- 提取 IT, PU, XU, OT 实体 - 解析所有流接口 (Interface 1)
- 生成 dev->entities 链表 - 解析格式/分辨率/帧率表 (Format/Frame)
│ - 记录端点 (EP 4 IN) 与带宽 AltSetting
│ - 生成 dev->streams 链表
▼ │
4. 硬件画质控件初始化 │
uvc_ctrl_init_device(dev) │
- 遍历每个 Entity 的 bmControls 位图 │
- 匹配分配 entity->controls (亮度/曝光等) │
│ │
└───────────────────────┬───────────────────────┘
▼
5. 拓扑回溯与缝合 (Video Chain)
uvc_scan_device(dev)
- 从 Output Terminal (OT 3) 逆向寻源回溯
- 将 IT 1、PU 7、OT 3 串联为 struct uvc_video_chain
- 【缝合】:将 dev->streams 中的 stream 挂入 chain->streams
│
▼
6. 设备暴露与初始化 (Register)
uvc_register_chains(dev)
┌────────────────────┴────────────────────┐
▼ ▼
uvc_video_init(stream) uvc_register_terms(dev, chain)
- 选定初始默认格式与分辨率 - 注册主视频流 video_device (/dev/video0)
- 执行 VS Probe & Commit 初始握手 - 挂载 chain->ctrl_handler (暴露画质 Controls)
- 指定解码回调 (uvc_video_decode_isoc) - 若支持,注册元数据流 (/dev/video1)
1. 总线驱动匹配(USB Bus Probe)
- 设备插入 USB 总线完成物理枚举后,USB Core 识别到该复合设备包含 Video Function(IAD 0)。
- USB Core 根据
bInterfaceClass = 14 (Video)和bInterfaceSubClass = 1 (Video Control),将 Interface 0(VC 控制接口) 与uvc_driver绑定,触发调用uvc_probe()。
2. 控制接口解析:提取实体(uvc_parse_control)
- 驱动读取 Interface 0 的类特定描述符。
- 识别摄像头内部的硬件结构单元,为每个单元动态分配
struct uvc_entity并填入dev->entities链表:- Input Terminal (IT: 1):Camera Sensor,记录光学控制项(自动曝光、曝光时间)。
- Processing Unit (PU: 7):ISP 图像处理模块,记录画质控制项(亮度、对比度、饱和度、白平衡等)。
- Extension Unit (XU: 6):厂商扩展单元。
- Output Terminal (OT: 3):向 USB 传输视频的输出端,记录其输入源来自前级单元。
3. 数据流接口解析:提取流配置(uvc_parse_streaming)
- 驱动主动遍历该 USB 设备上的所有其他接口,查找
bInterfaceSubClass = 2 (Video Streaming)的 VS 接口(Interface 1)。 - 为每一个流接口分配一个
struct uvc_streaming,挂入dev->streams。 - 解析该流支持的所有图像格式(MJPEG、YUY2)、各格式下的分辨率与帧率列表(1080P/720P 等),以及可用的传输端点(
0x84 EP 4 IN)和不同带宽档位的AltSetting。
4. 硬件控制项实例化(uvc_ctrl_init_device)
- 遍历
dev->entities中的每个实体。 - 读取其
bmControls位图,识别硬件声明支持的功能。 - 动态分配
entity->controls数组,将底层的 UVC 控制选择器(Control Selector)与 Linux 内核 V4L2 Control 规范对应起来。
5. 拓扑回溯与流缝合(uvc_scan_device)
- 以各个输出终端(OT)为起点,利用
bSourceID逆向寻源(Walk Backwards) ,将数据流动沿途经过的 Entity(IT →\to→ PU →\to→ OT)打包为一条逻辑流水线struct uvc_video_chain。 - 数据流与控制链路缝合 :通过 OT 描述符中声明的终端关联,从
dev->streams中取出对应的uvc_streaming,插入该 Chain 的chain->streams链表,完成数据通路与控制骨架的绑定。
6. 最终注册与参数初始化(uvc_register_chains)
驱动遍历装配完成的 Chain,完成两项核心落地操作:
-
流环境与默认协商初始化(
uvc_video_init):- 将 VS 接口切换为静默空闲态(
AltSetting 0)。 - 选定一个开机默认格式与分辨率(如 1080P MJPEG),通过 Endpoint 0 向 VS 接口发起首次 Probe & Commit 控制请求,完成基本参数协商。
- 根据端点属性绑定数据解码函数(如等时传输绑定
uvc_video_decode_isoc)。
- 将 VS 接口切换为静默空闲态(
-
生成用户空间节点(
video_register_device):- 主视频节点 :注册生成
/dev/video0,赋予V4L2_CAP_VIDEO_CAPTURE与V4L2_CAP_STREAMING能力,并将该 Chain 上由各 Entity 收集的画质参数挂到节点暴露给用户空间。 - 辅助/元数据节点 :若设备描述符包含多输出终端或内核启用了元数据支持,为次级终端注册生成
/dev/video1(V4L2_CAP_META_CAPTURE),用于向应用层导出逐帧硬件时间戳与快门快照字节流。
- 主视频节点 :注册生成