Linux v4l2架构学习总链接

内核代码是基于linux4.9分析

vivi 代码在线查看

vivi.c - drivers/media/video/vivi.c - Linux source code (v3.0.101) - Bootlin

vivi是一个比较老的版本,但是比较适合入门。

static int __init vivi_init(void)
{...for (i = 0; i < n_devs; i++) {ret = vivi_create_instance(i);}.../* n_devs will reflect the actual number of allocated devices */n_devs = i;return ret;
}

通过vivi_create_instance创建vivi实例

static int __init vivi_create_instance(int inst)
{struct vivi_dev *dev;struct video_device *vfd;struct v4l2_ctrl_handler *hdl;struct vb2_queue *q;int ret;dev = kzalloc(sizeof(*dev), GFP_KERNEL);if (!dev)return -ENOMEM;snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),"%s-%03d", VIVI_MODULE_NAME, inst);/* * 这里用register并不合适,这个函数其实就是对v4l2_device的初始化* 1. 初始化 subdevs 链表* 2. 初始化自旋锁 lock*/ret = v4l2_device_register(NULL, &dev->v4l2_dev);if (ret)goto free_dev;dev->fmt = &formats[0];dev->width = 640;dev->height = 480;.../* initialize locks */spin_lock_init(&dev->slock);/** 初始化队列* 队列是干什么的?* 我的理解就是操作从camera中获取一帧数据并存储* 队列主要操作申请存储空间,以及处理这块存储空间*//* initialize queue */q = &dev->vb_vidq;memset(q, 0, sizeof(dev->vb_vidq));q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;/** io_mode :支持的io方法* 常用的就是 MMAP,这里暂时记住MMAP就可以*/q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;q->drv_priv = dev;q->buf_struct_size = sizeof(struct vivi_buffer);q->ops = &vivi_video_qops;q->mem_ops = &vb2_vmalloc_memops;/*跟进分析*/vb2_queue_init(q);...
}

对于queue,当前还看不出来具体的作用,下面跟进分析

int vb2_queue_init(struct vb2_queue *q)
{if (q->buf_struct_size == 0)q->buf_struct_size = sizeof(struct vb2_v4l2_buffer);q->buf_ops = &v4l2_buf_ops;q->is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(q->type);q->is_output = V4L2_TYPE_IS_OUTPUT(q->type);q->copy_timestamp = (q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK)== V4L2_BUF_FLAG_TIMESTAMP_COPY;/** For compatibility with vb1: if QBUF hasn't been called yet, then* return POLLERR as well. This only affects capture queues, output* queues will always initialize waiting_for_buffers to false.*/q->quirk_poll_must_check_waiting_for_buffers = true;return vb2_core_queue_init(q);
}int vb2_core_queue_init(struct vb2_queue *q)
{INIT_LIST_HEAD(&q->queued_list);INIT_LIST_HEAD(&q->done_list);spin_lock_init(&q->done_lock);mutex_init(&q->mmap_lock);init_waitqueue_head(&q->done_wq);if (q->buf_struct_size == 0)q->buf_struct_size = sizeof(struct vb2_buffer);return 0;
}

vb2_queue_init中并没有特别难以理解的代码,执行到这里

queue的ops有如下几个

q->ops = &vivi_video_qops;
q->mem_ops = &vb2_vmalloc_memops;
q->buf_ops = &v4l2_buf_ops;

对于这3个ops的意思是什么,这个时候就要去看看相关结构体成员的解释了

 * @ops:    driver-specific callbacks 驱动特定的回调,可以猜测是注册的字符设备的ops会调用这个ops* @mem_ops:   memory allocator specific callbacks 内存分配相关的回调,这个代码中实际用到再分析* @buf_ops:   callbacks to deliver buffer information 在用户空间和内核空间传递缓冲区信息的回调*       between user-space and kernel-space    同样用到再分析

回到 vivi_create_instance继续分析

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,
};static int __init vivi_create_instance(int inst)
{...mutex_init(&dev->mutex);/* init video dma queues */INIT_LIST_HEAD(&dev->vidq.active);init_waitqueue_head(&dev->vidq.wq);ret = -ENOMEM;vfd = video_device_alloc();if (!vfd)goto unreg_dev;/** 这里直接赋值了video_device的相关数据* 包括fops ioctl_ops等等*/*vfd = vivi_template;vfd->debug = debug;vfd->v4l2_dev = &dev->v4l2_dev;set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);/** Provide a mutex to v4l2 core. It will be used to protect* all fops and v4l2 ioctls.*/vfd->lock = &dev->mutex;/* 注册video 进一步分析 */ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);if (ret < 0)goto rel_vdev;video_set_drvdata(vfd, dev);/* Now that everything is fine, let's add it to device list */list_add_tail(&dev->vivi_devlist, &vivi_devlist);if (video_nr != -1)video_nr++;dev->vfd = vfd;v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",video_device_node_name(vfd));return 0;
}

