封装V4L2接口获取usb camera帧图像

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zz531987464/article/details/83350670

这里我们使用c++的方式将需要调用到的相关v4l2指令封装成一个UsbCamera类,usb camera等uvc免驱设备可以很简单的通过该类实现yuv帧数据流的获取处理。

首先定义一个统一的数据结构

struct VideoInfo {
    struct v4l2_capability cap;   // 视频设备的基本功能等信息
    struct v4l2_format format;    // 帧格式、长宽等信息
    struct v4l2_fmtdesc fmtdesc;  // 枚举当前设备所支持的所有的帧格式
    struct v4l2_buffer buf;       // 驱动中的一帧图像缓存
    struct v4l2_requestbuffers rb;// 申请帧缓存
    void *mem[NB_BUFFER];// 驱动映射后的应用层使用的地址
    bool isStreaming;    //当前是否有数据流
    int width;    // 帧宽,可校验用
    int height;   // 帧高
    int formatIn; // 设置的帧格式类型
    int framesizeIn;//帧图像大小
};

如上,这里struct videoInfo结构体将常用的v4l2驱动提供的数据结构全部封装起来,便于数据管理,显示分明。

1. Initialize()

int Initialize()

初始化接口,获取基本的设备信息

入参

返回值

>=0:初始化成功

<0 : 初始化失败

        初始化成员函数,该函数主要目的就是

扫描二维码关注公众号,回复: 3746081 查看本文章

        a.给成员变量VideoInfo分配存储空间并初始化

mVideoInfo = (struct VideoInfo *) calloc (1, sizeof (struct VideoInfo));

        b.打开usb camera的设备节点/dev/video0,获取文件描述符 

mCameraHandle = open(device, O_RDWR)

        c.查询设备的基本信息与功能

ret = ioctl (mCameraHandle, VIDIOC_QUERYCAP, &mVideoInfo->cap);

  获取设备信息的命令VIDIOC_QUERYCAP 通过结构 v4l2_capability 获取设备的基本信息

struct v4l2_capability {
    __u8    driver[16];   //驱动名字
    __u8    card[32];     //设备名字
    __u8    bus_info[32]; //设备在系统中的位置
    __u32   version;      //驱动版本号
    __u32   capabilities; //设备支持的操作
    __u32   reserved[4];  //保留字段
};

其中域 capabilities 代表设备支持的操作模式,常见值有 V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING 表示是一个视频捕捉设备并且具有数据流控制模式;另外 driver 域需要和 struct video_device 中的 name 匹配.

2.getAllFormat()

void getAllFormat(int &num, int *fmt)

获取所有支持的帧格式

入参

Num为引用值,获取支持的帧格式数量

fmt 为获取到的帧格式的实际值

返回值

获取当前设备所支持的所有帧格式并通过入参回传相关信息

    mVideoInfo->fmtdesc.index = 0;
    mVideoInfo-> fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    cout << "Device Support:" << endl;
    while(ioctl(mCameraHandle, VIDIOC_ENUM_FMT, &mVideoInfo->fmtdesc) != -1) { 
            cout<<mVideoInfo->fmtdesc.index +1 <<" "<< (char *)(mVideoInfo->fmtdesc.description) << endl;
            fmt[mVideoInfo->fmtdesc.index] = mVideoInfo->fmtdesc.pixelformat;
            mVideoInfo->fmtdesc.index++;
    }
    num = mVideoInfo->fmtdesc.index;

代码中使用VIDIOC_ENUM_FMT来枚举当前设备所支持的帧格式种类

struct v4l2_fmtdesc {
    __u32 index;            // 要查询的格式序号,应用程序设置
    enum v4l2_buf_type type;//帧类型,如果为Camera,则填写V4L2_BUF_TYPE_VIDEO_CAPTURE
    __u32 flags;            // 如果压缩,则Driver填写V4L2_FMT_FLAG_COMPRESSED,否则为0 
    __u8 description[32];   // image format的描述(char *),如:YUV 4:2:2 (YUYV)
    __u32 pixelformat;      //所支持的格式(int)。 如:V4L2_PIX_FMT_UYVY
    __u32 reserved[4];
}; 

index从0开始,依次增加,直到返回 -1 (如果index有效,则返回0). Driver会填充结构体struct v4l2_fmtdesc的其它内容,如果index超出范围,则返回-1。通过这个结构体,可以知道当前设备所支持的所有帧格式,下一步可以对应的设置帧格式。

