韋東山第3期嵌入式Linux項目-視頻監控-1-v4l2視頻框架分析

韋東山第3期嵌入式Linux項目-視頻監控-1

硬 件 :電腦、 JZ2440 開發板、 UVC 攝像頭(推薦使用) 系 統 : Ubuntu9.10// 即光盤提供的那個虛擬機系統
Uboot : u-boot-2012.04.01 // 即畢業班移植新 uboot 那個
Kernel : linux-3.4.2 // 即畢業班移植新內核那個文件系統:
fs_mini_mdev_new.tar.bz2
① UVC 指 USB Video Class, UVC 攝像頭的簡單判斷標準就是接到 Windows 電腦上後,不用安裝驅動
程序就可以使用
② USB 攝像頭輸出的數據有多種格式,比如原始數據 RGB 或 YUV 格式,壓縮的 MJPEG 格式。使用
網絡傳輸視頻時,應該傳輸壓縮格式的,否則傳輸的數據量會非常大。
S3C2440 的主頻只有 400M,如果使用的攝像頭不支持 MJPEG 輸出,就需要使用軟件(mjpg-streamer)
來壓縮,這極大的耗費 CPU 資源,導致遠程視頻不流暢

1. 概念和整體框架介紹

視頻監控的Linux底層驅動程序屬於字符設備驅動,回顧字符設備驅動:
在這裏插入圖片描述
這種分層的結構除了可以讓我們更加專注於硬件相關的代碼外,還可以讓上層應用程序有一套統一的調用接口。

分析v4l2 框架結構:

上圖中所說的某結構體,指的是針對於某個具體的驅動的具體結構體,對於LCD來說是fb_info結構體,對於視頻來說又是另外一套框架和結構體:
在這裏插入圖片描述
在這裏插入圖片描述

(1)這個uvc_driver 結構體就是uvc_driver.c 向核心層註冊的結構體。一旦在id_table中發現可以支持的設備(USB攝像頭),內核就調用probe函數進行下一步操作:

在這裏插入圖片描述

(2)在uvc_probe函數中就會分配設置某個跟視頻相關的結構體:

在這裏插入圖片描述

(3)首先看到的是v4l2_device_register

在這裏插入圖片描述

uvc_init -->uvc_driver --> uvc_probe
	uvc_probe
		v4l2_device_register  // 不重要
		uvc_register_chains
			uvc_register_terms
				uvc_register_video
					video_device_alloc
					video_register_device  //重要的註冊函數
						__video_register_device 
						//最終調用到這個函數 (該函數位於:v4l-dev.c 這正是v4l2 框架的核心層)

其中,video_register_device 是重要的部分。

video_register_device 可以反推出哪個部分時核心層:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

對於V4L2框架的細節部分,可以參看:linux-3.4.2_jz2440\Documentation\video4linux\v4l2-framework.txt

2. 源碼分析:vivi.c (可以幫助我們快速分析清楚v4l2的框架)

通過分析 vivi.c(Virtual Video driver) 來深入理解視頻驅動框架:
1.分配video_device
2.設置
3.註冊:video_register_device

1)首先分析入口函數:

vivi_init
    vivi_create_instance
        v4l2_device_register   // 不是主要的, 只是用於初始化一些東西,比如自旋鎖、引用計數,這裏改爲v4l2_device_init更爲合適  

在這裏插入圖片描述
在這裏插入圖片描述

        vfd=video_device_alloc:
        struct video_device *vfd;
        *vfd = vivi_template;

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

        // 設置
          1. vfd:
            .fops           = &vivi_fops,
            .ioctl_ops 	= &vivi_ioctl_ops,
            .release	= video_device_release,
          2.
            vfd->v4l2_dev = &dev->v4l2_dev;       
			// v4l2_device_register(NULL, &dev->v4l2_dev); 
			//該函數中沒有做什麼實質性的事,只是用於初始化一些東西,比如自旋鎖、引用計數,這裏改爲v4l2_device_init更爲合適
			//因此這裏的 struct v4l2_device v4l2_dev 不是最主要的

