27、视频设备编程:从打开到数据捕获的完整指南

视频设备编程:从打开到数据捕获的完整指南

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;
}

通过以上示例代码,你可以完整地实现从视频设备打开到数据捕获的过程,并根据实际需求进行调整和优化。

基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)内容概要:本文围绕“基于数据驱动的Koopman算子的递归神经网络模型线性化”展开,旨在研究纳米定位系统的预测控制问题,并提供完整的Matlab代码实现。文章结合数据驱动方法与Koopman算子理论,利用递归神经网络(RNN)对非线性系统进行建模与线性化处理,从而提升纳米级定位系统的精度与动态响应性能。该方法通过提取系统隐含动态特征,构建近似线性模型,便于后续模型预测控制(MPC)的设计与优化,适用于高精度自动化控制场景。文中还展示了相关实验验证与仿真结果,证明了该方法的有效性和先进性。; 适合人群:具备一定控制理论基础和Matlab编程能力,从事精密控制、智能制造、自动化或相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于纳米级精密定位系统(如原子力显微镜、半导体制造设备)中的高性能控制设计;②为非线性系统建模与线性化提供一种结合深度学习与现代控制理论的新思路;③帮助读者掌握Koopman算子、RNN建模与模型预测控制的综合应用。; 阅读建议:建议读者结合提供的Matlab代码逐段理解算法实现流程,重点关注数据预处理、RNN结构设计、Koopman观测矩阵构建及MPC控制器集成等关键环节,并可通过更换实际系统数据进行迁移验证,深化对方法泛化能力的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值