
V4L2快速上手指南
v4l2应用编程
查询设备的属性/能力/功能
打开设备之后,接着需要查询设备的属性,确定该设备是否是一个视频采集类设备、以及其它一些属性
查询设备的属性,使用的指令为VIDIOC_QUERYCAP
ioctl(int fd, VIDIOC_QUERYCAP, struct v4l2_capability *cap);
此时通过ioctl()将获取到一个struct v4l2_capability类型数据,struct v4l2_capability数据结构描述了设备的一些属性,结构体定义如下所示:
struct v4l2_capability {
__u8 driver[16]; /* 驱动的名字 */
__u8 card[32]; /* 设备的名字 */
__u8 bus_info[32]; /* 总线的名字 */
__u32 version; /* 版本信息 */
__u32 capabilities; /* 设备拥有的能力 */
__u32 device_caps;
__u32 reserved[3]; /* 保留字段 */
};
capabilities字段,该字段描述了设备拥有的能力,该字段的值如下(可以是以下任意一个值或多个值的位或关系):
/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE 0x00000010 /* Is a raw VBI capture device */
#define V4L2_CAP_VBI_OUTPUT 0x00000020 /* Is a raw VBI output device */
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 /* Is a sliced VBI capture device */
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 /* Is a sliced VBI output device */
#define V4L2_CAP_RDS_CAPTURE 0x00000100 /* RDS data capture */
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 /* Can do video output overlay */
#define V4L2_CAP_HW_FREQ_SEEK 0x00000400 /* Can do hardware frequency seek */
#define V4L2_CAP_RDS_OUTPUT 0x00000800 /* Is an RDS encoder */
/* Is a video capture device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_CAPTURE_MPLANE 0x00001000
/* Is a video output device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_OUTPUT_MPLANE 0x00002000
/* Is a video mem-to-mem device that supports multiplanar formats */
#define V4L2_CAP_VIDEO_M2M_MPLANE 0x00004000
/* Is a video mem-to-mem device */
#define V4L2_CAP_VIDEO_M2M 0x00008000
#define V4L2_CAP_TUNER 0x00010000 /* has a tuner */
#define V4L2_CAP_AUDIO 0x00020000 /* has audio support */
#define V4L2_CAP_RADIO 0x00040000 /* is a radio device */
#define V4L2_CAP_MODULATOR 0x00080000 /* has a modulator */
#define V4L2_CAP_SDR_CAPTURE 0x00100000 /* Is a SDR capture device */
#define V4L2_CAP_EXT_PIX_FORMAT 0x00200000 /* Supports the extended pixel format */
#define V4L2_CAP_SDR_OUTPUT 0x00400000 /* Is a SDR output device */
#define V4L2_CAP_META_CAPTURE 0x00800000 /* Is a metadata capture device */
#define V4L2_CAP_READWRITE 0x01000000 /* read/write systemcalls */
#define V4L2_CAP_ASYNCIO 0x02000000 /* async I/O */
#define V4L2_CAP_STREAMING 0x04000000 /* streaming I/O ioctls */
#define V4L2_CAP_TOUCH 0x10000000 /* Is a touch device */
#define V4L2_CAP_DEVICE_CAPS 0x80000000 /* sets device capabilities field */
设置帧格式、帧率
获取摄像头支持的图像格式
v4l2_fmtdesc 结构体
struct v4l2_fmtdesc v4fmt;
struct v4l2_fmtdesc {
__u32 index; /* Format number */
__u32 type; /* enum v4l2_buf_type */
__u32 flags;
__u8 description[32]; /* Description string */
__u32 pixelformat; /* Format fourcc */
__u32 mbus_code; /* Media bus code */
__u32 reserved[3];
};
获取video设备的支持格式,并且会将结果放入这个结构体
ioctl使用
ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
第一个参数传入打开的设备描述符;
然后第二个参数是头文件里面的宏定义,不同的宏定义使ioctl执行不同的操作,VIDIOC_ENUM_FMT表示列举出设备支持格式;
第三个参数是可变参数,更具宏定义后面的拓展而定
/*
* I O C T L C O D E S F O R V I D E O D E V I C E S
*
*/
#define VIDIOC_QUERYCAP _IOR('V', 0, struct v4l2_capability)
#define VIDIOC_ENUM_FMT _IOWR('V', 2, struct v4l2_fmtdesc)
#define VIDIOC_G_FMT _IOWR('V', 4, struct v4l2_format)
#define VIDIOC_S_FMT _IOWR('V', 5, struct v4l2_format)
#define VIDIOC_REQBUFS _IOWR('V', 8, struct v4l2_requestbuffers)
#define VIDIOC_QUERYBUF _IOWR('V', 9, struct v4l2_buffer)
#define VIDIOC_G_FBUF _IOR('V', 10, struct v4l2_framebuffer)
#define VIDIOC_S_FBUF _IOW('V', 11, struct v4l2_framebuffer)
#define VIDIOC_OVERLAY _IOW('V', 14, int)
#define VIDIOC_QBUF _IOWR('V', 15, struct v4l2_buffer)
VIDIOC_QUERYCAP 查询设备的属性/能力/功能
VIDIOC_ENUM_FMT 枚举设备支持的像素格式
VIDIOC_G_FMT 获取设备当前的帧格式信息
VIDIOC_S_FMT 设置帧格式信息
VIDIOC_REQBUFS 申请帧缓冲
VIDIOC_QUERYBUF 查询帧缓冲
VIDIOC_QBUF 帧缓冲入队操作
VIDIOC_DQBUF 帧缓冲出队操作
VIDIOC_STREAMON 开启视频采集
VIDIOC_STREAMOFF 关闭视频采集
VIDIOC_G_PARM 获取设备的一些参数
VIDIOC_S_PARM 设置参数
VIDIOC_TRY_FMT 尝试设置帧格式、用于判断设备是否支持该格式
VIDIOC_ENUM_FRAMESIZES 枚举设备支持的视频采集分辨率
VIDIOC_ENUM_FRAMEINTERVALS 枚举设备支持的视频采集帧率
可以看到这里使用VIDIOC_ENUM_FMT就需要在可变参数传入 struct v4l2_fmtdesc
==在v4l2编程获取设备可支持的数据格式时,需要在v4l2_fmtdesc结构体里面初始化v4fmt.type成员==
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //用于单平面
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; //用于多平面
==除此以外,还需要把其余v4l2_fmtdesc成员全部设置成0==
memset(&v4fmt,0,sizeof(v4fmt));
pixelformat字段则是对应的像素格式编号,这是一个无符号32位数据,每一种像素格式都会使用一个u32类型数据来表示
接下来就是打印获取的支持设备
printf("index=%d\n", v4fmt.index);
printf("flags=%d\n", v4fmt.flags);
printf("description=%s\n", v4fmt.description);
unsigned char *p = (unsigned char *)&v4fmt.pixelformat;
printf("pixelformat=%c%c%c%c\n", p[0],p[1],p[2],p[3]);
printf("reserved=%d\n", v4fmt.reserved[0]);
其中pixelformat虽然是一个32位数据,但是其实是每个字节代表一个ascii字符
index表示编号,在枚举之前,需将其设置为0,然后每次ioctl()调用之后将其值加1。一次ioctl()调用只能得到一种像素格式的信息,如果设备支持多种像素格式,则需要循环调用多次,通过index来控制,index从0开始,调用一次ioctl()之后加1,直到ioctl()调用失败,表示已经将所有像素格式都枚举出来了;所以index就是一个编号,获取index编号指定的像素格式。
枚举摄像头所支持的所有视频采集分辨率
使用VIDIOC_ENUM_FRAMESIZES
使用VIDIOC_ENUM_FRAMESIZES指令可以枚举出设备所支持的所有视频采集分辨率
ioctl(int fd, VIDIOC_ENUM_FRAMESIZES, struct v4l2_frmsizeenum *frmsize);
struct v4l2_frmsizeenum结构体描述了视频帧大小相关的信息,我们来看看struct v4l2_frmsizeenum结构体的定义:
struct v4l2_frmsizeenum {
__u32 index; /* Frame size number */
__u32 pixel_format; /* 像素格式 */
__u32 type; /* type */
union { /* Frame size */
struct v4l2_frmsize_discrete discrete;
struct v4l2_frmsize_stepwise stepwise;
};
__u32 reserved[2]; /* Reserved space for future use */
};
struct v4l2_frmsize_discrete {
__u32 width; /* Frame width [pixel] */
__u32 height; /* Frame height [pixel] */
};
index字段与struct v4l2_fmtdesc结构体的index字段意义相同,一个摄像头通常支持多种不同的视频采集分辨率,一次ioctl()调用只能得到一种视频帧大小信息,如果设备支持多种视频帧大小,则需要循环调用多次,通过index来控制。pixel_format字段指定像素格式,对应在头文件中有不同格式的宏定义/* RGB formats */ #define V4L2_PIX_FMT_RGB332 v4l2_fourcc('R', 'G', 'B', '1') /* 8 RGB-3-3-2 */ #define V4L2_PIX_FMT_RGB444 v4l2_fourcc('R', '4', '4', '4') /* 16 xxxxrrrr ggggbbbb */ #define V4L2_PIX_FMT_ARGB444 v4l2_fourcc('A', 'R', '1', '2') /* 16 aaaarrrr ggggbbbb */ #define V4L2_PIX_FMT_XRGB444 v4l2_fourcc('X', 'R', '1', '2') /* 16 xxxxrrrr ggggbbbb */ #define V4L2_PIX_FMT_RGB555 v4l2_fourcc('R', 'G', 'B', 'O') /* 16 RGB-5-5-5 */ #define V4L2_PIX_FMT_ARGB555 v4l2_fourcc('A', 'R', '1', '5') /* 16 ARGB-1-5-5-5 */ #define V4L2_PIX_FMT_XRGB555 v4l2_fourcc('X', 'R', '1', '5') /* 16 XRGB-1-5-5-5 */ #define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R', 'G', 'B', 'P') /* 16 RGB-5-6-5 */ ...... /* Grey formats */ #define V4L2_PIX_FMT_GREY v4l2_fourcc('G', 'R', 'E', 'Y') /* 8 Greyscale */ #define V4L2_PIX_FMT_Y4 v4l2_fourcc('Y', '0', '4', ' ') /* 4 Greyscale */ #define V4L2_PIX_FMT_Y6 v4l2_fourcc('Y', '0', '6', ' ') /* 6 Greyscale */ #define V4L2_PIX_FMT_Y10 v4l2_fourcc('Y', '1', '0', ' ') /* 10 Greyscale */ ...... /* Luminance+Chrominance formats */ #define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y', 'U', 'Y', 'V') /* 16 YUV 4:2:2 */ #define V4L2_PIX_FMT_YYUV v4l2_fourcc('Y', 'Y', 'U', 'V') /* 16 YUV 4:2:2 */ #define V4L2_PIX_FMT_YVYU v4l2_fourcc('Y', 'V', 'Y', 'U') /* 16 YVU 4:2:2 */ #define V4L2_PIX_FMT_UYVY v4l2_fourcc('U', 'Y', 'V', 'Y') /* 16 YUV 4:2:2 */ ...... /* compressed formats */ #define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M', 'J', 'P', 'G') /* Motion-JPEG */ #define V4L2_PIX_FMT_JPEG v4l2_fourcc('J', 'P', 'E', 'G') /* JFIF JPEG */ #define V4L2_PIX_FMT_DV v4l2_fourcc('d', 'v', 's', 'd') /* 1394 */ #define V4L2_PIX_FMT_MPEG v4l2_fourcc('M', 'P', 'E', 'G') /* MPEG-1/2/4 Multiplexed */- 而
type字段与struct v4l2_fmtdesc结构体的type字段意义相同;在调用ioctl()之前,需要先设置type字段与pixel_format字段,确定我们将要枚举的是:==设备的哪种功能、哪种像素格式==支持的视频帧大小。 - 可以看到
struct v4l2_frmsizeenum结构体中有一个union共用体,type= V4L2_BUF_TYPE_VIDEO_CAPTURE情况下,discrete生效,这是一个struct v4l2_frmsize_discrete类型变量,描述了视频帧大小信息(包括视频帧的宽度和高度),也就是视频采集分辨率大小。
代码如下:
//get the frameSize of appointed pixpixelformat
struct v4l2_frmsizeenum frmsize;
memset(&frmsize,0,sizeof(frmsize));
frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
frmsize.pixel_format = V4L2_PIX_FMT_YUYV; //对应YUYV 4 2 2
while(ioctl(fd,VIDIOC_ENUM_FRAMESIZES,&frmsize) == 0)
{
printf("frame_size <%d * %d>\r\n",frmsize.discrete.width,frmsize.discrete.height);
frmsize.index++;
}
枚举摄像头所支持的所有视频采集帧率
VIDIOC_ENUM_FRAMEINTERVALS
同一种视频帧大小,摄像头可能会支持多种不同的视频采集帧率,譬如常见的15fps、30fps、45fps以及60fps等;使用VIDIOC_ENUM_FRAMEINTERVALS指令可以枚举出设备所支持的所有帧率,使用方式如下:
ioctl(int fd, VIDIOC_ENUM_FRAMEINTERVALS, struct v4l2_frmivalenum *frmival);
struct v4l2_frmivalenum结构体描述了视频帧率相关的信息,我们来看看struct v4l2_frmivalenum结构体的定义:
struct v4l2_frmivalenum {
__u32 index; /* Frame format index */
__u32 pixel_format;/* Pixel format */
__u32 width; /* Frame width */
__u32 height; /* Frame height */
__u32 type; /* type */
union { /* Frame interval */
struct v4l2_fract discrete;
struct v4l2_frmival_stepwise stepwise;
};
__u32 reserved[2]; /* Reserved space for future use */
};
struct v4l2_fract {
__u32 numerator; //分子
__u32 denominator; //分母
};
index、type字段与struct v4l2_frmsizeenum结构体的index、type字段意义相同。width、height字段用于指定视频帧大小,pixel_format字段指定像素格式。
以上这些字段都是需要在调用ioctl()之前设置它的值。
可以看到struct v4l2_frmivalenum结构体也有一个union共用体,当type= V4L2_BUF_TYPE_VIDEO_CAPTURE时,discrete生效,这是一个struct v4l2_fract类型变量,描述了视频帧率信息(一秒钟采集图像的次数);struct v4l2_fract结构体中,numerator表示分子、denominator表示分母,使用numerator / denominator来表示图像采集的周期(采集一幅图像需要多少秒),所以==视频帧率便等于denominator / numerator。==
查看或设置当前的格式
查看当前格式
VIDIOC_G_FMT
可以使用VIDIOC_G_FMT指令查看设备当期的格式
int ioctl(int fd, VIDIOC_G_FMT, struct v4l2_format *fmt);
设置当前格式
VIDIOC_S_FMT
使用VIDIOC_S_FMT指令设置设备的格式
int ioctl(int fd, VIDIOC_S_FMT, struct v4l2_format *fmt);
struct v4l2_format结构体
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;
};
type字段依然与前面介绍的结构体中的type字段意义相同,不管是获取格式、还是设置格式都需要在调用ioctl()函数之前设置它的值。- 接下来是一个
union共用体,当type被设置为V4L2_BUF_TYPE_VIDEO_CAPTURE时,pix变量生效,它是一个struct v4l2_pix_format类型变量,记录了视频帧格式相关的信息struct v4l2_pix_format { __u32 width; //视频帧的宽度(单位:像素) __u32 height; //视频帧的高度(单位:像素) __u32 pixelformat; //像素格式 __u32 field; /* enum v4l2_field */ __u32 bytesperline; /* for padding, zero if unused */ __u32 sizeimage; __u32 colorspace; /* enum v4l2_colorspace */ __u32 priv; /* private data, depends on pixelformat */ __u32 flags; /* format flags (V4L2_PIX_FMT_FLAG_*) */ union { /* enum v4l2_ycbcr_encoding */ __u32 ycbcr_enc; /* enum v4l2_hsv_encoding */ __u32 hsv_enc; }; __u32 quantization; /* enum v4l2_quantization */ __u32 xfer_func; /* enum v4l2_xfer_func */ }; colorspace字段描述的是一个颜色空间,可取值如下: enum v4l2_colorspace { /* * Default colorspace, i.e. let the driver figure it out. * Can only be used with video capture. */ V4L2_COLORSPACE_DEFAULT = 0, /* SMPTE 170M: used for broadcast NTSC/PAL SDTV */ V4L2_COLORSPACE_SMPTE170M = 1, /* Obsolete pre-1998 SMPTE 240M HDTV standard, superseded by Rec 709 */ V4L2_COLORSPACE_SMPTE240M = 2, /* Rec.709: used for HDTV */ V4L2_COLORSPACE_REC709 = 3, /* * Deprecated, do not use. No driver will ever return this. This was * based on a misunderstanding of the bt878 datasheet. */ V4L2_COLORSPACE_BT878 = 4, /* * NTSC 1953 colorspace. This only makes sense when dealing with * really, really old NTSC recordings. Superseded by SMPTE 170M. */ V4L2_COLORSPACE_470_SYSTEM_M = 5, /* * EBU Tech 3213 PAL/SECAM colorspace. This only makes sense when * dealing with really old PAL/SECAM recordings. Superseded by * SMPTE 170M. */ V4L2_COLORSPACE_470_SYSTEM_BG = 6, /* * Effectively shorthand for V4L2_COLORSPACE_SRGB, V4L2_YCBCR_ENC_601 * and V4L2_QUANTIZATION_FULL_RANGE. To be used for (Motion-)JPEG. */ V4L2_COLORSPACE_JPEG = 7, /* For RGB colorspaces such as produces by most webcams. */ V4L2_COLORSPACE_SRGB = 8, /* AdobeRGB colorspace */ V4L2_COLORSPACE_ADOBERGB = 9, /* BT.2020 colorspace, used for UHDTV. */ V4L2_COLORSPACE_BT2020 = 10, /* Raw colorspace: for RAW unprocessed images */ V4L2_COLORSPACE_RAW = 11, /* DCI-P3 colorspace, used by cinema projectors */ V4L2_COLORSPACE_DCI_P3 = 12, };使用VIDIOC_S_FMT指令设置格式时,通常不需要用户指定colorspace,底层驱动会根据像素格式pixelformat来确定对应的colorspace。
代码如下:
//get the fmt the camera setted now
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_G_FMT,&fmt);
if(ret < 0)
{
perror("get fmt fail");
return -1;
}
unsigned char *fmt_ptr = (unsigned char *)&fmt.fmt.pix.pixelformat;
printf("width: %d, height: %d, format: %c%c%c%c colorSpace: %d\r\n",fmt.fmt.pix.width,fmt.fmt.pix.height,fmt_ptr[0],fmt_ptr[1],fmt_ptr[2],fmt_ptr[3],fmt.fmt.pix.colorspace);
//set the fmt of the camera
fmt.fmt.pix.width = 800;
fmt.fmt.pix.height = 600;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
ret = ioctl(fd,VIDIOC_S_FMT,&fmt);
if(ret < 0)
{
perror("set fmt fail");
return -1;
}
设置或获取当前的流类型相关参数
获取参数
VIDIOC_G_PARM
使用VIDIOC_G_PARM指令可以获取设备的流类型相关参数(Stream type-dependent parameters)
ioctl(int fd, VIDIOC_G_PARM, struct v4l2_streamparm *streamparm);
设置参数
VIDIOC_S_PARM
使用VIDIOC_S_PARM指令设置设备的流类型相关参数
ioctl(int fd, VIDIOC_S_PARM, struct v4l2_streamparm *streamparm);
struct v4l2_streamparm结构体
struct v4l2_streamparm {
__u32 type; /* enum v4l2_buf_type */
union {
struct v4l2_captureparm capture;
struct v4l2_outputparm output;
__u8 raw_data[200]; /* user-defined */
} parm;
};
struct v4l2_captureparm {
__u32 capability; /* Supported modes */
__u32 capturemode; /* Current mode */
struct v4l2_fract timeperframe; /* Time per frame in seconds */
__u32 extendedmode; /* Driver-specific extensions */
__u32 readbuffers; /* # of buffers for read */
__u32 reserved[4];
};
struct v4l2_fract {
__u32 numerator; /* 分子 */
__u32 denominator; /* 分母 */
};
type字段与前面一样,不再介绍,在调用ioctl()之前需先设置它的值。- 当
type= V4L2_BUF_TYPE_VIDEO_CAPTURE时,union共用体中capture变量生效,它是一个struct v4l2_captureparm类型变量,struct v4l2_captureparm结构体描述了摄像头采集相关的一些参数,譬如视频采集帧率struct v4l2_captureparm结构体中,capability字段表示设备支持的模式有哪些,可取值如下(以下任意一个或多个的位或关系)/* Flags for ‘capability’ and ‘capturemode’ fields / #define V4L2_MODE_HIGHQUALITY 0x0001 / High quality imaging mode 高品质成像模式 / #define V4L2_CAP_TIMEPERFRAME 0x1000 / timeperframe field is supported 支持设置timeperframe字段 */capturemode则表示当前的模式,与capability字段的取值相同。 timeperframe字段是一个struct v4l2_fract结构体类型变量,描述了设备视频采集的周期,使用VIDIOC_S_PARM可以设置视频采集的周期,也就是视频采集帧率,但是很多设备并不支持应用层设置timeperframe字段,只有当capability字段包含V4L2_CAP_TIMEPERFRAME时才表示设备支持timeperframe字段,这样应用层才可以去设置设备的视频采集帧率。
所以,在设置之前,先通过VIDIOC_G_PARM命令获取到设备的流类型相关参数,判断capability字段是否包含V4L2_CAP_TIMEPERFRAME
代码如下:
//get the streamParam of the dev now
struct v4l2_streamparm streamparam;
streamparam.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd,VIDIOC_G_PARM,&streamparam);
if(ret < 0)
{
perror("fail to get streamParm");
close(fd);
return -1;
}
printf("cap : %08x\r\ncurrent mode : %08x\r\n interval: %d",
streamparam.parm.capture.capability,
streamparam.parm.capture.capturemode, streamparam.parm.capture.timeperframe.denominator/streamparam.parm.capture.timeperframe.numerator);
if(V4L2_CAP_TIMEPERFRAME && streamparam.parm.capture.capability)
{
printf("the dev surpport set interval\r\n");
streamparam.parm.capture.timeperframe.denominator = 10;
streamparam.parm.capture.timeperframe.numerator = 1;
ret = ioctl(fd,VIDIOC_S_PARM,&streamparam);
if(ret < 0)
{
perror("failed to set streamParm");
close(fd);
return -1;
}
}
申请帧缓冲
读取摄像头数据的方式有两种,一种是*read方式*,也就是直接通过read()系统调用读取摄像头采集到的数据;另一种则是*streaming方式*;
使用VIDIOC_QUERYCAP指令查询设备的属性、得到一个struct v4l2_capability类型数据,其中capabilities字段记录了设备拥有的能力:
- 当该字段包含
V4L2_CAP_READWRITE时,表示设备支持read I/O方式读取数据; - 当该字段包含
V4L2_CAP_STREAMING时,表示设备支持streaming I/O方式;
事实上,绝大部分设备都支持streaming I/O方式读取数据,使用streaming I/O方式,我们需要向设备申请帧缓冲,并将帧缓冲映射到应用程序进程地址空间中。
当完成对设备的配置之后,接下来就可以去申请帧缓冲了,帧缓冲顾名思义就是用于存储一帧图像数据的缓冲区,使用VIDIOC_REQBUFS指令可申请帧缓冲
ioctl(int fd, VIDIOC_REQBUFS, struct v4l2_requestbuffers *reqbuf);
调用ioctl()需要传入一个*struct v4l2_requestbuffers *指针*,struct v4l2_requestbuffers结构体描述了申请帧缓冲的信息,ioctl()会根据reqbuf所指对象填充的信息进行申请。
struct v4l2_requestbuffers {
__u32 count; //申请帧缓冲的数量
__u32 type; /* enum v4l2_buf_type */
__u32 memory; /* enum v4l2_memory */
__u32 reserved[2];
};
type字段与前面所提及到的type字段意义相同,不再介绍,在调用ioctl()之前需先设置它的值。count字段用于指定申请帧缓冲的数量。memory字段可取值如下:enum v4l2_memory { V4L2_MEMORY_MMAP = 1, V4L2_MEMORY_USERPTR = 2, V4L2_MEMORY_OVERLAY = 3, V4L2_MEMORY_DMABUF = 4, };通常将memory设置为V4L2_MEMORY_MMAP即可struct v4l2_requestbuffers reqbuf; reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.count = 3; // 申请3个帧缓冲 reqbuf.memory = V4L2_MEMORY_MMAP; if (0 > ioctl(fd, VIDIOC_REQBUFS, &reqbuf)) { fprintf(stderr, “ioctl error: VIDIOC_REQBUFS: %s\n”, strerror(errno)); return -1; }
streaming I/O方式会在内核空间中维护一个帧缓冲队列,驱动程序会将从摄像头读取的一帧数据写入到队列中的一个帧缓冲,接着将下一帧数据写入到队列中的下一个帧缓冲;

