摄像头驱动笔记3---从零写虚拟驱动(仿照vivi.c)

原创 2016年08月31日 11:14:06

一、关键点分析

(1)摄像头驱动程序结构

1. 分配结构体:video_device:video_device_alloc
2. 设置
   .fops   基本的操作函数
   .ioctl_ops (里面需要设置11项)(与摄像头操作密切相关的函数)
   如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops
3. 注册: video_register_device

(2)videobuf_queue_ops结构体

v4l2驱动需要包含一个videobuf_queue的实例用来管理缓冲队列,同时还要一个链表来维护这个队列,另外还要一个中断安全的spin_lock保护队列的操作。

下一步就是要填充一个回调函数集来处理实际的缓冲区队列,这个函数集用videobuf_queue_ops来描述:

struct videobuf_queue_ops {

int *(buf_setup)(struct videobuf_queue*q, uint *count, uint *size);

int *(buf_prepare)(structvideobuf_queue *q, struct videobuf_buffer *vb,

enum v4l2_field field);

void *(buf_queue)(structvideobuf_queue*q,struct videobuf_buffer *vb);

void *(buf_release)(...);

buf_setupIO处理请求之前被调用。目的是告诉videobuf关于IO的信息count参数提供一个缓冲区个数的参考,驱动必须检查它的合理性,一个经验是大于等于2,小于等于32个。Size参数指定了每一帧数据的大小

buf_prepare每一个缓冲(videobuf_buffer结构描述的)将被传递给该回调函数,来配置缓冲的height,widthfileds。如果field参数被设置为 VIDEOBUF_NEEDS_INIT,那么驱动将把vb传递给videobuf_iolock()这个函数。除此之外,该回调函数通常也将为vb分配内存,最后把vb状态置为VIDEOBUF_PREPARED

buf_queue当一个vb需要被放入IO请求队列时,调用该回调。它将把这个buffer放到可用的buffer链表当中去,然后把状态置为VIDEOBUF_QUEUED

buf_release一个buffer不再使用的时候,调用该回调函数。驱动必须保证 buffer上没有活跃的IO请求,之后就可以将这个buffer传递给合适的 free函数,根据申请的buffer类型调用对应的释放函数:

(3)buf在用户空间和内核空间的关系



二、函数详解

static struct v4l2_format myvivi_format;



/* 队列操作1: 定义 */

static struct videobuf_queue myvivi_vb_vidqueue;

定义自旋锁

static spinlock_t myvivi_queue_slock;

struct list_head {
struct list_head *next, *prev;
};

定义队列结构体myvivi_vb_local_queue


static struct list_head myvivi_vb_local_queue;


定时器结构体定义


static struct timer_list myvivi_timer;


#include "fillbuf.c"


/* ------------------------------------------------------------------
Videobuf operations
   ------------------------------------------------------------------*/

/* APP调用ioctl VIDIOC_REQBUFS时会导致此函数被调用,
 * 它重新调整count和size

 */

告诉videobuf关于IO的信息函数

static int myvivi_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
{

size指定每帧数据的大小
*size = myvivi_format.fmt.pix.sizeimage;

count表示缓冲区个数
if (0 == *count)
*count = 32;


return 0;
}


/* APP调用ioctlVIDIOC_QBUF时导致此函数被调用,
 * 它会填充video_buffer结构体并调用videobuf_iolock来分配内存
 * 当把1个buf放入队列时,会事先调用buffer_prepare做一些准备工作

 */

来配置缓冲的height,widthfileds函数

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分配内存 */
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(起通知、记录作用)

 */

当一个vb需要被放入IO请求队列时,调用该回调

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不再使用队列时, 用它来释放内存 */

一个buffer不再使用的时候,调用该回调函数

static void myvivi_buffer_release(struct videobuf_queue *vq,
  struct videobuf_buffer *vb)
{
videobuf_vmalloc_free(vb);
vb->state = VIDEOBUF_NEEDS_INIT;
}
videobuf_queue_ops结构体,成为为buf的操作函数

static struct videobuf_queue_ops myvivi_video_qops = {
.buf_setup      = myvivi_buffer_setup, /* 计算大小以免浪费 */当应用程序调用request_buf向内核申请空间时,就会调用这个函数确定buf的个数和大小
.buf_prepare    = myvivi_buffer_prepare,
.buf_queue      = myvivi_buffer_queue,
.buf_release    = myvivi_buffer_release,
};


/* ------------------------------------------------------------------
File operations for the device
   ------------------------------------------------------------------*/

open函数

static int myvivi_open(struct file *file)
{
    /* 队列操作2: 初始化 */myvivi_vb_vidqueue是队列myvivi_video_qops是队列操作函数相关的结构体,myvivi_queue_slock是给队列用的锁。V4L2_BUF_TYPE_VIDEO_CAPTURE代表视频捕抓类型
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的头部大小 */


以时下linux kernel来说:1s=jiffies/HZ(即1秒=jiffies/HZ);在asm_i386中,HZ被定义为一个常,且为1000.一般在内核中定义超时是这样用,如:xxx_timer.expires = jiffies+HZ/100;这个定义表示超时时间为10ms,如果超过个时间就处理中断函数或者做你想做的事.当然HZ的分母你可以定为别的数。如HZ/1000等

把定时器的超时时间定为 jiffies + 1,

    myvivi_timer.expires = jiffies + 1;

把定时器结构体timer_list放入内核定时链表

    add_timer(&myvivi_timer);


return 0;
}



close函数
static int myvivi_close(struct file *file)

{

把定时器结构体timer_list取出内核定时链表

    del_timer(&myvivi_timer);
videobuf_stop(&myvivi_vb_vidqueue);
videobuf_mmap_free(&myvivi_vb_vidqueue);
    
return 0;
}

映射函数

mmap系统调用以使能用户空间可以访问data数据

static int myvivi_mmap(struct file *file, struct vm_area_struct *vma)
{
return videobuf_mmap_mapper(&myvivi_vb_vidqueue, vma);
}

poll函数
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)

