V4L2学习笔记
+ -

Linux V4L2框架

2021-04-01 4449 1

Linux系统支持多钟多样的Video设备,如通过USB总线连接的相机,通过PCI设备连接的摄像头,通过MIPI总线连接其它Video设备。故Linux系统专门开发了V4L2子设备用来管理此类设备。

V4L2子系统向上为虚拟文件系统VFS提供一的接口,这样应用程序就可以通过虚拟文件系统访问视频设备。
V4L2子设系统向上为Video设备提供提口,同时管理所有的Video设备。对于V4L2来说,将设备为分主设备从设备,其中主设备用于传输图像数据,而从设备用于控制数据传输,如图像的大小,图像的分辨率和帧率等。当然主设备和从设备从也可以进行通讯。主设备可通过v4l2_subdev_call的宏调用从设备提供的方法,反过来从设备可以调用主设备的notify方法通知主设备某些事件发生了。

V4L2

图中V4L2是通过主要是通过总线数据解析或者硬件实际情况创建相应的主从设备。实际V4L2驱动层下可能连接的是PCI下设备,或者对于USB设备,其下为UVC驱动,UVC驱动下为USB core。

V4L2当前支持以下几种设备类型

名称 类型 minor范围 备注
/dev/video[X] VFL_TYPE_GRABBER 0~63 视频输入、输出设备
/dev/vbi[X] VFL_TYPE_RADIO 64~127 用于收音机调谐器
/dev/radio[X] VFL_TYPE_VBI 224~255 用于垂直空白数据(即闭路字幕、图文电视)
/dev/v4l-subdev[X] VFL_TYPE_SUBDEV 128~192 子设备
/dev/swradio[X] VFL_TYPE_SDR 128~192 软件定义的无线电调谐器
/dev/v4l-touch[X] VFL_TYPE_TOUCH 128~192 触摸传感器

V4L2总共约三类设备:

  • 主设备 struct video_device,通过video_register_device注册
  • 子设备 struct v4l2_sub_dev,通过v4l2_device_register_subdev注册
  • 管理设备struct v4l2_device,通过v4l2_device_register注册

他们三者的数据关系:

  • struct skeleton
    • struct video_device
    • struct v4l2_device
      • [链表]struct v4l2_sub_dev

在V4L2子系统中,Video设备是一个字符设备,设备节点为/dev/videoX,主设备号为81,次设备号范围为0-63。
在用户空间,应用可以通过open/close/ioctl/mmap/read/write系统调用操作Video设备。在内核空间中,Video设备的具体操作方法由驱动中的struct video_device提供。驱动使用video_register_device函数将struct video_device注册到V4L2的核心层,然后V4L2的核心层在向上注册一个字符设备,该字符设备实现了虚拟文件系统要求的方法。这样应用就可以使用系统调用访问虚拟文件系统中Video设备提供的方法,然后进一步访问V4L2核心层提供的v4l2_fops方法集合,最后通过struct video_device结构体中的fops和ioctl_ops方法集合访问Video主设备。Video主设备通过v4l2_subdev_call方法访问Video从设备,同时Video从设备可以通过notify回掉方法通知主设备发生了事件。Camera Host控制器为Video主设备,Camear Sensor(摄像头)为Video从设备,一般为I2C设备。
173052866158

所以,提供给上层VFS的接口是struct video_deivce创建的字符设备的ops,其变量名为v4l2_fops。
而在v4l2_fops的回调函数集中,则根据实际情况调用struct video_device的const struct v4l2_ioctl_ops ioctl_ops或const struct v4l2_file_operations fops 或 子设备。


V4L2 :video for linux version 2 ,是 Linux 里一套标准的视频驱动,它支持 UVC 标准的摄像头。本文来分析一下它的核心框架。
V4L2 UVC摄像头框架浅析

static inline int __must_check video_register_device(struct video_device *vdev,enum vfl_devnode_type type, int nr)
{
    return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}
+-----------------------------------------+
|              应用层 (Application)        | 例如:OpenCV, GStreamer, VLC, 自定义程序
| 使用 V4L2 API (open, ioctl, mmap, read) | 功能:配置设备、发起数据流请求、处理图像数据(显示、编码、分析)
+----------------------+------------------+
                       |
