2.1 摄像头V4L2驱动框架分析

本文深入解析V4L2(Video for Linux version 2)摄像头驱动框架,通过分析vivi.c源码,总结V4L2硬件相关驱动步骤。讲解了字符类驱动的编写流程,V4L2驱动架构及vivi虚拟视频驱动程序的调用过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习目标:学习V4L2(V4L2:vidio for linux version 2)摄像头驱动框架,分析vivi.c(虚拟视频硬件相关)驱动源码程序,总结V4L2硬件相关的驱动的步骤;

 一、V4L2架构

1. 字符类驱动

V4L2(V4L2:vidio for linux version 2)摄像头驱动属于字符类驱动,

对于一般的字符类驱动程序,其编写步骤一般分为:

1)构造一个file_operations: 编写open=drv_open .read=drv_read
2)注册设备,告诉内核:register_chrdev(主设备号,名字,&file_operations)
3)入口函数:调用register_chrdev
4)出口函数:卸载

对于复杂的字符类驱动程序,其程序是一种分层结构。例如LCD驱动程序。如下图所示。

--> 上层为核心层(内核已经做好的),在fbmem.c中 ,主要的作用为:

1)构造file_operations(open read write 函数);2)注册;3)入口、出口。

--> 硬件相关层(用户需要做的),供核心层的file_operations调用,主要完成:

1) 分配一个fb_info 结构体;2) 设置fb_info 结构体等;3) 注册;4) 硬件相关的操作。 

2. V4L2驱动架构

由以上字符类设备驱动架构可知,摄像头驱动也是分层结构的。

其中,ucv_driver.c中,定义了uvc_driver结构体,根据ucv_ids查找匹配的设备,如果支持,则会进入probe函数

 1 struct uvc_driver uvc_driver = {
 2     .driver = {
 3         .name        = "uvcvideo",
 4         .probe        = uvc_probe,
 5         .disconnect    = uvc_disconnect,
 6         .suspend    = uvc_suspend,
 7         .resume        = uvc_resume,
 8         .reset_resume    = uvc_reset_resume,
 9         .id_table    = uvc_ids,
10         .supports_autosuspend = 1,
11     },
12 };

二. vivi.c虚拟视频驱动程序架构

由于V4L2驱动程序是一种分层架构,用户只需要完成硬件相关驱动程序即可。这里主要以vivi虚拟视频驱动程序为例分析源码的调用过程和框架。

1.  进入入口的vivi_init(void)函数:

  1 static int __init vivi_create_instance(int inst)
  2 {
  3     struct vivi_dev *dev;
  4     struct video_device *vfd; //video_device结构体定义
  5     struct v4l2_ctrl_handler *hdl;
  6     struct vb2_queue *q;
  7     int ret;
  8 
  9     dev = kzalloc(sizeof(*dev), GFP_KERNEL);
 10     if (!dev)
 11         return -ENOMEM;
 12 
 13     snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
 14             "%s-%03d", VIVI_MODULE_NAME, inst);
 15     ret = v4l2_device_register(NULL, &dev->v4l2_dev);
 16     if (ret)
 17         goto free_dev;
 18    //摄像头相关属性设置
 19     dev->fmt = &formats[0];
 20     dev->width = 640;
 21     dev->height = 480;
 22     hdl = &dev->ctrl_handler;
 23     v4l2_ctrl_handler_init(hdl, 11);
 24     dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 25             V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
 26     dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 27             V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
 28     dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 29             V4L2_CID_CONTRAST, 0, 255, 1, 16);
 30     dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 31             V4L2_CID_SATURATION, 0, 255, 1, 127);
 32     dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 33             V4L2_CID_HUE, -128, 127, 1, 0);
 34     dev->autogain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 35             V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
 36     dev->gain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 37             V4L2_CID_GAIN, 0, 255, 1, 100);
55 /* initialize queue */ 56 q = &dev->vb_vidq; 57 memset(q, 0, sizeof(dev->vb_vidq)); 58 q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 59 q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; 60 q->drv_priv = dev; 61 q->buf_struct_size = sizeof(struct vivi_buffer); 62 q->ops = &vivi_video_qops; 63 q->mem_ops = &vb2_vmalloc_memops; 64 65 vb2_queue_init(q); 66 67 mutex_init(&dev->mutex); 68 69 /* init video dma queues */ 70 INIT_LIST_HEAD(&dev->vidq.active); 71 init_waitqueue_head(&dev->vidq.wq); 72 73 ret = -ENOMEM;
     //分配video_device结构体
