天天看点

【QT学习之路】使用V4L2驱动USB摄像头一、V4L2简介二、QT通过V4L2接口采集视频的步骤三、具体实现过程

一、V4L2简介

V4L2(video for linux 2的缩写)是Linux下关于采集图片、视频和音频数据相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范。使应用层跟硬件层分离,硬件层的驱动操作都交给V4L2,应用层只需要调用V4L2的接口即可,如下图

【QT学习之路】使用V4L2驱动USB摄像头一、V4L2简介二、QT通过V4L2接口采集视频的步骤三、具体实现过程

二、QT通过V4L2接口采集视频的步骤

      1、首先打开摄像头

      2、配置设备(采集的频率,图像宽高, 图像格式(jpeg, yuv--422,420)),获取设备信息,确保配置成功

      3、在内核空间申请缓冲区队列(2-4个),把申请好的缓冲区对象一一映射到用户空间

      4、开始 / 结束采集   

三、具体实现过程

1、打开摄像头,可通过open函数来打开摄像头

this->vfd = ::open(deviceName.c_str(), O_RDWR);
    if(this->vfd < 0)
    {
        perror("open fail");
        VideoException vexp("open fail");//创建异常对象
        //抛异常
        throw vexp;
    }
           

2、配置设备 和 获取设备信息

在配置时,用到了linux下videodev2.h(路径为/usr/include/linux/videodev2.h)头文件中的V4l2_format 和 v4l2_pix_format 结构体。

V4l2_format

【QT学习之路】使用V4L2驱动USB摄像头一、V4L2简介二、QT通过V4L2接口采集视频的步骤三、具体实现过程

v4l2_pix_format

【QT学习之路】使用V4L2驱动USB摄像头一、V4L2简介二、QT通过V4L2接口采集视频的步骤三、具体实现过程

将上面的结构体信息写入设备即可,通过ioctl函数将属性写入设备,该函数的参数用到了一些宏定义 (videodev2.h中)

     VIDIOC_S_FMT ---设置格式属性

     VIDIOC_G_FMT ---获取格式属性

【QT学习之路】使用V4L2驱动USB摄像头一、V4L2简介二、QT通过V4L2接口采集视频的步骤三、具体实现过程

函数参数说明

int  ioctl(文件描述符,  命令--上的宏,  参数--上宏定义里面的结构体对象)

配置属性和获取属性的实现的代码

truct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //使用第一个结构体
fmt.fmt.pix.width = 640;
vfmt.fmt.pix.height = 480;
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//(设置视频输出格式,但是要摄像头支持)
//通过ioctl把属性写入设备
int ret  = ioctl(this->vfd, VIDIOC_S_FMT, &vfmt);
//通过ioctl从设备获取属性
memset(&vfmt, 0, sizeof(vfmt));   //清空
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(this->vfd, VIDIOC_G_FMT, &vfmt)
           

3、申请缓冲区队列, 映射到用户空间

1、使用的结构体 (同样也在videodev2.h中)

申请缓冲区队列

【QT学习之路】使用V4L2驱动USB摄像头一、V4L2简介二、QT通过V4L2接口采集视频的步骤三、具体实现过程

映射 ---从队列中拿出一个空间做映射

【QT学习之路】使用V4L2驱动USB摄像头一、V4L2简介二、QT通过V4L2接口采集视频的步骤三、具体实现过程

2、使用到的宏定义

【QT学习之路】使用V4L2驱动USB摄像头一、V4L2简介二、QT通过V4L2接口采集视频的步骤三、具体实现过程

3、通过ioctl函数进行操作

申请队列长度

              ioctl(文件描述符,  VIDIOC_REQBUFS(宏定义),  struct v4l2_requestbuffer 对象地址)

从队列拿出一个空间做映射

              ioctl(文件描述符, VIDIOC_QUERYBUF(宏定义),  struct v4l2_buffer 对象地址)