+----------------------v------------------+
|             V4L2 核心框架 (Videobuf2)     | 内核空间 (Kernel Space)
| 功能:提供统一的API接口、管理缓冲区、路由数据 | 承上启下,将应用层请求转发给正确的设备驱动
+----------------------+------------------+
                       |
+----------------------v------------------+
|          设备特定驱动 (Device Driver)     | 例如:uvcvideo (USB Video Class driver)
| 功能:直接控制硬件、翻译V4L2命令为硬件操作、   | 处理USB通信、生成中断、管理硬件缓冲区
|       将原始图像数据填入V4L2框架的缓冲区     |
+----------------------+------------------+
                       |
+----------------------v------------------+
|                硬件层 (Hardware)         | USB摄像头本身
| 功能:通过USB总线传输图像数据、响应控制请求     |
+-----------------------------------------+

整个v4l2的框架分为三层:

  • 在应用层,我们可以在 /dev 目录发现 video0 类似的设备节点,上层的摄像头程序打开设备节点进行数据捕获,显示视频画面。设备节点的名字很统一,video0 video1 video2…这些设备节点在是核心层注册。

  • 核心层 v4l2-dev.c,承上启下,对于每一个硬件相关层注册进来的设备,设置一个统一的接口 v4l2_fops ,既然是统一的接口必然不是具体的视频设备的操作函数,应用层调用 v4l2_fops 中的函数最终将调用到硬件相关层的 video_device 的 fops 。

  • 硬件相关层,与具体的视频硬件打交道,分配、设置、注册 video_device 结构体。

通过分析vivi.c结构如下
vivi_init

   -->vivi_create_instance

        -->v4l2_device_register   // 不是主要, 只是用于初始化一些东西,比如自旋锁、引用计数

                 vfd = video_device_alloc(); //分配video_device结构体

             1.  *vfd = vivi_template; // 设置

              .fops           = &vivi_fops,
              .ioctl_ops    = &vivi_ioctl_ops,
              .release      = video_device_release,

     2.  vfd->v4l2_dev = &dev->v4l2_dev;

     3.  设置"ctrl属性"(用于APP的ioctl):(设置ctrl的属性让ioctl调用或者使用例如亮度等在驱动程序中抽象出来一个结构体 v4l2_ctrl,每个Ctrl对应其中的一项(音量、亮度等等)v4l2_ctrl_handler来管理他们)
       v4l2_ctrl_handler_init(hdl, 11);
       dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops, V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
       dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
       dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_CONTRAST, 0, 255, 1, 16);      

    4. video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);  //注册

       --> __video_register_device(vdev, type, nr, 1, vdev->fops->owner);

         -->vdev->cdev = cdev_alloc();  (v4l2.dev.c程序中)

           vdev->cdev->ops = &v4l2_fops;

           cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);

1617250498978

通过V4L2驱动框架知道如何编写V4L2驱动

  1. 分配、设置、注册:v4l2_device —》 v4l2_device_register()(辅助作用,提供自旋锁、引用计数等功能)

  2. 分配一个video_device:video_device_alloc()

  3. 设置

1)vfd->v4l2_dev

2) .fops 设置vfd的fops 里的open、read、write 被上层调用
  .ioctl_ops 设置属性被上层调用

3)注册:video_register_device()

  1. 接下来,应用层App可以通过ioctl来设置(获得)亮度等某些属性,在驱动程序里,谁来接收、存储、设置到硬件(提供这些信息)?

    在驱动程序中抽象出来一个结构体v4l2_ctrl,每个Ctrl对应其中的一项(音量、亮度等等);

    v4l2_ctrl_handler来管理他们,在vivi.c的vivi_create_instance函数中:

    1.初始化
    v4l2_ctrl_handler_init
    2.设置
    v4l2_ctrl_new_std
    v4l2_ctrl_new_custom
    这些函数就是创建各个属性,并且放入v4l2_ctrl_handler的链表
    3.跟vdev关联
    dev->v4l2_dev.ctrl_handler = hdl;

static int __init videodev_init(void)  
{  
    /* 申请设备号,留给 video 设备使用 */  
    dev_t dev = MKDEV(VIDEO_MAJOR, 0);  
    ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);  
    /* 创建 video 类 */  
    ret = class_register(&video_class);  
    return 0;  
}
struct video_device  
{  
    /* device ops */  
    const struct v4l2_file_operations *fops;  

