精华内容
下载资源
问答
  • V4L2是V4L的升级版本linux下视频设备程序提供了一套接口规范。常用的结构体在内核目录include/linux/videodev2.h中定义struct v4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFSstruct v4l2_capability /...

    V4L2是V4L的升级版本,linux下视频设备程序提供了一套接口规范。

    常用的结构体在内核目录include/linux/videodev2.h中定义

    struct v4l2_requestbuffers  //申请帧缓冲,对应命令VIDIOC_REQBUFS

    struct v4l2_capability      //视频设备的功能,对应命令VIDIOC_QUERYCAP

    struct v4l2_input           //视频输入信息,对应命令VIDIOC_ENUMINPUT

    struct v4l2_standard        //视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD

    struct v4l2_format          //帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等

    struct v4l2_buffer          //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF

    struct v4l2_crop            //视频信号矩形边框

    v4l2_std_id                 //视频制式

    V4L2采用流水线的方式,操作更简单直观,基本遵循打开视频设备、设置格式、处理数据、关闭设备,更多的具体操作通过ioctl函数来实现。

    1.打开视频设备

    在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备:

    // 用非阻塞模式打开摄像头设备

    int cameraFd;

    cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);

    // 如果用阻塞模式打开摄像头设备,上述代码变为:

    //cameraFd = open("/dev/video0", O_RDWR, 0);

    应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。

    2. 设定属性及采集方式

    打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。在Linux编程中,一般使用ioctl函数来对设备的I/O通道进行管理:

    int ioctl (int __fd, unsigned long int __request, .../*args*/) ;

    在进行V4L2开发中,常用的命令标志符如下(some are optional):

    •    VIDIOC_REQBUFS:分配内存

    •    VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址

    •    VIDIOC_QUERYCAP:查询驱动功能

    •    VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式

    •    VIDIOC_S_FMT:设置当前驱动的频捕获格式

    •    VIDIOC_G_FMT:读取当前驱动的频捕获格式

    •    VIDIOC_TRY_FMT:验证当前驱动的显示格式

    •    VIDIOC_CROPCAP:查询驱动的修剪能力

    •    VIDIOC_S_CROP:设置视频信号的边框

    •    VIDIOC_G_CROP:读取视频信号的边框

    •    VIDIOC_QBUF:把数据从缓存中读取出来

    •    VIDIOC_DQBUF:把数据放回缓存队列

    •    VIDIOC_STREAMON:开始视频显示函数

    •    VIDIOC_STREAMOFF:结束视频显示函数

    •    VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。

    2.1检查当前视频设备支持的标准

    在亚洲,一般使用PAL(720X576)制式的摄像头,而欧洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD来检测:

    v4l2_std_id std;

    do {

    ret = ioctl(fd, VIDIOC_QUERYSTD, &std);

    } while (ret == -1 && errno == EAGAIN);

    switch (std) {

    case V4L2_STD_NTSC:

    //……

    case V4L2_STD_PAL:

    //……

    }

    2.2 设置视频捕获格式

    当检测完视频设备支持的标准后,还需要设定视频捕获格式,结构如下:

    struct v4l2_format fmt;

    memset ( &fmt, 0, sizeof(fmt) );

    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    fmt.fmt.pix.width       = 720;

    fmt.fmt.pix.height      = 576;

    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;

    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {

    return -1;

    }

    v4l2_format结构如下:

    struct v4l2_format {

    enum v4l2_buf_type type; //数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE

    union

    {

    struct v4l2_pix_format   pix;

    struct v4l2_window        win;

    struct v4l2_vbi_format   vbi;

    __u8    raw_data[200];

    } fmt;

    };

    struct v4l2_pix_format {

    __u32                   width;          // 宽,必须是16的倍数

    __u32                   height;         // 高,必须是16的倍数

    __u32                   pixelformat;   // 视频数据存储类型,例如是YUV4:2:2还是RGB

    enum v4l2_field         field;

    __u32                   bytesperline;

    __u32                   sizeimage;

    enum v4l2_colorspace    colorspace;

    __u32                   priv;

    };

    2.3 分配内存

    接下来可以为视频捕获分配内存:

    struct v4l2_requestbuffers  req;

    req.count  = BUFFER_COUNT;

    req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    req.memory = V4L2_MEMORY_MMAP;

    if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {

    return -1;

    }

    v4l2_requestbuffers 结构如下:

    struct v4l2_requestbuffers {

    u32                count;//缓存数量,也就是说在缓存队列里保持多少张照片

    enum v4l2_buf_type type; //数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE

    enum v4l2_memory   memory;//V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR

    u32                reserved[2];

    };

    2.4 获取并记录缓存的物理空间

    使用VIDIOC_REQBUFS,我们获取了req.count个缓存,下一步通过调用VIDIOC_QUERYBUF命令来获取这些缓存的地址,然后使用mmap函数转换成应用程序中的绝对地址,最后把这段缓存放入缓存队列:

    typedef struct VideoBuffer {

    void   *start;

    size_t  length;

    } VideoBuffer;

    v4l2_buffer     结构如下:

    struct v4l2_buffer {

    __u32                 index;

    enum v4l2_buf_type    type;

    __u32                 bytesused;

    __u32                 flags;

    enum v4l2_field       field;

    struct timeval        timestamp;

    struct v4l2_timecode  timecode;

    __u32                 sequence;

    /* memory location */

    enum v4l2_memory      memory;

    union {

    __u32            offset;

    unsigned long  userptr;

    } m;

    __u32                   length;

    __u32                   input;

    __u32                   reserved;

    };

    VideoBuffer*         buffers = calloc( req.count, sizeof(*buffers) );

    struct v4l2_buffer  buf;

    for (numBufs = 0; numBufs < req.count; numBufs++)

    {

    memset( &buf, 0, sizeof(buf) );

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    buf.index = numBufs;

    // 读取缓存

    if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {

    return -1;

    }

    buffers[numBufs].length = buf.length;

    // 转换成相对地址

    buffers[numBufs].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,

    MAP_SHARED,fd, buf.m.offset);

    if (buffers[numBufs].start == MAP_FAILED) {

    return -1;

    }

    // 放入缓存队列

    if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {

    return -1;

    }

    }

    2.5 视频采集方式

    操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是供内核访问的代码和数据,用户不能直接访问。v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地址。

    一共有三种视频采集方式:使用read/write方式;内存映射方式和用户指针模式。

    read、write方式,在用户空间和内核空间不断拷贝数据,占用了大量用户内存空间,效率不高。

    内存映射方式:把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。上面的mmap函数就是使用这种方式。

    用户指针模式:内存片段由应用程序自己分配。这点需要在v4l2_requestbuffers里将memory字段设置成V4L2_MEMORY_USERPTR。

    2.6 处理采集数据

    V4L2有一个数据缓存,存放req.count数量的缓存数据。数据缓存采用FIFO的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的视频数据缓存送出,并重新采集一张视频数据。这个过程需要用到两个ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF:

    struct v4l2_buffer buf;

    memset(&buf,0,sizeof(buf));

    buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory=V4L2_MEMORY_MMAP;

    buf.index=0;

    //读取缓存

    if (ioctl(cameraFd, VIDIOC_DQBUF, &buf) == -1)

    {

    return -1;

    }

    //…………视频处理算法

    //重新放入缓存队列

    if (ioctl(cameraFd, VIDIOC_QBUF, &buf) == -1) {

    return -1;

    }

    3. 关闭视频设备

    使用close函数关闭一个视频设备

    close(cameraFd)

    如果使用mmap,最后还需要使用munmap方法。

    下面是damo程序(经过实际验证,修改了网上的例程的错误)

    -----------------------------------------------------------------------------------------------------------

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #define CAMERA_DEVICE "/dev/video0"

    #define CAPTURE_FILE "frame.jpg"

    #define VIDEO_WIDTH 640

    #define VIDEO_HEIGHT 480

    #define VIDEO_FORMAT V4L2_PIX_FMT_YUYV

    #define BUFFER_COUNT 4

    typedef struct VideoBuffer {

    void   *start;

    size_t  length;

    } VideoBuffer;

    int main()

    {

    int i, ret;

    // 打开设备

    int fd;

    fd = open(CAMERA_DEVICE, O_RDWR, 0);

    if (fd < 0) {

    printf("Open %s failed\n", CAMERA_DEVICE);

    return -1;

    }

    // 获取驱动信息

    struct v4l2_capability cap;

    ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);

    if (ret < 0) {

    printf("VIDIOC_QUERYCAP failed (%d)\n", ret);

    return ret;

    }

    // Print capability infomations

    printf("Capability Informations:\n");

    printf(" driver: %s\n", cap.driver);

    printf(" card: %s\n", cap.card);

    printf(" bus_info: %s\n", cap.bus_info);

    printf(" version: %08X\n", cap.version);

    printf(" capabilities: %08X\n", cap.capabilities);

    // 设置视频格式

    struct v4l2_format fmt;

    memset(&fmt, 0, sizeof(fmt));

    fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    fmt.fmt.pix.width       = VIDEO_WIDTH;

    fmt.fmt.pix.height      = VIDEO_HEIGHT;

    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;

    fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);

    if (ret < 0) {

    printf("VIDIOC_S_FMT failed (%d)\n", ret);

    return ret;

    }

    // 获取视频格式

    ret = ioctl(fd, VIDIOC_G_FMT, &fmt);

    if (ret < 0) {

    printf("VIDIOC_G_FMT failed (%d)\n", ret);

    return ret;

    }

    // Print Stream Format

    printf("Stream Format Informations:\n");

    printf(" type: %d\n", fmt.type);

    printf(" width: %d\n", fmt.fmt.pix.width);

    printf(" height: %d\n", fmt.fmt.pix.height);

    char fmtstr[8];

    memset(fmtstr, 0, 8);

    memcpy(fmtstr, &fmt.fmt.pix.pixelformat, 4);

    printf(" pixelformat: %s\n", fmtstr);

    printf(" field: %d\n", fmt.fmt.pix.field);

    printf(" bytesperline: %d\n", fmt.fmt.pix.bytesperline);

    printf(" sizeimage: %d\n", fmt.fmt.pix.sizeimage);

    printf(" colorspace: %d\n", fmt.fmt.pix.colorspace);

    printf(" priv: %d\n", fmt.fmt.pix.priv);

    printf(" raw_date: %s\n", fmt.fmt.raw_data);

    // 请求分配内存

    struct v4l2_requestbuffers reqbuf;

    reqbuf.count = BUFFER_COUNT;

    reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    reqbuf.memory = V4L2_MEMORY_MMAP;

    ret = ioctl(fd , VIDIOC_REQBUFS, &reqbuf);

    if(ret < 0) {

    printf("VIDIOC_REQBUFS failed (%d)\n", ret);

    return ret;

    }

    // 获取空间

    VideoBuffer*  buffers = calloc( reqbuf.count, sizeof(*buffers) );

    struct v4l2_buffer buf;

    for (i = 0; i < reqbuf.count; i++)

    {

    buf.index = i;

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    ret = ioctl(fd , VIDIOC_QUERYBUF, &buf);

    if(ret < 0) {

    printf("VIDIOC_QUERYBUF (%d) failed (%d)\n", i, ret);

    return ret;

    }

    // mmap buffer

    framebuf[i].length = buf.length;

    framebuf[i].start = (char *) mmap(0, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf.m.offset);

    if (framebuf[i].start == MAP_FAILED) {

    printf("mmap (%d) failed: %s\n", i, strerror(errno));

    return -1;

    }

    // Queen buffer

    ret = ioctl(fd , VIDIOC_QBUF, &buf);

    if (ret < 0) {

    printf("VIDIOC_QBUF (%d) failed (%d)\n", i, ret);

    return -1;

    }

    printf("Frame buffer %d: address=0x%x, length=%d\n", i, (unsigned int)framebuf[i].start, framebuf[i].length);

    }

    // 开始录制

    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ret = ioctl(fd, VIDIOC_STREAMON, &type);

    if (ret < 0) {

    printf("VIDIOC_STREAMON failed (%d)\n", ret);

    return ret;

    }

    // Get frame

    ret = ioctl(fd, VIDIOC_DQBUF, &buf);

    if (ret < 0) {

    printf("VIDIOC_DQBUF failed (%d)\n", ret);

    return ret;

    }

    // Process the frame

    FILE *fp = fopen(CAPTURE_FILE, "wb");

    if (fp < 0) {

    printf("open frame data file failed\n");

    return -1;

    }

    fwrite(framebuf[buf.index].start, 1, buf.length, fp);

    fclose(fp);

    printf("Capture one frame saved in %s\n", CAPTURE_FILE);

    // Re-queen buffer

    ret = ioctl(fd, VIDIOC_QBUF, &buf);

    if (ret < 0) {

    printf("VIDIOC_QBUF failed (%d)\n", ret);

    return ret;

    }

    // Release the resource

    for (i=0; i< 4; i++)

    {

    munmap(framebuf[i].start, framebuf[i].length);

    }

    close(fd);

    printf("Camera test Done.\n");

    return 0;

    }

    -----------------------------------------------------------------------------------------------------------

    附件:

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

    int munmap(void *start, size_t length);

    参数说明:

    ——start:映射区的开始地址。

    ——length:映射区的长度。

    ——prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

    —PROT_EXEC //页内容可以被执行

    —PROT_READ //页内容可以被读取

    —PROT_WRITE //页可以被写入

    —PROT_NONE //页不可访问

    ——flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

    —MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。

    —MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。

    —MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。

    —MAP_DENYWRITE //这个标志被忽略。

    —MAP_EXECUTABLE //同上

    —MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。

    —MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。

    —MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。

    —MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。

    —MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。

    —MAP_FILE //兼容标志,被忽略。

    —MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。

    —MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。

    —MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

    ——fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。

    ——offset:被映射对象内容的起点。

    返回值:

    成功执行时,mmap()返回被映射区的指针,munmap()返回0。

    失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值。

    展开全文
  • 至于从哪个版本开始内核支持UVC,官方的话是“Linux 2.6.26 and newer includes the Linux UVC driver natively.” 1、查看摄像头ID: [root@151 dev]# lsusb Bus 002 Device 013: ID 0ac8:3313 Z-Star ...

    至于从哪个版本开始内核支持UVC,官方的话是“Linux 2.6.26 and newer includes the Linux UVC driver natively.”

    1、查看摄像头ID:

    [root@151 dev]# lsusb

    Bus 002 Device 013: ID 0ac8:3313 Z-Star Microelectronics Corp.

    0xc8:3313在UVC中支持了。

    2、插入摄像头后,就可以在/dev/下查看是否有video设备文件:

    [root@151 dev]# ls | grep video

    video

    video0

    其中,video是video0的连接。如果没有再现,可能UVC没有配置到内核中,重新配置就可以了

    (大致在Device DriversàMultimedia devicesàVideo capture adaptersàV4L USB devices下面)。如果内核配置了,

    还是不出现,可以使用modprobe uvcvideo来加载该模块。

    3、先看看摄像头的相关信息:

    lshal | grep Cam

    出现:

    info.product = 'Vega USB 2.0 Camera.' (string)

    usb_device.product = 'Vega USB 2.0 Camera.' (string)

    usb.interface.description = 'Vega USB 2.0 Camera.' (string)

    info.product = 'Vega USB 2.0 Camera.' (string)

    input.product = 'Vega USB 2.0 Camera.' (string)

    info.product = 'Vega USB 2.0 Camera.' (string)

    对USB有研究的,就很熟悉这些字段。

    再看一下系统能不能识别出摄像头:

    [root@151 log]# dmesg | grep Cam

    uvcvideo: Found UVC 1.00 device Vega USB 2.0 Camera. (0ac8:3313)

    input: Vega USB 2.0 Camera. as /class/input/input6

    usb 2-7: Product: Vega USB 2.0 Camera.

    uvcvideo: Found UVC 1.00 device Vega USB 2.0 Camera. (0ac8:3313)

    input: Vega USB 2.0 Camera. as /class/input/input7

    usb 2-7: Product: Vega USB 2.0 Camera.

    uvcvideo: Found UVC 1.00 device Vega USB 2.0 Camera. (0ac8:3313)

    input: Vega USB 2.0 Camera. as /class/input/input8

    usb 2-7: Product: Vega USB 2.0 Camera.

    [root@151 ~]# dmesg | grep video

    You can install HAL from the PPA.

    sudo add-apt-repository ppa:mjblenner/ppa-hal
    sudo apt-get update
    sudo apt-get install hal
    

    https://www.binarytides.com/linux-commands-hardware-info/

    转载于:https://www.cnblogs.com/SZLLQ2000/p/8481836.html

    展开全文
  • 现在,假如你的手上有一只摄像头,它是罗技高清网络摄像头webcam-C270,还有一块cortexA8开发板,这块开发板来自FriendlyARM,已经预装了linux系统,版本号是最新提供的linux-3.0.8,图形界面是Qtopia-2.2.0,交

    转载地址(原文地址):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  (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占用的内存是不会被释放的。

    展开全文
  • Linux UVC driver 独立交叉编译记录

    千次阅读 2016-09-06 10:38:59
    官网介绍如下: Typical usage is:git clone git://linuxtv.org/... 这个是针对你当前运行的linux环境的编译。下面介绍下如何针对指定的内核版本进行交叉编译。 1 下载项目源码:git clone git://linuxtv.org/media_b

    前言

    https://linuxtv.org/
    官网介绍如下:

    Typical usage is:

    git clone git://linuxtv.org/media_build.git
    cd media_build
    ./build
    make install
    

    这个是针对你当前运行的linux环境的编译。下面介绍下如何针对指定的内核版本进行交叉编译。

    1.下载项目源码:

    git clone git://linuxtv.org/media_build.git
    cd media_build
    anzyelay@ubuntu:media_build$ ls devel_scripts/
    analyze_build.pl  gen_rename_patch.pl  README  rename_patch.sh
    anzyelay@ubuntu:media_build$ ls linux/
    Makefile  patches_for_kernel.pl  use_dir.pl  version_patch.pl
    anzyelay@ubuntu:media_build$ ls v4l/
    compat.h  Kconfig.misc  Kconfig.sound    Makefile         Makefile.mm     obsolete.txt  versions.txt
    firmware  Kconfig.mm    Kconfig.staging  Makefile.kernel  Makefile.sound  scripts
    

    2.项目文件media_build几点说明

    anzyelay@ubuntu:media_build$ ls
    backports  COPYING        dvb-firmwares.tar.bz2  linux     modules.order   README          v4l
    build      devel_scripts  INSTALL                Makefile  Module.symvers  README.patches
    
    • v4l:编译出来的文件都在此目录下,其中的.c文件都是链接到../linux里的
    • linux:下载和解压出的驱动源代码目录,原文件仅有makefile,和三个脚本文件
      patches_for_kernel.pl检测当前内核所需要的补丁文件,(其实就是读取Backport.txt,只要kernel version<[x.x.xx],就加入到要打的补丁行列)

    • backports:回溯补丁文件夹,简言之就是下载的uvc驱动源码是最新的内核函数,要倒回到旧版本的内核源码就需要反向打补丁了,所以叫了这么个名字.因此看说明(Backport.txt)上解释的是打的补丁要高于或等于当前版本的栏目.

    3.指定内核版本或内核源码目录:

    在此之前需要在你指定的内核源码中设置好交叉环境编译执行一次,然后再执行make modules_prepare生成Module.symvers文件,为外部模块编译做好准备工作,然后后面才能正常编译
    指定方法详情可以make help查看

    • 方法1 :
      make release DIR=内核源码目录
      执行结果如下

      anzyelay@ubuntu:media_build$ make release DIR=/home/anzyelay/Desktop/arm/linux-3.6.6-gcc.4.9.4
      make -C /home/anzyelay/Desktop/media_build/v4l release
      make[1]: Entering directory `/home/anzyelay/Desktop/media_build/v4l'
      Searching in /home/anzyelay/Desktop/arm/linux-3.6.6-gcc.4.9.4/Makefile for kernel version.
      Forcing compiling to version 3.6.6
      make[1]: Leaving directory `/home/anzyelay/Desktop/media_build/v4l'
      
    • 方法2 :
      make release VER=版本号(如:3.5.0-54-generic)
      但这方法要先做好/lib/modules/3.5.0-54-generic/build的指向。
      具体做法:内核源码同样编译执行好,也要make modules_prepare,然后将源码连接到此目录:

      ln -s /path/to/kerne-src /lib/modules/3.5.0-54-generic/build

      如果你直接在编译内核源码后make modules_install了,那就不需要了手动ln了,它会自动完成上述操作。
      指定了源码后,后面在执行makefile时,会引用内核源码顶层的makefile里的变量和规则,如此就可以实现交叉编译了.

    4.下载驱动并编译安装

    4.1 使用默认自动执行

    ./build

    这里包括了下面的步骤:
    1.检查下载最新UVC驱动并解压,
    2.检测指定内核所需要的backport补丁(我直接理解成逆向补丁),并打补丁
    3 执行配置脚本生成默认配置Kconfig, .config和makefile文件等
    4 编译

    注:如果是使用此方法的话make menuconfig不能在执行前做配置,因为当前的v4l下还没有uvc驱动源码,无法生成Kconfig文件,所以要先执行一次再配置才行.

    4.2 手动分步执行

    • 下载uvc驱动:
      • 方法1:手动自行选择版本下载–下载地址
      • 方法2:make download (自行下载最新版)
        新驱动可能有问题,我就遇上了补丁跟源码对应不上的.换了个旧的OK了,所以如果不对可以多试几个不同版本的驱动
    • 解压打补丁
      make untar
      make apply_patches
    • 配置
      make menuconfig

      这里项目代码打补丁后在v4l/Kconfig里有个小错误要解决,见下面的error1,执行make menuconfig 可能会出错.
      注:似乎在media_build目录下做menuconfig配置,有时不起作用,要在你指定的内核源码处修改配置才能起到作用,我重新来了一遍,把内核里的Multimedia support选项关了,在media_build的配置中选上了一样不编译,真奇怪。
      只能说内核里的配置选项会影响到譔项目本身的配置。

    • 编译
      make -jN

      编译成功后不要急,在执行一次make,看看有没有输出警告undefined,有的话要处理不然加载出现 Unknown symbol,

    4.3 安装

    make install

    网上说的使用INSTALL_MOD_PATH我试过没用。它就是默认安装在/lib/modules/kernel-version/下的。似乎项目并没有给我们提供安装路径的控制变量.不信自己可以看安装脚本,还有strip这个也无法更改,要自已手动更换下PC上的链接.不然会出无法识别文件,因为是arm版的文件嘛.

    anzyelay@ubuntu:media_build$ ls /lib/modules/3.6.6/
    kernel             modules.dep          modules.inputmap   modules.seriomap     modules.usbmap
    modules.alias      modules.dep.bin      modules.isapnpmap  modules.softdep
    modules.alias.bin  modules.devname      modules.ofmap      modules.symbols
    modules.ccwmap     modules.ieee1394map  modules.pcimap     modules.symbols.bin

    5 error

    5.1 make menuconfig时出现 :./Kconfig:n: unknown option “xxx”

    具体如下:

        ./Kconfig:519: syntax error
        ./Kconfig:518: unknown option "Say"
        ./Kconfig:519: unknown option "To"
        ./Kconfig:520: unknown option "called"
        ./Kconfig:523: syntax error
        ./Kconfig:522:warning: multi-line strings not supported
        ./Kconfig:522: unknown option "If"
        make[1]: *** [menuconfig] Error 1
        make[1]: Leaving directory `/home/anzyelay/Desktop/media_build/v4l'
        make: *** [menuconfig] Error 2

    这个是./v4l/kconfig这个文件的第518行有点格式问题

     508 # IR_HIX5HD2 disabled for insufficient kernel version
     509 config IR_HIX5HD2
     510     tristate "Hisilicon hix5hd2 IR remote control"
     511     depends on RC_CORE
     512     default n
     513     depends on VIDEO_KERNEL_VERSION
     514     ---help---
     515       WARNING! This driver needs at least kernel 3.10.0!  It may not
     516       compile or work correctly on your kernel, which is too old.
     517 
     518      Say Y here if you want to use hisilicon hix5hd2 remote control.                           
     519      To compile this driver as a module, choose M here: the module will be
     520      called ir-hix5hd2.
     521 
     522      If you're not sure, select N here
     523 
    

    修改下就好了

    5.2 make install 时出现strip: Unable to recognise the format of the input file xxx

    具体如下:

    strip: Unable to recognise the format of the input file `/lib/modules/3.6.6/kernel/drivers/media//cec-edid.ko'
    strip: Unable to recognise the format of the input file `/lib/modules/3.6.6/kernel/drivers/media//media.ko'

    这个是strip要用arm-linux-strip才行。它这里默认是用的Pc版的,所以无法安装,我找了N久也没找到此控制命令的环境变量,不知道如何处理了,我用了最笨的法子,直接暂时把strip的连接指向arm-linux-strip版,安装完好再恢复回来。

    5.3 frame_vector: module license ‘unspecified’ taints kernel.

    frame_vector 缺少 MODULE_LICENSE(“GPL”)申明
    在frame_vector.c文件中加入即可MODULE_LICENSE(“GPL”);

    5.4 加载驱动时出现:Unknown symbol xxx

    在插入摄像头自动加载驱动时出现如下错误:

    videobuf2_vmalloc: Unknown symbol dma_buf_vmap (err 0)
    videobuf2_vmalloc: Unknown symbol dma_buf_export_named (err 0)
    videobuf2_vmalloc: Unknown symbol dma_buf_vunmap (err 0)

    上网查知如下说明:

    > 如果模块在编译过程中出现没有定义的变量(编译过程中显示*.*undefined!)警告时, 这样编译出来的ko在最后插入时候将报以下错误(unknown symbol,can’t insert)
    

    回到项目中再次make时确实出现这些警告。

    WARNING: "dma_buf_vunmap" [/home/anzyelay/Desktop/media_build/v4l/videobuf2-vmalloc.ko] undefined!
    WARNING: "dma_buf_export_named" [/home/anzyelay/Desktop/media_build/v4l/videobuf2-vmalloc.ko] undefined!
    WARNING: "dma_buf_vmap" [/home/anzyelay/Desktop/media_build/v4l/videobuf2-vmalloc.ko] undefined!
    WARNING: "dma_buf_fd" [/home/anzyelay/Desktop/media_build/v4l/videobuf2-core.ko] undefined!
    WARNING: "dma_buf_put" [/home/anzyelay/Desktop/media_build/v4l/videobuf2-core.ko] undefined!
    WARNING: "dma_buf_get" [/home/anzyelay/Desktop/media_build/v4l/videobuf2-core.ko] undefined!
    

    搜索 dma_buf_vunmap发现如下

    ./backports/v3.1_no_dma_buf_h.patch: void *dma_buf_vmap(struct dma_buf *);
    ./backports/v3.1_no_dma_buf_h.patch:+static inline void *dma_buf_vmap(struct dma_buf *dmabuf)
    

    前面说过backports是补丁存放文件夹,既然有补丁,那会不会是没打上补丁呢?看看补丁文件发现是a/include/linux/dma-buf.h文件,直接打开./linux/include/linux/dma-buf.h看了下果然是没有打上补丁,那就手动打吧

    cd linux
    patch -p1 < ../backports/v3.1_no_dma_buf_h.patch

    再次make,警告消失

    5.3 WARNING “xxx” [xx.ko] undefined

    ... ...
    WARNING: "clk_enable" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_disable" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_put" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_get_rate" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_round_rate" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_get" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_set_rate" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "dma_buf_export_named" [/home/anzyelay/Desktop/media_build.pc/v4l/videobuf2-vmalloc.ko] undefined!
    WARNING: "split_page" [/home/anzyelay/Desktop/media_build.pc/v4l/videobuf2-dma-sg.ko] undefined!
    WARNING: "dma_buf_export_named" [/home/anzyelay/Desktop/media_build.pc/v4l/videobuf2-dma-sg.ko] undefined!
    WARNING: "nsecs_to_jiffies" [/home/anzyelay/Desktop/media_build.pc/v4l/gpio-ir-recv.ko] undefined!
    make[2]: Leaving directory `/usr/src/linux-headers-3.5.0-54-generic'
    ./scripts/rmmod.pl check
    found 281 modules
    make[1]: Leaving directory `/home/anzyelay/Desktop/media_build.pc/v4l'
    

    dma_buf_export_named解决如下 :

    anzyelay@ubuntu:media_build.pc$ grep dma_buf_export_named . -r
    ./linux/include/linux/dma-buf.h:struct dma_buf *dma_buf_export_named(void *priv, const struct dma_buf_ops *ops,
    ./linux/include/linux/dma-buf.h:    dma_buf_export_named(priv, ops, size, flags, KBUILD_MODNAME, resv)
    Binary file ./v4l/videobuf2-dma-sg.ko matches
    Binary file ./v4l/videobuf2-vmalloc.ko matches
    Binary file ./v4l/videobuf2-dma-sg.o matches
    Binary file ./v4l/videobuf2-vmalloc.o matches
    ./backports/v4.0_dma_buf_export.patch:+struct dma_buf *dma_buf_export_named(void *priv, const struct dma_buf_ops *ops,
    ./backports/v4.0_dma_buf_export.patch:+ dma_buf_export_named(priv, ops, size, flags, KBUILD_MODNAME, resv)
    anzyelay@ubuntu:media_build.pc$ cd linux/
    anzyelay@ubuntu:linux$ patch -p1 < ../backports/v4.0_dma_buf_export.patch 
    patching file drivers/media/v4l2-core/videobuf2-dma-contig.c
    Reversed (or previously applied) patch detected!  Assume -R? [n] yes
    Hunk #1 succeeded at 389 (offset -13 lines).
    Hunk #2 succeeded at 402 (offset -13 lines).
    patching file drivers/media/v4l2-core/videobuf2-dma-sg.c
    Reversed (or previously applied) patch detected!  Assume -R? [n] yes
    Hunk #1 succeeded at 526 (offset -63 lines).
    patching file drivers/media/v4l2-core/videobuf2-vmalloc.c
    Reversed (or previously applied) patch detected!  Assume -R? [n] yes
    Hunk #1 succeeded at 354 (offset -18 lines).
    patching file include/linux/dma-buf.h
    Reversed (or previously applied) patch detected!  Assume -R? [n] yes
    Hunk #1 succeeded at 219 (offset 11 lines).
    anzyelay@ubuntu:linux$ cd ..
    anzyelay@ubuntu:linux$ make
      ... ...
      MODPOST 281 modules
    WARNING: "clk_enable" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_disable" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_put" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_get_rate" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_round_rate" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_get" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "clk_set_rate" [/home/anzyelay/Desktop/media_build.pc/v4l/videodev.ko] undefined!
    WARNING: "split_page" [/home/anzyelay/Desktop/media_build.pc/v4l/videobuf2-dma-sg.ko] undefined!
    WARNING: "nsecs_to_jiffies" [/home/anzyelay/Desktop/media_build.pc/v4l/gpio-ir-recv.ko] undefined!
    make[2]: Leaving directory `/usr/src/linux-headers-3.5.0-54-generic'
    ./scripts/rmmod.pl check
    found 281 modules
    make[1]: Leaving directory `/home/anzyelay/Desktop/media_build.pc/v4l'

    dma_buf_export_named这个错误是补丁错了,删除原来打的补丁就行了。
    其它的几个clk的错误与补丁没关了,同样grep了下发现有v4l2_clk_enable,怀疑驱动本身做了修改,定位下发现
    ./linux/drivers/media/v4l2-core/v4l2-clk.c 里有所有上述函数,但都有个前缀v4l2_,所以试着更改代码。如下:

    替换代码:

    anzyelay@ubuntu:media_build.pc$ sed -i "s/\<clk_enable/v4l2_clk_enable/g" `grep '\<clk_enable' ./linux/ -rl`
    anzyelay@ubuntu:media_build.pc$ sed -i "s/\<clk_disable/v4l2_clk_disable/g" `grep '\<clk_disable' ./linux/ -rl`
    anzyelay@ubuntu:media_build.pc$ sed -i "s/\<clk_put/v4l2_clk_put/g" `grep '\<clk_put' ./linux/ -rl`
    anzyelay@ubuntu:media_build.pc$ sed -i "s/\<clk_put/v4l2_clk_put/g" `grep '\<clk_put' ./linux/ -rl`
    
    ....

    这里有个错误,那就是把linux/drivers/media/v4l2-core/v4l2-clk.c 里的正常的clk_xxx函数也给变了,在替换前先将此文件移开,换完了在拖回来吧。好吧,试验后错误还是一样的,没效果 。。。怎么办??

    查看了下旧版的文件终于解决了:
    是linux/drivers/media/v4l2-core/v4l2-clk.c这个文件的问题:
    将里面用到clk_xx函数的地方全屏蔽了就OK了。
    需要的可以直接去复制
    https://git.kernel.org/cgit/linux/kernel/git/mchehab/linux-media.git/tree/drivers/media/v4l2-core/v4l2-clk.c?h=media-v3.17-rc6
    真是IMBA.

    展开全文
  • 移植Linux3.4.2版本内核到mini2440(二)--添加网卡、UVC摄像头、LCD驱动 2014-01-01 11:36:45 分类: 嵌入式  移植Linux3.4.2版本内核到mini2440(二)--添加网卡、UVC摄像头、LCD驱动    该...
  • 第二期毕业班视频教程和友善之臂mini2440光盘提供的《Mini2440 Linux移植开发实战指南.pdf》,使用的bootloader是我根据韦东山老师制作的2012.04.01版本u-boot,具体制作方法可参考我的另一篇博客《u-boot-2012.04....
  • 摄像头是否支持uvc

    千次阅读 2014-09-07 13:18:45
    如果你能在http://linux-uvc.berlios.de/...至于从哪个版本开始内核支持UVC,官方的话是“Linux 2.6.26 and newer includes the Linux UVC driver natively.” 1、查看摄像头ID: [root@151 dev]# lsusb
  • 1 UVC枚举过程分析  UVC,全称为:USB video class 或USB video device class。...目前的主流操作系统都已提供UVC设备驱动,如:windows xp sp2、linux 2.4.6、macos10.5,这些系统及之后的版本。所以...
  • 现在,假如你的手上有一只摄像头,它是罗技高清网络摄像头webcam-C270,还有一块cortexA8开发板,这块开发板来自FriendlyARM,已经预装了linux系统,版本号是最新提供的linux-3.0.8,图形界面是Qtopia-2.2.0,交叉...
  • 现在用的是3518EV300,SDK版本LINUX版本的Hi3516E V200R001C01SPC011,不过在开发过程中发现这个版本没有UVC配置的相关文档说明,UVC摄像头是有人已经做出来的,说明这个芯片应该支持UVC功能,没有文档说明就很...
  • V4L2全名是video for linux 2之前还有个老版本v4l,也就是video for linux 1.0版本 V4L2不仅仅用于摄像头,也用于视频输出接口,收音机接口等,完整的框架可以参考这里 基本框架图如下:摘录自 L...
  • [ 移植Linux3.4.2版本内核到mini2440(二)--添加网卡、UVC摄像头、LCD驱动]曾经在全志平台上调试过UVC摄像头,当时调试过程比较流畅,丝毫没有碰上异常情况,这一次在RK上碰到较大的困难,下面介绍一下我的调试过程。...
  • 使用 USB 接口的 UVC 摄像头进行图像采集, 只有配置好驱动, 才能进行图像的采集和帧提取, 在图像采集时, 需要加载 USB 驱动和 V4L2 摄像头驱动。 进入 Device Drivers --> Multimedia support,选择V4L2 sub-...
  • Linux4.4 Android7.1 UVC目录: rk3399/kernel/drivers/media/usb/uvc UVC配置: 1、在内核源码目录下,make menuconfig Device Drivers &gt; Multimedia support &gt; Media USB Adapters 代...
  • 视频监控—uvc驱动框架分析 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3) ...开发环境:Linux-3.4.2内核、arm-linux-gcc-4.3.2工具链、源码分析版本为:Linux-2.6.31.14 源码仓库:https://gitee.com/d_1.
  • 配置摄像头经历的一些操作(这篇文章是刚接触Linux下V4L时作为记录记下的,感觉只有几个命令还有参考作用)一 确定摄像头种类确定是否符合UVC标准协议,一般Linux 2.6.3(好像是,不用在意)以上的版本都在内核集成了。...
  • 基于UVC的罗技摄像头C270 hack

    千次阅读 2013-12-13 16:50:41
    现在,假如你的手上有一只摄像头,它是罗技高清网络摄像头webcam-C270,还有一块cortexA8开发板,这块开发板来自FriendlyARM,已经预装了linux系统,版本号是最新提供的linux-3.0.8,图形界面是Qtopia-2.2.0,交叉...
  • Linux V4L2编程

    2018-10-15 16:41:57
    USB video class(又称为USB video device class or UVC)就是USB device class视频产品在不需要安装任何的驱动程序下即插即用,包括摄像头、数字摄影机、模拟视频转换器、电视卡及静态...最新的UVC版本为UVC 1....
  • 刚接触Linux硬件驱动有关的项目,配置摄像头经历的一些操作 ...确定是否符合UVC标准协议,一般Linux 2.6.3(好像是,不用在意)以上的版本都在内核集成了。 1.lsusb 使用这条命令能看到USB有关设备...
  • 环境:硬件平台:韦东山JZ2440开发板,搭载wm8976音频模块软件平台:VM、Ubuntu9.10、arm-linux-gcc-4.4.3、Linux3.4.2版本源码最近在搞jz2440嵌入式,之前做UVC驱动,由于韦东山教程自带的linux2.6.22内核不支持UVC...
  • 采用的摄像头是现代的,uvc免驱动的,如果想改为单usb摄像头的话只需要稍微修改一下就行了,linux内核版本采用的是2.6.30的,其他版本应该也能用
  • Linux下Camera编程--V4L2

    2018-07-16 13:33:16
    USB video class(又称为USB video device class or UVC)就是USB device class视频产品...最新的UVC版本为UVC 1.5,由USB-IF(USB Implementers Forum)定义包括基本协议及负载格式 [1] [2].这个链接是Li...
  • Using The Imaging Source Video Devices on Linux

    千次阅读 2010-08-13 13:45:00
    驱动:uvc linux版本:ubuntu 7.04 相机型号:THE Imaging Source Europe GmbH DFx 21BU04 Camera ID 199e:8101 在uvc的官方网站上,我是找到了这个id的支持的。可是我装了几个版本uvc一直...
  • USB video class(又称为USB video device class or UVC)就是USB device class视频产品在不需要安装...最新的UVC版本为UVC 1.5,由USB-IF(USB Implementers Forum)定义包括基本协议及负载格式 [1] [2].
  • luvcview源代码linux摄像头视频采集

    热门讨论 2013-03-04 17:51:40
    luvcview此代码已成功应用与ok6410开发板显示罗技c270摄像头视频,并有拍照、录像等等功能。适用于大部分arm开发板和uvc摄像头。其中有嵌入式和pc版本的makefile。
  • 映像源Linux系统信息库 该存储库将为您提供其他资源来控制TIS摄像机。 我们提供什么? gstreamer元素 内省内省 uvc扩展 公用事业 如何与相机互动的示例 文献资料 您可以在此处找到包含的用户文档的在线版本: 依存...
  • 说明:本分析基于AM6C平台Linux3.0.8内核,其他内核版本仅供参考。 本文以Amlogic的红外驱动代码片段为例;对之前内核学习有一个很好的实践: 1.平台总线、设备及驱动部分;《Linux总线、设备与驱动》uvc...

空空如也

空空如也

1 2 3
收藏数 42
精华内容 16
关键字:

linuxuvc版本

linux 订阅