放回队列

              ioctl(文件描述符, VIDIOC_QBUF(宏定义), struct v4l2_buffer 对象地址)

4、具体代码实现

一、申请缓冲区队列

//1申请缓冲区队列
struct v4l2_requestbuffers reqbuffer;
reqbuffer.count = this->count;//申请缓冲区队列长度
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.memory = V4L2_MEMORY_MMAP;
int ret = ioctl(this->vfd, VIDIOC_REQBUFS, &reqbuffer);
if(ret < 0)
{
    VideoException vexp("req buffer fail");//创建异常对象
    throw vexp;
}
           

二、映射缓冲区队列到用户空间

for(int i=0; i<this->count; i++)
{
    struct VideoFrame frame;

    struct v4l2_buffer mapbuffer;
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    mapbuffer.index = i;
    mapbuffer.memory = V4L2_MEMORY_MMAP;
    //从队列中拿到内核空间
    ret  = ioctl(this->vfd, VIDIOC_QUERYBUF, &mapbuffer);
    if(ret < 0)
    {
        perror("query fail");
    }
    //映射
    frame.length = mapbuffer.length;
    frame.start = (char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, this->vfd, mapbuffer.m.offset);

    //空间放回队列中(内核空间)
    ret = ioctl(this->vfd, VIDIOC_QBUF, &mapbuffer);

    //把frame添加到容器framebuffers
    framebuffers.push_back(frame);
}
           

4、开始/结束采集数据

1、使用到的宏定义

【QT学习之路】使用V4L2驱动USB摄像头一、V4L2简介二、QT通过V4L2接口采集视频的步骤三、具体实现过程

2、通过ioctl函数进行操作

                       enum v4l2_buf_type  type;  //赋值为:V4L2_BUF_TYPE_VIDEO_CAPTURE

开始采集        ioctl(文件描述符,  VIDIOC_STREAMON(宏定义),  &type)

结束采集        ioctl(文件描述符,  VIDIOC_STREAMOFF(宏定义),  &type)

3、采集数据的步骤

      1、从队列中拿出一个缓冲区   

      2、从映射用户空间中把缓冲区数据取走   

      3、把缓冲区放回队列

3、具体代码实现

一、开始采集

//开始采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret  =  ioctl(this->vfd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
    perror("start fail");
}
           

二、结束采集

//结束采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret  =  ioctl(this->vfd, VIDIOC_STREAMOFF, &type);
if(ret < 0)
{
    perror("stop fail");
}
//释放映射
for(int i=0; i<this->framebuffers.size(); i++)
{
    munmap(framebuffers.at(i).start, framebuffers.at(i).length);
}
           

三、采集数据

struct v4l2_buffer readbuf;
readbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
readbuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(this->vfd, VIDIOC_DQBUF, &readbuf)<0)//取一针数据
{
    perror("read image fail");
    exit(1);
}
printf("%ld\n", readbuf.length);
*length = readbuf.length;
memcpy(imageBuffer,framebuffers[readbuf.index].start, framebuffers[readbuf.index].length);  //拷贝数据

//把用完的队列空间放回队列中重复使用
if(ioctl(vfd, VIDIOC_QBUF, &readbuf)<0)
{
     perror("destroy fail");
     exit(1);
}
           

5、测试采集数据是否成功

int main()
{

    V4l2Api mV4l2("/dev/video0");

    mV4l2.open();

    char image[1000000];
    int length=0;
    while(1)
    {

        mV4l2.grapImage(image, &length);

        FILE * file = fopen("./my.jpg", "w+");
        
        fwrite(image,length, 1, file);
        fclose(file);
        break;
    }

    mV4l2.close();

    return 0;
}
           

注意:我这里采集的视频输出格式为MJPEG,所以才能直接存储为.jpg格式的图片。

问题咨询及项目源码请加群:

QQ群

名称:IT项目交流群

群号:245022761

继续阅读