重要:3. 設置"ctrl屬性"(用於APP的ioctl):
            	v4l2_ctrl_handler_init(hdl, 11);
            	dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
            	dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
            	dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_CONTRAST, 0, 255, 1, 16);               
            			``````

在這裏插入圖片描述
在這裏插入圖片描述

       video_register_device(video_device, type:VFL_TYPE_GRABBER, nr)
            __video_register_device
                vdev->cdev = cdev_alloc(); 
                vdev->cdev->ops = &v4l2_fops;
                cdev_add()
                
                video_device[vdev->minor] = vdev;  //以次設備號爲下標,將vdev存放到數組中

        		if (vdev->ctrl_handler == NULL)        //設置ctrl_handler 結構體
        			vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;

2) 分析驅動程序的讀寫過程:

分析vivi.c的open,read,write,ioctl過程
在這裏插入圖片描述

1. open
app:     open("/dev/video0",....)
---------------------------------------------------
drv:     v4l2_fops.v4l2_open   //vdev->cdev->ops = &v4l2_fops 中的open函數
            vdev = video_devdata(filp);  // 根據次設備號從數組中得到video_device  之後就可以使用video_device結構體所提供的各種函數了
                                         // video_register_device 函數中會將video設備放入數組中
            ret = vdev->fops->open(filp);
                        vivi_fops->open = v4l2_fh_open
                            

2. read
app:    read ....
---------------------------------------------------
drv:    v4l2_fops.v4l2_read  //vdev->cdev->ops = &v4l2_fops中的read函數
            struct video_device *vdev = video_devdata(filp);
            ret = vdev->fops->read(filp, buf, sz, off);      // vivi_fops -> v4l2_fh_open

3. ioctl //較爲複雜
app:   ioctl
----------------------------------------------------
drv:   v4l2_fops.unlocked_ioctl =  v4l2_ioctl  //vdev->cdev->ops = &v4l2_fops中的ioctl函數
         實際上最終調用到:v4l2_ioctl
                struct video_device *vdev = video_devdata(filp);
                ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
                            vivi.c:video_ioctl2
                                          video_usercopy(file, cmd, arg, __video_do_ioctl); // 從用戶空間拷貝命令參數,然後調用__video_do_ioctl
                                          __video_do_ioctl :
                                          struct video_device *vfd = video_devdata(file); //同理都是先根據次設備號得到video_device結構體
                                          根據APP傳入的cmd來獲得、設置"某些屬性"

總結:
在這裏插入圖片描述
在這裏插入圖片描述

3) v4l2_ctrl_handler的使用過程:

    __video_do_ioctl
        struct video_device *vfd = video_devdata(file);

    	case VIDIOC_QUERYCTRL:
    	{
    		struct v4l2_queryctrl *p = arg;
    
    		if (vfh && vfh->ctrl_handler)
    			ret = v4l2_queryctrl(vfh->ctrl_handler, p);
    		else if (vfd->ctrl_handler)  // 在哪設置?在video_register_device
    			ret = v4l2_queryctrl(vfd->ctrl_handler, p);
    			            // 根據ID在ctrl_handler裏找到v4l2_ctrl,返回它的值

在這裏插入圖片描述
之前在vivi.c中創建的Ctrl參數都有一個ID值:
在這裏插入圖片描述

3. 虛擬驅動vivi測試:

1) 測試USB攝像頭

在這裏插入圖片描述
準備工作:安裝xawtv
sudo 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

2)測試虛擬攝像頭

① 確實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

② 去www.kernel.org下載同版本的內核
解壓後把drivers/media/video目錄取出後放到服務器上面去(Ubuntu9.10)

修改它的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

③ make (只加入obj-m += vivi.o時報錯)

在這裏插入圖片描述
解決方法:在內核中搜索這些函數所在的c文件並將這些文件編譯成內核模塊:

④ insmod videobuf-core.ko
insmod videobuf-vmalloc.ko
insmod v4l2-common.ko
insmod vivi.ko
⑤ ls /dev/video*
⑥ xawtv -c /dev/videoX

4. 根據虛擬驅動vivi徹底分析USB攝像頭驅動:

在事先沒有連接USB攝像頭時,裝載驅動程序會報錯,是因爲在連接了USB攝像頭後,Linux會自動安裝一些驅動程序。

解決方法:
在這裏插入圖片描述
下面分析xawtv 源碼:
直接分析源碼很麻煩,可以用strace工具來分析xawtv用到了哪些系統調用(open、read、write),就知道xawtv應用程序做了那些事:
在這裏插入圖片描述

//這部分代碼是應用程序中讀出來的,但是跟源碼沒有對應關係,說明應用程序跟源碼還是有差別的
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_ENUMIN PUT   // 列舉輸入源
8. ioctl(4, VIDIOC_QUERYCTRL   // 查詢屬性,比如亮度、對比度
9. ioctl(4, VIDIOC_QUERYCAP
10. ioctl(4, VIDIOC_ENUMINPUT

xawtv源碼中對應的ioctl操作:

// 1~7都是在v4l2_open裏調用
1. open
2. ioctl(4, VIDIOC_QUERYCAP   //open之後就開始查詢性能,必不可少


// 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~12在v4l2_overlay中調用,暫時沒有用到該功能
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         //調用mmap來映射各個緩衝區的地址
                           //之後將所有的buf都放入隊列中:v4l2_queue_all 
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})   //查詢是否有數據
            //如果一旦驅動程序有了數據,它就會將應用程序喚醒,喚醒之後應用程序調用DQBUF把數據取出獲取buf的信息 

            ioctl(4, VIDIOC_DQBUF                // de-queue, 一旦有數據就把緩衝區從隊列中取出

            // 處理, 之以已經通過mmap獲得了緩衝區的地址, 就可以直接訪問數據 
       
            ioctl(4, VIDIOC_QBUF                 // 把緩衝區放入隊列
        }

在這裏插入圖片描述
總結:
(1)xawtv的幾大函數:

  1. v4l2_open
    打開攝像頭設備,獲取其性能參數

  2. v4l2_read_attr/v4l2_write_attr

  3. v4l2_start_streaming
    其中會請求buf並mmap buf

  4. v4l2_nextframe --> v4l2_waiton
    v4l2_waiton 函數來等待查詢攝像頭數據。

接下來要做的是將衆多的ioctl進行精簡,留下最少的部分完成攝像頭應用程序的基本功能:修改vivi.c
在這裏插入圖片描述
應用程序 --> ioctl --> video_ioctl2 (根據ioctl不同的CMD)–> v4l2_ioctl_ops 中的對應函數

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
經過分析後選出必不可少的ioctl函數:

攝像頭驅動程序必需的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只是分配緩衝區的頭部信息,真正的緩存還沒有分配呢
  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);
                            
                            // 如果沒有數據則休眠                			
                			poll_wait(file, &buf->done, wait);

    誰來產生數據、誰來喚醒它?
    內核線程vivi_thread每30MS執行一次,它調用
    vivi_thread_tick  //產生數據
        vivi_fillbuff(fh, buf);  // 構造數據 
        wake_up(&buf->vb.done);  // 喚醒進程

注意:真實的攝像頭是硬件攝像頭產生數據,而vivi虛擬攝像頭是由一個內核線程來產生數據:

  1. 有數據後從隊列裏取出緩衝區
// 有那麼多緩衝區,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);

  1. 應用程序根據VIDIOC_DQBUF所得到緩衝區狀態,知道是哪一個緩衝區有數據
    就去讀對應的地址(該地址來自前面的mmap)

總結:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
怎麼寫攝像頭驅動程序:

  1. 分配video_device:video_device_alloc
  2. 設置
    .fops
    .ioctl_ops (裏面需要設置11項)
    如果要用內核提供的緩衝區操作函數,還需要構造一個videobuf_queue_ops
  3. 註冊: video_register_device

5. USB攝像頭驅動框架:

在這裏插入圖片描述
UVC: USB Video Class
UVC驅動:drivers\media\video\uvc\

在這裏插入圖片描述
id_table 表示能夠支持的所有USB設備,probe函數是當接入所能支持的設備後調用的函數,在probe函數中就會像上述描述的那樣進行有關視頻的設置。

uvc_driver.c分析:

1. usb_register(&uvc_driver.driver);
2. uvc_probe
        uvc_register_video
            vdev = video_device_alloc();
            vdev->fops = &uvc_fops;
            video_register_device

在www.usb.org下載 uvc specification,
UVC 1.5 Class specification.pdf : 有詳細描述
USB_Video_Example 1.5.pdf : 有示例

通過VideoControl Interface來控制(比如亮度、白平衡等參數的控制)
通過VideoStreaming Interface來讀視頻數據,

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
VC裏含有多個Unit/Terminal等功能模塊,可以通過訪問這些模塊進行控制,比如調亮度

分析UVC驅動的調用過程:

const struct v4l2_file_operations uvc_fops = {
	.owner		= THIS_MODULE,
	.open		= uvc_v4l2_open,
	.release	= uvc_v4l2_release,
	.ioctl		= uvc_v4l2_ioctl,
	.read		= uvc_v4l2_read,
	.mmap		= uvc_v4l2_mmap,
	.poll		= uvc_v4l2_poll,
};
  1. open: 當應用程序調用open函數的時候就會調用到uvc_fops 結構體中的uvc_v4l2_open函數
    uvc_v4l2_open //其中基本上是一些狀態的設置

之後一個一個來跟蹤ioctl函數的操作過程:當應用程序調用到ioctl函數時就會調用到uvc_fops 結構體中的uvc_v4l2_ioctl函數:
(整個過程跟xawtv打開vivi虛擬攝像頭的原理類似)

在這裏插入圖片描述
uvc_v4l2_ioctl函數中的video_usercopy函數就是將應用程序傳來的cmd參數拷貝至內核態然後調用uvc_v4l2_do_ioctl函數。
uvc_v4l2_do_ioctl函數中有一系列的ioctl的調用。

2. VIDIOC_QUERYCAP   // video->streaming->type 應該是在設備被枚舉時分析描述符時設置的
		if (video->streaming->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
			cap->capabilities = V4L2_CAP_VIDEO_CAPTURE
					  | V4L2_CAP_STREAMING;
		else
			cap->capabilities = V4L2_CAP_VIDEO_OUTPUT
					  | V4L2_CAP_STREAMING;
3. VIDIOC_ENUM_FMT // format數組應是在設備被枚舉時設置的
        format = &video->streaming->format[fmt->index];
        
4. VIDIOC_G_FMT
        uvc_v4l2_get_format  // USB攝像頭支持多種格式fromat, 每種格式下有多種frame(比如分辨率)
            	struct uvc_format *format = video->streaming->cur_format;
            	struct uvc_frame *frame = video->streaming->cur_frame;
            	
5. VIDIOC_TRY_FMT
        uvc_v4l2_try_format
            /* Check if the hardware supports the requested format. */

        	/* Find the closest image size. The distance between image sizes is
        	 * the size in pixels of the non-overlapping regions between the
        	 * requested size and the frame-specified size.
        	 */
        	 
6. VIDIOC_S_FMT  // 只是把參數保存起來,還沒有發給USB攝像頭
        uvc_v4l2_set_format
            uvc_v4l2_try_format
        	video->streaming->cur_format = format;
        	video->streaming->cur_frame = frame;
        	
7. VIDIOC_REQBUFS  //請求緩衝區
        uvc_alloc_buffers
           	for (; nbuffers > 0; --nbuffers) {
        		mem = vmalloc_32(nbuffers * bufsize); 
        		if (mem != NULL)
        			break;
        	}
        	
8. VIDIOC_QUERYBUF  //查詢buf參數
        uvc_query_buffer
            __uvc_query_buffer
                memcpy(v4l2_buf, &buf->buf, sizeof *v4l2_buf);  // 複製參數
                
9. mmap
        uvc_v4l2_mmap
            
10. VIDIOC_QBUF  //數據入隊列
        uvc_queue_buffer
        	list_add_tail(&buf->stream, &queue->mainqueue);
        	list_add_tail(&buf->queue, &queue->irqqueue);

11. VIDIOC_STREAMON
        uvc_video_enable(video, 1)  // 把所設置的參數發給硬件,然後啓動攝像頭
        
            /* Commit the streaming parameters. */  把參數提交給硬件設備
            uvc_commit_video
                uvc_set_video_ctrl  /* 設置格式fromat, frame */
                    	ret = __uvc_query_ctrl(video->dev /* 哪一個USB設備 */, SET_CUR, 0,
                    		video->streaming->intfnum  /* 哪一個接口: VS 接口*/,
                    		probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,
                    		uvc_timeout_param);
                    
            /* 啓動:Initialize isochronous/bulk URBs and allocate transfer buffers. */
            uvc_init_video(video, GFP_KERNEL);
                    uvc_init_video_isoc / uvc_init_video_bulk
                        urb->complete = uvc_video_complete; (收到數據後此函數被調用,它又調用video->decode(urb, video, buf); ==> uvc_video_decode_isoc/uvc_video_encode_bulk => uvc_queue_next_buffer => wake_up(&buf->wait);)
                        
                    usb_submit_urb 
// 當應用程序將一個buf放入隊列之後,調用streamon 來啓動數據傳輸,在streamon 中做了一些初始化。
之後當應用程序調用poll函數來查詢是否數據已經就緒,這時就會有poll_wait 在這裏休眠等待數據,
當USB驅動程序獲得了數據之後,每個USB請求塊(urb)完成之後,其uvc_video_complete 函數被調用,
這個complete函數最終就會調用到wake_up 函數將應用程序喚醒。
                   	
12. poll     //poll函數返回以後,數據就可以使用了,接下來就是從隊列中取出數據
        uvc_v4l2_poll            
            uvc_queue_poll
                poll_wait(file, &buf->wait, wait);  // 休眠等待有數據

13. VIDIOC_DQBUF
        uvc_dequeue_buffer
        	list_del(&buf->stream);

14. VIDIOC_STREAMOFF            
        uvc_video_enable(video, 0);
    		usb_kill_urb(urb);
    		usb_free_urb(urb);

分析設置亮度過程:
ioctl: VIDIOC_S_CTRL
uvc_ctrl_set
uvc_ctrl_commit
__uvc_ctrl_commit(video, 0);
uvc_ctrl_commit_entity(video->dev, entity, rollback);
ret = uvc_query_ctrl(dev /* 哪一個USB設備 /, SET_CUR, ctrl->entity->id / 哪一個unit/terminal /,
dev->intfnum /
哪一個接口: VC interface */, ctrl->info->selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info->size);

總結:

  1. UVC設備有2個interface: VideoControl Interface, VideoStreaming Interface

  2. VideoControl Interface用於控制,比如設置亮度。它內部有多個Unit/Terminal(在程序裏Unit/Terminal都稱爲entity)
    可以通過類似的函數來訪問:
    ret = uvc_query_ctrl(dev /* 哪一個USB設備 /, SET_CUR, ctrl->entity->id / 哪一個unit/terminal /,
    dev->intfnum /
    哪一個接口: VC interface */, ctrl->info->selector,
    uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
    ctrl->info->size);

  3. VideoStreaming Interface用於獲得視頻數據,也可以用來選擇fromat/frame(VS可能有多種format, 一個format支持多種frame, frame用來表示分辨率等信息)
    可以通過類似的函數來訪問:
    ret = __uvc_query_ctrl(video->dev /* 哪一個USB設備 /, SET_CUR, 0,
    video->streaming->intfnum /
    哪一個接口: VS */,
    probe ? VS_PROBE_CONTROL : VS_COMMIT_CONTROL, data, size,
    uvc_timeout_param);

  4. 我們在設置FORMAT時只是簡單的使用video->streaming->format[fmt->index]等數據,
    這些數據哪來的?
    應是設備被枚舉時設置的,也就是分析它的描述符時設置的。

  5. UVC驅動的重點在於:
    描述符的分析
    屬性的控制: 通過VideoControl Interface來設置
    格式的選擇:通過VideoStreaming Interface來設置
    數據的獲得:通過VideoStreaming Interface的URB來獲得

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章