    /* sysfs */  
    struct device dev;      /* v4l device */  
    struct cdev *cdev;      /* character device */  

    /* Set either parent or v4l2_dev if your driver uses v4l2_device */  
    struct device *parent;          /* device parent */  
    struct v4l2_device *v4l2_dev;   /* v4l2_device parent */  

    /* Control handler associated with this device node. May be NULL. */  
    struct v4l2_ctrl_handler *ctrl_handler;  

    /* Priority state. If NULL, then v4l2_dev->prio will be used. */  
    struct v4l2_prio_state *prio;  

    /* device info */  
    char name[32];  
    int vfl_type;  
    /* 'minor' is set to -1 if the registration failed */  
    int minor;  
    u16 num;  
    /* use bitops to set/clear/test flags */  
    unsigned long flags;  
    /* attribute to differentiate multiple indices on one physical device */  
    int index;  

    /* V4L2 file handles */  
    spinlock_t      fh_lock; /* Lock for all v4l2_fhs */  
    struct list_head    fh_list; /* List of struct v4l2_fh */  

    int debug;          /* Activates debug level*/  

    /* Video standard vars */  
    v4l2_std_id tvnorms;        /* Supported tv norms */  
    v4l2_std_id current_norm;   /* Current tvnorm */  

    /* callbacks */  
    void (*release)(struct video_device *vdev);  

    /* ioctl callbacks */  
    const struct v4l2_ioctl_ops *ioctl_ops;  
    DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);  

    /* serialization lock */  
    DECLARE_BITMAP(disable_locking, BASE_VIDIOC_PRIVATE);  
    struct mutex *lock;  
};
struct v4l2_device {  
    struct device *dev;  
    /* used to keep track of the registered subdevs */  
    struct list_head subdevs;  
    spinlock_t lock;  
    char name[V4L2_DEVICE_NAME_SIZE];  
    void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);  
    struct v4l2_ctrl_handler *ctrl_handler;  
    struct v4l2_prio_state prio;  
    struct mutex ioctl_lock;  
    struct kref ref;  
    void (*release)(struct v4l2_device *v4l2_dev);  
};
static inline int __must_check video_register_device(struct video_device *vdev,  
        int type, int nr)  
{  
    return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);  
}  

