程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

linux驱动移植-USB摄像头uvc驱动

在前面的章节我们已经介绍了usb鼠标驱动的移植,那我们是不是通过该usb接口扩展一些其它的外设呢,比如usb键盘、usb声卡、usb摄像头;正好我手上有一款usb摄像头,同时带有麦克风功能,我们尝试一下能不能自己移植一个usb摄像头驱动。

一、前言

S3C2440内核采用的ARM9架构,型号为ARM920T,其可接入的摄像头分为两类:

  • CAMER接口的摄像头;
  • USB接口接口的摄像头;

这里主要介绍usb摄像头的设备驱动程序。

1.1 概念介绍

1.1.1 UVC

UVC全称为USB Video Class,即:USB视频类,是一种为USB视频捕获设备定义的协议标准。

如今的主流操作系统(如Windows XP SP2 and later, Linux 2.4.6 and later, MacOS 10.5 and later)都已提供 UVC 设备驱动,因此符合 UVC 规格的硬件设备在不需要安装任何的驱动程序下即可在主机中正常使用。使用 UVC 技术的包括摄像头、数码相机、类比影像转换器、电视棒及静态影像相机等设备。

Linux UVC driver(uvc) 驱动适用于符合USB视频类规范的摄像头设备,它包括V4L2内核设备驱动和用户空间工具补丁。只要符合这类标准,则不同厂商的USB camera设备,不需要特定的driver就能在Linux下使用。

1.1.2 V4L2

简单的讲V4L2就是用来管理UVC设备的并且能够提供视频相关的一些API。那么这些API怎么使用或者能被谁使用呢。在Linux系统上有很多的开源软件能够支持V4L2。常见的有FFmpeg、opencv、Skype、Mplayer等等。

我们知道在linux下,一切设备皆是是文件,可以像访问普通文件一样对其进行读写。因此,我们不难猜出,V4L2提供的API实际上就是一系列read、write、open、ioctl等函数:

APP在应用层调用read、write、open等接口,调用库函数,触发swi软件异常,进入内核,最终会调用到驱动程序的open、read、write等等。

1.2 确认usb摄像头支持UVC

1.2.1 linux系统

我们首先将摄像头插入我们的台式机上,这里我们是连接到我们虚拟机的linux操作系统上,使用dmeg查看内核打印信息:

复制代码
[  347.559618] usb 1-1: new high-speed USB device number 2 using ehci-pci
[  347.926071] usb 1-1: New USB device found, idVendor=0c45, idProduct=6340
[  347.926074] usb 1-1: New USB device strings: Mfr=2, Product=1, SerialNumber=0
[  347.926076] usb 1-1: Product: USB 2.0 Camera
[  347.926077] usb 1-1: Manufacturer: Sonix Technology Co., Ltd.
[  347.981873] media: Linux media interface: v0.10
[  347.986155] Linux video capture interface: v2.00
[  348.130562] usb 1-1: 3:1: cannot get freq at ep 0x84
[  349.190706] usbcore: registered new interface driver snd-usb-audio
[  349.194058] uvcvideo: Found UVC 1.00 device USB 2.0 Camera (0c45:6340)
[  349.217865] input: USB 2.0 Camera: USB Camera as /devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-1/1-1:1.0/input/input6
[  349.218030] usbcore: registered new interface driver uvcvideo
[  349.218030] USB Video Class driver (1.1.1)
[  349.270678] usb 1-1: 3:1: cannot get freq at ep 0x84
[  349.374801] usb 1-1: 3:1: cannot get freq at ep 0x84
[  349.389464] retire_capture_urb: 12 callbacks suppressed
复制代码

从输出信息可以看到:

  • usb主机控制器ehci识别到了我们的usb设备,并且为usb设备分配了一个地址为2,同时可以看到我们这是一个高速usb设备,即我们的usb设备采用usb 2.0通信协议。
  • 此外内核根据识别的usb设备信息,进行设备驱动匹配,匹配成功后,注册usb接口驱动snd-usb-audio(声卡驱动)、uvcvideo(usb摄像头驱动)
  • 注册事件类输入设备节点/dev/input/event6,用于usb设备数据的上报;

此外,从输出信息中我们也可以得知usb设备的VID=0x0c45、PID=0x6340。