74 vfd = video_device_alloc(); 75 if (!vfd) 76 goto unreg_dev; 77  //设置 78 *vfd = vivi_template;
  /******************************************************************
    其中,以赋值的方式进行设置vfd,进入vivi_template:
      static struct video_device vivi_template = {
       .name  = "vivi",
       .fops          = &vivi_fops,
       .ioctl_ops    = &vivi_ioctl_ops,
       .release     = video_device_release,
       .tvnorms              = V4L2_STD_525_60,
       .current_norm         = V4L2_STD_NTSC_M,
      };
  *******************************************************************//
 79     vfd->debug = debug;
 80     vfd->v4l2_dev = &dev->v4l2_dev;
 81     set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
 82 
 83     /*
 84      * Provide a mutex to v4l2 core. It will be used to protect
 85      * all fops and v4l2 ioctls.
 86      */
 87     vfd->lock = &dev->mutex;
 88     //注册
 89     ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
93 video_set_drvdata(vfd, dev); 94 95 /* Now that everything is fine, let's add it to device list */ 96 list_add_tail(&dev->vivi_devlist, &vivi_devlist); 97 101 dev->vfd = vfd; 102 v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n", 103 video_device_node_name(vfd)); 104 return 0;
107 video_device_release(vfd);110 v4l2_device_unregister(&dev->v4l2_dev);114 }

 vivi_init函数的调用结构如下:

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

2. vivi.c的open,read,write,ioctl过程

1 static const struct v4l2_file_operations vivi_fops = {
2     .owner        = THIS_MODULE,
3     .open           = v4l2_fh_open,
4     .release        = vivi_close,
5     .read           = vivi_read,
6     .poll         = vivi_poll,
7     .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
8     .mmap           = vivi_mmap,
9 };

1)open

app:     open("/dev/video0",....)向下层调用
-------------------------------------------------------------------
drv:     v4l2_fops.v4l2_open
           vdev = video_devdata(filp);  // 根据次设备号从数组中得到video_device

       return video_device[iminor(file->f_path.dentry->d_inode)];

   if (vdev->fops->open)     //如果有open函数
      if (video_is_registered(vdev))
        ret = vdev->fops->open(filp);//调用open 函数
          调用vivi.c 里的v4l2_fh_open函数

2)read

app:     read("/dev/video0",....)向下层调用
-------------------------------------------------------------------

drv:    v4l2_fops.v4l2_read
            struct video_device *vdev = video_devdata(filp);
            if (video_is_registered(vdev))
      ret = vdev->fops->read(filp, buf, sz, off);

       调用vivi.c 里的vivi_read

3)ioctl
app:   ioctl 
----------------------------------------------------
drv:   v4l2_fops.unlocked_ioctl => v4l2_ioctl
                struct video_device *vdev = video_devdata(filp);
             if (video_is_registered(vdev))
       ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
                       调用vivi.c 里的video_ioctl2
                                video_usercopy(file, cmd, arg, __video_do_ioctl); //从用户空间把用户的命令cmd复制进来,调用__video_do_ioctl
                                    __video_do_ioctl
                                        struct video_device *vfd = video_devdata(file); //根据次设备号从数组中得到video_device
                                        switch (cmd) { .....     // 根据APP传入的cmd来获得、设置"某些属性"
 
--->>(例程分析:2.3节)
v4l2_ctrl_handler的使用过程:
      .......
     case VIDIOC_QUERYCTRL:
     {
      struct v4l2_queryctrl *p = arg;
      if (vfh && vfh->ctrl_handler)
          ret = v4l2_queryctrl(vfh->ctrl_handler, p);
      else if (vfd->ctrl_handler)  // 在video_register_device设置 vivi_create_instance-->hdl = &dev->ctrl_handler;    v4l2_ctrl_handler_init(hdl, 11);
         ret = v4l2_queryctrl(vfd->ctrl_handler, p);  // 根据ID在ctrl_handler里找到v4l2_ctrl,返回它的值
-------->>
 1 hdl = &dev->ctrl_handler;
 2     v4l2_ctrl_handler_init(hdl, 11);
 3     dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 4             V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
 5     dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 6             V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
 7     dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
 8             V4L2_CID_CONTRAST, 0, 255, 1, 16);
 9     dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
10             V4L2_CID_SATURATION, 0, 255, 1, 127);
11     dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
12             V4L2_CID_HUE, -128, 127, 1, 0);
13     dev->autogain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
14             V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
15     dev->gain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
16             V4L2_CID_GAIN, 0, 255, 1, 100);

