在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
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