LINUX攝像驅動二:虛擬驅動VIVI測試及徹底分析
LInux Kernel:3.4.2
gcc version: 4.3.2
測試虛擬驅動vivi
準備工作:安裝xawtv
sudo apt-get install xawtv
在這個網站建立新的sources.list
http://repogen.simplylinux.ch/
1. 選擇國家
2.選擇相鄰的ubuntu版本
3. 選擇”Ubuntu Branches”
4. 生成sources.list
5. 把得到內容替換到/etc/apt/sources.list
6. sudo apt-get update
sudo apt-get install xawtv
測試USB攝像頭:
1.讓VMWAER處於前臺,接上USB攝像頭,可以看到生成了/dev/video0
2.執行 xawtv 即可看到影象
測試虛擬攝像頭vivi:
1. 確實ubuntu的核心版本
uname -a
Linux book-desktop 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux
2. 去www.kernel.org下載同版本的核心
解壓後把drivers/media/video目錄取出
修改它的Makefile為:
//指定核心目錄
KERN_DIR = /usr/src/linux-headers-2.6.31-14-generic
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += vivi.o
obj-m += videobuf-core.o
obj-m += videobuf-vmalloc.o
obj-m += v4l2-common.o
3、 make
4、insmod videobuf-core.ko
insmod videobuf-vmalloc.ko
insmod v4l2-common.ko
insmod vivi.ko
5、 ls /dev/video*
6、 xawtv -c /dev/videoX
vivi徹底分析
三、根據虛擬驅動vivi的使用過程徹底分析攝像頭驅動
問1:怎樣畢竟快捷獲得程式所涉及的系統呼叫呢?
答1:用strace命令;
例如:strace -o xawtv.log xawtv 這樣xawtv這個所涉及的open read等等函式的呼叫都會出現在log檔案中;
下面是從”xawtv涉及的vivi驅動的系統呼叫.txt” 所列出
//下面這些可能是沒有用,在原始碼裡面沒有對應的關係
3. ioctl(4, VIDIOC_G_FMT
4. for()
ioctl(4, VIDIOC_ENUM_FMT
5. ioctl(4, VIDIOC_QUERYCAP // 列舉效能
6. ioctl(4, VIDIOC_G_INPUT // 獲得當前使用輸入源
7. ioctl(4, VIDIOC_ENUMINPUT // 列舉輸入源
8. ioctl(4, VIDIOC_QUERYCTRL // 查詢屬性,比如亮度、對比度
9. ioctl(4, VIDIOC_QUERYCAP
10. ioctl(4, VIDIOC_ENUMINPUT
注意1:
1~7都是在v4l2_open(開啟攝像頭裝置)裡呼叫
1. open //第一個所涉及的系統呼叫
2. ioctl(4, VIDIOC_QUERYCAP //第二個呼叫
注意2:
3~7 都是在get_device_capabilities裡呼叫
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 // 查詢屬性(比如說亮度值最小值、最大值、預設值)
注意3:
8~10都是通過v4l2_read_attr來呼叫的
我們的應用程式通過呼叫v4l2_read_attr這個函式,來獲得屬性
8. ioctl(4, VIDIOC_G_STD // 獲得當前使用的標準(或者稱為制式), 不是必需的
9. ioctl(4, VIDIOC_G_INPUT //當前使用哪個通道
10. ioctl(4, VIDIOC_G_CTRL // 獲得當前屬性, 比如亮度是多少,與上面是有區別的
注意4:暫時不知道這兩個函式在哪裡被呼叫
11. ioctl(4, VIDIOC_TRY_FMT // 試試能否支援某種格式
12. ioctl(4, VIDIOC_S_FMT // 設定攝像頭使用某種格式
注意:5:
13~16在v4l2_start_streaming 啟動
13. ioctl(4, VIDIOC_REQBUFS // 請求系統分配緩衝區
14. for()
ioctl(4, VIDIOC_QUERYBUF // 查詢所分配的緩衝區;在for迴圈裡面,對於每一個緩衝區,我們都QUERYBUF得到它的大小、地址等
mmap //然後呼叫mmap來對映它的地址
15. for ()
ioctl(4, VIDIOC_QBUF // 把所有的buffer緩衝區都放入佇列
16. ioctl(4, VIDIOC_STREAMON // 啟動攝像頭
注意6:
17裡都是通過v4l2_write_attr來呼叫的
17. for ()
ioctl(4, VIDIOC_S_CTRL // 設定屬性,比如亮度等等
ioctl(4, VIDIOC_S_INPUT // 設定輸入源
ioctl(4, VIDIOC_S_STD // 設定標準(制式), 不是必需的
注意7:
v4l2_nextframe > v4l2_waiton
18. v4l2_queue_all
v4l2_waiton
for () //這是一個大迴圈,處理很多資料
{
select(5, [4], NULL, NULL, {5, 0}) = 1 (in [4], left {4, 985979}) //select就是我們之前的poll機制,就是去查詢裝置有無資料,有資料的話就把應用程式喚醒讀取資料
ioctl(4, VIDIOC_DQBUF // de-queue, 有資料了,呼叫DQBUF來獲得這些buffer的資訊,把緩衝區從佇列中取出;
// 處理, 之前已經通過mmap獲得了緩衝區的地址, 就可以直接訪問資料
ioctl(4, VIDIOC_QBUF // 處理完資料之後,再呼叫QBUF把緩衝區放入佇列
}
xawtv的幾大函式:
1. v4l2_open
2. v4l2_read_attr/v4l2_write_attr
3. v4l2_start_streaming
4. v4l2_nextframe/v4l2_waiton
Garmen:
//不是必須需要的,用於選擇輸入源,在xwatv裡面就是Video source;
.vidioc_enum_input = vidioc_enum_input, //表明輸入源;假如遮蔽掉這個項,xwatv顯示vivi的時候就會缺少Video source Cam n,顯示為unknown
.vidioc_g_input = vidioc_g_input, //獲得當前輸入源;
.vidioc_s_input = vidioc_s_input, //設定用哪個輸入源;
//不是必須需要的,用於列舉、設定、獲得TV制式
.vidioc_s_std = vidioc_s_std,
.video_dev:
.tvnorms = V4L2_STD_525_60, //用於VIDIOC_ENUMSTD
.current_norm = V4L2_STD_NTSC_M, //用於VIDIOC_G_STD
攝像頭驅動程式必需的11個ioctl:
// 表示它是一個攝像頭裝置,還用了V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;這兩個標誌位;
.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只是分配緩衝區的頭部資訊,真正的快取還沒有分配呢
在我們驅動程式有一條原則,這些資源只有在我們用到的時候才進行分配
- 查詢對映緩衝區:
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); // 在這裡才給緩衝區分配空間
到這裡時候我們已經擁有緩衝區了
把緩衝區放入佇列:
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); // 呼叫驅動程式提供的”入佇列函式”啟動攝像頭
ioctl(4, VIDIOC_STREAMON
videobuf_streamon
q->streaming = 1;用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虛擬用核心執行緒vivi_thread每30MS執行一次,真實攝像頭是硬體產生資料
VIVI 呼叫
vivi_thread_tick
vivi_fillbuff(fh, buf); // 構造資料
wake_up(&buf->vb.done); // 喚醒程序
- 有資料後(即是喚醒之後)從佇列裡取出緩衝區
// 問:有那麼多緩衝區,APP如何知道哪一個緩衝區有資料?
// 答:呼叫VIDIOC_DQBUF
ioctl(4, VIDIOC_DQBUF
vidioc_dqbuf
// 1、在佇列裡獲得有資料的緩衝區
retval = stream_next_buffer(q, &buf, nonblocking);
//2、 把它從佇列中刪掉
list_del(&buf->stream);
// 3、把這個緩衝區的狀態返回給APP
videobuf_status(q, b, buf, q->type);
- 應用程式根據VIDIOC_DQBUF所得到緩衝區狀態,知道是哪一個緩衝區有資料
就去讀對應的地址(該地址來自前面的mmap)
總結如下圖所示!
總結:
怎麼寫攝像頭驅動程式:
v4l2_dev根本不重要,重要的是video_devic!!
1. 分配video_device:video_device_alloc
2. 設定
.fops
.ioctl_ops (裡面需要設定11項)
如果要用核心提供的緩衝區操作函式,還需要構造一個videobuf_queue_ops
3. 註冊: video_register_device