2019年12月4日Linux开发手记

OK,经过昨天对V4L2工作流程的学习,现在已经大体了解了V4L2的工作原理,现在开始对V4L2的API的学习,目标:1、打开摄像头 2、储存图像 3、关闭摄像头,API网址:Linux Media Infrastructure userspace API — The Linux Kernel documentation https://linuxtv.org/downloads/v4l-dvb-apis/media_uapi.html

具体流程如下:

1、打开设备:

static void open_device()

{

dev_name = "/dev/video0";

fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);

}

2、初始化设备:

static void init_device()

介于初始化设备函数过于复杂,这里就直接copy官方例程中的init_device()函数,此处就不多做叙述了,例程:https://blog.csdn.net/snow_rain_1314/article/details/85072669 。在初始化设备时主要完成了对用户空间的获取,图像格式以及I/O模式的确定等,这里我们的I/O模式为内存映射即Memory Mapping(MMAP)模式。

3、捕获图像:

static void start_capturing(void)

{

      unsigned int i;

      enum v4l2_buf_type type;

      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");

}

4、主循环

static void mainloop(void)

    {

          unsigned int count;

 

          count = frame_count;//帧数70

 

          while (count-- > 0) {

                 for (;;) {/*select()机制中提供一fd_set的数据结构,实际上是一long类型的数组,

                           每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,

                           当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件可读*/

                        fd_set fds; 

                          

                        struct timeval tv;//时间值

                        int r;

 

                        FD_ZERO(&fds); /*将set清零使集合中不含任何fd*/

                        FD_SET(fd, &fds);/*将fd加入set集合*/

 

                        /* Timeout. */

                        tv.tv_sec = 2; //2s

                        tv.tv_usec = 0;//0微妙

 

                        r = select(fd + 1, &fds, NULL, NULL, &tv);//select操作,用于确定一个或多个套接口的状态,返回满足条件的套接口的数目,最多只等待两秒,

 

                        if (-1 == r) { //所有描述符集清零

                               if (EINTR == errno)//如果错误类型为4(中断系统呼叫)

                                      continue;

                               errno_exit("select");

                        }

 

                        if (0 == r) { //超时

                               fprintf(stderr, "select timeout\\n");

                               exit(EXIT_FAILURE);

                        }

 

                        if (read_frame())//读取帧

                               break;

                        /* EAGAIN - continue select loop. */

                 }

          }

    }

5、在主循环的过程中读取帧

static int read_frame(void)

    {

   struct v4l2_buffer buf;//v4l2中临时缓冲器

     unsigned int i;

     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:

                             /* Could ignore EIO, see spec. */

                             /* fall through */

                      default:

                             errno_exit("VIDIOC_DQBUF");

                      }

               }

               assert(buf.index < n_buffers);//断言,索引小于缓冲区个数

               process_image(buffers[buf.index].start, buf.bytesused);//进程映射

               if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))

                      errno_exit("VIDIOC_QBUF");

}

6、读取帧时开始存储图像

static void process_image(const void *p, int size)

    {

            FILE *fp = fopen("a.jpg","w");

 

           if (out_buf)

              {

                  fwrite(p, size, 1, fp);

                    fclose(fp);

                  //printf("1");

              }

 

           fflush(stderr);

           fprintf(stderr, "Can not open it!\n");

           fflush(stdout);

    }

7、停止捕获

static void stop_capturing(void)

    {

           enum v4l2_buf_type type;

 

           switch (io) {

           case IO_METHOD_READ:

                  /* Nothing to do. */

                  break;

 

           case IO_METHOD_MMAP:

           case IO_METHOD_USERPTR:

                  type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

                  if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))

                         errno_exit("VIDIOC_STREAMOFF");

                  break;

           }

    }

8、释放内存

static void uninit_device(void)

    {

           unsigned int i;

           switch (io) {

           case IO_METHOD_READ:

                  free(buffers[0].start);

                  break;

           case IO_METHOD_MMAP:

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

                         if (-1 == munmap(buffers[i].start, buffers[i].length))

                                errno_exit("munmap");

                  break;

           case IO_METHOD_USERPTR:

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

                         free(buffers[i].start);

                  break;

           }

           free(buffers);

    }

9、关闭设备

static void close_device(void)

    {

           if (-1 == close(fd))

                  errno_exit("close");

 

           fd = -1;

    }

10、输出异常

fprintf(stderr, "\\n");

 

使用V4L2打开摄像头获取图像的流程就是这样,下一步就是使用python调用这个程序,取代cv2.imwrite。

上一篇:详解百度大脑EdgeBoard出色的视频处理技术


下一篇:找回了当年一篇V4L2 linux 摄像头驱动的博客