羅技攝像頭C270與嵌入式LINUX

http://www.eefocus.com/marianna/blog/13-06/294567_a5fc8.html

現在,假如你的手上有一隻攝像頭,它是羅技高清網絡攝像頭webcam-C270,還有一塊cortexA8開發板,這塊開發板來自FriendlyARM,已經預裝了linux系統,版本號是最新提供的linux-3.0.8,圖形界面是Qtopia-2.2.0,交叉編譯器是arm-linux-gcc-4.5.1。主機是Fedora9

攝像頭和開發板,這兩樣東西安安靜靜的躺在了你的手裏,準備就緒,狀態良好。而你的任務,就是要讓攝像頭正常的工作在開發板上,並且完成一些簡單的任務,比如說將圖像顯示在Qtopia的界面上,並判斷當前的圖像中有沒有阿拉伯數字。

(雖然說C270並不支持linux系統,可是支持linux系統的攝像頭又有幾隻呢?即使C270不支持linux系統,不代表linux系統不支持C270^_^

插上攝像頭試試

在開發板的終端窗口,輸入“cat /proc/kmsg &”,來顯示內核打印信息。

FriendlyARM最新提供的光盤,裏面是包含了C270的驅動程序和應用程序。將C270連接在開發板的USB口上面,內核會打印出下面的信息:

<6>[960.933564] usb 1-1.3: new high speed USB device number 7 using s5p-ehci

<6>[961.262234] uvcvideo: Found UVC 1.00 device <unnamed> (046d:0825)

<6>[961.362302] input: UVC Camera (046d:0825) as /devices/platform/s5p-ehci/usb1/1-1/1-1.3/1-1.3:1.0/input/input5

內核信息打印有七個日誌級別,數字越低,級別越高。低於當前信息的打印級別的信息就不會被顯示出來。<6>[961.262234]的意思是,打印出的信息級別是KERN_INFO(提示信息),這個信息在系統的961.262234這個tick時間被執行。

1-1.3:1.0的意思是,攝像頭使用的根集線器編號爲1,集線器端口號爲1,集線器(攝像頭使用)端口號爲3,配置爲1,接口爲0input: UVC Camera說明linux內核認出了這個設備,而且知道這個設備是UVC標準攝像頭。

如果攝像頭被正常的識別和驅動,打開名爲“USB 攝像頭”的應用程序(FriendlyARM提供),其界面上就會顯示出圖像。

攝像頭如何被識別

如果攝像頭沒有找到正確的驅動,開發者如何確認這一點呢?如果沒有驅動或者安裝了錯誤的驅動,開發者如何安裝正確的驅動呢?

驅動一個LED燈,也就是cortexA8的一個IO口,驅動可以編譯進內核,也可以通過模塊的方式加載。然後在/dev下形成設備文件。用戶程序通過讀寫設備文件,來控制這個IO口。這樣的流程,並不適合USB設備。“USB設備是一個非常複雜的東西,官方USB文檔中有詳細的描述。幸運的是,Linux內核提供了一個稱爲USB核心(USB Core)的子系統來處理大部分的複雜性。”——摘自《LINUX設備驅動程序》。

USB設備通過USB Core和驅動交換數據,用戶程序讀寫驅動的數據,因此USB Core作爲中間層,需要把設備和驅動,正確的對應起來。每個USB設備插入主機的時候,主機都會請求設備的device descriptor。設備描述符device descriptor提供了USB協議版本、廠商ID、產品ID等信息。

廠商IDidVendor)和產品IDidProduct),是USB Core把設備和驅動聯繫起來的關鍵。USB設備具備自身的idVendoridProduct;驅動程序也會定義自己的idVendoridProduct。如果USB Core從設備請求到到的ID,正好符合驅動程序的定義,那麼USB Core就知道這個ID的設備使用的是這個ID的驅動程序。

idProductidVendor16位的數值。在內核打印信息中出現的046d0825這兩個數,就是idVendoridProduct。在/sys/bus/usb/drivers/usb/1-1.3/這個目錄下,查看idProductidVendor這兩個文件,也會發現它們的值是046d0825046d是羅技公司的idVendor

什麼是UVC設備

羅技C270是一個標準的UVC設備,需要UVC driver

Its important for me for a webcam to be UVC compatible where UVC is the USB Video Class, and defines a standard/specification for devices capable of streaming video. For example, being UVC compatible was a logo requirement for Windows Vista which helped make this class of device popular, and fortunately there is good support under GNU/Linux。”

http://forums.opensuse.org/blogs/oldcpu/logitech-c270-webcam-opensuse-110博主Oldcpu是歐洲的一名航天器操作工程師,同時也是一名linux愛好者。Oldcpu爲這個C270準備的系統是GNU/Linux (openSUSE),而驅動C270的關鍵是“UVC compatible”。

The USB Device Class Definition for Video Devices, or USB Video Class, defines video streaming functionality on the Universal Serial Bus. Much like nearly all mass storage devices (USB flash disks, external SATA disk enclosures, ...) can be managed by a single driver because they conform to the USB Mass Storage specification, UVC compliant peripherals only need a generic driver.

The UVC specification covers webcams, digital camcorders, analog video converters, analog and digital television tuners, and still-image cameras that support video streaming for both video input and output.

這段文字來自http://www.ideasonboard.org/uvc/。在網頁下方,列出了UVC支持的webcam型號,其中以046d作爲idVendor的,就是羅技攝像頭。

046d:0825(logitech HD Webcam C270)出現在這張表中。

UVC是在linux-2.6.38版本時加入內核的,那麼更早的版本沒有集成UVC。好在FriendlyARM提供內核版本是linux-3.0.8,裏面集成了UVC驅動。在內核源代碼的“Documentation/video4linux/uvcvideo.txt”中,是有關於UVC的說明。

UVC驅動程序的位置 

根據Documentation/video4linux/uvcvideo.txt給出的信息,UVC設備不需要編寫單獨的驅動,它在用戶空間提供了類似驅動的接口。如果用戶需要實現ioctl功能,就需要在用戶空間調用這個接口。