int __video_register_device(struct video_device *vdev, int type, int nr,  
        int warn_if_nr_in_use, struct module *owner)  
{  
    int i = 0;  
    int ret;  
    int minor_offset = 0;  
    int minor_cnt = VIDEO_NUM_DEVICES;  
    const char *name_base;  

    /* A minor value of -1 marks this video device as never having been registered */  
    vdev->minor = -1;  

    /* 视频设备的设备节点一般为 video0 ..video 就是由此而来 */  
    switch (type) {  
    case VFL_TYPE_GRABBER:  
        name_base = "video";  
        break;  
    case VFL_TYPE_VBI:  
        name_base = "vbi";  
        break;  
    ...  
    }  

    vdev->vfl_type = type;  
    vdev->cdev = NULL;  

    /* Part 2: find a free minor, device node number and device index. */  
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES  
    switch (type) {  
    case VFL_TYPE_GRABBER:  
        minor_offset = 0;  
        minor_cnt = 64;  
        break;  
    ...  
#endif  

    /* 寻找一个空的项,这个好像并不重要 */  
    mutex_lock(&videodev_lock);  
    nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);  
    if (nr == minor_cnt)  
        nr = devnode_find(vdev, 0, minor_cnt);  
    if (nr == minor_cnt) {  
        printk(KERN_ERR "could not get a free device node number\n");  
        mutex_unlock(&videodev_lock);  
        return -ENFILE;  
    }  
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES  
    /* 1-on-1 mapping of device node number to minor number */  
    i = nr;  
#else  
    /* 在全局 video_deivce 数组中寻找一个空的项,下标+minor_offset作为设备的次设备号 */  
    for (i = 0; i < VIDEO_NUM_DEVICES; i++)  
        if (video_device[i] == NULL)  
            break;  
    if (i == VIDEO_NUM_DEVICES) {  
        mutex_unlock(&videodev_lock);  
        printk(KERN_ERR "could not get a free minor\n");  
        return -ENFILE;  
    }  
#endif  
    vdev->minor = i + minor_offset;  
    vdev->num = nr;  
    devnode_set(vdev);  

    if (vdev->ioctl_ops)  
        determine_valid_ioctls(vdev);  

    /* 注册字符设备 */  
    vdev->cdev = cdev_alloc();  
    vdev->cdev->ops = &v4l2_fops;  
    vdev->cdev->owner = owner;  
    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);  

    /* 得把device注册进内核,mdev才能自动创建设备节点,/dev 目录下的video0 等就是来自这里 */  
    vdev->dev.class = &video_class;  
    vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);  
    if (vdev->parent)  
        vdev->dev.parent = vdev->parent;  
    dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);  
    ret = device_register(&vdev->dev);  

    vdev->dev.release = v4l2_device_release;  

    /* Part 6: Activate this minor. The char device can now be used. */  
    set_bit(V4L2_FL_REGISTERED, &vdev->flags);  
    mutex_lock(&videodev_lock);  
    video_device[vdev->minor] = vdev;  
    mutex_unlock(&videodev_lock);  

    return 0;  

}  
EXPORT_SYMBOL(__video_register_device);
static const struct file_operations v4l2_fops = {  
    .owner = THIS_MODULE,  
    .read = v4l2_read,  
    .write = v4l2_write,  
    .open = v4l2_open,  
    .get_unmapped_area = v4l2_get_unmapped_area,  
    .mmap = v4l2_mmap,  
    .unlocked_ioctl = v4l2_ioctl,  
#ifdef CONFIG_COMPAT  
    .compat_ioctl = v4l2_compat_ioctl32,  
#endif  
    .release = v4l2_release,  
    .poll = v4l2_poll,  
    .llseek = no_llseek,  
};
static int v4l2_open(struct inode *inode, struct file *filp)  
{  
    struct video_device *vdev = video_devdata(filp);  
    if (vdev->fops->open) {  

        if (video_is_registered(vdev))  
            ret = vdev->fops->open(filp);  
    }  
}  
static ssize_t v4l2_read(struct file *filp, char __user *buf,  
        size_t sz, loff_t *off)  
{  
    struct video_device *vdev = video_devdata(filp);  
    if (!vdev->fops->read)  
        return -EINVAL;  
    if (video_is_registered(vdev))  
        ret = vdev->fops->read(filp, buf, sz, off);  
}  
static int v4l2_mmap(struct file *filp, struct vm_area_struct *vm)  
{  
    struct video_device *vdev = video_devdata(filp);  
    if (!vdev->fops->mmap)  
        return ret;  
    ret = vdev->fops->mmap(filp, vm);  
}  

static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)  
{  
    struct video_device *vdev = video_devdata(filp);  
    if (vdev->fops->unlocked_ioctl) {  
        if (video_is_registered(vdev))  
            ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);  
    } else if (vdev->fops->ioctl) {  
        if (video_is_registered(vdev))  
            ret = vdev->fops->ioctl(filp, cmd, arg);  
    }   
}

0 篇笔记 写笔记

