一、概述:
V4L2子系统向上提供了很多访问Video设备的接口,应用程序可以通过系统调用访问Video设备。但由于Video设备千差万别,很少有设备驱动程序能支持所有的接口功能,因此在使用之前,需要了解设备驱动程序支持的功能。
二、访问流程:
2.1.打开设备文件
视频设备与其他设备一样可以视为一个文件,所以使用open打开文件。可以是阻塞打开,也可以是非阻塞打开,非阻塞打开,若没有数据,则会返回错误。
代码语言:javascript复制 #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
......
int camera_fd;
camera_fd = open("/dev/video0", O_RDWR); // 阻塞打开
......
camera_fd = open("/dev/video0", O_RDWR | O_NONBLOCK); // 非阻塞打开
......
2.2.获取设备支持的功能:
在使用Video设备之前,需要了解Video设备支持哪些功能,如是图像捕获设备还是图像输出设备、可对视频信号进行何种调制。支持VBI、是否具有音频功能等。可通过VIDIOC_QUERYCAP命令获取Video设备支持的功能。最终通过检查struct v4l2_capability中的capabilities变量获取设备支持的功能。
代码语言:javascript复制 #include <sys/ioctl.h>
#include <linux/videodev2.h>
......
struct v4l2_capability cap = {0};
int ret = ioctl(camera_fd, VIDIOC_QUERYCAP, &capability);
......
// 判断是否支持某些功能
if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
printf("v4l2 device support video capturen");
if(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)
printf("v4l2 device support video outputn");
......
设备的功能保存在struct v4l2_capability结构体中,capabilities变量具体表示了设备具有的功能,功能由宏定义V4L2_CAP_XXXX表示:
代码语言:javascript复制 // 描述V4L2设备的功能,对应ioctl命令VIDIOC_QUERYCAP
struct v4l2_capability {
__u8 driver[16]; // 驱动模块的名称,如"bttv"
__u8 card[32]; // 品牌名称,如"Hauppauge WinTV"
__u8 bus_info[32]; // 总线名称,如"PCI:" pci_name(pci_dev)
__u32 version; // 版本信息,KERNEL_VERSION
__u32 capabilities; // 设备整体的功能
__u32 device_caps;
__u32 reserved[3];
};
// capabilities字段值的宏定义
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 // 图像捕获设备
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 // 图像输出设备
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 // 支持预览功能
#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
#define V4L2_CAP_VIDEO_M2M 0x00008000 /* Is a video mem-to-mem device */
#define V4L2_CAP_TUNER 0x00010000 // 有高频头
#define V4L2_CAP_AUDIO 0x00020000 // 支持音频
#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_READWRITE 0x01000000 // 支持read、write系统调用
#define V4L2_CAP_ASYNCIO 0x02000000 // 支持异步I/O
#define V4L2_CAP_STREAMING 0x04000000 // 支持流式I/O ioctl功能
#define V4L2_CAP_DEVICE_CAPS 0x80000000 /* sets device capabilities field */
2.3.选择设备输入:
Video设备可能有多个输入。如某些芯片上,摄像头控制器可以接多个摄像头,则需要选择哪一个摄像头作为输入源。若只有一个输入,则无需选择。VIDIOC_ENUMINPUT命令可以列出对应编号输入设备的信息,信息存放在struct v4l2_input结构体中。VIDIOC_S_INPUT可以通过编号指定当前的输入设备。
代码语言:javascript复制 #include <sys/ioctl.h>
#include <linux/videodev2.h>
......
struct v4l2_input input = {0};
// 枚举Video设备所有输入
while (!ioctl(camera_fd, VIDIOC_ENUMINPUT, &input)) {
printf("video input name: %sn", input.name);
input.index;
}
......
input.index = XX; // 指定输入的编号为XX
ret = ioctl(camera_fd, VIDIOC_S_INPUT, &input); // 设置编号为XX的输入为当前的输入设备
......
设备的输入信息保存在struct v4l2_input结构体中。
代码语言:javascript复制 struct v4l2_input {
__u32 index; /* Which input,用来确定输入设备 */
__u8 name[32]; /* Label */
__u32 type; /* Type of input */
__u32 audioset; /* Associated audios (bitfield) */
__u32 tuner; /* enum v4l2_tuner_type */
v4l2_std_id std;
__u32 status;
__u32 capabilities;
__u32 reserved[3];
};
/* Values for the 'type' field */
#define V4L2_INPUT_TYPE_TUNER 1
#define V4L2_INPUT_TYPE_CAMERA 2
/* field 'status' - general */
#define V4L2_IN_ST_NO_POWER 0x00000001 /* Attached device is off */
#define V4L2_IN_ST_NO_SIGNAL 0x00000002
#define V4L2_IN_ST_NO_COLOR 0x00000004
/* field 'status' - sensor orientation */
/* If sensor is mounted upside down set both bits */
#define V4L2_IN_ST_HFLIP 0x00000010 /* Frames are flipped horizontally */
#define V4L2_IN_ST_VFLIP 0x00000020 /* Frames are flipped vertically */
/* field 'status' - analog */
#define V4L2_IN_ST_NO_H_LOCK 0x00000100 /* No horizontal sync lock */
#define V4L2_IN_ST_COLOR_KILL 0x00000200 /* Color killer is active */
/* field 'status' - digital */
#define V4L2_IN_ST_NO_SYNC 0x00010000 /* No synchronization lock */
#define V4L2_IN_ST_NO_EQU 0x00020000 /* No equalizer lock */
#define V4L2_IN_ST_NO_CARRIER 0x00040000 /* Carrier recovery failed */
/* field 'status' - VCR and set-top box */
#define V4L2_IN_ST_MACROVISION 0x01000000 /* Macrovision detected */
#define V4L2_IN_ST_NO_ACCESS 0x02000000 /* Conditional access denied */
#define V4L2_IN_ST_VTR 0x04000000 /* VTR time constant */
/* capabilities flags */
#define V4L2_IN_CAP_DV_TIMINGS 0x00000002 /* Supports S_DV_TIMINGS */
#define V4L2_IN_CAP_CUSTOM_TIMINGS V4L2_IN_CAP_DV_TIMINGS /* For compatibility */
#define V4L2_IN_CAP_STD 0x00000004 /* Supports S_STD */
#define V4L2_IN_CAP_NATIVE_SIZE 0x00000008 /* Supports setting native size */
2.4.获取和设置像素格式
有些摄像头支持多个像素格式,有的摄像头只支持一种像素格式。因此在设置像素格式之前需要了解摄像头支持的像素格式,然后再进行设置。VIDIOC_ENUM_FMT命令枚举设备支持的像素格式,VIDIOC_S_FMT命令设置设备当前采用的像素格式。
代码语言:javascript复制 #include <sys/ioctl.h>
#include <linux/videodev2.h>
struct v4l2_fmtdesc fmtdesc = {0};
......
// 获取支持的像素格式
while (!ioctl(camera_fd, VIDIOC_ENUM_FMT, &fmtdesc)) {
printf("fmt: %sn", fmtdesc.description);
fmtdesc.index ;
}
......
// 设置像素格式
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 视频捕获模式
v4l2_fmt.fmt.pix.width = 720; // 视频宽度
v4l2_fmt.fmt.pix.height = 576; // 视频高度
v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // YUYV像素格式
v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;
ret = ioctl(camera_fd, VIDIOC_S_FMT, &fmt);
......
下面是用到的结构体和枚举定义。
代码语言:javascript复制 // 用于获取像素格式
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];
};
// 用于设置像素格式
struct v4l2_format {
__u32 type; // 数据流类型,由enum v4l2_buf_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 */
__u8 raw_data[200]; /* user-defined */
} fmt;
};
enum v4l2_buf_type {
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
V4L2_BUF_TYPE_VIDEO_OUTPUT = 2,
V4L2_BUF_TYPE_VIDEO_OVERLAY = 3,
V4L2_BUF_TYPE_VBI_CAPTURE = 4,
V4L2_BUF_TYPE_VBI_OUTPUT = 5,
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6,
V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7,
......
};
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_*) */
__u32 ycbcr_enc; /* enum v4l2_ycbcr_encoding */
__u32 quantization; /* enum v4l2_quantization */
};
enum v4l2_field {
V4L2_FIELD_ANY = 0, /* driver can choose from none,
top, bottom, interlaced
depending on whatever it thinks
is approximate ... */
V4L2_FIELD_NONE = 1, /* this device has no fields ... */
V4L2_FIELD_TOP = 2, /* top field only */
V4L2_FIELD_BOTTOM = 3, /* bottom field only */
V4L2_FIELD_INTERLACED = 4, /* both fields interlaced */
V4L2_FIELD_SEQ_TB = 5, /* both fields sequential into one
buffer, top-bottom order */
V4L2_FIELD_SEQ_BT = 6, /* same as above bottom-top order */
V4L2_FIELD_ALTERNATE = 7, /* both fields alternating into
separate buffers */
V4L2_FIELD_INTERLACED_TB = 8, /* both fields interlaced, top field
first and the top field is
transmitted first */
V4L2_FIELD_INTERLACED_BT = 9, /* both fields interlaced, top field
first and the bottom field is
transmitted first */
};
2.5.申请缓冲区:
Video设备捕获的视频数据应该存放在预先分配的缓冲区中。使用VIDIOC_REQBUFS命令向内核申请缓冲区。在申请之前,需要设置申请的缓冲区数量nr_bufs、缓冲区数据流类型type和缓冲区内存使用方式memory。
代码语言:javascript复制 #include <sys/ioctl.h>
#include <linux/videodev2.h>
struct v4l2_requestbuffers req = {0};
req.count = nr_bufs; //缓存数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP; // 内存映射的方式,可提高效率,减少内存占用
......
ret = ioctl(camera_fd, VIDIOC_REQBUFS, &req);
......
下面是struct v4l2_requestbuffers结构体的具体定义。
代码语言:javascript复制 struct v4l2_requestbuffers {
__u32 count; // 缓冲区数量
__u32 type; // 数据流类型,通常为V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 memory; // 缓冲区内存使用方式,通常为V4L2_MEMORY_MMAP
__u32 reserved[2];
};
enum v4l2_memory {
V4L2_MEMORY_MMAP = 1,
V4L2_MEMORY_USERPTR = 2,
V4L2_MEMORY_OVERLAY = 3,
V4L2_MEMORY_DMABUF = 4,
};
2.6.缓冲区映射:
使用read会导致数据在用户空间和内核空间之间来回复制,效率低,且在用户空间和内核空间都占用内存,开销大。通常情况下缓冲区使用内存映射的方式,mmap把驱动程序中videobuf2管理的内存映射到用户空间,应用程序可直接访问videobuf2管理的内存,不发生数据拷贝,这种方式效率高,内存占用低。VIDIOC_QUERYBUF命令获取缓冲区信息,VIDIOC_QBUF将缓冲区加入到内核队列中。
代码语言:javascript复制 #include <stddef.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
struct video_buf {
void* start;
size_t length;
}
struct v4l2_buffer v4l2_buf;
struct video_buf* buf = calloc(req.count, sizeof(struct video_buf));
......
for (i = 0; i < req.count; i ) { // 将申请的缓冲区全部映射
memset(&v4l2_buf, 0, sizeof(v4l2_buf));
buf.index = i;
// 获取编号为i的缓冲区信息
ret = ioctl(camera_fd, VIDIOC_QUERYBUF, &v4l2_buf);
......
buf[i].length = v4l2_buf.length; // 记录缓冲区长度
// 开始映射,映射完成后,应用通过该地址获取视频数据
buf[i].start = mmap(NULL, v4l2_buf.length, PORT_READ | PORT_WRITE,
MAP_SHARED, camera_fd, v4l2_buf.m.offset);
......
// 将缓冲区加入到内核的缓冲队列中
ret = ioctl(camera_fd, VIDIOC_QBUF, v4l2_buf);
......
}
struct v4l2_buffer的详细定义如下。
代码语言:javascript复制 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;
};
2.7.开始采集视频:
经过前面的准备工作,现在可以使能设备,开始采集视频数据了。VIDIOC_STREAMON命令使能设备,开始采集视频。
代码语言:javascript复制 #include <sys/ioctl.h>
#include <linux/videodev2.h>
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
......
ret = ioctl(camera_fd, VIDIOC_STREAMON, &type);
......
2.8.处理视频数据:
内核中以环形队列的形式组织缓冲区,处理数据的时候从环形队列中获取一个缓冲区,处理完毕,将缓冲区放入环形队列。应用程序通过VIDIOC_DQBUF命令从队列中获取一个缓冲区,使用VIDIOC_QBUF将缓冲区加入到队列中。整个过程中不发生数据的拷贝,比传统的read和write I/O方式效率高很多,内存占用也大大降低了。
代码语言:javascript复制 #include <sys/ioctl.h>
#include <linux/videodev2.h>
void* data = NULL;
size_t length = 0;
struct v4l2_buffer v4l2_buf = {0};
for (;;) {
ret = ioctl(camera_fd, VIDIOC_DQBUF, &v4l2_buf); // 从内核队列中获取一个缓冲区
......
data = buf[v4l2_buf.index].start; // 缓冲区地址
length = buf[v4l2_buf.index].length // 缓冲区数据长度
......
ret = ioctl(camera_fd, VIDIOC_QBUF, &v4l2_buf); // 将缓冲区放入内核队列中
......
}
2.9.停止采集视频:
应用可使用VIDIOC_STREAMOFF命令停止视频采集,同时禁止设备。
代码语言:javascript复制 #include <sys/ioctl.h>
#include <linux/videodev2.h>
......
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(camera_fd, VIDIOC_STREAMOFF, &type);
......
2.10.关闭设备:
不再使用设备时,可使用close关闭设置,close调用后之前申请的缓冲区会被释放。
参考资料:
- https://blog.csdn.net/weixin_42462202/article/details/96826526
- Android驱动开发权威指南
- Android驱动开发与移植实战详解
文章参考:http://t.csdn.cn/nkIMu