LINUX攝像驅動二:虛擬驅動VIVI測試及徹底分析

LInux Kernel:3.4.2
gcc version: 4.3.2

測試虛擬驅動vivi
準備工作:安裝xawtvsudo apt-get install xawtv
源碼:xawtv-3.95.tar.gz: http://www.kraxel.org/releases/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
  1. 去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
     //不是必須需要的,用於選擇輸入源,在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只是分配緩衝區的頭部信息,真正的緩存還沒有分配呢
                                     

在我們驅動程序有一條原則,這些資源只有在我們用到的時候才進行分配

  1. 查詢映射緩衝區:
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);   // 在這裏纔給緩衝區分配空間

到這裏時候我們已經擁有緩衝區了

  1. 把緩衝區放入隊列:
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);           // 調用驅動程序提供的"入隊列函數"



  1. 啓動攝像頭
ioctl(4, VIDIOC_STREAMON
    videobuf_streamon
        q->streaming = 1;
  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);      // 喚醒進程
  1. 有數據後(即是喚醒之後)從隊列裏取出緩衝區
    // 問:有那麼多緩衝區,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);
  1. 應用程序根據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

參考文章:http://blog.csdn.net/lizuobin2/article/details/53006927

發佈了30 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章