一、v4l2基础

V4l2是V4L的升级版本,为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写,摄像头在/dev/video0下。

经典例程:​​https://linuxtv.org/downloads/legacy/video4linux/API/V4L2_API/v4l2spec/capture.c​

二、v4l2工具

常用的命令行工具v4l2-ctl在包v4l-utils中,可通过sudo apt install v4l-utils安装。

# 通过v4l2查看摄像头设备

$ sudo v4l2-ctl --list-devices

# 查看当前摄像头支持的视频格式、分辨率和帧速率

$ sudo v4l2-ctl -d /dev/video0 --list-formats

$ sudo v4l2-ctl -d /dev/video0 --list-formats-ext

# 查看摄像头所有参数

$ sudo v4l2-ctl -d  /dev/video0 --all

# 查看摄像头所支持的分辨率

 sudo v4l2-ctl --list-framesizes=MJPG -d /dev/video0

#设置

v4l2-ctl --set-parm=30

v4l2-ctl --set-fmt-video=width=320,height=240,pixelformat=YUYV



$ v4l2-ctl help
unknown arguments: help

General/Common options:
--all display all information available
-C, --get-ctrl=<ctrl>[,<ctrl>...]
get the value of the controls [VIDIOC_G_EXT_CTRLS]
-c, --set-ctrl=<ctrl>=<val>[,<ctrl>=<val>...]
set the value of the controls [VIDIOC_S_EXT_CTRLS]
-D, --info show driver info [VIDIOC_QUERYCAP]
-d, --device=<dev> use device <dev> instead of /dev/video0
if <dev> starts with a digit, then /dev/video<dev> is used
-e, --out-device=<dev> use device <dev> for output streams instead of the
default device as set with --device
if <dev> starts with a digit, then /dev/video<dev> is used
-h, --help display this help message
--help-all all options
--help-io input/output options
--help-misc miscellaneous options
--help-overlay overlay format options
--help-sdr SDR format options
--help-selection crop/selection options
--help-stds standards and other video timings options
--help-streaming streaming options
--help-tuner tuner/modulator options
--help-vbi VBI format options
--help-vidcap video capture format options
--help-vidout vidout output format options
--help-edid edid handling options
-k, --concise be more concise if possible.
-l, --list-ctrls display all controls and their values [VIDIOC_QUERYCTRL]
-L, --list-ctrls-menus
display all controls and their menus [VIDIOC_QUERYMENU]
-r, --subset=<ctrl>[,<offset>,<size>]+
the subset of the N-dimensional array to get/set for control <ctrl>,
for every dimension an (<offset>, <size>) tuple is given.
-w, --wrapper use the libv4l2 wrapper library.
--list-devices list all v4l devices
--log-status log the board status in the kernel log [VIDIOC_LOG_STATUS]
--get-priority query the current access priority [VIDIOC_G_PRIORITY]
--set-priority=<prio>
set the new access priority [VIDIOC_S_PRIORITY]
<prio> is 1 (background), 2 (interactive) or 3 (record)
--silent only set the result code, do not print any messages
--sleep=<secs> sleep <secs>, call QUERYCAP and close the file handle
--verbose turn on verbose ioctl status reporting


三、常用数据结构

常用的IOCTL接口命令在include/linux/videodev2.h中定义



VIDIOC_REQBUFS //向设备申请缓存区
VIDIOC_QUERYBUF //获取缓存帧地址、长度(把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址)
VIDIOC_QUERYCAP //查询驱动功能
VIDIOC_ENUM_FMT //获取当前驱动支持的视频格式
VIDIOC_S_FMT //设置当前驱动的视频捕获格式
VIDIOC_G_FMT //读取当前驱动的视频捕获格式
VIDIOC_TRY_FMT //验证当前驱动的显示格式
VIDIOC_ENUM_FRAMESIZES //枚举设备支持的分辨率信息
VIDIOC_ENUM_FRAMEINTERVALS //获取设备支持的帧间隔
VIDIOC_G_PARM //获取视频流参数(频率等)
VIDIOC_S_PARM //设定视频流参数(频率等)
VIDIOC_CROPCAP //查询驱动的修剪能力
VIDIOC_S_CROP //设置视频信号的矩形边框
VIDIOC_G_CROP //读取视频信号的矩形边框
VIDIOC_QBUF //把数据从缓存中读取出来
VIDIOC_DQBUF //把数据放回缓存队列
VIDIOC_STREAMON //开始视频显示函数
VIDIOC_STREAMOFF //结束视频显示函数
VIDIOC_QUERYSTD //检查当前视频设备支持的标准,例如PAL或NTSC。