linux-3.0.8源代碼的driver目錄下,執行find . -name *uvc* -type f,發現,uvc文件集中在./usb/gadget/./media/video/uvc/這兩個目錄。USB gadget雖然有UVC部分,但它並不是UVC driver。真正的UVC driver,是./media/video/uvc/

./media/video/uvc/目錄下有12個文件:

uvc*.c     8個) 

Kconfig

Makefile

modules.builtin

modules.order

Makefile用來指定生成目標文件的規則。Kconfig用在定義生成的目標文件在make menuconfig時選項名稱。.c文件是UVC的實現。

 

UVC驅動程序的Makefile 

uvcvideo-objs  := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o  uvc_ctrl.o uvc_status.o uvc_isight.o

ifeq ($(CONFIG_MEDIA_CONTROLLER),y)

        uvcvideo-objs  += uvc_entity.o

endif

obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o

在這個Makefile中,每個.c文件生成一個.o文件。除了uvc_entity.o外的共7.o文件,鏈接成一個uvcvideo-objs

如果條件“($(CONFIG_MEDIA_CONTROLLER),y)”成立,則把uvc_entity.o也鏈接進去,生成一個uvcvideo-objs。生成的uvcvideo-objs,就是uvcvideo.o

UVC驅動程序的Kconfig

config USB_VIDEO_CLASS

        tristate "USB Video Class (UVC)"

        ---help---

          Support for the USB Video Class (UVC).  Currently only video

          input devices, such as webcams, are supported.

Device Drivers -> Multimedia support -> Video capture adapters -> V4L USB Devices -> USB Video Class (UVC),這個選項就是在make menuconfig的時候對應的Kconfig中的內容。

如果用戶選擇了此選項,那麼Makefile產生的uvcvideo.o就會被編譯進內核。

FriendlyARM提供的開發板設置,這一項是[*],也就是將uvcvideo.o默認靜態編譯在內核中。

Linux下的編譯環境,沒有像Window下有那麼多可愛的按鈕讓你按下去,沒有工程的概念,也沒有後臺幫你把所有的事情都搞定了。Linux的編譯過程很麻煩,因爲你得自己使用makefile文件告訴它該怎麼編譯。這是linux的可恨之處,也是linux的可愛之處~

UVC驅動程序的uvc_driver.c

爲了方便調試,首先需要將靜態編譯模塊UVC Driver,改成動態加載的模塊。要不然,每次修改UVC Driver的時候,都需要重新編譯內核。

(如何將靜態編譯的模塊,改成動態加載的模塊呢?在編譯內核make menuconfig的時候,將Kconfig的對應項由“*”修改爲“M”,重新編譯內核make zImage並使用新的zImage引導系統。源文件driver/media/video/uvc/目錄下將生成uvcvideo.ko文件,將uvcvideo.ko拷貝到嵌入式開發板的/lib/modules/3.0.8-FriendlyARM下,在終端輸入指令modprobe uvcvideo。如果終端指示找不到uvcvideo這個文件,則需要先進入上一級目錄,執行depmod。)

修改uvc_driver.c,在其中加入調試語句,就能跟蹤UVC攝像頭的驅動步驟。uvc_driver.c是一個module文件,因爲它具備了兩個很典型的module函數:

static int __init uvc_init(void)

{

        int result;

        result = usb_register(&uvc_driver.driver);

        if (result == 0)

                printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");

        return result;

}

static void __exit uvc_cleanup(void)

{

        usb_deregister(&uvc_driver.driver);

}

uvc_init是模塊裝載函數,僅僅執行了usb_register(&uvc_driver.driver)這個操作。如果usb_register成功,會打印內核信息,並返回0,如果不成功,則返回錯誤代碼。一般來說,模塊初始化的“__init”函數中,除了註冊設備外,不進行任何動作,而需要進行的實質初始化動作,放在“open”函數中。這是因爲模塊註冊之後,用戶很少會去卸載它,如果“__init”函數中包含了對資源的佔領,那麼在它並不工作的時候,也無法釋放這些資源。

被“__init”註冊的struct uvc_driver定義在driver/media/video/uvcvideo.h中:

struct uvc_driver {

        struct usb_driver driver;

};

(好吧,雖然打着uvc_driver的旗號,尼瑪就是一個普通的USB設備好不?~)知道了uvc_driver是什麼,就能夠理解在uvc_driver.c中定義的struct uvc_driver uvc_driver

struct uvc_driver uvc_driver = {

        .driver = {

                .name           = "uvcvideo",

                .probe          = uvc_probe,

                .disconnect     = uvc_disconnect,

                .suspend        = uvc_suspend,

                .resume         = uvc_resume,

                .reset_resume   = uvc_reset_resume,

                .id_table       = uvc_ids,

                .supports_autosuspend = 1,

        },

};

.name是要註冊進USB Core的驅動名字,它會顯示在/sys/bus/usb/drivers/下。但它並不是應用程序將要讀寫的設備文件。當UVC設備插入的時候,會調用.uvc_probe。當UVC設備拔出的時候,會調用.uvc_disconnect。內核需要.id_table用來判斷插入的設備,是否自身適用。

定義.id_tableuvc_ids列表中,並沒有[046d:0825]這一項,但是探測回調函數.uvc_probe確實又被調用了。這是爲什麼呢?

const struct struct usb_device_id *id_table指向struct usb_device_id表的指針,該表中包含了一列該驅動程序可以支持的所有不同類型的USB設備。如果沒有設置該變量,USB驅動程序中的探測回調函數不會被調用。如果想要驅動程序對於系統中的每一個USB設備都被條用,創建一個只設置driver_info字段的條目。fromLINUX設備驅動程序》

雖然uvc_ids列表中的36項都沒有[046d:0825],但是最後一項是:

 { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) },

USB_INTERFACE_INFO定義在linux/usb.h:

#define USB_INTERFACE_INFO(cl, sc, pr) \

        .match_flags = USB_DEVICE_ID_MATCH_INT_INFO, \

        .bInterfaceClass = (cl), \

        .bInterfaceSubClass = (sc), \

        .bInterfaceProtocol = (pr)

