测试虚拟摄像头vivi: 1. 确实ubuntu的内核版本
uname -a Linux book-desktop 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux2. 去www.kernel.org下载同版本的内核 https://www.kernel.org/pub/linux/kernel/ 解压后把drivers/media/video目录取出 修改它的Makefile为:
KERN_DIR = /usr/src/linux-headers-2.6.31-14-generic all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += vivi.o obj-m += videobuf-core.o obj-m += videobuf-vmalloc.o obj-m += v4l2-common.o3、 make 4、
sudo insmod videobuf-core.ko sudo insmod videobuf-vmalloc.ko sudo insmod v4l2-common.ko发现在这里编译不通过!浪费我一晚上的时间; 解决方法:
sudo modprobe vivi sudo rmmod vivi sudo insmod ./vivi.ko先安装Ubantu里面自带的vivi程序,它会把它所依赖的驱动程序安装进来
sudo rmmod vivi.ko
6、ls /dev/video* 7、xawtv -c /dev/videoX 假如你自己写的myvivi.ko编译出来之后,对应的是video1 那么就使用xawtv -c /dev/video1
video_ioctl2: 这是v4l2-ioctl.c提供的默认的标准的ioctl,最终就会调用到 我们在init函数里面所设置的结构体:myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
1、函数里面定时器的问题: add_timer 在哪里add呢?建议在open函数里面,由于我们写驱动程序的规则,这些资源只有在我们真正用到的时候才会去分配;
**先定义该结构体:** static struct timer_list myvivi_timer **在open函数里面:** myvivi_timer.expires = jiffies + 1; add_timer(&myvivi_timer); **在close函数里面:** del_timer(&myvivi_timer); **在Init函数里面:** /* 用定时器产生数据并唤醒进程 */ init_timer(&myvivi_timer); myvivi_timer.function = myvivi_timer_function;然后就是编写function函数,这个function完成了很多工作,显示的主要工作都是在myvivi_fillbuff()这个函数里面完成的!
function函数: 1. 构造数据: 从队列头部取出第1个videobuf, 填充数据 1.1 从本地队列取出第1个videobuf
这个函数就是从队列头部取出第一个video_buf vb = list_entry(myvivi_vb_local_queue.next,struct videobuf_buffer, queue); 然后进行判断 A:该队列头是否为空 B:是否有人在等待这个缓冲区。在done这个位置,估计是判断它有无被设置 如果都不成立 goto out;1.2 填充数据
vbuf = videobuf_to_vmalloc(vb) myvivi_fillbuff(vb); 主要是用了myvivi_fillbuf()函数,这个函数需要我们另自编写 主要是这个状态要进行设置,不然会出错 vb->state = VIDEOBUF_DONE;1.3 把videobuf从本地队列中删除
list_del(&vb->queue);2. 唤醒进程: 唤醒videobuf->done上的进程
wake_up(&vb->done);3. 修改timer的超时时间 : 30fps, 1秒里有30帧数据:每1/30 秒产生一帧数据
mod_timer(&myvivi_timer, jiffies + HZ/30);2、函数队列的问题 ①:先讲本地队列
构造一个本地队列头:static struct list_head myvivi_vb_local_queue; 在初始化Init函数里面进行初始化:INIT_LIST_HEAD(&myvivi_vb_local_queue); 在myvivi_buffer_queue函数里 : 把videobuf放入本地一个队列尾部 ,定时器处理函数就可以从本地队列取出videobuf list_add_tail(&vb->queue, &myvivi_vb_local_queue); 在timer_function函数里:从本地队列取出第1个videobuf 完成填充数据的操作后 A: 把videobuf从本地队列中删除 list_del(&vb->queue); B: 唤醒进程: 唤醒videobuf->done上的进程 wake_up(&vb->done);这样一来我们的videobuf就可以拿到本地进行编写填充数据,完了之后再放虎归山了!
②: 队列操作1:进行定义
static struct videobuf_queue myvivi_vb_vidqueue;队列操作2::初始化 在open函数里面进行初始化: 用videobuf_queue_vmalloc_init函数进行队列
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的头部大小,第四个函数是自旋锁,我们要事先定义它
这个函数的第四个参数是slock:自旋锁
定义:static spinlock_t myvivi_queue_slock; 初始化:在入口函数进行初始化,队列的定义/初始化一个队列(会用到一个spinlock) spin_lock_init(&myvivi_queue_slock);这个函数值得深究! 既然是初始化,肯定就要有一系列相关的初始化操作。第二个参数就是对video的初始化操作
包含四个函数
.buf_setup = myvivi_buffer_setup, /* 计算大小以免浪费 */ .buf_prepare = myvivi_buffer_prepare, .buf_queue = myvivi_buffer_queue, .buf_release = myvivi_buffer_release,四个函数官方解释为:
/* 参考documentations/video4linux/v4l2-fram ework.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. *我自己的理解:
1:buf_setup在videobuf_reqbufs被调用,q->ops->buf_setup(q, &count, &size); 来确定这个conut和size,就是你想分配多少个buf,每个buf多大,通过这个函数重新计算,以免浪费 2:buf_prepare在videobuf_qbuf中retval = q->ops->buf_prepare(q, buf, field); 3:通知驱动程序说应用程序正在请求数据 4:APP不再使用队列时, 用它来释放内存再值得研究的就是vivi_buffer_prepare函数 里面主要做的事情有: 0、设置videobuf 1、 做些准备工作 2、调用videobuf_iolock为类型为V4L2_MEMORY_USERPTR的videobuf分配内存 注意!在这个vivi.c里面我们用不着,不用这个函数进行分配,这个分配内存的代码段我们是设置不工作的
3、设置状态
vb->state = VIDEOBUF_PREPARED;还有.buf_queue
/* APP调用ioctl VIDIOC_QBUF时: * 1、先调用buf_prepare进行一些准备工作 * 2、 把buf放入stream队列 * 3、 调用buf_queue(起通知、记录作用) */ 里面有两条参数: vb->state = VIDEOBUF_QUEUED; 把videobuf放入本地一个队列尾部 。定时器处理函数就可以从本地队列取出videobuf list_add_tail(&vb->queue, &myvivi_vb_local_queue); 队列操作3:注销 在close函数中进行注销: videobuf_stop(&myvivi_vb_vidqueue); videobuf_mmap_free(&myvivi_vb_vidqueue); 队列操作4:Poll机制 videobuf_poll_stream(file, &myvivi_vb_vidqueue, wait); 队列操作5:请求系统分配缓冲区 videobuf_reqbufs(&myvivi_vb_vidqueue, p) 队列操作6:查询所分配的缓冲区 videobuf_querybuf(&myvivi_vb_vidqueue, p) 队列操作7:根据前面分配的缓冲区的信息得出缓冲区大小,进行分配 videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma); 队列操作8:将缓冲区放入队列 videobuf_qbuf(&myvivi_vb_vidqueue, p) 队列操作9:将缓冲区从队列中取出 videobuf_dqbuf(&myvivi_vb_vidqueue, p, file->f_flags & O_NONBLOCK)); 队列操作10:启动 videobuf_streamon(&myvivi_vb_vidqueue) 队列操作11:关闭 videobuf_streamoff(&myvivi_vb_vidqueue);自己编写的vivi:myvivi.c
/* 仿照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> static struct v4l2_format myvivi_format; /* 队列操作1: 定义 */ static struct videobuf_queue myvivi_vb_vidqueue; static spinlock_t myvivi_queue_slock; //Garmen:进行本地队列结构体设置 static struct list_head myvivi_vb_local_queue; static struct timer_list myvivi_timer; #include "fillbuf.c" /* APP调用ioctl VIDIOC_REQBUFS(申请对流)时会导致此函数被调用, * 它重新调整count和size */ static int myvivi_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size) { *size = myvivi_format.fmt.pix.sizeimage; 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分配内存 */ //Garmen:但是在我们这里用不着 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(作用是:起通知、记录作用) Garmen:问题就来了怎么起通知作用? 答:把videobuf_queue 放入我本地的队列myvivi_vb_local_queue里面、队列尾部 定时器处理函数就可以从本地队列取出这个videobuf */ 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不再使用队列时, 用它来释放内存 */ static void myvivi_buffer_release(struct videobuf_queue *vq, struct videobuf_buffer *vb) { videobuf_vmalloc_free(vb); vb->state = VIDEOBUF_NEEDS_INIT; } /* 参考documentations/video4linux/v4l2-fram ework.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. * */ //Garmen1:buf_setup在videobuf_reqbufs被调用,q->ops->buf_setup(q, &count, &size); //来确定这个conut和size,就是你想分配多少个buf,每个buf多大,通过这个函数重新计算,以免浪费 //Garmen2:buf_prepare在videobuf_qbuf中retval = q->ops->buf_prepare(q, buf, field);翻到上面有解释 //Garmen3:通知驱动程序说应用程序正在请求数据 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: 初始化 */ /* 倒数第2个参数是buffer的头部大小 */ //Garmen:第四个函数是自旋锁,我们要事先定义它 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); 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); } 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; 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_PIX_FMT_YUYV if (f->index >= 1) return -EINVAL; strcpy(f->description, "4:2:2, packed, YUYV"); f->pixelformat = V4L2_PIX_FMT_YUYV; return 0; } /* 返回当前所使用的格式 */ //Garmen:get_fmt的时候,我们将v4l2_format拷贝会应用程序f static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { memcpy(f, &myvivi_format, sizeof(struct v4l2_format)); return 0; } /* 测试驱动程序是否支持某种格式 */ //Garmen:try_fmt的时候我们做一些判断,然后调整一下宽度高度等, 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 = f->fmt.pix.field; if (field == V4L2_FIELD_ANY) { field = V4L2_FIELD_INTERLACED; } else if (V4L2_FIELD_INTERLACED != field) { //dprintk(dev, 1, "Field type invalid.\n"); 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); f->fmt.pix.bytesperline = (f->fmt.pix.width * 16) >> 3; f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; return 0; } //Garmen:进行设置 static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { int ret = myvivi_vidioc_try_fmt_vid_cap(file, priv, f); if (ret < 0) return ret; memcpy(&myvivi_format, f, sizeof(struct v4l2_format)); return ret; } static int myvivi_vidioc_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p) { return (videobuf_reqbufs(&myvivi_vb_vidqueue, p)); } static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p) { return (videobuf_querybuf(&myvivi_vb_vidqueue, p)); } static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) { return (videobuf_qbuf(&myvivi_vb_vidqueue, p)); } 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; } 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, .poll = myvivi_poll, }; static struct video_device *myvivi_device; static void myvivi_release(struct video_device *vdev) { } static void (*function)(unsigned long) { /* 1. 构造数据: 从队列头部取出第1个videobuf, 填充数据 */ /* 1.1 从本地队列取出第1个videobuf */ } 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; } //Garmen:这个函数就是从队列头部取出第一个videobuf vb = list_entry(myvivi_vb_local_queue.next, struct videobuf_buffer, queue); /* Garmen:Nobody is waiting on this buffer, return */ if (!waitqueue_active(&vb->done)) goto out; //如果上面情况都通过,即是都没有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; //Garmen:主要是这个状态要进行设置 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 */ myvivi_device->release = myvivi_release; /* 2.2 */ myvivi_device->fops = &myvivi_fops; /* 2.3 */ myvivi_device->ioctl_ops = &myvivi_ioctl_ops; /* 2.4 队列操作 * a. 定义/初始化一个队列(会用到一个spinlock) */ spin_lock_init(&myvivi_queue_slock); /* 用定时器产生数据并唤醒进程 */ init_timer(&myvivi_timer); myvivi_timer.function = myvivi_timer_function; //Garmen:入口函数进行初始化 INIT_LIST_HEAD(&myvivi_vb_local_queue); /* 3. 注册 */ error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1); 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");