基于UVC的罗技摄像头C270 hack



现在,假如你的手上有一只摄像头,它是罗技高清网络摄像头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  (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占用的内存是不会被释放的。

阅读更多
个人分类: V4L
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