Skypx 观心知天下, 不露亦锋芒

v4l2视频采集框架

2019-09-03

最近在做视频采集方面的工作, 对V4L2稍微研究了下,写下一些自己的心得和体会,同时做一下总结, 如果有那方面写错的话请多多指教

什么是V4L2(这篇文章只是针对Android设备)

我们都知道硬件千千万万,比如有A厂家的摄像头,B厂家的摄像头,那对应得就有A厂家摄像头驱动,B厂家
摄像头的驱动,那HAL就得通过A厂家驱动获取数据,B厂家驱动获取数据.那有无数厂家了?所以就抽象出
来了一套驱动框架,不管你那个厂家的我摄像头,上面只用调接口,通过驱动框架来获取数据.

官方解释:

V4L2

下面的是总结的白话: Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。
凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。
就像公司的老板一般都不会直接找底层的员工谈话,而是找部门经理了解情况,一个是因为底层屌丝人数多,
意见各有不同,措辞也不准,部门经理会把情况汇总后再向上汇报;二个是老板时间宝贵。

camera中运用的框架是是这样的: 位于HAL下层, 以后我会分析v4l2在HAL层的运用获取数据

avatar

至此我们大概明白了V4L2大概是做什么的.

怎么用呢?

思考:: 假如我不通过V4L2那么我怎么获取camera数据,并且把数据保存下来,视频应该获取不了,因为
视频会在HAL里面把一帧一帧数据传送给codec,然后经过编解码,才生成视频,我们通过V4l2能获取到
一帧一帧数据,并且保存为图片,其实HAL的数据就是通过v4l2拿到原始数据的,然后HAL层在经过算法
等的处理,返回给cameraservice,然后返回给App,App在去保存。所以就生成了图片