Linux V4L2框架
Linux系统支持多钟多样的Video设备,如通过USB总线连接的相机,通过PCI设备连接的摄像头,通过MIPI总线连接其它Video设备。故Linux系统专门开发了V4L2子设备用来管理此类设备。V4L2子系统向上为虚拟文件系统VFS提供一的接口,这样应用程序就可以通过虚拟文件系统访问视频设备。......
linux下UsbMon-WireShark之USB协议抓取分析
usbmon配置使用usbmon抓包分的,是需要 内核开启CONFIG_USB_MON=m, 重新编译内核, 编译ko :make ARCH=arm64 CROSS_COMPILE=aarch64-himix100-linux- CONFIG_USB_MON=m M=./drivers/usb/ ......
Linux支持的UVC视频格式
Linux支持的UVC视频格式位于linux-5.6.11linux-5.6.11driversmediausbuvcuvc_driver.c文件下:其数组结构定义如下:static struct uvc_format_desc uvc_fmts[] = { { .n......
Linux源码分析UVC摄像头的初始化流程分析
UVC摄像头的初始化发生在硬件被接入USB集线器中,设备初USB驱动识别为摄像头的后续初始化流程。和Windows的AddDevice驱动函数一样,Linux设备的创建和侦测是通过int uvc_probe函数实现的。其函数的调用关系如下://linux/v5.11.11/source/drive......
ubuntu下使用usbmon进行usb抓包
开发或者调试USB设备相关的工具或者驱动,一个调试的利器就是usbmon抓包。 在ubuntu下使用步骤如下:1 运行命令 sudo mount -t debugfs none /sys/kernel/debug ,如果提示已经挂载,则下次抓包就可以不运行这个命令了。表示系统默认会挂载。 2 ......
LINUX&UVC输出终端描述符分析
UVC输出终端描述符用于描述UVC视频流的输出端。关于UVC输出终端描述符各字段的分析详见:https://www.usbzh.com/article/detail-10.htmlUVC输出终端描述符比较简单,没有什么特别的字段。惟一要关注的就是bSourceID。bSourceID:此终端所连接......
LINUX&UVC处理单元描述符
不同的UVC版本,其UVC处理单元描述格式稍有不同。关于UVC处理单元描述符各字段的详细说明可详见:https://www.usbzh.com/article/detail-84.html对于UVC1.1/1.5版本,其内容如下: UINT8 bLength; UINT8 bDescr......
Linux源码分析UVC摄像头的打开流程及抓包分析
和关闭摄像头类似,Linux使用uvc_video_start_streaming函数打开摄像头int uvc_video_start_streaming(struct uvc_streaming *stream){ int ret; ret = uvc_video_clock_init......
V4L2访问摄像头扩展单元命令
我们可以通过IOCTL访问扩展单元,调用方法如下:ioctl(fd, UVCIOC_CTRL_QUERY, struct uvc_xu_control_query *);访问不同的扩展命令只需要修改uvc_xu_control_query 结构体里面内容即可。uvc_xu_control_qu......
Linux对hidraw设备output report大小的限制
做了个自定义HID设备,可以收发数据,用它来作固件升级。主要是host通过output report下发固件数据,所以output report的size设置的比较大,有4kb,这样升级速度会快一些。经过测试在Windows xp、win7,win10上都木有问题,在linux上出现问题了。设......
USB键盘在Linux环境下一直返回NAK的输入端点和一直OUT数据的输出端点
群里有同学反馈,自己做的USB键盘在Windows下正常,但在Linux下就失败,想让帮忙分析一下原因。一个比较好的消息是他那边有USB总线分析仪,所以只需要抓包就可以进行分析了。最好开他给的抓包截图是样子的:从它的截图可以看到,USB键盘在获取了该键盘的HID报告描述符后,紧跟着一下发Report......
Windows和Linux不同主机下USB设备枚举过程中的差别
第一次获取设备描述符的不同USB设备刚上电时,是通过端点0使用控制方式来获取设备描述符。不同的设备模式获取端点0的大小不同的:高速模式的端点0最大包长固定为64个字节;全速模式可端点0在8、16、32、64字节中选择;低速模式的端点0最大包长固定为8个字节由于USB主机和USB设备第一次通讯时......
Linux系统V4L2访问UVC摄像头扩展单元命令
我们可以通过IOCTL访问扩展单元,调用方法如下:ioctl(fd, UVCIOC_CTRL_QUERY, struct uvc_xu_control_query *);访问不同的扩展命令只需要修改uvc_xu_control_query 结构体里面内容即可。uvc_xu_control_qu......
Linux打开V4L2摄像头并存储Camera数据流
Linux系统下打开UVC摄像头,并将从CAMERA读取到的数据存储在文件中。源代码版权归老吕、所有。感谢老吕、的无私贡献。v4l2_capture_demo.c#include #include #include
UVC输入终端是UVC设备拓扑结构中数据流的起始节点。UVC输入终端使用UVC输入终端描述符来描述.关于该描述符详细的字段描述详见:https://www.usbzh.com/article/detail-95.html关于相机终端描述符详见:https://www.usbzh.com/articl......
关注公众号
  • HID人机交互
  • Linux&USB
  • UAC音频
  • CDC
  • TYPE-C
  • USB规范
  • USB大容量存储
  • USB百科
  • USB周边
  • UVC摄像头
  • Windows系统USB
  • 音视频博客
  • 取消
    感谢您的支持,我会继续努力的!
    扫码支持
    扫码打赏,你说多少就多少

    打开支付宝扫一扫,即可进行扫码打赏哦

    您的支持,是我们前进的动力!