用 ls /dev/video* 查看设备节点:

root@zhengyang:~#  ls /dev/video*
/dev/video0
1.2.2 windows系统

我们将usb设备插入到window操作系统,我们打开设备管理器:

右键属性 -> 详细信息 –> 属性 选择硬件 Id 查看:

 

可以得到插的usb摄像头VID=0x0c45、PID=0x6340。

此外我这usb摄像头还自带麦克风,因此也可以在设备管理器中看到:

1.3 确定usb摄像头种类

通过这个网页 http://www.ideasonboard.org/uvc/ 来查看是否支持 UVC,这个网站是 USB Video Class Linux device driver 的主页,里面有 UVC 的详细的介绍。

根据前面的打印信息,根据自己的 ID 号,这里是搜索 usb摄像头的VID=0x0c45、PID=0x6340。

 

 从图上我们只找到了0c45:6310,实际上我们的usb摄像头是支持UVC的,可以在linux系统下查看:

复制代码
root@zhengyang:~# lsusb -d 0c45:6340 -v | grep "14 Video"
      bFunctionClass         14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
      bInterfaceClass        14 Video
复制代码

如果该摄像头兼容UVC,则会输出上面类似信息,若无以上信息,则是non-UVC设备。

1.4 测试摄像头

在linux下,安装 xawtv 测试软件:

root@zhengyang:~# sudo apt-get install xawtv

执行xawtv后面带usb摄像头的设备节点:

root@zhengyang:~# xawtv /dev/video0
This is xawtv-3.103, running on Linux/x86_64 (4.15.0-142-generic)
xinerama 0: 2560x1440+0+0
Xlib:  extension "XVideo" missing on display "localhost:10.0".
Xlib:  extension "XVideo" missing on display "localhost:10.0".
vid-open-auto: using grabber/webcam device /dev/video0
Alsa devices: cap: hw:1,0 (/dev/video0), out: default
Warning: Missing charsets in String to FontSet conversion

如果在运行时出现如下错误:v4l2:oops:select timeout,且界面显示的图像卡顿:

解决办法:修改虚拟机设置,将USB控制器选项中的 USB 兼容性 选择为 USB 3.0 即可。

可以看到如下图像:

 二、内核配置UVC

确定usb摄像头在台式机上可以使用之后,就需要让它在我们的开发板也能用上这个摄像头。但是将摄像头接插接入Mini2440开发版的usb接口,发现内核却没显示 PC机上打印的信息。

2.1 配置内核

默认情况是没有编译UVC驱动进内核的,我们运行make menuconfig,进行如下配置(下面只把需要变更的配置项展示出来了):

Device Drivers -->

<*> Multimedia support --->

[*]Cameras/video grabbers support

[*]Media Controller API(NEW)

[*]V4L2 sub-device userspace API(NEW)

