Linux系统下通过C语言访问摄像头通常依赖Video for Linux Two(V4L2)框架,这是Linux内核提供的视频设备接口标准,广泛应用于USB摄像头、内置摄像头等视频输入设备,V4L2通过设备文件(如/dev/video0)与用户程序交互,支持视频捕获、格式设置、帧缓冲管理等功能,本文将详细介绍使用C语言结合V4L2获取摄像头视频流的完整流程,包括环境准备、核心概念、代码实现及常见问题处理。

环境准备
在开始开发前,需确保系统已安装必要的工具和库,以Ubuntu/Debian系统为例,通过以下命令安装V4L2开发工具和依赖库:
sudo apt update sudo apt install v4l-utils libv4l-dev build-essential
安装后,使用ls /dev/video*查看摄像头设备节点,通常为/dev/video0(若有多个摄像头,可能依次编号为video1、video2等),若提示权限不足,需将当前用户加入video组(重启后生效):
sudo usermod -a -G video $USER
V4L2核心概念
V4L2通过一系列ioctl系统调用控制摄像头,核心概念包括:
- 设备文件:摄像头以字符设备形式存在,通过open()函数以读写模式(O_RDWR)打开。
- 视频格式:包括像素格式(如YUYV、MJPG、RGB24)、分辨率(如640×480)、帧率(如30fps)等,需通过struct v4l2_format结构体设置。
- 帧缓冲:存储视频帧的内存区域,V4L2支持两种内存映射方式:
- mmap:将内核空间缓冲区映射到用户空间,减少数据拷贝,适合实时视频流;
- 用户指针:用户自行分配内存,需手动拷贝数据,灵活性较低但可控性强。
具体实现步骤
打开设备
使用open()函数打开摄像头设备文件,需检查返回值是否有效:
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
perror("Failed to open device");
exit(EXIT_FAILURE);
}
查询设备能力
通过VIDIOC_QUERYCAP ioctl获取设备基本信息(如设备名称、版本号、支持的功能):
struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) {
perror("VIDIOC_QUERYCAP failed");
close(fd);
exit(EXIT_FAILURE);
}
printf("Device: %sn", cap.card);
设置视频格式
根据摄像头支持的格式设置分辨率、像素格式等参数,首先填写struct v4l2_format结构体,然后通过VIDIOC_S_FMT ioctl应用设置:

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; // 像素格式(YUV422)
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; // 隔行/逐行扫描
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
perror("VIDIOC_S_FMT failed");
close(fd);
exit(EXIT_FAILURE);
}
申请缓冲区
通过VIDIOC_REQBUFS ioctl申请内核缓冲区,指定缓冲区数量和内存类型:
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 4; // 缓冲区数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP; // 使用mmap映射
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
perror("VIDIOC_REQBUFS failed");
close(fd);
exit(EXIT_FAILURE);
}
映射缓冲区
遍历缓冲区,通过VIDIOC_QUERYBUF ioctl获取每个缓冲区的物理地址,并使用mmap映射到用户空间:
struct buffer {
void *start;
size_t length;
} *buffers = calloc(req.count, sizeof(*buffers));
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) < 0) {
perror("VIDIOC_QUERYBUF failed");
free(buffers);
close(fd);
exit(EXIT_FAILURE);
}
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 failed");
free(buffers);
close(fd);
exit(EXIT_FAILURE);
}
}
开始捕获
将缓冲区入队(VIDIOC_QBUF),然后调用VIDIOC_STREAMON启动视频流:
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) < 0) {
perror("VIDIOC_QBUF failed");
free(buffers);
close(fd);
exit(EXIT_FAILURE);
}
}
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
perror("VIDIOC_STREAMON failed");
free(buffers);
close(fd);
exit(EXIT_FAILURE);
}
循环捕获帧
使用select()或poll()等待缓冲区就绪,然后通过VIDIOC_DQBUF出队获取帧数据,处理完成后重新入队:
fd_set fds;
struct timeval tv;
int r;
while (1) {
FD_ZERO(&fds);
FD_SET(fd, &fds);
tv.tv_sec = 2; // 超时时间(秒)
tv.tv_usec = 0;
r = select(fd + 1, &fds, NULL, NULL, &tv);
if (r < 0) {
perror("select failed");
break;
} else if (r == 0) {
printf("Timeoutn");
continue;
}
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) < 0) {
perror("VIDIOC_DQBUF failed");
break;
}
// 处理帧数据(buffers[buf.index].start指向帧数据)
printf("Captured frame: %d bytesn", buf.bytesused);
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
perror("VIDIOC_QBUF failed");
break;
}
}
释放资源
停止视频流(VIDIOC_STREAMOFF),取消内存映射,关闭设备文件:
ioctl(fd, VIDIOC_STREAMOFF, &type);
for (int i = 0; i < req.count; i++) {
munmap(buffers[i].start, buffers[i].length);
}
free(buffers);
close(fd);
常见视频格式对比
| 像素格式 | 说明 | 适用场景 |
|---|---|---|
| V4L2_PIX_FMT_YUYV | 紧缩YUV422格式,每2字节存储4像素的YUV数据,兼容性高 | 通用视频捕获、实时传输 |
| V4L2_PIX_FMT_MJPG | JPEG压缩格式,数据量小,需CPU解压 | 网络传输、低带宽场景 |
| V4L2_PIX_FMT_RGB24 | 每像素3字节(R/G/B),处理方便但数据量大,需转换格式 | 图像处理、GUI显示 |
| V4L2_PIX_FMT_GREY | 灰度格式,每像素1字节,节省内存 | 人脸检测、边缘检测等算法处理 |
常见问题处理
- 权限问题:若提示“Permission denied”,可临时通过
sudo chmod 666 /dev/video0开放权限,或确保用户属于video组。 - 设备未识别:检查摄像头是否连接,使用
lsusb查看USB设备,若未显示摄像头驱动,尝试加载驱动(如UVC摄像头:sudo modprobe uvcvideo)。 - 格式不支持:通过
v4l2-ctl --list-formats命令查看摄像头支持的所有格式,修改代码中的fmt.fmt.pix.pixelformat为支持的值。 - 帧率异常:降低分辨率或尝试其他帧率设置,部分摄像头可能不支持高帧率,需在格式设置中调整
fmt.fmt.pix.field参数。
FAQs
Q1: 运行程序提示“VIDIOC_S_FMT: Invalid argument”怎么办?
A: 通常是因为设置的分辨率或像素格式不被摄像头支持,可先用v4l2-ctl --list-formats命令查看设备支持的所有格式和分辨率组合,

v4l2-ctl --list-formats -d /dev/video0
然后修改代码中的fmt.fmt.pix.width、fmt.fmt.pix.height和fmt.fmt.pix.pixelformat为设备支持的值。
Q2: 如何实现实时预览摄像头画面?
A: 在循环捕获帧后,可将帧数据通过GUI库(如SDL、GTK)显示,以SDL为例,需安装SDL2库(sudo apt install libsdl2-dev),并将YUYV格式转换为RGB格式后渲染到窗口,关键代码片段如下:
#include <SDL2/SDL.h>
// 初始化SDL
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window = SDL_CreateWindow("Camera Preview", 0, 0, 640, 480, SDL_WINDOW_SHOWN);
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, 0);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, 640, 480);
// 在循环捕获帧后,转换YUYV到RGB并更新纹理
SDL_UpdateTexture(texture, NULL, rgb_buffer, 640 * 3);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
注意:YUYV到RGB的转换需自行实现算法,或使用libv4l2提供的转换函数。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/34488.html