第2篇 vivi虚拟摄像头的应用和驱动

参考:韦东山Linux教程
参考:https://blog.csdn.net/weixin_42462202/article/details/99719750

v4l2的操作流程(应用层编程)和对应的驱动层数据结构

v4l2的操作流程
	查询设备功能(VIDIOC_QUERYCAP)
	枚举像素格式(VIDIOC_ENUM_FMT)
	设置像素格式(VIDIOC_S_FMT)
	申请缓存(VIDIOC_REQBUFS)
	映射缓存(mmap)
	缓存入队列(VIDIOC_QBUF)
	打开流(VIDIOC_STREAMON)
	等待数据可读(poll)
	缓存出队列(VIDIOC_DQBUF)

摄像头驱动程序必需的11个ioctl:		(struct v4l2_ioctl_ops)
	// 表示它是一个摄像头设备
	.vidioc_querycap      = vidioc_querycap,

	/* 用于列举、获得、测试、设置摄像头的数据的格式 */
	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,

	/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
	.vidioc_reqbufs       = vidioc_reqbufs,
	.vidioc_querybuf      = vidioc_querybuf,
	.vidioc_qbuf          = vidioc_qbuf,
	.vidioc_dqbuf         = vidioc_dqbuf,

	// 启动/停止
	.vidioc_streamon      = vidioc_streamon,
	.vidioc_streamoff     = vidioc_streamoff,	

怎么写 v4l2 驱动

(1)分配/设置/注册 v4l2_device (可能不是必须的,看下文):	struct v4l2_device (辅助作用,提供自旋锁,引用计数)
	v4l2_device_register(本质是做初始化 init)
	
(2)分配 video_device : struct video_device
	video_device_alloc
	
(3)设置 video_device :(vfd)
	a. vfd->v4l2_dev  (指向 struct v4l2_device)
	b. vfd: (即 video_device)
		.fops							//struct v4l2_file_operations
		.ioctl_ops						//struct v4l2_ioctl_ops

	c. APP可以通过 ioctl 来设置/获得亮度等信息		(视频中这一部分没有讲解的很透彻, 以后可能需要查找其他资料,了解 v4l2)
		驱动程序里,由 v4l2_ctrl 来接收/存储/设置到硬件, 提供这些这些信息
		属性: v4l2_ctrl											//struct v4l2_ctrl
		管理: v4l2_ctrl_handler(可理解为由多个 v4l2_ctrl 组成)		//struct v4l2_ctrl_handler
		
		1)初始化 v4l2_ctrl_handler_init
		2)创建 v4l2_ctrl ,放入链表 v4l2_ctrl_handler
			v4l2_ctrl_new_std
			v4l2_ctrl_new_custom
		3)跟 vdev 关联
			v4l2_dev.ctrl_handler = hdl(即前面创建出来的 v4l2_ctrl_handler)
			video_dev->v4l2_dev = v4l2_dev

		注意:
			这些属性 ctrl 是由应用层的 ioctl 来引用

	v4l2_ctrl_handler 的使用过程:
		__video_do_ioctl
			struct video_device *vfd = video_devdata(file);
			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
					ret = v4l2_queryctrl(vfd->ctrl_handler, p);
																// 根据 ID 在 ctrl_handler 里找到 v4l2_ctrl ,返回它的值
			......

(4)注册 video_device: 
	video_register_device(video_device, type:VFL_TYPE_GRABBER, nr)		//重要
			__video_register_device
				|
				|if (vdev->ctrl_handler == NULL)
				|	vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;		//
				|video_device[vdev->minor] = vdev;
				|vdev->cdev = cdev_alloc();
				|vdev->cdev->ops = &v4l2_fops;			//cdev.fops
				|cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);								//
				|device_register(&vdev->dev);

vivi 驱动

虚拟视频驱动vivi.c分析:
	1.分配 video_device
	2.设置	video_device
	3.注册: video_register_device

	vivi_init
		vivi_create_instance
			v4l2_device_register   // 不是主要, 只是用于初始化一些东西,比如自旋锁、引用计数
			
			//分配 video_device
			video_device_alloc		
			
			//设置 video_device
			  1. vfd:				//struct video_device
				.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);              
			//注册 video_device
			video_register_device(video_device, type:VFL_TYPE_GRABBER, nr)		//重要
					__video_register_device
						|
						|if (vdev->ctrl_handler == NULL)
						|	vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;		//
						|video_device[vdev->minor] = vdev;
						|vdev->cdev = cdev_alloc();
						|vdev->cdev->ops = &v4l2_fops;			//cdev.fops
						|cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);								//
						|device_register(&vdev->dev);
						



