流程

  1. 配置
  2. 获取一帧
/* 初始化 V4L2:打开设备、设置 YUYV 格式、申请 mmap 缓冲区、开始流 */
int camera_init(const char *dev, int w, int h)
{
    struct v4l2_format fmt = {0};
    struct v4l2_requestbuffers req = {0};
    struct v4l2_buffer vbuf = {0};
    enum v4l2_buf_type type;

    cam_fd = open(dev, O_RDWR);
    if (cam_fd < 0) { perror("open"); return -1; }

    /* 协商 YUYV 格式 */
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width       = w;
    fmt.fmt.pix.height      = h;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field       = V4L2_FIELD_NONE;
    if (ioctl(cam_fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("VIDIOC_S_FMT"); close(cam_fd); return -1;
    }

    img_w = fmt.fmt.pix.width;
    img_h = fmt.fmt.pix.height;
    printf("camera: %dx%d YUYV\n", img_w, img_h);

    /* 申请 V4L2 循环缓冲区(内存由驱动管理) */
    req.count  = BUF_NUM;
    req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if (ioctl(cam_fd, VIDIOC_REQBUFS, &req) < 0) {
        perror("VIDIOC_REQBUFS"); close(cam_fd); return -1;
    }

    for (int i = 0; i < BUF_NUM; i++) {
        memset(&vbuf, 0, sizeof(vbuf));
        vbuf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        vbuf.memory = V4L2_MEMORY_MMAP;
        vbuf.index  = i;
        if (ioctl(cam_fd, VIDIOC_QUERYBUF, &vbuf) < 0) {
            perror("VIDIOC_QUERYBUF"); close(cam_fd); return -1;
        }
        buffers[i].length = vbuf.length;
        buffers[i].start  = mmap(NULL, vbuf.length, PROT_READ | PROT_WRITE,
                                  MAP_SHARED, cam_fd, vbuf.m.offset);
        if (buffers[i].start == MAP_FAILED) {
            perror("mmap"); close(cam_fd); return -1;
        }
    }

    /* 所有缓冲入队,准备采集 */
    for (int i = 0; i < BUF_NUM; i++) {
        memset(&vbuf, 0, sizeof(vbuf));
        vbuf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        vbuf.memory = V4L2_MEMORY_MMAP;
        vbuf.index  = i;
        ioctl(cam_fd, VIDIOC_QBUF, &vbuf);
    }

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(cam_fd, VIDIOC_STREAMON, &type) < 0) {
        perror("VIDIOC_STREAMON"); close(cam_fd); return -1;
    }

    return 0;
}
/*
 * 捕获一帧:DQBUF 取一帧 → 直接转换到 rgb_buf → QBUF 归还
 * 不经过中间缓冲,mmap 数据直转 ARGB8888
 */
int camera_capture_frame(uint8_t *rgb_buf, int *size)
{
    struct v4l2_buffer vbuf = {0};
    fd_set fds;
    struct timeval tv;

    FD_ZERO(&fds);
    FD_SET(cam_fd, &fds);
    tv.tv_sec  = 0;
    tv.tv_usec = 200000;

    int ret = select(cam_fd + 1, &fds, NULL, NULL, &tv);
    if (ret <= 0) return -1;

    vbuf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vbuf.memory = V4L2_MEMORY_MMAP;
    if (ioctl(cam_fd, VIDIOC_DQBUF, &vbuf) < 0) return -1;

    /* 从 mmap 缓冲直接转换到输出缓冲,节省一次 memcpy */
    yuyv_to_argb8888(buffers[vbuf.index].start, rgb_buf, img_w, img_h);
    ioctl(cam_fd, VIDIOC_QBUF, &vbuf);

    if (size) *size = img_w * img_h * 4;
    return 0;
}
/*
 * YUYV → ARGB8888 转换(BT.601 近似)
 * 输入 4 字节 = 2 像素 (Y0 U Y1 V)
 * 输出每像素 4 字节 (B G R A)
 */
static void yuyv_to_argb8888(const uint8_t *src, uint8_t *dst, int w, int h)
{
    for (int i = 0; i < w * h / 2; i++) {
        int y0 = src[0], u = src[1] - 128, y1 = src[2], v = src[3] - 128;
        src += 4;

        int r = (298 * y0 + 409 * v + 128) >> 8;
        int g = (298 * y0 - 100 * u - 208 * v + 128) >> 8;
        int b = (298 * y0 + 516 * u + 128) >> 8;
        dst[0] = (uint8_t)(b < 0 ? 0 : (b > 255 ? 255 : b));
        dst[1] = (uint8_t)(g < 0 ? 0 : (g > 255 ? 255 : g));
        dst[2] = (uint8_t)(r < 0 ? 0 : (r > 255 ? 255 : r));
        dst[3] = 0xFF;
        dst += 4;

        r = (298 * y1 + 409 * v + 128) >> 8;
        g = (298 * y1 - 100 * u - 208 * v + 128) >> 8;
        b = (298 * y1 + 516 * u + 128) >> 8;
        dst[0] = (uint8_t)(b < 0 ? 0 : (b > 255 ? 255 : b));
        dst[1] = (uint8_t)(g < 0 ? 0 : (g > 255 ? 255 : g));
        dst[2] = (uint8_t)(r < 0 ? 0 : (r > 255 ? 255 : r));
        dst[3] = 0xFF;
        dst += 4;
    }
}