V4L2驱动开发完全指南
第1章 V4L2框架概述
1.1 V4L2历史与发展
**V4L2**(Video for Linux Two)是Linux内核中视频设备的标准API框架。从Linux 2.5.46内核开始引入,逐步替代了旧的V4L接口。
**发展历程**:
-
1998年:V4L首次发布
-
2002年:V4L2正式成为内核标准
-
2010年:引入V4L2控制框架
-
2012年:引入媒体控制器框架
-
2015年:V4L2异步框架
1.2 V4L2系统架构
1.2.1 用户空间接口
```
用户空间应用程序
│
├── 应用程序层 (v4l2-ctl, gstreamer, ffmpeg)
│
├── V4L2库 (libv4l2)
│
└── 系统调用接口
```
1.2.2 内核空间架构
```
内核空间
├── 字符设备层 (V4L2字符设备)
│ ├── /dev/videoX
│ ├── /dev/v4l-subdevX
│ └── /dev/mediaX
│
├── V4L2核心层
│ ├── v4l2-device.c - 设备管理
│ ├── v4l2-ioctl.c - IOCTL处理
│ ├── v4l2-ctrls.c - 控制框架
│ ├── v4l2-fh.c - 文件句柄管理
│ └── v4l2-event.c - 事件通知
│
├── 视频缓冲区核心 (videobuf2)
│ ├── videobuf2-core.c
│ ├── videobuf2-v4l2.c
│ ├── videobuf2-memops.c
│ └── 内存后端 (dma-contig, dma-sg)
│
├── 子设备框架 (v4l2-subdev)
│ ├── 子设备注册
│ ├── 子设备操作
│ └── 内部接口
│
├── 媒体控制器框架
│ ├── 媒体设备
│ ├── 实体与链接
│ └── 拓扑管理
│
└── 平台特定驱动
├── 传感器驱动
├── ISP驱动
├── 编解码器驱动
└── 显示驱动
```
1.3 V4L2核心数据结构
1.3.1 struct v4l2_device
```c
struct v4l2_device {
struct device *dev; // 关联的设备
const char *name; // 设备名称
struct list_head subdevs; // 子设备链表
spinlock_t lock; // 锁
struct v4l2_ctrl_handler *ctrl_handler;
struct media_device *mdev; // 媒体设备
};
```
1.3.2 struct video_device
```c
struct video_device {
struct device dev; // 设备结构
struct cdev *cdev; // 字符设备
struct v4l2_device *v4l2_dev; // V4L2设备
const struct v4l2_file_operations *fops;
struct v4l2_ioctl_ops *ioctl_ops;
struct vb2_queue *queue; // 视频缓冲区队列
struct media_entity entity; // 媒体实体
u32 device_caps; // 设备能力
int minor; // 次设备号
char name[32]; // 设备名称
};
```
1.3.3 struct v4l2_subdev
```c
struct v4l2_subdev {
struct media_entity entity; // 媒体实体
struct list_head list; // 链表
struct module *owner; // 模块所有者
struct v4l2_device *v4l2_dev; // 所属V4L2设备
const struct v4l2_subdev_ops *ops;
struct v4l2_ctrl_handler *ctrl_handler;
char name[V4L2_SUBDEV_NAME_SIZE];
};
```
第2章 V4L2设备类型与能力
2.1 设备类型分类
2.1.1 捕获设备 (Capture)
```c
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001
#define V4L2_CAP_VIDEO_CAPTURE_MPLANE 0x00001000
```
用于从摄像头、采集卡等设备捕获视频数据。
2.1.2 输出设备 (Output)
```c
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002
#define V4L2_CAP_VIDEO_OUTPUT_MPLANE 0x00002000
```
用于视频输出到显示设备。
2.1.3 覆盖设备 (Overlay)
```c
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004
```
用于视频覆盖显示。
2.1.4 视频空白缓冲设备 (VBI)
```c
#define V4L2_CAP_VBI_CAPTURE 0x00000010
#define V4L2_CAP_VBI_OUTPUT 0x00000020
```
用于VBI(垂直消隐间隔)数据。
2.1.5 切片设备 (Sliced VBI)
```c
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080
```
切片VBI数据。
2.1.6 元数据设备 (Metadata)
```c
#define V4L2_CAP_META_CAPTURE 0x00080000
```
用于捕获元数据。
2.2 设备能力标志
2.2.1 流类型
```c
#define V4L2_CAP_STREAMING 0x04000000
#define V4L2_CAP_READWRITE 0x01000000
```
2.2.2 扩展能力
```c
#define V4L2_CAP_DEVICE_CAPS 0x80000000
#define V4L2_CAP_EXT_PIX_FORMAT 0x10000000
```
第3章 视频格式与像素格式
3.1 像素格式定义
3.1.1 常见RGB格式
```c
#define V4L2_PIX_FMT_RGB332 v4l2_fourcc('R', 'G', 'B', '1')
#define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R', 'G', 'B', 'P')
#define V4L2_PIX_FMT_RGB24 v4l2_fourcc('R', 'G', 'B', '3')
#define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B', 'G', 'R', '3')
#define V4L2_PIX_FMT_RGB32 v4l2_fourcc('R', 'G', 'B', '4')
#define V4L2_PIX_FMT_BGR32 v4l2_fourcc('B', 'G', 'R', '4')
```
3.1.2 YUV格式
```c
// 打包YUV格式
#define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y', 'U', 'Y', 'V')
#define V4L2_PIX_FMT_YVYU v4l2_fourcc('Y', 'V', 'Y', 'U')
#define V4L2_PIX_FMT_UYVY v4l2_fourcc('U', 'Y', 'V', 'Y')
#define V4L2_PIX_FMT_VYUY v4l2_fourcc('V', 'Y', 'U', 'Y')
// 平面YUV格式
#define V4L2_PIX_FMT_YUV420 v4l2_fourcc('Y', 'U', '1', '2')
#define V4L2_PIX_FMT_YVU420 v4l2_fourcc('Y', 'V', '1', '2')
#define V4L2_PIX_FMT_YUV422P v4l2_fourcc('4', '2', '2', 'P')
```
3.1.3 压缩格式
```c
#define V4L2_PIX_FMT_MJPEG v4l2_fourcc('M', 'J', 'P', 'G')
#define V4L2_PIX_FMT_JPEG v4l2_fourcc('J', 'P', 'E', 'G')
#define V4L2_PIX_FMT_H264 v4l2_fourcc('H', '2', '6', '4')
#define V4L2_PIX_FMT_H265 v4l2_fourcc('H', '2', '6', '5')
```
3.2 视频格式结构
3.2.1 struct v4l2_pix_format
```c
struct v4l2_pix_format {
__u32 width; // 图像宽度
__u32 height; // 图像高度
__u32 pixelformat; // 像素格式
__u32 field; // 场序
__u32 bytesperline; // 每行字节数
__u32 sizeimage; // 图像总大小
__u32 colorspace; // 色彩空间
__u32 priv; // 私有数据
__u32 flags; // 标志
union {
__u32 ycbcr_enc;
__u32 hsv_enc;
};
__u32 quantization; // 量化方式
__u32 xfer_func; // 转换函数
};
```
3.2.2 struct v4l2_pix_format_mplane
```c
struct v4l2_pix_format_mplane {
__u32 width;
__u32 height;
__u32 pixelformat;
__u32 field;
__u32 colorspace;
struct v4l2_plane_pix_format plane_fmt[VIDEO_MAX_PLANES];
__u8 num_planes; // 平面数
__u8 flags;
union {
__u8 ycbcr_enc;
__u8 hsv_enc;
};
__u8 quantization;
__u8 xfer_func;
__u32 reserved[7];
};
```
第4章 视频缓冲区管理
4.1 缓冲区类型
4.1.1 内存映射 (MMAP)
```c
#define V4L2_MEMORY_MMAP 1
```
-
内核分配缓冲区
-
用户空间通过mmap映射
4.1.2 用户指针 (USERPTR)
```c
#define V4L2_MEMORY_USERPTR 2
```
-
用户空间分配缓冲区
-
驱动直接访问用户空间内存
4.1.3 DMA缓冲区 (DMABUF)
```c
#define V4L2_MEMORY_DMABUF 4
```
-
使用DMA缓冲区框架
-
支持零拷贝
4.2 缓冲区状态机
4.2.1 缓冲区状态
```c
enum vb2_buffer_state {
VB2_BUF_STATE_DEQUEUED = 0, // 已出队
VB2_BUF_STATE_PREPARING, // 准备中
VB2_BUF_STATE_QUEUED, // 已入队
VB2_BUF_STATE_ACTIVE, // 活跃状态
VB2_BUF_STATE_DONE, // 处理完成
VB2_BUF_STATE_ERROR, // 错误状态
VB2_BUF_STATE_REQUEUEING, // 重新入队
};
```
4.2.2 缓冲区流转
```
DEQUEUED → PREPARING → QUEUED → ACTIVE → DONE → DEQUEUED
↑ ↓
└─────────────────────────────────────┘
```
4.3 videobuf2框架
4.3.1 核心数据结构
```c
struct vb2_queue {
enum v4l2_buf_type type; // 缓冲区类型
unsigned int io_modes; // IO模式
const struct vb2_mem_ops *mem_ops; // 内存操作
const struct vb2_ops *ops; // 驱动操作
void *drv_priv; // 驱动私有数据
unsigned int buf_struct_size; // 缓冲区结构大小
struct mutex *lock; // 锁
struct list_head queued_list; // 已入队列表
atomic_t owned_by_drv_count; // 驱动持有计数
void *alloc_ctx; // 分配上下文
unsigned int min_buffers_needed; // 最小缓冲区数
};
```
4.3.2 内存操作回调
```c
struct vb2_mem_ops {
void *(*alloc)(struct vb2_buffer *vb, unsigned long size);
void (*put)(void *buf_priv);
struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags);
void *(*get_userptr)(struct vb2_buffer *vb, unsigned long vaddr,
unsigned long size, enum dma_data_direction dma_dir);
void (*put_userptr)(void *buf_priv);
void (*prepare)(void *buf_priv);
void (*finish)(void *buf_priv);
void (*attach_dmabuf)(struct vb2_buffer *vb, struct dma_buf *dbuf);
void (*detach_dmabuf)(struct vb2_buffer *vb);
int (*map_dmabuf)(void *buf_priv);
void (*unmap_dmabuf)(void *buf_priv);
void *(*vaddr)(void *buf_priv);
void *(*cookie)(void *buf_priv);
unsigned int (*num_users)(void *buf_priv);
int (*mmap)(void *buf_priv, struct vm_area_struct *vma);
};
```
第5章 V4L2控制框架
5.1 控制类型
5.1.1 标准控制
```c
// 基础控制
#define V4L2_CID_BRIGHTNESS 0x00980900
#define V4L2_CID_CONTRAST 0x00980901
#define V4L2_CID_SATURATION 0x00980902
#define V4L2_CID_HUE 0x00980903
// 白平衡
#define V4L2_CID_AUTO_WHITE_BALANCE 0x0098090c
#define V4L2_CID_WHITE_BALANCE_TEMPERATURE 0x0098090f
// 曝光控制
#define V4L2_CID_EXPOSURE_AUTO 0x00980912
#define V4L2_CID_EXPOSURE_ABSOLUTE 0x00980913
```
5.1.2 自定义控制
```c
#define V4L2_CID_PRIVATE_BASE 0x08000000
```
5.2 控制结构
5.2.1 struct v4l2_ctrl
```c
struct v4l2_ctrl {
struct list_head node;
struct v4l2_ctrl_handler *handler;
struct v4l2_ctrl **cluster;
unsigned int ncontrols;
unsigned int done:1;
unsigned int id; // 控制ID
const char *name; // 控制名称
enum v4l2_ctrl_type type; // 控制类型
s32 minimum, maximum, default_value;
u32 step;
u32 menu_skip_mask;
const char * const *qmenu;
const s64 *qmenu_int;
unsigned long flags;
s32 val; // 当前值
struct v4l2_ctrl_ops *ops;
};
```
5.2.2 控制类型枚举
```c
enum v4l2_ctrl_type {
V4L2_CTRL_TYPE_INTEGER = 1,
V4L2_CTRL_TYPE_BOOLEAN = 2,
V4L2_CTRL_TYPE_MENU = 3,
V4L2_CTRL_TYPE_BUTTON = 4,
V4L2_CTRL_TYPE_INTEGER64 = 5,
V4L2_CTRL_TYPE_CTRL_CLASS = 6,
V4L2_CTRL_TYPE_STRING = 7,
V4L2_CTRL_TYPE_BITMASK = 8,
V4L2_CTRL_TYPE_INTEGER_MENU = 9,
V4L2_CTRL_TYPE_U8 = 0x0100,
V4L2_CTRL_TYPE_U16 = 0x0101,
V4L2_CTRL_TYPE_U32 = 0x0102,
};
```
第6章 子设备框架
6.1 子设备操作
6.1.1 子设备操作结构
```c
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core;
const struct v4l2_subdev_tuner_ops *tuner;
const struct v4l2_subdev_audio_ops *audio;
const struct v4l2_subdev_video_ops *video;
const struct v4l2_subdev_vbi_ops *vbi;
const struct v4l2_subdev_ir_ops *ir;
const struct v4l2_subdev_sensor_ops *sensor;
const struct v4l2_subdev_pad_ops *pad;
};
```
6.1.2 核心操作
```c
struct v4l2_subdev_core_ops {
int (*log_status)(struct v4l2_subdev *sd);
int (*s_io_pin_config)(struct v4l2_subdev *sd, size_t n,
struct v4l2_subdev_io_pin_config *pincfg);
int (*init)(struct v4l2_subdev *sd, u32 val);
int (*load_fw)(struct v4l2_subdev *sd);
int (*reset)(struct v4l2_subdev *sd, u32 val);
int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
int (*s_power)(struct v4l2_subdev *sd, int on);
int (*interrupt_service_routine)(struct v4l2_subdev *sd,
u32 status, bool *handled);
int (*subscribe_event)(struct v4l2_subdev *sd,
struct v4l2_fh *fh,
struct v4l2_event_subscription *sub);
int (*unsubscribe_event)(struct v4l2_subdev *sd,
struct v4l2_fh *fh,
struct v4l2_event_subscription *sub);
};
```
6.2 子设备注册与使用
6.2.1 注册函数
```c
int v4l2_subdev_init(struct v4l2_subdev *sd,
const struct v4l2_subdev_ops *ops);
int v4l2_async_register_subdev(struct v4l2_subdev *sd);
void v4l2_async_unregister_subdev(struct v4l2_subdev *sd);
```
6.2.2 子设备调用
```c
// 调用子设备操作
v4l2_subdev_call(sd, video, s_stream, 1);
v4l2_subdev_call(sd, core, s_power, 1);
```
第7章 媒体控制器框架
7.1 媒体控制器概念
7.1.1 核心概念
-
**媒体设备** (Media Device): 顶级媒体设备
-
**实体** (Entity): 硬件组件(传感器、ISP等)
-
**接口** (Pad): 实体的输入/输出接口
-
**链接** (Link): 实体间的连接关系
7.1.2 数据结构
```c
struct media_device {
struct device *dev;
char model[32];
char driver_name[32];
struct media_entity_graph graph;
struct list_head entities;
struct mutex graph_mutex;
};
struct media_entity {
struct list_head list;
struct media_device *graph_obj.mdev;
u32 id;
const char *name;
u32 type;
u32 revision;
u32 flags;
u32 group_id;
u16 num_pads;
u16 num_links;
u16 num_backlinks;
struct media_pad *pads;
struct media_link *links;
};
```
7.2 媒体拓扑配置
7.2.1 创建链接
```c
struct media_link *media_create_pad_link(struct media_entity *source,
u16 source_pad,
struct media_entity *sink,
u16 sink_pad,
u32 flags);
```
7.2.2 设备树配置
```dts
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
/* 连接到ISP输入 */
isp_in: endpoint {
remote-endpoint = <&sensor_out>;
bus-width = <8>;
hsync-active = <1>;
vsync-active = <1>;
};
};
};
```
第8章 流控制与同步
8.1 流控制机制
8.1.1 启动/停止流
```c
int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type);
int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type);
```
8.1.2 缓冲区处理
```c
int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b);
int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking);
```
8.2 同步机制
8.2.1 时间戳
```c
struct timeval timestamp; // V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
```
8.2.2 事件通知
```c
struct v4l2_event {
u32 type;
union {
struct v4l2_event_vsync vsync;
struct v4l2_event_ctrl ctrl;
u8 data[64];
} u;
u32 pending;
u32 sequence;
struct timespec timestamp;
u32 id;
u32 reserved[8];
};
```
第9章 驱动开发最佳实践
9.1 错误处理
9.1.1 资源管理
```c
static int my_driver_probe(struct platform_device *pdev)
{
struct my_device *dev;
int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
// 使用devm_系列函数管理资源
dev->clk = devm_clk_get(&pdev->dev, "aclk");
if (IS_ERR(dev->clk)) {
ret = PTR_ERR(dev->clk);
dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
return ret;
}
// 注册设备
ret = video_register_device(&dev->vdev, VFL_TYPE_VIDEO, -1);
if (ret) {
dev_err(&pdev->dev, "Failed to register video device\n");
return ret;
}
return 0;
}
```
9.2 性能优化
9.2.1 零拷贝支持
```c
// 支持DMABUF
.queue->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
// 实现DMABUF操作
static struct vb2_mem_ops my_memops = {
.get_dmabuf = my_get_dmabuf,
.attach_dmabuf = my_attach_dmabuf,
.map_dmabuf = my_map_dmabuf,
.unmap_dmabuf = my_unmap_dmabuf,
};
```
9.2.2 中断处理优化
```c
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
struct my_buffer *buf;
spin_lock(&dev->lock);
if (list_empty(&dev->active_list)) {
spin_unlock(&dev->lock);
return IRQ_NONE;
}
buf = list_first_entry(&dev->active_list, struct my_buffer, list);
list_del(&buf->list);
spin_unlock(&dev->lock);
// 标记缓冲区完成
buf->vb.vb2_buf.timestamp = ktime_get_ns();
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
return IRQ_HANDLED;
}
```
第10章 调试与测试
10.1 调试工具
10.1.1 内核调试
```bash
启用调试输出
echo 8 > /proc/sys/kernel/printk
查看驱动消息
dmesg | grep -i v4l2
调试级别控制
#define debug 1
#ifdef debug
#define v4l2_dbg(fmt, args...) printk(KERN_DEBUG fmt, ##args)
#else
#define v4l2_dbg(fmt, args...)
#endif
```
10.1.2 用户空间工具
```bash
列出设备
v4l2-ctl --list-devices
查看设备能力
v4l2-ctl -d /dev/video0 --info
查看格式
v4l2-ctl -d /dev/video0 --list-formats-ext
测试采集
v4l2-ctl -d /dev/video0 --stream-mmap=3 --stream-count=100 --stream-to=test.raw
```
10.2 单元测试
10.2.1 测试用例
```c
// 模拟硬件中断
static void simulate_frame(struct work_struct *work)
{
struct my_device *dev = container_of(work, struct my_device, frame_work);
struct my_buffer *buf;
if (list_empty(&dev->buf_list))
return;
buf = list_first_entry(&dev->buf_list, struct my_buffer, list);
list_del(&buf->list);
// 填充测试数据
fill_test_pattern(buf->vaddr, dev->pix_fmt.width, dev->pix_fmt.height);
// 通知缓冲区完成
buf->vb.vb2_buf.timestamp = ktime_get_ns();
vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
}
```
附录A: V4L2 IOCTL命令速查
A.1 设备信息
```c
VIDIOC_QUERYCAP // 查询设备能力
VIDIOC_ENUM_FMT // 枚举格式
VIDIOC_G_FMT // 获取格式
VIDIOC_S_FMT // 设置格式
VIDIOC_TRY_FMT // 尝试格式
```
A.2 缓冲区管理
```c
VIDIOC_REQBUFS // 请求缓冲区
VIDIOC_QUERYBUF // 查询缓冲区
VIDIOC_QBUF // 入队缓冲区
VIDIOC_DQBUF // 出队缓冲区
VIDIOC_CREATE_BUFS // 创建缓冲区
VIDIOC_PREPARE_BUF // 准备缓冲区
VIDIOC_EXPBUF // 导出缓冲区
```
A.3 流控制
```c
VIDIOC_STREAMON // 启动流
VIDIOC_STREAMOFF // 停止流
```
A.4 控制接口
```c
VIDIOC_QUERYCTRL // 查询控制
VIDIOC_QUERYMENU // 查询菜单
VIDIOC_G_CTRL // 获取控制值
VIDIOC_S_CTRL // 设置控制值
VIDIOC_G_EXT_CTRLS // 获取扩展控制
VIDIOC_S_EXT_CTRLS // 设置扩展控制
VIDIOC_TRY_EXT_CTRLS // 尝试扩展控制
```
A.5 输入输出
```c
VIDIOC_ENUMINPUT // 枚举输入
VIDIOC_G_INPUT // 获取当前输入
VIDIOC_S_INPUT // 选择输入
VIDIOC_ENUMOUTPUT // 枚举输出
VIDIOC_G_OUTPUT // 获取当前输出
VIDIOC_S_OUTPUT // 选择输出
```
A.6 其他
```c
VIDIOC_G_PARM // 获取参数
VIDIOC_S_PARM // 设置参数
VIDIOC_G_STD // 获取标准
VIDIOC_S_STD // 设置标准
VIDIOC_ENUMSTD // 枚举标准
VIDIOC_ENUM_FRAMESIZES // 枚举帧大小
VIDIOC_ENUM_FRAMEINTERVALS // 枚举帧间隔
```
附录B: 常见问题解决
B.1 格式协商失败
**问题**: 应用程序请求的格式驱动不支持
**解决**:
-
在驱动中正确实现枚举格式
-
在try_fmt中验证格式参数
-
支持标准格式转换
B.2 DMA内存分配失败
**问题**: CMA内存不足或对齐错误
**解决**:
-
检查内核CMA配置
-
确保缓冲区大小正确对齐
-
考虑使用DMABUF导入
B.3 流控制问题
**问题**: 启动/停止流失败
**解决**:
-
确保在streamon之前有足够缓冲区
-
正确处理硬件启动顺序
-
实现正确的错误恢复机制
这个V4L2驱动开发指南涵盖了从基础概念到高级特性的完整知识体系。理解这些概念后,你将能够:
-
设计并实现V4L2驱动
-
调试和优化驱动性能
-
集成复杂的多媒体硬件
-
提供稳定的用户空间API
建议在实际开发中结合内核源码和现有驱动进行学习,逐步掌握V4L2驱动的开发技巧。