基于linux2.6.31.14内核自己写虚拟摄像头驱动myvivi.c

come from : https://blog.csdn.net/zhangjianjun521/article/details/74999923

 
/* 仿照linux2.6.31.14内核中的vivi.c	
 */
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/pci.h>
#include <linux/random.h>
#include <linux/version.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/highmem.h>
#include <linux/freezer.h>
#include <media/videobuf-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
 
/* 当前摄像头所使用的格式,在vivi.c中该结构体是在函数的参数中,我们为了简单
 * 使用全局变量。但是在内核的开发中,万不得已不使用全局变量,都要使用参数的形式传递
 * 为了程序的聚拢型。
 */
static struct v4l2_format myvivi_format;
 
/* 队列操作1: 定义 */
static struct videobuf_queue myvivi_vb_vidqueue;
static spinlock_t myvivi_queue_slock;
 
static struct list_head myvivi_vb_local_queue;
 
 
static struct timer_list myvivi_timer;
 
#include "fillbuf.c"
 
/* 参考documentations/video4linux/v4l2-framework.txt:
 * 参考drivers\media\video\videobuf-core.c 
 ops->buf_setup   - calculates the size of the video buffers and avoid they
            to waste more than some maximum limit of RAM;
 ops->buf_prepare - fills the video buffer structs and calls
            videobuf_iolock() to alloc and prepare mmaped memory;
 ops->buf_queue   - advices the driver that another buffer were
            requested (by read() or by QBUF);
 ops->buf_release - frees any buffer that were allocated.
 
 *
 */
 
 
/* ------------------------------------------------------------------
	Videobuf operations
   ------------------------------------------------------------------*/
/* APP调用ioctl VIDIOC_REQBUFS时会导致此函数被调用,(追驱动的ioctl的VIDIOC_REQBUFS命令得到结果)
 * 它重新调整count和size(有多少个buffer,每个buffer的大小是多少)
 */
static int myvivi_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
{
 
	*size = myvivi_format.fmt.pix.sizeimage;
	/* 如果传入的count为0的话就赋值为32*/
	if (0 == *count)
		*count = 32;
 
	return 0;
}
 
/* APP调用ioctlVIDIOC_QBUF时导致此函数被调用,
 * 它会填充video_buffer结构体并调用videobuf_iolock来分配内存
 * 
 */
static int myvivi_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
						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分配内存 
	 * 如果类型为V4L2_MEMORY_MMAP的话,就不会分配内存,只是会判断类型返回而已
	 * 因为我们的videobuf_buffer中的memory是V4L2_MEMORY_MMAP类型的,是不需要分配内存的,
	 * 所以可以不使用videobuf_iolock函数。
	 */
	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放入videobuf_queue中的stream队列
 * 3. 调用buf_queue(起通知、记录作用)
 */
static void myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
	vb->state = VIDEOBUF_QUEUED;
 
    /* 把videobuf放入本地一个队列尾部
     * 定时器处理函数就可以从本地队列取出videobuf
     */
    list_add_tail(&vb->queue, &myvivi_vb_local_queue);
}
 
/* APP不再使用队列时, 用它来释放内存,在videobuf_queue_cancel中被调用 */
static void myvivi_buffer_release(struct videobuf_queue *vq,
			   struct videobuf_buffer *vb)
{
	/*  也可以使用free_buffer(vq, buf);这个函数是vivi.c中buffer_release中的函数
	 *	以下两个函数是从free_buffer中主要函数
  	 */
	videobuf_vmalloc_free(vb);
	/* 设置videobuf_buffer的状态为初始状态 */
	vb->state = VIDEOBUF_NEEDS_INIT;
}
 
/* 通过追videobuf_queue_vmalloc_init函数可知,以下四个函数是必不可少的 
 * 可以通过在videobuf-core.c中搜索buf_setup来查看是在哪里被调用,另外三个函数同理 
 */
static struct videobuf_queue_ops myvivi_video_qops = {
	.buf_setup      = myvivi_buffer_setup, /* 计算大小以免浪费 */
	.buf_prepare    = myvivi_buffer_prepare,
	.buf_queue      = myvivi_buffer_queue,
	.buf_release    = myvivi_buffer_release,
};
 
