深入学习Linux摄像头系列
深入学习Linux摄像头(三)虚拟摄像头驱动分析
上一篇文章讲解了V4L2的驱动框架,这一节我们来分析一个驱动程序,Linux内核带有一个虚拟摄像头驱动(vivi.c),这个虚拟摄像头使用V4L2驱动框架编写,只是少了硬件操作,数据来源是虚拟的,这篇文章就来分析它
看一个驱动程序首先从入口开始看起
module_init(vivi_init);
static int __init vivi_init(void)
{
vivi_create_instance(i);
}
入口函数调用了vivi_create_instance
static int __init vivi_create_instance(int inst)
{
struct vivi_dev *dev;
struct video_device *vfd;
/* 分配vivi_dev */
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
/* 初始化缓存队列 */
videobuf_queue_vmalloc_init(&dev->vb_vidq, &vivi_video_qops, // 赋值和初始化
NULL, &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE,
V4L2_FIELD_INTERLACED,
sizeof(struct vivi_buffer), dev);
/* 分配video_device */
vfd = video_device_alloc();
/* 设置video_device */
*vfd = vivi_template;
/* 注册video_device */
video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
}
从上面可以看到,首分配一个vivi_dev,看一看vivi_dev
struct vivi_dev {
struct v4l2_device v4l2_dev;
struct video_device *vfd;
...
/* 像素格式 */
struct vivi_fmt *fmt;
}
然后初始化缓存队列,videobuf_queue_vmalloc_init
这个函数v4l2的videobuf提供的接口,作用是初始化一个缓存队列struct videobuf_queue
,其中就设置了vivi_video_qops
struct videobuf_queue {
/* 缓存 */
struct videobuf_buffer *bufs[VIDEO_MAX_FRAME];
/* capture via mmap() + ioctl(QBUF/DQBUF) */
struct list_head stream; // 维护缓存队列的一个链表
}
static struct videobuf_queue_ops vivi_video_qops = {
.buf_setup = buffer_setup,
.buf_prepare = buffer_prepare,
.buf_queue = buffer_queue,
.buf_release = buffer_release,
};
这些函数会在操作缓存队列的时候会被调用
再接下来就是分配一个video_device
,设置它,然后再注册它
上一篇文章我们分析了调用video_register_device
会为video_device
注册一个字符设备并生成设备节点,当应用层发生系统调用时,会先调用到字符设备的fops,经过v4l2的核心层,最终回调到video_device
的fops
我们来看一看是如何设置video_device
的
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,
};
可以看到vivi.c提供的video_device模板设置了vivi_fops
和vivi_ioctl_ops
,这两个结构体中有一大堆回调函数,我们先看一眼,稍后再具体分析
static const struct v4l2_file_operations vivi_fops = {
.owner = THIS_MODULE,
.release = vivi_close,
.read = vivi_read,
.poll = vivi_poll,
.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_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
};
上面已经介绍了vivi.c大概做了什么事了,接下来我们按照v4l2的应用编写流程来具体分析每个回调函数的实现细节
这篇文章深入学习Linux摄像头(一)v4l2应用编程对v4l2应用编程作了详解的讲解
v4l2的操作流程
- 查询设备功能(VIDIOC_QUERYCAP)
- 枚举像素格式(VIDIOC_ENUM_FMT)
- 设置像素格式(VIDIOC_S_FMT)
- 申请缓存(VIDIOC_REQBUFS)
- 映射缓存(mmap)
- 缓存入队列(VIDIOC_QBUF)
- 打开流(VIDIOC_STREAMON)
- 等待数据可读(poll)
- 缓存出队列(VIDIOC_DQBUF)
下面按照这些流程来分析虚拟摄像头驱动
-
VIDIOC_QUERYCAP
返回设备的功能
static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { cap->version = VIVI_VERSION; cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | \ V4L2_CAP_READWRITE; }
V4L2_CAP_VIDEO_CAPTURE:表示捕获设备
V4L2_CAP_STREAMING:数据的读取支持流形式
V4L2_CAP_READWRITE:数据的读取支持read/write操作
-
VIDIOC_ENUM_FMT
枚举设备支持的像素格式
static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { fmt = &formats[f->index]; strlcpy(f->description, fmt->name, sizeof(f->description)); f->pixelformat = fmt->fourcc; }
从代码中可以看到,根据下表从formats数组中获取一项,然后将结果返回
看一看formats,表示vivi支持的像素格式
static struct vivi_fmt formats[] = { { .name = "4:2:2, packed, YUYV", .fourcc = V4L2_PIX_FMT_YUYV, .depth = 16, }, { .name = "4:2:2, packed, UYVY", .fourcc = V4L2_PIX_FMT_UYVY, .depth = 16, }, { .name = "RGB565 (LE)", .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */ .depth = 16, }, { .name = "RGB565 (BE)", .fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */ .depth = 16, }, { .name = "RGB555 (LE)", .fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */ .depth = 16, }, { .name = "RGB555 (BE)", .fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */ .depth = 16, }, };
-
VIDIOC_S_FMT
设置像素格式
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct vivi_dev *dev = video_drvdata(file); /* 测试是否支持此格式 */ vidioc_try_fmt_vid_cap(file, priv, f); /* 设置好格式 */ dev->fmt = get_format(f); }
首先调用
vidioc_try_fmt_vid_cap
测试是否支持此格式如果支持就记录在vivi_dev中
看一看
vidioc_try_fmt_vid_cap
static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { /* 判断是否能从formats数组中获取该像素 */ fmt = get_format(f); if (!fmt) return -EINVAL; /* 对齐 */ v4l_bound_align_image(); }
-
VIDIOC_REQBUFS
申请缓存
static int vidioc_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p) { videobuf_reqbufs(&dev->vb_vidq, p); }
通过调用videobuf提供的接口
其中的
dev->vb_vidq
是在vivi_create_instance
中初始化的struct videobuf_queue
对象static int __init vivi_create_instance(int inst) { ... /* 初始化缓存队列 */ videobuf_queue_vmalloc_init(&dev->vb_vidq, &vivi_video_qops, // 赋值和初始化 NULL, &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED, sizeof(struct vivi_buffer), dev); ... }
看一看
videobuf_reqbufs
做了什么int videobuf_reqbufs(struct videobuf_queue *q, struct v4l2_requestbuffers *req) { count = req->count; /* 回到buf_setup函数获取buf的个数和大小 */ q->ops->buf_setup(q, &count, &size); /* 申请缓存 */ __videobuf_mmap_setup(q, count, size, req->memory); }
其中的q->ops是在初始化缓存队列时设置的(videobuf_queue_vmalloc_init),内容如下
static struct videobuf_queue_ops vivi_video_qops = { .buf_setup = buffer_setup, .buf_prepare = buffer_prepare, .buf_queue = buffer_queue, .buf_release = buffer_release, };
我们看一看
buffer_setup
函数static int buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size) { /* 返回一帧图像的字节数 */ *size = dev->width * dev->height * 2; /* 设置好缓存个数 */ while (*size * *count > vid_limit * 1024 * 1024) (*count)--; }
接下来看一看
__videobuf_mmap_setup
如何申请缓存int __videobuf_mmap_setup(struct videobuf_queue *q, unsigned int bcount, unsigned int bsize, enum v4l2_memory memory) { for (i = 0; i < bcount; i++) { q->bufs[i] = videobuf_alloc(q); //分配缓存 q->bufs[i]->i = i; //缓存编号 q->bufs[i]->boff = PAGE_ALIGN(bsize) * i; //缓存偏移量 } }
可以看到调用了
videobuf_alloc
分配缓存struct videobuf_buffer *videobuf_alloc(struct videobuf_queue *q) { q->int_ops->alloc(q->msize); }
videobuf_alloc
又通过回调函数来分配缓存,那么这个回调函数是在什么时候设置的呢?在
videobuf_queue_vmalloc_init
初始化的时候videobuf_queue_vmalloc_init() { videobuf_queue_core_init(..., &qops); }
void videobuf_queue_core_init(..., struct videobuf_qtype_ops *int_ops) { q->int_ops = int_ops; }
可以看到
int_ops
被设置为&qops
static struct videobuf_qtype_ops qops = { .magic = MAGIC_QTYPE_OPS, .alloc = __videobuf_alloc, //申请缓存 .iolock = __videobuf_iolock, .mmap_mapper = __videobuf_mmap_mapper, .vaddr = videobuf_to_vmalloc, //得到缓存地址 };
所以
videobuf_alloc
最终会调用到qops
的__videobuf_alloc
最后分配
videobuf_buffer
结构体,此时并未分配真正的视频缓存区static struct videobuf_buffer *__videobuf_alloc(size_t size) { struct videobuf_buffer *vb; vb = kzalloc(size + sizeof(*mem), GFP_KERNEL); }
为什么分配内存要搞得如此复杂呢?
因为并非所有视频设备都使用相同形式得缓存区,事实上至少有三种变化
- 分散在物理和(内核)虚拟地址空间中的缓冲区
- 物理上分散但实际上是连续的缓冲物
- 物理上连续的缓冲区
videobuf支持这三种形式,提供了三种操作函数集,分别在缓存队列的初始化时设置
videobuf_queue_vmalloc_init() videobuf_queue_dma_contig_init() videobuf_queue_sg_init()
-
mmap
映射缓存
static int vivi_mmap(struct file *file, struct vm_area_struct *vma) { videobuf_mmap_mapper(&dev->vb_vidq, vma); }
videobuf_mmap_mapper
时videobuf提供得接口int videobuf_mmap_mapper(struct videobuf_queue *q, struct vm_area_struct *vma) { /* 根据偏移值找到缓存队列中对应的缓存 */ for (i = 0; i < VIDEO_MAX_FRAME; i++) { struct videobuf_buffer *buf = q->bufs[i]; if(buf->boff == (vma->vm_pgoff << PAGE_SHIFT)) CALL(q, mmap_mapper, q, buf, vma); //找到调用函数 } }
CALL
是一个宏定义,其定义如下#define CALL(q, f, arg...) \ ((q->int_ops->f) ? q->int_ops->f(arg) : 0)
又回调到
int_ops
,int_ops
在缓存队列初始化的时候被设置为qops
static struct videobuf_qtype_ops qops = { .magic = MAGIC_QTYPE_OPS, .alloc = __videobuf_alloc, //申请缓存 .iolock = __videobuf_iolock, .mmap_mapper = __videobuf_mmap_mapper, .vaddr = videobuf_to_vmalloc, //得到缓存地址 };
所以最终会调用到
__videobuf_mmap_mapper
,此函数真正地分配了视频缓存区,并映射到用户空间static int __videobuf_mmap_mapper(struct videobuf_queue *q, struct videobuf_buffer *buf, struct vm_area_struct *vma) { mem->vmalloc = vmalloc_user(pages); //分配视频缓存区 remap_vmalloc_range(vma, mem->vmalloc, 0); //映射 }
-
VIDIOC_QBUF
缓存入队列
static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) { videobuf_dqbuf(&dev->vb_vidq, p, file->f_flags & O_NONBLOCK); }
调用videobuf提供的
videobuf_dqbuf
int videobuf_qbuf(struct videobuf_queue *q, struct v4l2_buffer *b) { /* 回调buf_prepare函数 */ q->ops->buf_prepare(q, buf, field); /* 将缓存添加到缓存队列中 */ list_add_tail(&buf->stream, &q->stream); }
buf_prepare在缓存队列初始化的时候设置为
static struct videobuf_queue_ops vivi_video_qops = { .buf_setup = buffer_setup, .buf_prepare = buffer_prepare, .buf_queue = buffer_queue, .buf_release = buffer_release, };
static int buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, enum v4l2_field field) { /* 设置好图像格式 */ buf->fmt = dev->fmt; buf->vb.width = dev->width; buf->vb.height = dev->height; precalculate_bars(dev); precalculate_line(dev); /* 设置缓存状态 */ buf->vb.state = VIDEOBUF_PREPARED; }
-
VIDIOC_STREAMON
打开流
static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) { videobuf_streamon(&dev->vb_vidq); vivi_start_generating(file); }
调用videobuf提供的
videobuf_streamon
设置好缓存的状态,准备生产数据调用
vivi_start_generating
开始生产数据下面好好分析
vivi_start_generating
函数static void vivi_start_generating(struct file *file) { /* 启动一个线程 */ kthread_run(vivi_thread, dev, dev->v4l2_dev.name); }
看一看线程函数,此函数负责生产图像数据
static int vivi_thread(void *data) { for (;;) { vivi_sleep(dev); //只要队列中的buf被请求,那么就填充数据,然后唤醒等待队列 } }
static void vivi_sleep(struct vivi_dev *dev) { vivi_thread_tick(dev); //填充图像数据 schedule_timeout_interruptible(timeout); //调度,等待超时或者被唤醒继续填充数据 }
static void vivi_thread_tick(struct vivi_dev *dev) { /* 取出队列的第一个buf */ buf = list_entry(dma_q->active.next, struct vivi_buffer, vb.queue); /* 填充buf数据,设置buf的状态VIDEOBUF_DONE; */ vivi_fillbuff(dev, buf); /* 唤醒正在等待此缓存的进程 */ wake_up(&buf->vb.done); }
总结一下:
vidioc_streamon
会调用vivi_start_generating
启动了一个线程,此线程会填充buf的图像数据,然后唤醒正在等待此buf的线程,最后睡眠等待超时或者被唤醒继续填充下一块buf的图像数据 -
poll
等待缓存区有缓存准备好
static unsigned int vivi_poll(struct file *file, struct poll_table_struct *wait) { videobuf_poll_stream(file, q, wait); }
调用了videobuf提供的
videobuf_poll_stream
unsigned int videobuf_poll_stream(struct file *file, struct videobuf_queue *q, poll_table *wait) { struct videobuf_buffer *buf = NULL; /* 取得队列头的buf */ buf = list_entry(q->stream.next, //得到队列头 struct videobuf_buffer, stream); /* 阻塞等待这个buf */ poll_wait(file, &buf->done, wait); }
看到其中回调用poll_wait(file, &buf->done, wait)去等待这个buf,而在我们上面中讲到在
vivi_thread
中,只要填充完buf的数据后,就会唤醒等待这个buf的进程 -
VIDIOC_DQBUF
缓存出队列
static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) { videobuf_dqbuf(&dev->vb_vidq, p, file->f_flags & O_NONBLOCK); }
会调用videobuf提供的
videobuf_dqbuf
int videobuf_dqbuf(struct videobuf_queue *q, struct v4l2_buffer *b, int nonblocking) { /* 拿出队列的第一个buf */ stream_next_buffer(q, &buf, nonblocking); /* 判断buf的状态是否是VIDEOBUF_DONE */ switch (buf->state) { ... case VIDEOBUF_DONE: ... } /* 返回信息给用户空间 */ videobuf_status(q, b, buf, q->type); /* 从队列中删除,设置buf的状态 */ list_del(&buf->stream); buf->state = VIDEOBUF_IDLE; }
应用层得到buf的信息后,可以知道buf的编号,然后从之前mmap的缓存里取出队列的缓存读取图像的数据,处理完之后再次将缓存放入缓存队列中(VIDIOC_QBUF)
至此,vivi就分析完成了