myvivi 基于韦东山三期视频学习 V4L2 框架笔记

前言

简单回顾下学习过程的文档,有张好图,但他弄不出来。。。

总结

在这里插入图片描述

myvivi 从应用到驱动

########################################################
# vivi 应用层程序调用流程:
########################################################  
    //  1. 打开设备节点	/dev/videoX
    //
    //  2. 查询摄像头属性
    //      VIDIOC_ENUMINPUT: 列举输入源
    //      VIDIOC_ENUMSTD:   列举制式标准
    //      VIDIOC_ENUM_EMT:  列举格式
    //      VIDIOC_QUERYCTRL: 查询属性(比如说亮度值最小值、最大值等)
    //
    //  3. 缓冲区分配与操作
    //          VIDIOC_REWBUFS :请求系统分配缓冲区
    //              这里只分配缓冲区头部信息
    //                      v4l2_buff 结构体
    //                      用于管理下面分配的真正数据缓冲区
    //              videobuf_rebufs()
    //          VIDIOC_QUERYBUF
    //              查询所分配的缓冲区
    //              获得缓冲区的数据格式、大小、每一行长度、高度  
    //          mmap: 调用驱动的mmap函数分配内存
    //              v4l2_mmap                       |
    //              vivi_mmap                       |
    //              videobuf_mmap_mapper            | 调用流程  
    //              __videobuf_mmap_mapper          |
    //                  videobuf-vmalloc.c          |
    //              vmalloc_user(pages)             V 
    //              
    //          VIDIOC_QBUF:查询缓冲区,把无数据缓冲区放入【本地队列】
    //              videobuf_qbuf                               |
    //                  buf_prepare()                           |
    //                      调用驱动程序提供的函数做些预处理    |
    //                  list_add_tail()                         | 调用流程 
    //                      把缓冲区放入队列的尾部              |
    //                  buf_queue()                             |
    //                      调用驱动程序提供的"入队列函数"      V 
	//
    //  4. 打开摄像头
	//      VIDIOC_STREAMON
	//  	    videobuf_streamon()
	//  		    q->streaming = 1
    //
    //  5. 等待数据到来
    //      select()
    //          v4l2_poll                                       |
    //          vdev->fops->poll                                |
    //              驱动中的 poll 函数                          |
    //              即 vivi_poll()                              |
    //          videobuf_poll_stream                            | 调用流程
    //              list_entry()                                |
    //                  从队列的头部获得缓冲区                  |
    //              poll_wait()【在下面的定时器中被唤醒】       |
    //                  如果没有数据则睡眠                      V 
    //
    //  6. 有数据来了,判断是哪个缓冲区有数据
	//      VIDIOC_DQBUF
	//      	vidioc_dqbuf()                                  |
	//      		stream_next_buffer()                        |
	//      			在队列里获得有数据的缓冲区              |
	//      		list_del()                                  | 调用流程 
	//      			把它从队列中删掉                        |
	//      		videobuf_status()                           |
	//      			把这个缓冲区的状态返回给APP             V 
    // 
    //  7. 从对应缓冲区取出数据
    //      应用程序根据 VIDIOC_DQBUF 所得到缓冲区状态
    //      知道是哪一个缓冲区有数据
    //      读对应的地址(该地址来自前面的 mmap )
    
    
		
########################################################
# vivi 驱动编写及流程分析:
########################################################
# 编写总结: 
    // 1. 分配 video_device = video_device_alloc()
    // 2. 设置: 
    //      2.1 video_device.fops: 正常通用的 open/read/write/close 等
    //          .open: 初始化队列,注册 videobuf_queue_ops 
    //                  videobuf_queue_vmalloc_init()
    //                  //      videobuf_queue_ops:  缓冲区操作函数 
    //                  //          .buf_setup      = myvivi_buffer_setup,      APP 调用 ioctl VIDIOC_REQBUFS 时会导致此函数被调用 计算大小以免浪费
    //                  //
    //                  //          .buf_prepare    = myvivi_buffer_prepare,    APP 调用 ioctl VIDIOC_QBUF 时导致此函数被调用
    //                  //                                                        0. 设置 videobuf
    //                  //                                                        1. 做些准备工作
    //                  //                                                        2. 调用 videobuf_iolock 为类型为 V4L2_MEMORY_USERPTR 的 videobuf 分配内存
    //                  //                                                        3. 设置状态
    //                  //
    //                  //          .buf_queue      = myvivi_buffer_queue,      APP调用 ioctl VIDIOC_QBUF 时此函数被调用
    //                  //                                                        1. 先调用 buf_prepare 进行一些准备工作
    //                  //                                                        2. 把 buf 放入 stream 队列
    //                  //                                                        3. 调用 buf_queue(起通知、记录作用)
    //                  //                                                                      把检查无数据的 videobuf 放入本地一个队列尾部
    //                  //                                                                      数据来了会从本地队列取出 videobuf 填充数据
    //                  //
    //                  //          .buf_release    = myvivi_buffer_release,    APP 不再使用队列时, 用它来释放内存
    //
    //                  
    //          .release: 释放相关资源 
    //                      videobuf_stop()
    //                      videobuf_mmap_free()
    //          .mmap: videobuf_mmap_mapper()
    //
    //          .ioctl = video_ioctl2 注:他会调用到下面的 ioctl_ops 的
    //  
    //          .poll: videobuf_poll_stream()
    //
    //      2.2 video_device.ioctl_ops: v4l2 特定的调用实现 
    //          .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    = videobuf_reqbufs()  | 缓冲区操作函数
    //          .vidioc_querybuf            = myvivi_vidioc_querybuf   = videobuf_querybuf() |
    //          .vidioc_qbuf                = myvivi_vidioc_qbuf       = videobuf_qbuf()     |
    //          .vidioc_dqbuf               = myvivi_vidioc_dqbuf      = videobuf_dqbuf()    |
    //
    //          .vidioc_streamon            = myvivi_vidioc_streamon   = videobuf_streamon()  | 摄像头启动/停止
    //          .vidioc_streamoff           = myvivi_vidioc_streamoff  = videobuf_streamoff() |
    //       
    //      2.3 创建一个本地队列保存 buffer:【这里保存的是无数据的 buff 】 
    //                      入队列:是在 VIDIOC_QBUF 中入队列的
    //                      出队列:是在定时器有数据时出的队列,然后以自身唤醒等待进程处理
    // 3. 注册:
    //      video_register_device()
                                  
# 实现过程:
    module_init(myvivi_init);
    myvivi_init(void)
        /* 1. 分配一个video_device结构体 */
        //  static struct video_device *myvivi_device;
        myvivi_device = video_device_alloc();

        /* 2. 设置 */
        /* 2.1 空函数 */
        myvivi_device->release = myvivi_release;
        /* 2.2 操作函数*/
        myvivi_device->fops    = &myvivi_fops;
            // 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,
            // };
            
        /* 2.3 ioctl 操作函数 */
        myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
            //  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,   = videobuf_reqbufs()
            //          .vidioc_querybuf      = myvivi_vidioc_querybuf,  = videobuf_querybuf()
            //          .vidioc_qbuf          = myvivi_vidioc_qbuf,      = videobuf_qbuf()
            //          .vidioc_dqbuf         = myvivi_vidioc_dqbuf,     = videobuf_dqbuf()
            //          
            //          // 摄像头启动/停止
            //          .vidioc_streamon      = myvivi_vidioc_streamon,  = videobuf_streamon()
            //          .vidioc_streamoff     = myvivi_vidioc_streamoff, = videobuf_streamoff()
            //  };

        /* 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);
        

    # 定时器模拟的数据上报流程:
        // 流程总结:
        //      1. list_entry(): 从本地队列取出第一个 videobuf 
        //      2. myvivi_fillbuff(): 填充数据 
        //      3. list_del(): 把 videobuf 从本地队列中删除 
        //      4. wake_up(): 唤醒进程:唤醒 videobuf->done 上的进程 
        
        // 代码总结:
        myvivi_open()
            /* 队列操作 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);
            /////////////////////////////////////
            // 定时器到期后
            myvivi_timer_function()
                /* 1. 构造数据: 从队列头部取出第 1 个 videobuf, 填充数据
                */
                /* 1.1 从本地队列取出第 1 个 videobuf */    
                vb = list_entry(myvivi_vb_local_queue.next, struct videobuf_buffer, queue);
            
                /* 1.2 填充数据 */
                vbuf = videobuf_to_vmalloc(vb);
                memset(vbuf, 0xff, vb->size);
                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);
            
                /* 3. 修改timer的超时时间 : 30fps, 1秒里有30帧数据
                 *    每1/30 秒产生一帧数据
                 */
                mod_timer(&myvivi_timer, jiffies + HZ/30);

猜你喜欢

转载自blog.csdn.net/wangjun7121/article/details/88137918