/* ------------------------------------------------------------------
	File operations for the device
   ------------------------------------------------------------------*/
 
static int myvivi_open(struct file *file)
{
    /* 队列操作2: 初始化 
	 * 参数V4L2_BUF_TYPE_VIDEO_CAPTURE是追函数得到的结果,并不是从vivi.c中抄过来的
	 * 倒数第2个参数是是队列中的buffer头部的大小,并不包含buffer中存放视频数据的大小
	 * 如果倒数第二个参数是其他的结构体把videobuf_buffer封装起来了,此时这个结构体
	 * 的第一个成员必输是videobuf_buffer,参考vivi.c
	 * videobuf_queue_vmalloc_init函数是用于初始化vmalloc类型的缓存
	 */
	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的头部大小 */
 
    myvivi_timer.expires = jiffies + 1;
	/* 只有真正用起来了才使用资源 */
    add_timer(&myvivi_timer);
 
	return 0;
}
 
 
static int myvivi_close(struct file *file)
{
    del_timer(&myvivi_timer);
	/* 停止和销毁队列 */
	videobuf_stop(&myvivi_vb_vidqueue);
	videobuf_mmap_free(&myvivi_vb_vidqueue);
    
	return 0;
}
 
static int myvivi_mmap(struct file *file, struct vm_area_struct *vma)
{
	return videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);
}
 
static unsigned int myvivi_poll(struct file *file, struct poll_table_struct *wait)
{
	return videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait);
}
 
/* 从vivi.c中拷贝过来然后进行修改
 * 该函数的目的是表征这是一个摄像头设备
 */
static int myvivi_vidioc_querycap(struct file *file, void  *priv,
					struct v4l2_capability *cap)
{
	strcpy(cap->driver, "myvivi");
	strcpy(cap->card, "myvivi");
	cap->version = 0x0001;	//随便写
	/* V4L2_CAP_VIDEO_CAPTURE表示这是一个摄像头设备,V4L2_CAP_STREAMING表明可以通过
	 * qbuf或者dbuf来获得数据,V4L2_CAP_READWRITE表示可以通过读写函数来获取数据,因此
	 * 这个摄像头驱动支持两种获取数据的格式
	 */
	cap->capabilities =	V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
	return 0;
}
 
/* 列举支持哪种格式 */
static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
					struct v4l2_fmtdesc *f)
{
	/* 我们只支持一种格式,应用程序查询时会使用v4l2_fmtdesc结构体,里面有index成员
	 * 从0开始使用for循环来一次查询所支持的格式
	 */
	if (f->index >= 1)
		return -EINVAL;
 
	strcpy(f->description, "4:2:2, packed, YUYV");
	/* 只支持yuyv */
	f->pixelformat = V4L2_PIX_FMT_YUYV;
	return 0;
}
 
/* 返回当前所使用的格式 */
static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
					struct v4l2_format *f)
{
    memcpy(f, &myvivi_format, sizeof(myvivi_format));
	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;
	/* 判断格式是否是我们驱动所支持的类型 */
    if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
        return -EINVAL;
	/* field照抄vivi.c写法 */
	field = f->fmt.pix.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,照抄vivi.c
     */
	v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
			      &f->fmt.pix.height, 32, maxh, 0, 0);
	/* 像素深度为16,即每个像素有16位表示,>>3表示除以8,为什么不用/8,因为移位指令比除法指令快 */
	f->fmt.pix.bytesperline =
		(f->fmt.pix.width * 16) >> 3;
	f->fmt.pix.sizeimage =
		f->fmt.pix.height * f->fmt.pix.bytesperline;
 
	return 0;
}
 
static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
					struct v4l2_format *f)
{	/* 该函数返回0表示支持这种格式,如果返回值小于0,表示不支持这种格式
	 * 该函数的作用只是对所设格式进行一下判断 
 	 */
	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;
}
 