[*] Media USB Adapters(启用usb总线的媒体驱动程序,drivers/media/usb/*)-->

<*> USB Video Class(UVC)(我们的摄像头支持UVC,选择这个驱动即可)

[*] UVC input events device  supports(NEW)

<> GSPCA based webcams --> (GSPCA 是一个法国程序员在业余时间制作的一个万能USB 摄像头驱动程序,在此可以选择对应类型USB摄像头的支持)

<> SONIX Bayer USB Camer Driver (NEW)

<> OV772x/OV965x/... 系列摄像头支持

<> ....  

[*]V4L platform devices

<*> USB support  -->

[*] USB announce new devices(输出识别的每个usb设备的基本信息,比如idVendor、idProduct、制造商、产品、和序列号等)

修改完配置后,保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:

mv s3c2440_defconfig ./arch/arm/configs/

此时重新执行:

make s3c2440_defconfig

2.2 编译内核和模块

如果修改了内核代码,则需要重新编译内核:

root@zhengyang:~# cd /work/sambashare/linux-5.2.8/
make V=1 uImage

将uImage复制到tftp服务器路径下:

 cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

2.3 烧录内核

开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。

设置开发板ip地址,从而可以使用网络服务:

复制代码
SMDK2440 # set ipaddr 192.168.0.105
SMDK2440 # save
Saving Environment to NAND...
Erasing NAND...

Erasing at 0x40000 -- 100% complete.
Writing to NAND... OK
SMDK2440 # ping 192.168.0.200
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 08:00:3e:26:0a:5b
operating at unknown: 0 mode
Using dm9000 device
host 192.168.0.200 is alive
复制代码

设置tftp服务器地址,也就是我们ubuntu服务器地址:

set serverip 192.168.0.200
save

下载内核到内存,并写NAND FLASH:

tftp 30000000 uImage
nand erase.part kernel
nand write 30000000 kernel
bootm

开发板启动后,插入我们的usb摄像头:

复制代码
usb 1-1: new full-speed USB device number 2 using s3c2410-ohci
random: fast init done
usb 1-1: New USB device found, idVendor=0c45, idProduct=6340, bcdDevice= 0.00
usb 1-1: New USB device strings: Mfr=2, Product=1, SerialNumber=0
usb 1-1: Product: USB 2.0 Camera
usb 1-1: Manufacturer: Sonix Technology Co., Ltd.
uvcvideo: Found UVC 1.00 device USB 2.0 Camera (0c45:6340)
uvcvideo 1-1:1.0: Entity type for entity Extension 4 was not initialized!
uvcvideo 1-1:1.0: Entity type for entity Processing 3 was not initialized!
uvcvideo 1-1:1.0: Entity type for entity Camera 1 was not initialized!
input: USB 2.0 Camera: USB Camera as /devices/platform/s3c2410-ohci/usb1/1-1/1-1:1.0/input/input0
复制代码

从上面的信息可以看到这里,已经识别到了我们的usb摄像头,VID=0c45、PID=6340,并且注册了事件类输入设备节点/dev/intput/event0:

[root@zy:/]# ls /dev/input/event0 -l
crw-rw----    1 0        0          13,  64 Jan  1 00:00  /dev/input/event

可以看到主设备号为13,次设备号从64,设备类型为字符设备。

同时我们可以用 ls /dev/video0 -l 查看设备节点:

[root@zy:/]# ls /dev/video0 -l 
crw-rw----    1 0        0          81,   0 Jan  1 00:00 35m/dev/video0

三、UVC驱动分析

在linux内核5.2.8中,UVC的驱动在drivers/media/usb/uvc/文件夹下,如下:

Kconfig   uvc_ctrl.c     uvc_driver.c  uvc_isight.c    uvc_queue.c   uvc_v4l2.c   uvcvideo.h
Makefile  uvc_debugfs.c  uvc_entity.c  uvc_metadata.c  uvc_status.c  uvc_video.c

3.1 Kconfig

我们首先来看一下Kconfig文件,这个文件定义了配置项USB_VIDEO_CLASS,和USB_VIDEO_CLASS_INPUT_EVDEV:

复制代码
# SPDX-License-Identifier: GPL-2.0-only
config USB_VIDEO_CLASS
        tristate "USB Video Class (UVC)"
        depends on VIDEO_V4L2
        select VIDEOBUF2_VMALLOC
        help
          Support for the USB Video Class (UVC).  Currently only video
          input devices, such as webcams, are supported.

          For more information see: <http://linux-uvc.berlios.de/>

config USB_VIDEO_CLASS_INPUT_EVDEV
        bool "UVC input events device support"
        default y
        depends on USB_VIDEO_CLASS
        depends on USB_VIDEO_CLASS=INPUT || INPUT=y
        help
          This option makes USB Video Class devices register an input device
          to report button events.

          If you are in doubt, say Y.
~
复制代码

我们再来看一下Makefile文件:

# SPDX-License-Identifier: GPL-2.0
uvcvideo-objs  := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \
                  uvc_status.o uvc_isight.o uvc_debugfs.o uvc_metadata.o
ifeq ($(CONFIG_MEDIA_CONTROLLER),y)
uvcvideo-objs  += uvc_entity.o
endif
obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o

可以看到内核编译时配置了CONFIG_USB_VIDEO_CLASS,将会编译uvcvideo.o文件,该文件是由以下依赖链接生成:

uvcvideo-objs  := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \
                  uvc_status.o uvc_isight.o uvc_debugfs.o uvc_metadata.o

3.2 uvc_driver.c

uvc_driver.c文件是UVC驱动模块的入口文件,我们定位到模块的入口和出口函数:

复制代码
static int __init uvc_init(void)
{
        int ret;

        uvc_debugfs_init();

        ret = usb_register(&uvc_driver.driver);
        if (ret < 0) {
                uvc_debugfs_cleanup();
                return ret;
        }

        printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
        return 0;
}

static void __exit uvc_cleanup(void)
{
        usb_deregister(&uvc_driver.driver);
        uvc_debugfs_cleanup();
}

module_init(uvc_init);
module_exit(uvc_cleanup);
复制代码

由于UVC属于usb设备,所以在uvc_init函数,通过usb_register进行了usb接口驱动uvc_driver.driver的注册。

3.2.1 uvc_driver
复制代码
struct uvc_driver {
    struct usb_driver driver;
};
struct uvc_driver uvc_driver = {
        .driver = {
                .name           = "uvcvideo",
                .probe          = uvc_probe,
                .disconnect     = uvc_disconnect,
                .suspend        = uvc_suspend,
                .resume         = uvc_resume,
                .reset_resume   = uvc_reset_resume,
                .id_table       = uvc_ids,
                .supports_autosuspend = 1,
        },
};
复制代码

当在系统中insmod装载该驱动程序时,会在入口函数直接注册usb_drvier结构体,通过比较usb设备提供的信息,和id_table比较,若匹配,则表明驱动支持该usb。uvc_ids里面可以填充某个厂家特定的设备,也可以填充通用的设备,如下所示:

复制代码
/*
 * The Logitech cameras listed below have their interface class set to
 * VENDOR_SPEC because they don't announce themselves as UVC devices, even
 * though they are compliant.
 */
static const struct usb_device_id uvc_ids[] = {
        /* LogiLink Wireless Webcam */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0416,
          .idProduct            = 0xa91a,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Genius eFace 2025 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0458,
          .idProduct            = 0x706e,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Microsoft Lifecam NX-6000 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x045e,
          .idProduct            = 0x00f8,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Microsoft Lifecam NX-3000 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x045e,
          .idProduct            = 0x0721,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Microsoft Lifecam VX-7000 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x045e,
          .idProduct            = 0x0723,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
 /* Logitech Quickcam Fusion */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c1,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam Orbit MP */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c2,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam Pro for Notebook */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c3,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam Pro 5000 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c5,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam OEM Dell Notebook */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c6,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech Quickcam OEM Cisco VT Camera II */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x08c7,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Logitech HD Pro Webcam C920 */
  { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x046d,
          .idProduct            = 0x082d,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_RESTORE_CTRLS_ON_INIT) },
        /* Chicony CNF7129 (Asus EEE 100HE) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x04f2,
          .idProduct            = 0xb071,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_RESTRICT_FRAME_RATE) },
        /* Alcor Micro AU3820 (Future Boy PC USB Webcam) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x058f,
          .idProduct            = 0x3820,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Dell XPS m1530 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x2640,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Dell SP2008WFP Monitor */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x2641,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Dell Alienware X51 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x2643,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Dell Studio Hybrid 140g (OmniVision webcam) */
 { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x264a,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Dell XPS M1330 (OmniVision OV7670 webcam) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05a9,
          .idProduct            = 0x7670,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Apple Built-In iSight */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05ac,
          .idProduct            = 0x8501,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
                                        | UVC_QUIRK_BUILTIN_ISIGHT) },
        /* Apple Built-In iSight via iBridge */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05ac,
          .idProduct            = 0x8600,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* Foxlink ("HP Webcam" on HP Mini 5103) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05c8,
          .idProduct            = 0x0403,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* Genesys Logic USB 2.0 PC Camera */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x05e3,
          .idProduct            = 0x0505,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Hercules Classic Silver */
  { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x06f8,
          .idProduct            = 0x300c,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* ViMicro Vega */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0ac8,
          .idProduct            = 0x332d,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* ViMicro - Minoru3D */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0ac8,
          .idProduct            = 0x3410,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* ViMicro Venus - Minoru3D */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0ac8,
          .idProduct            = 0x3420,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
        /* Ophir Optronics - SPCAM 620U */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0bd3,
          .idProduct            = 0x0555,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* MT6227 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x0e8d,
          .idProduct            = 0x0004,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
                                        | UVC_QUIRK_PROBE_DEF) },
        /* IMC Networks (Medion Akoya) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x13d3,
          .idProduct            = 0x5103,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* JMicron USB2.0 XGA WebCam */
  { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x152d,
          .idProduct            = 0x0310,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Syntek (HP Spartan) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x5212,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (Samsung Q310) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x5931,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (Packard Bell EasyNote MX52 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x8a12,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (Asus F9SG) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x8a31,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (Asus U3S) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x8a33,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Syntek (JAOtech Smart Terminal) */
{ .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x174f,
          .idProduct            = 0x8a34,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Miricle 307K */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x17dc,
          .idProduct            = 0x0202,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Lenovo Thinkpad SL400/SL500 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x17ef,
          .idProduct            = 0x480b,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_stream_no_fid },
        /* Aveo Technology USB 2.0 Camera */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1871,
          .idProduct            = 0x0306,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
                                        | UVC_QUIRK_PROBE_EXTRAFIELDS) },
        /* Aveo Technology USB 2.0 Camera (Tasco USB Microscope) */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1871,
          .idProduct            = 0x0516,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Ecamm Pico iMage */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x18cd,
          .idProduct            = 0xcafe,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_EXTRAFIELDS) },
        /* Manta MM-353 Plako */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x18ec,
          .idProduct            = 0x3188,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax }
   /* FSC WebCam V30S */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x18ec,
          .idProduct            = 0x3288,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Arkmicro unbranded */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x18ec,
          .idProduct            = 0x3290,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_def },
        /* The Imaging Source USB CCD cameras */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x199e,
          .idProduct            = 0x8102,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0 },
        /* Bodelin ProScopeHR */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_DEV_HI
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x19ab,
          .idProduct            = 0x1000,
          .bcdDevice_hi         = 0x0126,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_STATUS_INTERVAL) },
        /* MSI StarCam 370i */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1b3b,
          .idProduct            = 0x2951,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* Generalplus Technology Inc. 808 Camera */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1b3f,
          .idProduct            = 0x2002,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_probe_minmax },
        /* SiGma Micro USB Web Camera */
  { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x1c4f,
          .idProduct            = 0x3000,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_QUIRK(UVC_QUIRK_PROBE_MINMAX
                                        | UVC_QUIRK_IGNORE_SELECTOR_UNIT) },
        /* Oculus VR Positional Tracker DK2 */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x2833,
          .idProduct            = 0x0201,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_force_y8 },
        /* Oculus VR Rift Sensor */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x2833,
          .idProduct            = 0x0211,
          .bInterfaceClass      = USB_CLASS_VENDOR_SPEC,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = (kernel_ulong_t)&uvc_quirk_force_y8 },
        /* Intel RealSense D4M */
        { .match_flags          = USB_DEVICE_ID_MATCH_DEVICE
                                | USB_DEVICE_ID_MATCH_INT_INFO,
          .idVendor             = 0x8086,
          .idProduct            = 0x0b03,
          .bInterfaceClass      = USB_CLASS_VIDEO,
          .bInterfaceSubClass   = 1,
          .bInterfaceProtocol   = 0,
          .driver_info          = UVC_INFO_META(V4L2_META_FMT_D4XX) },
        /* Generic USB Video Class */
        { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_UNDEFINED) },
        { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_15) },
        {}
};
View Code
复制代码

