简述基于V4L2驱动框架的UVC摄像头驱动(只用于获取数据,不具备控制功能)

分析的是韦东山第三期视频中的从零编写USB摄像头驱动里的代码

1)入口函数:

注册一个usb_driver结构体:usb_register
里面有什么内容?
根据id_table进行匹配 :表示它能支持哪些设备
当接上能够支持的设备的时候,会调用probe函数


2)在probe函数里注册video_device结构体:
       分配video_device结构体:video_device_alloc


设置这个结构体
   将v4l2_file_operation结构体的内容赋给video_device的fops
使用.open打开对应的设备节点    
使用.ioctl传递相应的参数和命令
使用.mmap把缓存映射到应用空间,让应用可以直接操作这块缓存
使用.poll确定缓存是否有数据
使用.close关闭对应的设备节点


   将v4l2_ioctl_ops结构体的内容赋给video_device的ioctl_ops
v4l2_ioctl_ops包含了我们需要操作的ioctl函数


注册这个结构体到内核:video_register_device


3)在disconnect函数里卸载video_device结构体:
卸载video_device结构体:video_unregister_device

释放为video_device申请的内存:video_device_release


4)ioctl填充:
(1)获取这是一个什么设备:
   myuvc_vidioc_querycap(struct file *file, void  *priv,
   struct v4l2_capability *cap)
表示这是一个摄像头设备
具有的性能是图像捕获和流传输


(2)列举这个摄像头设备所能够支持的格式:
   myuvc_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
struct v4l2_fmtdesc *f)
这里只支持一种格式:"YUV 4:2:2 (YUYV)"


   视频格式包含在drivers/media/video/uvc/uvc_driver.c的uvc_format_desc结构体里面

(3)返回当前所使用的格式:
   myuvc_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
一开始当前所使用的格式是我们初始化时设置在myuvc_format的格式
所以我们直接将我们初始化好的格式赋给v4l2_format结构体即可:memcpy


(4)测试驱动程序是否支持某种格式,如果支持,强制设置为这种格式:
   myuvc_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
判断性能格式类型是否图像捕获类型:V4L2_BUF_TYPE_VIDEO_CAPTURE
判断图像格式是否是YUYV:V4L2_PIX_FMT_YUYV
查看支持的分辨率frame是哪几种,强制设置为我们需要的分辨率frame
   设置的参数:
f->fmt.pix.width 帧宽
f->fmt.pix.height 帧高
f->fmt.pix.bytesperline 帧宽所占据的字节数
f->fmt.pix.sizeimage 一帧图像所占据的字节数


(5)设置这种格式:
   myuvc_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
调用myuvc_vidioc_try_fmt_vid_cap函数测试是否支持这种格式
如果支持,强制设置这个格式的分辨率

把强制设置的这个格式的分辨率赋给myuvc_format结构体:memcpy


(6)申请缓存:应用通过这些缓存从驱动程序中获取想要的数据
   myuvc_vidioc_reqbufs(struct file *file, void *priv,
 struct v4l2_requestbuffers *p)
申请多少块缓存:nbuffers
申请的缓存大小(页对齐),以整页进行分配:PAGE_ALIGN
   至少能够存储一幅图像大小的数据
分配缓存的函数:vmalloc_32
   直接分配一整块缓存
   如果分配失败,减小块数再进行分配
将分配的缓存全部清零
初始化链表:INIT_LIST_HEAD
   &queue->mainqueue
   &queue->irqqueue

设置整块缓存中每一块缓存的参数:
   第几块缓存:index
   偏移地址: offset
   每块缓存的大小(真实大小):length
   性能格式: type
   领域: filed
   是否映射:  memory
   标志:      flags
   状态:      state(刚分配好后的缓存状态为空闲状态:VIDEOBUF_IDLE)
                            初始化等待队列(缓存无数据是休眠):  init_waitqueue_head

   缓存起始地址:  mem
   缓存块数:      count
   缓存大小(页对齐大小):buf_size


