V4L2视频驱动主要涉及到以下几个知识点:摄像头方面的知识、Camera解码器、控制器、V4L2的API和数据结构、V4L2的驱动架构。
一. 视频驱动的整体框架
Video的基本框架图如下:
对驱动程序员来说,主要是关心camera驱动这部分代码的实现。
二. V4L2重要的数据结构
常用的结构体在内核文件 include/linux/videodev2.h与v4l2-dev.h中定义,重要的数据结构如下面这些。
1. struct v4l2_requestbuffers //申请帧缓冲,对应命令 VIDIOC_REQBUFS
struct v4l2_requestbuffers{
__u32 count; //缓存数量,也就是说在缓存队列里保持多少张照片
enum v4l2_buf_type type; //数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
enum v4l2_memory memory;
//V4L2_MEMORY_MMAP或V4L2_MEMORY_USERPTR
__u32 reserved[2];
};
2. struct v4l2_capability //视频设备的功能,对应命令 VIDIOC_QUERYCAP
struct v4l2_capability{
__u8 driver[16]; //驱动名称
__u8 card[32];
__u8 bus_info[32]; //总线信息
__u32 version;
__u32 capabilities; //设备能力
__u32 device_caps;
__u32 reserved[3];
};
3. struct v4l2_input //视频输入信息,对应命令 VIDIOC_ENUMINPUT
struct v4l2_input{
__u32 index; /* Which input */ //应用关注的输入的索引号; 这是惟一一个用户空间设定的字段. 驱动要分配索引号给输入,从0开始,依次往上增加.
__u8 name[32]; /* Label */
//输入的名字,由驱动设定.简单起见,可以设为”Camera”,如果卡上有多个输入,名称就要与接口的打印相符合.
__u32 type; /* Type of input */
//输入的类型,现在只有两个值可选:V4L2_INPUT_TYPE_TUNER 和V4L2_INPUT_TYPE_CAMERA.
__u32 audioset; /* Associated audios (bitfield) *///描述哪个音频输入可以与些视频输入相关联.如果没有音频输入可以与之关联,或是只有一个可选,那么就可以简单地把这个字段置0.
__u32 tuner; /* Associated tuner *///如果输入是一个调谐器 (type 字段置为V4L2_INPUT_TYPE_TUNER), 这个字段就是会包含一个相应的调谐设备的索引号.
v4l2_std_id std; //描述设备支持哪个或哪些视频标准.
__u32 status; //给出输入的状态.,status 中设置的每一位都代表一个问题. 这些问题包括没有电源,没有信号,没有同频锁,或是其他一些不幸的问题.
__u32 capabilities;
__u32 reserved[3];
};
4. struct v4l2_standard //视频的制式,比如PAL ,NTSC ,对应命令 VIDIOC_ENUMSTD
struct v4l2_standard{
__u32 index;
v4l2_std_id id;
__u8 name[24];
struct v4l2_fract frameperiod; /* Frames, not fields */
__u32 framelines;
__u32 reserved[4];
};
5. struct v4l2_format //帧的格式,对应命令VIDIOC_G_FMT 、VIDIOC_S_FMT 等
struct v4l2_format{
enum v4l2_buf_type type; //数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format_mplane pix_mp; /*V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE*/
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
__u8 raw_data[200]; /* user-defined */
} fmt;
};
6. struct v4l2_buffer //驱动 中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
struct v4l2_buffer{
__u32 index; //字段是鉴别缓冲区的序号;它只在内存映射缓冲区中使用。内存映射缓冲区的index从0开始,依次递增。
enum v4l2_buf_type type; //是缓冲区的类型 ,通常是V4L2_BUF_TYPE_VIDEO_CAPTURE 或V4L2_BUF_TYPE_VIDEO_OUTPUT.
__u32 bytesused; //.缓冲区中的图像数据大小,对于捕获设备而言,驱动会设置bytesused; 对输出设备而言,应用必须设置这个字段。
__u32 flags;
enum v4l2_field field; //描述图像存在缓冲区的那一个区域。
struct timeval timestamp; //对于输入设备来说,代表帧捕获的时间.对输出设备来说,在没有到达时间戳所代表的时间前,驱动不可以把帧发送出去;时间戳值为0代表越快越 好。驱动会把时间戳设为帧的第一个字节传送到设备的时间,或者说是驱动所能达到的最接近的时间。
struct v4l2_timecode timecode; //用来存放时间编码,对于视频编辑类的应用是非常有用的。
__u32 sequence; //驱动对传过设备的帧维护了一个递增的计数; 每一帧传送时,它都会在sequence字段中存入现行序号。对于输入设备来讲,应用可以观察这一字段来检测帧。
enum v4l2_memory memory; //表示的是缓冲是内存映射的还是用户空间的。
union {
__u32 offset; //对于内存映谢的缓冲区,m.offset 描述的是缓冲区的位置. 规范将它描述为“从设备存储器开发的缓冲区偏移”,但其实质却是一个 magic cookie,应用可以将其传给mmap(),以确定那一个缓冲区被映射了。
unsigned long userptr; //然而对于用户空间缓冲区而言,m.userptr是缓冲区的用户空间地址。
struct v4l2_plane *planes;
} m;
__u32 length;
__u32 input; //可以用来快速切换捕获设备的输入 – 当然,这要得设备支持帧与帧音的快速切换才来。
__u32 reserved;
};
7. struct v4l2_crop //视频信号矩形边框
struct v4l2_crop{
enum v4l2_buf_type type;
struct v4l2_rect c;
};
8. v4l2_std_id //视频制式
typedef __u64 v4l2_std_id;
9. struct video_device //视频设备
struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
#endif
const struct v4l2_file_operations *fops; //设置v4l2_file_operations结构
struct device dev; /* v4l device */
struct cdev *cdev; /* character device */
struct device *parent; /* device parent */ //NULL .如果硬件中有多个PCI设备共享v4l2_device核心,那么就要设置父设备。
struct v4l2_device *v4l2_dev; /* v4l2_device parent */ //设置v4l2_device父设备
struct v4l2_ctrl_handler *ctrl_handler;
struct v4l2_prio_state *prio; //跟踪属性。用来实现VIDIOC_G/S_PRIORITY,如果设置为NULL,将使用v4l2_device中的v4l2_prio_state。
char name[32]; //设置唯一的描述名
int vfl_type;
int minor;
u16 num;
unsigned long flags; //可选,设置V4L2_FL_USE_FH_PRIO,如果想让framework来处理VIDIOC_G/S_PRIORITYioctls的话。
int index;
spinlock_t fh_lock; /* Lock for all v4l2_fhs */
struct list_head fh_list; /* List of struct v4l2_fh */
int debug; /* Activates debug level*/
v4l2_std_id tvnorms; /* Supported tv norms */
v4l2_std_id current_norm; /* Current tvnorm */
void (*release)(struct video_device *vdev);
const struct v4l2_ioctl_ops *ioctl_ops; //使用v4l2_ioctl_ops来简化ioctl的维护
struct mutex *lock; //如果想在驱动中进行全局锁定的话设置为NULL,否则初始化为一个 mutex_lock,这样就可以在unlocked_ioctl的操作之前和之后对操作内容进行保护
};
三. 关键API函数
1. 注册视频设备 video_register_device
static inline int __must_check video_register_device(struct video_device *vdev, int type, int nr)
{
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}
video核心层提供的注册函数,video_device是核心构建的数据结构,video_device_alloc()分配好一个video_device后,然再给这个结构初始化,最后调用这个函数来注册。type表示设备类型,nr,次设备号为base+nr。
2. 注销视频设备 video_unregister_device
void video_unregister_device(struct video_device *vdev)
{
if (!vdev || !video_is_registered(vdev))
return;
mutex_lock(&videodev_lock);
clear_bit(V4L2_FL_REGISTERED, &vdev->flags);
mutex_unlock(&videodev_lock);
device_unregister(&vdev->dev);
}
四. 驱动编写流程
1. 包含头文件
#include <linux/videodev2.h>
#include <media/videobuf2-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <media/v4l2-common.h>
2. 定义video_device并初始化各成员
static const struct v4l2_file_operations vivi_fops= {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vivi_close,
.read = vivi_read,
.poll = vivi_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = vivi_mmap,
};
static const struct v4l2_ioctl_ops vivi_ioctl_ops= {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_s_std = vidioc_s_std,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static struct video_device vivi_template= {
.name = "vivi",
.fops = &vivi_fops,
.ioctl_ops = &vivi_ioctl_ops,
.release = video_device_release,
.tvnorms = V4L2_STD_525_60,
.current_norm = V4L2_STD_NTSC_M,
};
3. 调用video_register_device注册video设备
static int __init vivi_create_instance(int inst)
{
struct vivi_dev *dev;
struct video_device *vfd;
struct v4l2_ctrl_handler *hdl;
struct vb2_queue *q;
int ret;
......
ret = -ENOMEM;
vfd = video_device_alloc();
if (!vfd)
goto unreg_dev;
*vfd = vivi_template;
vfd->debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;
set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
ret = video_register_device(vfd,VFL_TYPE_GRABBER, video_nr); //VFL_TYPE_GRABBER 表明是一个图像采集设备–包括摄像头、调谐器,诸如此类。
if (ret < 0)
{
video_device_release(vfd);
v4l2_device_unregister(&dev->v4l2_dev);
}
......
return 0;
}
五. 应用程序的处理流程
1. 打开设备文件
int fd = open(Devicename,mode);
Devicename:/dev/video0, /dev/video1
Mode:O_RDWR [| O_NONBLOCK]
如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。
2. 取得设备的capability
struct v4l2_capability capability;
int ret = ioctl(fd,
VIDIOC_QUERYCAP, &capability);
查看设备具有什么功能,比如是否具有视频输入特性。
3. 选择视频输入
struct v4l2_input input;
初始化input;
int ret = ioctl(fd,
VIDIOC_QUERYCAP, &input);
一个视频设备可以有多个视频输入。如果只有一路输入,这个功能可以没有。
4. 检测视频支持的制式
v4l2_std_id std;
do {
ret = ioctl(fd,
VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno == EAGAIN);
switch (std) {
case V4L2_STD_NTSC:
//……
case V4L2_STD_PAL:
//……
}
5. 设置视频捕获格式
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
fmt.fmt.pix.height = height;
fmt.fmt.pix.width = width;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
ret = ioctl(fd,
VIDIOC_S_FMT, &fmt);
if(ret) {
perror("VIDIOC_S_FMT\n");
close(fd);
return -1;
}
6. 向驱动申请帧缓存
struct v4l2_requestbuffers req;
if (ioctl(fd,
VIDIOC_REQBUFS, &req) == -1) {
return -1;
}
v4l2_requestbuffers 结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立 FIFO ,来提高视频采集的效率。
7. 获取每个缓存的信息,并 mmap 到用户空间
typedef struct VideoBuffer {
void *start;
size_t length;
}VideoBuffer;
VideoBuffer* buffers = calloc( req.count, sizeof(*buffers) );
struct v4l2_buffer buf;
for (numBufs = 0; numBufs < req.count; numBufs++) { //映射所有的缓存
memset( &buf, 0, sizeof(buf) );
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = numBufs;
if (ioctl( fd,
VIDIOC_QUERYBUF, &buf) == -1) { //获取到对应 index 的缓存信息,此处主要利用 length 信息及 offset 信息来完成后面的 mmap 操作。
return -1;
}
buffers[numBufs].length = buf.length; //转换成相对地址
buffers[numBufs].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,fd, buf.m.offset);
if (buffers[numBufs].start == MAP_FAILED) {
return -1;
}
8. 开始采集视频
int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = ioctl(fd,
VIDIOC_STREAMON, &buf_type);
9. 取出 FIFO 缓存中已经采样的帧缓存
struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=0;// 此值由下面的 ioctl 返回
if (ioctl(fd,
VIDIOC_DQBUF, &buf) == -1)
{
return -1;
}
根据返回的 buf.index 找到对应的 mmap 映射好的缓存,取出视频数据。
10. 将刚刚处理完的缓冲重新入队列尾,这样可以循环采集
if (ioctl(fd,
VIDIOC_QBUF, &buf) == -1) {
return -1;
}
11. 停止视频的采集
int ret = ioctl(fd,
VIDIOC_STREAMOFF, &buf_type);
12. 关闭视频设备
close(fd);