sheldon_blogs

Linux摄像头驱动学习之:(三)从零写虚拟驱动(仿照vivi.c)

本篇仿照vivi.c 写虚拟视频驱动,代码(myvivi.c+fillbuf.c+Makefile)如下:

//==========================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;

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时会导致此函数被调用,
 * 它重新调整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分配内存 */
    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(起通知、记录作用)
 */
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;
}

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: 初始化 */
    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);
}

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)
{
    if (f->index >= 1)
        return -EINVAL;

    strcpy(f->description, "4:2:2, packed, 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 = 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
     */
    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;
}

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, NULL, f);
    if (ret < 0)
        return ret;

    memcpy(&myvivi_format, f, sizeof(myvivi_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, /* V4L2 ioctl handler */
    .poll       = myvivi_poll,
};


static struct video_device *myvivi_device;

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 */
    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);

    /* 3. 注册 */
    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);

    /* 用定时器产生数据并唤醒进程 */
    init_timer(&myvivi_timer);
    myvivi_timer.function  = myvivi_timer_function;

    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");

 

//==========================fillbuf.c=======================================

/* Bars and Colors should match positions */

enum colors {
    WHITE,
    AMBAR,
    CYAN,
    GREEN,
    MAGENTA,
    RED,
    BLUE,
    BLACK,
};

    /* R   G   B */
#define COLOR_WHITE    {204, 204, 204}
#define COLOR_AMBAR    {208, 208,   0}
#define COLOR_CIAN    {  0, 206, 206}
#define    COLOR_GREEN    {  0, 239,   0}
#define COLOR_MAGENTA    {239,   0, 239}
#define COLOR_RED    {205,   0,   0}
#define COLOR_BLUE    {  0,   0, 255}
#define COLOR_BLACK    {  0,   0,   0}

struct bar_std {
    u8 bar[8][3];
};

/* Maximum number of bars are 10 - otherwise, the input print code
   should be modified */
static struct bar_std bars[] = {
    {    /* Standard ITU-R color bar sequence */
        {
            COLOR_WHITE,
            COLOR_AMBAR,
            COLOR_CIAN,
            COLOR_GREEN,
            COLOR_MAGENTA,
            COLOR_RED,
            COLOR_BLUE,
            COLOR_BLACK,
        }
    }, {
        {
            COLOR_WHITE,
            COLOR_AMBAR,
            COLOR_BLACK,
            COLOR_WHITE,
            COLOR_AMBAR,
            COLOR_BLACK,
            COLOR_WHITE,
            COLOR_AMBAR,
        }
    }, {
        {
            COLOR_WHITE,
            COLOR_CIAN,
            COLOR_BLACK,
            COLOR_WHITE,
            COLOR_CIAN,
            COLOR_BLACK,
            COLOR_WHITE,
            COLOR_CIAN,
        }
    }, {
        {
            COLOR_WHITE,
            COLOR_GREEN,
            COLOR_BLACK,
            COLOR_WHITE,
            COLOR_GREEN,
            COLOR_BLACK,
            COLOR_WHITE,
            COLOR_GREEN,
        }
    },
};

#define NUM_INPUTS ARRAY_SIZE(bars)

#define TO_Y(r, g, b) \
    (((16829 * r + 33039 * g + 6416 * b  + 32768) >> 16) + 16)
/* RGB to  V(Cr) Color transform */
#define TO_V(r, g, b) \
    (((28784 * r - 24103 * g - 4681 * b  + 32768) >> 16) + 128)
/* RGB to  U(Cb) Color transform */
#define TO_U(r, g, b) \
    (((-9714 * r - 19070 * g + 28784 * b + 32768) >> 16) + 128)



static unsigned char myvivi_cur_bars[8][3];