.match_flags定義的USB_DEVICE_ID_MATCH_INT_INFO,說明只要USB設備的接口描述符bInterfaceClassbInterfaceSubClassbInterfaceProtocol這三個變量,符合uvc_driver.c中定義的USB_CLASS_VIDEO10,那麼驅動也是可以被這個USB設備使用的。這三個變量的意義,由USB協會定義。USB_CLASS_VIDEO定義在usb.h中。當把uvc_ids列表中的最後一項註釋掉之後,攝像頭也不再能被識別。

UVC攝像頭被正常識別之後,uvc_probe函數就會被調用。爲了正常使用uvc_probe裏面的uvc_trace函數(定義在driver/media/video/uvcvideo.h中),需要將uvc_trace_param這個變量置爲0x07FF。(意思是不管uvc你有什麼信息,儘管全都顯示出來吧~)

當修改了uvc_trace_param後,原來被隱藏的UVC內核調試信息就會被顯示出來。(你會發現這時候內核會打印出一大串信息,終端會刷刷刷的刷屏,弄得俺差點以爲是segment錯誤)而這一大串信息都來自probe函數(或者由它調用的函數)。

UVC驅動程序的uvc_driver.cprobe函數

當插上攝像頭時,usb core會自動調用probe函數,這個函數很重要。probe函數會完成很多事情。(既然是調試,就讓我們讓它完成更多的事情,來幫助理解probe是怎麼工作的)

static int uvc_probe(struct usb_interface *intf, const struct usb_device_id *id)

上面就是probe函數頭了,它傳遞了兩個參數,這兩個參數由usb core直接賦值,用戶不用關心如何被賦值,只需要關心如何使用它們就好了。

probe函數需要完成的工作有:

NO.1:從一個struct usb_interface獲取一個控制的struct usb_device

struct usb_device *udev = interface_to_usbdev(intf);

probe函數被調用時,usb core僅僅傳輸了usb_interfaceusb_device_id這兩個參數。通過usb_interface就可以獲取到當前的usb_device了。usb_device就是我們的羅技攝像頭C270了,設備描述符、配置描述符等等都可以通它來取得。

爲嘛還要通過函數interface_to_usbdev獲取usb_device,而不通過參數直接傳遞呢?

一個usb_interface代表了一個基本功能,而每個USB驅動程序控制一個usb_interface。所有的USB驅動程序都用usb_interface來和usb core進行通信,所以驅動程序就用usb_interface來做入口參數啦。

usb_device通常具備一個或多個usb_host_config;一個usb_host_config通常具備一個或多個usb_interface。(usb_device是爺爺,usb_host_config是爸爸,usb_interface是孩子)

NO.2(這一步是俺加的)打印設備描述符/配置描述符/接口描述符的內容。

知道了usb_device之後,就可以知道它包含的關於攝像頭C270更多的內容。usb_device有一項是struct usb_device_descriptor,也就是攝像頭C270的設備描述符。

usb_device包含的配置有兩個,一個是config,一個是actconfig,從字面上就能理解它們的區別了,前者用來存儲所有的配置,後者用來存儲當前的配置。羅技攝像頭C270只有一個配置,所以configactconfig也是一樣的。

actconfig包含了struct usb_interface,實際上羅技攝像頭C2704個。雖然有4個可用,但是目前活動的只有1個,一般來說是interface 0。活動的interface 0會傳輸設備描述符、配置描述符,而其他3個接口描述符也會和配置描述符一起被傳輸過來。

使用printk函數將它們的內容打印出來,打印的時候注意檢查指針是否爲NULL

printk(KERN_NOTICE "maria_debug: bLength=0x%04x\n", udev->descriptor.bLength);

printk(KERN_NOTICE "maria_debug: bLength=0x%04x\n", 

udev->actconfig->desc.bLength);

 

intf_t = udev->actconfig->interface[i];

intf_desc_t = &(intf_t->altsetting->desc);

printk(KERN_NOTICE "maria_debug: bLength=0x%04x\n", intf_desc_t->bLength);

就能得到下面的三張表:

O f f s e t

F i e l d  

S i z e 

V a l u e

D e s c r i p t i o n

0

bLength

1

0x12

描述符長度爲18

1

bDescriptorType

1

0x01

這是設備描述符

2

bcdUSB

2

0x0200

2.00的USB協議版本

4

bDeviceClass 

1

0xEF

Miscellaneous Device Class 

5

bDeviceSubClass

1

0x02

Common Class 

6

bDeviceProtocol 

1

0x01

Interface Association Descriptor

7

bMaxPacketSize0

1

0x40

Control endpoint packet最大長度64字節

8

idVendor

2

0x046D

廠商id

10

idProduct

2

0x0825

產品id

12

bcdDevice

2

0x0010

Device release code

14

iManufacturer

1

0x00

idVendor的字符串索引,爲0則未用

15

iProduct

1

0x00

idProduct的字符串索引,爲0則未用

16

iSerialNumber

1

0x02

序列號的字符串索引

17

bNumConfigurations

1

0x01

可能的配置數

 

O f f s e t

F i e l d  

S i z e 

V a l u e

D e s c r i p t i o n

0

bLength

1

0x09

描述符長度爲9

1

bDescriptorType

1

0x02

這是配置描述符

2

wTotalLength

2

0x09A5

描述符的總長度爲2469個字節

4

bNumInterfaces

1

0x04

4個interface

5

bConfigurationValue

1

0x01

這個配置的編號

6

iConfiguration

1

0x00

配置描述符的字符串索引,爲0x00則未用

7

bmAttributes

1

0x80

總線供電設備

8

bMaxPower

1

0xFA

最大功耗爲500mA

 

O f f

 s e t

F i e l d  

大小

D e s c r i p t i o n

0

bLength

1

0x09

0x09

0x09

0x09

描述符長度爲9

1

bDescriptorType

1

0x04

0x04

0x04

0x04

這是接口描述符

2

bInterfaceNumber

1

0x00

0x01

0x02

0x03

這是接口0、1、2、3

3

bAlternateSetting 

1

0x00

0x00

0x00

0x00

這是設置0

4

bNumEndpoints

1

0x01

0x00

0x01

0x00

這是端點1、0、1、0

5

bInterfaceClass

1

0x0e

0x0e

0x01

0x01

e:CC_VIDEO

6

bInterfaceSubClass

1

0x01

0x02

0x01

0x02

