V4L2快速上手指南

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方式会在内核空间中维护一个帧缓冲队列,驱动程序会将从摄像头读取的一帧数据写入到队列中的一个帧缓冲,接着将下一帧数据写入到队列中的下一个帧缓冲;

queue

内存映射

使用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)。

上一篇
下一篇