视频设备编程:从打开到数据捕获的完整指南
1. 引言
在视频处理领域,从设备打开到视频流捕获的整个过程涉及多个关键步骤。本文将详细介绍如何通过一系列操作,实现从视频设备获取数据的功能,包括设备打开、属性管理、能力查询、缓冲区管理、图像格式协商以及缓冲区请求等重要环节。
2. 视频设备打开与属性管理
2.1 设备打开
驱动程序会在
/dev/
目录下暴露与视频接口对应的节点条目,对于捕获设备,这些文件节点对应
/dev/videoX
特殊文件。应用程序在与视频设备交互之前,需要使用
open()
系统调用打开相应的文件节点,示例代码如下:
static const char *dev_name = " /dev/video0";
fd = open (dev_name, O_RDWR);
if (fd == -1) {
perror(" Failed to open capture device\n");
return -1;
}
上述代码以阻塞模式打开设备。若传递
O_NONBLOCK
给
open()
,在尝试出队时若无可用缓冲区,应用程序不会被阻塞。使用完视频设备后,需使用
close()
系统调用关闭设备:
close (fd);
2.2 查询设备能力
为确保设备支持所需的工作模式,通常需要查询设备的能力。可使用
VIDIOC_QUERYCAP
ioctl 命令实现,应用程序需传递
struct v4l2_capability
结构体,该结构体将由驱动程序填充。以下是可能的能力值:
| 能力宏定义 | 描述 |
| — | — |
|
V4L2_CAP_VIDEO_CAPTURE
| 视频捕获设备 |
|
V4L2_CAP_VIDEO_OUTPUT
| 视频输出设备 |
|
V4L2_CAP_VIDEO_OVERLAY
| 支持视频覆盖 |
|
V4L2_CAP_VIDEO_CAPTURE_MPLANE
| 支持多平面格式的视频捕获设备 |
|
V4L2_CAP_VIDEO_OUTPUT_MPLANE
| 支持多平面格式的视频输出设备 |
|
V4L2_CAP_VIDEO_M2M_MPLANE
| 支持多平面格式的内存到内存设备 |
|
V4L2_CAP_VIDEO_M2M
| 视频内存到内存设备 |
|
V4L2_CAP_READWRITE
| 支持读写系统调用 |
|
V4L2_CAP_ASYNCIO
| 支持异步 I/O |
|
V4L2_CAP_STREAMING
| 支持流式 I/O ioctl |
|
V4L2_CAP_TOUCH
| 触摸设备 |
以下是查询设备能力的代码示例:
#include <linux/videodev2.h>
struct v4l2_capability cap;
memset(&cap, 0, sizeof(cap));
if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) {
if (EINVAL == errno) {
fprintf(stderr, " %s is no V4L2 device\n", dev_name);
exit(EXIT_FAILURE);
} else {
errno_exit(" VIDIOC_QUERYCAP");
}
}
可通过以下代码检查设备类型和 I/O 方法:
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
fprintf(stderr, " %s is not a video capture device\n", dev_name);
exit(EXIT_FAILURE);
}
if (!(cap.capabilities & V4L2_CAP_READWRITE))
fprintf(stderr, " %s does not support read i/o\n", dev_name);
if (!(cap.capabilities & V4L2_CAP_STREAMING))
fprintf(stderr, " %s does not support streaming i/o\n", dev_name);
为避免参数中存在陈旧内容,建议在使用前清除传递给 V4L2 API 的参数,可定义宏
CLEAR
实现:
#define CLEAR(x) memset(&(x), 0, sizeof(x))
3. 缓冲区管理
在 V4L2 中,维护着两个缓冲区队列:驱动程序的输入队列和用户的输出队列。用户空间应用程序使用
VIDIOC_QBUF
ioctl 将缓冲区排入驱动程序队列,驱动程序按入队顺序填充缓冲区。填充完成后,缓冲区从输入队列移至输出队列。用户应用程序使用
VIDIOC_DQBUF
出队缓冲区,使用完后需使用
VIDIOC_QBUF
将其重新排入输入队列。
驱动程序初始化后,应用程序调用
VIDIOC_REQBUFS
ioctl 设置所需的缓冲区数量,成功后将所有缓冲区入队,然后调用
VIDIOC_STREAMON
ioctl,驱动程序开始填充队列中的缓冲区。若队列中无缓冲区,驱动程序将等待应用程序入队新的缓冲区,此时可能会丢失一些帧。
以下是缓冲区管理的流程:
graph LR
A[驱动程序初始化] --> B[调用 VIDIOC_REQBUFS 设置缓冲区数量]
B --> C[使用 VIDIOC_QBUF 入队所有缓冲区]
C --> D[调用 VIDIOC_STREAMON]
D --> E[驱动程序填充缓冲区]
E --> F{缓冲区是否用完}
F -- 是 --> G[等待应用程序入队新缓冲区]
F -- 否 --> E
4. 图像(缓冲区)格式
应用程序在开始捕获数据前,需协商视频设备的输出格式,确保设备以应用程序能处理的格式发送视频帧。V4L2 API 使用
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; /* _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;/*_SLICED_VBI_CAPTURE */
struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
struct v4l2_meta_format meta;/* V4L2_BUF_TYPE_META_CAPTURE */
[...]
} fmt;
};
对于视频捕获设备,
type
字段应设置为
V4L2_BUF_TYPE_VIDEO_CAPTURE
,此时
fmt
为
struct v4l2_pix_format
类型。
4.1 查询当前格式
应用程序可使用
VIDIOC_G_FMT
ioctl 查询当前缓冲区格式,示例代码如下:
struct v4l2_format fmt;
CLEAR(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_G_FMT, &fmt)) {
printf(" Getting format failed\n");
exit(2);
}
4.2 设置新格式
获取当前格式后,可修改相关属性并使用
VIDIOC_S_FMT
ioctl 将新格式发送给设备,示例代码如下:
#define WIDTH 1920
#define HEIGHT 1080
#define PIXFMT V4L2_PIX_FMT_YUV420
fmt.fmt.pix.width = WIDTH;
fmt.fmt.pix.height = HEIGHT;
fmt.fmt.pix.bytesperline = fmt.fmt.pix.width * 2u;
fmt.fmt.pix.sizeimage = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
fmt.fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
fmt.fmt.pix.pixelformat = PIXFMT;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (xioctl(fd, VIDIOC_S_FMT, &fmt)) {
printf(" Setting format failed\n");
exit(2);
}
需注意,
ioctl
调用成功并不意味着参数已按原样应用,驱动程序可能会应用最接近的支持值,因此需检查参数是否被接受:
if (fmt.fmt.pix.pixelformat != PIXFMT)
printf(" Driver didn't accept our format. Can't proceed.\n");
if ((fmt.fmt.pix.width != WIDTH) || (fmt.fmt.pix.height != HEIGHT))
fprintf(stderr, " Warning: driver is sending image at %dx%d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
4.3 更改流参数
可通过以下步骤更改视频设备的流参数,如每秒帧数:
1. 使用
VIDIOC_G_PARM
ioctl 查询视频设备的流参数,需传递
struct v4l2_streamparm
结构体,
type
成员应设置为
enum v4l2_buf_type
中的值。
2. 检查
v4l2_streamparm.parm.capture.capability
,确保
V4L2_CAP_TIMEPERFRAME
标志已设置,表明驱动程序允许更改捕获帧率。
3. (可选)使用
VIDIOC_ENUM_FRAMEINTERVALS
ioctl 获取可能的帧间隔列表。
4. 使用
VIDIOC_S_PARM
ioctl 设置捕获帧率,填充
v4l2_streamparm.parm.capture.timeperframe
成员。
以下是示例代码:
#define FRAMERATE 30
struct v4l2_streamparm parm;
int error;
CLEAR(parm);
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
error = xioctl(fd, VIDIOC_G_PARM, &parm);
if (!error) {
if (parm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) {
CLEAR(parm);
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm.parm.capture.capturemode = 0;
parm.parm.capture.timeperframe.numerator = 1;
parm.parm.capture.timeperframe.denominator = FRAMERATE;
error = xioctl(fd, VIDIOC_S_PARM, &parm);
if (error)
printf(" Unable to set the FPS\n");
else
if (FRAMERATE != parm.parm.capture.timeperframe.denominator)
printf (" fps coerced ......: from %d to %d\n", FRAMERATE, parm.parm.capture.timeperframe.denominator);
}
}
5. 请求缓冲区
完成格式准备后,需指示驱动程序分配用于存储视频帧的内存,可使用
VIDIOC_REQBUFS
ioctl 实现。
struct v4l2_requestbuffers
结构体的相关字段需设置如下:
-
count
:应设置为要分配的内存缓冲区数量,通常设置为 3 或 4 可避免因输入队列中缓冲区不足而丢帧。驱动程序可能不接受请求的数量,会在 ioctl 返回路径中设置实际分配的数量,应用程序需检查该值。
-
type
:设置为视频缓冲区类型,对于视频捕获设备,使用
V4L2_BUF_TYPE_VIDEO_CAPTURE
。
-
memory
:设置为
enum v4l2_memory
中的值,如
V4L2_MEMORY_MMAP
、
V4L2_MEMORY_USERPTR
或
V4L2_MEMORY_DMABUF
。
5.1 请求用户指针缓冲区
此步骤要求驱动程序支持流式模式,特别是用户指针 I/O 模式。应用程序先通知驱动程序即将分配一定数量的缓冲区,示例代码如下:
#define BUF_COUNT 4
struct v4l2_requestbuffers req;
CLEAR (req);
req.count = BUF_COUNT;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;
if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
if (EINVAL == errno)
fprintf(stderr, " %s does not support user pointer i/o\n", dev_name);
else
fprintf(" VIDIOC_REQBUFS failed \n");
}
然后,应用程序从用户空间分配缓冲区内存:
struct buffer_addr {
void *start;
size_t length;
};
struct buffer_addr *buf_addr;
int i;
buf_addr = calloc(BUF_COUNT, sizeof(*buffer_addr));
if (!buf_addr) {
fprintf(stderr, " Out of memory\n");
exit (EXIT_FAILURE);
}
for (i = 0; i < BUF_COUNT; ++i) {
buf_addr[i].length = buffer_size;
buf_addr[i].start = malloc(buffer_size);
if (!buf_addr[i].start) {
fprintf(stderr, " Out of memory\n");
exit(EXIT_FAILURE);
}
}
5.2 请求可内存映射的缓冲区
在驱动程序缓冲区模式下,
VIDIOC_REQBUFS
ioctl 会返回实际分配的缓冲区数量。此流式方法还需要
struct v4l2_buffer
结构体,用于查询每个分配缓冲区的物理地址。示例代码如下:
#define BUF_COUNT_MIN 3
struct v4l2_requestbuffers req;
CLEAR (req);
req.count = BUF_COUNT;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
if (EINVAL == errno)
fprintf(stderr, " %s does not support memory mapping\n", dev_name);
else
fprintf(" VIDIOC_REQBUFS failed \n");
}
if (req.count < BUF_COUNT_MIN) {
fprintf(stderr, " Insufficient buffer memory on %s\n", dev_name);
exit (EXIT_FAILURE);
}
struct buffer_addr {
void *start;
size_t length;
};
struct buffer_addr *buf_addr;
buf_addr = calloc(BUF_COUNT, sizeof(*buffer_addr));
if (!buf_addr) {
fprintf (stderr, " Out of memory\n");
exit (EXIT_FAILURE);
}
for (i = 0; i < req.count; ++i) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))
errno_exit(" VIDIOC_QUERYBUF");
buf_addr[i].length = buf.length;
buf_addr[i].start = mmap (NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, fd, buf.m.offset);
if (MAP_FAILED == buf_addr[i].start)
errno_exit(" mmap");
}
5.3 请求 DMABUF 缓冲区
DMABUF 主要用于内存到内存设备,引入了导出器和导入器的概念。应用程序可使用
VIDIOC_EXPBUF
ioctl 将驱动程序的 DMA 缓冲区导出为文件描述符。示例代码如下:
struct v4l2_requestbuffers req;
CLEAR (req);
req.count = BUF_COUNT;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_DMABUF;
if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req))
errno_exit (" VIDIOC_QUERYBUFS");
int outdev_dmabuf_fd[BUF_COUNT] = {-1};
int i;
for (i = 0; i < req.count; i++) {
struct v4l2_exportbuffer expbuf;
CLEAR (expbuf);
expbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
expbuf.index = i;
if (-1 == xioctl(fd, VIDIOC_EXPBUF, &expbuf))
errno_exit (" VIDIOC_EXPBUF");
outdev_dmabuf_fd[i] = expbuf.fd;
}
5.4 请求读写 I/O 内存
从编码角度看,这是最简单的流式模式。对于读写 I/O,只需分配应用程序存储读取数据的内存位置,示例代码如下:
struct buffer_addr {
void *start;
size_t length;
};
struct buffer_addr *buf_addr;
buf_addr = calloc(1, sizeof(*buf_addr));
if (!buf_addr) {
// 处理内存分配失败的情况
}
通过以上步骤,可完成从视频设备打开到数据捕获的整个过程,实现视频流的处理。在实际应用中,需根据具体需求选择合适的缓冲区请求方式和视频格式,以确保系统的性能和稳定性。
6. 不同请求方式的总结与比较
6.1 不同请求方式的特点对比
| 请求方式 | 特点 | 适用场景 |
|---|---|---|
| 用户指针缓冲区 | 应用程序在用户空间分配缓冲区并告知驱动程序,驱动程序填充数据。需要驱动支持用户指针 I/O 模式。 | 适合对内存管理有较高控制权,且驱动支持该模式的场景。 |
| 可内存映射的缓冲区 |
驱动程序在内核分配缓冲区,应用程序通过
mmap
映射到用户空间。
| 适用于需要高效数据传输,且对内存映射有需求的场景。 |
| DMABUF 缓冲区 |
引入导出器和导入器概念,通过
VIDIOC_EXPBUF
导出 DMA 缓冲区为文件描述符。
| 主要用于内存到内存设备,方便不同设备间的数据共享。 |
| 读写 I/O 内存 | 编码简单,只需分配存储读取数据的内存位置。 | 对编码复杂度要求较低,数据量较小的场景。 |
6.2 选择合适请求方式的流程图
graph LR
A[开始] --> B{是否需要高内存控制权}
B -- 是 --> C{驱动是否支持用户指针 I/O}
C -- 是 --> D[选择用户指针缓冲区]
C -- 否 --> E{是否需要高效数据传输和内存映射}
E -- 是 --> F[选择可内存映射的缓冲区]
E -- 否 --> G{是否为内存到内存设备}
G -- 是 --> H[选择 DMABUF 缓冲区]
G -- 否 --> I{对编码复杂度要求是否低}
I -- 是 --> J[选择读写 I/O 内存]
I -- 否 --> K[重新评估需求]
B -- 否 --> E
7. 错误处理与调试建议
7.1 常见错误及处理方法
| 错误情况 | 错误信息 | 处理方法 |
|---|---|---|
| 设备打开失败 |
Failed to open capture device
| 检查设备路径是否正确,设备是否可用。 |
| 查询设备能力失败 |
VIDIOC_QUERYCAP failed
| 检查设备是否为 V4L2 设备,驱动是否正常。 |
| 请求缓冲区失败 |
VIDIOC_REQBUFS failed
| 检查设备是否支持相应的 I/O 模式,内存是否充足。 |
| 内存映射失败 |
mmap failed
| 检查文件描述符、偏移量等参数是否正确。 |
7.2 调试建议
- 日志记录 :在关键步骤添加日志输出,记录函数调用的参数和返回值,方便定位问题。
printf("VIDIOC_REQBUFS: count = %d, type = %d, memory = %d\n", req.count, req.type, req.memory);
-
使用调试工具
:如
strace可跟踪系统调用,帮助分析程序的执行流程和系统调用的参数。
strace -o trace.log ./your_program
8. 性能优化建议
8.1 缓冲区数量优化
合理设置缓冲区数量,避免因缓冲区不足导致丢帧,或因缓冲区过多占用过多内存。可根据实际帧率和处理速度进行调整。
8.2 数据传输优化
-
对于可内存映射的缓冲区,使用
MAP_SHARED标志,减少数据拷贝。
buf_addr[i].start = mmap (NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, fd, buf.m.offset);
-
对于读写 I/O 模式,可使用
O_DIRECT标志,避免内核缓冲区的额外拷贝。
fd = open (dev_name, O_RDWR | O_DIRECT);
8.3 帧率优化
根据设备性能和应用需求,合理设置帧率。可通过
VIDIOC_S_PARM
调整帧率,避免因帧率过高导致处理不及而丢帧。
9. 总结
通过本文的介绍,我们详细了解了从视频设备打开到数据捕获的完整流程,包括设备打开、属性管理、能力查询、缓冲区管理、图像格式协商以及缓冲区请求等关键步骤。同时,我们还介绍了不同的缓冲区请求方式及其适用场景,以及错误处理、调试和性能优化的建议。
在实际应用中,需要根据具体需求选择合适的方法和参数,以确保视频流处理的性能和稳定性。希望本文能为你在视频设备编程方面提供有价值的参考。
10. 示例代码整合
以下是一个整合了上述步骤的示例代码,展示了从设备打开到数据捕获的完整过程:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#define CLEAR(x) memset(&(x), 0, sizeof(x))
#define WIDTH 1920
#define HEIGHT 1080
#define PIXFMT V4L2_PIX_FMT_YUV420
#define BUF_COUNT 4
#define FRAMERATE 30
void errno_exit(const char *s) {
fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
exit(EXIT_FAILURE);
}
int main() {
const char *dev_name = "/dev/video0";
int fd = open(dev_name, O_RDWR);
if (fd == -1) {
perror("Failed to open capture device\n");
return -1;
}
// 查询设备能力
struct v4l2_capability cap;
CLEAR(cap);
if (-1 == ioctl(fd, VIDIOC_QUERYCAP, &cap)) {
if (EINVAL == errno) {
fprintf(stderr, "%s is no V4L2 device\n", dev_name);
exit(EXIT_FAILURE);
} else {
errno_exit("VIDIOC_QUERYCAP");
}
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
fprintf(stderr, "%s is not a video capture device\n", dev_name);
exit(EXIT_FAILURE);
}
if (!(cap.capabilities & V4L2_CAP_READWRITE))
fprintf(stderr, "%s does not support read i/o\n", dev_name);
if (!(cap.capabilities & V4L2_CAP_STREAMING))
fprintf(stderr, "%s does not support streaming i/o\n", dev_name);
// 查询当前格式
struct v4l2_format fmt;
CLEAR(fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_G_FMT, &fmt)) {
printf("Getting format failed\n");
exit(2);
}
// 设置新格式
fmt.fmt.pix.width = WIDTH;
fmt.fmt.pix.height = HEIGHT;
fmt.fmt.pix.bytesperline = fmt.fmt.pix.width * 2u;
fmt.fmt.pix.sizeimage = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
fmt.fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
fmt.fmt.pix.pixelformat = PIXFMT;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_S_FMT, &fmt)) {
printf("Setting format failed\n");
exit(2);
}
if (fmt.fmt.pix.pixelformat != PIXFMT)
printf("Driver didn't accept our format. Can't proceed.\n");
if ((fmt.fmt.pix.width != WIDTH) || (fmt.fmt.pix.height != HEIGHT))
fprintf(stderr, "Warning: driver is sending image at %dx%d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
// 更改流参数
struct v4l2_streamparm parm;
int error;
CLEAR(parm);
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
error = ioctl(fd, VIDIOC_G_PARM, &parm);
if (!error) {
if (parm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) {
CLEAR(parm);
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm.parm.capture.capturemode = 0;
parm.parm.capture.timeperframe.numerator = 1;
parm.parm.capture.timeperframe.denominator = FRAMERATE;
error = ioctl(fd, VIDIOC_S_PARM, &parm);
if (error)
printf("Unable to set the FPS\n");
else
if (FRAMERATE != parm.parm.capture.timeperframe.denominator)
printf("fps coerced ......: from %d to %d\n", FRAMERATE, parm.parm.capture.timeperframe.denominator);
}
}
// 请求可内存映射的缓冲区
struct v4l2_requestbuffers req;
CLEAR(req);
req.count = BUF_COUNT;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl(fd, VIDIOC_REQBUFS, &req)) {
if (EINVAL == errno)
fprintf(stderr, "%s does not support memory mapping\n", dev_name);
else
fprintf("VIDIOC_REQBUFS failed \n");
}
if (req.count < 3) {
fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name);
exit(EXIT_FAILURE);
}
struct buffer_addr {
void *start;
size_t length;
};
struct buffer_addr *buf_addr;
buf_addr = calloc(BUF_COUNT, sizeof(*buf_addr));
if (!buf_addr) {
fprintf(stderr, "Out of memory\n");
exit(EXIT_FAILURE);
}
for (int i = 0; i < req.count; ++i) {
struct v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf))
errno_exit("VIDIOC_QUERYBUF");
buf_addr[i].length = buf.length;
buf_addr[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
if (MAP_FAILED == buf_addr[i].start)
errno_exit("mmap");
}
// 开始流
struct v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
for (int i = 0; i < req.count; ++i) {
buf.index = i;
if (-1 == ioctl(fd, VIDIOC_QBUF, &buf))
errno_exit("VIDIOC_QBUF");
}
if (-1 == ioctl(fd, VIDIOC_STREAMON, &buf.type))
errno_exit("VIDIOC_STREAMON");
// 模拟数据捕获
for (int i = 0; i < 10; ++i) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv = {0};
tv.tv_sec = 2;
int r = select(fd + 1, &fds, NULL, NULL, &tv);
if (-1 == r) {
if (EINTR == errno)
continue;
errno_exit("select");
}
if (0 == r) {
fprintf(stderr, "select timeout\n");
exit(EXIT_FAILURE);
}
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 == ioctl(fd, VIDIOC_DQBUF, &buf))
errno_exit("VIDIOC_DQBUF");
// 处理数据
// ...
if (-1 == ioctl(fd, VIDIOC_QBUF, &buf))
errno_exit("VIDIOC_QBUF");
}
// 停止流
if (-1 == ioctl(fd, VIDIOC_STREAMOFF, &buf.type))
errno_exit("VIDIOC_STREAMOFF");
// 释放内存映射
for (int i = 0; i < req.count; ++i) {
if (-1 == munmap(buf_addr[i].start, buf_addr[i].length))
errno_exit("munmap");
}
free(buf_addr);
// 关闭设备
close(fd);
return 0;
}
通过以上示例代码,你可以完整地实现从视频设备打开到数据捕获的过程,并根据实际需求进行调整和优化。
超级会员免费看
3300

被折叠的 条评论
为什么被折叠?