常用的结构体在系统目录/usr/include/linux/videodev2.h。



struct v4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFS    memory-mapping buffers
struct v4l2_capability //视频设备的功能,对应命令VIDIOC_QUERYCAP
struct v4l2_input //视频输入信息,对应命令VIDIOC_ENUMINPUT
struct v4l2_standard //视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
struct v4l2_format //视频格式、帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
struct v4l2_fmtdesc //帧的格式描述,对应命令VIDIOC_ENUM_FMT等
struct v4l2_buffer //驱动中的一帧图像缓存信息,对应命令VIDIOC_QUERYBUF
struct v4l2_crop //视频信号矩形边框
struct v4l2_cropcap //设置摄像头的捕捉能力,在捕捉视频时应先设置
typedef __u64 v4l2_std_id //视频制式


重要数据结构详情



/*
* F O R M A T E N U M E R A T I O N
*/
struct v4l2_fmtdesc {
__u32 index; /* Format number,应用程序设置*/
__u32 type; /* enum v4l2_buf_type,帧类型*/
__u32 flags; /*是否为压缩格式*/
__u8 description[32]; /* 格式名称描述 */
__u32 pixelformat; /* Format fourcc,格式*/
__u32 reserved[4];
};
#define V4L2_FMT_FLAG_COMPRESSED 0x0001
#define V4L2_FMT_FLAG_EMULATED 0x0002


所有的视频格式可以能下面的方法查看

#define v4l2_fourcc(a, b, c, d) ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24))



/*
* V I D E O I M A G E F O R M A T
*/
struct v4l2_pix_format {
__u32 width;
__u32 height;
__u32 pixelformat; //帧格式 V4L2_PIX_FMT_MJPEG V4L2_PIX_FMT_YUYV
__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 */
};
/**
* struct v4l2_format - stream data format
* @type: enum v4l2_buf_type; type of the data stream
* @pix: definition of an image format
* @pix_mp: definition of a multiplanar image format
* @win: definition of an overlaid image
* @vbi: raw VBI capture or output parameters
* @sliced: sliced VBI capture or output parameters
* @raw_data: placeholder for future extensions and custom formats
*/
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_capability - Describes V4L2 device caps returned by VIDIOC_QUERYCAP
*
* @driver: name of the driver module (e.g. "bttv")
* @card: name of the card (e.g. "Hauppauge WinTV")
* @bus_info: name of the bus (e.g. "PCI:" + pci_name(pci_dev) )
* @version: KERNEL_VERSION
* @capabilities: capabilities of the physical device as a whole
* @device_caps: capabilities accessed via this particular device (node)
* @reserved: reserved fields for future extensions
*/
struct v4l2_capability {
__u8 driver[16]; //驱动名
__u8 card[32]; //设备名
__u8 bus_info[32]; //设备在系统中的位置
__u32 version; //驱动版本号
__u32 capabilities; //支持的操作 V4L2_CAP_VIDEO_CAPTURE, V4L2_CAP_STREAMING
__u32 device_caps; //设备能力
__u32 reserved[3];
};