内存映射
使用VIDIOC_REQBUFS指令申请帧缓冲,该缓冲区实质上是由内核所维护的,应用程序不能直接读取该缓冲区的数据,我们需要将其映射到用户空间中,这样,应用程序读取映射区的数据实际上就是读取内核维护的帧缓冲中的数据。
在映射之前,需要查询帧缓冲的信息,譬如帧缓冲的长度、偏移量等信息,使用VIDIOC_QUERYBUF指令查询,使用方式如下所示:
ioctl(int fd, VIDIOC_QUERYBUF, struct v4l2_buffer *buf);
调用ioctl()需要传入一个struct v4l2_buffer *指针,struct v4l2_buffer结构体描述了帧缓冲的信息,ioctl()会将获取到的数据写入到buf指针所指的对象中。
struct v4l2_buffer {
__u32 index; //buffer的编号
__u32 type; //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; //buffer的长度
__u32 reserved2;
__u32 reserved;
};
index字段表示一个编号,申请的多个帧缓冲、每一个帧缓冲都有一个编号,从0开始。一次ioctl()调用只能获取指定编号对应的帧缓冲的信息,所以要获取多个帧缓冲的信息,需要重复调用多次,每调用一次ioctl()、index加1,指向下一个帧缓冲。type字段与前面所提及到的type字段意义相同,不再介绍,在调用ioctl()之前需先设置它的值。memory字段与struct v4l2_requestbuffers结构体的memory字段意义相同,需要在调用ioctl()之前设置它的值。length字段表示帧缓冲的长度- 而共同体中的
offset则表示帧缓冲的偏移量,如何理解这个偏移量?因为应用程序通过VIDIOC_REQBUFS指令申请帧缓冲时,内核会向操作系统申请一块内存空间作为帧缓冲区,这块内存空间的大小就等于申请的帧缓冲数量 * 每一个帧缓冲的大小,每一个帧缓冲对应到这一块内存空间的某一段,所以它们都有一个地址偏移量。
帧缓冲的数量不要太多了,尤其是在一些内存比较吃紧的嵌入式系统中,帧缓冲的数量太多,势必会占用太多的系统内存。==一般帧缓冲不超过四个==
struct v4l2_requestbuffers reqbuf;
struct v4l2_buffer buf;
void *frm_base[3];
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.count = 3; // 申请3个帧缓冲
reqbuf.memory = V4L2_MEMORY_MMAP;
/* 申请3个帧缓冲 */
if (0 > ioctl(fd, VIDIOC_REQBUFS, &reqbuf)) {
fprintf(stderr, "ioctl error: VIDIOC_REQBUFS: %s\n", strerror(errno));
return -1;
}
/* 建立内存映射 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
for (buf.index = 0; buf.index < 3; buf.index++) {
ioctl(fd, VIDIOC_QUERYBUF, &buf);
frm_base[buf.index] = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd, buf.m.offset);
if (MAP_FAILED == frm_base[buf.index]) {
perror("mmap error");
return -1;
}
}
在上述的示例中,我们会将三个帧缓冲映射到用户空间,并将每一个帧缓冲对应的映射区的起始地址保存在frm_base数组中,后面读取摄像头采集的数据时,直接读取映射区即可。
入队操作
使用VIDIOC_QBUF指令将帧缓冲放入到内核的帧缓冲队列中
ioctl(int fd, VIDIOC_QBUF, struct v4l2_buffer *buf);
/* 入队操作 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
for (buf.index = 0; buf.index < 3; buf.index++) {
if (0 > ioctl(fd, VIDIOC_QBUF, &buf)) {
perror("ioctl error");
return -1;
}
}
开启视频采集
将三个帧缓冲放入到队列中之后,接着便可以打开摄像头、开启图像采集了,使用VIDIOC_DQBUF指令开启视频采集
ioctl(int fd, VIDIOC_STREAMON, int *type); //开启视频采集
ioctl(int fd, VIDIOC_STREAMOFF, int *type); //停止视频采集
type其实一个enum v4l2_buf_type *指针,通常用法如下:
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (0 > ioctl(fd, VIDIOC_STREAMON, &type)) {
perror(“ioctl error”);
return -1;
}
读取数据、对数据进行处理
开启视频采集之后,接着便可以去读取数据了
前面已经说过,直接读取每一个帧缓冲的在用户空间的映射区即可读取到摄像头采集的每一帧图像数据。在读取数据之前,需要将帧缓冲从内核的帧缓冲队列中取出,这个操作叫做帧缓冲出队(有入队自然就有出队)
使用VIDIOC_DQBUF指令执行出队操作,使用方式如下:
ioctl(int fd, VIDIOC_DQBUF, struct v4l2_buffer *buf);
帧缓冲出队之后,接下来便可读取数据了,然后对数据进行处理,譬如将摄像头采集的图像显示到LCD屏上;数据处理完成之后,再将帧缓冲入队,将队列中的下一个帧缓冲出队,然后读取数据、处理,这样往复操作。
struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
for ( ; ; ) {
for(buf.index = 0; buf.index < 3; buf.index++) {
ioctl(fd, VIDIOC_DQBUF, &buf); //出队
// 读取帧缓冲的映射区、获取一帧数据
// 处理这一帧数据
do_something();
// 数据处理完之后、将当前帧缓冲入队、接着读取下一帧数据
ioctl(fd, VIDIOC_QBUF, &buf);
}
}
结束视频采集
如果要结束视频采集,使用VIDIOC_STREAMOFF指令
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (0 > ioctl(fd, VIDIOC_STREAMOFF, &type)) {
perror(“ioctl error”);
return -1;
}
所遇到的问题
对于缓冲区队列的使用,在每次VIDIOC_DQBUF之前都需要将缓冲区结构体清零,只指定type
struct v4l2_buffer readbuffer = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
};
keep_capture = 1;
while(keep_capture)
{
ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer); // ← 第一次 OK
// ... 处理 ...
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer); // ← 归还
// ⚠️ 下一次循环:readbuffer.index 仍是上次的值!
// ⚠️ 而且 .memory 字段未设置(虽然 mmap 模式下可能被忽略,但 .index 错了!)
}
VIDIOC_DQBUF会填充readbuffer.index(比如 2)。- 归还后,下一次调用
VIDIOC_DQBUF前,必须将readbuffer重置为“只包含 type”。 - 否则,内核可能认为指定了
.index,从而行为异常(比如阻塞、返回错误、或重复同一buffer)。