简介
安装
sudo apt-get install v4l-utils libv4l-dev
一般流程
打开设备,进行初始化参数设置,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
申请图像帧缓冲,并进行内存映射,将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取、处理图像数据;
将帧缓冲进行入队操作,启动视频采集;
驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
释放资源,停止采集工作。
常用命令标识符
#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)
#define VIDIOC_EXPBUF _IOWR('V', 16, struct v4l2_exportbuffer)
#define VIDIOC_DQBUF _IOWR('V', 17, struct v4l2_buffer)
#define VIDIOC_STREAMON _IOW('V', 18, int)
#define VIDIOC_STREAMOFF _IOW('V', 19, int)
#define VIDIOC_G_PARM _IOWR('V', 21, struct v4l2_streamparm)
#define VIDIOC_S_PARM _IOWR('V', 22, struct v4l2_streamparm)
#define VIDIOC_G_STD _IOR('V', 23, v4l2_std_id)
#define VIDIOC_S_STD _IOW('V', 24, v4l2_std_id)
#define VIDIOC_ENUMSTD _IOWR('V', 25, struct v4l2_standard)
#define VIDIOC_ENUMINPUT _IOWR('V', 26, struct v4l2_input)
#define VIDIOC_G_CTRL _IOWR('V', 27, struct v4l2_control)
#define VIDIOC_S_CTRL _IOWR('V', 28, struct v4l2_control)
#define VIDIOC_G_TUNER _IOWR('V', 29, struct v4l2_tuner)
#define VIDIOC_S_TUNER _IOW('V', 30, struct v4l2_tuner)
#define VIDIOC_G_AUDIO _IOR('V', 33, struct v4l2_audio)
#define VIDIOC_S_AUDIO _IOW('V', 34, struct v4l2_audio)
#define VIDIOC_QUERYCTRL _IOWR('V', 36, struct v4l2_queryctrl)
#define VIDIOC_QUERYMENU _IOWR('V', 37, struct v4l2_querymenu)
#define VIDIOC_G_INPUT _IOR('V', 38, int)
#define VIDIOC_S_INPUT _IOWR('V', 39, int)
#define VIDIOC_G_EDID _IOWR('V', 40, struct v4l2_edid)
#define VIDIOC_S_EDID _IOWR('V', 41, struct v4l2_edid)
#define VIDIOC_G_OUTPUT _IOR('V', 46, int)
#define VIDIOC_S_OUTPUT _IOWR('V', 47, int)
#define VIDIOC_ENUMOUTPUT _IOWR('V', 48, struct v4l2_output)
#define VIDIOC_G_AUDOUT _IOR('V', 49, struct v4l2_audioout)
#define VIDIOC_S_AUDOUT _IOW('V', 50, struct v4l2_audioout)
#define VIDIOC_G_MODULATOR _IOWR('V', 54, struct v4l2_modulator)
#define VIDIOC_S_MODULATOR _IOW('V', 55, struct v4l2_modulator)
#define VIDIOC_G_FREQUENCY _IOWR('V', 56, struct v4l2_frequency)
#define VIDIOC_S_FREQUENCY _IOW('V', 57, struct v4l2_frequency)
#define VIDIOC_CROPCAP _IOWR('V', 58, struct v4l2_cropcap)
#define VIDIOC_G_CROP _IOWR('V', 59, struct v4l2_crop)
#define VIDIOC_S_CROP _IOW('V', 60, struct v4l2_crop)
#define VIDIOC_G_JPEGCOMP _IOR('V', 61, struct v4l2_jpegcompression)
#define VIDIOC_S_JPEGCOMP _IOW('V', 62, struct v4l2_jpegcompression)
#define VIDIOC_QUERYSTD _IOR('V', 63, v4l2_std_id)
#define VIDIOC_TRY_FMT _IOWR('V', 64, struct v4l2_format)
#define VIDIOC_ENUMAUDIO _IOWR('V', 65, struct v4l2_audio)
#define VIDIOC_ENUMAUDOUT _IOWR('V', 66, struct v4l2_audioout)
#define VIDIOC_G_PRIORITY _IOR('V', 67, __u32) /* enum v4l2_priority */
#define VIDIOC_S_PRIORITY _IOW('V', 68, __u32) /* enum v4l2_priority */
#define VIDIOC_G_SLICED_VBI_CAP _IOWR('V', 69, struct v4l2_sliced_vbi_cap)
#define VIDIOC_LOG_STATUS _IO('V', 70)
#define VIDIOC_G_EXT_CTRLS _IOWR('V', 71, struct v4l2_ext_controls)
#define VIDIOC_S_EXT_CTRLS _IOWR('V', 72, struct v4l2_ext_controls)
#define VIDIOC_TRY_EXT_CTRLS _IOWR('V', 73, struct v4l2_ext_controls)
#define VIDIOC_ENUM_FRAMESIZES _IOWR('V', 74, struct v4l2_frmsizeenum)
#define VIDIOC_ENUM_FRAMEINTERVALS _IOWR('V', 75, struct v4l2_frmivalenum)
#define VIDIOC_G_ENC_INDEX _IOR('V', 76, struct v4l2_enc_idx)
#define VIDIOC_ENCODER_CMD _IOWR('V', 77, struct v4l2_encoder_cmd)
#define VIDIOC_TRY_ENCODER_CMD _IOWR('V', 78, struct v4l2_encoder_cmd)
VIDIOC_REQBUFS:分配内存;
VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址;
VIDIOC_QUERYCAP:查询驱动功能;
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式;
VIDIOC_S_FMT:设置当前驱动的视频捕获格式;
VIDIOC_G_FMT:读取当前驱动的视频捕获格式;
VIDIOC_TRY_FMT:验证当前驱动的显示格式;
VIDIOC_CROPCAP:查询驱动的修剪功能;
VIDIOC_S_CROP:设置视频信号的边框;
VIDIOC_G_CROP:读取视频信号的边框;
VIDIOC_QBUF:把数据从缓存中读取出来;
VIDIOC_DQBUF:把数据放回缓存队列;
VIDIOC_STREAMOP:开始视频显示函数;
VIDIOC_STREAMOFF:结束视频显示函数;
VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC;
API说明
查询设备属性(v4l2_capability)
查询设备属性需要使用struct v4l2_capability结构体,该结构体描述了视频采集设备的driver信息。
struct v4l2_capability
{
__u8 driver[16]; // 驱动名字
__u8 card[32]; // 设备名字
__u8 bus_info[32]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32 capabilities; // 设备支持的操作
__u32 reserved[4]; // 保留字段
};
通过VIDIOC_QUERYCAP命令来查询driver是否合乎规范。因为V4L2要求所有driver和device都支持这个ioctl。所以,通过VIDIOC_QUERYCAP命令是否成功来判断当前device和driver是否符合V4L2规范。当然,这个命令执行成功的同时还能够得到设备足够的信息,如struct v4l2_capability结构体所示内容。
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);
}
代码示例 - 列出所有设备[^1]
显示所有支持的格式(v4l2_fmtdesc)
struct v4l2_fmtdesc
{
__u32 index; // 要查询的格式序号,应用程序设置
enum v4l2_buf_type type; // 帧类型,应用程序设置
__u32 flags; // 是否为压缩格式
__u8 description[32]; // 格式名称
__u32 pixelformat; // 所支持的格式, fourcc
__u32 reserved[4]; // 保留
};
显示所有支持的格式需要用到struct v4l2_fmtdesc结构体,该结构体描述当前camera支持的格式信息。
使用VIDIOC_ENUM_FMT命令查询当前camera支持的所有格式。struct v4l2_fmtdesc结构体中index要设置,从0开始;enum v4l2_buf_type type也要设置,如果使用的是camera设备,则enum v4l2_buf_type type要设置为V4L2_BUF_TYPE_VIDEO_CAPTURE,因为camera是CAPTURE设备。结构体中的其他内容driver会填充。其中__u32 pixelformat参数在设置图像帧格式时需要使用。
列出可用格式列出可用格式[^2]
设置图像帧格式
设置图像格式需要用到struct v4l2_format结构体,该结构体描述每帧图像的具体格式,包括帧类型以及图像的长、宽等信息。
struct v4l2_format {
__u32 type;//镇类型, 应用程序设置
union {
struct v4l2_pix_format pix; // 视频设备使用
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结构体需要设置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支持的长、宽,如124~125行所示。
119 /* set format in */
120 GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);
121 memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
122 tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
123 tV4l2Fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;
124 tV4l2Fmt.fmt.pix.width = iLcdWidth;
125 tV4l2Fmt.fmt.pix.height = iLcdHeigt;
126 tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY;
127
128 /* 如果驱动程序发现无法某些参数(比如分辨率),
129 * 它会调整这些参数, 并且返回给应用程序
130 */
131 iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);
132 if (iError)
133 {
134 DBG_PRINTF("Unable to set format\n");
135 goto err_exit;
136 }
代码示例
列出所有设备
static void v4l2_device_list() {
std::string device = "/dev/";
bool cur_device_found = false;
size_t cur_device_index;
std::string cur_device_name;
fs::path dir("/sys/class/video4linux");
std::vector<std::string> device_list;
if (fs::exists(dir) && fs::is_directory(dir)) {
// 遍历该目录中的所有文件和目录
for (auto &entry: fs::directory_iterator(dir)) {
if (!entry.is_symlink()) {
continue;
}
device.resize(5);
device.append(entry.path().filename());
std::cout << device << endl;
int fd = open(device.c_str(), O_RDWR | O_NONBLOCK);
if (fd == -1) {
std::cout << "Unable to open " << device << endl;
continue;
}
struct v4l2_capability video_cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &video_cap) == -1) {
cout << "Failed to query capabilities for " << device << endl;
close(fd);
continue;
}
uint32_t caps = (video_cap.capabilities & V4L2_CAP_DEVICE_CAPS)
? video_cap.device_caps
: video_cap.capabilities;
if (!(caps & V4L2_CAP_VIDEO_CAPTURE)) {
cout << device << " seems to not support video capture" << endl;
close(fd);
continue;
}
/* make sure device names are unique */
char unique_device_name[68];
int ret = snprintf(unique_device_name,
sizeof(unique_device_name), "%s (%s)",
video_cap.card, video_cap.bus_info);
if (ret >= (int) sizeof(unique_device_name)) {
cout << "linux-v4l2: A format truncation may have occurred."
" This can be ignored since it is quite improbable." << endl;
}
device_list.emplace_back(device);
cout << "Found device " << video_cap.card << device << endl;
/* check if this is the currently used device */
if (!cur_device_name.empty() && cur_device_name == device) {
cur_device_found = true;
}
close(fd);
}
}
}
列出可用格式
static void v4l2_format_list(int fd) {
cout << "fd:" << fd << endl;
struct v4l2_fmtdesc fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.index = 0;
std::string buffer;
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == 0) {
buffer = (char *) fmt.description;
if (fmt.flags & V4L2_FMT_FLAG_EMULATED) {
buffer.append(" (Emulated)");
}
// 主流格式
if (fmt.pixelformat == V4L2_PIX_FMT_MJPEG ||
fmt.pixelformat == V4L2_PIX_FMT_H264) {
cout << "Pixelformat:" << buffer << "(available)" << endl;
} else {
cout << "Pixelformat: " << buffer << " (unavailable)" << endl;
}
fmt.index++;
}
}
v4l2-utils编译
https://linuxtv.org/downloads/v4l-utils/
wget https://linuxtv.org/downloads/v4l-utils/v4l-utils-1.24.1.tar.bz2
tar xvf v4l-utils-1.24.1.tar.bz2
cd v4l-utils-1.24.1
./confiure
make -j8
参考资料
[Linux 基础] -- V4L2 实例分析 —— vivi.c 源码详解(深度好文)
[^1]: ## 列出所有设备
[^2]: ## 列出可用格式