选自《v4l2研究进展》 作者lapset
简介:本文所附代码是根据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;
- }
- }
- } else {
-
- }
- 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;
- };
4,mainloop
这个模块主要的工作就是你获得数据后如何处理,可以直接存储,也可以实时显示在屏幕上。
- static void mainLoop(void)//main capture control
- {
- unsigned int count;
- count = 1;
- while (count-- > 0) {
- for (;;) {
- fd_set fds;
- struct timeval tv;
- int r;
- FD_ZERO(&fds);
- FD_SET(fd, &fds);
- /* Timeout. */
- tv.tv_sec = 2;
- tv.tv_usec = 0;
- r = select(fd + 1, &fds, NULL, NULL, &tv);
- if (-1 == r) {
- if (EINTR == errno)
- continue;
- errno_exit("select");
- }
- if (0 == r) {
- fprintf (stderr, "select timeout\n");
- exit(EXIT_FAILURE);
- }
- if (frameRead())
- break;
- /* EAGAIN - continue select loop. */
- }
- }
- }
上面的frameRead函数很亮。请看:
- static int frameRead(void)
- {
- struct v4l2_buffer buf;
- CLEAR (buf);
- buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- buf.memory = V4L2_MEMORY_MMAP;
- if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {
- switch (errno) {
- case EAGAIN:
- return 0;
- case EIO:
- default:
- errno_exit("VIDIOC_DQBUF");
- }
- }
- assert(buf.index < n_buffers);//反logic
- imageProcess(buffers[buf.index].start);
- if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
- errno_exit("VIDIOC_QBUF");
- }
上面的imageProcess函数,我采用的是存储的做法。也可以转换为RGB,再转换为jpg
算法如下:
- static void jpegWrite(unsigned char* img)
- {
- struct jpeg_compress_struct cinfo;
- struct jpeg_error_mgr jerr;
- JSAMPROW row_pointer[1];
- //char name[4]={'a','b','c','x'};
- FILE *outfile = fopen( jpegFilename, "wb" );
- // try to open file for saving
- if (!outfile) {
- errno_exit("jpeg");
- }
- // create jpeg data
- cinfo.err = jpeg_std_error( &jerr );
- jpeg_create_compress(&cinfo);
- jpeg_stdio_dest(&cinfo, outfile);
- // set image parameters
- cinfo.image_width = width;
- cinfo.image_height = height;
- cinfo.input_components = 3;
- cinfo.in_color_space = JCS_RGB;
- // set jpeg compression parameters to default
- jpeg_set_defaults(&cinfo);
- // and then adjust quality setting
- jpeg_set_quality(&cinfo, jpegQuality, TRUE);
- // start compress
- jpeg_start_compress(&cinfo, TRUE);
- // feed data
- while (cinfo.next_scanline < cinfo.image_height) {
- row_pointer[0] = &img[cinfo.next_scanline * cinfo.image_width *
- cinfo.input_components];
- jpeg_write_scanlines(&cinfo, row_pointer, 1);
- }
- // finish compression
- jpeg_finish_compress(&cinfo);
- // destroy jpeg data
- jpeg_destroy_compress(&cinfo);
- // close output file
- fclose(outfile);
- }
- /**
- process image read
- */
- static void imageProcess(const void* p)
- {
- unsigned char* src = (unsigned char*)p;
- unsigned char* dst = malloc(width*height*3*sizeof(char));
- // convert from YUV422 to RGB888
- YUV422toRGB888(width,height,src,dst);
- // write jpeg
- jpegWrite(dst);
- }
- static void YUV422toRGB888(int width, int height, unsigned char *src, unsigned char
- *dst)
- {
- int line, column;
- unsigned char *py, *pu, *pv;
- unsigned char *tmp = dst;
- /* In this format each four bytes is two pixels. Each four bytes is two Y's, a Cb
- and a Cr.
- Each Y goes to one of the pixels, and the Cb and Cr belong to both pixels. */
- py = src;
- pu = src + 1;
- pv = src + 3;
- #define CLIP(x) ( (x)>=0xFF ? 0xFF : ( (x) <= 0x00 ? 0x00 : (x) ) )
- for (line = 0; line < height; ++line) {
- for (column = 0; column < width; ++column) {
- *tmp++ = CLIP((double)*py + 1.402*((double)*pv-128.0));
- *tmp++ = CLIP((double)*py - 0.344*((double)*pu-128.0) - 0.714*((double)*pv-
- 128.0));
- *tmp++ = CLIP((double)*py + 1.772*((double)*pu-128.0));
- // increase py every time
- py += 2;
- // increase pu,pv every second time
- if ((column & 1)==1) {
- pu += 4;
- pv += 4;
- }
- }
- }
- }
5,关闭捕捉数据流代码,重点在与ioctl()函数的参数:VIDIOC_STREAMOFF,这个和capture_start中的VIDIOC_STREAMON成对使用。
- static void captureStop(void)
- {
- enum v4l2_buf_type type;
- type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
- errno_exit("VIDIOC_STREAMOFF");
- }
6, 设备关闭相关
- static void deviceUninit(void)
- {
- unsigned int i;
- for (i = 0; i < n_buffers; ++i)
- if (-1 == munmap (buffers[i].start, buffers[i].length))
- errno_exit("munmap");
- for (i = 0; i < n_buffers; ++i)
- free (buffers[i].start);
-
- free(buffers);
- }
7,关闭设备文件
- static void deviceClose(void)
- {
- if (-1 == close (fd))
- errno_exit("close");
- fd = -1;
- }
阅读(3083) | 评论(1) | 转发(3) |