V4L2编程模型简介(一)


http://www.embedu.org/Column/Column514.htm


作者:邹南,华清远见嵌入式学院讲师。


简介:本文所附代码是根据v4l2官方文档以及demo(capture.c)修改而来,纯粹为学习交流之用,请勿使用在商用场合。


地址:由于官方网的域名有敏感词汇,所以请google一下。


一 、操作流程简单看



二、 模块概要分析


以下是所附代码所涉及到的全局变量,摆出来只是参考,具体修改的话请自行安排。


#define CLEAR(x) memset (&(x), 0, sizeof (x))
        typedef enum {


#ifdef IO_READ
                IO_METHOD_READ,
        #endif
        #ifdef IO_MMAP
                IO_METHOD_MMAP,
        #endif
        #ifdef IO_USERPTR
                IO_METHOD_USERPTR,
        #endif
                } io_method;


struct buffer {
        void * start;
        size_t length;
        };


static io_method io = IO_METHOD_MMAP;
        static int fd = -1;
        struct buffer * buffers = NULL;
        static unsigned int n_buffers = 0;
        // global settings
        static unsigned int width = 640;
        static unsigned int height = 480;
        static unsigned char jpegQuality = 70;
        static char* jpegFilename = NULL;
        static char* deviceName = "/dev/video0";


1.deviceOpen


主要就是打开你的设备文件,一般情况下就是,/dev/vedio0 取决于你的设备数量。前面提到的stat这个结构体主要是记录了文件的基本信息。通过这一点来校验文件的打开权限。


2.deviceInit


这个模块稍微复杂些,它主要是使用了v4l2中定义的4种相关的数据结构。以下列出每种结构的具体属性。
        struct v4l2_cropcap {
                enum v4l2_buf_type type;
                struct v4l2_rect bounds;
                struct v4l2_rect defrect;
                struct v4l2_fract pixelaspect;
        };
        struct v4l2_crop {
                 enum v4l2_buf_type type;
                struct v4l2_rect c;
        };
        struct v4l2_capability {
                 __u8 driver[16]; /* i.e. "bttv" */
                __u8 card[32]; /* i.e. "Hauppauge WinTV" */
                __u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
                __u32 version; /* should use KERNEL_VERSION() */
                __u32 capabilities; /* Device capabilities */
                __u32 reserved[4];
        };
        struct v4l2_format {
                enum v4l2_buf_type type;
                union {
                        struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
                        struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
                        struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
                        struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
                         __u8 raw_data[200]; /* user-defined */
                } fmt;
        };


这里不得不提醒一点,通常usb摄像头驱动,都会提供3种不同的数据传输方式,1,read IO 2,mmap内存映射 3,USERPTR(这一种是测试方法,具体可以去查询)


本文暂且只讨论常见的操作方法,即mmap内存映射方式.


