Linux环境应用编程(九):Camera

一:V4L2简介

1、概念

V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动,为驱动和应用程序提供了一套统一的接口规范。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写。

可以支持多种设备,它可以有以下几种接口:

(1) 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的.

(2)视频输出接口(video output interface):可以驱动计算机的外围视频图像设备--像可以输出电视信号格式的设备.

(3)直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU.

(4)视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号.

(5)收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流.

V4L2在linux系统中的结构图:

2、编程模式

v4L2是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等,编程模式如下:

(1)打开设备,进行初始化参数设置,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;

(2)申请图像帧缓冲,并进行内存映射,将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取、处理图像数据;

(3)将帧缓冲进行入队操作,启动视频采集;

(4)驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;

(5)释放资源,停止采集工作。

二:V4L2指令及工作流程

从结构图中可以看出V4L2是一个字符设备,而V4L2的大部分功能都是通过设备文件的ioctl导出的。

1、ioctl常用指令分类:

  1. Query Capability:查询设备支持的功能,只有VIDIOC_QUERYCAP一个。
  2. 优先级相关:包括VIDIOC_G_PRIORITY,VIDIOC_S_PRIORITY,设置优先级。
  3. capture相关:视频捕获相关Ioctl.
    ID 描述
    VIDIOC_ENUM_FMT 枚举设备所支持的所有数据格式
    VIDIOC_S_FMT 设置数据格式
    VIDIOC_G_FMT 获取数据格式
    VIDIOC_TRY_FMT 与VIDIOC_S_FMT一样,但不会改变设备的状态
    VIDIOC_REQBUFS 向设备请求视频缓冲区,即初始化视频缓冲区
    VIDIOC_QUERYBUF 查询缓冲区的状态
    VIDIOC_QBUF 从设备获取一帧视频数据
    VIDIOC_DQBUF 将视频缓冲区归回给设备,
    VIDIOC_OVERLAY 开始或者停止overlay
    VIDIOC_G_FBUF 获取video overlay设备或OSD设备的framebuffer参数
    VIDIOC_S_FBUF 设置framebuffer参数
    VIDIOC_STREAMON 开始流I/O操作,capture or output device
    VIDIOC_STREAMOFF 关闭流I/O操作
  4. TV视频标准:
    ID 描述
    VIDIOC_ENUMSTD 枚举设备支持的所有标准
    VIDIOC_G_STD 获取当前正在使用的标准
    VIDIOC_S_STD 设置视频标准
    VIDIOC_QUERYSTD 有的设备支持自动侦测输入源的视频标准,此时使用此ioctl查询侦测到的视频标准

2、具体流程图

三:软件框架

1、打开设备查看属性

使用VIDIOC_QUERYCAP命令查看设备属性用到的结构体tV4l2Cap原型为v4l2_capability用来描述视频采集设备的driver信息,其中根据capabilities可以判断当前设备是否为capture设备,并检查使用内存映射还是直接读的方式获取图像数据。

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

struct v4l2_capability tV4l2Cap;

iFd = open(strDevName, O_RDWR);
if (iFd < 0)
{
    DBG_PRINTF("can not open %s\n", strDevName);
    return -1;
}
ptVideoDevice->iFd = iFd;

iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
	DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);
	goto err_exit;
}

if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
	DBG_PRINTF("%s is not a video capture device\n", strDevName);
    goto err_exit;
}

if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {
    DBG_PRINTF("%s supports streaming i/o\n", strDevName);
}

if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {
    DBG_PRINTF("%s supports read i/o\n", strDevName);
}

2、显示所有支持的格式

 v4l2_fmtdesc 结构体描述当前camera支持的格式信息,使用命令VIDIOC_ENUM_FMT查询, v4l2_fmtdesc结构体中index要设置,从0开始;v4l2_buf_type type也要设置,如果使用的是camera设备,则v4l2_buf_type type要设置为V4L2_BUF_TYPE_VIDEO_CAPTURE,因为camera是CAPTURE设备。结构体中的其他内容driver会填充。

struct v4l2_fmtdesc {
	__u32		    index;             // 要查询的格式序号,应用程序设置
	__u32		    type;              // 帧类型,应用程序设置
	__u32               flags;         // 是否为压缩格式
	__u8		    description[32];   // 格式名称
	__u32		    pixelformat;       //所支持的格式
	__u32		    reserved[4];
};

