Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1301243
  • 博文数量: 548
  • 博客积分: 7597
  • 博客等级: 少将
  • 技术积分: 4224
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-15 13:21
个人简介

嵌入式软件工程师&&太极拳

文章分类

全部博文(548)

文章存档

2014年(10)

2013年(76)

2012年(175)

2011年(287)

分类:

2011-12-27 16:20:32

原文地址:v4l2驱动浅析 作者:lapset

选自《v4l2研究进展》 作者lapset
简介:本文所附代码是根据v4l2官方文档以及demo(capture.c)修改而来,纯粹为学习交流之用,请勿使用在商用场合。
地址:由于官方网的域名有敏感词汇,所以请google一下。
一 ,操作流程简单看
 
 
 
二 模块概要分析
 
以下是所附代码所涉及到的全局变量,摆出来只是参考,具体修改的话请自行安排。
  1. #define CLEAR(x) memset (&(x), 0, sizeof (x))

  2. typedef enum {
  3. #ifdef IO_READ
  4.         IO_METHOD_READ,
  5. #endif
  6. #ifdef IO_MMAP
  7.         IO_METHOD_MMAP,
  8. #endif
  9. #ifdef IO_USERPTR
  10.         IO_METHOD_USERPTR,
  11. #endif
  12. } io_method;

  13. struct buffer {
  14.         void * start;
  15.         size_t length;
  16. };

  17. static io_method io = IO_METHOD_MMAP;
  18. static int fd = -1;
  19. struct buffer * buffers = NULL;
  20. static unsigned int n_buffers = 0;

  21. // global settings

  22. static unsigned int width = 640;
  23. static unsigned int height = 480;
  24. static unsigned char jpegQuality = 70;
  25. static char* jpegFilename = NULL;
  26. static char* deviceName = "/dev/video0";
1,deviceOpen
   主要就是打开你的设备文件,一般情况下就是,/dev/vedio0 取决于你的设备数量。前面提到的stat这个结构体主要是记录了文件的基本信息。通过这一点来校验文件的打开权限。
 
2,deviceInit
   这个模块稍微复杂些,它主要是使用了v4l2中定义的4种相关的数据结构。以下列出每种结构的具体属性。
  
  1. struct v4l2_cropcap {

  2. enum v4l2_buf_type type;

  3.        struct v4l2_rect bounds;

  4. struct v4l2_rect defrect;

  5. struct v4l2_fract pixelaspect;

  6. };





  7. struct v4l2_crop {

  8. enum v4l2_buf_type type;

  9. struct v4l2_rect c;

  10. };





  11. struct v4l2_capability {

  12. __u8 driver[16]; /* i.e. "bttv" */

  13. __u8 card[32]; /* i.e. "Hauppauge WinTV" */

  14. __u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */

  15. __u32 version; /* should use KERNEL_VERSION() */

  16. __u32 capabilities; /* Device capabilities */

  17. __u32 reserved[4];

  18. };





  19. struct v4l2_format {

  20. enum v4l2_buf_type type;

  21. union {

  22. struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */

  23. struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */

  24. struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */

  25. struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */

  26. __u8 raw_data[200]; /* user-defined */

  27. } fmt;

  28. };
