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验证)。