V4L2摄像头驱动开发:从零开始视频采集
·
一、顶级架构一句话总结
视频设备 → V4L2框架 → 缓冲区管理 → DMA传输 → 用户空间
V4L2(Video for Linux 2)是Linux视频设备的标准框架,专为摄像头、电视卡、视频采集卡等设备设计。
二、V4L2核心概念
V4L2设备类型
| 设备类型 | 描述 | 主设备号 |
|---|---|---|
| 视频捕获 | /dev/videoX | 81 |
| 视频输出 | /dev/videoX | 81 |
| VBI设备 | /dev/vbiX | 81 |
| 收音机 | /dev/radioX | 81 |
视频格式
- RGB格式:RGB24、RGB32、BGR24
- YUV格式:YUYV、UYVY、NV12、NV21、YUV420P
- 压缩格式:MJPEG、H.264、H.265
三、V4L2核心数据结构
video_device
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dev.h>
struct video_device {
const struct v4l2_file_operations *fops;
struct device dev;
struct cdev *cdev;
int minor;
int num;
char name[32];
struct v4l2_device *v4l2_dev;
void *priv;
enum vfl_devnode_type vfl_type;
enum vfl_direction vfl_dir;
struct mutex lock;
atomic_t refcount;
};
v4l2_device
struct v4l2_device {
struct device *dev;
struct media_device *mdev;
struct list_head subdevs;
spinlock_t lock;
char name[32];
void (*release)(struct v4l2_device *v4l2_dev);
};
vb2_queue(videobuf2队列)
#include <media/videobuf2-core.h>
struct vb2_queue {
const struct vb2_ops *ops;
void *drv_priv;
enum vb2_state state;
struct mutex lock;
spinlock_t done_lock;
struct list_head queued_list;
struct list_head done_list;
unsigned int num_buffers;
unsigned int buffer_count;
struct vb2_buffer *bufs[32];
};
四、V4L2文件操作
v4l2_file_operations
struct v4l2_file_operations {
struct module *owner;
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
unsigned int (*poll)(struct file *, struct poll_table_struct *);
long (*ioctl)(struct file *, unsigned int, unsigned long);
long (*compat_ioctl)(struct file *, unsigned int, unsigned long);
int (*mmap)(struct file *, struct vm_area_struct *);
int (*open)(struct file *);
int (*release)(struct file *);
};
关键ioctl命令
// 查询设备能力
VIDIOC_QUERYCAP
// 设置/获取格式
VIDIOC_S_FMT
VIDIOC_G_FMT
VIDIOC_TRY_FMT
// 请求缓冲区
VIDIOC_REQBUFS
// 查询缓冲区
VIDIOC_QUERYBUF
// 缓冲区入队
VIDIOC_QBUF
// 缓冲区出队
VIDIOC_DQBUF
// 启动/停止流
VIDIOC_STREAMON
VIDIOC_STREAMOFF
五、简单V4L2驱动示例
虚拟摄像头驱动
#include <linux/module.h>
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-vmalloc.h>
#define DRIVER_NAME "virtual_camera"
#define WIDTH 640
#define HEIGHT 480
#define FORMAT V4L2_PIX_FMT_YUYV
struct virtual_camera_dev {
struct v4l2_device v4l2_dev;
struct video_device vdev;
struct vb2_queue vb_queue;
struct mutex lock;
int streaming;
u8 *frame_buffer;
unsigned int sequence;
struct timer_list timer;
};
static struct virtual_camera_dev *cam_dev;
static void virtual_camera_generate_frame(struct virtual_camera_dev *dev)
{
int i, j;
u8 *p = dev->frame_buffer;
for (i = 0; i < HEIGHT; i++) {
for (j = 0; j < WIDTH; j += 2) {
*p++ = 0x40 + (i + dev->sequence) % 128; // Y
*p++ = 0x80; // U
*p++ = 0x40 + (j + dev->sequence) % 128; // Y
*p++ = 0x80; // V
}
}
dev->sequence++;
}
static void virtual_camera_timer(unsigned long data)
{
struct virtual_camera_dev *dev = (struct virtual_camera_dev *)data;
struct vb2_buffer *vb;
unsigned long flags;
if (!dev->streaming)
return;
spin_lock_irqsave(&dev->vb_queue.done_lock, flags);
if (list_empty(&dev->vb_queue.queued_list)) {
spin_unlock_irqrestore(&dev->vb_queue.done_lock, flags);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(33));
return;
}
vb = list_first_entry(&dev->vb_queue.queued_list,
struct vb2_buffer, queued_entry);
list_del(&vb->queued_entry);
spin_unlock_irqrestore(&dev->vb_queue.done_lock, flags);
virtual_camera_generate_frame(dev);
void *vaddr = vb2_plane_vaddr(vb, 0);
unsigned int size = vb2_plane_size(vb, 0);
memcpy(vaddr, dev->frame_buffer, min(size, (unsigned int)(WIDTH * HEIGHT * 2)));
vb->v4l2_buf.sequence = dev->sequence;
vb->v4l2_buf.timestamp.tv_sec = ktime_get_real_seconds();
vb->v4l2_buf.timestamp.tv_usec = ktime_get_real_ns() % NSEC_PER_SEC / 1000;
vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(33));
}
static int virtual_camera_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers,
unsigned int *nplanes,
unsigned int sizes[],
struct device *alloc_devs[])
{
struct virtual_camera_dev *dev = vb2_get_drv_priv(vq);
if (*nplanes != 1)
return -EINVAL;
sizes[0] = WIDTH * HEIGHT * 2;
return 0;
}
static int virtual_camera_buf_prepare(struct vb2_buffer *vb)
{
struct vb2_queue *vq = vb->vb2_queue;
struct virtual_camera_dev *dev = vb2_get_drv_priv(vq);
if (vb2_plane_size(vb, 0) < WIDTH * HEIGHT * 2)
return -EINVAL;
vb2_set_plane_payload(vb, 0, WIDTH * HEIGHT * 2);
return 0;
}
static void virtual_camera_buf_queue(struct vb2_buffer *vb)
{
struct vb2_queue *vq = vb->vb2_queue;
struct virtual_camera_dev *dev = vb2_get_drv_priv(vq);
unsigned long flags;
spin_lock_irqsave(&vq->done_lock, flags);
list_add_tail(&vb->queued_entry, &vq->queued_list);
spin_unlock_irqrestore(&vq->done_lock, flags);
}
static int virtual_camera_start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct virtual_camera_dev *dev = vb2_get_drv_priv(vq);
dev->streaming = 1;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(33));
return 0;
}
static void virtual_camera_stop_streaming(struct vb2_queue *vq)
{
struct virtual_camera_dev *dev = vb2_get_drv_priv(vq);
struct vb2_buffer *vb;
unsigned long flags;
dev->streaming = 0;
del_timer_sync(&dev->timer);
spin_lock_irqsave(&vq->done_lock, flags);
while (!list_empty(&vq->queued_list)) {
vb = list_first_entry(&vq->queued_list,
struct vb2_buffer, queued_entry);
list_del(&vb->queued_entry);
vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
}
spin_unlock_irqrestore(&vq->done_lock, flags);
}
static const struct vb2_ops virtual_camera_vb2_ops = {
.queue_setup = virtual_camera_queue_setup,
.buf_prepare = virtual_camera_buf_prepare,
.buf_queue = virtual_camera_buf_queue,
.start_streaming = virtual_camera_start_streaming,
.stop_streaming = virtual_camera_stop_streaming,
};
static int virtual_camera_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
strcpy(cap->driver, DRIVER_NAME);
strcpy(cap->card, "Virtual Camera");
strcpy(cap->bus_info, "platform:virtual_camera");
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int virtual_camera_enum_fmt_vid_cap(struct file *file, void *fh,
struct v4l2_fmtdesc *f)
{
if (f->index != 0)
return -EINVAL;
f->pixelformat = FORMAT;
strcpy(f->description, "YUYV 4:2:2");
return 0;
}
static int virtual_camera_g_fmt_vid_cap(struct file *file, void *fh,
struct v4l2_format *f)
{
f->fmt.pix.width = WIDTH;
f->fmt.pix.height = HEIGHT;
f->fmt.pix.pixelformat = FORMAT;
f->fmt.pix.field = V4L2_FIELD_INTERLACED;
f->fmt.pix.bytesperline = WIDTH * 2;
f->fmt.pix.sizeimage = WIDTH * HEIGHT * 2;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
return 0;
}
static int virtual_camera_s_fmt_vid_cap(struct file *file, void *fh,
struct v4l2_format *f)
{
if (f->fmt.pix.pixelformat != FORMAT)
return -EINVAL;
f->fmt.pix.width = WIDTH;
f->fmt.pix.height = HEIGHT;
f->fmt.pix.field = V4L2_FIELD_INTERLACED;
f->fmt.pix.bytesperline = WIDTH * 2;
f->fmt.pix.sizeimage = WIDTH * HEIGHT * 2;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
return 0;
}
static int virtual_camera_try_fmt_vid_cap(struct file *file, void *fh,
struct v4l2_format *f)
{
f->fmt.pix.width = WIDTH;
f->fmt.pix.height = HEIGHT;
f->fmt.pix.field = V4L2_FIELD_INTERLACED;
f->fmt.pix.bytesperline = WIDTH * 2;
f->fmt.pix.sizeimage = WIDTH * HEIGHT * 2;
f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
return 0;
}
static const struct v4l2_ioctl_ops virtual_camera_ioctl_ops = {
.vidioc_querycap = virtual_camera_querycap,
.vidioc_enum_fmt_vid_cap = virtual_camera_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = virtual_camera_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = virtual_camera_s_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = virtual_camera_try_fmt_vid_cap,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
};
static const struct v4l2_file_operations virtual_camera_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.read = vb2_fop_read,
.mmap = vb2_fop_mmap,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,
};
static int virtual_camera_probe(struct platform_device *pdev)
{
int ret;
cam_dev = devm_kzalloc(&pdev->dev, sizeof(*cam_dev), GFP_KERNEL);
if (!cam_dev)
return -ENOMEM;
mutex_init(&cam_dev->lock);
cam_dev->frame_buffer = devm_kmalloc(&pdev->dev, WIDTH * HEIGHT * 2, GFP_KERNEL);
if (!cam_dev->frame_buffer)
return -ENOMEM;
ret = v4l2_device_register(&pdev->dev, &cam_dev->v4l2_dev);
if (ret)
return ret;
cam_dev->vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
cam_dev->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
cam_dev->vb_queue.drv_priv = cam_dev;
cam_dev->vb_queue.buf_struct_size = sizeof(struct vb2_buffer);
cam_dev->vb_queue.ops = &virtual_camera_vb2_ops;
cam_dev->vb_queue.mem_ops = &vb2_vmalloc_memops;
cam_dev->vb_queue.lock = &cam_dev->lock;
ret = vb2_queue_init(&cam_dev->vb_queue);
if (ret) {
v4l2_device_unregister(&cam_dev->v4l2_dev);
return ret;
}
strcpy(cam_dev->vdev.name, DRIVER_NAME);
cam_dev->vdev.fops = &virtual_camera_fops;
cam_dev->vdev.ioctl_ops = &virtual_camera_ioctl_ops;
cam_dev->vdev.v4l2_dev = &cam_dev->v4l2_dev;
cam_dev->vdev.release = video_device_release_empty;
cam_dev->vdev.queue = &cam_dev->vb_queue;
cam_dev->vdev.vfl_type = VFL_TYPE_GRABBER;
cam_dev->vdev.vfl_dir = VFL_DIR_RX;
video_set_drvdata(&cam_dev->vdev, cam_dev);
ret = video_register_device(&cam_dev->vdev, VFL_TYPE_GRABBER, -1);
if (ret) {
vb2_queue_release(&cam_dev->vb_queue);
v4l2_device_unregister(&cam_dev->v4l2_dev);
return ret;
}
setup_timer(&cam_dev->timer, virtual_camera_timer, (unsigned long)cam_dev);
platform_set_drvdata(pdev, cam_dev);
pr_info("Virtual camera driver probed: /dev/video%d\n", cam_dev->vdev.num);
return 0;
}
static int virtual_camera_remove(struct platform_device *pdev)
{
struct virtual_camera_dev *dev = platform_get_drvdata(pdev);
del_timer_sync(&dev->timer);
video_unregister_device(&dev->vdev);
vb2_queue_release(&dev->vb_queue);
v4l2_device_unregister(&dev->v4l2_dev);
pr_info("Virtual camera driver removed\n");
return 0;
}
static struct platform_driver virtual_camera_driver = {
.probe = virtual_camera_probe,
.remove = virtual_camera_remove,
.driver = {
.name = DRIVER_NAME,
},
};
static struct platform_device *virtual_camera_device;
static int __init virtual_camera_init(void)
{
int ret;
ret = platform_driver_register(&virtual_camera_driver);
if (ret)
return ret;
virtual_camera_device = platform_device_alloc(DRIVER_NAME, -1);
if (!virtual_camera_device) {
platform_driver_unregister(&virtual_camera_driver);
return -ENOMEM;
}
ret = platform_device_add(virtual_camera_device);
if (ret) {
platform_device_put(virtual_camera_device);
platform_driver_unregister(&virtual_camera_driver);
return ret;
}
pr_info("Virtual camera driver loaded\n");
return 0;
}
static void __exit virtual_camera_exit(void)
{
platform_device_unregister(virtual_camera_device);
platform_driver_unregister(&virtual_camera_driver);
pr_info("Virtual camera driver unloaded\n");
}
module_init(virtual_camera_init);
module_exit(virtual_camera_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Developer");
MODULE_DESCRIPTION("Virtual V4L2 Camera Driver");
六、videobuf2操作
vb2_queue初始化
vb2_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vb2_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
vb2_queue.drv_priv = dev;
vb2_queue.ops = &my_vb2_ops;
vb2_queue.mem_ops = &vb2_vmalloc_memops;
vb2_queue.lock = &dev->lock;
vb2_queue_init(&vb2_queue);
vb2_ops回调
struct vb2_ops {
int (*queue_setup)(struct vb2_queue *vq, unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
struct device *alloc_devs[]);
int (*buf_prepare)(struct vb2_buffer *vb);
void (*buf_queue)(struct vb2_buffer *vb);
int (*start_streaming)(struct vb2_queue *vq, unsigned int count);
void (*stop_streaming)(struct vb2_queue *vq);
void (*buf_cleanup)(struct vb2_buffer *vb);
int (*buf_init)(struct vb2_buffer *vb);
};
缓冲区完成
vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
七、用户空间V4L2应用示例
#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 WIDTH 640
#define HEIGHT 480
#define BUFFER_COUNT 4
struct buffer {
void *start;
size_t length;
};
int main() {
int fd = open("/dev/video0", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
printf("Driver: %s\nCard: %s\n", cap.driver, cap.card);
struct v4l2_format fmt = {0};
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_INTERLACED;
ioctl(fd, VIDIOC_S_FMT, &fmt);
struct v4l2_requestbuffers req = {0};
req.count = BUFFER_COUNT;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &req);
struct buffer *buffers = calloc(req.count, sizeof(*buffers));
for (int i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ioctl(fd, VIDIOC_QUERYBUF, &buf);
buffers[i].length = buf.length;
buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, buf.m.offset);
}
for (int i = 0; i < req.count; i++) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ioctl(fd, VIDIOC_QBUF, &buf);
}
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);
for (int i = 0; i < 10; i++) {
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_DQBUF, &buf);
printf("Frame %d: seq=%u, ts=%ld.%06ld\n", i,
buf.sequence, buf.timestamp.tv_sec, buf.timestamp.tv_usec);
ioctl(fd, VIDIOC_QBUF, &buf);
}
ioctl(fd, VIDIOC_STREAMOFF, &type);
for (int i = 0; i < req.count; i++)
munmap(buffers[i].start, buffers[i].length);
free(buffers);
close(fd);
return 0;
}
八、调试技巧
查看V4L2设备
# 查看视频设备
ls -l /dev/video*
# 查看设备信息
v4l2-ctl --all
# 列出支持的格式
v4l2-ctl --list-formats
# 列出支持的分辨率
v4l2-ctl --list-formats-ext
测试视频采集
# 采集一帧图像
v4l2-ctl --stream-mmap=3 --stream-count=1 --stream-to=frame.raw
# 使用ffplay播放
ffplay -f rawvideo -pixel_format yuyv422 -video_size 640x480 frame.raw
九、终极总结
V4L2驱动 = 视频设备的标准框架
- 核心结构:video_device、v4l2_device、vb2_queue
- 缓冲区管理:videobuf2管理DMA缓冲区
- 两种模式:read()、mmap()/streaming
- 关键ioctl:QUERYCAP、S_FMT、REQBUFS、QBUF/DQBUF、STREAMON/OFF
掌握V4L2,就能开发各类视频设备!
更多推荐



所有评论(0)