4. 申请帧缓冲区

任务说明

本案例实现Linux平台下基于V4L2的视频采集设备帧缓冲区申请与映射。目标设备为UVC摄像头或任意支持V4L2的Video Capture设备,通过mmap方式向驱动申请4个帧缓冲区,并将内核空间缓冲区映射到用户空间,为后续帧采集准备内存区域。

核心实现

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

#define DEVICE_PATH "/dev/video0"
#define BUFFER_COUNT 4

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

static int xioctl(int fd, unsigned long request, void *arg)
{
    int ret;
    do {
        ret = ioctl(fd, request, arg);
    } while (ret == -1 && errno == EINTR);
    return ret;
}

int main(void)
{
    int fd = open(DEVICE_PATH, O_RDWR);
    if (fd < 0) {
        perror("open");
        return -1;
    }

    /* 1. 查询设备能力,确认支持视频采集 */
    struct v4l2_capability cap;
    if (xioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
        perror("VIDIOC_QUERYCAP");
        close(fd);
        return -1;
    }
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        fprintf(stderr, "设备不支持视频采集\n");
        close(fd);
        return -1;
    }

    /* 2. 设置采集格式:分辨率640x480,像素格式YUYV */
    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 640;
    fmt.fmt.pix.height = 480;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field = V4L2_FIELD_NONE;
    if (xioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("VIDIOC_S_FMT");
        close(fd);
        return -1;
    }

    /* 3. 申请4个帧缓冲区(mmap方式) */
    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 (xioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
        perror("VIDIOC_REQBUFS");
        close(fd);
        return -1;
    }
    if (req.count < BUFFER_COUNT) {
        fprintf(stderr, "驱动返回的缓冲区数量不足: %u\n", req.count);
        close(fd);
        return -1;
    }

    /* 4. 逐个查询缓冲区信息并映射到用户空间 */
    struct frame_buffer buffers[BUFFER_COUNT];
    for (int i = 0; i < BUFFER_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 (xioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
            perror("VIDIOC_QUERYBUF");
            goto error;
        }

        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("mmap");
            goto error;
        }
        printf("Buffer %d: 长度=%lu, 映射地址=%p\n",
               i, buffers[i].length, buffers[i].start);
    }

    printf("成功申请并映射 %d 个帧缓冲区\n", BUFFER_COUNT);

    /* 清理:解除映射并关闭设备 */
    for (int i = 0; i < BUFFER_COUNT; i++) {
        if (buffers[i].start != MAP_FAILED)
            munmap(buffers[i].start, buffers[i].length);
    }
    close(fd);
    return 0;

error:
    for (int i = 0; i < BUFFER_COUNT; i++) {
        if (buffers[i].start != NULL && buffers[i].start != MAP_FAILED)
            munmap(buffers[i].start, buffers[i].length);
    }
    close(fd);
    return -1;
}

编译与运行

# 编译(需安装linux-headers,通常系统自带)
gcc -o v4l2_mmap v4l2_mmap.c

# 运行(需确保/dev/video0存在且有读写权限)
sudo ./v4l2_mmap

验证方法

程序执行后,预期输出4行缓冲区信息,每行包含缓冲区索引、长度(通常为640×480×2=614400字节)和映射后的用户空间虚拟地址。地址应为非空且各缓冲区地址不同。程序退出码为0表示成功;若驱动返回缓冲区数量不足或mmap失败,程序会打印错误信息并以非零码退出。

可通过dmesg | tail查看内核日志确认驱动侧缓冲区分配情况,或使用v4l2-ctl --list-buffers(需安装v4l-utils)交叉验证缓冲区数量为4。

判定标准

  • 程序无编译错误,运行后打印4行缓冲区信息且无错误输出;
  • 每行缓冲区长度一致且大于0,映射地址各不相同;
  • 程序正常退出(exit code 0),无段错误或内存泄漏(可用valgrind验证)。