V4L2采集视频
V4L2 API:
https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/v4l2.html
视频采集流程
V4L2视频采集的简化流程:
打开设备 -> 设置输入源 -> 设置视频帧格式 -> 设置帧率等参数 -> 申请BUF及映射 -> 启动流 -> 获取数据 -> 停止视频流 -> 关闭设备
其他一些细节就省略了,这里只记录V4L2相关的 ioctl
操作流程。
打开设备
fd = open(v4l_device, O_RDWR, 0);
获取设备信息
VIDIOC_QUERYCAP
struct v4l2_capability cam_cap;
ret = ioctl(fd, VIDIOC_QUERYCAP, &cam_cap);
printf("Driver Name: %s\nCard Name: %s\nBus info: %s\nDriver Version: %u.%u.%u\n",
cam_cap.driver,
cam_cap.card,
cam_cap.bus_info,
(cam_cap.version >> 16) & 0XFF, (cam_cap.version >> 8) & 0XFF, cam_cap.version & 0XFF);
设置或获取输入源
VIDIOC_G_INPUT
VIDIOC_S_INPUT
/* set video input */
ioctl(fd, VIDIOC_S_INPUT, &channel);
/* get current video input */
ioctl(fd, VIDIOC_G_INPUT, &channel);
/* enum input */
ioctl(fd, VIDIOC_ENUMINPUT, &input);
枚举支持的视频帧格式
VIDIOC_ENUM_FMT
struct v4l2_fmtdesc vfd = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
ioctl(fd, VIDIOC_ENUM_FMT, &vfd);
枚举设备支持的所有标准
VIDIOC_ENUMSTD
struct v4l2_standard standard;
ioctl(fd, VIDIOC_ENUMSTD, &standard);
获取和设置当前的视频帧格式
VIDIOC_G_FMT
struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
ioctl(fd, VIDIOC_G_FMT, &fmt);
printf(" width : %d height : %d\n\
pixelformat : %d\n\
field : %d\n\
bytesperline : %d\n\
sizeimage : %d\n\
colorspace : %d\n\
priv : %d\n",\
fmt.pix.width,\
fmt.pix.height,\
fmt.pix.pixelformat,\
fmt.pix.field, \
fmt.pix.bytesperline, \
fmt.pix.sizeimage, \
fmt.pix.colorspace, \
fmt.pix.priv);
VIDIOC_S_FMT
/*设置帧格式,长,宽,采样类型等*/
struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
fmt.fmt.pix.width = *width;
fmt.fmt.pix.height = *height;
fmt.fmt.pix.pixelformat = pixelformat;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
ioctl(fd, VIDIOC_S_FMT, &fmt);
获取和设置参数
VIDIOC_G_PARM
struct v4l2_streamparm parm = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
ioctl(fd, VIDIOC_G_PARM, &parm);
VIDIOC_S_PARM
/*设置帧率fps等*/
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm.parm.capture.timeperframe.numerator = 1;
parm.parm.capture.timeperframe.denominator = fps;
parm.parm.capture.capturemode = mode;
ioctl(fd, VIDIOC_S_PARM, &parm);
申请BUF
VIDIOC_REQBUFS
struct v4l2_requestbuffers req = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.count = desired_video_buffers,
.memory = V4L2_MEMORY_MMAP
};
ioctl(fd, VIDIOC_REQBUFS, &req);
三种交换数据的方法:
- 直接 read/write、
- 内存映射(V4L2_MEMORY_MMAP)
- 用户指针(V4L2_MEMORY_USERPTR)
获取缓存信息并内存映射:
VIDIOC_QUERYBUF
for (i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.index = i,
.memory = V4L2_MEMORY_MMAP
};
if(ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
printf("VIDIOC_QUERYBUF error!\n");
return -1;
}
buf_len[i].length = buf.length;
buf_start[i].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd, buf.m.offset);
if(buf_start[i].start == MAP_FAILED) {
printf("v4l_start_capturing mmap error!\n");
return -1;
}
}
将缓存放入队列
VIDIOC_QBUF
for (i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.index = i,
.memory = V4L2_MEMORY_MMAP
};
if(ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
printf("VIDIOC_QBUF error!\n");
return -1;
}
}
启动数据流
VIDIOC_STREAMON
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);
从队列中获取数据
VIDIOC_DQBUF #从队列中取出帧
struct v4l2_buffer buf = {
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
.memory = V4L2_MEMORY_MMAP
};
ioctl(fd, VIDIOC_DQBUF, &buf);
停止数据流
VIDIOC_STREAMOFF
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMOFF, &type);
取消映射
for(i = 0; i < req.count; i++) {
if(buf_start[i].start > 0)
munmap(buf_start[i].start, buf_len[i].length);
}
关闭设备
close(dev_fd);
参考:
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 DD'Notes!
评论