/* precalculate color bar values to speed up rendering */
static void myvivi_precalculate_bars(int input)
{
    unsigned char r, g, b;
    int k, is_yuv;


    for (k = 0; k < 8; k++) {
        r = bars[input].bar[k][0];
        g = bars[input].bar[k][1];
        b = bars[input].bar[k][2];
        is_yuv = 0;

        switch (myvivi_format.fmt.pix.pixelformat) {
        case V4L2_PIX_FMT_YUYV:
        case V4L2_PIX_FMT_UYVY:
            is_yuv = 1;
            break;
        case V4L2_PIX_FMT_RGB565:
        case V4L2_PIX_FMT_RGB565X:
            r >>= 3;
            g >>= 2;
            b >>= 3;
            break;
        case V4L2_PIX_FMT_RGB555:
        case V4L2_PIX_FMT_RGB555X:
            r >>= 3;
            g >>= 3;
            b >>= 3;
            break;
        }

        if (is_yuv) {
            myvivi_cur_bars[k][0] = TO_Y(r, g, b);    /* Luma */
            myvivi_cur_bars[k][1] = TO_U(r, g, b);    /* Cb */
            myvivi_cur_bars[k][2] = TO_V(r, g, b);    /* Cr */
        } else {
            myvivi_cur_bars[k][0] = r;
            myvivi_cur_bars[k][1] = g;
            myvivi_cur_bars[k][2] = b;
        }
    }

}


static void myvivi_gen_twopix(unsigned char *buf, int colorpos)
{
    unsigned char r_y, g_u, b_v;
    unsigned char *p;
    int color;

    r_y = myvivi_cur_bars[colorpos][0]; /* R or precalculated Y */
    g_u = myvivi_cur_bars[colorpos][1]; /* G or precalculated U */
    b_v = myvivi_cur_bars[colorpos][2]; /* B or precalculated V */

    for (color = 0; color < 4; color++) {
        p = buf + color;

        switch (myvivi_format.fmt.pix.pixelformat) {
        case V4L2_PIX_FMT_YUYV:
            switch (color) {
            case 0:
            case 2:
                *p = r_y;
                break;
            case 1:
                *p = g_u;
                break;
            case 3:
                *p = b_v;
                break;
            }
            break;
        case V4L2_PIX_FMT_UYVY:
            switch (color) {
            case 1:
            case 3:
                *p = r_y;
                break;
            case 0:
                *p = g_u;
                break;
            case 2:
                *p = b_v;
                break;
            }
            break;
        case V4L2_PIX_FMT_RGB565:
            switch (color) {
            case 0:
            case 2:
                *p = (g_u << 5) | b_v;
                break;
            case 1:
            case 3:
                *p = (r_y << 3) | (g_u >> 3);
                break;
            }
            break;
        case V4L2_PIX_FMT_RGB565X:
            switch (color) {
            case 0:
            case 2:
                *p = (r_y << 3) | (g_u >> 3);
                break;
            case 1:
            case 3:
                *p = (g_u << 5) | b_v;
                break;
            }
            break;
        case V4L2_PIX_FMT_RGB555:
            switch (color) {
            case 0:
            case 2:
                *p = (g_u << 5) | b_v;
                break;
            case 1:
            case 3:
                *p = (r_y << 2) | (g_u >> 3);
                break;
            }
            break;
        case V4L2_PIX_FMT_RGB555X:
            switch (color) {
            case 0:
            case 2:
                *p = (r_y << 2) | (g_u >> 3);
                break;
            case 1:
            case 3:
                *p = (g_u << 5) | b_v;
                break;
            }
            break;
        }
    }
}

static void myvivi_gen_line(char *basep, int inipos, int wmax,
        int hmax, int line, int count)
{
    int  w;
    int pos = inipos;

    /* We will just duplicate the second pixel at the packet */
    wmax /= 2;

    /* Generate a standard color bar pattern */
    for (w = 0; w < wmax; w++) {
        int colorpos = ((w + count) * 8/(wmax + 1)) % 8;

        myvivi_gen_twopix(basep + pos, colorpos);
        pos += 4; /* only 16 bpp supported for now */
    }
    return;
}

static void myvivi_fillbuff(struct videobuf_buffer *vb)
{
    int h , pos = 0;
    int hmax  = vb->height;
    int wmax  = vb->width;
    char *tmpbuf;
    void *vbuf = videobuf_to_vmalloc(vb);
    static int mv_count = 0;

    if (!vbuf)
        return;

    tmpbuf = kmalloc(wmax * 2, GFP_ATOMIC);
    if (!tmpbuf)
        return;

    for (h = 0; h < hmax; h++) {
        myvivi_gen_line(tmpbuf, 0, wmax, hmax, h, mv_count);
        memcpy(vbuf + pos, tmpbuf, wmax * 2);
        pos += wmax*2;
    }

    mv_count++;

    kfree(tmpbuf);
}

 

==========================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    += myvivi.o

 

posted on 2015-11-21 14:48  sheldon_blogs  阅读(3267)  评论(1编辑  收藏  举报

导航