三、怎么写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()

4. 接下来,应用层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;

 1 static int __init vivi_create_instance(int inst)
 2 {
 3     struct vivi_dev *dev;
 4     struct video_device *vfd;
 5     struct v4l2_ctrl_handler *hdl; //定义v4l2_ctrl_handler结构体
 6     struct vb2_queue *q;
 7     int ret;
 8 
 9     dev = kzalloc(sizeof(*dev), GFP_KERNEL);
10     if (!dev)
11         return -ENOMEM;
12 
13     snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
14             "%s-%03d", VIVI_MODULE_NAME, inst);
15     ret = v4l2_device_register(NULL, &dev->v4l2_dev);
16     if (ret)
17         goto free_dev;
18 
19     dev->fmt = &formats[0];  
20     dev->width = 640;
21     dev->height = 480;
22     hdl = &dev->ctrl_handler;
23     v4l2_ctrl_handler_init(hdl, 11); //初始化
24     dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops, //创建一个属性设置其值,并且放入v4l2_ctrl_handler的链表
25             V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
26     dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
27             V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
28     dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
29             V4L2_CID_CONTRAST, 0, 255, 1, 16);
30     dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
31             V4L2_CID_SATURATION, 0, 255, 1, 127);
32     dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
33             V4L2_CID_HUE, -128, 127, 1, 0);
34     dev->autogain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
35             V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
36     dev->gain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
37             V4L2_CID_GAIN, 0, 255, 1, 100);
38     dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);
39     dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);
40     dev->int64 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int64, NULL);
41     dev->boolean = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_boolean, NULL);
42     dev->menu = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_menu, NULL);

45 dev->v4l2_dev.ctrl_handler = hdl;
    .........
}

 

转载于:https://www.cnblogs.com/lxl-lennie/p/10265132.html