/*
* M E M O R Y - M A P P I N G B U F F E R S
*/
struct v4l2_requestbuffers {
__u32 count;
__u32 type; /* enum v4l2_buf_type */
__u32 memory; /* enum v4l2_memory */
__u32 reserved[2];
};
/**
* struct v4l2_buffer - video buffer info
* @index: id number of the buffer
* @type: enum v4l2_buf_type; buffer type (type == *_MPLANE for
* multiplanar buffers);
* @bytesused: number of bytes occupied by data in the buffer (payload);
* unused (set to 0) for multiplanar buffers
* @flags: buffer informational flags
* @field: enum v4l2_field; field order of the image in the buffer
* @timestamp: frame timestamp
* @timecode: frame timecode
* @sequence: sequence count of this frame
* @memory: enum v4l2_memory; the method, in which the actual video data is
* passed
* @offset: for non-multiplanar buffers with memory == V4L2_MEMORY_MMAP;
* offset from the start of the device memory for this plane,
* (or a "cookie" that should be passed to mmap() as offset)
* @userptr: for non-multiplanar buffers with memory == V4L2_MEMORY_USERPTR;
* a userspace pointer pointing to this buffer
* @fd: for non-multiplanar buffers with memory == V4L2_MEMORY_DMABUF;
* a userspace file descriptor associated with this buffer
* @planes: for multiplanar buffers; userspace pointer to the array of plane
* info structs for this buffer
* @length: size in bytes of the buffer (NOT its payload) for single-plane
* buffers (when type != *_MPLANE); number of elements in the
* planes array for multi-plane buffers
*
* Contains data exchanged by application and driver using one of the Streaming
* I/O methods.
*/
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_fract {
__u32 numerator;
__u32 denominator;
};
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];
};

/* Flags for 'capability' and 'capturemode' fields */
#define V4L2_MODE_HIGHQUALITY 0x0001 /* High quality imaging mode */
#define V4L2_CAP_TIMEPERFRAME 0x1000 /* timeperframe field is supported */

struct v4l2_outputparm {
__u32 capability; /* Supported modes */
__u32 outputmode; /* Current mode */
struct v4l2_fract timeperframe; /* Time per frame in seconds */
__u32 extendedmode; /* Driver-specific extensions */
__u32 writebuffers; /* # of buffers for write */
__u32 reserved[4];
};
/* Stream type-dependent parameters
*/
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;
};


帧分辨率

VIDIOC_ENUM_FRAMESIZES:针对VIDIOC_ENUM_FMT所列出的图像格式信息,列举某一格式所对应的分辨率信息。

v4l2_frmsizeenum->type可能有三种情况:

V4L2_FRMSIZE_TYPE_DISCRETE: 可以递增的设置v4l2_frmsizeenum->index来重复调用直到返回EINVAL来获取该种图像格式下所有支持的分辨率. 此时, 可以通过v4l2_frmsizeenum->width和height来获取支持的分辨率的长宽。

V4L2_FRMSIZE_TYPE_STEPWISE: 此时只有v4l2_frmsizeenum->stepwise是有效的, 并且不能再将index设为其他值重复调用此ioctl。

V4L2_FRMSIZE_TYPE_CONTINUOUS: STEPWISE的一种特殊情况, 此时同样只有stepwise有效, 并且stepwise.step_width和stepwise.step_height都为1。



int ioctl( int fd, int request, struct v4l2_frmsizeenum *arg);
struct v4l2_frmsizeenum
{
__u32 index; //IN:index of the given frame size in the enumeration
__u32 pixel_format; //IN:pixel format for which the frame sizes are enumerated
__u32 type; //OUT: frame size type the device supports
union //OUT: frame size with the given index
{
struct v4l2_frmsize_discrete discrete;
struct v4l2_frmsize_stepwise stepwise;
};
__u32 reserved[2];
};

struct v4l2_frmsize_discrete
{
__u32 width;
__u32 height;
};

struct v4l2_frmsize_stepwise
{
__u32 min_width; //minimum frame width[pixel]
__u32 max_width; //maximum frame width[pixel]
__u32 step_width; //frame width step size[pixel]
__u32 min_height; //minimum frame height[pixel]
__u32 max_height; //maximum frame height[pixel]
__u32 step_height; //frame height step size[pixel]
};


Video帧

Video的帧可区分为隔行和逐行:逐行顺序的传输一帧所有的行,而隔行则把一帧划分成两个fields,分别保存帧的奇数行和偶数行,被称作奇场和偶场。