这里到了关键的一步,注册video节点,video_register_device

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 neverhaving been registered */vdev->minor = -1;/* v4l2_fh support */spin_lock_init(&vdev->fh_lock);INIT_LIST_HEAD(&vdev->fh_list);/* 检查设备类型,这里传入的是 GRABBER,所以最后注册的节点是 video *//* Part 1: check device type */switch (type) {case VFL_TYPE_GRABBER:name_base = "video";break;case VFL_TYPE_VBI:name_base = "vbi";break;case VFL_TYPE_RADIO:name_base = "radio";break;case VFL_TYPE_SUBDEV:name_base = "v4l-subdev";break;case VFL_TYPE_SDR:/* Use device name 'swradio' because 'sdr' was already taken. */name_base = "swradio";break;case VFL_TYPE_TOUCH:name_base = "v4l-touch";break;default:printk(KERN_ERR "%s called with unknown type: %d\n",__func__, type);return -EINVAL;}vdev->vfl_type = type;vdev->cdev = NULL;if (vdev->dev_parent == NULL)vdev->dev_parent = vdev->v4l2_dev->dev;if (vdev->ctrl_handler == NULL)vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;/* If the prio state pointer is NULL, then use the v4l2_deviceprio state. */if (vdev->prio == NULL)vdev->prio = &vdev->v4l2_dev->prio;/* Part 2: find a free minor, device node number and device index. *//* Pick a device node number */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;}/* The device node number and minor numbers are independent, sowe just find the first free minor number. */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;}/* 经过上面方法获取到了次设备号,获取过程不关心 */vdev->minor = i + minor_offset;vdev->num = nr;devnode_set(vdev);/* Should not happen since we thought this minor was free */WARN_ON(video_device[vdev->minor] != NULL);vdev->index = get_index(vdev);/*数组video_device次设备号下标中存储 device,open的时候会根据次设备号取出 */video_device[vdev->minor] = vdev;mutex_unlock(&videodev_lock);if (vdev->ioctl_ops)determine_valid_ioctls(vdev);/* Part 3: Initialize the character device */vdev->cdev = cdev_alloc();if (vdev->cdev == NULL) {ret = -ENOMEM;goto cleanup;}vdev->cdev->ops = &v4l2_fops;vdev->cdev->owner = owner;/** 这里要注意,cdev_add并不会在/dev下面产生节点video节点* 只是通过主副设备号组合成一个下标,根据下标存储在一个数组中* open的时候,根据下标取出ops等相关信息*/ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);if (ret < 0) {printk(KERN_ERR "%s: cdev_add failed\n", __func__);kfree(vdev->cdev);vdev->cdev = NULL;goto cleanup;}/* Part 4: register the device with sysfs */vdev->dev.class = &video_class;vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);vdev->dev.parent = vdev->dev_parent;dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);/** 这里才是注册/dev/videox* 比较奇怪,为什么不直接注册cdev?而是要分开?*/ret = device_register(&vdev->dev);if (ret < 0) {printk(KERN_ERR "%s: device_register failed\n", __func__);goto cleanup;}/* Register the release callback that will be called when the lastreference to the device goes away. */vdev->dev.release = v4l2_device_release;if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)printk(KERN_WARNING "%s: requested %s%d, got %s\n", __func__,name_base, nr, video_device_node_name(vdev));/* Increase v4l2_device refcount */v4l2_device_get(vdev->v4l2_dev);/** 对于media controller暂时不分析* 暂时只分析直接注册video节点,不分析使用media节点的方式*//* Part 5: Register the entity. */ret = video_register_media_controller(vdev, type);/* Part 6: Activate this minor. The char device can now be used. */set_bit(V4L2_FL_REGISTERED, &vdev->flags);return 0;}

以上就完成了video节点的注册

看到这里也许感觉还是没什么收获,只是简单的注册了节点,架构的细节并不是很多,后面会结合应用代码分析调用过程,这个过程会深入到驱动代码中,敬请期待...

补充:

由于手里的板子跑的是4.9.11的内核,为了方便测试,将vivi代码修改移植到了4.9.11

代码放在码云上

https://gitee.com/ldl17/v4l2-learn/blob/master/normal/vivi.c