数组最后一个元素:

        /*这个设备属于USB_CLASS_VIDEO类(14),子类是1,协议是0/1*/
        { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_UNDEFINED) },
        { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_15) },

假设我们接入一个usb摄像头,而这个usb摄像头的信息在uvc_ids中,驱动usb_driver.probe函数将会被调用。

3.2.2 uvc_probe
复制代码
static int uvc_probe(struct usb_interface *intf,
                     const struct usb_device_id *id)
{
        struct usb_device *udev = interface_to_usbdev(intf);  // 根据usb接口获取usb设备对象
        struct uvc_device *dev;        // uvc设备
        const struct uvc_device_info *info =
                (const struct uvc_device_info *)id->driver_info;
        int function;
        int ret;

        if (id->idVendor && id->idProduct)
                uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s "
                                "(%04x:%04x)\n", udev->devpath, id->idVendor,
                                id->idProduct);
        else
                uvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s\n",
                                udev->devpath);

        /* Allocate memory for the device and initialize it. */
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);  // 动态申请内存空间
        if (dev == NULL)
                return -ENOMEM;

        INIT_LIST_HEAD(&dev->entities);
        INIT_LIST_HEAD(&dev->chains);
        INIT_LIST_HEAD(&dev->streams);
        kref_init(&dev->ref);
        atomic_set(&dev->nmappings, 0);
        mutex_init(&dev->lock);

        dev->udev = usb_get_dev(udev);          // 初始化uvc设备
        dev->intf = usb_get_intf(intf);
        dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
        dev->info = info ? info : &uvc_quirk_none;
        dev->quirks = uvc_quirks_param == -1
                    ? dev->info->quirks : uvc_quirks_param;

        if (udev->product != NULL)
                strscpy(dev->name, udev->product, sizeof(dev->name));
        else
                snprintf(dev->name, sizeof(dev->name),
                         "UVC Camera (%04x:%04x)",
                         le16_to_cpu(udev->descriptor.idVendor),
                         le16_to_cpu(udev->descriptor.idProduct));

        /*
         * Add iFunction or iInterface to names when available as additional
         * distinguishers between interfaces. iFunction is prioritized over
         * iInterface which matches Windows behavior at the point of writing.
         */
        if (intf->intf_assoc && intf->intf_assoc->iFunction != 0)
                function = intf->intf_assoc->iFunction;
        else
                function = intf->cur_altsetting->desc.iInterface;
        if (function != 0) {
                size_t len;

                strlcat(dev->name, ": ", sizeof(dev->name));
                len = strlen(dev->name);
                usb_string(udev, function, dev->name + len,
                           sizeof(dev->name) - len);
        }

        /* Parse the Video Class control descriptor. */
        if (uvc_parse_control(dev) < 0) {
                uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
                        "descriptors.\n");
                goto error;
        }

        uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",
                dev->uvc_version >> 8, dev->uvc_version & 0xff,
                udev->product ? udev->product : "<unnamed>",
                le16_to_cpu(udev->descriptor.idVendor),
                le16_to_cpu(udev->descriptor.idProduct));

        if (dev->quirks != dev->info->quirks) {
                uvc_printk(KERN_INFO, "Forcing device quirks to 0x%x by module "
                        "parameter for testing purpose.\n", dev->quirks);
                uvc_printk(KERN_INFO, "Please report required quirks to the "
                        "linux-uvc-devel mailing list.\n");
        }

        /* Initialize the media device and register the V4L2 device. */
