流媒体:V4L2视频获取

匿名 (未验证) 提交于 2019-12-03 00:30:01


从深圳回来已经20多天了,除了完善毕业设计程序和论文,其他时间都去玩游戏了。真的是最后的一段时间能够无忧无虑在校园里挥霍自己的青春了。今天完成的答辩,比想象的要简单,一直以来想把我现在的这个流媒体的东西用文字记录下来,但是都去玩了。但是今天开始还是把这些东西都记录下来。其实整个项目最开始接触的是socket编程,用socket写一个很简单的机遇POP3协议的邮件发送程序都觉得沾沾自喜。现在看来但是确实还是很幼稚的。。。

  其实V4L2就是LINUX下的一套API,我刚刚开始接触的时候觉得好难,完全就TMD看不懂啊。。。反正就是各种不靠谱,其实现在看来这些东西不难,其实很简单。只是当时没有决心去做而已。其实大多数的初学者都有我这样的想法,看着这些不熟悉的东西都会很烦躁,沉不住气不想去看。但是事实是多看看多GOOGLE查查基本就能理解了。下面是API的代码解析:

1)打开一个视频设备:

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

  要从摄像头中回去到图像首先当然要打开一个摄像头,在LINUX中对摄像头的操作是对相应的设备文件进行操作实现的。在LINUX的根文件系统中/dev目录有很多设备文件,其中摄像头对应的是viode0,使用open函数打开,O_RDWR表示读写,O_NONBLOCK表示非阻塞,屏蔽掉表示阻塞方式(使用非阻塞方式调用视频设备,即使尚未捕获到视频信息,驱动依旧会把缓存里面的东西返回给应用程序,如果使用阻塞方式调用视频设备,正好相反,在没捕获到视频设备之前,程序会阻塞在OPEN函数所在的位置)open函数返回一个文件描述符fd,以后的程序中就是fd进行操作。

2)ioctl()函数

  具体的ioctl()函数是啥玩意儿我也不知道,百度百科里面说是一种获得设备信息和向设备发送控制参数的手段。那么在我们的代码中就是通过这个函数来和摄像头进行“交互”。比如我要知道这个摄像头是什么型号,有多大的视野,我要获取多大的图像。。。等等,具体设置在后文中会有详细解释。ioctl()函数有3个参数,第一个是前面提到的文件描述符fd,就是我们要操作摄像头。第二个参数是命令,第三个参数是一个结构体,根据第二个参数的不同使用不同的结构体。

a)在这个程序中ioclt函数用到的命令:

VIDIOC_QUERYCAP  //查询设备功能信息

VIDIOC_CROPCAP  //查询驱动的修剪能力

VIDIOC_S_CROP  //设置视频信号的矩形边框

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

VIDIOC_REQBUFS  //分配内存

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

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

VIDIOC_STREAMON  //开始视频流的获取

VIDIOC_STREAMOFF  //结束视频流的获取

VIDIOC_DQBUF  //把数据放回缓存队列

此处可戳这里:http://www.cnblogs.com/xmphoenix/archive/2011/08/20/2147064.html(反正这个V4L2比我写得好)

b)程序中用到的结构体

struct v4l2_capability cap  //返回当前视频设备所支持的功能;

struct v4l2_cropcap cropcap  //设置设备的捕捉能力参数;

struct v4l2_crop crop  //设置窗口的捕捉能力参数;

struct v4l2_format fmt  //设置帧的格式,比如宽度,高度等;

struct v4l2_requestbuffers req  //向驱动申请帧缓冲的请求,里面包含申请的个数;

struct v4l2_buffer buf  //代表驱动中的一帧;

(以上所有的命令和结构体都是在程序中出现的,但是绝对不是完整的,还有很多没有没有一一列举出来)

3)V4L2操作流程

a.打开设备文件:

b.查询视频设备支持的功能:

struct v4l2_capability cap;  ioctl(fd, VIDIOC_QUERYCAP, &cap);

利用ioctl()函数来对打开的设备文件而获得的句柄fd进行读写,第二个参数VIDIOC_QUERYCAP是查询设备属性命令,获取到的设备信息保存到 struct v4l2_capability声明的结构体中,通过判断结构体中capabilities成员的值来判断设备文件是不是支持视频捕捉(V4L2_CAP_VIDEO_CAPTURE)等操作。

