概述

前面简单分析了内核中虚拟摄像头驱动 vivi 的框架与实现,本文参考 vivi 来写一个虚拟摄像头驱动,查询、设置视频格式相对简单,难点在于 vb2_buf 的处理过程。

数据采集流程分析

在我的程序中,大概的数据采集流程如上图所示,启动视频采集之后,创建了一个内核线程,内核线程每30ms 唤醒一次,每一次唤醒都会尝试用 queue_list 中取出一个 buffer 填充数据之后挂入 done_list ,挂入 done_list 之后就会唤醒应用程序(poll 中休眠),应用程序唤醒之后就会 dqbuf 获取数据,处理完数据再 qbuf 把 buffer 挂入 queue_list 的头部,一直循环。

代码

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/font.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
#include <media/videobuf2-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <media/v4l2-common.h> #define VFL_TYPE_GRABBER 0 #define MAX_WIDTH 1920
#define MAX_HEIGHT 1200
static unsigned int vid_limit = ; static struct video_device *video_dev; // video_device 结构,用来描述一个 video 设备
static struct vb2_queue vivi_queue; // vivi_queue 用来存放缓冲区信息,缓冲区链表等
struct task_struct *kthread; // 内核线程,用来向缓冲区中填充数据
DECLARE_WAIT_QUEUE_HEAD(wait_queue_head); // 等待队列头
struct list_head my_list; // 链表头 // 用来存放应用程序设置的视频格式
static struct mformat {
__u32 width;
__u32 height;
__u32 pixelsize;
__u32 field;
__u32 fourcc;
__u32 depth;
}mformat; static void mvideo_device_release(struct video_device *vdev)
{ } static long mvideo_ioctl(struct file *file, unsigned int cmd, void *arg)
{
int ret = ;
struct v4l2_fh *fh = NULL;
switch (cmd) { case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = (struct v4l2_capability *)arg;
cap->version = LINUX_VERSION_CODE;
ret = video_dev->ioctl_ops->vidioc_querycap(file, NULL, cap);
break;
}
case VIDIOC_ENUM_FMT:
{
struct v4l2_fmtdesc *f = arg;
if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
ret = video_dev->ioctl_ops->vidioc_enum_fmt_vid_cap(file, fh, f);
}else{
printk("V4L2_BUF_TYPE_VIDEO_CAPTURE error\n");
}
break;
}
case VIDIOC_G_FMT:
{
struct v4l2_format *f = (struct v4l2_format *)arg;
if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
ret = video_dev->ioctl_ops->vidioc_g_fmt_vid_cap(file, fh, f);
}else{
printk("V4L2_BUF_TYPE_VIDEO_CAPTURE error\n");
}
break;
}
case VIDIOC_TRY_FMT:
{
struct v4l2_format *f = (struct v4l2_format *)arg;
if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
ret = video_dev->ioctl_ops->vidioc_try_fmt_vid_cap(file, fh, f);
}else{
printk("V4L2_BUF_TYPE_VIDEO_CAPTURE error\n");
}
break;
}
case VIDIOC_S_FMT:
{
struct v4l2_format *f = (struct v4l2_format *)arg;
if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
video_dev->ioctl_ops->vidioc_s_fmt_vid_cap(file, fh, f);
}else{
printk("V4L2_BUF_TYPE_VIDEO_CAPTURE error\n");
}
break;
}
case VIDIOC_REQBUFS:
{
struct v4l2_requestbuffers *p = arg;
ret = video_dev->ioctl_ops->vidioc_reqbufs(file, fh, p);
break;
}
case VIDIOC_QUERYBUF:
{
struct v4l2_buffer *p = arg;
ret = video_dev->ioctl_ops->vidioc_querybuf(file, fh, p);
break;
}
case VIDIOC_QBUF:
{
struct v4l2_buffer *p = arg;
ret = video_dev->ioctl_ops->vidioc_qbuf(file, fh, p);
break;
}
case VIDIOC_DQBUF:
{
struct v4l2_buffer *p = arg;
ret = video_dev->ioctl_ops->vidioc_dqbuf(file, fh, p);
break;
}
case VIDIOC_STREAMON:
{
enum v4l2_buf_type i = *(int *)arg;
ret = video_dev->ioctl_ops->vidioc_streamon(file, fh, i);
break;
}
case VIDIOC_STREAMOFF:
{
enum v4l2_buf_type i = *(int *)arg;
ret = video_dev->ioctl_ops->vidioc_streamoff(file, fh, i);
break;
}
}
return ret;
} static int vivi_mmap(struct file *file, struct vm_area_struct *vma)
{
int ret;
printk("enter mmap\n");
ret = vb2_mmap(&vivi_queue, vma);
if(ret == ){
printk("mmap ok\n");
}else{
printk("mmap error\n");
}
return ret;
} // 查询设备能力
static int mvidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
{
strcpy(cap->driver, "vivi");
strcpy(cap->card, "vivi");
strcpy(cap->bus_info, "mvivi");
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE;
cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING;
printk("mvidioc_querycap \n");
return ;
} // 枚举视频支持的格式
static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f)
{
printk("vidioc_enum_fmt_vid_cap \n");
if (f->index >= )
return -EINVAL; strcpy(f->description, "mvivi");
f->pixelformat = mformat.fourcc;
printk("vidioc_enum_fmt_vid_cap \n");
return ;
} // 修正应用层传入的视频格式
static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
printk("vidioc_try_fmt_vid_cap\n");
if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) {
return -EINVAL;
} f->fmt.pix.field = V4L2_FIELD_INTERLACED;
v4l_bound_align_image(&f->fmt.pix.width, , MAX_WIDTH, ,
&f->fmt.pix.height, , MAX_HEIGHT, , );
f->fmt.pix.bytesperline = (f->fmt.pix.width * mformat.depth) / ;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
if (mformat.fourcc == V4L2_PIX_FMT_YUYV)
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
else
f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
return ;
} // 获取支持的格式
static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
// 将参数写回用户空间
f->fmt.pix.width = mformat.width;
f->fmt.pix.height = mformat.height;
f->fmt.pix.field = mformat.field;
f->fmt.pix.pixelformat = mformat.fourcc;
f->fmt.pix.bytesperline = (f->fmt.pix.width * mformat.depth) / ;
f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
if (mformat.fourcc == V4L2_PIX_FMT_YUYV)
f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
else
f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
printk("vidioc_g_fmt_vid_cap \n");
return ;
} // 设置视频格式
static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f)
{
int ret = vidioc_try_fmt_vid_cap(file, priv, f);
if (ret < )
return ret;
// 存储用户空间传入的参数设置
mformat.fourcc = V4L2_PIX_FMT_YUYV;
mformat.pixelsize = mformat.depth / ;
mformat.width = f->fmt.pix.width;
mformat.height = f->fmt.pix.height;
mformat.field = f->fmt.pix.field;
printk("vidioc_s_fmt_vid_capp \n");
return ;
} // vb2 核心层 vb2_reqbufs 中调用它,确定申请缓冲区的大小
static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
unsigned int *nbuffers, unsigned int *nplanes,
unsigned int sizes[], void *alloc_ctxs[])
{
unsigned long size;
printk("mformat.width %d \n",mformat.width);
printk("mformat.height %d \n",mformat.height);
printk("mformat.pixelsize %d \n",mformat.pixelsize); size = mformat.width * mformat.height * mformat.pixelsize; if ( == *nbuffers)
*nbuffers = ; while (size * *nbuffers > vid_limit * * )
(*nbuffers)--; *nplanes = ; sizes[] = size;
return ;
} static int buffer_init(struct vb2_buffer *vb)
{
return ;
} static int buffer_finish(struct vb2_buffer *vb)
{
return ;
} static int buffer_prepare(struct vb2_buffer *vb)
{
unsigned long size;
size = mformat.width * mformat.height * mformat.pixelsize;
vb2_plane_size(vb, );
//vb2_set_plane_payload(&buf->vb, 0, size);
return ;
} static void buffer_queue(struct vb2_buffer *vb)
{ } // 内核线程中填充数据,效果是一个逐渐放大的圆形,视频大小为 640 * 480
static void vivi_fillbuff(struct vb2_buffer *vb)
{
void *vbuf = NULL;
unsigned char (*p)[mformat.width][mformat.pixelsize];
unsigned int i,j;
vbuf = vb2_plane_vaddr(vb, );
static unsigned int t = ;
p = vbuf; for(j = ; j < mformat.height; j++)
for(i = ; i < mformat.width; i++){
if((j - )*(j - ) + (i - )*(i - ) < (t * t)){
*(*(*(p+j)+i)+) = (unsigned char)0xff;
*(*(*(p+j)+i)+) = (unsigned char)0xff;
}else{
*(*(*(p+j)+i)+) = (unsigned char);
*(*(*(p+j)+i)+) = (unsigned char);
}
}
t++;
printk("%d\n",t);
if( t >= mformat.height/) t = ;
} // 内核线程每一次唤醒调用它
static void vivi_thread_tick(void)
{
struct vb2_buffer *buf = NULL;
struct list_head *list;
struct vb2_buffer *task;
unsigned long flags;
if (list_empty(&vivi_queue.queued_list)) {
//printk("No active queue to serve\n");
return;
}
// 注意我们这里取出之后就删除了,剩的重复工作,但是在 dqbuf 时,vb2_dqbuf 还会删除一次,我做的处理是在dqbuf之前将buf随便挂入一个链表
buf = list_entry(vivi_queue.queued_list.next, struct vb2_buffer, queued_entry);
list_del(&buf->queued_entry); /* 填充数据 */
vivi_fillbuff(buf);
printk("filled buffer %p\n", buf->planes[].mem_priv); // 它干两个工作,把buffer 挂入done_list 另一个唤醒应用层序,让它dqbuf
vb2_buffer_done(buf, VB2_BUF_STATE_DONE);
} #define WAKE_NUMERATOR 30
#define WAKE_DENOMINATOR 1001
#define BUFFER_TIMEOUT msecs_to_jiffies(500) /* 0.5 seconds */
#define frames_to_ms(frames) \
((frames * WAKE_NUMERATOR * ) / WAKE_DENOMINATOR) static void vivi_sleep(void)
{
int timeout;
DECLARE_WAITQUEUE(wait, current); add_wait_queue(&wait_queue_head, &wait);
if (kthread_should_stop())
goto stop_task; /* Calculate time to wake up */
timeout = msecs_to_jiffies(frames_to_ms()); vivi_thread_tick(); schedule_timeout_interruptible(timeout); stop_task:
remove_wait_queue(&wait_queue_head, &wait);
try_to_freeze();
} static int vivi_thread(void *data)
{
set_freezable(); for (;;) {
vivi_sleep(); if (kthread_should_stop())
break;
}
printk("thread: exit\n");
return ;
} static int vivi_start_generating(void)
{
kthread = kthread_run(vivi_thread, video_dev, video_dev->name); if (IS_ERR(kthread)) {
printk("kthread_run error\n");
return PTR_ERR(kthread);
} /* Wakes thread */
wake_up_interruptible(&wait_queue_head); return ;
} static int start_streaming(struct vb2_queue *vq, unsigned int count)
{
vivi_start_generating();
return ;
}
static int stop_streaming(struct vb2_queue *vq)
{
if (kthread) {
kthread_stop(kthread);
kthread = NULL;
}
/* while (!list_empty(&vivi_queue.queued_list)) {
struct vb2_buffer *buf;
buf = list_entry(vivi_queue.queued_list.next, struct vb2_buffer, queued_entry);
list_del(&buf->queued_entry);
vb2_buffer_done(buf, VB2_BUF_STATE_ERROR);
}
*/
return ;
}
static struct vb2_ops vivi_video_qops = {
.queue_setup = queue_setup,
.buf_init = buffer_init,
.buf_finish = buffer_finish,
.buf_prepare = buffer_prepare,
.buf_queue = buffer_queue,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
}; static int mvivi_open(struct file *filp)
{
vivi_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vivi_queue.io_modes = VB2_MMAP;
vivi_queue.drv_priv = video_dev;
//vivi_queue.buf_struct_size = sizeof(struct vivi_buffer);
vivi_queue.ops = &vivi_video_qops;
vivi_queue.mem_ops = &vb2_vmalloc_memops;
vivi_queue.name = "vb2";
vivi_queue.buf_struct_size = sizeof(struct vb2_buffer);
INIT_LIST_HEAD(&vivi_queue.queued_list);
INIT_LIST_HEAD(&vivi_queue.done_list);
spin_lock_init(&vivi_queue.done_lock);
init_waitqueue_head(&vivi_queue.done_wq);
mformat.fourcc = V4L2_PIX_FMT_YUYV;
mformat.depth = ;
INIT_LIST_HEAD(&my_list);
return ;
} static int vidioc_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p)
{
printk("vidioc_reqbufs \n");
printk("count %d\n",p->count);
printk("memory %d\n",p->memory);
return vb2_reqbufs(&vivi_queue, p);
} static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
printk("vidioc_querybuf \n");
return vb2_querybuf(&vivi_queue, p);
} static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
printk("vidioc_qbuf buffer \n");
return vb2_qbuf(&vivi_queue, p);
} static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
printk("vidioc_dqbuf buffer \n");
struct vb2_buffer *vb;
vb = list_first_entry(&vivi_queue.done_list, struct vb2_buffer, done_entry);
list_add_tail(&vb->queued_entry, &my_list);
return vb2_dqbuf(&vivi_queue, p, file->f_flags & O_NONBLOCK);
} static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
printk("vidioc_streamon \n");
return vb2_streamon(&vivi_queue, i);
} static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
printk("vidioc_streamoff \n");
return vb2_streamoff(&vivi_queue, i);
} static struct v4l2_ioctl_ops mvivi_ioctl_ops = {
.vidioc_querycap = mvidioc_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_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
}; static unsigned int mvivi_poll(struct file *file, struct poll_table_struct *wait)
{
struct vb2_buffer *vb = NULL;
int res = ;
printk("enter the poll \n"); poll_wait(file, &vivi_queue.done_wq, wait); if (!list_empty(&vivi_queue.done_list))
vb = list_first_entry(&vivi_queue.done_list, struct vb2_buffer, done_entry); if (vb && (vb->state == VB2_BUF_STATE_DONE || vb->state == VB2_BUF_STATE_ERROR)) {
return (V4L2_TYPE_IS_OUTPUT(vivi_queue.type)) ?
res | POLLOUT | POLLWRNORM :
res | POLLIN | POLLRDNORM;
}
return ;
} long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, mvideo_ioctl);
} static struct v4l2_file_operations mvivi_fops = {
.owner = THIS_MODULE,
.open = mvivi_open,
.unlocked_ioctl = video_ioctl2, //.release = mvivi_close,
.poll = mvivi_poll,
.mmap = vivi_mmap,
}; static struct video_device vivi_template = {
.name = "mvivi",
.fops = &mvivi_fops,
.ioctl_ops = &mvivi_ioctl_ops,
.release = mvideo_device_release,
}; static int mvivi_init(void)
{
int ret;
video_dev = video_device_alloc();
*video_dev = vivi_template;
ret = video_register_device(video_dev, VFL_TYPE_GRABBER, -);
if(ret != ){
printk(" video_register_device error\n");
}else{
printk(" video_register_device ok\n");
}
return ret;
} static void mvivi_exit(void)
{
video_unregister_device(video_dev);
} module_init(mvivi_init);
module_exit(mvivi_exit);
MODULE_LICENSE("GPL");