通过一段时间的学习,才知道为什么只支持mmap,其实是我们所用的去架构是基于uvc.在uvc架构中,是不支持read/write io mode 以及用户扩展模式。
        static void deviceInit(void)
        {
                struct v4l2_capability cap;
                struct v4l2_cropcap cropcap;
                struct v4l2_crop crop;
                struct v4l2_format fmt;
                unsigned int min;
                if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { //get the capab info about
                if (EINVAL == errno) {
                fprintf(stderr, "%s is no V4L2 device\n",deviceName);
                exit(EXIT_FAILURE);
                } else {
                errno_exit("VIDIOC_QUERYCAP");
                }
        }
        if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { //check is it support capture mode ?
                fprintf(stderr, "%s is no video capture device\n",deviceName);
                exit(EXIT_FAILURE);
        }
        if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
                fprintf(stderr, "%s does not support streaming i/o\n",deviceName);
                exit(EXIT_FAILURE);
        }
        /* Select video input, video standard and tune here. */
        CLEAR(cropcap);// init -0 it is a initialize func about set 0 to parameter
        cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) {
                crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                crop.c = cropcap.defrect; /* reset to default */
                 if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) {
                        switch (errno) {
                        case EINVAL:
                        /* Cropping not supported. */
                        break;
                        default:
                         /* Errors ignored. */
                        break;
                        }
                }
        }
        CLEAR (fmt);
        // v4l2_format
        fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //mode is capture
        fmt.fmt.pix.width = width; //define pixee width
        fmt.fmt.pix.height = height; //define pixel height
        fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //define pixel format
        fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
        if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) //set fmt
        errno_exit("VIDIOC_S_FMT");
        /* Note VIDIOC_S_FMT may change width and height. */
         if (width != fmt.fmt.pix.width) {
                width = fmt.fmt.pix.width;
                fprintf(stderr,"Image width set to %i by device %s.\n",width,deviceName);
                }
        if (height != fmt.fmt.pix.height) {
                height = fmt.fmt.pix.height;
                fprintf(stderr,"Image height set to %i by device %s.\n",height,deviceName);
                }
        /* Buggy driver paranoia. */
        min = fmt.fmt.pix.width * 2;
        if (fmt.fmt.pix.bytesperline < min)
        fmt.fmt.pix.bytesperline = min;
        min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
         if (fmt.fmt.pix.sizeimage < min)
        fmt.fmt.pix.sizeimage = min;
        //this function is important to init mmap pre_work
        mmapInit();
        }


可以看到上面主要是初始化工作,具体的参数意义,请参看v4l2的specification 。


static void mmapInit(void)
        {
                struct v4l2_requestbuffers req;//apply for frame buffer
                CLEAR (req);
                req.count = 4;
                req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                req.memory = V4L2_MEMORY_MMAP;
                if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {
                        if (EINVAL == errno) {
                                fprintf(stderr, "%s does not support memory mapping\n", deviceName);
                                exit(EXIT_FAILURE);
                        } else {
                errno_exit("VIDIOC_REQBUFS");
                }
        }
        if (req.count < 2) {
                fprintf(stderr, "Insufficient buffer memory on %s\n", deviceName);
                exit(EXIT_FAILURE);
         }
         buffers = calloc(req.count, sizeof(*buffers));
        if (!buffers) {
                fprintf(stderr, "Out of memory\n");
                exit(EXIT_FAILURE);
        }
        for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
                struct v4l2_buffer buf;
                CLEAR (buf);
                buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buf.memory = V4L2_MEMORY_MMAP;
                buf.index = n_buffers;
                if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
                errno_exit("VIDIOC_QUERYBUF");
                buffers[n_buffers].length = buf.length;
                buffers[n_buffers].start =
                mmap (NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, fd, buf.m.offset);
                if (MAP_FAILED == buffers[n_buffers].start)
                errno_exit("mmap");
                }
        }


3.capture_start


初始化以后就可以进行正题了,就是所谓的capture data.不过在此之前,应该打开数据流通道,重点在于最后那个ioctl函数的参数:VIDIOC_STREAMON


static void captureStart(void) //grap after initialize
        {
                unsigned int i;
                enum v4l2_buf_type type; //page-68
                #ifdef IO_MMAP
                for (i = 0; i < n_buffers; ++i) {
                        struct v4l2_buffer buf;
                        CLEAR (buf);
                        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                        buf.memory = V4L2_MEMORY_MMAP;
                         buf.index = i;
                        if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
                        errno_exit("VIDIOC_QBUF");
                        }
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
        errno_exit("VIDIOC_STREAMON");
        #endif


上面出现的两个结构体的分别定义如下:
        enum v4l2_buf_type {
        V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
        V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
        V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
        V4L2_BUF_TYPE_VBI_CAPTURE = 4,
        V4L2_BUF_TYPE_VBI_OUTPUT = 5,
        V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,
        V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,
        #if 1
        /* Experimental */
        V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
        #endif
        V4L2_BUF_TYPE_PRIVATE = 0x80,
        };
         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;
        };

創作者介紹
創作者 立你斯 的頭像
立你斯

立你斯學習記錄

立你斯 發表在 痞客邦 留言(0) 人氣()