• V4L2视频采集原理


    一、简介

    Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。可以对uvc免驱摄像头直接操作。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。

    二、V4L2视频采集原理

    V4L2支持内存映射方式(mmap)和直接读取方式(read)来采集数据,前者一般用于连续视频数据的采集,后者常用于静态图片数据的采集。我们一般使用内存映射方式来进行视频采集。
      V4L2采集视频数据的五个步骤:
      首先,打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
      其次,申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;
      第三,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;
      第四,驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
      第五,停止视频采集。

                V4L2采集流程图

    其实其他的都比较简单,就是通过ioctl这个接口去设置一些参数。最主要的就是buf管理。他有一个或者多个输入队列和输出队列。
      启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。
      应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。
      最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图所示

                    循环采集图

    三、基于v4l2的远程监控测试程序

    测试程序属于未完成的阶段,v4l2部分已经完成。
      V4l2各项函数定义在测试程序的camera.cpp中。
      程序设计师按照以上流程设计,查看源码的时候可以对照调用流程图,对于其中一些参数理解可以参考参考文献的第一篇文章。

    3.1打开摄像头

    <pre>

    void open_camera(Camera* cam)
    {
    cam->fd=open(cam->device_name,O_RDWR);
    if(cam->fd==-1)
    {
    cout<<"Cannot open the device."??endl;
    exit(1);
    }
    else
    {
    cout<<"Open the device."??endl;
    }
    }


    </pre>

    3.2查看摄像头支持的模式已经初始化

    需要用到的结构体:
    <pre>

    struct v4l2_capability
    {
    __u8 driver[16]; // 驱动名字
    __u8 card[32]; // 设备名字
    __u8 bus_info[32]; // 设备在系统中的位置
    __u32 version; // 驱动版本号
    __u32 capabilities; // 设备支持的操作
    __u32 reserved[4]; // 保留字段
    };
    </pre>
      capabilities常用值:
    <pre>
    V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取
    struct v4l2_format
    {
    enum v4l2_buf_type type;// 帧类型,应用程序设置
    union fmt
    {
    struct v4l2_pix_format pix;// 视频设备使用
    struct v4l2_window win;
    struct v4l2_vbi_format vbi;
    struct v4l2_sliced_vbi_format sliced;
    __u8 raw_data[200];
    };
    </pre>
      实现函数:
    <pre>
    void init_camera(Camera* cam){
    struct v4l2_capability cap;
    
    if (-1 == ioctl(cam->fd, VIDIOC_QUERYCAP, &cap))
    {
        if (EINVAL == errno)
        {
            fprintf(stderr, "%s is no V4L2 device
    ", cam->device_name);
            exit(EXIT_FAILURE);
        }
        else
        {
            errno_exit("VIDIOC_QUERYCAP");
        }
    }
    
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
        fprintf(stderr, "%s is no video capture device
    ", cam->device_name);
        exit(EXIT_FAILURE);
    }
    
    if (!(cap.capabilities & V4L2_CAP_STREAMING))
    {
        fprintf(stderr, "%s does not support streaming i/o
    ",cam->device_name);
        exit(EXIT_FAILURE);
    }
    
    //#ifdef DEBUG_CAM
    printf("
    VIDOOC_QUERYCAP
    ");
    printf("the camera driver is %s
    ", cap.driver);
    printf("the camera card is %s
    ", cap.card);
    printf("the camera bus info is %s
    ", cap.bus_info);
    printf("the version is %d
    ", cap.version);
    
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = cam->width;
    fmt.fmt.pix.height = cam->height;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    
    if (ioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0)
    {
        close(cam->fd);
    }
    cout<<"init the camera."<<endl;
    init_mmap(cam);
    }

    </pre>

    3.3内存映射

    需要用到的结构体:
    <pre>

    structv4l2_requestbuffers
    {
    __u32count;//缓冲区内缓冲帧的数目
    enumv4l2_buf_typetype;//缓冲帧数据格式
    enumv4l2_memorymemory;//区别是内存映射还是用户指针方式
    __u32 reserved[2];
    };
    struct v4l2_buffer
    {
    __u32index;//buffer序号
    enumv4l2_buf_typetype;//buffer类型
    __u32byteused;//buffer中已使用的字节数
    __u32flags;//区分是MMAP还是USERPTR
    enumv4l2_fieldfield;
    structtimevaltimestamp;//获取第一个字节时的系统时间
    structv4l2_timecode timecode;
    __u32sequence;//队列中的序号
    enum v4l2_memorymemory;//IO方式,被应用程序设置
    union m
    {
    __u32 offset;//缓冲帧地址,只对MMAP有效
    unsignedlonguserptr;
    };
    __u32length;//缓冲帧长度
    __u32input;
    __u32reserved;
    };


    </pre>
      自己定义的一个结构体来映射每个缓存帧:
    <pre>

    struct buffer
    {
    void* start;
    unsigned int length;
    }buffers;
    </pre>
      实现函数:
    <pre>
    void init_mmap(Camera cam)
    {
    struct v4l2_requestbuffers req;
    memset(&req, 0, sizeof(req));
    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    
    if (ioctl(cam->fd, VIDIOC_REQBUFS, &req) < 0)
    {
        fprintf(stderr, "Request buffers failure.
    ");
        exit(EXIT_FAILURE);
    }
    if (req.count < 2)
    {
        fprintf(stderr, "Insufficient buffer memory on %s
    ",
                cam->device_name);
        return;
    }
    cam->buffers = (Buffer *)calloc(req.count, sizeof(*cam->buffers));
    struct v4l2_buffer buf;
    for (unsigned int numBufs = 0; numBufs < req.count; numBufs++)
    {
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = numBufs;
        if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf) == -1)
        {
            return ;
        }
        cam->buffers[numBufs].length = buf.length;
        cam->buffers[numBufs].start = mmap(NULL, buf.length,PROT_READ | PROT_WRITE,
            MAP_SHARED,
            cam->fd, buf.m.offset);
        if (cam->buffers[numBufs].start == MAP_FAILED)
        {
            return ;
        }
    }
    cout<<"mmap the camera."<<endl;
    }

    </pre>

    3.4开启流

    <pre>

    void start_capturing(Camera* cam)
    {
    struct v4l2_buffer buf;
    enum v4l2_buf_type type;
    for (int i = 0; i < 4; i++)
    {
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    // buf.m.offset = buffer[i].offset;
    
         if (ioctl(cam->fd, VIDIOC_QBUF, &buf) < 0)
         {
    
         }
     }
    
     type =  V4L2_BUF_TYPE_VIDEO_CAPTURE;;
     if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < 0)
     {
    
     }
     cout<<"STREAMON"<<endl;
    }

    </pre>

    3.5读取一帧并交给用户程序处理

    <pre>

    int read_and_encode_frame(Camera* cam)
    {
    struct v4l2_buffer capture_buf;
    memset(&capture_buf, 0, sizeof(capture_buf));
    capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    capture_buf.memory = V4L2_MEMORY_MMAP;
    if (ioctl(cam->fd, VIDIOC_DQBUF, &capture_buf) < 0)
    {
    cout<<"cannot get buf"<<endl;
    }
    
    cout<<"read_and_encode_frame"<<endl;
    encode_frame(cam,capture_buf.index,capture_buf.length);
    
    if (-1 == ioctl(cam->fd, VIDIOC_QBUF, &capture_buf))
                return -1;
    return 0;
    }

    </pre>

    3.6自定义处理程序:

    在这里,我把获得的帧数据保存到自己定义的队列中,相对应的可以将此函数改为你所需的功能。
    <pre>

    void encode_frame(Camera* cam,unsigned int i,unsigned int length)
    {
    unsigned char *yuv_frame=static_cast<unsigned char *>(cam->buffers[i].start);
    if(yuv_frame[0]=='')
    {
    cout<<"yuv_frame[0]=='' "<<endl;
    return;
    }
    //fwrite(yuv_frame,length,1,cam->yuv_fp);
    
    mBuffer *inBuffer=(mBuffer*)malloc(sizeof(mBuffer));
    inBuffer->mpBuffer=(char*)malloc(length);
    memcpy(inBuffer->mpBuffer, yuv_frame, length);
    //inBuffer->mpBuffer=(char*)yuv_frame;
    inBuffer->mSize=length;
    putBufferWithData(&cam->buffer_list,inBuffer );
    
    //fwrite(inBuffer->mpBuffer,length,1,outfile);
     free(yuv_frame);
    cout<<"fwrite done."<<endl;
    }

    </pre>

    3.7结束采集

    后面就是采集结束后的释放过程,原先demo程序在释放资源过程中一直存在问题,一直还没解决。

    参考资料

    1.v4l2参数和机构体说明http://blog.sina.com.cn/s/blog_602f87700100znq7.html
    2.V4l2采集流程http://blog.csdn.net/eastmoon502136/article/details/8190262



    作者:onesixthree
    链接:https://www.jianshu.com/p/fd5730e939e7
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 相关阅读:
    python访问mysql和redis
    南昌PHP程序员的工资水平据说可达到8000了
    Android开发总是难以入门
    AppCan可以视为Rexsee的存活版
    像我这样的人搞程序开发
    PHPWind 8.7中插件金币竞价插件的漏洞
    混合式APP开发中中间件方案Rexsee
    看到一份名单发现很多公司都和自己发生了或多或少的联系
    PhpWind 8.7中禁止后台管理员随意修改会员用户名功能
    个人前途
  • 原文地址:https://www.cnblogs.com/cyyljw/p/11274725.html
Copyright © 2020-2023  润新知