然后初始化缓存队列,videobuf_queue_vmalloc_init这个函数v4l2的videobuf提供的接口,作用是初始化一个缓存队列struct videobuf_queue,其中就设置了vivi_video_qops
 
struct videobuf_queue {
    
	struct videobuf_buffer     *bufs[VIDEO_MAX_FRAME];
    
	
	struct list_head           stream; 
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 -  
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;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 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;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 从代码中可以看到,根据下表从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, 
		.depth    = 16,
	},
	{
		.name     = "RGB565 (BE)",
		.fourcc   = V4L2_PIX_FMT_RGB565X, 
		.depth    = 16,
	},
	{
		.name     = "RGB555 (LE)",
		.fourcc   = V4L2_PIX_FMT_RGB555, 
		.depth    = 16,
	},
	{
		.name     = "RGB555 (BE)",
		.fourcc   = V4L2_PIX_FMT_RGB555X, 
		.depth    = 16,
	},
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">
- 1
 - 2
 - 3
 - 4
 - 5
 - 6
 - 7
 - 8
 - 9
 - 10
 - 11
 - 12
 - 13
 - 14
 - 15
 - 16
 - 17
 - 18
 - 19
 - 20
 - 21
 - 22
 - 23
 - 24
 - 25
 - 26
 - 27
 - 28
 - 29
 - 30
 - 31
 - 32
 
  -  
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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 首先调用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)
{
    
	fmt = get_format(f);   
    if (!fmt)
        return -EINVAL;
    
    
    v4l_bound_align_image();
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  -  
VIDIOC_REQBUFS
 申请缓存
 static int vidioc_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *p)
{
    videobuf_reqbufs(&dev->vb_vidq, p);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 通过调用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);
    ...
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 看一看videobuf_reqbufs做了什么
 int videobuf_reqbufs(struct videobuf_queue *q,
		 struct v4l2_requestbuffers *req)
{
    count = req->count;
    
    
    q->ops->buf_setup(q, &count, &size);
    
    
    __videobuf_mmap_setup(q, count, size, req->memory);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 其中的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,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 我们看一看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)--;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 接下来看一看__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; 
    }
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 可以看到调用了videobuf_alloc分配缓存
 struct videobuf_buffer *videobuf_alloc(struct videobuf_queue *q)
{
    q->int_ops->alloc(q->msize);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 videobuf_alloc又通过回调函数来分配缓存,那么这个回调函数是在什么时候设置的呢?
 在videobuf_queue_vmalloc_init初始化的时候
 videobuf_queue_vmalloc_init()
{
	videobuf_queue_core_init(..., &qops);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 void videobuf_queue_core_init(..., struct videobuf_qtype_ops *int_ops)
{
    q->int_ops   = int_ops;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 可以看到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, 
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 所以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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 为什么分配内存要搞得如此复杂呢?
 因为并非所有视频设备都使用相同形式得缓存区,事实上至少有三种变化
 
  - 分散在物理和(内核)虚拟地址空间中的缓冲区
 - 物理上分散但实际上是连续的缓冲物
 - 物理上连续的缓冲区
 
 videobuf支持这三种形式,提供了三种操作函数集,分别在缓存队列的初始化时设置
 videobuf_queue_vmalloc_init()
videobuf_queue_dma_contig_init()
videobuf_queue_sg_init()
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  -  
mmap
 映射缓存
 static int vivi_mmap(struct file *file, struct vm_area_struct *vma)
{
    videobuf_mmap_mapper(&dev->vb_vidq, vma);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 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); 
    }
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 CALL是一个宏定义,其定义如下
 #define CALL(q, f, arg...)						\
	((q->int_ops->f) ? q->int_ops->f(arg) : 0)
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 又回调到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, 
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 所以最终会调用到__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); 
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  -  
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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 调用videobuf提供的videobuf_dqbuf
 int videobuf_qbuf(struct videobuf_queue *q, struct v4l2_buffer *b)
{
    
    q->ops->buf_prepare(q, buf, field);
    
    
    list_add_tail(&buf->stream, &q->stream);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 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,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 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;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  -  
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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 调用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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 看一看线程函数,此函数负责生产图像数据
 static int vivi_thread(void *data)
{
	for (;;) {
		vivi_sleep(dev); 
	}
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 static void vivi_sleep(struct vivi_dev *dev)
{   
    vivi_thread_tick(dev); 
    
    schedule_timeout_interruptible(timeout); 
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 static void vivi_thread_tick(struct vivi_dev *dev)
{
    
	buf = list_entry(dma_q->active.next,
			 struct vivi_buffer, vb.queue);
    
    
    vivi_fillbuff(dev, buf);
    
    
    wake_up(&buf->vb.done);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 总结一下: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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 调用了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 = list_entry(q->stream.next, 
                     struct videobuf_buffer, stream);
    
    
    poll_wait(file, &buf->done, wait);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 看到其中回调用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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 会调用videobuf提供的videobuf_dqbuf
 int videobuf_dqbuf(struct videobuf_queue *q,
		   struct v4l2_buffer *b, int nonblocking)
{
    
    stream_next_buffer(q, &buf, nonblocking);
    
    
    switch (buf->state) {
        ...
        case VIDEOBUF_DONE:    	
    	...
    }
     
    
    videobuf_status(q, b, buf, q->type);
    
    
    list_del(&buf->stream);
    buf->state = VIDEOBUF_IDLE;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">
- 1
 - 2
 - 3
 - 4
 - 5
 - 6
 - 7
 - 8
 - 9
 - 10
 - 11
 - 12
 - 13
 - 14
 - 15
 - 16
 - 17
 - 18
 - 19
 - 20
 
 应用层得到buf的信息后,可以知道buf的编号,然后从之前mmap的缓存里取出队列的缓存读取图像的数据,处理完之后再次将缓存放入缓存队列中(VIDIOC_QBUF)
  
 
         id="article_content" class="article_content clearfix">
        
        
                 id="content_views" class="markdown_views prism-atom-one-light">
                    
                    深入学习Linux摄像头系列
 
深入学习Linux摄像头(一)v4l2应用编程
 
深入学习Linux摄像头(二)v4l2驱动框架
 
深入学习Linux摄像头(三)虚拟摄像头驱动分析
 
深入学习Linux摄像头(四)三星平台fimc驱动详解
 
深入学习Linux摄像头(三)虚拟摄像头驱动分析
 
上一篇文章讲解了V4L2的驱动框架,这一节我们来分析一个驱动程序,Linux内核带有一个虚拟摄像头驱动(vivi.c),这个虚拟摄像头使用V4L2驱动框架编写,只是少了硬件操作,数据来源是虚拟的,这篇文章就来分析它
 
看一个驱动程序首先从入口开始看起
 
module_init(vivi_init);
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 
static int __init vivi_init(void)
{
    vivi_create_instance(i);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 
入口函数调用了vivi_create_instance
 
static int __init vivi_create_instance(int inst)
{
    struct vivi_dev *dev;
    struct video_device *vfd;
    
    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);
    
    vfd = video_device_alloc();
    
	*vfd = vivi_template;
    
    
    video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">
- 1
 - 2
 - 3
 - 4
 - 5
 - 6
 - 7
 - 8
 - 9
 - 10
 - 11
 - 12
 - 13
 - 14
 - 15
 - 16
 - 17
 - 18
 - 19
 - 20
 - 21
 - 22
 - 23
 
 
从上面可以看到,首分配一个vivi_dev,看一看vivi_dev
 
struct vivi_dev {
	struct v4l2_device 	   v4l2_dev;
    struct video_device        *vfd; 
    ...
    
    struct vivi_fmt            *fmt;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 
然后初始化缓存队列,videobuf_queue_vmalloc_init这个函数v4l2的videobuf提供的接口,作用是初始化一个缓存队列struct videobuf_queue,其中就设置了vivi_video_qops
 
struct videobuf_queue {
    
	struct videobuf_buffer     *bufs[VIDEO_MAX_FRAME];
    
	
	struct list_head           stream; 
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 
static struct videobuf_queue_ops vivi_video_qops = {
	.buf_setup      = buffer_setup,
	.buf_prepare    = buffer_prepare,
	.buf_queue      = buffer_queue,
	.buf_release    = buffer_release,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 
这些函数会在操作缓存队列的时候会被调用
 
再接下来就是分配一个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,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 
可以看到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, 
	.mmap           = vivi_mmap,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 
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,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">
- 1
 - 2
 - 3
 - 4
 - 5
 - 6
 - 7
 - 8
 - 9
 - 10
 - 11
 - 12
 - 13
 - 14
 - 15
 - 16
 - 17
 - 18
 - 19
 - 20
 
 
上面已经介绍了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;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 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;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 从代码中可以看到,根据下表从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, 
		.depth    = 16,
	},
	{
		.name     = "RGB565 (BE)",
		.fourcc   = V4L2_PIX_FMT_RGB565X, 
		.depth    = 16,
	},
	{
		.name     = "RGB555 (LE)",
		.fourcc   = V4L2_PIX_FMT_RGB555, 
		.depth    = 16,
	},
	{
		.name     = "RGB555 (BE)",
		.fourcc   = V4L2_PIX_FMT_RGB555X, 
		.depth    = 16,
	},
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">
- 1
 - 2
 - 3
 - 4
 - 5
 - 6
 - 7
 - 8
 - 9
 - 10
 - 11
 - 12
 - 13
 - 14
 - 15
 - 16
 - 17
 - 18
 - 19
 - 20
 - 21
 - 22
 - 23
 - 24
 - 25
 - 26
 - 27
 - 28
 - 29
 - 30
 - 31
 - 32
 
  -  
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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 首先调用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)
{
    
	fmt = get_format(f);   
    if (!fmt)
        return -EINVAL;
    
    
    v4l_bound_align_image();
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  -  
VIDIOC_REQBUFS
 申请缓存
 static int vidioc_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *p)
{
    videobuf_reqbufs(&dev->vb_vidq, p);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 通过调用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);
    ...
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 看一看videobuf_reqbufs做了什么
 int videobuf_reqbufs(struct videobuf_queue *q,
		 struct v4l2_requestbuffers *req)
{
    count = req->count;
    
    
    q->ops->buf_setup(q, &count, &size);
    
    
    __videobuf_mmap_setup(q, count, size, req->memory);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 其中的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,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 我们看一看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)--;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 接下来看一看__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; 
    }
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 可以看到调用了videobuf_alloc分配缓存
 struct videobuf_buffer *videobuf_alloc(struct videobuf_queue *q)
{
    q->int_ops->alloc(q->msize);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 videobuf_alloc又通过回调函数来分配缓存,那么这个回调函数是在什么时候设置的呢?
 在videobuf_queue_vmalloc_init初始化的时候
 videobuf_queue_vmalloc_init()
{
	videobuf_queue_core_init(..., &qops);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 void videobuf_queue_core_init(..., struct videobuf_qtype_ops *int_ops)
{
    q->int_ops   = int_ops;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 可以看到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, 
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 所以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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 为什么分配内存要搞得如此复杂呢?
 因为并非所有视频设备都使用相同形式得缓存区,事实上至少有三种变化
 
  - 分散在物理和(内核)虚拟地址空间中的缓冲区
 - 物理上分散但实际上是连续的缓冲物
 - 物理上连续的缓冲区
 
 videobuf支持这三种形式,提供了三种操作函数集,分别在缓存队列的初始化时设置
 videobuf_queue_vmalloc_init()
videobuf_queue_dma_contig_init()
videobuf_queue_sg_init()
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  -  
mmap
 映射缓存
 static int vivi_mmap(struct file *file, struct vm_area_struct *vma)
{
    videobuf_mmap_mapper(&dev->vb_vidq, vma);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 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); 
    }
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 CALL是一个宏定义,其定义如下
 #define CALL(q, f, arg...)						\
	((q->int_ops->f) ? q->int_ops->f(arg) : 0)
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 又回调到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, 
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 所以最终会调用到__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); 
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  -  
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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 调用videobuf提供的videobuf_dqbuf
 int videobuf_qbuf(struct videobuf_queue *q, struct v4l2_buffer *b)
{
    
    q->ops->buf_prepare(q, buf, field);
    
    
    list_add_tail(&buf->stream, &q->stream);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 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,
};
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 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;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  -  
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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 调用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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 看一看线程函数,此函数负责生产图像数据
 static int vivi_thread(void *data)
{
	for (;;) {
		vivi_sleep(dev); 
	}
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 static void vivi_sleep(struct vivi_dev *dev)
{   
    vivi_thread_tick(dev); 
    
    schedule_timeout_interruptible(timeout); 
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 static void vivi_thread_tick(struct vivi_dev *dev)
{
    
	buf = list_entry(dma_q->active.next,
			 struct vivi_buffer, vb.queue);
    
    
    vivi_fillbuff(dev, buf);
    
    
    wake_up(&buf->vb.done);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 总结一下: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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 调用了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 = list_entry(q->stream.next, 
                     struct videobuf_buffer, stream);
    
    
    poll_wait(file, &buf->done, wait);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 看到其中回调用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);
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
 会调用videobuf提供的videobuf_dqbuf
 int videobuf_dqbuf(struct videobuf_queue *q,
		   struct v4l2_buffer *b, int nonblocking)
{
    
    stream_next_buffer(q, &buf, nonblocking);
    
    
    switch (buf->state) {
        ...
        case VIDEOBUF_DONE:    	
    	...
    }
     
    
    videobuf_status(q, b, buf, q->type);
    
    
    list_del(&buf->stream);
    buf->state = VIDEOBUF_IDLE;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">
- 1
 - 2
 - 3
 - 4
 - 5
 - 6
 - 7
 - 8
 - 9
 - 10
 - 11
 - 12
 - 13
 - 14
 - 15
 - 16
 - 17
 - 18
 - 19
 - 20
 
 应用层得到buf的信息后,可以知道buf的编号,然后从之前mmap的缓存里取出队列的缓存读取图像的数据,处理完之后再次将缓存放入缓存队列中(VIDIOC_QBUF)
  
 
至此,vivi就分析完成了
                 data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146287.html","extend1":"pc","ab":"new"}">>
                
                
        
        
    
                
                
评论记录:
回复评论: