2. 摄像头采集与预览
任务目标
在嵌入式Linux设备上,通过V4L2接口从USB摄像头采集原始视频帧,并将帧数据直接写入本地LCD framebuffer实现实时预览。本案例聚焦最小可运行闭环:V4L2采集→帧缓冲→LCD显示,不涉及格式转换、编码或网络传输。
环境与依赖
- 硬件:USB摄像头(支持YUYV格式)、带LCD屏的嵌入式板卡(如RV1126/全志V3s)
- 内核:启用V4L2驱动(
CONFIG_VIDEO_DEV)、LCD framebuffer驱动(CONFIG_FB) - 设备节点:
/dev/video0(摄像头)、/dev/fb0(LCD) - 交叉编译工具链(如arm-linux-gnueabihf-gcc)
实现原理
程序通过V4L2的VIDIOC_S_FMT将摄像头输出格式固定为YUYV(4:2:2打包格式),分辨率与LCD一致(如800×480)。采集到的YUYV数据无法直接显示在RGB565的LCD上,因此需要将每个像素的YUV分量转换为RGB565。转换后通过mmap映射写入framebuffer,实现单帧显示;循环采集即形成实时预览。
关键完整代码
// preview.c - V4L2 USB摄像头采集 + LCD framebuffer实时预览
#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>
#include <linux/fb.h>
#define WIDTH 800
#define HEIGHT 480
#define FB_SIZE (WIDTH * HEIGHT * 2) // RGB565
// YUV(YUYV) 转 RGB565
static unsigned short yuyv_to_rgb565(unsigned char y, unsigned char u, unsigned char v) {
int c = y - 16, d = u - 128, e = v - 128;
int r = (298 * c + 409 * e + 128) >> 8;
int g = (298 * c - 100 * d - 208 * e + 128) >> 8;
int b = (298 * c + 516 * d + 128) >> 8;
r = r < 0 ? 0 : (r > 255 ? 255 : r);
g = g < 0 ? 0 : (g > 255 ? 255 : g);
b = b < 0 ? 0 : (b > 255 ? 255 : b);
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
int main() {
// ---- 1. 打开LCD framebuffer ----
int fbfd = open("/dev/fb0", O_RDWR);
if (fbfd < 0) { perror("open fb0"); return 1; }
unsigned short *fb = mmap(NULL, FB_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fbfd, 0);
if (fb == MAP_FAILED) { perror("mmap fb"); return 1; }
// ---- 2. 打开摄像头并设置格式 ----
int vfd = open("/dev/video0", O_RDWR);
if (vfd < 0) { perror("open video0"); return 1; }
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(vfd, VIDIOC_S_FMT, &fmt) < 0) { perror("set format"); return 1; }
// ---- 3. 请求缓冲区并mmap ----
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(vfd, VIDIOC_REQBUFS, &req) < 0) { perror("reqbufs"); return 1; }
struct v4l2_buffer buf;
unsigned char *buffers[4];
for (int i = 0; i < 4; i++) {
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(vfd, VIDIOC_QUERYBUF, &buf) < 0) { perror("querybuf"); return 1; }
buffers[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
MAP_SHARED, vfd, buf.m.offset);
if (buffers[i] == MAP_FAILED) { perror("mmap buf"); return 1; }
if (ioctl(vfd, VIDIOC_QBUF, &buf) < 0) { perror("qbuf"); return 1; }
}
// ---- 4. 启动采集流 ----
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(vfd, VIDIOC_STREAMON, &type) < 0) { perror("streamon"); return 1; }
// ---- 5. 采集循环:取帧→转换→写LCD ----
for (int frame = 0; frame < 300; frame++) { // 约5秒@60fps
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(vfd, VIDIOC_DQBUF, &buf) < 0) { perror("dqbuf"); break; }
unsigned char *yuyv = buffers[buf.index];
for (int i = 0, p = 0; i < WIDTH * HEIGHT * 2; i += 4, p += 2) {
// 每4字节含2个像素: Y0 U Y1 V
fb[p] = yuyv_to_rgb565(yuyv[i], yuyv[i+1], yuyv[i+3]);
fb[p + 1] = yuyv_to_rgb565(yuyv[i+2], yuyv[i+1], yuyv[i+3]);
}
if (ioctl(vfd, VIDIOC_QBUF, &buf) < 0) { perror("qbuf"); break; }
usleep(16000); // 约60fps
}
// ---- 6. 清理 ----
ioctl(vfd, VIDIOC_STREAMOFF, &type);
for (int i = 0; i < 4; i++) munmap(buffers[i], 0);
munmap(fb, FB_SIZE);
close(vfd);
close(fbfd);
return 0;
}
编译与运行
# 交叉编译(示例)
arm-linux-gnueabihf-gcc -o preview preview.c
# 拷贝到目标板后运行
./preview
验证方法
- 运行程序前,确认
/dev/video0和/dev/fb0均存在且有读写权限。 - 执行
./preview后,观察LCD屏幕是否出现摄像头拍摄的实时画面。 - 用手在摄像头前晃动,画面应同步变化,无明显撕裂或卡顿。
- 程序运行约5秒后自动退出,终端无报错信息即表示采集与显示链路正常。
通过条件:LCD上呈现清晰、连续、方向正确的实时视频画面,且程序正常退出无错误输出。
常见问题
- 画面颜色异常:检查LCD实际像素格式是否为RGB565,若为RGB888需调整
FB_SIZE和写入方式。 - ioctl报错:确认摄像头支持YUYV格式且分辨率不超过其最大能力,可用
v4l2-ctl --list-formats-ext查询。 - 画面闪烁:尝试增加
usleep时间降低帧率,或检查LCD刷新率是否匹配。