(7)查询缓存状态,如地址信息(用于mmap映射):
   myuvc_vidioc_querybuf(struct file *file, void *priv, 
  struct v4l2_buffer *v4l2_buf)
   根据传进来的参数,查找到使用到的缓存,然后复制到应用层
传入的索引值index大于实际具有的缓存数,返回错误
将对应的某个缓存的参数buf结构体拷贝到v4l2_buf结构体:memcpy


设置标志位flags和计算mmap次数:vma_use_count
   用来表示缓存是否已经被mmap
   计算mmap了多少次


设置被拷贝的缓存的状态state:
   VIDEOBUF_DONE  :数据处理完成
   VIDEOBUF_QUEUED:放入队列,正在队列中进行处理


(8)将缓存mmap到应用空间,供应用使用
   myuvc_mmap(struct file *file, struct vm_area_struct *vma)
定义了一个myuvc_buffer的指针*buffer
设置映射的起始地址和结束地址

应用程序调用mmap函数时, 会传入offset参数
根据这个offset在多个缓存中找出指定的缓存


根据虚拟地址找到缓存对应的page构体
   把page和APP传入的虚拟地址挂构
把这块page映射到这块虚拟地址上面

计算引用的次数
   每映射一次就将引用计数加1 


(9)把缓存放入队列,底层的硬件操作函数会把数据放入这个队列的缓存
   myuvc_vidioc_qbuf(struct file *file, void *priv, 
          struct v4l2_buffer *v4l2_buf)
判断应用层传入的v4l2_buf是否有问题:
   性能格式是否是图像捕获
   是否进行了mmap
   索引值是否大于缓存块数
   放入队列前,缓存状态是否是空闲状态


修改状态:
   将对应索引值的缓存状态修改为入队列状态:VIDEOBUF_QUEUED
   将对应索引值的缓存参数状态改为未使用状态:0

放入队列:
   队列1:提供给应用层使用
       当缓存没有数据时,缓存放入mainqueue队列
当缓冲区有数据时,应用层从mainqueue队列中取出
list_add_tail(链表头,链入的节点mainqueue)
  队列头mainqueue需要初始化(open函数里)
   队列2:提供产生数据的函数使用
当采集到数据时,从irqqueue队列取出第1个缓存,存入数据
list_add_tail(链表头,链入的节点irqqueue)
  队列头irqqueue需要初始化(open函数里)


(10)启动传输
   myuvc_vidioc_streamon(struct file *file, void *priv, 
  enum v4l2_buf_type i)
[1]向摄像头设置参数:使用的格式format;使用的分辨率frame
   根据一个结构体uvc_streaming_control设置数据包
   调用usb_control_msg发出数据包


   测试参数:myuvc_try_streaming_params
   应用提供参数结构体,在这个函数里测试可不可用
如果可用,则对这个参数结构体进行补齐
   分配一个data结构体
将这个参数结构体赋给data结构体
   设置完之后,用usb_control_msg发出usb命令


   取出参数:myuvc_get_streaming_params
   分配一个data结构体
分配完成后发起usb传输读取数据
   数据保存在data结构体
然后通过data结构体赋给uvc_streaming_control结构体


   把数据转存到uvc_streaming_control这个结构体里面
   设置参数:myuvc_set_streaming_params
   根据uvc_version知道发送多少数据
分配好data结构体之后
   用上一个函数设置好的uvc_streaming_control来设置data
设置完之后,用usb_control_msg发出usb命令


   设置videostreaming interface所使用的setting
       从myuvc_params确定带宽
       根据setting的endpoint能传输的wMaxPocketSize,找到能够满足该带宽的setting

       实现步骤:
   首先,得到相应的带宽:bandwidth
       轮询所有的setting,找到需要的endpoint
   根据endpoint里面的wMaxPocketSize得到一个值
       如果这个值比带宽bandwidth大,则选定了这个setting
       然后调用usb_set_interface来设置这个setting
   
 
[2]分配URB:
   实时传输端点一次能传输的最大字节数:psize
   一帧数据的最大长度 :size
   传一帧数据需要的次数:npackets(向上取整:DIV_ROUND_UP)


   分配urb_buffer:usb_buffer_alloc(分配5个)