{

原型声明:char *strcpy(char* dest, const char *src);

strcpy(cap->driver, "myvivi");

strcpy(cap->card, "myvivi");

版本号

cap->version = 0x0001;

表明是一个视频捕捉设备,且用ioctl接口读取数据

cap->capabilities =V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
return 0;
}


/* 列举支持哪种格式 */
static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
structv4l2_fmtdesc *f)

{

如果支持的格式数大于1,返回错误

if (f->index >= 1)
return -EINVAL;
原型声明:char *strcpy(char* dest, const char *src);

strcpy(f->description, "4:2:2, packed, YUYV");

YUYV格式如下: 
Y0U0Y1V0 Y2U1Y3V1.......... 
说明:一个Y代表一个像素,而一个Y和UV组合起来构成一个像素,所以第0个像素Y0和第一个像素Y1都是共用第0个像素的U0和V0。而每个分量Y,U,V都是占用一个字节的存储空间。所以Y0U0Y1V0相当于两个像素,占用了4个字节的存储空间,平均一个像素占用两个字节。 

像素格式是YUYV 

f->pixelformat = V4L2_PIX_FMT_YUYV;
return 0;
}


/* 返回当前所使用的格式 */

static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
structv4l2_format *f)

{

void *memcpy(void *dest, const void *src, size_t n);

    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;

如果像素格式不是YUYV返回错误
    if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
        return -EINVAL;


field = f->fmt.pix.field;
V4L2_FIELD_ANY:Application 可以请求使用这个参数,如果V4L2_FIELD_NONE, V4L2_FIELD_TOP, V4L2_FIELD_BOTTOM V4L2_FIELD_INTERLACE 中任何一个格式都支持.驱动选择使用哪一个格式依赖于硬件能力,以及请求的image尺寸,驱动选择一个然后返回这个格式。struct_buffer的field成员不可以为V4L2_FIELD_ANY.
IV4L2_FIELD_INTERLACEDimages包含top和bottom field, 隔行交替,场序依赖于当前video的标准。NTSC首先传输bottom field, PAL则先传输top 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
     */

v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,

     &f->fmt.pix.height, 32, maxh, 0, 0);

bytesperline是每行字节数,16是每个像素用16位表示,右移3位相当于除以2^3,也就是单位是字节。

f->fmt.pix.bytesperline =

(f->fmt.pix.width * 16) >> 3;

sizeimage是文件的大小,也就是高值*每行字节数

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,
structv4l2_format *f)

{

先试一下设置的格式是否符合要求

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;
}

请求buf
static int myvivi_vidioc_reqbufs(struct file *file, void *priv,
 structv4l2_requestbuffers *p)
{
return (videobuf_reqbufs(&myvivi_vb_vidqueue, p));
}

查询buf

static int myvivi_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)

{
return (videobuf_querybuf(&myvivi_vb_vidqueue, p));
}
把buf放入队列

static int myvivi_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
return (videobuf_qbuf(&myvivi_vb_vidqueue, p));
}

把buf从队列中取出

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,   
};




static const struct v4l2_file_operationsmyvivi_fops = {
.owner= THIS_MODULE,
    .open       = myvivi_open,
    .release    = myvivi_close,
    .mmap       = myvivi_mmap,
    .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
    .poll       = myvivi_poll,
};



定义video_device结构体
static struct video_device *myvivi_device;

myvivi_releaseh函数
static void myvivi_release(struct video_device *vdev)
{
}
定时时间到时的处理函数

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;
    }
    
    vb = list_entry(myvivi_vb_local_queue.next,
             struct videobuf_buffer, queue);
    
    /* Nobody is waiting on this buffer, return */
    if (!waitqueue_active(&vb->done))
        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;
    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 */  release成员指向myvivi_release函数
    myvivi_device->release = myvivi_release;


    /* 2.2 */