<think>好的,用户让我介绍一下V4L2框架的源码分析。首先,我需要回忆一下V4L2是什么。V4L2是Video4Linux2的缩写,是Linux内核中负责视频设备驱动框架,主要用于摄像头、采集卡等设备的支持。用户可能是一个开发者,想要了解V4L2的内部结构,或者是在开发驱动时遇到了问题,需要深入理解源码。 接下来,我需要考虑如何组织这个分析。通常,源码分析可以从框架结构、核心模块、关键数据结构、流程分析等方面入手。用户可能希望了解V4L2的层次结构,各个模块的作用,以及数据是如何流转的。同时,用户可能对如何编写V4L2驱动或调试问题感兴趣,所以可能需要包括一些实际开发的指导。 首先,我应该介绍V4L2的整体架构,比如用户空间和内核空间的交互,V4L2子系统的组成部分。然后分解核心模块,比如视频设备抽象层、IOCTL接口、缓冲区管理等。数据结构方面,v4l2_device、v4l2_subdev、v4l2_buffer这些是关键的结构体,需要解释它们的用途和关系。 流程分析部分,可以举例子,比如摄像头数据采集的流程,从打开设备到配置参数,再到数据捕获的过程。这部分需要用用户能够理解的语言,避免过于深入内核细节,但又要足够详细,帮助开发者理解工作原理。 另外,用户可能想知道如何阅读和修改源码,所以需要给出一些代码路径,比如内核源码中的drivers/media目录,以及一些典型的驱动示例。可能还需要提到相关的工具,比如v4l2-ctl,这些工具在调试时的作用。 需要检查是否遗漏了V4L2的重要特性,比如支持多种设备类型(摄像头、收音机等),多平面缓冲区的处理,或者DMA相关的内容。还有,V4L2的事件机制和异步操作可能也是需要提及的部分。 要确保回答结构清晰,分点说明,每个部分有明确的标题,比如框架概述、核心模块、数据结构、数据流、开发建议等。同时,要遵循用户的要求,行内公式用$...$,独立公式用$$...$$,但在这个问题中可能数学公式不多,主要涉及代码和结构。不过如果有涉及到算法或数学表达的地方,比如缓冲区计算,可能需要应用格式。 还需要注意使用中文,避免专业术语的直译,保持口语化,但又要准确。例如,解释v4l2_subdev时,可以说明这是用于子设备控制,比如传感器或图像处理器,而v4l2_device代表整个视频设备。 最后,总结部分要强调V4L2在Linux多媒体中的重要性,以及学习源码对开发者的帮助,比如定制驱动或优化性能。同时,提供进一步的学习资源,如内核文档、示例代码,帮助用户继续深入。 现在需要检查是否有错误的地方,比如V4L2的代码路径是否正确,数据结构的作用是否描述准确。确保提到的模块和流程符合最新的内核版本,或者至少是通用的结构,因为V4L2可能会有更新,但核心部分变化不大。 可能还需要提醒用户,实际开发时需要参考具体的内核版本源码,因为不同版本可能会有差异。此外,调试工具和技巧,如使用dmesg查看内核日志,对于问题排查很重要,这些可以放在开发建议里。 总之,整个思考过程需要从用户的需求出发,组织信息,确保覆盖主要方面,结构清晰,同时准确可靠,帮助用户逐步理解V4L2框架的源码结构和工作原理。</think>### V4L2框架源码分析 #### 1. 框架概述 V4L2(Video4Linux2)是Linux内核中用于视频设备驱动的核心框架,支持摄像头、采集卡、TV调谐器等设备。其源码位于内核的`drivers/media/v4l2-core/`目录,主要分为以下层级: - **用户空间接口**:通过`/dev/videoX`设备节点提供`ioctl`系统调用。 - **内核核心层**:定义核心数据结构和API(如`v4l2_device`、`v4l2_subdev`)。 - **设备驱动层**:具体硬件驱动实现(如`uvcvideo`驱动USB摄像头)。 --- #### 2. 核心模块分析 ##### 2.1 视频设备抽象(`v4l2_device`) - **作用**:表示一个完整的视频设备(如摄像头模组)。 - **关键结构体**: ```c struct v4l2_device { struct device *dev; // 关联的硬件设备 char name[V4L2_DEVICE_NAME_SIZE]; struct list_head subdevs; // 子设备链表 // ... }; ``` ##### 2.2 子设备管理(`v4l2_subdev`) - **作用**:抽象硬件中的子模块(如传感器、ISP芯片)。 - **操作接口**: ```c struct v4l2_subdev_ops { const struct v4l2_subdev_core_ops *core; const struct v4l2_subdev_video_ops *video; // ... }; ``` ##### 2.3 IOCTL处理 - **用户空间交互**:通过`v4l2_ioctl_ops`定义设备支持的IOCTL命令(如`VIDIOC_QUERYCAP`查询设备能力)。 ```c const struct v4l2_ioctl_ops *ioctl_ops; ``` ##### 2.4 缓冲区管理(`vb2_queue`) - **内存模型**:支持`MMAP`、`USERPTR`、`DMABUF`等内存分配方式。 - **关键流程**: ```c struct vb2_queue { enum v4l2_buf_type type; const struct vb2_mem_ops *mem_ops; // 内存操作函数 // ... }; ``` --- #### 3. 数据流分析(以摄像头为例) 1. **设备初始化**: - 驱动注册`v4l2_device`并关联子设备。 - 创建`/dev/videoX`节点。 2. **用户配置**: - 通过`VIDIOC_S_FMT`设置视频格式。 - 调用`VIDIOC_REQBUFS`申请缓冲区。 3. **数据采集**: - 启动流(`VIDIOC_STREAMON`)。 - 通过`poll`或`select`等待数据就绪。 - 使用`VIDIOC_DQBUF`获取填充的缓冲区。 --- #### 4. 关键数据结构关系 $$ \text{v4l2_device} \leftrightarrow \text{v4l2_subdev} \leftrightarrow \text{v4l2_ioctl_ops} \leftrightarrow \text{vb2_queue} $$ --- #### 5. 开发调试建议 1. **阅读代码路径**: - 核心逻辑:`drivers/media/v4l2-core/v4l2-dev.c` - 缓冲区管理:`drivers/media/common/videobuf2/` 2. **调试工具**: - `v4l2-ctl`(用户空间工具,查询设备信息或配置参数)。 - 内核日志(`dmesg`)跟踪`pr_debug`输出。 3. **典型驱动示例**: - USB摄像头:`drivers/media/usb/uvc/uvc_driver.c` - 复杂设备:`drivers/media/platform/`(如CSI接口摄像头) --- #### 6. 总结 V4L2框架通过分层设计实现了设备驱动的标准化,其源码核心在于: - 统一的设备抽象模型(`v4l2_device`和`v4l2_subdev`)。 - 灵活的IOCTL接口和内存管理机制(`vb2`)。 - 高度可扩展的驱动开发模式。 如需深入理解,建议结合内核文档(`Documentation/media/v4l-drivers/`)和实际硬件驱动代码进行交叉分析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值