这里不得不提醒一点,通常usb摄像头驱动,都会提供3种不同的数据传输方式,1,read IO 2,mmap内存映射 3,USERPTR(这一种是测试方法,具体可以去查询)
本文暂且只讨论常见的操作方法,即mmap内存映射方式.
通过一段时间的学习,才知道为什么只支持mmap,其实是我们所用的去架构是基于uvc.在uvc架构中,是不支持read/write io mode 以及用户扩展模式。
 
 
 
 
  1. static void deviceInit(void)
  2. {
  3.   struct v4l2_capability cap;
  4.   struct v4l2_cropcap cropcap; 
  5.   struct v4l2_crop crop;
  6.   struct v4l2_format fmt;
  7.   unsigned int min;

  8.   if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { //get the capab info about

  9.     if (EINVAL == errno) {
  10.       fprintf(stderr, "%s is no V4L2 device\n",deviceName);
  11.       exit(EXIT_FAILURE);
  12.     } else {
  13.       errno_exit("VIDIOC_QUERYCAP");
  14.     }
  15.   }

  16.   if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { //check is it support capture mode ?

  17.     fprintf(stderr, "%s is no video capture device\n",deviceName);
  18.     exit(EXIT_FAILURE);
  19.   }

  20.   
  21.      if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
  22.         fprintf(stderr, "%s does not support streaming i/o\n",deviceName);
  23.         exit(EXIT_FAILURE);
  24.       }


  25.   /* Select video input, video standard and tune here. */
  26.   CLEAR(cropcap);// init -0    it is a initialize func about set 0 to parameter  


  27.   cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  28.   if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) {
  29.     crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  30.     crop.c = cropcap.defrect; /* reset to default */

  31.     if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) {
  32.       switch (errno) {
  33.         case EINVAL:
  34.           /* Cropping not supported. */
  35.           break;
  36.         default:
  37.           /* Errors ignored. */
  38.           break;
  39.       }
  40.     }
  41.   } else {
  42.   
  43.   }

  44.   CLEAR (fmt);

  45.   // v4l2_format

  46.   fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;        //mode is capture
  47.   fmt.fmt.pix.width = width;                     //define pixee width
  48.   fmt.fmt.pix.height = height;                   //define pixel height
  49.   fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;   //define pixel format
  50.   fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;      

  51.   if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))       //set fmt
  52.     errno_exit("VIDIOC_S_FMT");

  53.   /* Note VIDIOC_S_FMT may change width and height. */
  54.   if (width != fmt.fmt.pix.width) {
  55.     width = fmt.fmt.pix.width;
  56.     fprintf(stderr,"Image width set to %i by device %s.\n",width,deviceName);
  57.   }
  58.   if (height != fmt.fmt.pix.height) {
  59.     height = fmt.fmt.pix.height;
  60.     fprintf(stderr,"Image height set to %i by device %s.\n",height,deviceName);
  61.   }

  62.   /* Buggy driver paranoia. */
  63.   min = fmt.fmt.pix.width * 2;
  64.   if (fmt.fmt.pix.bytesperline < min)
  65.     fmt.fmt.pix.bytesperline = min;
  66.   min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
  67.   if (fmt.fmt.pix.sizeimage < min)
  68.     fmt.fmt.pix.sizeimage = min;

  69.   
  70.   //this function is important to init mmap pre_work
  71.   mmapInit();
  72. }
 
可以看到上面主要是初始化工作,具体的参数意义,请参看v4l2的specification 。
 
  1. static void mmapInit(void)
  2. {
  3.   struct v4l2_requestbuffers req;//apply for frame buffer


  4.   CLEAR (req);

  5.   req.count = 4;
  6.   req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  7.   req.memory = V4L2_MEMORY_MMAP;

  8.   if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {
  9.     if (EINVAL == errno) {
  10.       fprintf(stderr, "%s does not support memory mapping\n", deviceName);
  11.       exit(EXIT_FAILURE);
  12.     } else {
  13.       errno_exit("VIDIOC_REQBUFS");
  14.     }
  15.   }

  16.   if (req.count < 2) {
  17.     fprintf(stderr, "Insufficient buffer memory on %s\n", deviceName);
  18.     exit(EXIT_FAILURE);
  19.   }

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

  21.   if (!buffers) {
  22.     fprintf(stderr, "Out of memory\n");
  23.     exit(EXIT_FAILURE);
  24.   }

  25.   for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
  26.     struct v4l2_buffer buf;

  27.     CLEAR (buf);

  28.     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  29.     buf.memory = V4L2_MEMORY_MMAP;
  30.     buf.index = n_buffers;

  31.     if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
  32.       errno_exit("VIDIOC_QUERYBUF");

  33.     buffers[n_buffers].length = buf.length;
  34.     buffers[n_buffers].start =
  35.     mmap (NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, fd, buf.m.offset);

  36.     if (MAP_FAILED == buffers[n_buffers].start)
  37.       errno_exit("mmap");
  38.   }
  39. }
 
 
