UVC驱动源码研究

本文提到的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,完成两项核心落地操作:

  1. 流环境与默认协商初始化(uvc_video_init):

    • 将 VS 接口切换为静默空闲态(AltSetting 0)。
    • 选定一个开机默认格式与分辨率(如 1080P MJPEG),通过 Endpoint 0 向 VS 接口发起首次 Probe & Commit 控制请求,完成基本参数协商。
    • 根据端点属性绑定数据解码函数(如等时传输绑定 uvc_video_decode_isoc)。
  2. 生成用户空间节点(video_register_device):

    • 主视频节点 :注册生成 /dev/video0 ,赋予 V4L2_CAP_VIDEO_CAPTURE 与 V4L2_CAP_STREAMING 能力,并将该 Chain 上由各 Entity 收集的画质参数挂到节点暴露给用户空间。
    • 辅助/元数据节点 :若设备描述符包含多输出终端或内核启用了元数据支持,为次级终端注册生成 /dev/video1 (V4L2_CAP_META_CAPTURE),用于向应用层导出逐帧硬件时间戳与快门快照字节流。
相关推荐
高山有多高1 小时前
【Linux笔记】Linux基本指令
linux·运维·笔记
码上有光1 小时前
Linux:进程间通信——匿名管道通信、命名管道通信
android·java·linux·linux通信·匿名通信·命名通信
w01_02_031 小时前
cpu , 控制 ,计算。
linux
醇氧2 小时前
uvicorn 详细介绍
linux·运维·python·python3.11
xiaoye-duck2 小时前
《Linux 网络编程》深入理解 IO 多路复用:select服务器完善、poll 接口详解与服务端改造实战
linux·网络
wacsii2 小时前
【Linux基础开发工具(二)】Vim编辑器从入门到实战:模式、命令、配置与常见问题全解析
linux
꯭自꯭闭꯭3 小时前
达梦DMDSC主备搭建
linux·服务器·数据库
墨家句子3 小时前
AnythingLLM 搭本地知识库:文档问答不准怎么调
linux·后端
高山有多高3 小时前
【Linux笔记】冯诺依曼体系结构
linux·运维·笔记