从vivi虚拟摄像头驱动开始分析v4l2相关推荐

  1. 二十四、V4L2框架主要结构体分析和虚拟摄像头驱动编写

    一.V4L2框架主要结构体分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括两层 ...

  2. Linux摄像头驱动第一篇之虚拟摄像头驱动vivi.c

    本文学习自韦东山老师的摄像头驱动模块 目录 一 摄像头驱动程序学习切入点以及V4L2模型概览 二 简析虚拟视频驱动 VIVI.C 2.1 初始化.设置.注册过程 2.2 简析vivi.c的open,r ...

  3. 深入学习Linux摄像头(三)虚拟摄像头驱动分析

    深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...

  4. V4L2(三)编写虚拟摄像头驱动

    内核版本:linux-3.5 开发板:tiny4412 概述 前面简单分析了内核中虚拟摄像头驱动 vivi 的框架与实现,本文参考 vivi 来写一个虚拟摄像头驱动,查询.设置视频格式相对简单,难点在 ...

  5. 虚拟摄像头驱动原理及开发

    (以下所说的都是基于微软的windows平台)                类似功能的产品,如著名的e2eSoft的 VCam,国内新浪的9518虚拟视频, 新浪的虚拟视频是DirectShow应用 ...

  6. 2.2 vivi虚拟视频驱动测试

    学习目标:在linux终端安装xawtv,并测试vivi.ko驱动程序. 一.安装xawtv 1)ubuntu能上网情况下,使用命令:# sudo apt-get install xawtv 2)如果 ...

  7. 写一个虚拟摄像头驱动3

    /* 仿照vivi.c */ #include <linux/module.h> #include <linux/delay.h> #include <linux/err ...

  8. VcdRom 虚拟光驱驱动代码分析

    VcdRom 是网上一个爱好者模拟微软虚拟驱动编写一个虚拟光驱程序,近日在研究虚拟磁盘驱动时,无意中看到了VcdRom的虚拟光驱的代码,不禁就开始研究了起来,希望以后能有用武之地. VcdRom驱动的 ...

  9. 2.3 摄像头驱动_vivi驱动程序分析

    学习目标:熟悉vivi的调用过程,分析vivi程序源码的ioctl函数: 一.vivi虚拟视频驱动测试方法 当我们接上usb摄像头设备时,系统会自动给我们安装对应的usb设备驱动程序.如果下次直接测试 ...

最新文章

  1. 毕业BG(01背包问题)
  2. linux系统关于ping的命令,详解Linux系统中ping和arping命令的用法
  3. 量子计算机物理学,百年的超越:量子物理学与量子计算机
  4. windows配置java运行环境
  5. workspace-project-target关系与解耦工程结构总结
  6. vim的tab键设定
  7. 电影:『新警察故事』
  8. 【论文分享】ACL 2020 图神经网络在自然语言处理中的应用
  9. 基于bootsplash的嵌入式linux启动画面定制
  10. 计算机cpu型号在哪看,cpu型号,教您电脑cpu型号怎么看
  11. STM32F0单片机快速入门八: Coolie DMA
  12. ActiveMQ反序列化漏洞(CVE-2015-5254)
  13. 开放平台-百度开放平台:百度开放平台
  14. Segmentation简记5-AuxNet: Auxiliary tasks enhanced Semantic Segmentation for Automated Driving
  15. 通达信公式:如何表示5个数据中的前三大数值?
  16. 从零实战:爬2019富豪榜进行数据分析
  17. 阴阳师真八歧大蛇最低配置攻略,蛇黑切
  18. 这五个数据科学家和机器学习工程师油管博主,你必须关注
  19. 高楼火灾的时候如何利用逃生缓降器进行逃生?
  20. 你离拍出好的雪景作品,还有多远? @教摄影

热门文章

  1. 【Unity2D】GameOver制作人物游戏结束菜单
  2. 华科计算机杰出青年科学基金,喜报!曾小勤教授获得2018年度国家杰出青年科学基金资助...
  3. filecoin 多签钱包创建流程
  4. 2022年广东二级建造师建设工程施工管理《施工成本管理》模拟题及答案
  5. H5页面长按截图+保存页面截图到本地功能
  6. 深度报告:Nordic Semi 北欧半导体的现在和未来
  7. 上海小学生学计算机,关于参加“六一”杯上海市小学生计算机竞赛的通知
  8. 崔永元逗老外逗出的震撼
  9. Linux文件的压缩和解压命令tar
  10. CSDN中希腊字母输入