所有的video capture和out devices必须汇报当前的field顺序。一些驱动可能允许选择不同的序,终端应用可以在调用VIDIOC_S_FMT前初始化struct v4l2_pix_format的field成员,否则可以使用V4L2_FIELD_ANY。


V4L2_FIELD_ANY



0



Application 可以请求使用这个参数,如果V4L2_FIELD_NONE, V4L2_FIELD_TOP, V4L2_FIELD_BOTTOM,V4L2_FIELD_INTERLACE 中任何一个格式都支持.驱动选择使用哪一个格式依赖于硬件能力,以及请求的image尺寸,驱动选择一个然后返回这个格式。struct_buffer的field成员不可以为V4L2_FIELD_ANY。



V4L2_FIELD_NONE



1



Images是逐行格式,当驱动无法区分V4L2_FIELD_TOP和V4L2_FIELD_BOTTOM,可以使用这种field类型



V4L2_FIELD_TOP



2



Images仅仅包含top field



V4L2_FIELD_BOTTOM



3



Images仅仅包含bottom field,应用可能希望防止设备捕获interlaced的图片,因为这种图片会在运动物体周围产生毛状特效



V4L2_FIELD_INTERLACED



4



Images包含top和bottom field, 隔行交替,场序依赖于当前video的标准。NTSC首先传输bottom field, PAL则先传输top field。



V4L2_FIELD_SEQ_TB



5



Images包含top和bottom field, top field的行首先存放在memory中,然后紧跟着bottom field的行。 Fields一直以瞬间序存储,较老的放在内存前面。Images的尺寸和帧相关,而不是field

 



V4L2_FIELD_SEQ_BT



6



Images包含top和bottom field, bottom field的行首先存放在memory中,然后紧跟着top field的行。 Fields一直以瞬间序存储,较老的放在内存前面。Images的尺寸和帧相关,而不是field



V4L2_FIELD_ALTERATE



7



 一个帧的两个field分别放在不同的buffer, 按照瞬间序,也就是说老的一个是第一个。driver或者应用指明field的奇偶性(奇偶性:当前的field是top 还是bottom field). 任何两个连续的field构成一个frame,是否两个field是连续的,不需要drop掉他们,可以通过v4l2_buffer中的sequence 成员判定。Images的尺寸和frame相关而不是fields相关



V4L2_FIELD_INTERLACED_TB



8



Images 包含top和bottom field, 每行交替, top field在前面。top field首先传送



V4L2_FIELD_INTERLACED_BT



9



Images 包含top和bottom field, 每行交替, bottom field在前面。bottom field首先传送


四、采集流程

打开设备->获取和设置设备属性->设置输出输入->(申请缓冲及缓冲管理)->获取数据->关闭设备。

注:各类VIDIOC命令相关的ioctl接口函数返回值 ret,若小于零,则表示函数调用失败,若等于零,表示成功执行。

应用程序和设备有三种交换数据的方法,直接read/write、内存映射(mmap)和用户指针,一般采用内存映射方式。

  1. Open设备文件;
  2. 获取设备能力:VIDIOC_QUERYCAP;
  3. 枚举设备支持的视频格式:VIDIOC_ENUM_FMT;
  4. 设置捕获视频格式:VIDIOC_S_FMT,VIDIOC_G_FMT;
  5. 设置视频流信息:VIDIOC_S_PARM,VIDIOC_G_PARM;
  6. 向驱动申请帧缓存,一般不超过5个:VIDIOC_REQBUFS;申请的帧缓存位于内核空间,应用程序需要mmap访问。
  7. 获取每个缓存信息并映射(mmap)到用户空间:VIDIOC_QUERYBUF;将所有缓存放入视频采集队列:VIDIOC_QBUF。
  8. 开启视频采集:VIDIOC_STREAMON;
  9. 取出帧缓存:VIDIOC_DQBUF;处理数据;把处理后的帧缓存放入队列尾:VIDIOC_QBUF;如此循环。
  10. 停止采集:VIDIOC_STREAMOFF;
  11. 取出帧缓存:VIDIOC_DQBUF;解除映射munmap;关闭设备。

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