概述

前面简单分析了内核中虚拟摄像头驱动 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");

V4L2学习(五)VIVI虚拟摄像头驱动的更多相关文章

  1. V4L2(二)虚拟摄像头驱动vivi深入分析【转】

    转自:http://www.cnblogs.com/tureno/articles/6694463.html 转载于: http://blog.csdn.net/lizuobin2/article/d ...

  2. vivi虚拟摄像头驱动程序

    一.vivi虚拟摄像头驱动 基于V4L2(video for linux 2)摄像头驱动程序,我们减去不需要的ioctl_fops的函数,只增加ioctl函数增加的必要的摄像头流查询等函数: #inc ...

  3. 二十四、V4L2框架主要结构体分析和虚拟摄像头驱动编写

    一.V4L2框架主要结构体分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括两层 ...

  4. 通过虚拟驱动vivi分析摄像头驱动

    Linux摄像头驱动学习之:(二)通过虚拟驱动vivi分析摄像头驱动 一.通过指令 "strace -o xawtv.log xawtv" 得到以下调用信息: // 1~7都是在v ...

  5. 2.2 vivi虚拟视频驱动测试

    学习目标:在linux终端安装xawtv,并测试vivi.ko驱动程序. 一.安装xawtv 1)ubuntu能上网情况下,使用命令:# sudo apt-get install xawtv 2)如果 ...

  6. Linux摄像头驱动学习之:(二)通过虚拟驱动vivi分析摄像头驱动

    一.通过指令 "strace -o xawtv.log xawtv" 得到以下调用信息:// 1~7都是在v4l2_open里调用1. open2. ioctl(4, VIDIOC ...

  7. (一)V4L2学习流程

    title: V4L2学习流程 date: 2019/4/23 18:00:00 toc: true --- V4L2学习流程 参考资料 关键资料,插图让人一下子就理解了 Linux摄像头驱动1--v ...

  8. V4l2初识(七)-----------浅析app获取虚拟摄像头数据的过程

    继续分析数据的获取过程: 1.请求分配的缓冲区: ioctl(4,VIDIOC_REQBUFS) vidioc_reqbufs 2.查询和映射缓冲区   ioctl(4,VIDIOC_QUERYBUF ...

  9. 2.3 摄像头驱动_vivi驱动程序分析

    学习目标:熟悉vivi的调用过程,分析vivi程序源码的ioctl函数: 一.vivi虚拟视频驱动测试方法 当我们接上usb摄像头设备时,系统会自动给我们安装对应的usb设备驱动程序.如果下次直接测试 ...

随机推荐

  1. SpringBoot | 第二十三章:日志管理之整合篇

    前言 在本系列<第四章:日志管理>中,由于工作中日志这块都是走默认配置,也没有深入了解过,因为部署过程中直接使用了linux中的输出重定向功能,如java -jar xx.jar > ...

  2. Mysql 如何设置字段自动获取当前时间,附带添加字段和修改字段的例子

    --添加CreateTime 设置默认时间 CURRENT_TIMESTAMP  ALTER TABLE `table_name`ADD COLUMN  `CreateTime` datetime N ...

  3. Http请求get、post工具类

    在网上找了好久都没有找到post.get请求的工具类,现在整理了一下分享出来.http工具类如下: package com.qlwb.business.util; import java.io.Buf ...

  4. unicode字符和多字节字符的相互转换接口

    作者:朱金灿 来源:http://blog.csdn.net/clever101 发现开源代码的可利用资源真多,从sqlite3的源码中抠出了几个字符转换接口,稍微改造下了发现还挺好用的.下面是实现代 ...

  5. RxJava 1升级到RxJava 2过程中踩过的一些“坑”

    RxJava2介绍 RxJava2 发布已经有一段时间了,是对 RxJava 的一次重大的升级,由于我的一个库cv4j使用了 RxJava2 来尝鲜,但是 RxJava2 跟 RxJava1 是不能同 ...

  6. SqlServer Alwayson 搭建排错记录(二)

    下面记录下建立好alwayson可用性组后,向可用性组内添加数据库出现过的问题及解决方法 一.数据库未处于恢复状态 将数据库联接到可用性组的时候报错: 数据库“XXXX”未处于恢复状态,而此状态是镜像 ...

  7. Google Authenticator加强ssh安全

    一.安装依赖包 软件包可以在这个地址下载:https://pan.baidu.com/s/1r0CmwbtCfNiBqU9rh_TxtA yum -y install pam-devel tar jx ...

  8. 编程之美2015 资格赛 hihocoder 题目2: 回文字符序列

    思路:暴力搜,用BFS的方式,生成每一种可能,再对每一种可能进行判断是否回文,进行统计.严重超时!计算一个25个字符的,大概要20多秒! #include <iostream> #incl ...

  9. java Vamei快速教程14 异常处理

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 程序很难做到完美,不免有各种各样的异常.比如程序本身有bug,比如程序打印时打印机 ...

  10. Codeforces Round #324 (Div. 2) A B C D E

    A,水题不多说. #include<bits/stdc++.h> using namespace std; //#define LOCAL int main() { #ifdef LOCA ...