完全分析虛擬視頻驅動vivi(三)

在Ubuntu系統中接上usb攝像頭設備時,系統會自動安裝對應的usb設備驅動程序。
咱們如今要使用本身編譯的vivi驅動,該怎麼辦呢?
  1.先安裝系統自帶的vivi驅動和它所依賴的全部驅動:sudo modprobe vivi ;
  2.卸載原有的vivi驅動 : sudo rmmod vivi ;
  3.裝載本身的驅動 :sudo insmod ./vivi.ko ;
而後 ls /dev/video* ,能夠看到有一個video設備節點 /dev/video0 ,即對應的是vivi虛擬出來的視頻設備。
咱們能夠直接閱讀xawtv源碼,從main函數開始一路分析它調用vivi驅動的過程,可是這個過程會很是漫長,由於它除了調用vivi驅動以外
,還會作許多其餘的準備工做。咱們能夠經過strace 這個命令來跟蹤調用過程。
本文目的:緩存

(經過追蹤應用程序xawtv調用驅動vivi的過程,使之生成對應的TXT文件,在文件中搜索 /dev/video*字段,獲得一系列函數,再打開xawtv源碼,獲得vivi驅動必須的系統調用,進而分析驅動框架)app

1、xawtv所涉及的vivi驅動的系統調用框架

使用方法 :執行 strace -o xawtv.txt xawtv ,生成了調用過程xawtv.txtide

搜索 /dev/video0,獲得以下:函數

open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0x95b8998) = -1 EINVAL (Invalid argument)
close(4)                                = 0
open("/dev/video0", O_RDWR|O_LARGEFILE) = 4
.....
發現打開了兩次,open成功以後獲得file_fd =4,後面有一大堆ioctl,把全部的ioctl列舉出來,便可獲得ioctl的過程:測試

open("/dev/video0", O_RDWR|O_LARGEFILE) = 4 ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0xbff6c704) = 0 ioctl(4, VIDIOC_G_FMT or VT_SENDSIG, 0xbff6c638) = 0 ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0 ioctl(4, 0xc02c564a, 0xbff6c518)        = -1 EINVAL (Invalid argument) ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0 ioctl(4, 0xc02c564a, 0xbff6c518)        = -1 EINVAL (Invalid argument) ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0 ioctl(4, 0xc02c564a, 0xbff6c518)        = -1 EINVAL (Invalid argument) ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0 ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0 ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = 0 ioctl(4, VIDIOC_ENUM_FMT or VT_SETMODE, 0xbff6c5ac) = -1 EINVAL (Invalid argument) ioctl(4, VIDIOC_QUERYCAP or VT_OPENQRY, 0xbff6c544) = 0 ioctl(4, VIDIOC_G_INPUT, 0xbff6c3ec)    = 0 ioctl(4, VIDIOC_ENUMINPUT, 0xbff6c3ec)  = 0
View Code

xawtv涉及的vivi驅動的系統調用:spa

// 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                 // 把緩衝區放入隊列
        }

 

由上可知xawtv的幾大函數:.net

  1. v4l2_open線程

  2. v4l2_read_attr/v4l2_write_attrcode

  3. v4l2_start_streaming

  4. v4l2_nextframe/v4l2_waiton

攝像頭驅動程序必需的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,

 

分析數據的獲取過程:

1. 請求分配緩衝區: ioctl(4, VIDIOC_REQBUFS          // 請求系統分配緩衝區
                        videobuf_reqbufs(隊列, v4l2_requestbuffers) // 隊列在open函數用videobuf_queue_vmalloc_init初始化 // 注意:這個IOCTL只是分配緩衝區的頭部信息,真正的緩存尚未分配呢
               //在驅動程序有一條原則,這些資源只有在咱們用到的時候才進行分配
2. 查詢映射緩衝區: ioctl(4, VIDIOC_QUERYBUF         // 查詢所分配的緩衝區
        videobuf_querybuf        // 得到緩衝區的數據格式、每一行長度、高度、緩衝區使用狀態、在內核空間的偏移地址、緩衝區長度等 
mmap(參數裏有"大小")   // 在這裏才分配緩存
 v4l2_mmap vivi_mmap videobuf_mmap_mapper videobuf-vmalloc.c裏的__videobuf_mmap_mapper mem->vmalloc = vmalloc_user(pages);   // 在這裏纔給緩衝區分配空間

3. 把緩衝區放入隊列: ioctl(4, VIDIOC_QBUF             // 把緩衝區放入隊列 
 videobuf_qbuf q->ops->buf_prepare(q, buf, field);  // 調用驅動程序提供的函數作些預處理
        list_add_tail(&buf->stream, &q->stream);  // 把緩衝區放入隊列的尾部
        q->ops->buf_queue(q, buf);           // 調用驅動程序提供的"入隊列函數"
        

4. 啓動攝像頭 ioctl(4, VIDIOC_STREAMON videobuf_streamon q->streaming = 1; 5. 用select查詢是否有數據 // 驅動程序裏一定有: 產生數據、喚醒進程
 v4l2_poll vdev->fops->poll vivi_poll videobuf_poll_stream // 從隊列的頭部得到緩衝區
                            buf = list_entry(q->stream.next, struct videobuf_buffer, stream); // 若是沒有數據則休眠,在buf->done這裏進行休眠 
                            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) 

 

總結數據獲取過程:(圖片來自:https://blog.csdn.net/ljmiaw/article/details/72801456)


怎麼寫攝像頭驅動程序:
1. 分配video_device:video_device_alloc
2. 設置
  .fops
  .ioctl_ops (裏面須要設置11項)
  若是要用內核提供的緩衝區操做函數,還須要構造一個videobuf_queue_ops
3. 註冊: video_register_device

 

 

 

怎麼寫攝像頭驅動程序:

1. 分配video_device:video_device_alloc

2. 設置 .fops .ioctl_ops (裏面須要設置11項) 若是要用內核提供的緩衝區操做函數,還須要構造一個videobuf_queue_ops

3. 註冊: video_register_device

相關文章
相關標籤/搜索