fops成员指向v4l2_file_operation结构体(基本操作函数)

    myvivi_device->fops    = &myvivi_fops;


    /* 2.3 */

ioctl_ops成员指向v4l2_ioctl_ops结构体(包含11个必须的ioctl)

    myvivi_device->ioctl_ops = &myvivi_ioctl_ops;


    /* 2.4 队列操作

     *  a. 定义/初始化一个队列(会用到一个spinlock)

     */

在Linux中提供了一些机制用来避免竞争条件,最简单的一个种就是自旋锁,例如:当一个临界区的数据在多个函数之间被调用时,为了保护数据不被破坏,可以采用spinlock来保护临界区的数据,当然还有一个就是信号量也是可以实现临界区数据的保护的。以后在介绍信号量吧。这里还是先说说splinlock吧。

初始化自旋锁lock,其实是将自旋锁指针lock 指向SPIN_LOCK_UNLOCKED宏,该宏的定义

在内核文件linux-2.6.30/include/linux/spinlock_types.h中


宏定义格式:# define spin_lock_init(lock)                             \

   

                         do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)


,它表示自旋锁的状态为未加锁
    spin_lock_init(&myvivi_queue_slock);


   

参数一:struct video_device * vdev:
我们想要注册的Video Device结构体。
参数二:int type:
要注册的device类型。其中包括:
#define VFL_TYPE_GRABBER 0   //图像采集设备,包括摄像头,调谐器等。
#define VFL_TYPE_VBI 1      //从视频消隐的时间段取得信息的设备(注1)
#define VFL_TYPE_RADIO 2      //无线电设备
#define VFL_TYPE_VTX 3      // 视频传播设备
#define VFL_TYPE_MAX 4
参数三: int nr:
device node number 
(0 == /dev/video0, 1 == /dev/video1, ... -1 == first free)

此函数注册一个V4L2  Device。 指定类型,指定device node.(通过参数3)
 /* 3. 注册 */

    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
VFL_TYPE_GRABBER是注册设备的类型

    /* 用定时器产生数据并唤醒进程 */

初始化myvivi_timer结构体

init_timer(&myvivi_timer);

myvivi_timer结构体的funtion指向myvivi_timer_function函数

    myvivi_timer.function  = myvivi_timer_function;
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;

}
队列结构体初始化
    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");

相关文章推荐

从0写USB摄像头驱动程序

从0写USB摄像头驱动程序 1.构造一个usb_driver结构体 .id_table .probe 1.分配video_device结构体 2.设置 3.注册 2.下面具体分析probe函数中的内容...

USB设备驱动开发之扩展(利用USB虚拟总线驱动模拟USB摄像头)

fanxiushu 2016-10-08 转载或引用,请注明原始作者 做这个事情写这篇文章之前,压根没朝模拟USB摄像头这方面去想过。 直到CSDN上一位朋友提出问题,才想到还有这么一个玩意。...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

驱动摄像头的三种方式

备注:本文部分解决方案来自互联网,本文仅作为自己学习笔记,不存在任何商业目的   进来研究把硬件摄像头驱动起来,得到3种方法:VFW、DirectShow、OpenCV。   下面依次对3种方...

终于搞定android驱动USB摄像头了!

2014-01-14 18:15 13642人阅读 评论(72) 收藏 举报  分类:   android(18)  多亏了stackoverflow看到的一篇帖子,其中有几句关键的话,然...

摄像头驱动2_虚拟驱动vivi的测试

1、结合应用程序分析涉及到的调用 (1)如怎么得到里面的数据、怎么控制摄像头的亮度等信息。 (2)现在PC机上做实验 在ubuntu上安装一个应用程序的测试程序xawtv 安装xawtv:sudo a...

摄像头驱动之实现数据传输5_调试_学习笔记

1、实验 (1)编译好驱动程序,卸载原来的驱动,安装新驱动 (2)查看设备节点 (3)桌面环境下调用xawtv应用程序进行测试(出现段错误) (4)查看 之前存储内核信息的文本文件(显...

wince6.0+s3c6410摄像头驱动修改 (分辨率)

文章转自:http://blog.csdn.net/zpf03/article/details/5903484   这段时间开发一个图像识别的项目,基于WinCE6.0+s3c6410 系统,使用...

linux应用项目(二)摄像头(2)从零写一个V4L2虚拟摄像头驱动之详细分析

一、框架搭建 内核在V4l2-dev.c (linux-3.4.2\drivers\media\video) 中提供了V4L2的核心函数。我们再来看一下整体框架: 我们要做的是写个硬件相关驱动,...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:摄像头驱动笔记3---从零写虚拟驱动(仿照vivi.c)
举报原因:
原因补充:

(最多只允许输入30个字)