3.setParameters()

int setParameters(int width, int height, int format)

设置帧图像的相关信息,帧长、帧宽以及帧格式

入参

Width为设置的帧宽度

Height为设置的帧高度

Format为设置的帧格式

返回值

>=0:设置参数成功

<0 : 设置参数失败

设置当前设备的帧格式、长、宽等信息

    mVideoInfo->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    mVideoInfo->format.fmt.pix.width = width;
    mVideoInfo->format.fmt.pix.height = height;
    mVideoInfo->format.fmt.pix.pixelformat = format;
    mVideoInfo->width = width;
    mVideoInfo->height = height;
    mVideoInfo->formatIn = format;
    int ret = ioctl(mCameraHandle, VIDIOC_S_FMT, &mVideoInfo->format);

通常用 VIDIOC_S_FMT 命令通过结构 v4l2_format 初始化捕获视频的格式,后面4.getParameters()将会使用VIDIOC_G_FMT命令通过结构体v4l2_format来查询初始化设置的参数是否生效。

struct v4l2_format {
	enum v4l2_buf_type type;
	union {
		struct v4l2_pix_format        pix;    /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
		struct v4l2_window            win;    /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
		struct v4l2_vbi_format        vbi;    /* V4L2_BUF_TYPE_VBI_CAPTURE */
		struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE*/
		__u8   raw_data[200];                 /* user-defined */
	} fmt;
};
enum v4l2_buf_type {
	V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1,
	V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2,
	V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3,
	...........
	V4L2_BUF_TYPE_PRIVATE              = 0x80,
};
struct v4l2_pix_format {
	__u32     width;		//宽,必须是16的倍数
	__u32     height;		//高,必须是16的倍数
	__u32     pixelformat;	// 视频数据存储类型,例如是YUV4:2:2还是RGB
	enum v4l2_field  field; // 逐行扫描/隔行扫描. Sam通常采用V4L2_FIELD_NONE,逐行放置数据
	__u32     bytesperline; /* for padding, zero if unused */
	__u32     sizeimage;
	enum v4l2_colorspace colorspace;
	__u32     priv;         /* private data, depends on pixelformat */
};

常见的捕获模式为 V4L2_BUF_TYPE_VIDEO_CAPTURE 即视频捕捉模式,在此模式下 fmt 联合体采用域 v4l2_pix_format:其中 width 为视频的宽、height 为视频的高、pixelformat 为视频数据格式(常见的值有 V4L2_PIX_FMT_YUV422P | V4L2_PIX_FMT_RGB565)、bytesperline 为一行图像占用的字节数、sizeimage 则为图像占用的总字节数、colorspace 指定设备的颜色空间。

enum v4l2_buf_type 枚举类型也会单独与VIDIOC_STREAMON、VIDIOC_STREAMOFF联合使用,用于控制视频流的开关。

4.getParameters()

int getParameters()

获取验证帧图像的相关信息,帧长、帧宽以及帧格式

入参

返回值

>=0:获取设置参数成功

<0 : 获取设置参数失败

获取当前设备的帧格式、长、宽等信息

memset(&mVideoInfo->format, 0, sizeof( struct v4l2_format));
mVideoInfo->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(mCameraHandle, VIDIOC_G_FMT, &mVideoInfo->format);

5.UseBuffersPreview()

int UseBuffersPreview(int num)

申请缓存帧图形的buffer并映射到应用程序

入参

Num表示申请的buf数量

返回值

>=0:申请缓存buf成功

<0 : 申请缓存buf失败

该成员函数主要工作如下:

使用VIDIOC_REQBUFS在内核中申请缓存空间;

mVideoInfo->rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
mVideoInfo->rb.memory = V4L2_MEMORY_MMAP;
mVideoInfo->rb.count = num;
ret = ioctl(mCameraHandle, VIDIOC_REQBUFS, &mVideoInfo->rb);

VIDIOC_REQBUFS 命令通过结构 v4l2_requestbuffers 请求在驱动里面申请一片连续的内存用于缓存视频信息:

struct v4l2_requestbuffers {
	__u32              count; //帧缓存数量,也就是说在缓存队列里保持多少张照片
	enum v4l2_buf_type type;  //数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
    enum v4l2_memory   memory;// V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
	__u32              reserved[2];
};
enum v4l2_memory {
	V4L2_MEMORY_MMAP    = 1,
	V4L2_MEMORY_USERPTR	= 2,
	V4L2_MEMORY_OVERLAY	= 3,
};

请求V4L2驱动分配视频缓冲区(申请V4L2视频驱动分配内存),V4L2是视频设备的驱动层,位于内核空间,所以通过VIDIOC_REQBUFS控制命令字申请的内存位于内核空间,应用程序不能直接访问,需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。

注意:VIDIOC_REQBUFS会修改V4L2_reqbuf的count值,V4L2_reqbuf的count值返回实际申请成功的视频缓冲区数目。

应用程序中调用VIDIOC_QUERYBUF取得了内核缓冲区信息后,紧接着调用mmap函数把内核空间地址映射到用户空间;

for (int i = 0; i < num; i++) {
    memset (&mVideoInfo->buf, 0, sizeof (struct v4l2_buffer));
    mVideoInfo->buf.index = i;
    mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    mVideoInfo->buf.memory = V4L2_MEMORY_MMAP;
    ret = ioctl (mCameraHandle, VIDIOC_QUERYBUF, &mVideoInfo->buf);
    mVideoInfo->mem[i] = mmap (0, mVideoInfo->buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, mCameraHandle, mVideoInfo->buf.m.offset);
}
mPreviewBufferCount = num;

//mmap(addr,length,prot,flags,fd,offset)
//addr 映射起始地址,一般为NULL ,让内核自动选择
//length 被映射内存块的长度
//prot 标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE
//flags 确定此内存映射能否被其他进程共享,MAP_SHARED,MAP_PRIVATE
//fd,offset, 确定被映射的内存地址 返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1)

其中VIDIOC_QUERYBUF 命令通过结构 v4l2_buffer 查询驱动申请的缓存块信息:

struct v4l2_buffer {
	__u32       		index; // index 为缓存编号
	enum v4l2_buf_type 	type; // type 为视频捕获模式
	__u32       	    bytesused; // 缓存已使用空间大小
	__u32       		flags; // 缓存当前状态(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据)
	enum v4l2_field      field;
	struct timeval       timestamp; // 时间戳
	struct v4l2_timecode timecode;
	__u32                sequence; // 缓存序号
	/* memory location */
	enum v4l2_memory     memory; // 缓存使用方式,被应用程序设置
	union{
		__u32            offset; // 当前缓存与内存区起始地址的偏移,只对MMAP有效
		unsigned long    userptr;
	}m;
	__u32                length; // 缓冲帧大小
	__u32                input;
	__u32                reserved; // 一般用于传递物理地址值
};

struct v4l2_buffer结构体变量中保存了指令的缓冲区的相关信息;一般情况下,应用程序中调用VIDIOC_QUERYBUF取得了内核缓冲区信息后,紧接着调用mmap函数把内核空间地址映射到用户空间,方便用户空间应用程序的访问。

6.startPreview()

int startPreview()

将前面申请的缓存buf加入视频输入队列并启动视频设备

入参

返回值

>=0:视频设备启动成功

<0 : 视频设备启动失败

视频缓冲区进入视频输入队列中并启动视频设备

for (int i = 0; i < mPreviewBufferCount; i++) {
    mVideoInfo->buf.index = i;
    mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    mVideoInfo->buf.memory = V4L2_MEMORY_MMAP; //缓冲帧放入缓冲队列
    ret = ioctl(mCameraHandle, VIDIOC_QBUF, &mVideoInfo->buf);
    if (ret == -1){
        cout << "ioctl VIDIOC_QBUF failed" << endl;
        return -1;
    }
}
enum v4l2_buf_type bufType;
bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl (mCameraHandle, VIDIOC_STREAMON, &bufType);

VIDIOC_QBUF采用结构v4l2_buffer与驱动通信:VIDIOC_QBUF 命令执行成功后,指令(指定)的视频缓冲区进入视频输入队列,在启动视频设备拍摄图像时,相应的视频数据被保存到视频输入队列相应的视频缓冲区中,即将缓存加入空闲可捕获视频的队列,传递的主要参数为 index。