一个大致的流程类似这样的 avatar

  1. open device 去open 驱动节点的位置, 因为会v4l2的驱动节点会挂载在/dev/video0下面
    fd = open(deviceName, O_RDWR | O_NONBLOCK, 0);
    
  2. init_device
    struct v4l2_capability cap; //这个设备的功能,比如是否是视频输入设备
     struct v4l2_cropcap cropcap; //裁剪能力
     struct v4l2_crop crop; // 裁剪
     struct v4l2_format fmt;  // 设置视频捕获格式
     struct v4l2_input input; //视频输入
    
     一些参数的意思
     // VIDIOC_REQBUFS:分配内存
     // VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
     // VIDIOC_QUERYCAP:查询驱动功能
     // VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
     // VIDIOC_S_FMT:设置当前驱动的频捕获格式
     // VIDIOC_G_FMT:读取当前驱动的频捕获格式
     // VIDIOC_TRY_FMT:验证当前驱动的显示格式
     // VIDIOC_CROPCAP:查询驱动的修剪能力
     // VIDIOC_S_CROP:设置视频信号的边框
     // VIDIOC_G_CROP:读取视频信号的边框
     // VIDIOC_QBUF:把数据从缓存中读取出来
     // VIDIOC_DQBUF:把数据放回缓存队列
     // VIDIOC_STREAMON:开始视频显示函数
     // VIDIOC_STREAMOFF:结束视频显示函数
     // VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。
    
     //查询能力
     if (-1 == px_ioctrl(fd, VIDIOC_QUERYCAP, &cap)){
         SKY_LOG("VIDIOC_QUERYCAP ERROR  ");
     }
    
     if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
         SKY_LOG("can't capture for V4L2_CAP_VIDEO_CAPTURE");
     }
    
     if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
         SKY_LOG("V4L2_CAP_STREAMING error");
     }
    
     //把结构体所占的内存全部初始化为0, 方便以后的赋值操作
     //这个地方可以定义一个宏函数暂时先这样
     memset(&input, 0x00, sizeof(struct v4l2_input));
     memset(&cropcap, 0x00, sizeof(struct v4l2_cropcap));
     memset(&fmt, 0, sizeof(struct v4l2_format));
    
     fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     fmt.fmt.pix.width       = cap_rect.width;
     fmt.fmt.pix.height      = cap_rect.height;
     fmt.fmt.pix.pixelformat = CAPTURE_PIXELFORMAT;
     fmt.fmt.pix.field = V4L2_FIELD_INTERLACED_BT;
    
     //设置图像的格式
     int result = ioctrl(fd, VIDIOC_TRY_FMT, &fmt);
     if (result < 0){
         SKY_LOG("VIDIOC_TRY_FMT error");
     }
    
     result = ioctrl(fd, VIDIOC_S_FMT, &fmt);
    
     if (result < 0){
         SKY_LOG("VIDIOC_S_FMT error");
     }
    
     memset(&crop, 0x00, sizeof(struct v4l2_crop));
     crop.type     = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     crop.c.left   = cropcap.defrect.left;
     crop.c.top    = cropcap.defrect.top;
     crop.c.width  = cropcap.defrect.width;
     crop.c.height = cropcap.defrect.height;
    
     result = ioctrl(fd, VIDIOC_S_CROP, &crop);
     if (result < 0){
         SKY_LOG("VIDIOC_S_CROP error");
     }
    
  3. init_userptr
    SLOGD("sky init userptr");
     int result;
     struct v4l2_requestbuffers req;
     memset(&req, 0x00, sizeof(struct v4l2_requestbuffers));
    
     req.count = USE_BUF_COUNT; // buffer size if 4
     req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // video capture type
     req.memory = V4L2_MEMORY_MMAP; //mmap 方式
    
     result = px_ioctrl(fd, VIDIOC_REQBUFS, &req); //申请空间
    
     if (req.count < 2) {
         SKY_LOG("insufficient buf memory\n"); //不足的
     }
    
     if (req.count > USE_BUF_COUNT){
         SKY_LOG("request buf memory\n"); //申请error
     }
    
     buffers = (struct buffer*)calloc(req.count, sizeof(*buffers));
    
     for (n_buffers = 0; n_buffers < req.count; n_buffers++)
     {
         struct v4l2_buffer buf;
         //clear buffer
         memset(&buf, 0x00, sizeof(struct v4l2_buffer));
    
         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
         buf.memory = V4L2_MEMORY_MMAP;
         buf.index  = n_buffers;
    
         //查询分配的buffer信息
         int result = px_ioctrl(fd, VIDIOC_QUERYBUF, &buf);
         if (-1 == result)
         {
             SKY_LOG("VIDIOC_QUERYBUF error");
         }
    
         //void *mmap(void *start, size_t length, int prot, int flags,
         //int fd, off_t offset);
         //start:: 映射区的开始地址
         //length:: 映射区的长度
         //prot :: 期望的内存保护标志,不能与文件的打开模式冲突
         //flags:: 指定映射类型
         //offset:被映射对象内容的起点。
    
         buffers[n_buffers].length = buf.length;
         buffers[n_buffers].start =
         mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
    }
    
  4. start_capturing VIDIOC_STREAMON 代表开始获取图片流
     SLOGD("sky start capturing");
     enum v4l2_buf_type type;
     int result;
     //n_buffers 在初始化buffer的时候已经自动+1了
     for (size_t i = 0; i < n_buffers; i++){
         struct v4l2_buffer buf;
         memset(&buf, 0x00, sizeof(buf));
         buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
         buf.memory = V4L2_MEMORY_MMAP;
         buf.index = i;
         //VIDIOC_QBUF 把buffer放入放进输入队列,等待填充数据
         result = px_ioctrl(fd, VIDIOC_QBUF, &buf);
         if (-1 == result){
             SKY_LOG("VIDIOC_QBUF error");
             return;
         }
     }
     type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     //开始
     result = px_ioctrl(fd, VIDIOC_STREAMON, &type);
     if (-1 == result){
         SKY_LOG("VIDIOC_STREAMON error");
     }
    
  5. 不断的loop查询 ```java void mainloop() { SLOGD(“sky start main loop”); unsigned int count; count = MAX_FRAME_COUNT; //获取200帧数据

    while (count – >0) { for (;;) { fd_set fds; struct timeval tv; int r;

         FD_ZERO(&fds);
         FD_SET(fd, &fds);
    
         tv.tv_sec = 2;
         tv.tv_usec = 0;
    
         //检测摄像头是否有数据可以读取
         //select 和 poll 来对文件描述符进行监听是否有数据可读。
         r = select(fd+1, &fds, NULL, NULL, &tv);
    
         if (-1 == r) {
             if (EINTR == errno)
                 continue;
             SKY_LOG("system error");
         }
    
         if (0 == r) {
             SKY_LOG("select time out");
         }
    
         read_frame();
    
    
     }
    

    }

}

6.关掉设备
```java
void close_device()
{
    SLOGD("sky close device");
    int close_id = close(fd);

    if (-1 ==  close_id){
        SKY_LOG("close error");
    }
}

Github地址


下一篇 Art与Dalvik区别

评论

Content