#ifdef CONFIG_MEDIA_CONTROLLER
        dev->mdev.dev = &intf->dev;
        strscpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model));
        if (udev->serial)
                strscpy(dev->mdev.serial, udev->serial,
                        sizeof(dev->mdev.serial));
        usb_make_path(udev, dev->mdev.bus_info, sizeof(dev->mdev.bus_info));
        dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
        media_device_init(&dev->mdev);

        dev->vdev.mdev = &dev->mdev;
#endif
        if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
                goto error;

        /* Initialize controls. */
        if (uvc_ctrl_init_device(dev) < 0)
                goto error;

        /* Scan the device for video chains. */
        if (uvc_scan_device(dev) < 0)
                goto error;

        /* Register video device nodes. */
        if (uvc_register_chains(dev) < 0)
                goto error;

#ifdef CONFIG_MEDIA_CONTROLLER
        /* Register the media device node */
        if (media_device_register(&dev->mdev) < 0)
                goto error;
#endif
        /* Save our data pointer in the interface data. */
        usb_set_intfdata(intf, dev);

        /* Initialize the interrupt URB. */
        if ((ret = uvc_status_init(dev)) < 0) {
                uvc_printk(KERN_INFO, "Unable to initialize the status "
                        "endpoint (%d), status interrupt will not be "
                        "supported.\n", ret);
        }

        uvc_trace(UVC_TRACE_PROBE, "UVC device initialized.\n");
        usb_enable_autosuspend(udev);
        return 0;

error:
        uvc_unregister_video(dev);
        kref_put(&dev->ref, uvc_delete);
        return -ENODEV;
}
复制代码

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(8181)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2018-08-21 第十一节、Harris角点检测原理(附源码)
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示