3,capture_start
初始化以后就可以进行正题了,就是所谓的capture data.不过在此之前,应该打开数据流通道,重点在于最后那个ioctl函数的参数:VIDIOC_STREAMON
 
  1. static void captureStart(void) //grap after initialize

  2. {
  3.   unsigned int i;
  4.   enum v4l2_buf_type type; //page-68


  5.  #ifdef IO_MMAP
  6.     
  7.       for (i = 0; i < n_buffers; ++i) {
  8.         struct v4l2_buffer buf;

  9.         CLEAR (buf);

  10.         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  11.         buf.memory = V4L2_MEMORY_MMAP;
  12.         buf.index = i;

  13.         if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
  14.           errno_exit("VIDIOC_QBUF");
  15.       }

  16.       type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  17.       if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
  18.         errno_exit("VIDIOC_STREAMON");

  19.        #endif
上面出现的两个结构体的分别定义如下:
 
  1. enum v4l2_buf_type {
  2.     V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
  3.     V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
  4.     V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
  5.     V4L2_BUF_TYPE_VBI_CAPTURE = 4,
  6.     V4L2_BUF_TYPE_VBI_OUTPUT = 5,
  7.     V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,
  8.     V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,
  9. #if 1
  10.     /* Experimental */
  11.     V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,
  12. #endif
  13.     V4L2_BUF_TYPE_PRIVATE = 0x80,
  14. };
  1. struct v4l2_buffer {
  2.     __u32            index;
  3.     enum v4l2_buf_type type;
  4.     __u32            bytesused;
  5.     __u32            flags;
  6.     enum v4l2_field        field;
  7.     struct timeval        timestamp;
  8.     struct v4l2_timecode    timecode;
  9.     __u32            sequence;

  10.     /* memory location */
  11.     enum v4l2_memory memory;
  12.     union {
  13.         __u32 offset;
  14.         unsigned long userptr;
  15.     } m;
  16.     __u32            length;
  17.     __u32            input;
  18.     __u32            reserved;
  19. };
 
 
4,mainloop
  这个模块主要的工作就是你获得数据后如何处理,可以直接存储,也可以实时显示在屏幕上。
  1. static void mainLoop(void)//main capture control

  2. {
  3.   unsigned int count;

  4.   count = 1;

  5.   while (count-- > 0) {
  6.     for (;;) {
  7.       fd_set fds;
  8.       struct timeval tv;
  9.       int r;

  10.       FD_ZERO(&fds);
  11.       FD_SET(fd, &fds);

  12.       /* Timeout. */
  13.       tv.tv_sec = 2;
  14.       tv.tv_usec = 0;

  15.       r = select(fd + 1, &fds, NULL, NULL, &tv);

  16.       if (-1 == r) {
  17.         if (EINTR == errno)
  18.           continue;

  19.         errno_exit("select");
  20.       }

  21.       if (0 == r) {
  22.         fprintf (stderr, "select timeout\n");
  23.         exit(EXIT_FAILURE);
  24.       }

  25.       if (frameRead())
  26.         break;

  27.       /* EAGAIN - continue select loop. */
  28.     }
  29.   }
  30. }
 上面的frameRead函数很亮。请看:
  1. static int frameRead(void)
  2. {
  3.   struct v4l2_buffer buf;

  4.       CLEAR (buf);
  5.       buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  6.       buf.memory = V4L2_MEMORY_MMAP;
  7.      if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {
  8.         switch (errno) {
  9.           case EAGAIN:
  10.             return 0;
  11.           case EIO:
  12.           default:
  13.             errno_exit("VIDIOC_DQBUF");
  14.         }
  15.       }

  16.        assert(buf.index < n_buffers);//反logic
  17.        imageProcess(buffers[buf.index].start);

  18.        if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
  19.         errno_exit("VIDIOC_QBUF");

  20. }
上面的imageProcess函数,我采用的是存储的做法。也可以转换为RGB,再转换为jpg
算法如下:
 
  1. static void jpegWrite(unsigned char* img)
  2. {
  3.   struct jpeg_compress_struct cinfo;
  4.   struct jpeg_error_mgr jerr;

  5.   JSAMPROW row_pointer[1];
  6.   //char name[4]={'a','b','c','x'};



  7.   FILE *outfile = fopen( jpegFilename, "wb" );

  8.   // try to open file for saving

  9.   if (!outfile) {
  10.     errno_exit("jpeg");
  11.   }

  12.   // create jpeg data

  13.   cinfo.err = jpeg_std_error( &jerr );
  14.   jpeg_create_compress(&cinfo);
  15.   jpeg_stdio_dest(&cinfo, outfile);

  16.   // set image parameters

  17.   cinfo.image_width = width;
  18.   cinfo.image_height = height;
  19.   cinfo.input_components = 3;
  20.   cinfo.in_color_space = JCS_RGB;

  21.   // set jpeg compression parameters to default

  22.   jpeg_set_defaults(&cinfo);
  23.   // and then adjust quality setting

  24.   jpeg_set_quality(&cinfo, jpegQuality, TRUE);

  25.   // start compress

  26.   jpeg_start_compress(&cinfo, TRUE);

  27.   // feed data

  28.   while (cinfo.next_scanline < cinfo.image_height) {
  29.     row_pointer[0] = &img[cinfo.next_scanline * cinfo.image_width *

  30. cinfo.input_components];
  31.     jpeg_write_scanlines(&cinfo, row_pointer, 1);
  32.   }

  33.   // finish compression

  34.   jpeg_finish_compress(&cinfo);

  35.   // destroy jpeg data

  36.   jpeg_destroy_compress(&cinfo);

  37.   // close output file

  38.   fclose(outfile);
  39. }

  40. /**
  41.   process image read
  42. */
  43. static void imageProcess(const void* p)
  44. {
  45.   unsigned char* src = (unsigned char*)p;
  46.   unsigned char* dst = malloc(width*height*3*sizeof(char));

  47.   // convert from YUV422 to RGB888

  48.   YUV422toRGB888(width,height,src,dst);

  49.   // write jpeg

  50.   jpegWrite(dst);
  51. }


  52. static void YUV422toRGB888(int width, int height, unsigned char *src, unsigned char

  53. *dst)
  54. {
  55.   int line, column;
  56.   unsigned char *py, *pu, *pv;
  57.   unsigned char *tmp = dst;

  58.   /* In this format each four bytes is two pixels. Each four bytes is two Y's, a Cb

  59. and a Cr.
  60.      Each Y goes to one of the pixels, and the Cb and Cr belong to both pixels. */
  61.   py = src;
  62.   pu = src + 1;
  63.   pv = src + 3;

  64.   #define CLIP(x) ( (x)>=0xFF ? 0xFF : ( (x) <= 0x00 ? 0x00 : (x) ) )

  65.   for (line = 0; line < height; ++line) {
  66.     for (column = 0; column < width; ++column) {
  67.       *tmp++ = CLIP((double)*py + 1.402*((double)*pv-128.0));
  68.       *tmp++ = CLIP((double)*py - 0.344*((double)*pu-128.0) - 0.714*((double)*pv-

  69. 128.0));
  70.       *tmp++ = CLIP((double)*py + 1.772*((double)*pu-128.0));

  71.       // increase py every time

  72.       py += 2;
  73.       // increase pu,pv every second time

  74.       if ((column & 1)==1) {
  75.         pu += 4;
  76.         pv += 4;
  77.       }
  78.     }
  79.   }
  80. }
 
 
5,关闭捕捉数据流代码,重点在与ioctl()函数的参数:VIDIOC_STREAMOFF,这个和capture_start中的VIDIOC_STREAMON成对使用。
 
  1. static void captureStop(void)
  2. {
  3.   enum v4l2_buf_type type;

  4.       type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  5.       if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
  6.         errno_exit("VIDIOC_STREAMOFF");
  7.  }
6, 设备关闭相关
 
  1. static void deviceUninit(void)
  2. {
  3.   unsigned int i;
  4.       for (i = 0; i < n_buffers; ++i)
  5.       if (-1 == munmap (buffers[i].start, buffers[i].length))
  6.         errno_exit("munmap");
  7.          for (i = 0; i < n_buffers; ++i)
  8.         free (buffers[i].start);
  9.       
  10.   free(buffers);
  11. }
 
 
7,关闭设备文件
  1. static void deviceClose(void)
  2. {
  3.   if (-1 == close (fd))
  4.     errno_exit("close");

  5.   fd = -1;
  6. }
阅读(603) | 评论(0) | 转发(0) |
0

上一篇:什么是 CMNET?

下一篇:NTSC和PAL的区别详解

给主人留下些什么吧!~~