(二) V4L2引入(含浅析UVC)

V4L2引入(含浅析UVC)

基本框架#

  • V4L2全名是video for linux 2之前还有个老版本v4l,也就是video for linux 1.0版本

  • V4L2不仅仅用于摄像头,也用于视频输出接口,收音机接口等,完整的框架可以参考这里

基本框架图如下:摘录自 Linux摄像头驱动1——vivid

mark

代码入手#

我们插入USB,使用dmesg查看usb的输出信息

Copy
uvcvideo: Found UVC 1.00 device USB 2.0 Camera (05a3:9310)

搜索内核源码可以找到相关函数

Copy
cd linux-3.4.2/ cd drivers/ $ grep "Found UVC" * -nR media/video/uvc/uvc_driver.c:1848: uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s

这里就涉及到UVC这个名词了,所谓的UVC也就是usb video class类的驱动,也就是USB硬件相关的驱动,也就是应该会向上注册这个驱动程序

UVC流程简述#

这里简单分析下UVC驱动的流程,详细分析到USB摄像头那里,对于UVC驱动也是一个驱动,从入口函数分析下

Copy
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; }

注册了一个结构体,看下这个结构体的成员

Copy
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, }, };

按照以往的经验,应该就是先匹配id_table,然后执行probe来初始化,查看下这个probe,深入分析最后是调用cdev注册了一个 v4l2_fopsfile_operation,也就是说最终app的操作函数就是这个啦.

v4l2_fops > 具体的驱动

简单流程一览,抓住关键注册函数是uvc_register_chains,而v4l2_device_register并不重要在整体上

Copy
// drivers\media\video\uvc\uvc_driver.c uvc_probe v4l2_device_register //这个不重要,只是进行一些初始化 .. uvc_register_chains //这里才是实际的注册v4l2管理结构 >uvc_register_terms >uvc_register_video(dev, stream) >video_device_alloc //分配 >vdev->v4l2_dev = &dev->vdev; ---- >vdev->fops = &uvc_fops; //这个是最终的读写函数 >vdev->release = uvc_release; >video_set_drvdata(vdev, stream); // 设置 >video_register_device(vdev, VFL_TYPE_GRABBER, -1) //注册 // \include\media\v4l2-dev.h 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); } ----这个实际的 __video_register_device 注册函数是在 drivers\media\video\v4l2-dev.c 也就是它为下层提供注册接口的 __video_register_device >case VFL_TYPE_GRABBER: name_base = "video"; > 获得一个空的次设备号 for (i = 0; i < VIDEO_NUM_DEVICES; i++) if (video_device[i] == NULL) break; > 这里就有字符设备驱动的了 >vdev->cdev = cdev_alloc(); >vdev->cdev->ops = &v4l2_fops; //这个也就是具体的 /dev/video的fileoperation >vdev->cdev->owner = owner; >ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);

调用流程#

appopen,read,write 最终就会调用到vdev->cdev->ops=v4l2_fops

Copy
// \drivers\media\video\v4l2-dev.c 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, };

open#

Copy
vdev = video_devdata(filp); //return video_device[iminor(file->f_path.dentry->d_inode)]; >vdev->fops->open(filp)

read#

这里调用的最终就是vdev->fops->read

Copy
video_device *vdev = video_devdata(filp); > return video_device[iminor(file->f_path.dentry->d_inode)]; vdev->fops->read(filp, buf, sz, off)

这个是在uvc_register_video中设置的

Copy
vdev->fops = &uvc_fops; const struct v4l2_file_operations uvc_fops = { .owner = THIS_MODULE, .open = uvc_v4l2_open, .release = uvc_v4l2_release, .unlocked_ioctl = uvc_v4l2_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl32 = uvc_v4l2_compat_ioctl32, #endif .read = uvc_v4l2_read, .mmap = uvc_v4l2_mmap, .poll = uvc_v4l2_poll, #ifndef CONFIG_MMU .get_unmapped_area = uvc_v4l2_get_unmapped_area, #endif };

v4l2_ioctl#

也就是调用了`uvc_v4l2_ioctl>uvc_v4l2_do_ioctl

Copy
struct video_device *vdev = video_devdata(filp); vdev->fops->unlocked_ioctl(filp, cmd, arg)

videodev_init(字符设备驱动注册)#

这个是v4l2-dev.c的入口,这里就是常规的字符设备驱动,这里使用了主设备号81

Copy
static int __init videodev_init(void) { dev_t dev = MKDEV(VIDEO_MAJOR, 0); int ret; printk(KERN_INFO "Linux video capture interface: v2.00\n"); ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME); if (ret < 0) { printk(KERN_WARNING "videodev: unable to get major %d\n", VIDEO_MAJOR); return ret; } ret = class_register(&video_class); if (ret < 0) { unregister_chrdev_region(dev, VIDEO_NUM_DEVICES); printk(KERN_WARNING "video_dev: class_register failed\n"); return -EIO; } return 0; }
posted @   zongzi10010  阅读(1357)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示
CONTENTS