一、关键点分析
(1)摄像头驱动程序结构
1. 分配结构体:video_device:video_device_alloc
2. 设置
.fops 基本的操作函数
.ioctl_ops (里面需要设置11项)(与摄像头操作密切相关的函数)
如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
3. 注册: video_register_device
(2)videobuf_queue_ops结构体
v4l2驱动需要包含一个videobuf_queue的实例用来管理缓冲队列,同时还要一个链表来维护这个队列,另外还要一个中断安全的spin_lock来保护队列的操作。
下一步就是要填充一个回调函数集来处理实际的缓冲区队列,这个函数集用videobuf_queue_ops来描述:
struct videobuf_queue_ops {
int *(buf_setup)(struct videobuf_queue*q, uint *count, uint *size);
int *(buf_prepare)(structvideobuf_queue *q, struct videobuf_buffer *vb,
enum v4l2_field field);
void *(buf_queue)(structvideobuf_queue*q,struct videobuf_buffer *vb);
void *(buf_release)(...);
}
buf_setup在IO处理请求之前被调用。目的是告诉videobuf关于IO的信息。count参数提供一个缓冲区个数的参考,驱动必须检查它的合理性,一个经验是大于等于2,小于等于32个。Size参数指定了每一帧数据的大小。
buf_prepare每一个缓冲(videobuf_buffer结构描述的)将被传递给该回调函数,用来配置缓冲的height,width和fileds。如果field参数被设置为 VIDEOBUF_NEEDS_INIT,那么驱动将把vb传递给videobuf_iolock()这个函数。除此之外,该回调函数通常也将为vb分配内存,最后把vb的状态置为VIDEOBUF_PREPARED。
buf_queue当一个vb需要被放入IO请求队列时,调用该回调。它将把这个buffer放到可用的buffer链表当中去,然后把状态置为VIDEOBUF_QUEUED。
buf_release当一个buffer不再使用的时候,调用该回调函数。驱动必须保证 buffer上没有活跃的IO请求,之后就可以将这个buffer传递给合适的 free函数,根据申请的buffer类型调用对应的释放函数:
(3)buf在用户空间和内核空间的关系
二、函数详解
static struct v4l2_format myvivi_format;
/* 队列操作1: 定义 */
static struct videobuf_queue myvivi_vb_vidqueue;
定义自旋锁
static spinlock_t myvivi_queue_slock;
struct list_head {
struct list_head *next, *prev;
};
定义队列结构体myvivi_vb_local_queue
static struct list_head myvivi_vb_local_queue;
定时器结构体定义
static struct timer_list myvivi_timer;
#include "fillbuf.c"
/* ------------------------------------------------------------------
Videobuf operations
------------------------------------------------------------------*/
/* APP调用ioctl VIDIOC_REQBUFS时会导致此函数被调用,
* 它重新调整count和size
*/
告诉videobuf关于IO的信息函数
{
size指定每帧数据的大小
*size = myvivi_format.fmt.pix.sizeimage;
count表示缓冲区个数
if (0 == *count)
*count = 32;
return 0;
}
/* APP调用ioctlVIDIOC_QBUF时导致此函数被调用,
* 它会填充video_buffer结构体并调用videobuf_iolock来分配内存
* 当把1个buf放入队列时,会事先调用buffer_prepare做一些准备工作
*/
用来配置缓冲的height,width和fileds函数
enum v4l2_field field)
{
/* 0. 设置videobuf */
vb->size = myvivi_format.fmt.pix.sizeimage;
vb->bytesperline = myvivi_format.fmt.pix.bytesperline;
vb->width = myvivi_format.fmt.pix.width;
vb->height = myvivi_format.fmt.pix.height;
vb->field = field;
/* 1. 做些准备工作 */
myvivi_precalculate_bars(0);
#if 0
/* 2. 调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存 */
if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
rc = videobuf_iolock(vq, &buf->vb, NULL);
if (rc < 0)
goto fail;
}
#endif
/* 3. 设置状态 */
vb->state = VIDEOBUF_PREPARED;
return 0;
}
/* APP调用ioctl VIDIOC_QBUF时:
* 1. 先调用buf_prepare进行一些准备工作
* 2. 把buf放入stream队列
* 3. 调用buf_queue(起通知、记录作用)
*/
当一个vb需要被放入IO请求队列时,调用该回调
{
vb->state = VIDEOBUF_QUEUED;
/* 把videobuf放入本地一个队列尾部
* 定时器处理函数就可以从本地队列取出videobuf
*/
list_add_tail(&vb->queue, &myvivi_vb_local_queue);
}
/* APP不再使用队列时, 用它来释放内存 */
一个buffer不再使用的时候,调用该回调函数
struct videobuf_buffer *vb)
{
videobuf_vmalloc_free(vb);
vb->state = VIDEOBUF_NEEDS_INIT;
}
videobuf_queue_ops结构体,成为为buf的操作函数
static struct videobuf_queue_ops myvivi_video_qops = {
.buf_setup = myvivi_buffer_setup,/* 计算大小以免浪费 */当应用程序调用request_buf向内核申请空间时,就会调用这个函数确定buf的个数和大小
.buf_prepare = myvivi_buffer_prepare,
.buf_queue = myvivi_buffer_queue,
.buf_release = myvivi_buffer_release,
};
/* ------------------------------------------------------------------
File operations for the device
------------------------------------------------------------------*/
open函数
static int myvivi_open(struct file *file)
{
/* 队列操作2: 初始化 */myvivi_vb_vidqueue是队列myvivi_video_qops是队列操作函数相关的结构体,myvivi_queue_slock是给队列用的锁。V4L2_BUF_TYPE_VIDEO_CAPTURE代表视频捕抓类型
videobuf_queue_vmalloc_init(&myvivi_vb_vidqueue, &myvivi_video_qops,
NULL, &myvivi_queue_slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,
sizeof(struct videobuf_buffer), NULL); /* 倒数第2个参数是buffer的头部大小 */
以时下linux kernel来说:1s=jiffies/HZ(即1秒=jiffies/HZ);在asm_i386中,HZ被定义为一个常,且为1000.一般在内核中定义超时是这样用,如:xxx_timer.expires = jiffies+HZ/100;这个定义表示超时时间为10ms,如果超过个时间就处理中断函数或者做你想做的事.当然HZ的分母你可以定为别的数。如HZ/1000等
把定时器的超时时间定为 jiffies + 1,
myvivi_timer.expires = jiffies + 1;
把定时器结构体timer_list放入内核定时链表
add_timer(&myvivi_timer);return 0;
}
close函数
static int myvivi_close(struct file *file)
{
把定时器结构体timer_list取出内核定时链表
videobuf_stop(&myvivi_vb_vidqueue);
videobuf_mmap_free(&myvivi_vb_vidqueue);
return 0;
}
映射函数
mmap系统调用以使能用户空间可以访问data数据
{
return videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);
}
poll函数
static unsigned int myvivi_poll(struct file *file, struct poll_table_struct *wait)
{
return videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait);
}
表示它是一个摄像头设备函数
static int myvivi_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
原型声明:char *strcpy(char* dest, const char *src);
strcpy(cap->card, "myvivi");
版本号
cap->version = 0x0001;
表明是一个视频捕捉设备,且用ioctl接口读取数据
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;return 0;
}
/* 列举支持哪种格式 */
static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
structv4l2_fmtdesc *f)
{
如果支持的格式数大于1,返回错误
if (f->index >= 1)return -EINVAL;
原型声明:char *strcpy(char* dest, const char *src);
strcpy(f->description, "4:2:2, packed, YUYV");
YUYV格式如下: Y0U0Y1V0 Y2U1Y3V1.......... 说明:一个Y代表一个像素,而一个Y和UV组合起来构成一个像素,所以第0个像素Y0和第一个像素Y1都是共用第0个像素的U0和V0。而每个分量Y,U,V都是占用一个字节的存储空间。所以Y0U0Y1V0相当于两个像素,占用了4个字节的存储空间,平均一个像素占用两个字节。
像素格式是YUYV
f->pixelformat = V4L2_PIX_FMT_YUYV;return 0;
}
/* 返回当前所使用的格式 */
static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
structv4l2_format *f)
{
void *memcpy(void *dest, const void *src, size_t n);
return (0);
}
/* 测试驱动程序是否支持某种格式 */
static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
unsigned int maxw, maxh;
enum v4l2_field field;
如果像素格式不是YUYV返回错误
if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
return -EINVAL;
field = f->fmt.pix.field;
V4L2_FIELD_ANY:Application 可以请求使用这个参数,如果V4L2_FIELD_NONE, V4L2_FIELD_TOP, V4L2_FIELD_BOTTOM V4L2_FIELD_INTERLACE 中任何一个格式都支持.驱动选择使用哪一个格式依赖于硬件能力,以及请求的image尺寸,驱动选择一个然后返回这个格式。struct_buffer的field成员不可以为V4L2_FIELD_ANY.
IV4L2_FIELD_INTERLACED:i mages包含top和bottom field, 隔行交替,场序依赖于当前video的标准。NTSC首先传输bottom field, PAL则先传输top field。
if (field == V4L2_FIELD_ANY) {
field = V4L2_FIELD_INTERLACED;
} else if (V4L2_FIELD_INTERLACED != field) {
return -EINVAL;
}
maxw = 1024;
maxh = 768;
/* 调整format的width, height,
* 计算bytesperline, sizeimage
*/
v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
&f->fmt.pix.height, 32, maxh, 0, 0);
bytesperline是每行字节数,16是每个像素用16位表示,右移3位相当于除以2^3,也就是单位是字节。
f->fmt.pix.bytesperline =(f->fmt.pix.width * 16) >> 3;
sizeimage是文件的大小,也就是高值*每行字节数
f->fmt.pix.height * f->fmt.pix.bytesperline;
return 0;
}
/* 设置格式 */
static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
structv4l2_format *f)
{
先试一下设置的格式是否符合要求
int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);if (ret < 0)
return ret;
如果符合要求,把设置的格式设为当前使用模式
memcpy(&myvivi_format, f, sizeof(myvivi_format));
return ret;
}
请求buf
static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
structv4l2_requestbuffers *p)
{
return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
}
查询buf
static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
}
把buf放入队列
static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
}
把buf从队列中取出
static int myvivi_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_dqbuf(&myvivi_vb_vidqueue, p,
file->f_flags & O_NONBLOCK));
}
启动摄像设备
static int myvivi_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
return videobuf_streamon(&myvivi_vb_vidqueue);
}
关闭摄像设备
static int myvivi_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
videobuf_streamoff(&myvivi_vb_vidqueue);
return 0;
}
11个ioctl
static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
// 表示它是一个摄像头设备
.vidioc_querycap = myvivi_vidioc_querycap,
/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.vidioc_enum_fmt_vid_cap = myvivi_vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = myvivi_vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = myvivi_vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = myvivi_vidioc_s_fmt_vid_cap,
/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
.vidioc_reqbufs = myvivi_vidioc_reqbufs,
.vidioc_querybuf = myvivi_vidioc_querybuf,
.vidioc_qbuf = myvivi_vidioc_qbuf,
.vidioc_dqbuf = myvivi_vidioc_dqbuf,
// 启动/停止
.vidioc_streamon = myvivi_vidioc_streamon,
.vidioc_streamoff = myvivi_vidioc_streamoff,
};
static const struct v4l2_file_operations myvivi_fops = {
.owner = THIS_MODULE,
.open = myvivi_open,
.release = myvivi_close,
.mmap = myvivi_mmap,
.ioctl = video_ioctl2, /* V4L2 ioctl handler */
.poll = myvivi_poll,
};
定义video_device结构体
static struct video_device *myvivi_device;
myvivi_releaseh函数
static void myvivi_release(struct video_device *vdev)
{
}
定时时间到时的处理函数
static void myvivi_timer_function(unsigned long data)
{
struct videobuf_buffer *vb;
void *vbuf;
struct timeval ts;
/* 1. 构造数据: 从队列头部取出第1个videobuf, 填充数据
*/
/* 1.1 从本地队列取出第1个videobuf */
if (list_empty(&myvivi_vb_local_queue)) {
goto out;
}
vb = list_entry(myvivi_vb_local_queue.next,
struct videobuf_buffer, queue);
/* Nobody is waiting on this buffer, return */
if (!waitqueue_active(&vb->done))
goto out;
/* 1.2 填充数据 */
vbuf = videobuf_to_vmalloc(vb);
//memset(vbuf, 0xff, vb->size);
myvivi_fillbuff(vb);
vb->field_count++;
do_gettimeofday(&ts);
vb->ts = ts;
vb->state = VIDEOBUF_DONE;
/* 1.3 把videobuf从本地队列中删除 */
list_del(&vb->queue);
/* 2. 唤醒进程: 唤醒videobuf->done上的进程 */
wake_up(&vb->done);
out:
/* 3. 修改timer的超时时间 : 30fps, 1秒里有30帧数据
* 每1/30 秒产生一帧数据
*/
mod_timer(&myvivi_timer, jiffies + HZ/30);
}
入口函数
static int myvivi_init(void)
{
int error;
/* 1. 分配一个video_device结构体 */
myvivi_device = video_device_alloc();
/* 2. 设置 */
/* 2.1 */ release成员指向myvivi_release函数
myvivi_device->release = myvivi_release;
/* 2.2 */
fops成员指向v4l2_file_operation结构体(基本操作函数)
myvivi_device->fops = &myvivi_fops;/* 2.3 */
ioctl_ops成员指向v4l2_ioctl_ops结构体(包含11个必须的ioctl)
myvivi_device->ioctl_ops = &myvivi_ioctl_ops;/* 2.4 队列操作
* a. 定义/初始化一个队列(会用到一个spinlock)
*/
在Linux中提供了一些机制用来避免竞争条件,最简单的一个种就是自旋锁,例如:当一个临界区的数据在多个函数之间被调用时,为了保护数据不被破坏,可以采用spinlock来保护临界区的数据,当然还有一个就是信号量也是可以实现临界区数据的保护的。以后在介绍信号量吧。这里还是先说说splinlock吧。
初始化自旋锁lock,其实是将自旋锁指针lock
在内核文件linux-2.6.30/include/linux/spinlock_types.h中
宏定义格式:# define spin_lock_init(lock)
spin_lock_init(&myvivi_queue_slock);
参数一:struct video_device * vdev: 我们想要注册的Video Device结构体。 参数二:int type: 要注册的device类型。其中包括: #define VFL_TYPE_GRABBER 0 //图像采集设备,包括摄像头,调谐器等。#define VFL_TYPE_VBI 1
此函数注册一个V4L2
error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
VFL_TYPE_GRABBER是注册设备的类型
/* 用定时器产生数据并唤醒进程 */
初始化myvivi_timer结构体
init_timer(&myvivi_timer);
myvivi_timer结构体的funtion指向myvivi_timer_function函数
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
队列结构体初始化
INIT_LIST_HEAD(&myvivi_vb_local_queue);
return error;
}
出口函数
static void myvivi_exit(void)
{
把结构体解除出队列
video_unregister_device(myvivi_device);
释放分配的结构体
video_device_release(myvivi_device);}
定义入口函数
module_init(myvivi_init);
定义出口函数
module_exit(myvivi_exit);
模块许可
MODULE_LICENSE("GPL");