1:SC_VIDEOCONTROL

2:SC_VIDEOSTREAMING

7

bInterfaceProtocol

1

0x00

0x00

0x00

0x00

Not used

8

iInterface

1

0x00

0x00

0x00

0x00

字符串索引

除了設備描述符和配置描述符,USB設備的描述符還有很多(真的是很多~),有興趣的話可以都打印出來看一看。觀察USB的描述符能夠對設備有更加直觀的印象,這點對硬件工程師尤其重要。(學過USB協議的童鞋有木有覺得,這幾張表很親切很親切涅~~

NO.3:爲UVC設備申請內存空間,並做相應的初始化。

dev = kzalloc(sizeof *dev, GFP_KERNEL));

INIT_LIST_HEAD(&dev->entities);

INIT_LIST_HEAD(&dev->chains);

INIT_LIST_HEAD(&dev->streams);

atomic_set(&dev->nstreams, 0);

atomic_set(&dev->users, 0);

atomic_set(&dev->nmappings, 0);

dev->udev = usb_get_dev(udev);

dev->intf = usb_get_intf(intf);

dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;

dev->quirks = (uvc_quirks_param == -1)? id->driver_info : uvc_quirks_param;

if (udev->product != NULL)

strlcpy(dev->name, udev->product, sizeof dev->name);

else

snprintf(dev->name, sizeof dev->name,

"UVC Camera (%04x:%04x)",

le16_to_cpu(udev->descriptor.idVendor),

le16_to_cpu(udev->descriptor.idProduct));

probe函數會定義一個struct uvc_device *dev,既然是指針,就需要爲它申請內存空間,否則後面對結構體變量的賦值就是做無用功了。若是內存申請成功,還需要對它其中的內容進行初始化。

entitieschainsstreamslist_head類型的變量。它們的用途後面會提到。list_head這個結構體類型很有意思,實際上和list結構體類型非常類似,它定義在include/linux/types.h中:

struct list_head {        

struct list_head *next, *prev;

};

INIT_LIST_HEAD這個函數就是將這幾個list_headnextprev都指向自身。nstreamsusersnmappings這幾個是原子變量,不能直接賦值,而是要使用atomic_set

dev裏面包含了一個usb_device,這個時候就需要將羅技攝像頭C270udev賦給它,從此有了dev就什麼都可以訪問,簡直是天下無敵了。這裏爲什麼要用usb_get_dev,而不是直接使用指針賦值?因爲使用usb_get_dev增加了對usb_device的引用,usb core就知道還有幾個程序使用這個usb_device,也從而不會在還有程序使用它的時候將它釋放掉。使用usb_get_intf也是一樣的道理。

另外intfnumquirksname也跟着被初始化了。

NO.4:識別攝像頭的畫面格式和指令格式uvc_parse_control

uvc_parse_control(dev);

不要小看這個函數,它是灰常灰常重要的。以下都是對它的分析:

struct usb_host_interface *alts = dev->intf->cur_altsetting;

unsigned char *buffer = alts->extra;

int buflen = alts->extralen;

 

while (buflen > 2) {

if (uvc_parse_vendor_control(dev, buffer, buflen) ||

buffer[1] != USB_DT_CS_INTERFACE) 

goto next_descriptor;

 

if ((ret = uvc_parse_standard_control(dev, buffer, buflen)) < 0) 

return ret;

 

next_descriptor:

buflen -= buffer[0];

buffer += buffer[0];

}

它首先獲取了cur_altsetting指針,指向當前設備的活動usb_host_interface。羅技攝像頭C2704usb_host_interface,而cur_altsetting指向當前活動的第0個。

配置描述符有一項是wTotalLength,它的值是0x09A5。配置描述符本身才9個字節,哪裏佔得了0x09A5這麼多呢?所以0x09A5大部分是其他描述符的內容。

0x09A5這麼多字節中,cur_altsetting(usb_host_interface 0)佔據了168(159+9自身描述符)個字節,這個通過觀察cur_altsetting->extralen就可以知道了。

uvc_parse_control的內容,就是遍歷這159個字節,查找其中有用的描述符內容。實際上,這159個字節對應的8個描述符很容易被觀察出來(please使用萬能的printk函數~),對應的uvc_parse_standard_control函數也被執行了8次。

Interface 0的描述符內容:

編號

長度

Interface 0 內容

1

13

13  36  1  0  1  159  0  0  108  220  2  1  1  

2

18

18  36  2  1  1  2  0  0  0  0  0  0  0  0  3  14  0  0  

3

11

11  36  5  2  1  0  64  2  91  23  0

4

27

27  36  6  3  228  142  103  105  15  65  219  64  168  80  116  32  215  216  36  14  8  1  2  2  63  3  0

5

26

26  36  6  4  21  2  228  73  52  244  254  71  177  88  14  136  80  35  229  27  0  1  2  1  0  0

6

28

28  36  6  6  169  76  93  31  17  222  135  68  132  13  80  147  60  142  200  209  18  1  4  3  255  255  3  0

7

27

27  36  6  7  33  45  229  255  48  128  44  78  130  217  245  135  208  5  64  189  2  1  4   2  0  3  0

8

9

9  36  3  5  1  1  0  4  0

第一個描述符告訴我們:這是一個Class-specific VC Interface DescriptorbcdUVC0x0100,總長度是159(是不是剛好對應上buflen呢?)設備提供的時鐘爲48000000Hz,有1streaming interface,且這個streaming interface 1從屬於本控制interface 0。查閱一下描述符的說明就更清楚了。

這個描述符的buffer[2]1,所以會進入函數中switchUVC_VC_HEADER項。在這一項中,首先獲得了dev->uvc_version,也就是0x0100;然後獲得了dev->clock_frequency,也就是48MHz;其次運行usb_ifnum_to_if(udev, buffer[12]),獲得指向interface 1的指針intf;最後執行uvc_parse_streaming(dev, intf)函數。uvc_parse_streaming裏面是對interface 1的處理。