/* 请求buffer */
static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
			  struct v4l2_requestbuffers *p)
{
	return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
}
/* 查询buffer */
static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
	return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
}
/* 从队列中取出buffer */
static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
	return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
}
/* 从队列中取出buffer
 * v4l2框架中有videobuf_buffer和v4l2_buffer两个关于buffer的结构体,那么他们直接有什么联系呢?
 * videobuf_buffer是在v4l2框架中内核中表示摄像头buffer的结构体,而v4l2_buffer结构体是应用层
 * 使用关于查询,申请,入队,出队的结构体,由此可知,这两个结构体使用的场合不同,当应用层通过
 * ioctl来申请或者查询等操作buffer时,要传入v4l2_buffer结构体,这个结构体中有index成员,通过
 * 应用层传入的index成员就可以来选择内核中的哪个videobuf_buffer结构体,
 * 从而就使两种结构体关联起来了
 */
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,   
};
 
/* 这个操作方法集中的所有的函数都是由v4l2框架中字符设备接口的file_operations中回调用的。
 * 摄像头整体的框架就是接口层的file_operations操作方法会回调video_device中提供的
 * v4l2_file_operations操作方法集中的函数
 */
static const struct v4l2_file_operations myvivi_fops = {
	.owner		= THIS_MODULE,
    .open       = myvivi_open,
    .release    = myvivi_close,
   	/* 在v4l2框架中字符设备接口的file_operations中的mmap是直接调用video_device中
	 * 操作方法集中的mmap,因此我们必须填写这个函数,这个函数主要作用是分配存储摄像头
	 * 数据的缓冲区,前面申请的videobuf_buffer只是缓冲区的头部,并没有存放摄像头数据的缓冲区
   	 */
    .mmap       = myvivi_mmap,
    /* video_ioctl2是V4l2-ioctl.c提供的标准ioctl,由摄像头驱动的框架可知,当应用层调用
	 * ioctl时会调用vl42中cdev中的ioctl,这个ioctl会调用video_device中fops的ioctl,就是
	 * video_ioctl2,在video_ioctl2中会调用video_device中的myvivi_ioctl_ops
     */
    .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
    .poll       = myvivi_poll,
};
 
 
static struct video_device *myvivi_device;
 
static void myvivi_release(struct video_device *vdev)
{
}
 
 
/* 在定时器处理函数中来填充videobuf_buffer
 * 对所有类型的驱动,填充缓冲区的流程都是如下方式:
 * 1.获取下一个buffer,并且确保有人正在等待这个buffer
 * 2.得到一个内存指针,然后将视频数据放到那个地方
 * 3.标记buffer完成,并且唤醒等待的进程
 */
