1. 程式人生 > >簡述基於V4L2驅動框架的UVC攝像頭驅動(只用於獲取資料,不具備控制功能)

簡述基於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