比起interface 0159個字節,interface 11850個字節。想了解uvc_parse_streaming裏面發生了什麼,強烈建議把這1850個字節使用printk打印出來看看。(因爲內核信息文件/proc/kmsg是個循環緩衝區,打印的信息被打出來之後就沒有了,所以打印這麼多字節可能cat的時候是看不到的,因爲被後面的覆蓋了,所以可能需要把其他的內核打印信息屏幕起來,反正你調試的時候就知道了~

編號

長度

Interface 1 內容

1

16

10 24 01 03 3a 07 81 00 05 01 00 00 01 00 04 04

2

27

1b 24 04 01 13 59 55 59 32 00 00 10 00 80 00 00 aa 00 38 9b 71 10 01 00 00 00 00

3

50

32 24 05 01 01 80 02 e0 01 00 00 77 01 00 00 ca 08 00 60 09 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

4

50

32 24 05 02 01 a0 00 78 00 00 70 17 00 00 a0 8c 00 00 96 00 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

5

50

32 24 05 03 01 b0 00 90 00 00 f0 1e 00 00 a0 b9 00 00 c6 00 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

6

50

32 24 05 04 01 40 01 b0 00 00 c0 44 00 00 80 9c 01 00 b8 01 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

7

50

32 24 05 05 01 40 01 f0 00 00 c0 5d 00 00 80 32 02 00 58 02 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

8

50

32 24 05 06 01 60 01 20 01 00 c0 7b 00 00 80 e6 02 00 18 03 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

9

50

32 24 05 07 01 b0 01 f0 00 00 90 7e 00 00 60 f7 02 00 2a 03 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

10

50

32 24 05 08 01 20 02 20 01 00 40 bf 00 00 80 7b 04 00 c8 04 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

11

50

32 24 05 09 01 80 02 68 01 00 40 19 01 00 80 97 06 00 08 07 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

12

46

2e 24 05 0a 01 f0 02 a0 01 00 e0 7d 01 00 60 75 07 00 8c 09 00 80 1a 06 00 05 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

13

46

2e 24 05 0b 01 20 03 c0 01 00 80 b5 01 00 80 8b 08 00 f0 0a 00 80 1a 06 00 05 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

14

42

2a 24 05 0c 01 20 03 58 02 00 f0 49 02 00 c0 27 09 00 a6 0e 00 20 a1 07 00 04 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

15

42

2a 24 05 0d 01 60 03 e0 01 00 40 fa 01 00 00 e9 07 00 a8 0c 00 20 a1 07 00 04 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

16

38

26 24 05 0e 01 c0 03 20 02 00 80 7d 02 00 80 78 07 00 f0 0f 00 2a 2c 0a 00 03 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

17

34

22 24 05 0f 01 c0 03 d0 02 00 c0 4b 03 00 80 97 06 00 18 15 00 40 42 0f 00 02 40 42 0f 00 80 84 1e 00 

18

34

22 24 05 10 01 00 04 40 02 00 00 d0 02 00 00 a0 05 00 00 12 00 40 42 0f 00 02 40 42 0f 00 80 84 1e 00

19

34

22 24 05 11 01 a0 04 90 02 00 20 b4 03 00 40 68 07 00 b4 17 00 40 42 0f 00 02 40 42 0f 00 80 84 1e 00 

20

34

22 24 05 12 01 00 05 d0 02 00 00 65 04 00 00 ca 08 00 20 1c 00 40 42 0f 00 02 40 42 0f 00 80 84 1e 00

21

34

22 24 05 13 01 00 05 c0 03 00 00 dc 05 00 00 b8 0b 00 80 25 00 80 84 1e 00 02 55 58 14 00 80 84 1e 00

22

6

06 24 0d 01 01 04  

23

11

0b 24 06 02 13 01 01 00 00 00 00 

24

50

32 24 07 01 01 80 02 e0 01 00 00 77 01 00 00 ca 08 00 60 09 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

25

50

32 24 07 02 01 a0 00 78 00 00 70 17 00 00 a0 8c 00 00 96 00 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

26

50

32 24 07 03 01 b0 00 90 00 00 f0 1e 00 00 a0 b9 00 00 c6 00 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

27

50

32 24 07 04 01 40 01 b0 00 00 c0 44 00 00 80 9c 01 00 b8 01 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

28

50

32 24 07 05 01 40 01 f0 00 00 c0 5d 00 00 80 32 02 00 58 02 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

29

50

32 24 07 06 01 60 01 20 01 00 c0 7b 00 00 80 e6 02 00 18 03 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

30

50

32 24 07 07 01 b0 01 f0 00 00 90 7e 00 00 60 f7 02 00 2a 03 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

31

50

32 24 07 08 01 20 02 20 01 00 40 bf 00 00 80 7b 04 00 c8 04 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

32

50

32 24 07 09 01 80 02 68 01 00 40 19 01 00 80 97 06 00 08 07 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

33

50

32 24 07 0a 01 f0 02 a0 01 00 e0 7d 01 00 40 f3 08 00 8c 09 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

34

50

32 24 07 0b 01 20 03 c0 01 00 80 b5 01 00 00 41 0a 00 f0 0a 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

35

50

32 24 07 0c 01 20 03 58 02 00 f0 49 02 00 a0 bb 0d 00 a6 0e 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

36

50

32 24 07 0d 01 60 03 e0 01 00 40 fa 01 00 80 dd 0b 00 a8 0c 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00 

37

50

32 24 07 0e 01 c0 03 20 02 00 80 7d 02 00 00 f1 0e 00 f0 0f 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

38

50

32 24 07 0f 01 c0 03 d0 02 00 c0 4b 03 00 80 c6 13 00 18 15 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

39

50

32 24 07 10 01 00 04 40 02 00 00 d0 02 00 00 e0 10 00 00 12 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

40

50

32 24 07 11 01 a0 04 90 02 00 20 b4 03 00 c0 38 16 00 b4 17 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

41

50

32 24 07 12 01 00 05 d0 02 00 00 65 04 00 00 5e 1a 00 20 1c 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

42

50

32 24 07 13 01 00 05 c0 03 00 00 dc 05 00 00 28 23 00 80 25 00 15 16 05 00 06 15 16 05 00 80 1a 06 00 20 a1 07 00 2a 2c 0a 00 40 42 0f 00 80 84 1e 00

43

6

06 24 0d 01 01 04

 

步驟

uvc_parse_streaming(dev, intf) 函數的工作

第一步

檢查接口描述符的bInterfaceSubClass是否爲0x02(是);

第二步

執行usb_driver_claim_interface,將當前的intf和usb_driver捆綁起來;

第三步

爲uvc_streaming指針申請內存空間,並初始化它內部的變量;

第四步

檢查interface 1的第1條描述符,檢查出這是一條UVC_VS_INPUT_HEADER描述符,streaming->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; size =13;

第五步

p=3說明video payload format descriptors數目有3個;

n=1說明Size of each bmaControls(x) field爲1;

第六步

給streaming.header的成員變量賦值;

第七步

檢查interface 1的剩餘42條描述符,其中有1條UVC_VS_FORMAT_UNCOMPRESSED描述符,19條UVC_VS_FRAME_UNCOMPRESSED描述符,1條UVC_VS_FORMAT_MJPEG描述符,19條UVC_VS_FRAME_MJPEG描述符;(還有2條UVC_VS_COLORFORMAT描述符);

爲上訴描述符的formats和frames申請內存空間共2116個字節;

第八步

再次檢查interface 1的剩餘42條描述符,在UVC_VS_FORMAT_UNCOMPRESSEDUVC_VS_FORMAT_MJPEG處執行uvc_parse_format start(遍歷每個FORMAT下的描述符,得出可用的format和frame信息);

第九步

Parse the alternate settings to find the maximum bandwidth.

第十步

list_add_tail(&streaming->list, &dev->streams);

總而言之,uvc_parse_control執行完第一條對interface 0的處理之後,羅技攝像頭的參數就基本被填入uvc_device了。需要注意的是,在這個函數中申請的framestreaming,雖然沒有直接將自身的指針填入uvc_device,但是它們包含的鏈表,添加進入了uvc_devic的鏈表,也就可以通過uvc_device中的鏈表來訪問了。

第二條interface 0描述符是一條UVC_VC_INPUT_TERMINAL描述符,在對它的處理中,調用了uvc_alloc_entity函數,它與dev中的list_head類型的entities有關。

第三條interface 0描述符是一條UVC_VC_PROCESSING_UNIT描述符,在對它的處理中,調用了uvc_alloc_entity函數,它與dev中的list_head類型的entities有關。

第四條至第七條interface 0描述符是一條UVC_VC_EXTENSION_UNIT描述符,在對它的處理中,調用了uvc_alloc_entity函數,它與dev中的list_head類型的entities有關。

第八條interface 0描述符是一條UVC_VC_OUTPUT_TERMINAL描述符,在對它的處理中,調用了uvc_alloc_entity函數,它與dev中的list_head類型的entities有關。

NO.5:註冊v4l2設備。

v4l2_device_register(&intf->dev, &dev->vdev);

V4l2的意思是video for linux version2,爲什麼要註冊它呢?

NO.6Initialize controls.

uvc_ctrl_init_device(dev);

NO4的步驟中,對第二條至第八條的interface 0描述符的處理,都和entites有關。而在此處的步驟中,就是進一步的處理entities了。 list_for_each_entry(entity, &dev->entities, list),這個函數其實是一個for循環的宏定義,它的作用是遍歷鏈表的所有成員。

NO.7Scan the device for video chains.

uvc_scan_device(dev);

這個函數。

NO.8Register video device nodes.

uvc_register_chains(dev);

uvc_driver.c雖然是一個模塊文件,但是它裏面並沒有提供fops操作。木有fops就沒有openreadwriteioctl,就是說驅動程序這塊大蛋糕,把自己放在了密封的玻璃箱裏面,連把勺子都沒給。真的沒有fops嗎?實際上uvc_driver.c雖然沒有自己的fops,但是它借用了別人的,也就是v4l2的。

v4l2fops名字叫做uvc_fops,定義在uvc_v4l2.c中。

另外,uvc_register_chains這個函數還實現了make devnod的功能。Linux系統下,通用設備有自己的主設備號,video設備的主設備號是81,這個通過查看/dev就能知道。副設備號是自動申請的,驅動程序會檢查設備號是否可用,然後將那個設備號給當前設備。

這裏同樣會遍歷鏈表,給鏈表的每個可用項都分配設備號。像羅技攝像頭C270這樣的webcam只有一個視頻流,當然devnode也只有1個。像視頻採集卡這樣的有好幾個視頻流的,當然devnode也相應的有好幾個。

如果想知道羅技攝像頭C270生成的是哪個設備,那麼遍歷devchains鏈表,再遍歷它裏面的entities鏈表,就能夠找到可用的uvc_streaming,在它裏面,存放着devnode的副設備號,把它打印出來,就知道了。(好吧~其實更簡單的方法就是插上拔下攝像頭,看看/dev有什麼變化)

No.9: Save our data pointer in the interface data. 

usb_set_intfdata(intf, dev);

這個函數和usb_get_intfdata配合使用,用來設置和獲取intf內私有數據段的指針。編寫過字符驅動程序的都知道,一般在模塊文件中,都會聲明一個結構體,用來存儲專屬於本模塊文件的變量。比如這個模塊的名字、信號量、自旋鎖等等。但是這個結構體並不會以全局變量的形式來定義,而是在open或者probe函數中動態的申請。

這是因爲,如果這個結構體有很多很多很多的變量,如果定義成全局的,那麼只要模塊被初始化,這些變量就會佔據很多很多很多的內存。Linux系統纔不管這個模塊有沒有被使用,哪怕一輩子都不會用到,這些內存都不會被釋放。而如果在openprobe函數中申請,那麼被用到的模塊纔會申請內存,這樣即使用戶裝載了很多很多很多的模塊,每個模塊都使用了很多很多很多的變量,只要它們不同時使用,內存都能夠被最大程序的利用。

既然是動態申請,就牽扯到一個很重要的問題,就是動態申請的指針如何保存。既然全局變量的形式並不推薦,那麼模塊的各個函數之間怎麼共享這個指針呢?一般來說,字符驅動程序會在open函數中申請內存,而且將這個指針存放在自己的file->private_data中,因爲這個變量存在於文件打開期間,而且對模塊驅動程序來說,所有的函數都可用它。

USB驅動程序有自己的特殊之處,它在probe函數中就申請了內存,但此時還沒有被open了,file->private_data也就無從談起。好在usb_interface也提供了類似的私有變量區,所以將dev的指針存放在這裏,方便以後的函數訪問。

No.10: 使能自動休眠。

usb_enable_autosuspend(udev);

這個函數是usb core的事情,就不在uvc裏面討論了。

UVC應用程序的open函數

終於到了應用程序的編寫了。相信很多學習linux圖像處理的童鞋,幾乎迫不及待的想跳過前面的步驟,直接到這裏。(其實俺原來也是這麼想的,不過老話說的好,磨刀不誤砍柴工,與其找一大堆應用程序的攻略,不如仔細研究驅動程序,因爲應用程序很關鍵的功能就包括“驅動程序的使用”。)

這裏的應用程序在qtopia2.2.0下開發,實際上與uvc應用程序相關的語句,在控制檯程序下也是通用的。控制檯程序的入口函數是main,而qtopia2.2.0程序界面的入口函數是TMainForm::TMainForm(QWidget *parent, const char *name, WFlags f),初始化函數幾乎都放在這個裏面,uvcopen函數也不例外。

#define DEVICE          "/dev/video3"

 

logi_fd = open(DEVICE, O_RDWR);

if (logi_fd < 0)

goto ERROR;

DEVICE是第幾個video,通過前面“UVC驅動程序”NO.8就知道了,這裏是video3。當然,一個優秀的、精緻的、人性化的、把用戶當成是傻瓜的應用程序,咳咳~~好吧~~把用戶當成是上帝的應用程序,必然會考慮到linux系統也許會生成不一樣的devnode,所以初始化的時候會掃描所有的video,並且匹配idVendoridProduct來確定最終的devnode

這裏的open函數,實際上執行的是uvc_v4l2.c中的uvc_v4l2_open函數,它爲一個uvc_fh結構體類型申請了內存,並將它的指針handle保存在了file->privata_data中。(這個handle要和前面保存在intf私有變量中的dev指針區分開)

handle包含的一個變量struct uvc_streaming,被賦值爲video_drvdata(file)(這個值實際上爲video_device[iminor(file->f_path.dentry->d_inode)]->dev->p->driver_data)

UVC應用程序的ioctl函數之VIDIOC_QUERYCAP

先從最簡單的功能開始吧,ioctl的第一個參數就是VIDIOC_QUERYCAP,意思是通過ioctl查詢capability。(ioctl是幹嘛用的?你不如問我太陽爲蝦米是圓的,蘋果爲蝦米是甜的……請補習linux的驅動程序基本構成)

if (ioctl(logi_fd, VIDIOC_QUERYCAP, &capability) < 0)

goto ERROR;

capabilitystruct v4l2_capability類型的變量,類型定義在linux/videodev2中,裏面存放的基本上都是字符串,執行此ioctl之後,將它的內容打印出來:

capability.driver = uvcvideo

capability.card = UVC Camera (046d:0825)

capability.bus_info = usb-s5p-ehci-1.2

capability.version = 0x00010100

capability.capabilities = 0x04000001

前面的4項都比較好理解,最後一項實際上有如下成立:

0x04000001 == V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE

這說明,當前設備是一個video streaming設備。

UVC應用程序的ioctl函數之VIDIOC_QUERYCTRL:

ioctl的第二個參數是VIDIOC_QUERYCTRL,它的功能是獲取、設置、請求control

if (ioctl(logi_fd, VIDIOC_QUERYCTRL, &v4l2_ctrl) < 0)

goto ERROR;  

這個ioctl功能將調用int uvc_query_v4l2_ctrl(chain, arg)函數,chaindev->chain。而它的arg則爲ioctl的入口參數,也就是v4l2_ctrl,爲struct v4l2_queryctrl類型的結構體變量。

這個參數的使用就沒有前面的VIDIOC_QUERYCAP那麼簡單了,因爲v4l2_ctrl的成員變量id必須要被賦初值,否則內核會提示“Control 0x00000000 not found.”那這個id該如何設置呢?要是隻有少數幾個,那就一個個試,看看返回了什麼,但是尼瑪有u32那麼多個好不好?所以,還是先找一個已知的可用的id宏,然後看看它被定義在了什麼地方。V4L2_CID_GAIN就是一個可用的宏,它定義在linux/videodev2.h,和它定義在一起的就是可用的id值。

v4l2_ctrl->id賦值爲V4L2_CID_GAIN,然後執行ioctl,則會有下面的語句被打印出來:

v4l2_ctrl.id=0x00980913

v4l2_ctrl.type=0x00000001

v4l2_ctrl.name=Gain

v4l2_ctrl.minimum=0

v4l2_ctrl.maximum=255

v4l2_ctrl.step=1

v4l2_ctrl.default_value=0

v4l2_ctrl.flags=0x00000000

 

v4l2_ctrl->id賦值爲V4L2_CID_BRIGHTNESS,然後執行ioctl,則會有下面的語句被打印出來:

v4l2_ctrl.id=0x00980900

v4l2_ctrl.type=0x00000001

v4l2_ctrl.name=Brightness

v4l2_ctrl.minimum=0

v4l2_ctrl.maximum=255

v4l2_ctrl.step=1

v4l2_ctrl.default_value=128

v4l2_ctrl.flags=0x00000000

 

(還有各種各樣其他的參數,有興趣的童鞋請試一試~

UVC應用程序的ioctl函數之VIDIOC_G_CTRL:

上面的參數VIDIOC_QUERYCTRL,是讀取設備可用的值。而此處的參數VIDIOC_G_CTRL是讀取設備當前的設定值。

if (ioctl(logi_fd, VIDIOC_G_CTRL, &ctrl) < 0)

goto ERROR;

ctrlstruct v4l2_control類型的結構體,它的成員變量只有兩個,就是idvalue。用法和上面的VIDIOC_QUERYCTRL是一致的。

ctrl->id賦值爲V4L2_CID_GAIN,然後執行ioctl,則會有下面的語句被打印出來:

ctrl.id=0x00980913

ctrl.value=0x00000000

ctrl->id賦值爲V4L2_CDI_BRIGHTNESS,然後執行ioctl,則會有下面的語句被打印出來:

ctrl.id=0x00980900

ctrl.value=0x00000080

是不是很簡單呢?

UVC應用程序的ioctl函數之VIDIOC_S_CTRL:

設置設備的參數值,使用它之後,最好再使用VIDIOC_G_CTRL,查詢一下設置是否成功了。

ctrl.id = V4L2_CID_BRIGHTNESS;

ctrl.value = 0x0079;

if (ioctl(logi_fd, VIDIOC_S_CTRL, &ctrl) < 0)

goto ERROR; 

UVC應用程序的ioctl函數之VIDIOC_G_FMT:

這個是比較重要的ioctl參數。

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if (ioctl(logi_fd, VIDIOC_G_FMT, &fmt) < 0) 

goto ERROR;

fmtstruct v4l2_fmt類型。爲了使用這個ioctl功能,必須先初始化fmt,並且不同的type對應的聯合體成員變量fmt類型也是不一樣的。V4L2_BUF_TYPE_VIDEO_CAPTURE對應的是struct v4l2_pix_format

執行完這個ioctl之後,將返回的信息打印出來,就有:

fmt.fmt.pix.width = 640

fmt.fmt.pix.height = 480

fmt.fmt.pix.pixelformat = 1196444237

fmt.fmt.pix.field = 1

fmt.fmt.pix.bytesperline = 0

fmt.fmt.pix.sizeimage = 213333

fmt.fmt.pix.colorspace = 8

fmt.fmt.pix.priv = 0

這個就是羅技攝像頭C270的默認設置了,它會生成640*480的圖像,圖像格式是1196444237。這是嘛數?還記得設備驅動程序中,內核打印的信息嗎?羅技攝像頭C270的輸出格式有兩種,一個是YUV 4:2:2,一個是MJPEG。而圖像格式的數,是通過下面這個宏來實現的:

#define v4l2_fourcc(a, b, c, d)\

        ((__u32)(a) | ((__u32)(b) << 8) | ((__u32)(c) << 16) | ((__u32)(d) << 24))

1196444237=0x47504A4D,不就是v4l2_fourcc(M,J,P,G)嗎?也就是宏定義V4L2_PIX_FMT_MJPEG

UVC應用程序的ioctl函數之VIDIOC_S_FMT:

這個也是很重要的ioctl參數,使用它之後,最好再使用VIDIOC_G_FMT,查詢一下設置是否成功了。

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;

fmt.fmt.pix.width = 800;

fmt.fmt.pix.height = 600;

if (ioctl(logi_fd, VIDIOC_S_FMT, &fmt) < 0)

goto ERROR;

UVC應用程序的ioctl函數之VIDIOC_G_PARM

這個ioctl的功能是爲了獲取設備的幀信息。

parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if (ioctl(logi_fd, VIDIOC_G_CTRL, &ctrl) < 0)

goto ERROR;

執行完這個ioctl之後,將返回的信息打印出來,就有:

parm.parm.capture.capability = 0x00001000

parm.parm.capture.capturemode = 0x00000000

parm.parm.capture.extendedmode = 0x00000000

parm.parm.capture.readbuffers = 0x00000000

parm.parm.capture.timeperframe.numeriator = 0x00000001

parm.parm.capture.timeperframe.denominator = 0x0000001e

capability = 0x00001000的意思是timeperframe field is supported

UVC應用程序的ioctl函數之VIDIOC_REQBUFS

這個ioctl的功能是申請緩衝區。

reg.count = 1;

reg.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

reg.memory = V4L2_MEMORY_MMAP;

if (ioctl(logi_fd, VIDIOC_REQBUFS, ®) < 0) 

goto ERROR;

regstruct v4l2_requestbuffers類型的變量,REQBUFS,意思是請求BUFS,可是它不是應用程序從模塊中請求BUFS,而是應用程序命令模塊從內存中得到BUFS。名字很唬人,用法其實很簡單,reg總共才3個成員變量,分別賦值之後進行ioctl,模塊會根據運行的情況修改這3個變量值,but正常運行無誤的情況下,總會返回原來的值。

UVC應用程序的ioctl函數之VIDIOC_QUERYBUF

這個ioctl的功能是獲得內存映射的參數。它和後面要說到的VIDIOC_QBUF實在是很像,它們的入口參數類型相同,值也一模一樣,實際上在驅動程序裏面,它們倆調用的也確實是同一個函數uvc_query_buffer。但應用程序使用它們實現不同的功能,不同的參數名方便了程序的使用。

v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

v4l2_buf.memory = V4L2_MEMORY_MMAP;

v4l2_buf.index = 0;

if (ioctl(logi_fd, VIDIOC_QUERYBUF, &v4l2_buf) < 0)

goto ERROR;

 

buffer.size = v4l2_buf.length;

buffer.data = mmap(NULL, buffer.size, PROT_READ | PROT_WRITE, MAP_SHARED, logi_fd,  v4l2_buf.m.offset);

if (buffer.data == NULL)

goto ERROR;

 

printf("buffer.size=%d\n", buffer.size);

printf("buffer.data=0x%08xd\n", (int)buffer.data);

運行的結果是:

buffer.size=816000

buffer.data=0x41284000

執行了ioctl後,v4l2_buf會被賦值,它其中存放了圖像的大小、地址偏移量等信息。知道了這些信息之後,需要申請一個用戶空間的緩衝區buffer,大小由v4l2_buf而定,地址使用mmap來實現。

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

start爲映射區的起始地址,設置爲NULL時則由系統分配;

length爲映射區的長度;

prot爲期望的內存保護標誌,不能與文件的打開模式衝突,PROT_EXEC表示頁內容可以被執行,PROT_READ表示頁內容可以被讀取,PROT_WRITE表示頁可以被寫入,PROT_NONE表示頁不可被訪問;

flags爲指定映射對象的類型,映射選項和映射頁是否可以共享;

fd爲文件描述符,一般由open函數返回;

offset爲被映射對象內容的起點。

執行了mmap之後,會發現buffer.size816000,並且buffer.data指向地址0x41284000,它小於0xC0000000說明這個地址在用戶空間。要是記得在程序結束的時候使用munmap來解除映射,否則buffer佔用的內存是不會被釋放的。


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