精华内容
下载资源
问答
  • 本文学习自韦东山老师的摄像头驱动模块 ...三 虚拟摄像头驱动的启动过程简析 3.1 查看虚拟摄像头应用程序启动虚拟摄像头过程中都做了什么 3.2 分析数据的获取过程 四 编写摄像头驱动程序 4....

    本文学习自韦东山老师的摄像头驱动模块


    目录

    一 摄像头驱动程序学习切入点以及V4L2模型概览
    二 简析虚拟视频驱动 VIVI.C
    • 2.1 初始化、设置、注册过程
    • 2.2 简析vivi.c的open,read,write,ioctl过程
    三 虚拟摄像头驱动的启动过程简析
    • 3.1 查看虚拟摄像头应用程序启动虚拟摄像头过程中都做了什么
    • 3.2 分析数据的获取过程
    四 编写摄像头驱动程序
    • 4.1 第一步 :仿照vivi.c,搭建摄像头驱动程序框架
    • 4.2 第二步 : 创建v4l2_ioctl_ops 并实现成员函数 vidioc_querycap()
    • 4.3 第三步:填充 v4l2_ioctl_ops 结构体中 列举、获得、测试、设置 摄像头的数据的格式的相关函数
    • 4.4 第四步 : 增加 缓冲区操作: 申请/查询/放入队列/取出队列 相关函数
    • 4.5 第五步 : 增加 启动/停止
    • 4.6 第六步 : 定时器定时填充数据并唤醒进程

    一 摄像头驱动程序学习切入点以及V4L2模型概览

    V4L2 : Video for Linux 2,是linux为视频设备提供的一套标准接口。我学习V4L2 的切入点,首先学习虚拟摄像头驱动程序vivi.c。学习vivi.c虚拟摄像头驱动的切入点如下:

    打开虚拟机,将虚拟机置于最前台,插上USB摄像头,dmesg 看kernel输出LOG。有关键信息 :Found UVC 1.00 … 搜索该关键字,经过搜索,定位在 uvc_driver.c ,粗略的看一下代码,总结出如下模糊框架:

    V4L2 大致框架:

    硬件相关层
    uvc_driver.c 
    
    uvc_probe
    	v4l2_device_register
    	uvc_register_chains
    		uvc_register_terms
    			uvc_register_video
    				uvc_register_video_device 
    					//   从硬件相关层到核心层;
    					video_register_device ==  __video_register_device //drivers\media\v4l2-core\v4l2-dev.c
    
    核心层
    v4l2-dev.c 
    __video_register_device 
    	//以下三步是字符设备相关步骤,v4l2_fops是特殊的字符设备fops
    	vdev->cdev = cdev_alloc();
    	vdev->cdev->ops = &v4l2_fops;
    	cdev_add
    
    
    应用程序:
    	open read write
    

    二 简析虚拟视频驱动 VIVI.C

    2.1 初始化、设置、注册过程

    主要分为三步

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

    2.2 简析vivi.c的open,read,write,ioctl过程

    1 open

    app:     open("/dev/video0",....)
    ------------------------------------------------------------------
    drv:     v4l2_fops.v4l2_open
                vdev = video_devdata(filp);  // 根据次设备号从数组中得到video_device
                ret = vdev->fops->open(filp);
                            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
                    struct video_device *vdev = video_devdata(filp);
                    ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
                                video_ioctl2
                                    video_usercopy(file, cmd, arg, __video_do_ioctl);
                                        __video_do_ioctl
                                            struct video_device *vfd = video_devdata(file);
                                            根据APP传入的cmd来获得、设置"某些属性"
    

    三 虚拟摄像头驱动的启动过程简析

    3.1 查看虚拟摄像头应用程序启动虚拟摄像头过程中都做了什么

    前期环境搭建步骤后面补充,这边直接记录陈述结果,测试虚拟摄像头,发现虚拟摄像头应用程序调用虚拟摄像头驱动的ioctl过程如下:

    // 1~7都是在v4l2_open里调用
    1. open
    2. ioctl(4, VIDIOC_QUERYCAP
    
    // 3~7 都是在get_device_capabilities里调用
    3. for()
            ioctl(4, VIDIOC_ENUMINPUT   // 列举输入源,VIDIOC_ENUMINPUT/VIDIOC_G_INPUT/VIDIOC_S_INPUT不是必需的
    4. for()
            ioctl(4, VIDIOC_ENUMSTD  // 列举标准(制式), 不是必需的
    5. for()        
            ioctl(4, VIDIOC_ENUM_FMT // 列举格式
    
    6. ioctl(4, VIDIOC_G_PARM
    7. for()
            ioctl(4, VIDIOC_QUERYCTRL    // 查询属性(比如说亮度值最小值、最大值、默认值)
    
    // 8~10都是通过v4l2_read_attr来调用的        
    8. ioctl(4, VIDIOC_G_STD            // 获得当前使用的标准(制式), 不是必需的
    9. ioctl(4, VIDIOC_G_INPUT 
    10. ioctl(4, VIDIOC_G_CTRL           // 获得当前属性, 比如亮度是多少
    
    11. ioctl(4, VIDIOC_TRY_FMT          // 试试能否支持某种格式
    12. ioctl(4, VIDIOC_S_FMT            // 设置摄像头使用某种格式
    
    
    // 13~16在v4l2_start_streaming
    13. ioctl(4, VIDIOC_REQBUFS          // 请求系统分配缓冲区
    14. for()
            ioctl(4, VIDIOC_QUERYBUF         // 查询所分配的缓冲区
            mmap        
    15. for ()
            ioctl(4, VIDIOC_QBUF             // 把缓冲区放入队列        
    16. ioctl(4, VIDIOC_STREAMON             // 启动摄像头
    
    
    // 17里都是通过v4l2_write_attr来调用的
    17. for ()
            ioctl(4, VIDIOC_S_CTRL           // 设置属性
        ioctl(4, VIDIOC_S_INPUT              // 设置输入源
        ioctl(4, VIDIOC_S_STD                // 设置标准(制式), 不是必需的
    
    // v4l2_nextframe > v4l2_waiton    
    18. v4l2_queue_all
        v4l2_waiton    
            for ()
            {
                select(5, [4], NULL, NULL, {5, 0})      = 1 (in [4], left {4, 985979})
                ioctl(4, VIDIOC_DQBUF                // de-queue, 把缓冲区从队列中取出
                // 处理, 之以已经通过mmap获得了缓冲区的地址, 就可以直接访问数据        
                ioctl(4, VIDIOC_QBUF                 // 把缓冲区放入队列
            }
    

    由此可知,虚拟摄像头应用程序经过一系列的 ioctl() 最终将虚拟摄像头驱动启动成功。那么应用程序调用了那么多vivi.c中的 ioctl(),是不是每一个ioctl()都是必要的呢,经过删减测试。发现如下11个 ioctl() 是必要的,其余的ioctl()可删减。

    摄像头驱动程序必需的11个ioctl:

    // 表示它是一个摄像头设备
    .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,	
    

    3.2 分析数据的获取过程

    1 请求分配缓冲区:

    ioctl(4, VIDIOC_REQBUFS    // 请求系统分配缓冲区
      videobuf_reqbufs(队列, v4l2_requestbuffers) // 队列在open函数用videobuf_queue_vmalloc_init()初始化
    

    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)
    

    四 编写摄像头驱动程序

    4.1 第一步 :仿照vivi.c,搭建摄像头驱动程序框架

    仿照vivi.c,搭建摄像头驱动程序框架

    工作:

    1 分配一个video_device结构体 myvivi_device
    2 创建 v4l2_file_operations 并且绑定到 myvivi_device 
    3 注册 myvivi_device
    

    /* 仿照vivi.c */
    /* 原始框架  */
    #include <linux/module.h>
    #include <linux/delay.h>
    #include <linux/errno.h>
    #include <linux/fs.h>
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/mm.h>
    #include <linux/ioport.h>
    #include <linux/init.h>
    #include <linux/sched.h>
    #include <linux/pci.h>
    #include <linux/random.h>
    #include <linux/version.h>
    #include <linux/mutex.h>
    #include <linux/videodev2.h>
    #include <linux/dma-mapping.h>
    #include <linux/interrupt.h>
    #include <linux/kthread.h>
    #include <linux/highmem.h>
    #include <linux/freezer.h>
    #include <media/videobuf-vmalloc.h>
    #include <media/v4l2-device.h>
    #include <media/v4l2-ioctl.h>
    
    
    static const struct v4l2_file_operations myvivi_fops = {
    	.owner		= THIS_MODULE,
    };
    
    
    static struct video_device *myvivi_device;
    
    static void myvivi_release(struct video_device *vdev)
    {
    }
    
    
    static int myvivi_init(void)
    {
        int error;
        
        /* 1. 分配一个video_device结构体 */
        myvivi_device = video_device_alloc();
    
        /* 2. 设置 */
        myvivi_device->release = myvivi_release;
        myvivi_device->fops    = &myvivi_fops;
    
        /* 3. 注册 */                                 //注册摄像头类型    which device number
    	//注意:教程版本内核 该注册函数内部还会判断 时候已经编写release函数,和fops 结构。如果没有的话 注册失败
        error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
        
        return error;
    }
    
    static void myvivi_exit(void)
    {
        video_unregister_device(myvivi_device);
        video_device_release(myvivi_device);
    }
    
    module_init(myvivi_init);
    module_exit(myvivi_exit);
    MODULE_LICENSE("GPL");
    

    4.2 第二步 : 创建v4l2_ioctl_ops 并实现成员函数 vidioc_querycap()

    仿照vivi.c ,创建v4l2_ioctl_ops 并实现成员函数 vidioc_querycap(),实现该函数可以代表当前为一个摄像头设备。
    工作:

     1 构建 v4l2_ioctl_ops结构体
     	1.1 实现成员函数  vidioc_querycap ioctl(),表示是一个摄像头设备 
     
     2 设置 myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
     
     3 设置 v4l2_file_operations结构体的 .ioctl = video_ioctl2 ;
       说明:此项是参考 vivi.c,当应用程序调用v4l2_file_operations 的 .ioctl时候,就会调用video_ioctl2,
       		而video_ioctl2最终调用到 v4l2_ioctl_ops结构体中的成员函数
    

    #include <linux/module.h>
    #include <linux/delay.h>
    #include <linux/errno.h>
    #include <linux/fs.h>
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/mm.h>
    #include <linux/ioport.h>
    #include <linux/init.h>
    #include <linux/sched.h>
    #include <linux/pci.h>
    #include <linux/random.h>
    #include <linux/version.h>
    #include <linux/mutex.h>
    #include <linux/videodev2.h>
    #include <linux/dma-mapping.h>
    #include <linux/interrupt.h>
    #include <linux/kthread.h>
    #include <linux/highmem.h>
    #include <linux/freezer.h>
    #include <media/videobuf-vmalloc.h>
    #include <media/v4l2-device.h>
    #include <media/v4l2-ioctl.h>
    
    
    // 查询是否是一个 摄像头设备
    static int myvivi_vidioc_querycap(struct file *file, void  *priv,
    					struct v4l2_capability *cap)
    {
    	strcpy(cap->driver, "myvivi");
    	strcpy(cap->card, "myvivi");
    	cap->version = 0x0001;
    
    	// 读取方式 目前使用 V4L2_CAP_STREAMING 读取方式
    	cap->capabilities =	V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    	return 0;
    }
    
    static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
            // 表示它是一个摄像头设备
            .vidioc_querycap      = myvivi_vidioc_querycap,
    #if 0    
            /* 用于列举、获得、测试、设置摄像头的数据的格式 */
            .vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,
            .vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,
            .vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,
            .vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,
            
            /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
            .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
            .vidioc_querybuf      = myvivi_vidioc_querybuf,
            .vidioc_qbuf          = myvivi_vidioc_qbuf,
            .vidioc_dqbuf         = myvivi_vidioc_dqbuf,
            
            // 启动/停止
            .vidioc_streamon      = myvivi_vidioc_streamon,
            .vidioc_streamoff     = myvivi_vidioc_streamoff,   
    #endif
    };
    
    static const struct v4l2_file_operations myvivi_fops = {
    	.owner		= THIS_MODULE,
        .ioctl      = video_ioctl2, /* V4L2 ioctl handler V4L2 提供的表针的ioctl 最终就会调用到 myvivi_ioctl_ops*/
    };
    
    
    static struct video_device *myvivi_device;
    
    static void myvivi_release(struct video_device *vdev)
    {
    }
    
    
    static int myvivi_init(void)
    {
        int error;
        
        /* 1. 分配一个video_device结构体 */
        myvivi_device = video_device_alloc();
    
        /* 2. 设置 */
        myvivi_device->release = myvivi_release;
        myvivi_device->fops    = &myvivi_fops;
        myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
    
        /* 3. 注册 */
        error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
        
        return error;
    }
    
    static void myvivi_exit(void)
    {
        video_unregister_device(myvivi_device);
        video_device_release(myvivi_device);
    }
    
    module_init(myvivi_init);
    module_exit(myvivi_exit);
    MODULE_LICENSE("GPL");
    

    4.3 第三步:填充 v4l2_ioctl_ops 结构体中 列举、获得、测试、设置 摄像头的数据的格式的相关函数

    仿照vivi.c

    工作:继续填充 v4l2_ioctl_ops 结构体 : 填充 用于 列举、获得、测试、设置 摄像头的数据的格式的相关函数

    1 实现 vidioc_enum_fmt_vid_cap 
    	 说明 : 例举摄像头格式,当应用程序想查询我们的摄像头支持哪种格式的时候,发送 ioctl(4, VIDIOC_ENUMINPUT 指令,最终
    			调用到 vidioc_enum_fmt_vid_cap()
    2 实现 vidioc_g_fmt_vid_cap
    	 说明 : 返回当前摄像头所使用的格式
    	 
    	 2.1 定义 struct v4l2_format
    	 	 说明:用于存储当前格式,当应用程序想要获取当前摄像头格式的时候,直接返回该结构体
    
    3 实现 vidioc_try_fmt_vid_cap
    	 说明 : 测试摄像头驱动程序是否支持某种数据格式,即判断应用程序传递进来的摄像头格式:
    		 	struct v4l2_format->fmt.pix.pixelformat,是否等于 我们驱动程序唯一支持的格式:v4l2_fmtdesc->pixelformat = V4L2_PIX_FMT_YUYV
    	 
    4 实现 vidioc_s_fmt_vid_cap
    	 说明 : 设置摄像头数据格式
    	
    	4.1 先测试是否支持应用程序传递进来的摄像头格式 struct v4l2_format,调用 vidioc_try_fmt_vid_cap函数,判断应用程
    		序传递进来的摄像头格式 f->fmt.pix.pixelformat是否等于我们驱动程序唯一支持的格式 v4l2_fmtdesc->pixelformat = V4L2_PIX_FMT_YUYV
    
    	4.2 将应用程序传递进来的格式 拷贝到 我们自定义的 struct v4l2_format
    

    #include <linux/module.h>
    #include <linux/delay.h>
    #include <linux/errno.h>
    #include <linux/fs.h>
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/mm.h>
    #include <linux/ioport.h>
    #include <linux/init.h>
    #include <linux/sched.h>
    #include <linux/pci.h>
    #include <linux/random.h>
    #include <linux/version.h>
    #include <linux/mutex.h>
    #include <linux/videodev2.h>
    #include <linux/dma-mapping.h>
    #include <linux/interrupt.h>
    #include <linux/kthread.h>
    #include <linux/highmem.h>
    #include <linux/freezer.h>
    #include <media/videobuf-vmalloc.h>
    #include <media/v4l2-device.h>
    #include <media/v4l2-ioctl.h>
    
    
    static struct v4l2_format myvivi_format;
    
    static int myvivi_vidioc_querycap(struct file *file, void  *priv,
    					struct v4l2_capability *cap)
    {
    	strcpy(cap->driver, "myvivi");
    	strcpy(cap->card, "myvivi");
    	cap->version = 0x0001;
    	cap->capabilities =	V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    	return 0;
    }
    
    /* 列举支持哪种格式  固定只支持一种格式 V4L2_PIX_FMT_YUYV */
    static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
    					struct v4l2_fmtdesc *f)
    {
    	if (f->index >= 1) //先设置只支持一种格式
    		return -EINVAL;
    
    	strcpy(f->description, "4:2:2, packed, YUYV");//先设置只支持一种格式,这种格式是 YUYV,描述为 “4:2:2, packed, YUYV”
    	f->pixelformat = V4L2_PIX_FMT_YUYV;//先设置只支持一种格式,这种格式是 YUYV
    	return 0;
    }
    
    /* 返回当前所使用的格式 */
    static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
    					struct v4l2_format *f)
    {
    	// 将结构体 myvivi_format 数据拷贝回 应用程序
        memcpy(f, &myvivi_format, sizeof(myvivi_format));
    	return (0);
    }
    
    /* 测试驱动程序是否支持某种格式 */
    static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
    			struct v4l2_format *f)
    {
    	unsigned int maxw, maxh;
        enum v4l2_field field;
    
    	//格式,目前我们的驱动程序只支持  V4L2_PIX_FMT_YUYV 格式
        if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
            return -EINVAL;
    
    	field = f->fmt.pix.field;
    
    	if (field == V4L2_FIELD_ANY) {
    		field = V4L2_FIELD_INTERLACED;
    	} else if (V4L2_FIELD_INTERLACED != field) {
    		return -EINVAL;
    	}
    
    	//支持的最大尺寸
    	maxw  = 1024;
    	maxh  = 768;
    
        /* 调整format的width, height, 
         * 计算 bytesperline, sizeimage
         */
    	v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
    			      &f->fmt.pix.height, 32, maxh, 0, 0);
    	f->fmt.pix.bytesperline =
    		(f->fmt.pix.width * 16) >> 3;
    	f->fmt.pix.sizeimage =
    		f->fmt.pix.height * f->fmt.pix.bytesperline;
    
    	return 0;
    }
    
    //设置摄像头数据格式 由于此时没有硬件支持,所以将应用传递下来的数据格式保存在 myvivi_format
    static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
    					struct v4l2_format *f)
    {
    	//测试驱动程序是否支持某种(f)格式 
    	int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);
    	if (ret < 0)
    		return ret;
    	//将 f 拷贝到 myvivi_format
        memcpy(&myvivi_format, f, sizeof(myvivi_format));
        
    	return ret;
    }
    
    static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
            // 表示它是一个摄像头设备
            .vidioc_querycap      = myvivi_vidioc_querycap,
    
            /* 用于列举、获得、测试、设置摄像头的数据的格式 */
            .vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,
            .vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,
            .vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,
            .vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,
    
    #if 0    
            
            /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
            .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
            .vidioc_querybuf      = myvivi_vidioc_querybuf,
            .vidioc_qbuf          = myvivi_vidioc_qbuf,
            .vidioc_dqbuf         = myvivi_vidioc_dqbuf,
            
            // 启动/停止
            .vidioc_streamon      = myvivi_vidioc_streamon,
            .vidioc_streamoff     = myvivi_vidioc_streamoff,   
    #endif
    };
    
    static const struct v4l2_file_operations myvivi_fops = {
    	.owner		= THIS_MODULE,
        .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
    };
    
    
    static struct video_device *myvivi_device;
    
    static void myvivi_release(struct video_device *vdev)
    {
    }
    
    
    static int myvivi_init(void)
    {
        int error;
        
        /* 1. 分配一个video_device结构体 */
        myvivi_device = video_device_alloc();
    
        /* 2. 设置 */
        myvivi_device->release = myvivi_release;
        myvivi_device->fops    = &myvivi_fops;
        myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
    
        /* 3. 注册 */
        error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
        
        return error;
    }
    
    static void myvivi_exit(void)
    {
        video_unregister_device(myvivi_device);
        video_device_release(myvivi_device);
    }
    
    module_init(myvivi_init);
    module_exit(myvivi_exit);
    MODULE_LICENSE("GPL");
    

    4.4 第四步 : 增加 缓冲区操作: 申请/查询/放入队列/取出队列 相关函数

    工作:继续填充 v4l2_ioctl_ops 结构体 : 填充 用于 列举、获得、测试、设置 摄像头的数据的格式的相关函数

    1 
    	1.1 定义 videobuf_queue 队列
    	
    	1.2 定义、初始化 spinlock_t 自旋锁 :
    		1.2.1 定义  spinlock_t 自旋锁:static spinlock_t myvivi_queue_slock;
    		1.2.2 初始化  spinlock_t 自旋锁: pin_lock_init(&myvivi_queue_slock);
    
    2 在 v4l2_file_operations  .open中 初始化 videobuf_queue 队列 
    
    	 说明 :在 v4l2_file_operations  .open中 初始化 videobuf_queue 队列 : videobuf_queue_vmalloc_init()
    
    	 videobuf_queue_vmalloc_init()参数分析如下
    	 
    	2.1 参数1:videobuf_queue 队列
    	2.2 参数2: videobuf_queue_ops 结构体, 初始化 videobuf_queue_ops结构体,定义初始化其中的操作函数
    		
    		说明:填充videobuf_queue_ops结构体
    		
    		2.2.1 填充 buf_setup()
    			说明 : APP调用ioctl VIDIOC_REQBUFS(请求系统分配缓冲区) 时,会调用驱动的结构体v4l2_ioctl_ops
    				   的 .vidioc_reqbufs函数,在vidioc_reqbufs函数中,他会调用该函数buf_setup(),重新调整count
    				   和size
    
    		2.2.2 填充 buf_prepare()
    			说明 : 在结构体v4l2_ioctl_ops的 .vidioc_qbuf函数(入队列操作)中调用,设置状态 
    					struct videobuf_buffer->state = VIDEOBUF_PREPARED;
    		
    		2.2.3 填充 buf_queue()
    			说明 : 设置状态 struct videobuf_buffer->state = VIDEOBUF_QUEUED; APP调用 ioctl VIDIOC_QBUF时,
    					2.2.3.1 首先调用 buf_prepare 进行一些准备工作,
    					2.2.3.2 把buf放入队列
    					2.2.3.3 调用   buf_queue (起通知作用),设置
    
    		2.2.4 填充 buf_release()
    			说明:  APP不再使用队列时, 用它来释放内存 
    
    	2.3 参数3:v4l2_buf_type ,选择 V4L2_BUF_TYPE_VIDEO_CAPTURE 类型
    	
    	2.4 参数4:v4l2_field, V4L2_FILED_INTERLACED : 略 参考vivi.c代码参数
    
    	2.5 参数5:videobuf_buffer,buffer的头部大小
    	
    	2.6 参数6:私有数据 NULL
    
    3 添加 v4l2_file_operations .release函数,销毁队列
    	3.1 停止队列操作 videobuf_stop(&myvivi_vb_vidqueue);
    	
     	3.2 删除队列  videobuf_mmap_free(&myvivi_vb_vidqueue);
    		
    4 实现缓冲区操作函数:        
    		/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
            .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
            .vidioc_querybuf      = myvivi_vidioc_querybuf,
            .vidioc_qbuf          = myvivi_vidioc_qbuf,
            .vidioc_dqbuf         = myvivi_vidioc_dqbuf,
    

    /* 继续填充 v4l2_ioctl_ops 结构体 :增加 缓冲区操作: 申请/查询/放入队列/取出队列 相关函数 
     * 1. vidioc_reqbufs  : 申请缓冲区操作
     * 2. vidioc_querybuf  : 查询缓冲区
     * 3. vidioc_qbuf  : 放入队列操作
     * 4. vidioc_dqbuf  : 取出队列操作
     */
    
    #include <linux/module.h>
    #include <linux/delay.h>
    #include <linux/errno.h>
    #include <linux/fs.h>
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/mm.h>
    #include <linux/ioport.h>
    #include <linux/init.h>
    #include <linux/sched.h>
    #include <linux/pci.h>
    #include <linux/random.h>
    #include <linux/version.h>
    #include <linux/mutex.h>
    #include <linux/videodev2.h>
    #include <linux/dma-mapping.h>
    #include <linux/interrupt.h>
    #include <linux/kthread.h>
    #include <linux/highmem.h>
    #include <linux/freezer.h>
    #include <media/videobuf-vmalloc.h>
    #include <media/v4l2-device.h>
    #include <media/v4l2-ioctl.h>
    
    
    static struct v4l2_format myvivi_format;
    
    /* 队列操作1: 定义 */
    static struct videobuf_queue myvivi_vb_vidqueue;
    static spinlock_t myvivi_queue_slock;
    
    
    
    /* 参考documentations/video4linux/v4l2-framework.txt:
     *     drivers\media\video\videobuf-core.c 
     ops->buf_setup   - calculates the size of the video buffers and avoid they
                to waste more than some maximum limit of RAM;
     ops->buf_prepare - fills the video buffer structs and calls
                videobuf_iolock() to alloc and prepare mmaped memory;
     ops->buf_queue   - advices the driver that another buffer were
                requested (by read() or by QBUF);
     ops->buf_release - frees any buffer that were allocated.
     
     *
     */
    
    
    /* ------------------------------------------------------------------
    	Videobuf operations
       ------------------------------------------------------------------*/
    /* APP调用ioctl VIDIOC_REQBUFS(请求系统分配缓冲区) 时会导致此函数被调用,
     * 它重新调整count和size
     */
    static int myvivi_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
    {
    
    	*size = myvivi_format.fmt.pix.sizeimage;
    
    	if (0 == *count)
    		*count = 32;
    
    	return 0;
    }
    
    /* APP调用ioctlVIDIOC_QBUF时导致此函数被调用,
     * 它会填充video_buffer结构体并调用videobuf_iolock来分配内存
     * 
     */
    static int myvivi_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
    						enum v4l2_field field)
    {
        /* 1. 做些准备工作 */
    
    #if 0
        /* 2. 调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存 */
    	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
    		rc = videobuf_iolock(vq, &buf->vb, NULL);
    		if (rc < 0)
    			goto fail;
    	}
    #endif
        /* 3. 设置状态 */
    	vb->state = VIDEOBUF_PREPARED;
    
    	return 0;
    }
    
    
    /* APP调用ioctlVIDIOC_QBUF时:
     * 1. 先调用buf_prepare进行一些准备工作
     * 2. 把buf放入队列
     * 3. 调用buf_queue(起通知作用)
     */
    static void myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
    {
    	vb->state = VIDEOBUF_QUEUED;
    	//list_add_tail(&buf->vb.queue, &vidq->active);
    }
    
    /* APP不再使用队列时, 用它来释放内存 */
    static void myvivi_buffer_release(struct videobuf_queue *vq,
    			   struct videobuf_buffer *vb)
    {
    	videobuf_vmalloc_free(vb);
    	vb->state = VIDEOBUF_NEEDS_INIT;
    }
    
    static struct videobuf_queue_ops myvivi_video_qops = {
    	.buf_setup      = myvivi_buffer_setup, /* 计算大小以免浪费 */
    	.buf_prepare    = myvivi_buffer_prepare,
    	.buf_queue      = myvivi_buffer_queue,
    	.buf_release    = myvivi_buffer_release,
    };
    
    /* ------------------------------------------------------------------
    	File operations for the device
       ------------------------------------------------------------------*/
    
    static int myvivi_open(struct file *file)
    {
        /* 队列操作2: 初始化 */
    	/*  V4L2_BUF_TYPE_VIDEO_CAPTURE 类型*/
    	videobuf_queue_vmalloc_init(&myvivi_vb_vidqueue, &myvivi_video_qops,
    			NULL, &myvivi_queue_slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,
    			sizeof(struct videobuf_buffer), NULL); /* 倒数第2个参数是buffer的头部大小 */
    
    	return 0;
    }
    
    
    static int myvivi_close(struct file *file)
    {
    	//队列销毁
    	videobuf_stop(&myvivi_vb_vidqueue);
    	videobuf_mmap_free(&myvivi_vb_vidqueue);
        
    	return 0;
    }
    
    static int myvivi_mmap(struct file *file, struct vm_area_struct *vma)
    {
    	return videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);
    }
    
    static int myvivi_vidioc_querycap(struct file *file, void  *priv,
    					struct v4l2_capability *cap)
    {
    	strcpy(cap->driver, "myvivi");
    	strcpy(cap->card, "myvivi");
    	cap->version = 0x0001;
    	cap->capabilities =	V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    	return 0;
    }
    
    /* 列举支持哪种格式 */
    static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
    					struct v4l2_fmtdesc *f)
    {
    	if (f->index >= 1)
    		return -EINVAL;
    
    	strcpy(f->description, "4:2:2, packed, YUYV");
    	f->pixelformat = V4L2_PIX_FMT_YUYV;
    	return 0;
    }
    
    /* 返回当前所使用的格式 */
    static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
    					struct v4l2_format *f)
    {
        memcpy(f, &myvivi_format, sizeof(myvivi_format));
    	return (0);
    }
    
    /* 测试驱动程序是否支持某种格式 */
    static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
    			struct v4l2_format *f)
    {
    	unsigned int maxw, maxh;
        enum v4l2_field field;
    
        if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
            return -EINVAL;
    
    	field = f->fmt.pix.field;
    
    	if (field == V4L2_FIELD_ANY) {
    		field = V4L2_FIELD_INTERLACED;
    	} else if (V4L2_FIELD_INTERLACED != field) {
    		return -EINVAL;
    	}
    
    	maxw  = 1024;
    	maxh  = 768;
    
        /* 调整format的width, height, 
         * 计算bytesperline, sizeimage
         */
    	v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
    			      &f->fmt.pix.height, 32, maxh, 0, 0);
    	f->fmt.pix.bytesperline =
    		(f->fmt.pix.width * 16) >> 3;
    	f->fmt.pix.sizeimage =
    		f->fmt.pix.height * f->fmt.pix.bytesperline;
    
    	return 0;
    }
    
    static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
    					struct v4l2_format *f)
    {
    	int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);
    	if (ret < 0)
    		return ret;
    
        memcpy(&myvivi_format, f, sizeof(myvivi_format));
        
    	return ret;
    }
    
    static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
    			  struct v4l2_requestbuffers *p)
    {
    	return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
    }
    
    static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
    	return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
    }
    
    static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
    	return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
    }
    
    static int myvivi_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
    	return (videobuf_dqbuf(&myvivi_vb_vidqueue, p,
    				file->f_flags & O_NONBLOCK));
    }
    
    static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
            // 表示它是一个摄像头设备
            .vidioc_querycap      = myvivi_vidioc_querycap,
    
            /* 用于列举、获得、测试、设置摄像头的数据的格式 */
            .vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,
            .vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,
            .vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,
            .vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,
            
            /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
            .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
            .vidioc_querybuf      = myvivi_vidioc_querybuf,
            .vidioc_qbuf          = myvivi_vidioc_qbuf,
            .vidioc_dqbuf         = myvivi_vidioc_dqbuf,
    
    #if 0    
            
            // 启动/停止
            .vidioc_streamon      = myvivi_vidioc_streamon,
            .vidioc_streamoff     = myvivi_vidioc_streamoff,   
    #endif
    };
    
    
    static const struct v4l2_file_operations myvivi_fops = {
    	.owner		= THIS_MODULE,
        .open       = myvivi_open, //初始化队列
        .release    = myvivi_close,
        .mmap       = myvivi_mmap,
        .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
    };
    
    
    static struct video_device *myvivi_device;
    
    static void myvivi_release(struct video_device *vdev)
    {
    }
    
    
    static int myvivi_init(void)
    {
        int error;
        
        /* 1. 分配一个video_device结构体 */
        myvivi_device = video_device_alloc();
    
        /* 2. 设置 */
    
        /* 2.1 */
        myvivi_device->release = myvivi_release;
    
        /* 2.2 */
        myvivi_device->fops    = &myvivi_fops;
    
        /* 2.3 */
        myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
    
        /* 2.4 队列操作
         *  a. 定义/初始化一个队列(会用到一个spinlock)
         */
        spin_lock_init(&myvivi_queue_slock);
    
        /* 3. 注册 */
        error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
        
        return error;
    }
    
    static void myvivi_exit(void)
    {
        video_unregister_device(myvivi_device);
        video_device_release(myvivi_device);
    }
    
    module_init(myvivi_init);
    module_exit(myvivi_exit);
    MODULE_LICENSE("GPL");
    

    4.5 第五步 : 增加 启动/停止

    /* 仿照vivi.c */
    #include <linux/module.h>
    #include <linux/delay.h>
    #include <linux/errno.h>
    #include <linux/fs.h>
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/mm.h>
    #include <linux/ioport.h>
    #include <linux/init.h>
    #include <linux/sched.h>
    #include <linux/pci.h>
    #include <linux/random.h>
    #include <linux/version.h>
    #include <linux/mutex.h>
    #include <linux/videodev2.h>
    #include <linux/dma-mapping.h>
    #include <linux/interrupt.h>
    #include <linux/kthread.h>
    #include <linux/highmem.h>
    #include <linux/freezer.h>
    #include <media/videobuf-vmalloc.h>
    #include <media/v4l2-device.h>
    #include <media/v4l2-ioctl.h>
    
    
    static struct v4l2_format myvivi_format;
    
    /* 队列操作1: 定义 */
    static struct videobuf_queue myvivi_vb_vidqueue;
    static spinlock_t myvivi_queue_slock;
    
    
    
    /* 参考documentations/video4linux/v4l2-framework.txt:
     *     drivers\media\video\videobuf-core.c 
     ops->buf_setup   - calculates the size of the video buffers and avoid they
                to waste more than some maximum limit of RAM;
     ops->buf_prepare - fills the video buffer structs and calls
                videobuf_iolock() to alloc and prepare mmaped memory;
     ops->buf_queue   - advices the driver that another buffer were
                requested (by read() or by QBUF);
     ops->buf_release - frees any buffer that were allocated.
     
     *
     */
    
    
    /* ------------------------------------------------------------------
    	Videobuf operations
       ------------------------------------------------------------------*/
    /* APP调用ioctl VIDIOC_REQBUFS时会导致此函数被调用,
     * 它重新调整count和size
     */
    static int myvivi_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
    {
    
    	*size = myvivi_format.fmt.pix.sizeimage;
    
    	if (0 == *count)
    		*count = 32;
    
    	return 0;
    }
    
    /* APP调用ioctlVIDIOC_QBUF时导致此函数被调用,
     * 它会填充video_buffer结构体并调用videobuf_iolock来分配内存
     * 
     */
    static int myvivi_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
    						enum v4l2_field field)
    {
        /* 1. 做些准备工作 */
    
    #if 0
        /* 2. 调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存 */
    	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
    		rc = videobuf_iolock(vq, &buf->vb, NULL);
    		if (rc < 0)
    			goto fail;
    	}
    #endif
        /* 3. 设置状态 */
    	vb->state = VIDEOBUF_PREPARED;
    
    	return 0;
    }
    
    
    /* APP调用ioctlVIDIOC_QBUF时:
     * 1. 先调用buf_prepare进行一些准备工作
     * 2. 把buf放入队列
     * 3. 调用buf_queue(起通知作用)
     */
    static void myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
    {
    	vb->state = VIDEOBUF_QUEUED;
    	//list_add_tail(&buf->vb.queue, &vidq->active);
    }
    
    /* APP不再使用队列时, 用它来释放内存 */
    static void myvivi_buffer_release(struct videobuf_queue *vq,
    			   struct videobuf_buffer *vb)
    {
    	videobuf_vmalloc_free(vb);
    	vb->state = VIDEOBUF_NEEDS_INIT;
    }
    
    static struct videobuf_queue_ops myvivi_video_qops = {
    	.buf_setup      = myvivi_buffer_setup, /* 计算大小以免浪费 */
    	.buf_prepare    = myvivi_buffer_prepare,
    	.buf_queue      = myvivi_buffer_queue,
    	.buf_release    = myvivi_buffer_release,
    };
    
    /* ------------------------------------------------------------------
    	File operations for the device
       ------------------------------------------------------------------*/
    
    static int myvivi_open(struct file *file)
    {
        /* 队列操作2: 初始化 */
    	videobuf_queue_vmalloc_init(&myvivi_vb_vidqueue, &myvivi_video_qops,
    			NULL, &myvivi_queue_slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,
    			sizeof(struct videobuf_buffer), NULL); /* 倒数第2个参数是buffer的头部大小 */
    
    	return 0;
    }
    
    
    static int myvivi_close(struct file *file)
    {
    	videobuf_stop(&myvivi_vb_vidqueue);
    	videobuf_mmap_free(&myvivi_vb_vidqueue);
        
    	return 0;
    }
    
    static int myvivi_mmap(struct file *file, struct vm_area_struct *vma)
    {
    	return videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);
    }
    
    static unsigned int myvivi_poll(struct file *file, struct poll_table_struct *wait)
    {
    	return videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait);
    }
    
    static int myvivi_vidioc_querycap(struct file *file, void  *priv,
    					struct v4l2_capability *cap)
    {
    	strcpy(cap->driver, "myvivi");
    	strcpy(cap->card, "myvivi");
    	cap->version = 0x0001;
    	cap->capabilities =	V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    	return 0;
    }
    
    /* 列举支持哪种格式 */
    static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
    					struct v4l2_fmtdesc *f)
    {
    	if (f->index >= 1)
    		return -EINVAL;
    
    	strcpy(f->description, "4:2:2, packed, YUYV");
    	f->pixelformat = V4L2_PIX_FMT_YUYV;
    	return 0;
    }
    
    /* 返回当前所使用的格式 */
    static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
    					struct v4l2_format *f)
    {
        memcpy(f, &myvivi_format, sizeof(myvivi_format));
    	return (0);
    }
    
    /* 测试驱动程序是否支持某种格式 */
    static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
    			struct v4l2_format *f)
    {
    	unsigned int maxw, maxh;
        enum v4l2_field field;
    
        if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
            return -EINVAL;
    
    	field = f->fmt.pix.field;
    
    	if (field == V4L2_FIELD_ANY) {
    		field = V4L2_FIELD_INTERLACED;
    	} else if (V4L2_FIELD_INTERLACED != field) {
    		return -EINVAL;
    	}
    
    	maxw  = 1024;
    	maxh  = 768;
    
        /* 调整format的width, height, 
         * 计算bytesperline, sizeimage
         */
    	v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
    			      &f->fmt.pix.height, 32, maxh, 0, 0);
    	f->fmt.pix.bytesperline =
    		(f->fmt.pix.width * 16) >> 3;
    	f->fmt.pix.sizeimage =
    		f->fmt.pix.height * f->fmt.pix.bytesperline;
    
    	return 0;
    }
    
    static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
    					struct v4l2_format *f)
    {
    	int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);
    	if (ret < 0)
    		return ret;
    
        memcpy(&myvivi_format, f, sizeof(myvivi_format));
        
    	return ret;
    }
    
    static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
    			  struct v4l2_requestbuffers *p)
    {
    	return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
    }
    
    static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
    	return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
    }
    
    static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
    	return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
    }
    
    static int myvivi_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
    	return (videobuf_dqbuf(&myvivi_vb_vidqueue, p,
    				file->f_flags & O_NONBLOCK));
    }
    
    static int myvivi_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
    {
    	return videobuf_streamon(&myvivi_vb_vidqueue);
    }
    
    static int myvivi_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
    {
    	videobuf_streamoff(&myvivi_vb_vidqueue);
        return 0;
    }
    
    
    static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
            // 表示它是一个摄像头设备
            .vidioc_querycap      = myvivi_vidioc_querycap,
    
            /* 用于列举、获得、测试、设置摄像头的数据的格式 */
            .vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,
            .vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,
            .vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,
            .vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,
            
            /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
            .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
            .vidioc_querybuf      = myvivi_vidioc_querybuf,
            .vidioc_qbuf          = myvivi_vidioc_qbuf,
            .vidioc_dqbuf         = myvivi_vidioc_dqbuf,
            
            // 启动/停止
            .vidioc_streamon      = myvivi_vidioc_streamon,
            .vidioc_streamoff     = myvivi_vidioc_streamoff,   
    };
    
    
    static const struct v4l2_file_operations myvivi_fops = {
    	.owner		= THIS_MODULE,
        .open       = myvivi_open,
        .release    = myvivi_close,
        .mmap       = myvivi_mmap,
        .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
        .poll       = myvivi_poll,
    };
    
    
    static struct video_device *myvivi_device;
    
    static void myvivi_release(struct video_device *vdev)
    {
    }
    
    
    static int myvivi_init(void)
    {
        int error;
        
        /* 1. 分配一个video_device结构体 */
        myvivi_device = video_device_alloc();
    
        /* 2. 设置 */
    
        /* 2.1 */
        myvivi_device->release = myvivi_release;
    
        /* 2.2 */
        myvivi_device->fops    = &myvivi_fops;
    
        /* 2.3 */
        myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
    
        /* 2.4 队列操作
         *  a. 定义/初始化一个队列(会用到一个spinlock)
         */
        spin_lock_init(&myvivi_queue_slock);
    
        /* 3. 注册 */
        error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
        
        return error;
    }
    
    static void myvivi_exit(void)
    {
        video_unregister_device(myvivi_device);
        video_device_release(myvivi_device);
    }
    
    module_init(myvivi_init);
    module_exit(myvivi_exit);
    MODULE_LICENSE("GPL");
    

    4.6 定时器定时填充数据并唤醒进程

    /* 仿照vivi.c */
    #include <linux/module.h>
    #include <linux/delay.h>
    #include <linux/errno.h>
    #include <linux/fs.h>
    #include <linux/kernel.h>
    #include <linux/slab.h>
    #include <linux/mm.h>
    #include <linux/ioport.h>
    #include <linux/init.h>
    #include <linux/sched.h>
    #include <linux/pci.h>
    #include <linux/random.h>
    #include <linux/version.h>
    #include <linux/mutex.h>
    #include <linux/videodev2.h>
    #include <linux/dma-mapping.h>
    #include <linux/interrupt.h>
    #include <linux/kthread.h>
    #include <linux/highmem.h>
    #include <linux/freezer.h>
    #include <media/videobuf-vmalloc.h>
    #include <media/v4l2-device.h>
    #include <media/v4l2-ioctl.h>
    
    
    static struct v4l2_format myvivi_format;
    
    /* 队列操作1: 定义 */
    static struct videobuf_queue myvivi_vb_vidqueue;
    static spinlock_t myvivi_queue_slock;
    
    static struct list_head myvivi_vb_local_queue;
    
    
    static struct timer_list myvivi_timer;
    
    #include "fillbuf.c"
    
    /* 参考documentations/video4linux/v4l2-framework.txt:
     *     drivers\media\video\videobuf-core.c 
     ops->buf_setup   - calculates the size of the video buffers and avoid they
                to waste more than some maximum limit of RAM;
     ops->buf_prepare - fills the video buffer structs and calls
                videobuf_iolock() to alloc and prepare mmaped memory;
     ops->buf_queue   - advices the driver that another buffer were
                requested (by read() or by QBUF);
     ops->buf_release - frees any buffer that were allocated.
     
     *
     */
    
    
    /* ------------------------------------------------------------------
    	Videobuf operations
       ------------------------------------------------------------------*/
    /* APP调用ioctl VIDIOC_REQBUFS时会导致此函数被调用,
     * 它重新调整count和size
     */
    static int myvivi_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
    {
    
    	*size = myvivi_format.fmt.pix.sizeimage;
    
    	if (0 == *count)
    		*count = 32;
    
    	return 0;
    }
    
    /* APP调用ioctlVIDIOC_QBUF时导致此函数被调用,
     * 它会填充video_buffer结构体并调用videobuf_iolock来分配内存
     * 
     */
    static int myvivi_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
    						enum v4l2_field field)
    {
        /* 0. 设置videobuf */
    	vb->size = myvivi_format.fmt.pix.sizeimage;
        vb->bytesperline = myvivi_format.fmt.pix.bytesperline;
    	vb->width  = myvivi_format.fmt.pix.width;
    	vb->height = myvivi_format.fmt.pix.height;
    	vb->field  = field;
        
        
        /* 1. 做些准备工作 */
        myvivi_precalculate_bars(0);
    
    #if 0
        /* 2. 调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存 */
    	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
    		rc = videobuf_iolock(vq, &buf->vb, NULL);
    		if (rc < 0)
    			goto fail;
    	}
    #endif
        /* 3. 设置状态 */
    	vb->state = VIDEOBUF_PREPARED;
    
    	return 0;
    }
    
    
    /* APP调用ioctl VIDIOC_QBUF时:
     * 1. 先调用buf_prepare进行一些准备工作
     * 2. 把buf放入stream队列
     * 3. 调用buf_queue(起通知、记录作用)
     */
    static void myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
    {
    	vb->state = VIDEOBUF_QUEUED;
    
        /* 把videobuf放入本地一个队列尾部
         * 定时器处理函数就可以从本地队列取出videobuf
         */
        list_add_tail(&vb->queue, &myvivi_vb_local_queue);
    }
    
    /* APP不再使用队列时, 用它来释放内存 */
    static void myvivi_buffer_release(struct videobuf_queue *vq,
    			   struct videobuf_buffer *vb)
    {
    	videobuf_vmalloc_free(vb);
    	vb->state = VIDEOBUF_NEEDS_INIT;
    }
    
    static struct videobuf_queue_ops myvivi_video_qops = {
    	.buf_setup      = myvivi_buffer_setup, /* 计算大小以免浪费 */
    	.buf_prepare    = myvivi_buffer_prepare,
    	.buf_queue      = myvivi_buffer_queue,
    	.buf_release    = myvivi_buffer_release,
    };
    
    /* ------------------------------------------------------------------
    	File operations for the device
       ------------------------------------------------------------------*/
    
    static int myvivi_open(struct file *file)
    {
        /* 队列操作2: 初始化 */
    	videobuf_queue_vmalloc_init(&myvivi_vb_vidqueue, &myvivi_video_qops,
    			NULL, &myvivi_queue_slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,
    			sizeof(struct videobuf_buffer), NULL); /* 倒数第2个参数是buffer的头部大小 */
    
        myvivi_timer.expires = jiffies + 1;
        add_timer(&myvivi_timer);
    
    	return 0;
    }
    
    
    static int myvivi_close(struct file *file)
    {
        del_timer(&myvivi_timer);
    	videobuf_stop(&myvivi_vb_vidqueue);
    	videobuf_mmap_free(&myvivi_vb_vidqueue);
        
    	return 0;
    }
    
    static int myvivi_mmap(struct file *file, struct vm_area_struct *vma)
    {
    	return videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);
    }
    
    static unsigned int myvivi_poll(struct file *file, struct poll_table_struct *wait)
    {
    	return videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait);
    }
    
    static int myvivi_vidioc_querycap(struct file *file, void  *priv,
    					struct v4l2_capability *cap)
    {
    	strcpy(cap->driver, "myvivi");
    	strcpy(cap->card, "myvivi");
    	cap->version = 0x0001;
    	cap->capabilities =	V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    	return 0;
    }
    
    /* 列举支持哪种格式 */
    static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
    					struct v4l2_fmtdesc *f)
    {
    	if (f->index >= 1)
    		return -EINVAL;
    
    	strcpy(f->description, "4:2:2, packed, YUYV");
    	f->pixelformat = V4L2_PIX_FMT_YUYV;
    	return 0;
    }
    
    /* 返回当前所使用的格式 */
    static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
    					struct v4l2_format *f)
    {
        memcpy(f, &myvivi_format, sizeof(myvivi_format));
    	return (0);
    }
    
    /* 测试驱动程序是否支持某种格式 */
    static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
    			struct v4l2_format *f)
    {
    	unsigned int maxw, maxh;
        enum v4l2_field field;
    
        if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
            return -EINVAL;
    
    	field = f->fmt.pix.field;
    
    	if (field == V4L2_FIELD_ANY) {
    		field = V4L2_FIELD_INTERLACED;
    	} else if (V4L2_FIELD_INTERLACED != field) {
    		return -EINVAL;
    	}
    
    	maxw  = 1024;
    	maxh  = 768;
    
        /* 调整format的width, height, 
         * 计算bytesperline, sizeimage
         */
    	v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
    			      &f->fmt.pix.height, 32, maxh, 0, 0);
    	f->fmt.pix.bytesperline =
    		(f->fmt.pix.width * 16) >> 3;
    	f->fmt.pix.sizeimage =
    		f->fmt.pix.height * f->fmt.pix.bytesperline;
    
    	return 0;
    }
    
    static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
    					struct v4l2_format *f)
    {
    	int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);
    	if (ret < 0)
    		return ret;
    
        memcpy(&myvivi_format, f, sizeof(myvivi_format));
        
    	return ret;
    }
    
    static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
    			  struct v4l2_requestbuffers *p)
    {
    	return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
    }
    
    static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
    	return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
    }
    
    static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
    	return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
    }
    
    static int myvivi_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
    	return (videobuf_dqbuf(&myvivi_vb_vidqueue, p,
    				file->f_flags & O_NONBLOCK));
    }
    
    static int myvivi_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
    {
    	return videobuf_streamon(&myvivi_vb_vidqueue);
    }
    
    static int myvivi_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
    {
    	videobuf_streamoff(&myvivi_vb_vidqueue);
        return 0;
    }
    
    
    static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
            // 表示它是一个摄像头设备
            .vidioc_querycap      = myvivi_vidioc_querycap,
    
            /* 用于列举、获得、测试、设置摄像头的数据的格式 */
            .vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,
            .vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,
            .vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,
            .vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,
            
            /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
            .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
            .vidioc_querybuf      = myvivi_vidioc_querybuf,
            .vidioc_qbuf          = myvivi_vidioc_qbuf,
            .vidioc_dqbuf         = myvivi_vidioc_dqbuf,
            
            // 启动/停止
            .vidioc_streamon      = myvivi_vidioc_streamon,
            .vidioc_streamoff     = myvivi_vidioc_streamoff,   
    };
    
    
    static const struct v4l2_file_operations myvivi_fops = {
    	.owner		= THIS_MODULE,
        .open       = myvivi_open,
        .release    = myvivi_close,
        .mmap       = myvivi_mmap,
        .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
        .poll       = myvivi_poll,
    };
    
    
    static struct video_device *myvivi_device;
    
    static void myvivi_release(struct video_device *vdev)
    {
    }
    
    static void myvivi_timer_function(unsigned long data)
    {
        struct videobuf_buffer *vb;
    	void *vbuf;
    	struct timeval ts;
        
        /* 1. 构造数据: 从队列头部取出第1个videobuf, 填充数据
         */
    
        /* 1.1 从本地队列取出第1个videobuf */
        if (list_empty(&myvivi_vb_local_queue)) {
            goto out;
        }
        
        vb = list_entry(myvivi_vb_local_queue.next,
                 struct videobuf_buffer, queue);
        
        /* Nobody is waiting on this buffer, return */
        if (!waitqueue_active(&vb->done))
            goto out;
        
    
        /* 1.2 填充数据 */
        vbuf = videobuf_to_vmalloc(vb);
        //memset(vbuf, 0xff, vb->size);
        myvivi_fillbuff(vb);
        
        vb->field_count++;
        do_gettimeofday(&ts);
        vb->ts = ts;
        vb->state = VIDEOBUF_DONE;
    
        /* 1.3 把videobuf从本地队列中删除 */
        list_del(&vb->queue);
    
        /* 2. 唤醒进程: 唤醒videobuf->done上的进程 */
        wake_up(&vb->done);
        
    out:
        /* 3. 修改timer的超时时间 : 30fps, 1秒里有30帧数据
         *    每1/30 秒产生一帧数据
         */
        mod_timer(&myvivi_timer, jiffies + HZ/30);
    }
    
    static int myvivi_init(void)
    {
        int error;
        
        /* 1. 分配一个video_device结构体 */
        myvivi_device = video_device_alloc();
    
        /* 2. 设置 */
    
        /* 2.1 */
        myvivi_device->release = myvivi_release;
    
        /* 2.2 */
        myvivi_device->fops    = &myvivi_fops;
    
        /* 2.3 */
        myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
    
        /* 2.4 队列操作
         *  a. 定义/初始化一个队列(会用到一个spinlock)
         */
        spin_lock_init(&myvivi_queue_slock);
    
        /* 3. 注册 */
        error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
    
        /* 用定时器产生数据并唤醒进程 */
    	init_timer(&myvivi_timer);
        myvivi_timer.function  = myvivi_timer_function;
    
        INIT_LIST_HEAD(&myvivi_vb_local_queue);
        
        return error;
    }
    
    static void myvivi_exit(void)
    {
        video_unregister_device(myvivi_device);
        video_device_release(myvivi_device);
    }
    
    module_init(myvivi_init);
    module_exit(myvivi_exit);
    MODULE_LICENSE("GPL");
    
    展开全文
  • by fanxiushu 202-03-01 转载或引用请注明原始作者。...从CSDN上的第一篇文章开始:https://blog.csdn.net/fanxiushu/article/details/8496747 (虚拟摄像头驱动原理开发) 文章描述的是利用老的流内核来...

    by fanxiushu 202-03-01 转载或引用请注明原始作者。

    对于windows平台下的虚拟摄像头实现方式的研究比较多,范围也比较广,曾采用各种方式来实现windows平台下的虚拟摄像头。
    从CSDN上的第一篇文章开始:
    https://blog.csdn.net/fanxiushu/article/details/8496747 (虚拟摄像头驱动原理开发)
    文章描述的是利用老的流内核来实现的虚拟摄像头,调用stream.sys中导出的StreamClassRegisterAdapter 来注册和初始化摄像头。
    这种摄像头其实依然是WDM基于内核驱动模式的摄像头,绝大部分程序会把他当成硬件摄像头,只是直到WIN10以上的系统中,
    特别是UWP程序,由于放弃了老旧流内核,因此UWP程序基本上不能识别这种基于stream.sys的摄像头。

    再后来,实现了虚拟USB总线驱动之后,利用虚拟USB总线驱动模拟出了虚拟USB摄像头。
    https://blog.csdn.net/fanxiushu/article/details/52761644 ( USB设备驱动开发之扩展(利用USB虚拟总线驱动模拟USB摄像头)
    而这种驱动,本质上是调用windows自己提供的usbvideo.sys驱动,usbvideo.sys驱动根据USB通讯中描述符提供的信息,
    动态生成硬件摄像头,俗称USB摄像头。而我们再研究usbvideo.sys驱动,本质上它是基于 AVStream框架的流内核框架的驱动,
    它调用 ks.sys提供的 KsInitializeDriver 来注册和初始化摄像头。
    这篇文章就是基于AVstream框架,深入windows平台实现摄像头驱动本质。
    简单的说,现代的windows系统中,不管是你想怎么从驱动层实现摄像头驱动,都离不开AVStream框架。
    AVStream作为流内核框架,不单是实现摄像头,它可以开发基于视频流的各种视频采集卡的驱动,
    它同样适合开发基于音频采集的驱动,比如声卡驱动等。
    开发声卡驱动当然也可以使用 PortClass框架的音频驱动框架。
    windows提供了多种选择来开发声卡驱动。一般来说开发PortClass框架的声卡驱动稍微简单些。

    再后来也发布了一篇基于DirectShow应用层解决方案的摄像头驱动和对应的源代码,有兴趣可自行查阅。

    如果你的需求只是实现一款虚拟摄像头驱动,
    如果采用USB模拟方式来实现虚拟摄像头驱动,过程是冗余的,代价是高昂的。
    因为你得实现虚拟USB总线驱动,实现了总线驱动,还得模拟符合UVC标准的USB描述符。
    你得熟悉各种USB通讯协议和UVC协议。
    而实现了这些之后,实际上windows系统中的usbvideo.sys驱动还是得使用AVStream框架来实现摄像头驱动。
    因此这个过程是冗余的。
    以前的文章中也描述过,windows下的虚拟USB总线驱动比起linux平台中的虚拟USB总线驱动实现起来复杂得多。
    除非想利用虚拟USB总线驱动同时模拟各种USB设备,比如同时模拟USB摄像头,模拟USB鼠标键盘,模拟USB游戏手柄,
    模拟USB声卡,模拟U盘,等等。否则是并不划算的。

    虚拟摄像头用途好像还挺多,

    除了干“坏事”,比如提供虚假在线视频,在线聊天摄像头展现的所谓的“美女”极有可能都是假的。因此不可去信任所谓的视频认证。
    正常用途其实也多,比如实现 WebCamera,
    比如电脑没有摄像头或者自带的摄像头效果非常差,因此可以把手机的摄像头模拟成电脑的摄像头。

    在远程桌面控制中,甚至是在云桌面应用中。
    因为大部分都是USB的摄像头,似乎可以直接使用USB远程访问的方式,把USB摄像头的图像数据直接重定向到服务端。
    然而我们仔细分析,因为做USB重定向,传输的是USB通讯协议数据,摄像头的图像数据包含在USB通讯协议中,
    这个USB通讯协议是不能做有损压缩的,图像数据本身就非常庞大,这种方式浪费的网络带宽非常高。
    曾经做过实验,做USB重定向,传输640X480大小的YUY2格式的摄像头图像数据,大致30FPS,没对USB通讯协议做压缩,
    传输的时候,大约是 5Mbytes 每秒,基本上占了百兆网卡一半的带宽,而且还只是传输 640X480这种小分辨率的图像,
    如果是1920P这种图像,那更加恐怖,即使做无损压缩,使用LZMA这种高强度压缩,对图像数据来说,基本上也压缩不到多少。
    因此使用USB重定向方式来传输USB摄像头数据,不大合适。
    这种时候,基本上还是得开发对应的虚拟摄像头驱动,然后采集到真实摄像头数据之后,通过H264或H265压缩,把数据量大大减少,
    再通过网络传输,再到虚拟摄像头驱动端解码传输给虚拟摄像头驱动。

    回到我们的正题,如何开发基于AVStream框架的摄像头驱动。

    说起AVStream框架,就不得不和应用层的DirectShow框架做个类比。
    他们在核心概念上比较类同,因为同样有Filter概念,PIN概念。
    在AVStream框架驱动中,首先会生成一个设备KSDEVICE,这个通常是唯一的。
    然后再生成一个或多个FILTER Factory(Filter类工厂),每个Filter类工厂再生成一个或多个KSFILTER(Filter实例),
    接着再往下派生,
    一个KSFILTER可以再生成一个或多个 PIN Factory(PIN类工厂),每个PIN类工厂再生成一个或多个KSPIN(PIN实例)。
    如果熟悉WIndows平台中的COM组件编程,你会发现这种架构与COM类出奇的相似。
    AVStream本身就是借用COM类的概念,某些AVStream接口函数都是COM类型的函数。
    AVStream框架驱动生成一个或多个KSPIN之后,就该干正事的时候了,
    每个PIN的生成,意味着应用层有程序开始请求视频(或音频)数据了。
    然后在我们的AVStream驱动中,根据每个PIN请求视频(音频)的格式等参数,填充相应的图像(或音频)数据,
    然后应用层就能正确接收到对应的图像(音频)数据了。
    是滴,从概念上来说就这么简单。
    比如摄像头,虚拟摄像头和真实摄像头除了数据来源不同(一个通常从应用层传入数据源,另一个从硬件传入数据源),
    其他没有什么区别,都会在获取到数据源的时候,朝PIN实例填充数据。
    因此对于应用层的程序来说,他们是无法区分哪些是虚拟摄像头,哪些是硬件摄像头。

    上面已经说过了,在驱动的DriverEntry入口函数中,调用
    KsInitializeDriver 函数来注册和初始化AVStream框架的驱动。
    AVStream驱动依然是WDM驱动,依然要处理AddDevice,PNP即插即用事件。
    但是你可以简单到只在DriverEntry入口函数中调用
    KsInitializeDriver 这一个函数,
    因为大部分通用的WDM请求,AVStream框架都已经帮你处理好了。

    KsInitializeDriver函数需要传入一个参数 KSDEVICE_DISPATCH,
    这个结构中包含 KSDEVICE_DISPATCH和KSFILTER_DESCRIPTOR结构的参数。
    KSDEVICE_DISPATCH包含一组回调函数列表,其实也就是一组通用的PNP即插即用函数列表,
    包含AddDevice,IRP_MN_START_DEVICE等,都是通过AVStream框架封装之后导出的函数列表,
    这些函数通常是可选择的,如果没兴趣可以不用填写,AVStream会按照默认方式处理。
    KSFILTER_DESCRIPTOR 结构描述 Filter类信息,用于指导AVStream框架如何创建 filter Factory。
    如果不希望AVStream·帮你创建Filter类工厂,可以简单设置这个参数为NULL,
    然后在在其他地方,比如 在
    IRP_MN_START_DEVICE 设备启动的时候调用 KsCreateFilterFactory函数动态创建Filter类工厂。
    如下是个通常方式的初始化代码:
    static KSDEVICE_DISPATCH device_func_addr =
    {
        avs_AddDevice,                          // Pnp Add Device
        avs_Start,                              // Pnp Start
        NULL,                                   // Post-Start
        NULL,                                   // Pnp Query Stop
        NULL,                                   // Pnp Cancel Stop
        avs_Stop,                               // Pnp Stop
        NULL,                                   // Pnp Query Remove
        NULL,                                   // Pnp Cancel Remove
        NULL,                                   // Pnp Remove
        NULL,                                   // Pnp Query Capabilities
        NULL,                                   // Pnp Surprise Removal
        NULL,                                   // Power Query Power
        NULL,                                   // Power Set Power
        NULL                                    // Pnp Query Interface
    };
    static KSDEVICE_DESCRIPTOR device_desc =
    {
        &device_func_addr, 0, NULL    //自己动态创建 Filter类工厂
    };

    extern "C" NTSTATUS
    DriverEntry(PDRIVER_OBJECT  DriverObject, PUNICODE_STRING RegistryPath)
    {
        NTSTATUS status = STATUS_SUCCESS;

        status = KsInitializeDriver(DriverObject, RegistryPath, &device_desc);
        if (!NT_SUCCESS(status)) {
            DPT("*** KsInitializeDriver err=0x%X\n", status );
        }
        return status;
    }

    接着我们再来看看 KSFILTER_DESCRIPTOR这个结构,
    这个结构描述的内容很多,有描述Filter对应的回调函数的 KSFILTER_DISPATCH,
    有用于与应用层通讯的KSAUTOMATION_TABLE,这个结构在我们的虚拟摄像头获取应用层的数据源比较重要。
    有描述PIN Factory的 KSPIN_DESCRIPTOR_EX,同样的,如果想自己动态创建Pin Factory,可以简单设置此参数为NULL,
    然后在别的地方使用 KsFilterCreatePinFactory函数创建PIN类工厂。
    有描述 拓扑节点信息的KSNODE_DESCRIPTOR,和拓扑节点之间如何链接的KSTOPOLOGY_CONNECTION结构。
    这两个在摄像头驱动中可以不用填写,如果要开发声卡驱动比如麦克风驱动,并且需要出现在windows声音系统设备中,
    这两个参数就必须正确填写。
    至于其他参数的解释,可去查阅MSDN文档说明。

    然后就是对应的 PIN类工厂结构的描述
    KSPIN_DESCRIPTOR_EX, 这个结构里边的内容也比较多。
    有对应的PIN回调函数 KSPIN_DISPATCH,有与应用层通讯的
    KSAUTOMATION_TABLE
    有描述PIN的数据格式
    KSPIN_DESCRIPTOR,有如何分配PIN数据内存KSALLOCATOR_FRAMING_EX,
    有如何查看数据格式是否有匹配交集的 IntersectHandler回调函数。

    总之,
    KSFILTER_DESCRIPTOR和KSPIN_DESCRIPTOR_EX描述的内容非常多,很热闹。
    可以理解成这两个结构是指导AVstream框架如何创建对应的Filter Factory和PIN Factory的模板参数。

    我们在应用层开发对应的DSHOW应用程序的时候,通常是创建GraphBuild,然后添加filter,
    然后设置PIN 的format,然后把对应的PIN 连接起来,最后才是run运行起来。
    为了更好的理解这些流程,你可以去下载使用 graphedt.exe。
    驱动中AVStream框架为了配合应用层的流媒体框架(Dshow,Media Foundation)运行流程,需要提供对应的回调函数完成这些流程。
    核心部分就是KSFILTER_DISPATHCH和KSPIN_DISPATCH提供的回调函数。
    KSFILTER_DISPATCH提供了filter的创建和销毁回调函数,还提供了 filter Process回调函数,
    这个函数的用处就是处理基于Filter-Centric为中心的音视频数据源。具体下面再描述。
    KSPIN_DISPATCH提供了 PIN的创建和销毁回调函数,提供了setformat回调函数用于应用层程序设置音视频流的格式。
    提供了setstate回调函数,用于设置启动,暂停,运行,停止等PIN的各种状态。
    提供了 pin process 用于处理基于PIN-Centric为中心的音视频数据源。
    同时还提供了 PIN的connect和disconnect回调函数,也就是应用层对应的连接和断开连接的函数,clock时钟回调函数等等。

    当应用层程序set format,connect pin之后,就开始运行起来接收或者发送流数据了。
    这个时候,KSFILTER_DISPATCH或KSPIN_DISPATCH中对应的Process回调函数就会被调用。
    至于究竟调用哪个Process回调函数,取决于我们在驱动中的处理方式。
    如果是基于filter-Gentric方式,我们把KSFILTER_DISPATCH中的Process设置为有效函数,KSPIN_DISPATCH中的Process设置为NULL,
    基于Pin-Centric方式,把上面的过程反过来即可。
    当然还得注意两种方式中某些Flag参数的设置,具体可查阅MSDN文档或WDK实例程序。
    这两种方式没有本质的不同,不会影响生成的驱动。至于采用哪种方式,取决于你的驱动使用哪种方式更合适。

    在我们的虚拟摄像头驱动中,我们采用Filter-Centric方式。
    在filter Process回调函数中,会提供一个 PKSPROCESSPIN_INDEXENTRY 参数。
    这个参数包含 PKSPROCESSPIN 结构的参数和用于指示有多少个实例PIN的Count。

    PKSPROCESSPIN包含PKSSTREAM_POINTER结构,这个结构里边存储或者需要填充的就是音视频数据。
    AVStream框架在处理Filter Process的时候,会把当前Filter生成的所有PIN Factory组成一个

    PKSPROCESSPIN_INDEXENTRY数组,然后对于每个PIN Factory生成的PIN实例组成 PKSPROCESSPIN数组,
    传递给Filter Process回调函数,也就是我们在这个回调函数中,就能一下子填充所有PIN实例需求的音视频数据了。
    通常,我们在Filter Process回调函数中返回 STATUS_PENDING,
    当我们的驱动有数据源的时候,调用 KsFilterAttemptProcessing触发AVStream框架再次调用Filter Process回调函数。
    如果直接返回STATUS_SUCCESS,上层AVStream框架就会不停调用Filter Process回调函数,这显然不是我们需要的结果。

    最后,对于我们的虚拟摄像头驱动,如何从应用层把视频数据传递给驱动层。上面也提到过,

    KSAUTOMATION_TABLE 结构是用于与应用层通讯的结构,我们可以注册一个自定义的Property属性。
    然后视频数据通过这个接口传递给驱动层。这样驱动层就获取到视频数据了。


    基于AVStream框架的驱动就介绍到此。


    有兴趣可去
    https://github.com/fanxiushu/xdisp_virt
    项目下的virtual_camera子目录下载使用最近开发的虚拟摄像头驱动。
    可以安装多个驱动实例。
    每个驱动实例成对的生成一个虚拟摄像头和生成一个虚拟麦克风。
    至于音视频的数据源,则来自xdisp_virt程序。

    下图是WIN10系统中skype发现查询到的虚拟摄像头

    其中 A代表的 Fanxiushu VCamera是采用stream.sys实现的老的内核流方式的虚拟摄像头,可以看到,在skype中并不能被识别出来。
    B部分则是使用AVStream框架实现的虚拟摄像头和虚拟麦克风,在skype中都被正确识别,同时正常运行了。
    虽然基于UWP的skype在中国境内估计没啥人在用,但是可以代表一大类对摄像头驱动非常挑剔的应用层程序。

    下图是win10 自带的camera相机和teamviewer,以及QQ中同时使用的情况,可以看到,不同于硬件摄像头本身的资源限制,
    我们在虚拟摄像头中,可以同时开启多个实例。




    下图是在windows7 平台实现的虚拟摄像头,然后给Android模拟器使用的效果。
    这可以给Android的app提供一个摄像头作弊的机会。




     

    展开全文
  • 这是一个基于linux-2.6.31.14内核的VIVI虚拟摄像头驱动的简单实现,只实现了摄像头驱动的核心代码,亲测可以使用,其他版本的linux内核稍加修改就可以使用。
  • 概述前面简单分析了内核中虚拟摄像头驱动 vivi 的框架与实现,本文参考 vivi 来写一个虚拟摄像头驱动,查询、设置视频格式相对简单,难点在于 vb2_buf 的处理过程。数据采集流程分析 在我的程序中,大概的数据采集...

    概述

    前面简单分析了内核中虚拟摄像头驱动 vivi 的框架与实现,本文参考 vivi 来写一个虚拟摄像头驱动,查询、设置视频格式相对简单,难点在于 vb2_buf 的处理过程。

    数据采集流程分析

    图1 
    在我的程序中,大概的数据采集流程如上图所示,启动视频采集之后,创建了一个内核线程,内核线程每30ms 唤醒一次,每一次唤醒都会尝试用 queue_list 中取出一个 buffer 填充数据之后挂入 done_list ,挂入 done_list 之后就会唤醒应用程序(poll 中休眠),应用程序唤醒之后就会 dqbuf 获取数据,处理完数据再 qbuf 把 buffer 挂入 queue_list 的头部,一直循环。

    代码

    #include <linux/module.h>
    #include <linux/errno.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/sched.h>
    #include <linux/slab.h>
    #include <linux/font.h>
    #include <linux/mutex.h>
    #include <linux/videodev2.h>
    #include <linux/kthread.h>
    #include <linux/freezer.h>
    #include <media/videobuf2-vmalloc.h>
    #include <media/v4l2-device.h>
    #include <media/v4l2-ioctl.h>
    #include <media/v4l2-ctrls.h>
    #include <media/v4l2-fh.h>
    #include <media/v4l2-event.h>
    #include <media/v4l2-common.h>
    
    #define VFL_TYPE_GRABBER    0
    
    #define MAX_WIDTH 1920
    #define MAX_HEIGHT 1200
    static unsigned int vid_limit = 16;
    
    static struct video_device *video_dev;      // video_device 结构,用来描述一个 video 设备
    static struct vb2_queue vivi_queue;         // vivi_queue 用来存放缓冲区信息,缓冲区链表等
    struct task_struct         *kthread;        // 内核线程,用来向缓冲区中填充数据
    DECLARE_WAIT_QUEUE_HEAD(wait_queue_head);   // 等待队列头
    struct list_head        my_list;            // 链表头
    
    // 用来存放应用程序设置的视频格式
    static struct mformat {
        __u32           width;
        __u32           height;
        __u32           pixelsize;
        __u32           field;
        __u32           fourcc;
        __u32           depth;
    }mformat;
    
    static void mvideo_device_release(struct video_device *vdev)
    {
    
    }
    
    static long mvideo_ioctl(struct file *file, unsigned int cmd, void *arg)
    {
        int ret = 0;
        struct v4l2_fh *fh = NULL;
        switch (cmd) {
    
            case VIDIOC_QUERYCAP:
            {
                struct v4l2_capability *cap = (struct v4l2_capability *)arg;
                cap->version = LINUX_VERSION_CODE;
                ret = video_dev->ioctl_ops->vidioc_querycap(file, NULL, cap);
                break;
            }
            case VIDIOC_ENUM_FMT:
            {
                struct v4l2_fmtdesc *f = arg;
                if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
                    ret = video_dev->ioctl_ops->vidioc_enum_fmt_vid_cap(file, fh, f);
                }else{
                    printk("V4L2_BUF_TYPE_VIDEO_CAPTURE error\n");
                }
                break;
            }
            case VIDIOC_G_FMT:
            {
                struct v4l2_format *f = (struct v4l2_format *)arg;
                if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
                    ret = video_dev->ioctl_ops->vidioc_g_fmt_vid_cap(file, fh, f);
                }else{
                    printk("V4L2_BUF_TYPE_VIDEO_CAPTURE error\n");
                }
                break;
            }
            case VIDIOC_TRY_FMT:
            {
                struct v4l2_format *f = (struct v4l2_format *)arg;
                if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
                    ret = video_dev->ioctl_ops->vidioc_try_fmt_vid_cap(file, fh, f);
                }else{
                    printk("V4L2_BUF_TYPE_VIDEO_CAPTURE error\n");
                }
                break;
            }
            case VIDIOC_S_FMT:
            {
                struct v4l2_format *f = (struct v4l2_format *)arg;
                if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
                    video_dev->ioctl_ops->vidioc_s_fmt_vid_cap(file, fh, f);
                }else{
                    printk("V4L2_BUF_TYPE_VIDEO_CAPTURE error\n");
                }
                break;
            }
            case VIDIOC_REQBUFS:
            {
                struct v4l2_requestbuffers *p = arg;
                ret = video_dev->ioctl_ops->vidioc_reqbufs(file, fh, p);
                break;
            }
            case VIDIOC_QUERYBUF:
            {   
                struct v4l2_buffer *p = arg;
                ret = video_dev->ioctl_ops->vidioc_querybuf(file, fh, p);
                break;
            }
            case VIDIOC_QBUF:
            {
                struct v4l2_buffer *p = arg;
                ret = video_dev->ioctl_ops->vidioc_qbuf(file, fh, p);
                break;
            }
            case VIDIOC_DQBUF:
            {
                struct v4l2_buffer *p = arg;
                ret = video_dev->ioctl_ops->vidioc_dqbuf(file, fh, p);
                break;
            }
            case VIDIOC_STREAMON:
            {
                enum v4l2_buf_type i = *(int *)arg;
                ret = video_dev->ioctl_ops->vidioc_streamon(file, fh, i);
                break;
            }
            case VIDIOC_STREAMOFF:
            {
                enum v4l2_buf_type i = *(int *)arg;
                ret = video_dev->ioctl_ops->vidioc_streamoff(file, fh, i);
                break;
            }
        }
        return ret;
    }
    
    static int vivi_mmap(struct file *file, struct vm_area_struct *vma)
    {   
        int ret;
        printk("enter mmap\n");
        ret = vb2_mmap(&vivi_queue, vma);
        if(ret == 0){
            printk("mmap ok\n");    
        }else{
            printk("mmap error\n"); 
        }
        return ret;
    }
    
    
    // 查询设备能力
    static int mvidioc_querycap(struct file *file, void  *priv, struct v4l2_capability *cap)
    {
        strcpy(cap->driver, "vivi");
        strcpy(cap->card, "vivi");
        strcpy(cap->bus_info, "mvivi");
        cap->device_caps = V4L2_CAP_VIDEO_CAPTURE;
        cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING;
        printk("mvidioc_querycap  \n");
        return 0;
    }
    
    // 枚举视频支持的格式
    static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv, struct v4l2_fmtdesc *f)
    {
        printk("vidioc_enum_fmt_vid_cap  \n");
        if (f->index >= 1)
            return -EINVAL;
    
        strcpy(f->description, "mvivi");
        f->pixelformat = mformat.fourcc;
        printk("vidioc_enum_fmt_vid_cap  \n");
        return 0;
    }
    
    // 修正应用层传入的视频格式
    static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
    {
        printk("vidioc_try_fmt_vid_cap\n");
        if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) {
            return -EINVAL;
        }
    
        f->fmt.pix.field = V4L2_FIELD_INTERLACED;
        v4l_bound_align_image(&f->fmt.pix.width, 48, MAX_WIDTH, 2,
                      &f->fmt.pix.height, 32, MAX_HEIGHT, 0, 0);
        f->fmt.pix.bytesperline = (f->fmt.pix.width * mformat.depth) / 8;
        f->fmt.pix.sizeimage    = f->fmt.pix.height * f->fmt.pix.bytesperline;
        if (mformat.fourcc == V4L2_PIX_FMT_YUYV)
            f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
        else
            f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
        return 0;
    }
    
    // 获取支持的格式
    static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
    {
        // 将参数写回用户空间
        f->fmt.pix.width        = mformat.width;
        f->fmt.pix.height       = mformat.height;
        f->fmt.pix.field        = mformat.field;
        f->fmt.pix.pixelformat  = mformat.fourcc;
        f->fmt.pix.bytesperline = (f->fmt.pix.width * mformat.depth) / 8;
        f->fmt.pix.sizeimage    = f->fmt.pix.height * f->fmt.pix.bytesperline;
        if (mformat.fourcc == V4L2_PIX_FMT_YUYV)
            f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
        else
            f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
        printk("vidioc_g_fmt_vid_cap  \n");
        return 0;
    }
    
    // 设置视频格式
    static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
    {
        int ret = vidioc_try_fmt_vid_cap(file, priv, f);
        if (ret < 0)
            return ret;
            // 存储用户空间传入的参数设置
        mformat.fourcc      =  V4L2_PIX_FMT_YUYV;
        mformat.pixelsize   =  mformat.depth / 8;
        mformat.width       =  f->fmt.pix.width;
        mformat.height      =  f->fmt.pix.height;
        mformat.field       =  f->fmt.pix.field;
        printk("vidioc_s_fmt_vid_capp  \n");
        return 0;
    }
    
    // vb2 核心层 vb2_reqbufs 中调用它,确定申请缓冲区的大小
    static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
                    unsigned int *nbuffers, unsigned int *nplanes,
                    unsigned int sizes[], void *alloc_ctxs[])
    {
        unsigned long size;
        printk("mformat.width %d \n",mformat.width);
        printk("mformat.height %d \n",mformat.height);
        printk("mformat.pixelsize %d \n",mformat.pixelsize);
    
        size = mformat.width * mformat.height * mformat.pixelsize;
    
        if (0 == *nbuffers)
            *nbuffers = 32;
    
        while (size * *nbuffers > vid_limit * 1024 * 1024)
            (*nbuffers)--;
    
        *nplanes = 1;
    
        sizes[0] = size;
        return 0;
    }
    
    static int buffer_init(struct vb2_buffer *vb)
    {
        return 0;
    }
    
    static int buffer_finish(struct vb2_buffer *vb)
    {
        return 0;
    }
    
    static int buffer_prepare(struct vb2_buffer *vb)
    {
        unsigned long size;
        size = mformat.width * mformat.height * mformat.pixelsize;
        vb2_plane_size(vb, 0);
        //vb2_set_plane_payload(&buf->vb, 0, size);
        return 0;
    }
    
    static void buffer_queue(struct vb2_buffer *vb)
    {
    
    }
    
    // 内核线程中填充数据,效果是一个逐渐放大的圆形,视频大小为 640 * 480
    static void vivi_fillbuff(struct vb2_buffer *vb)
    {
        void *vbuf = NULL;   
        unsigned char (*p)[mformat.width][mformat.pixelsize];
        unsigned int i,j;
        vbuf = vb2_plane_vaddr(vb, 0);
        static unsigned int t = 0;
        p = vbuf;
    
        for(j = 0; j < mformat.height; j++)
            for(i = 0; i < mformat.width; i++){
                if((j - 240)*(j - 240) + (i - 320)*(i - 320) < (t * t)){
                    *(*(*(p+j)+i)+0) = (unsigned char)0xff;
                    *(*(*(p+j)+i)+1) = (unsigned char)0xff;
                }else{
                    *(*(*(p+j)+i)+0) = (unsigned char)0;
                    *(*(*(p+j)+i)+1) = (unsigned char)0;
                }           
            }
        t++;
        printk("%d\n",t);
        if( t >= mformat.height/2) t = 0;
    }
    
    // 内核线程每一次唤醒调用它
    static void vivi_thread_tick(void)
    {
        struct vb2_buffer *buf = NULL;
            struct list_head *list;
            struct vb2_buffer *task;
        unsigned long flags;
        if (list_empty(&vivi_queue.queued_list)) {
            //printk("No active queue to serve\n");
            return;
        }
        // 注意我们这里取出之后就删除了,剩的重复工作,但是在 dqbuf 时,vb2_dqbuf 还会删除一次,我做的处理是在dqbuf之前将buf随便挂入一个链表
        buf = list_entry(vivi_queue.queued_list.next, struct vb2_buffer, queued_entry);
        list_del(&buf->queued_entry);
    
        /* 填充数据 */
        vivi_fillbuff(buf);
        printk("filled buffer %p\n", buf->planes[0].mem_priv);
    
        // 它干两个工作,把buffer 挂入done_list 另一个唤醒应用层序,让它dqbuf
        vb2_buffer_done(buf, VB2_BUF_STATE_DONE);
    }
    
    #define WAKE_NUMERATOR 30
    #define WAKE_DENOMINATOR 1001
    #define BUFFER_TIMEOUT     msecs_to_jiffies(500)  /* 0.5 seconds */
    #define frames_to_ms(frames)                    \
        ((frames * WAKE_NUMERATOR * 1000) / WAKE_DENOMINATOR)
    
    static void vivi_sleep(void)
    {
        int timeout;
        DECLARE_WAITQUEUE(wait, current);
    
        add_wait_queue(&wait_queue_head, &wait);
        if (kthread_should_stop())
            goto stop_task;
    
        /* Calculate time to wake up */
        timeout = msecs_to_jiffies(frames_to_ms(1));
    
        vivi_thread_tick();
    
        schedule_timeout_interruptible(timeout);
    
    stop_task:
        remove_wait_queue(&wait_queue_head, &wait);
        try_to_freeze();
    }
    
    static int vivi_thread(void *data)
    {
        set_freezable();
    
        for (;;) {
            vivi_sleep();
    
            if (kthread_should_stop())
                break;
        }
        printk("thread: exit\n");
        return 0;
    }
    
    static int vivi_start_generating(void)
    {   
        kthread = kthread_run(vivi_thread, video_dev, video_dev->name);
    
        if (IS_ERR(kthread)) {
            printk("kthread_run error\n");
            return PTR_ERR(kthread);
        }
    
        /* Wakes thread */
        wake_up_interruptible(&wait_queue_head);
    
        return 0;
    }
    
    static int start_streaming(struct vb2_queue *vq, unsigned int count)
    {
        vivi_start_generating();
        return 0;
    }
    static int stop_streaming(struct vb2_queue *vq)
    {
        if (kthread) {
            kthread_stop(kthread);
            kthread = NULL;
        }
    /*
    
        while (!list_empty(&vivi_queue.queued_list)) {
            struct vb2_buffer *buf;
            buf = list_entry(vivi_queue.queued_list.next, struct vb2_buffer, queued_entry);
            list_del(&buf->queued_entry);
            vb2_buffer_done(buf, VB2_BUF_STATE_ERROR);
        }
    */
        return 0;
    }
    static struct vb2_ops vivi_video_qops = {
        .queue_setup        = queue_setup,
        .buf_init       = buffer_init,
        .buf_finish     = buffer_finish,
        .buf_prepare        = buffer_prepare,
        .buf_queue      = buffer_queue,
        .start_streaming    = start_streaming,
        .stop_streaming     = stop_streaming,
    };
    
    static int mvivi_open(struct file *filp)
    {   
        vivi_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        vivi_queue.io_modes = VB2_MMAP;
        vivi_queue.drv_priv = video_dev;
        //vivi_queue.buf_struct_size = sizeof(struct vivi_buffer);
        vivi_queue.ops      = &vivi_video_qops;
        vivi_queue.mem_ops  = &vb2_vmalloc_memops;
        vivi_queue.name = "vb2";
        vivi_queue.buf_struct_size = sizeof(struct vb2_buffer);
        INIT_LIST_HEAD(&vivi_queue.queued_list);
        INIT_LIST_HEAD(&vivi_queue.done_list);
        spin_lock_init(&vivi_queue.done_lock);
        init_waitqueue_head(&vivi_queue.done_wq);
        mformat.fourcc = V4L2_PIX_FMT_YUYV;
        mformat.depth  = 16;
        INIT_LIST_HEAD(&my_list);
        return 0;
    }
    
    static int vidioc_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
    {
        printk("vidioc_reqbufs  \n");
        printk("count %d\n",p->count);
        printk("memory %d\n",p->memory);
        return vb2_reqbufs(&vivi_queue, p);
    }
    
    static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
        printk("vidioc_querybuf  \n");
        return vb2_querybuf(&vivi_queue, p);
    }
    
    static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
        printk("vidioc_qbuf buffer  \n");
        return vb2_qbuf(&vivi_queue, p);
    }
    
    static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
    {
        printk("vidioc_dqbuf buffer  \n");
        struct vb2_buffer *vb;
        vb = list_first_entry(&vivi_queue.done_list, struct vb2_buffer, done_entry);
        list_add_tail(&vb->queued_entry, &my_list);
        return vb2_dqbuf(&vivi_queue, p, file->f_flags & O_NONBLOCK);
    }
    
    static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
    {
        printk("vidioc_streamon  \n");
        return vb2_streamon(&vivi_queue, i);
    }
    
    static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
    {
        printk("vidioc_streamoff  \n");
        return vb2_streamoff(&vivi_queue, i);
    }
    
    static struct v4l2_ioctl_ops mvivi_ioctl_ops = {
        .vidioc_querycap            = mvidioc_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,
    };
    
    static unsigned int mvivi_poll(struct file *file, struct poll_table_struct *wait)
    {
        struct vb2_buffer *vb = NULL;
        int res = 0;
        printk("enter the poll \n");
    
        poll_wait(file, &vivi_queue.done_wq, wait);
    
        if (!list_empty(&vivi_queue.done_list))
            vb = list_first_entry(&vivi_queue.done_list, struct vb2_buffer, done_entry);
    
        if (vb && (vb->state == VB2_BUF_STATE_DONE || vb->state == VB2_BUF_STATE_ERROR)) {
            return (V4L2_TYPE_IS_OUTPUT(vivi_queue.type)) ?
                    res | POLLOUT | POLLWRNORM :
                    res | POLLIN | POLLRDNORM;  
        }
        return 0;
    }
    
    long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg)
    {
        return video_usercopy(file, cmd, arg, mvideo_ioctl);
    }
    
    static struct v4l2_file_operations mvivi_fops = {
        .owner          = THIS_MODULE,
        .open           = mvivi_open,
        .unlocked_ioctl = video_ioctl2,
    
        //.release        = mvivi_close,
        .poll           = mvivi_poll,   
        .mmap           = vivi_mmap,
    };
    
    static struct video_device vivi_template = {
        .name       = "mvivi",
        .fops       = &mvivi_fops,
        .ioctl_ops  = &mvivi_ioctl_ops,
        .release    = mvideo_device_release,
    };
    
    static int  mvivi_init(void)
    {
        int ret;
        video_dev   = video_device_alloc();
        *video_dev  = vivi_template;
        ret = video_register_device(video_dev, VFL_TYPE_GRABBER, -1);
        if(ret != 0){
            printk(" video_register_device error\n");
        }else{
            printk(" video_register_device ok\n");
        }
        return ret;
    }
    
    static void  mvivi_exit(void)
    {
        video_unregister_device(video_dev); 
    }
    
    module_init(mvivi_init);
    module_exit(mvivi_exit);
    MODULE_LICENSE("GPL");
    展开全文
  • 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头(五)三星平台fimc驱动详解一 深入学习Linux摄像头(六)三星平台fimc驱动详解二 深入学习Linux摄像头(三)虚拟摄像头驱动分析 上一篇文章讲解了...

    深入学习Linux摄像头系列

    深入学习Linux摄像头(一)v4l2应用编程

    深入学习Linux摄像头(二)v4l2驱动框架

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

    深入学习Linux摄像头(四)三星平台fimc驱动详解

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

    上一篇文章讲解了V4L2的驱动框架,这一节我们来分析一个驱动程序,Linux内核带有一个虚拟摄像头驱动(vivi.c),这个虚拟摄像头使用V4L2驱动框架编写,只是少了硬件操作,数据来源是虚拟的,这篇文章就来分析它

    看一个驱动程序首先从入口开始看起

    module_init(vivi_init);
    
    static int __init vivi_init(void)
    {
        vivi_create_instance(i);
    }
    

    入口函数调用了vivi_create_instance

    static int __init vivi_create_instance(int inst)
    {
        struct vivi_dev *dev;
        struct video_device *vfd;
    
        /* 分配vivi_dev */
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        
        /* 初始化缓存队列 */
        videobuf_queue_vmalloc_init(&dev->vb_vidq, &vivi_video_qops, // 赋值和初始化
                                    NULL, &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE,
                                    V4L2_FIELD_INTERLACED,
                                    sizeof(struct vivi_buffer), dev);
    
        /* 分配video_device */
        vfd = video_device_alloc();
    
        /* 设置video_device */
    	*vfd = vivi_template;
        
        /* 注册video_device */
        video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
    }
    

    从上面可以看到,首分配一个vivi_dev,看一看vivi_dev

    struct vivi_dev {
    	struct v4l2_device 	   v4l2_dev;
        struct video_device        *vfd; 
        ...
        /* 像素格式 */
        struct vivi_fmt            *fmt;
    }
    

    然后初始化缓存队列,videobuf_queue_vmalloc_init这个函数v4l2的videobuf提供的接口,作用是初始化一个缓存队列struct videobuf_queue,其中就设置了vivi_video_qops

    struct videobuf_queue {
        /* 缓存 */
    	struct videobuf_buffer     *bufs[VIDEO_MAX_FRAME];
        
    	/* capture via mmap() + ioctl(QBUF/DQBUF) */
    	struct list_head           stream; // 维护缓存队列的一个链表
    }
    
    static struct videobuf_queue_ops vivi_video_qops = {
    	.buf_setup      = buffer_setup,
    	.buf_prepare    = buffer_prepare,
    	.buf_queue      = buffer_queue,
    	.buf_release    = buffer_release,
    };
    

    这些函数会在操作缓存队列的时候会被调用

    再接下来就是分配一个video_device,设置它,然后再注册它

    上一篇文章我们分析了调用video_register_device会为video_device注册一个字符设备并生成设备节点,当应用层发生系统调用时,会先调用到字符设备的fops,经过v4l2的核心层,最终回调到video_device的fops

    我们来看一看是如何设置video_device

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

    可以看到vivi.c提供的video_device模板设置了vivi_fopsvivi_ioctl_ops,这两个结构体中有一大堆回调函数,我们先看一眼,稍后再具体分析

    static const struct v4l2_file_operations vivi_fops = {
    	.owner		= THIS_MODULE,
    	.release        = vivi_close,
    	.read           = vivi_read,
    	.poll		= vivi_poll,
    	.ioctl          = video_ioctl2, /* V4L2 ioctl handler */
    	.mmap           = vivi_mmap,
    };
    
    static const struct v4l2_ioctl_ops vivi_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_s_std         = vidioc_s_std,
    	.vidioc_enum_input    = vidioc_enum_input,
    	.vidioc_g_input       = vidioc_g_input,
    	.vidioc_s_input       = vidioc_s_input,
    	.vidioc_streamon      = vidioc_streamon,
    	.vidioc_streamoff     = vidioc_streamoff,
    	.vidioc_queryctrl     = vidioc_queryctrl,
    	.vidioc_g_ctrl        = vidioc_g_ctrl,
    	.vidioc_s_ctrl        = vidioc_s_ctrl,
    };
    

    上面已经介绍了vivi.c大概做了什么事了,接下来我们按照v4l2的应用编写流程来具体分析每个回调函数的实现细节

    这篇文章深入学习Linux摄像头(一)v4l2应用编程对v4l2应用编程作了详解的讲解

    v4l2的操作流程

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

    下面按照这些流程来分析虚拟摄像头驱动

    • VIDIOC_QUERYCAP

      返回设备的功能

      static int vidioc_querycap(struct file *file, void  *priv,
      					struct v4l2_capability *cap)
      {
          cap->version = VIVI_VERSION;
      	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | \
      			    V4L2_CAP_READWRITE;
      }
      

      V4L2_CAP_VIDEO_CAPTURE:表示捕获设备

      V4L2_CAP_STREAMING:数据的读取支持流形式

      V4L2_CAP_READWRITE:数据的读取支持read/write操作

    • VIDIOC_ENUM_FMT

      枚举设备支持的像素格式

      static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
      					struct v4l2_fmtdesc *f)
      {
          fmt = &formats[f->index];
          strlcpy(f->description, fmt->name, sizeof(f->description));
          f->pixelformat = fmt->fourcc;
      }
      

      从代码中可以看到,根据下表从formats数组中获取一项,然后将结果返回

      看一看formats,表示vivi支持的像素格式

      static struct vivi_fmt formats[] = {
      	{
      		.name     = "4:2:2, packed, YUYV",
      		.fourcc   = V4L2_PIX_FMT_YUYV,
      		.depth    = 16,
      	},
      	{
      		.name     = "4:2:2, packed, UYVY",
      		.fourcc   = V4L2_PIX_FMT_UYVY,
      		.depth    = 16,
      	},
      	{
      		.name     = "RGB565 (LE)",
      		.fourcc   = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
      		.depth    = 16,
      	},
      	{
      		.name     = "RGB565 (BE)",
      		.fourcc   = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
      		.depth    = 16,
      	},
      	{
      		.name     = "RGB555 (LE)",
      		.fourcc   = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */
      		.depth    = 16,
      	},
      	{
      		.name     = "RGB555 (BE)",
      		.fourcc   = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */
      		.depth    = 16,
      	},
      };
      
    • VIDIOC_S_FMT

      设置像素格式

      static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
      					struct v4l2_format *f)
      {
          struct vivi_dev *dev = video_drvdata(file);
          
          /* 测试是否支持此格式 */
          vidioc_try_fmt_vid_cap(file, priv, f);
          
          /* 设置好格式 */
          dev->fmt = get_format(f);
      }
      

      首先调用vidioc_try_fmt_vid_cap测试是否支持此格式

      如果支持就记录在vivi_dev中

      看一看vidioc_try_fmt_vid_cap

      static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
      			struct v4l2_format *f)
      {
          /* 判断是否能从formats数组中获取该像素 */
      	fmt = get_format(f);   
          if (!fmt)
              return -EINVAL;
          
          /* 对齐 */
          v4l_bound_align_image();
      }
      
    • VIDIOC_REQBUFS

      申请缓存

      static int vidioc_reqbufs(struct file *file, void *priv,
      			  struct v4l2_requestbuffers *p)
      {
          videobuf_reqbufs(&dev->vb_vidq, p);
      }
      

      通过调用videobuf提供的接口

      其中的dev->vb_vidq是在vivi_create_instance中初始化的struct videobuf_queue对象

      static int __init vivi_create_instance(int inst)
      {
          ...
          /* 初始化缓存队列 */
          videobuf_queue_vmalloc_init(&dev->vb_vidq, &vivi_video_qops, // 赋值和初始化
                                      NULL, &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE,
                                      V4L2_FIELD_INTERLACED,
                                      sizeof(struct vivi_buffer), dev);
          ...
      }
      

      看一看videobuf_reqbufs做了什么

      int videobuf_reqbufs(struct videobuf_queue *q,
      		 struct v4l2_requestbuffers *req)
      {
          count = req->count;
          
          /* 回到buf_setup函数获取buf的个数和大小 */
          q->ops->buf_setup(q, &count, &size);
          
          /* 申请缓存 */
          __videobuf_mmap_setup(q, count, size, req->memory);
      }
      

      其中的q->ops是在初始化缓存队列时设置的(videobuf_queue_vmalloc_init),内容如下

      static struct videobuf_queue_ops vivi_video_qops = {
      	.buf_setup      = buffer_setup,
      	.buf_prepare    = buffer_prepare,
      	.buf_queue      = buffer_queue,
      	.buf_release    = buffer_release,
      };
      

      我们看一看buffer_setup函数

      static int
      buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
      {
          /* 返回一帧图像的字节数 */
          *size = dev->width * dev->height * 2;
          
          /* 设置好缓存个数 */
      	while (*size * *count > vid_limit * 1024 * 1024)
      		(*count)--;
      }
      

      接下来看一看__videobuf_mmap_setup如何申请缓存

      int __videobuf_mmap_setup(struct videobuf_queue *q,
      			unsigned int bcount, unsigned int bsize,
      			enum v4l2_memory memory)
      {
      	for (i = 0; i < bcount; i++) {
           	q->bufs[i] = videobuf_alloc(q); //分配缓存
      		q->bufs[i]->i      = i; //缓存编号
              q->bufs[i]->boff = PAGE_ALIGN(bsize) * i; //缓存偏移量
          }
      }
      

      可以看到调用了videobuf_alloc分配缓存

      struct videobuf_buffer *videobuf_alloc(struct videobuf_queue *q)
      {
          q->int_ops->alloc(q->msize);
      }
      

      videobuf_alloc又通过回调函数来分配缓存,那么这个回调函数是在什么时候设置的呢?

      videobuf_queue_vmalloc_init初始化的时候

      videobuf_queue_vmalloc_init()
      {
      	videobuf_queue_core_init(..., &qops);
      }
      
      void videobuf_queue_core_init(..., struct videobuf_qtype_ops *int_ops)
      {
          q->int_ops   = int_ops;
      }
      

      可以看到int_ops被设置为&qops

      static struct videobuf_qtype_ops qops = {
      	.magic        = MAGIC_QTYPE_OPS,
      
      	.alloc        = __videobuf_alloc, //申请缓存
      	.iolock       = __videobuf_iolock,
      	.mmap_mapper  = __videobuf_mmap_mapper,
      	.vaddr        = videobuf_to_vmalloc, //得到缓存地址
      };
      

      所以videobuf_alloc最终会调用到qops__videobuf_alloc

      最后分配videobuf_buffer结构体,此时并未分配真正的视频缓存区

      static struct videobuf_buffer *__videobuf_alloc(size_t size)
      {
          struct videobuf_buffer *vb;
          vb = kzalloc(size + sizeof(*mem), GFP_KERNEL);
      }
      

      为什么分配内存要搞得如此复杂呢?

      因为并非所有视频设备都使用相同形式得缓存区,事实上至少有三种变化

      • 分散在物理和(内核)虚拟地址空间中的缓冲区
      • 物理上分散但实际上是连续的缓冲物
      • 物理上连续的缓冲区

      videobuf支持这三种形式,提供了三种操作函数集,分别在缓存队列的初始化时设置

      videobuf_queue_vmalloc_init()
      videobuf_queue_dma_contig_init()
      videobuf_queue_sg_init()
      
    • mmap

      映射缓存

      static int vivi_mmap(struct file *file, struct vm_area_struct *vma)
      {
          videobuf_mmap_mapper(&dev->vb_vidq, vma);
      }
      

      videobuf_mmap_mapper时videobuf提供得接口

      int videobuf_mmap_mapper(struct videobuf_queue *q, struct vm_area_struct *vma)
      {
          /* 根据偏移值找到缓存队列中对应的缓存 */
          for (i = 0; i < VIDEO_MAX_FRAME; i++) {
              struct videobuf_buffer *buf = q->bufs[i];
              if(buf->boff == (vma->vm_pgoff << PAGE_SHIFT))
                  CALL(q, mmap_mapper, q, buf, vma); //找到调用函数
          }
      }
      

      CALL是一个宏定义,其定义如下

      #define CALL(q, f, arg...)						\
      	((q->int_ops->f) ? q->int_ops->f(arg) : 0)
      

      又回调到int_opsint_ops在缓存队列初始化的时候被设置为qops

      static struct videobuf_qtype_ops qops = {
      	.magic        = MAGIC_QTYPE_OPS,
      
      	.alloc        = __videobuf_alloc, //申请缓存
      	.iolock       = __videobuf_iolock,
      	.mmap_mapper  = __videobuf_mmap_mapper,
      	.vaddr        = videobuf_to_vmalloc, //得到缓存地址
      };
      

      所以最终会调用到__videobuf_mmap_mapper,此函数真正地分配了视频缓存区,并映射到用户空间

      static int __videobuf_mmap_mapper(struct videobuf_queue *q,
      				  struct videobuf_buffer *buf,
      				  struct vm_area_struct *vma)
      {
          mem->vmalloc = vmalloc_user(pages); //分配视频缓存区
          
          remap_vmalloc_range(vma, mem->vmalloc, 0); //映射
      }
      
    • VIDIOC_QBUF

      缓存入队列

      static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
      {
      	videobuf_dqbuf(&dev->vb_vidq, p,
      				file->f_flags & O_NONBLOCK);
      }
      

      调用videobuf提供的videobuf_dqbuf

      int videobuf_qbuf(struct videobuf_queue *q, struct v4l2_buffer *b)
      {
          /* 回调buf_prepare函数 */
          q->ops->buf_prepare(q, buf, field);
          
          /* 将缓存添加到缓存队列中 */
          list_add_tail(&buf->stream, &q->stream);
      }
      

      buf_prepare在缓存队列初始化的时候设置为

      static struct videobuf_queue_ops vivi_video_qops = {
      	.buf_setup      = buffer_setup,
      	.buf_prepare    = buffer_prepare,
      	.buf_queue      = buffer_queue,
      	.buf_release    = buffer_release,
      };
      
      static int
      buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
      						enum v4l2_field field)
      {
          /* 设置好图像格式 */
      	buf->fmt       = dev->fmt;
      	buf->vb.width  = dev->width;
      	buf->vb.height = dev->height;
          precalculate_bars(dev);
          precalculate_line(dev);
          
          /* 设置缓存状态 */
          buf->vb.state = VIDEOBUF_PREPARED;
      }
      
    • VIDIOC_STREAMON

      打开流

      static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
      {
          videobuf_streamon(&dev->vb_vidq);
          vivi_start_generating(file);
      }
      

      调用videobuf提供的videobuf_streamon设置好缓存的状态,准备生产数据

      调用vivi_start_generating开始生产数据

      下面好好分析vivi_start_generating函数

      static void vivi_start_generating(struct file *file)
      {
          /* 启动一个线程 */
          kthread_run(vivi_thread, dev, dev->v4l2_dev.name);
      }
      

      看一看线程函数,此函数负责生产图像数据

      static int vivi_thread(void *data)
      {
      	for (;;) {
      		vivi_sleep(dev); //只要队列中的buf被请求,那么就填充数据,然后唤醒等待队列
      	}
      }
      
      static void vivi_sleep(struct vivi_dev *dev)
      {   
          vivi_thread_tick(dev); //填充图像数据
          
          schedule_timeout_interruptible(timeout); //调度,等待超时或者被唤醒继续填充数据
      }
      
      static void vivi_thread_tick(struct vivi_dev *dev)
      {
          /* 取出队列的第一个buf */
      	buf = list_entry(dma_q->active.next,
      			 struct vivi_buffer, vb.queue);
          
          /* 填充buf数据,设置buf的状态VIDEOBUF_DONE; */
          vivi_fillbuff(dev, buf);
          
          /* 唤醒正在等待此缓存的进程 */
          wake_up(&buf->vb.done);
      }
      

      总结一下:vidioc_streamon会调用vivi_start_generating启动了一个线程,此线程会填充buf的图像数据,然后唤醒正在等待此buf的线程,最后睡眠等待超时或者被唤醒继续填充下一块buf的图像数据

    • poll

      等待缓存区有缓存准备好

      static unsigned int
      vivi_poll(struct file *file, struct poll_table_struct *wait)
      {
          videobuf_poll_stream(file, q, wait);
      }
      

      调用了videobuf提供的videobuf_poll_stream

      unsigned int videobuf_poll_stream(struct file *file,
      				  struct videobuf_queue *q,
      				  poll_table *wait)
      {
          struct videobuf_buffer *buf = NULL;
          
          /* 取得队列头的buf */
          buf = list_entry(q->stream.next, //得到队列头
                           struct videobuf_buffer, stream);
          
          /* 阻塞等待这个buf */
          poll_wait(file, &buf->done, wait);
      }
      

      看到其中回调用poll_wait(file, &buf->done, wait)去等待这个buf,而在我们上面中讲到在vivi_thread中,只要填充完buf的数据后,就会唤醒等待这个buf的进程

    • VIDIOC_DQBUF

      缓存出队列

      static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
      {
      	videobuf_dqbuf(&dev->vb_vidq, p,
      				file->f_flags & O_NONBLOCK);
      }
      

      会调用videobuf提供的videobuf_dqbuf

      int videobuf_dqbuf(struct videobuf_queue *q,
      		   struct v4l2_buffer *b, int nonblocking)
      {
          /* 拿出队列的第一个buf */
          stream_next_buffer(q, &buf, nonblocking);
          
          /* 判断buf的状态是否是VIDEOBUF_DONE */
          switch (buf->state) {
              ...
              case VIDEOBUF_DONE:    	
          	...
          }
           
          /* 返回信息给用户空间 */
          videobuf_status(q, b, buf, q->type);
          
          /* 从队列中删除,设置buf的状态 */
          list_del(&buf->stream);
          buf->state = VIDEOBUF_IDLE;
      }
      

      应用层得到buf的信息后,可以知道buf的编号,然后从之前mmap的缓存里取出队列的缓存读取图像的数据,处理完之后再次将缓存放入缓存队列中(VIDIOC_QBUF)

    至此,vivi就分析完成了

    展开全文
  •  前面一篇文章中,简单分析了 V4L2 大框架,本文借助内核中的虚拟摄像头驱动 vivi 来分析一个完整的摄像头驱动程序。vivi 相对于后面要分析的 usb 摄像头驱动程序,它没有真正的硬件相关层的操作,也就是说抛开了...
  • 一、vivi虚拟摄像头驱动 基于V4L2(video for linux 2)摄像头驱动程序,我们减去不需要的ioctl_fops的函数,只增加ioctl函数增加的必要的摄像头流查询等函数; 1 #include <linux/module.h> 2 #...
  • 虚拟摄像头驱动程序框架分析
  • 前面简单分析了内核中虚拟摄像头驱动 vivi 的框架与实现,本文参考 vivi 来写一个虚拟摄像头驱动,查询、设置视频格式相对简单,难点在于 vb2_buf 的处理过程。 数据采集流程分析 在我的程序中,大概的数据采集...
  • Microsoft提供了几种可以访问图像数据的API。 >吐温:用于从扫描仪等拍摄单张图像 WIA:这似乎已经退化为一个单一的图像编解码库。 > VfW:一个非常老的(Win16)API,... _InstanceID是为此“虚拟设备”条目创建的GUID。
  • V4L2(三)编写虚拟摄像头驱动

    千次阅读 2016-11-12 15:04:24
    概述前面简单分析了内核中虚拟摄像头驱动 vivi 的框架与实现,本文参考 vivi 来写一个虚拟摄像头驱动,查询、设置视频格式相对简单,难点在于 vb2_buf 的处理过程。数据采集流程分析 在我的程序中,大概的数据采集...
  • 我现在在做一个C#的桌面软件,需要使用虚拟摄像头,查了些关于虚拟摄像头开发的资料,稍微了解了一点,但是还是不是很清楚具体该怎么着手做。 请问下如何对虚拟摄像头采用什么编程语言,什么开发工具比较合适,...
  • 前面一篇文章中,简单分析了 V4L2 大框架,本文借助内核中的虚拟摄像头驱动 vivi 来分析一个完整的摄像头驱动程序。vivi 相对于后面要分析的 usb 摄像头驱动程序,它没有真正的硬件相关层的操作,也就是说抛开了复杂...
  • WinXP下虚拟摄像头驱动程序开发 摄像头驱动程序的主要目的是通过硬件捕捉视频信号。微软公司提供了一套视频驱动的接口,可以满足这个接口的视频驱动程序。第三方厂商开发的软件,如QQ和MSN等软件,都可以通过这个...
  • 转自:http://www.cnblogs.com/tureno/articles/6694463.html ... 本文基于:linux3.5 前面一篇文章中,简单分析了 V4L2 大框架,本文借助内核中的虚拟摄像头驱动 vivi 来分析一个完整的摄像头驱动程序。...
  • 虚拟摄像头驱动vivi的简单实现

    千次阅读 2018-03-15 20:08:46
    一、vivi摄像头驱动基本框架 分配一个video_device结构体变量 设置这个结构体变量 注册这个结构体变量二、vivi摄像头驱动数据的获取过程 请求分配缓冲区 查询缓冲区,并为缓冲区分配空间 将缓冲区放入队列 ...
  • 测试要求:安装虚拟摄像头驱动和安装xawtv测试程序 视频上是把/drivers/media/video目录取出,修改Makefile,然后安装vivi.ko。由于ubuntu12.04上已经有编译 好的模块,模块路径在/lib/modules/3.2.0-23
  • /* 测试驱动程序是否支持某种格式 */ static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,struct v4l2_format *f) { unsigned int maxw, maxh; enum v4l2_field field; if (f->fmt.pix....
  • /* 仿照vivi.c */ #include <linux/module.h> #include <linux/delay.h> #include <linux/errno.h> #include <linux/fs.h> #include <linux/kernel.h> #include <...#...
  •  /* 用于列举、获得、测试、设置摄像头的数据的格式 */  .vidioc_enum_fmt_vid_cap = myvivi_vidioc_enum_fmt_vid_cap,  .vidioc_g_fmt_vid_cap = myvivi_vidioc_g_fmt_vid_cap,  .vidioc_try_fmt_vid_cap = ...
  • /* 测试驱动程序是否支持某种格式 */ static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { unsigned int maxw, maxh; enum v4l2_field field; if (f->fmt.pix...

空空如也

空空如也

1 2 3 4 5 ... 19
收藏数 377
精华内容 150
关键字:

虚拟摄像头驱动