c.设置设备和窗口参数:

struct v4l2_cropcap cropcap;  struct v4l2_crop crop;  ioctl(fd, VIDIOC_CROPCAP, &cropcap);  ioctl(fd, VIDIOC_S_CROP, &crop);

同样利用ioctl()函数,VIDIOC_CROPCAP用于查询设备的窗口属性,包括最大窗口左上角坐标和宽高、默认窗口左上角坐标和宽高等属性,根据查询到的值再使用VIDIOC_S_CROP命令和struct v4l2_crop声明的结构体来设置我们的窗口。

d.设置获取帧的格式:

struct v4l2_format fmt;  ioctl(fd, VIDIOC_S_FMT, &fmt);

先是为声明的结构体变量fmt即帧的属性赋值,包括帧类型、宽、高、帧的数据存储类型(YUV\RGB)等,然后是ioctl()函数和对应的命令VIDIOC_S_FMT进行设置。

e.向驱动申请帧缓冲和地址映射:

struct v4l2_requestbuffers req;  struct v4l2_buffer buf;  buffers = calloc(req.count, sizeof(*buffers));  ioctl(fd, VIDIOC_REQBUFS, &req);   ioctl(fd, VIDIOC_QUERYBUF, &buf);  mmap(NULL, // start anywhere                       buf.length,                       PROT_READ | PROT_WRITE,                       MAP_SHARED,                       fd, buf.m.offset);   ioctl(fd, VIDIOC_QBUF, &buf);

首先是用VIDIOC_REQBUFS命令向驱动申请帧(一般不超过5个),在结构体struct v4l2_requestbuffers有我们需要设置的参数,然后在我们的计算机内存中定义帧空间,用VIDIOC_QUERYBUF命令查询设备中的帧信息,并把查询到的信息保存到struct v4l2_buffer定义的结构体中,使用mmap()函数将设备中的帧的缓存地址一一映射成计算机内存中的绝对地址,然后使用VIDIOC_QBUF命令把这些帧放到缓存中,这样我们就可以方便对这些帧进行读取。

enum v4l2_buf_type type;  struct v4l2_buffer queue_buf;  ioctl(fd, VIDIOC_STREAMON, &type);  ioctl(fd, VIDIOC_DQBUF, &queue_buf);  ioctl(fd, VIDIOC_QBUF, &queue_buf);

V4L2有一个数据缓存,存放了前面已经申请数量的缓存数据。数据缓存采用FIFO的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的视频数据送出,并重新回到缓存队列中等待接收数据。这个过程中需要用到两个命令VIDIOC_DQBUF和VIDIOC_QBUF来送去和放入缓存。当然在这之前我们需要使用VIDIOC_STREAMON来打开视频流。

全部代码如下,我现在发现我封装得特别傻逼,但是还是写下来,有时间再改改,文件是video.c

#include "video.h"   static struct buffer * buffers = NULL; static unsigned int n_buffers = 0;  extern findex; extern fd;  int open_device() {        fd = open("/dev/video0", O_RDWR/*|O_NONBLOCK*/, 0);     if(-1 == fd)     {         printf("open error\n");         return -1;     }     return 0; }  int close_device() {     if(-1 == close(fd))     {         printf("close error\n");         return -1;     }     return 0; }  int init_device() {     struct v4l2_capability cap;     struct v4l2_cropcap cropcap;     struct v4l2_crop crop;     struct v4l2_format fmt;      if(-1 == ioctl(fd, VIDIOC_QUERYCAP, &cap))     {         printf("querycap error\n");         return -1;     }     printf("**************************************************\n");     printf("Driver Name:%s\nCard Name:%s\nBus info:%s\nDriver Version:%u.%u.%u\nCapabilities:%u    \n",cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0xff, (cap.version>>8)&0xff,cap.version&0xff,cap.capabilities);     printf("**************************************************\n");      if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))     {         printf("Capture error\n");         return -1;     }      if(!(cap.capabilities & V4L2_CAP_STREAMING))     {         printf("Streaming error\n");         return -1;     }      CLEAR(cropcap);      cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;      if(0 == ioctl(fd, VIDIOC_CROPCAP, &cropcap))     {                  CLEAR(crop);         crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;         crop.c = cropcap.defrect;         if(-1 == ioctl(fd, VIDIOC_S_CROP, &crop))         {             switch (errno)             {                 case EINVAL:                     printf("not support crop\n");             }             printf("can't set VIDIOC_S_CROP\n");             //return -1;         }     }     CLEAR(fmt);      fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;     fmt.fmt.pix.width = 640;     fmt.fmt.pix.height = 480;     fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;     fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;      if(-1 == ioctl(fd, VIDIOC_S_FMT, &fmt))     {         printf("VIDIOC_S_FMT error\n");         return -1;     }     return 0; }  int init_mmap() {     struct v4l2_requestbuffers req;     CLEAR(req);      req.count = 4;     req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;     req.memory = V4L2_MEMORY_MMAP;      if(-1 == ioctl(fd, VIDIOC_REQBUFS, &req))     {                  printf("VIDIOC_REQBUFS\n");             return -1;         }      if(req.count < 2)     {         printf("Insufficient buffer memory\n");         return -1;     }      buffers = calloc(req.count, sizeof(*buffers));      if(!buffers)     {         printf("out of memory\n");         return -1;     }      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 == ioctl(fd, VIDIOC_QUERYBUF, &buf))         {             printf("VIDIOC_QUERYBUF\n");             return -1;         }          buffers[n_buffers].length = buf.length;         buffers[n_buffers].start =                 mmap(NULL, // start anywhere                      buf.length,                      PROT_READ | PROT_WRITE,                      MAP_SHARED,                      fd, buf.m.offset);          if(MAP_FAILED == buffers[n_buffers].start)         {                printf("mmap error\n");             return -1;         }     }     return 0;  }  int start_capturing() {     unsigned int i;     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; //        fprintf(stderr, "n_buffers: %d\n", i);          if(-1 == ioctl(fd, VIDIOC_QBUF, &buf))         {             printf("VIDIOC_QBUF error\n");             return -1;         }     }      enum v4l2_buf_type type;     type = V4L2_BUF_TYPE_VIDEO_CAPTURE;      if(-1 == ioctl(fd, VIDIOC_STREAMON, &type))     {         printf("VIDIOC_STREAMON error\n");         return -1;     }     return 0; }  int stop_capturing() {     enum v4l2_buf_type type;     type = V4L2_BUF_TYPE_VIDEO_CAPTURE;      if(-1 == ioctl(fd, VIDIOC_STREAMOFF, &type))     {         printf(" VIDIOC_STREAMOFF error\n");         return -1;     }     return 0; }  int uninit_mmap() {     unsigned int i;     for(i = 0; i < n_buffers; ++i)     {         if(-1 == munmap(buffers[i].start, buffers[i].length))         {             printf("munmap error\n");             return -1;         }      }     free(buffers);     return 0; }  int get_frame(void **frame_buf, size_t* len) {     struct v4l2_buffer queue_buf;     CLEAR(queue_buf);      queue_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;     queue_buf.memory = V4L2_MEMORY_MMAP;      if(-1 == ioctl(fd, VIDIOC_DQBUF, &queue_buf))     {         printf("VIDIOC_DQBUF error\n");         return -1;     }     printf("run to here\n");     *frame_buf = buffers[queue_buf.index].start;     *len = buffers[queue_buf.index].length;     findex = queue_buf.index;     return 0;  }  int unget_frame() {     if(findex != -1)     {         struct v4l2_buffer queue_buf;         CLEAR(queue_buf);          queue_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;         queue_buf.memory = V4L2_MEMORY_MMAP;         queue_buf.index = findex;          if(-1 == ioctl(fd, VIDIOC_QBUF, &queue_buf))         {             printf("VIDIOC_QBUF error\n");             return -1;         }         return 0;     }     return -1; }

这些代码我忘记当初是在那抄的了!!!!

总结:get_frame的第一个参数是一个双重指针是由于C语言的值传递,在这个函数中获取到了每一帧的开始地址和每一帧的大小,那么就是获取到一帧图像啦!


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!