struct v4l2_fmtdesc tFmtDesc;

memset(&tFmtDesc, 0, sizeof(tFmtDesc));
tFmtDesc.index = 0;
tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {
    if (isSupportThisFormat(tFmtDesc.pixelformat))
    {
        ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;
        break;
    }
    tFmtDesc.index++;
}

3、设置图像帧格式

struct v4l2_format结构体需要设置enum v4l2_buf_type type和union fmt中的struct v4l2_pix_format pix。enum v4l2_buf_type type因为使用的是camera设备,camera是CAPTURE设备,所以设置成V4L2_BUF_TYPE_VIDEO_CAPTURE。struct v4l2_pix_format pix设置一帧图像的长、宽和格式等,由于要适配LCD输出,所以长、宽设置为LCD支持的长、宽。

struct v4l2_format {
	__u32	 type;
	union {
		struct v4l2_pix_format		pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
		struct v4l2_pix_format_mplane	pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
		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 */
		struct v4l2_sdr_format		sdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */
		struct v4l2_meta_format		meta;    /* V4L2_BUF_TYPE_META_CAPTURE */
		__u8	raw_data[200];                   /* user-defined */
	} fmt;
};
struct v4l2_format  tV4l2Fmt;

/* set format in */
GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);
memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;
tV4l2Fmt.fmt.pix.width       = iLcdWidth;
tV4l2Fmt.fmt.pix.height      = iLcdHeigt;
tV4l2Fmt.fmt.pix.field       = V4L2_FIELD_ANY;

iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);

4、申请缓冲区

申请一个拥有NB_BUFFER个缓冲帧的缓冲区,__u32 count为缓冲帧的数目;enum v4l2_buf_type type和前文一样,同样设置成V4L2_BUF_TYPE_VIDEO_CAPTURE;enum v4l2_memorymemory用来区分是内存映射还是用户指针,我们使用内存映射的方式,所以设置成V4L2_MEMORY_MMAP。

struct v4l2_requestbuffers {
	__u32			count;
	__u32			type;		/* enum v4l2_buf_type */
	__u32			memory;		/* enum v4l2_memory */
	__u32			reserved[2];
};
struct v4l2_requestbuffers tV4l2ReqBuffs;

/* request buffers */
memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
tV4l2ReqBuffs.count = NB_BUFFER;
tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;

iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
if (iError) 
{
	DBG_PRINTF("Unable to allocate buffers.\n");
    goto err_exit;        
}

5、将申请的缓冲帧从内核空间映射到用户空间并将缓冲帧放如队列

struct v4l2_buffer {
	__u32			index;
	__u32			type;
	__u32			bytesused;
	__u32			flags;
	__u32			field;
	struct timeval		timestamp;
	struct v4l2_timecode	timecode;
	__u32			sequence;

	/* memory location */
	__u32			memory;
	union {
		__u32           offset;
		unsigned long   userptr;
		struct v4l2_plane *planes;
		__s32		fd;
	} m;
	__u32			length;
	__u32			reserved2;
	__u32			reserved;
};

struct v4l2_buffer tV4l2Buf;

if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING)
{
    /* map the buffers */
    for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) 
    {
    	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
    	tV4l2Buf.index = i;
    	tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
    	iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
    	if (iError) 
        {
    	    DBG_PRINTF("Unable to query buffer.\n");
    	    goto err_exit;
    	}

        ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;
    	ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,
    			  tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,
    			  tV4l2Buf.m.offset);
    	if (ptVideoDevice->pucVideBuf[i] == MAP_FAILED) 
        {
    	    DBG_PRINTF("Unable to map buffer\n");
    	    goto err_exit;
    	}
    }        

    /* Queue the buffers. */
    for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) 
    {
    	memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
    	tV4l2Buf.index = i;
    	tV4l2Buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    	tV4l2Buf.memory = V4L2_MEMORY_MMAP;
    	iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);
    	if (iError)
        {
    	    DBG_PRINTF("Unable to queue buffer.\n");
    	    goto err_exit;
    	}
    }
}

猜你喜欢

转载自blog.csdn.net/qq_34968572/article/details/107554310