前期初始化完成后,只是解决了一帧视频数据的格式和大小问题,而连续视频帧数据的采集需要用帧缓冲区队列的方式来解决,即要通过驱动程序在内存中申请几个帧缓冲区来存放视频数据。

  应用程序通过API接口提供的方法(VIDIOC_REQBUFS)申请若干个视频数据的帧缓冲区,申请帧缓冲区数量一般不低于3个,每个帧缓冲区存放一帧视频数据,这些帧缓冲区在内核空间。

  应用程序通过API接口提供的查询方法(VIDIOC_QUERYBUF)查询到帧缓冲区在内核空间的长度和偏移量地址。

  应用程序再通过内存映射方法(mmap),将申请到的内核空间帧缓冲区的地址映射到用户空间地址,这样就可以直接处理帧缓冲区的数据。

  (1)将帧缓冲区在视频输入队列排队,并启动视频采集

  在驱动程序处理视频的过程中,定义了两个队列:视频采集输入队列(incoming queues)和视频采集输出队列(outgoing queues),前者是等待驱动存放视频数据的队列,后者是驱动程序已经放入了视频数据的队列。

  应用程序需要将上述帧缓冲区在视频采集输入队列排队(VIDIOC_QBUF),然后可启动视频采集。

  (2)循环往复,采集连续的视频数据

  启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。

  应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。

  最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集.

7.stopPreview()

int stopPreview()

关闭视频设备并断开内存映射

入参

返回值

>=0:视频设备关闭断开映射成功

<0 : 视频设备关闭断开映射失败

关闭视频设备并断开地址映射

v4l2_buf_type bufType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl (mCameraHandle, VIDIOC_STREAMOFF, &bufType);
for (int i = 0; i < mPreviewBufferCount; i++) {
    munmap(mVideoInfo->mem[i], mVideoInfo->buf.length);
}

8.GetFrame()

char *GetFrame(int &index)

帧缓冲区移至视频采集输出队列,获取一帧图像

入参

Index引用表示获取的一帧图像具体为哪一帧

返回值

>=0:视频设备关闭断开映射成功

<0 : 视频设备关闭断开映射失败

将帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出

memset(&mVideoInfo->buf, 0, sizeof(struct v4l2_buffer));
mVideoInfo->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
mVideoInfo->buf.memory = V4L2_MEMORY_MMAP;
int ret = ioctl(mCameraHandle, VIDIOC_DQBUF, &mVideoInfo->buf);
if (ret == -1){
    cout << "ioctl VIDIOC_DQBUF failed." << endl;
    return NULL;
}
return (char *)mVideoInfo->mem[mVideoInfo->buf.index];    

VIDIOC_DQBUF采用结构v4l2_buffer与驱动通信:VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会根据 index 确定可用数据的起始地址和范围。(前面已经通过调用函数 mmap做了用户空间和内核空间的内存映射).

9.UnGetFrame()
 

int UnGetFrame();

获取完一帧后,将这一帧图像的buf放回去

入参

返回值

>=0:视频缓冲区再次加入输入队列成功

<0 : 视频缓冲区再次加入输入队列失败

获取完一帧后,将取出的这一帧图像的buf再次放回去

int ret = ioctl(mCameraHandle, VIDIOC_QBUF, &mVideoInfo->buf);

将V4L2命令操作进行了简单封装后,我们写一个简单的test demo来获取usbcamera的帧图像并保存下来,然后使用7yuv软件进行帧图像的查看。

UsbCamera usbCam;
FILE *fp = fopen("./camera.yuv", "wb");
int ret = usbCam.initialize();
ret = usbCam.setParameters(width,height,V4L2_PIX_FMT_YUYV);
ret = usbCam.getParameters();
ret = usbCam.UseBuffersPreview(4);
ret = usbCam.startPreview();
int index = 0;
for (int i = 0; i < 50; i++){
    char *addr = usbCam.GetFrame(index);
    fwrite(addr, width*height*2,1,fp);
    usbCam.UnGetFrame();
}
stopPreview();
fclose(fp);

在上面的test.cpp程序中,我们将采集到的50帧图像保存到camera.yuv文件中。在ubuntu中,通过7yuv软件可以打开camera.yuv文件查看采集到的图像。如下:

完整的代码已经上传,代码链接:https://download.csdn.net/download/zz531987464/10742398

解压直接make,然后运行test即可

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/zz531987464/article/details/83350670