static void myvivi_timer_function(unsigned long data)
{
    struct videobuf_buffer *vb;
	void *vbuf;
	struct timeval ts;
    
    /* 1. 构造数据: 从队列头部取出第1个videobuf, 填充数据
     */
 
    /* 1.1 从本地队列取出第1个videobuf 
	 * 这个判断函数是非常有必要的,因为在open函数中就已经开启定时器了,在执行定时器
	 * 处理函数时,videobuf_buffer中的数据还没有数据呢,此时myvivi_vb_local_queue一定是
	 * 空的,此时就不应该在下面对该队列中的buffer操作
	 */
    if (list_empty(&myvivi_vb_local_queue)) {
        goto out;
    }
    /* 从本地队列头部取出第一个buffer 
	 * 在myvivi_buffer_queue函数中把videobuf放入本地一个队列尾部,应用层获得的摄像头缓冲
	 * videobuf_buffer其实是从myvivi_vb_local_queue队列中获取的,而vivi.c中不是
	 * myvivi_vb_local_queue,而是在vivi_dmaqueue中的active队列中,因此可知,在linux2.6.31.14
	 * 的版本中的v4l2框架中,关于缓冲区其实是由两个队列来完成的,一个是由videobuf_queue来维护的
	 * videobuf_buffer,这个队列主要的作用是供应用程序来申请,分配,查询的数据缓存。
	 * 而另一个队列就是当应用层获取摄像头缓存时,从这个队列中取缓冲,并不是从videobuf_queue
	 * 队列中取出摄像头缓冲。
	 */
    vb = list_entry(myvivi_vb_local_queue.next,
             struct videobuf_buffer, queue);
    
    /* Nobody is waiting on this buffer, return */
	/* 该函数判断之前是否有wait_event_interruptible将videobuf_buffer中的done挂入等待队列,
	 * 如果没有的话就说明没有应用程序正在等在videobuf_buffer,即应用程序想要获得videobuf_buffer
	 * 的摄像头数据,
	 * 那么在哪里使用wait_event_interruptible函数呢?其实是在poll函数中,如果videobuf_buffer
	 * 没有准备好数据,就会将videobuf_buffer中的done放入核心层进行休眠。
	 * wait_event_interruptible函数是如何实现的呢?该函数的第一个参数是一个wait_queue_head_t
	 * 等待队列头,然后该函数会添加一个DEFINE_WAIT(__wait);将__wait添加到wait_queue_head_t中,
	 * 因此判断wait_queue_head_t中的struct list_head task_list链接因子是否为空即可。
	 * 如果有用户调用wait_event_interruptible函数后,wait_queue_head_t中的
	 * struct list_head task_list链接因子不会为空。
	 *
	 * 总之就是如果myvivi_vb_local_queue队列当中的buffer没有一个进程在其上等待的话,
	 * 是不能被移除或者填充的。
	 * 
	 * 这个函数是非常有必要的,如果应用层没有使用select函数在等待获取videobuf_buffer
	 * 时,就不应该对videobuf_buffer填充数据,然后删除videobuf_buffer和唤醒等待的进程
	 * 
	 */
    if (!waitqueue_active(&vb->done))
        goto out;
 
 
    /* 1.2 填充数据 */
	/* 从videobuf_buffer中得到真正存储摄像头数据的缓存,应用程序就可以从这个缓存中取出
	 * 摄像头数据并显示,对于vmalloc类型的缓存使用videobuf_to_vmalloc函数来获得用于存放
	 * 摄像头数据的内存指针,如果是其他类型的缓存则需要使用其他函数来获得内存指针
	 */
    vbuf = videobuf_to_vmalloc(vb);
    //memset(vbuf, 0xff, vb->size);
    myvivi_fillbuff(vb);
    
    vb->field_count++;
    do_gettimeofday(&ts);
    vb->ts = ts;
	/* 状态一定要改为VIDEOBUF_DONE,因为在poll函数中会判断videobuf_buffer的状态是否为VIDEOBUF_DONE */
    vb->state = VIDEOBUF_DONE;
 
    /* 1.3 把videobuf从本地队列中删除 */
    list_del(&vb->queue);
 
    /* 2. 唤醒进程: 唤醒videobuf->done上的进程 
	 * 唤醒因为poll函数而休眠的进程
	 */
    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函数必须设置,否则在video_register_device注册时,在该函数中就会
	 * 判断时候有这个函数,如果没有的话就会返回。
	 * 在V4l2-dev.c中的 if (WARN_ON(!vdev->release))返回
     */
    myvivi_device->release = myvivi_release;
 
    /* 2.2 */
    myvivi_device->fops    = &myvivi_fops;
 
    /* 2.3 myvivi_fops中的ioctl最终会调用myvivi_device->ioctl_ops中对应的函数 */
    myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
 
    /* 2.4 队列操作
     *  a. 定义/初始化一个队列(会用到一个spinlock)
     */
    spin_lock_init(&myvivi_queue_slock);
 
    /* 3. 注册 ,以下信息是通过追踪源代码得到的结论
     * 参数-1表示分配第一个可用的号
     * which device node number (0 == /dev/video0, 1 == /dev/video1, ...
 	 *            -1 == first free) 
 	 */
    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
 
    /* 用定时器产生数据并唤醒进程
	 * 用定时器来模仿vivi.c中来创建内核线程来构造数据并唤醒进程
     */
	init_timer(&myvivi_timer);
    myvivi_timer.function  = myvivi_timer_function;
	/* 初始化一个队列头,这个队列是用来管理可以向应用层提供摄像头数据缓存的buffer,
	 * 也就是说应用层从驱动获得摄像头缓存就是从这个队列中获取的
	 */
    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");
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值