设置一个全局变量保存urb_buffer的数据(在myuvc_queue结构体里设置)
   使用usb_buffer_alloc进行分配
分配后的urb_buffer的物理地址保存在myuvc_queue.urb_dma[i]
  
   分配urb:usb_alloc_urb(分配5个)
使用usb_alloc_urb分配urb
   
   如果上述的分配中任意一次分配出现问题
释放掉所有的urb_buffer:使用usb_buffer_free
   将里面所有的urb_bufer设置为NULL


释放掉所有的urb:使用usb_free_urb
   将里面所有的urb设置为NULL


[3]设置URB:
   对urb结构体里的参数进行设置
urb->dev:指向我们的usb_device结构体
        urb->context 不用管,设置为NULL
        urb->pipe:设置要传输的端点是哪一个
        urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
        urb->transfer_buffer: 就是我们分配的urb_buffer是哪一个 
        urb->transfer_dma: 分配的urb_buffer物理地址是多少
        urb->complete:当驱动程序接收到一个urb包的时候,产生一个中断,
然后进入这个中断处理函数:
    myuvc_video_complete(struct urb *urb)
从irqqueue队列中取出第1个缓冲区,用来存放数据
   判断irqqueue队列是否为空,如果不为空
   从这个队列里取出第一个缓存myuvc_buffer

   源src     :urb->transfer_buffer+偏移地址,urb中的数值来源于data
   目的dest  :我们分配的data结构体
   长度length:urb传输的实际大小


   判断数据是否有效:根据头部信息来判断
       URB数据含义:
   data[0]:头部长度
   data[1]:错误状态

   除去头部以后的数据长度:
       urb传输的实际大小 - 头部src[0]的长度 
 
   maxlen:缓冲区最多还能存多少数据
   nbytes:最多能存的数据和实际要存入的数据比较,取最小值存入
   
   使用memcpy复制数据


   判断一帧数据是否已经全部接收到 
根据头部信息里的第一个收到的数据里面是否有EOF标志和
已经接收了数据buf.byteuse不等于0判断

除去头部,数据长度等于0,表示它接收到的是一个空的帧


缓存数据修改为:VIDEOBUF_DONE(已经接收完数据)

   当接收完一帧数据,从irqqueue中删除这个缓冲区,唤醒等待数据的进程
使用wake_up函数唤醒等待数据的进程 &buf->wait


   再次提交urb:usb_submit_urb




        urb->number_of_packets:要传输的次数
        urb->transfer_buffer_length:总共是多长的数据
        
            设置每次传输的数据保存在哪里
            urb->iso_frame_desc[j].offset:偏移地址
            urb->iso_frame_desc[j].length:每次传输的大小
           
[4]提交URB以接受数据
   usb_submit_urb
如果提交失败
   使用myuvc_uninit_urbs释放urb和urb_buffer


(11)应用调用poll/select来确定缓存是否就绪(有数据)
   myuvc_poll(struct file *file, struct poll_table_struct *wait)
从mainqueuq中取出第1个缓冲区
   判断它的状态, 如果未就绪, 休眠
如果myuvc_queue.mainqueue队列为空,则发生了错误:list_empty就判断

   从list_first_entry函数中取出第一个缓存
它以stream这个节点从myuvc_queue.mainqueue取出缓存myuvc_buffer
   然后休眠:poll_wait


(12)应用确定poll/select有数据后,把缓存从队列中取出来
   myuvc_vidioc_dqbuf(struct file *file, void *priv, 
  struct v4l2_buffer *v4l2_buf)
   应用发现数据就绪后,从mainqueue里取出第1个缓存buffer
将链入节点mainqueue从队列中删除:list_del(&buf->stream )




(13)停止传输:
   myuvc_vidioc_streamoff(struct file *file, void *priv, 
enum v4l2_buf_type t)
杀死所有的URB
   usb_kill_urb
释放掉URB,并把urb_buffer设置NULL
   myuvc_uninit_urbs

设置VideoStreaming Interface为setting 0,让其休眠不工作
   usb_set_interface
发布了23 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/karaskass/article/details/54932110