最新文章

  1. JavaScript中的this
  2. showSetPwdDialog--自定义对话框
  3. 海量日志分析方案--logstash+kibnana+kafka
  4. python random
  5. UITextFiled,UITextView长度限制
  6. SVN里常见的图标及其含义
  7. cocos2d 遍历CCAarray
  8. 斯坦福机器学习视频笔记 Week1 Linear Regression and Gradient Descent
  9. HDU1005 Number Sequence (奇技淫巧模拟)
  10. ioctl函数
  11. windows安装pycrypto报错
  12. VS 2013Ultimate 开发过程中遇到的问题——listbox的隐藏问题,combobox.textchanged的中文问题
  13. JavaScript是如何工作的:编写自己的Web开发框架 + React及其虚拟DOM原理
  14. iis 站点中文乱码 解决方案
  15. NoSuchBeanDefinitionException:No qualifying bean of type
  16. luogu3385 负环 (spfa)
  17. JS循环中使用bind函数的参数传递问题
  18. CentOS 6.5环境下使用HAProxy+apache实现web服务的动静分离
  19. 峰Redis学习(5)Redis 数据结构(Set的操作)
  20. 谈一谈我所了解的https

热门文章

  1. 妙用Object
  2. Java hibernate 遇到的问题:could not read a hi value
  3. Java 在使用@Select遇到的问题:拼接字符串将数组拼为了字符串
  4. 显示单位px、dip以及sp的区别
  5. Java并发(五):并发,迭代器和容器
  6. 从零开始的全栈工程师——js篇2.11(原型)
  7. Mybatis介绍(一)
  8. 创见VR-上海,会后总结
  9. 浏览器兼容圆角Border-radius的问题
  10. 工作中碰到的css问题解决方法