分析vivi.c的open,read,write,ioctl过程
	1. open
		app:     open("/dev/video0",....)
		---------------------------------------------------
		drv:     v4l2_fops.v4l2_open						//v4l2_fops 对应 cdev 的 struct file_operations v4l2_fops
					vdev = video_devdata(filp); 			 //根据次设备号从数组中得到 video_device
					ret = vdev->fops->open(filp);			//fops 对应 struct v4l2_file_operations
								vivi_ioctl_ops.open
									v4l2_fh_open

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

	3. ioctl
		app:   ioctl
		----------------------------------------------------
		drv:   v4l2_fops.unlocked_ioctl				//即 v4l2_ioctl
				   v4l2_ioctl
						struct video_device *vdev = video_devdata(filp);
						ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);		//即 video_ioctl2
									video_ioctl2
										video_usercopy(file, cmd, arg, __video_do_ioctl);
											__video_do_ioctl
												struct video_device *vfd = video_devdata(file);
												根据APP传入的cmd来获得、设置"某些属性"



继续分析数据的获取过程:	(这里也是基于 应用层 分析, 这里假设通过 ioctl(vidioc_streamon)来读数据(不使用read) 参考图片: V4L2框架.jpg)
	1. 请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS          // 请求系统分配缓冲区
							videobuf_reqbufs(队列, v4l2_requestbuffers) // 队列在 open 函数用 videobuf_queue_vmalloc_init 初始化
							// 注意:这个IOCTL只是分配缓冲区的头部信息,真正的缓存还没有分配呢

	2. 查询映射缓冲区:
		ioctl(4, VIDIOC_QUERYBUF         // 查询所分配的缓冲区
				videobuf_querybuf        // 获得缓冲区的数据格式、大小、每一行长度、高度            
		mmap(参数里有"大小")  			 // 在这里才分配缓存
				v4l2_mmap
					vivi_mmap
						videobuf_mmap_mapper
							videobuf-vmalloc.c里的__videobuf_mmap_mapper
									mem->vmalloc = vmalloc_user(pages);   // 在这里才给缓冲区分配空间

	3. 把缓冲区放入队列:
		ioctl(4, VIDIOC_QBUF             // 把缓冲区放入队列        
			videobuf_qbuf
				q->ops->buf_prepare(q, buf, field);  // 调用驱动程序提供的函数做些预处理
				list_add_tail(&buf->stream, &q->stream);  // 把缓冲区放入队列的尾部
				q->ops->buf_queue(q, buf);           // 调用驱动程序提供的"入队列函数"
				

	4. 启动摄像头
		ioctl(4, VIDIOC_STREAMON
			videobuf_streamon
				q->streaming = 1;
				

	5. 用select查询是否有数据
			  // 驱动程序里必定有: 产生数据、唤醒进程
			  v4l2_poll
					vdev->fops->poll
						vivi_poll   
							videobuf_poll_stream
								// 从队列的头部获得缓冲区
								buf = list_entry(q->stream.next, struct videobuf_buffer, stream);
								
								// 如果没有数据则休眠                			
								poll_wait(file, &buf->done, wait);

		谁来产生数据、谁来唤醒它?
		内核线程 vivi_thread 每30MS执行一次,它调用
			vivi_thread_tick
				vivi_fillbuff(fh, buf);  // 构造数据 
				wake_up(&buf->vb.done);  // 唤醒进程
				  
	6. 有数据后从队列里取出缓冲区
		// 有那么多缓冲区,APP如何知道哪一个缓冲区有数据?调用 VIDIOC_DQBUF
		ioctl(4, VIDIOC_DQBUF 
			vidioc_dqbuf   
				// 在队列里获得有数据的缓冲区
				retval = stream_next_buffer(q, &buf, nonblocking);
				
				// 把它从队列中删掉
				list_del(&buf->stream);
				
				// 把这个缓冲区的状态返回给APP
				videobuf_status(q, b, buf, q->type);
			
	7. 应用程序根据 VIDIOC_DQBUF 所得到缓冲区状态,知道是哪一个缓冲区有数据
	   就去读对应的地址(该地址来自前面的 mmap)

posted @   charlie12345  阅读(261)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示