You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用V4L2 API捕获MIPI CSI相机图像时程序卡顿问题咨询

解决V4L2调用MIPI CSI相机时的冻结问题及无GStreamer依赖示例

我帮你梳理下这个问题——你的V4L2代码在USB相机上正常工作,但换到MIPI CSI相机就冻结,确实是因为两类相机在V4L2配置上有不少差异,必须针对性调整代码才行。下面我分点说明要改的核心点,再给你一个无GStreamer依赖的完整示例:

一、必须修改的代码要点

1. 确认相机支持的像素格式与分辨率

MIPI CSI相机的格式支持和USB相机差异很大:很多USB相机默认支持MJPEG,但多数MIPI CSI相机(比如树莓派的IMX219、Jetson的OV5647)原生只支持YUYV、NV12或者RAW格式,并不支持MJPEG压缩输出。如果你的代码硬编码了MJPEG格式,相机无法响应就会导致程序冻结。

解决方法:

  • 先通过VIDIOC_ENUM_FMT枚举相机支持的所有格式,选择相机支持的格式(比如YUYV)
  • 用VIDIOC_ENUM_FRAMESIZES查询对应格式下支持的分辨率,不要硬编码不兼容的分辨率

2. 调整缓冲区分配方式

USB相机常用MMAP或USERPTR缓冲区,但MIPI CSI相机在嵌入式平台上通常需要使用DMABUF(直接内存访问缓冲区),因为MIPI数据是通过硬件DMA直接传输到内核缓冲区的,用户空间直接映射可能有权限或兼容性问题。如果你的代码用的是MMAP,可以先尝试保留,但如果还是冻结,就需要切换到DMABUF模式。

3. 严格配置帧率与曝光参数

MIPI CSI相机对帧率配置的要求更严格:

  • 必须先通过VIDIOC_S_PARM设置帧率,有些相机需要先将曝光模式设为自动(VIDIOC_S_CTRL设置V4L2_CID_EXPOSURE_AUTO),再启动流
  • 避免设置相机不支持的帧率,建议先通过VIDIOC_ENUM_FRAMEINTERVALS查询支持的帧率区间

4. 确认设备节点与驱动加载

MIPI CSI相机的设备节点通常是/dev/video0或/dev/video1,但要确保你打开的是CSI相机设备(可以用v4l2-ctl --list-devices在终端确认)。另外,部分平台需要先加载对应的相机驱动模块(比如树莓派的bcm2835-v4l2),否则相机无法被V4L2识别。

二、无GStreamer依赖的MIPI CSI相机V4L2示例代码

下面是一个简化的、针对MIPI CSI相机的V4L2捕获示例,支持YUYV格式,用MMAP缓冲区(如果你的相机不支持MMAP,可以替换为DMABUF逻辑):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>

#define DEVICE_PATH "/dev/video0"
#define WIDTH 1920
#define HEIGHT 1080
#define BUFFER_COUNT 4

struct buffer {
    void *start;
    size_t length;
};

struct buffer *buffers;
int fd = -1;

// 打开设备并初始化
int init_camera() {
    fd = open(DEVICE_PATH, O_RDWR | O_NONBLOCK, 0);
    if (fd == -1) {
        perror("Failed to open device");
        return -1;
    }

    // 查询设备能力
    struct v4l2_capability cap;
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
        perror("Failed to query capability");
        return -1;
    }

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        fprintf(stderr, "Device is not a video capture device\n");
        return -1;
    }

    if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
        fprintf(stderr, "Device does not support streaming\n");
        return -1;
    }

    // 设置格式为YUYV
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = WIDTH;
    fmt.fmt.pix.height = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field = V4L2_FIELD_NONE;

    if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
        perror("Failed to set format");
        return -1;
    }

    // 确认实际设置的格式(相机可能调整分辨率)
    if (ioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
        perror("Failed to get format");
        return -1;
    }
    printf("Actual format: %dx%d, format: %c%c%c%c\n",
           fmt.fmt.pix.width, fmt.fmt.pix.height,
           (fmt.fmt.pix.pixelformat >> 0) & 0xFF,
           (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
           (fmt.fmt.pix.pixelformat >> 16) & 0xFF,
           (fmt.fmt.pix.pixelformat >> 24) & 0xFF);

    // 请求缓冲区
    struct v4l2_requestbuffers req;
    memset(&req, 0, sizeof(req));
    req.count = BUFFER_COUNT;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
        perror("Failed to request buffers");
        return -1;
    }

    // 分配缓冲区并映射
    buffers = calloc(req.count, sizeof(struct buffer));
    for (int i = 0; i < req.count; i++) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
            perror("Failed to query buffer");
            return -1;
        }

        buffers[i].length = buf.length;
        buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);

        if (buffers[i].start == MAP_FAILED) {
            perror("Failed to mmap buffer");
            return -1;
        }
    }

    // 入队缓冲区
    for (int i = 0; i < req.count; i++) {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
            perror("Failed to queue buffer");
            return -1;
        }
    }

    // 启动流
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_STREAMON, &type) == -1) {
        perror("Failed to start stream");
        return -1;
    }

    return 0;
}

// 捕获一帧图像
int capture_frame() {
    struct v4l2_buffer buf;
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    // 出队缓冲区
    if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {
        if (errno == EAGAIN) {
            // 没有可用帧,重试
            return 0;
        }
        perror("Failed to dequeue buffer");
        return -1;
    }

    // 这里可以处理图像数据,比如保存为文件
    printf("Captured frame %d, size: %zu bytes\n", buf.index, buf.bytesused);

    // 重新入队缓冲区
    if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
        perror("Failed to requeue buffer");
        return -1;
    }

    return 1;
}

// 清理资源
void cleanup() {
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd, VIDIOC_STREAMOFF, &type);

    for (int i = 0; i < BUFFER_COUNT; i++) {
        munmap(buffers[i].start, buffers[i].length);
    }
    free(buffers);
    close(fd);
}

int main() {
    if (init_camera() == -1) {
        cleanup();
        return EXIT_FAILURE;
    }

    // 捕获10帧后退出
    int count = 0;
    while (count < 10) {
        int ret = capture_frame();
        if (ret == 1) {
            count++;
        }
        usleep(10000); // 10ms延迟
    }

    cleanup();
    return EXIT_SUCCESS;
}

这个示例做了这些适配MIPI CSI相机的工作:

  • 明确使用YUYV格式(多数MIPI相机支持)
  • 用MMAP缓冲区(如果你的相机不支持,可以修改为DMABUF,需要额外处理V4L2_MEMORY_DMABUF逻辑)
  • 严格遵循V4L2流启动流程:请求缓冲区→映射→入队→启动流
  • 处理EAGAIN错误(非阻塞模式下无帧可用时的正常情况)

三、额外调试技巧

如果还是冻结,可以用这些方法排查:

  • 用v4l2-ctl --all查看相机的当前配置,确认格式、帧率是否正确
  • 用v4l2-ctl --stream-mmap --stream-count=10测试相机是否能正常捕获帧,如果这个命令也冻结,说明是驱动或硬件问题,不是代码问题
  • 检查程序的权限,确保当前用户有访问/dev/videoX的权限(可以加入video用户组)

内容的提问来源于stack exchange,提问作者Avis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:00:55