V4L2学习5--VIVI虚拟摄像头驱动

概述

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

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 = 0;
    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 == 0){
        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 0;
}

// 枚举视频支持的格式
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 >= 1)
        return -EINVAL;

    strcpy(f->description, "mvivi");
    f->pixelformat = mformat.fourcc;
    printk("vidioc_enum_fmt_vid_cap  \n");
    return 0;
}

// 修正应用层传入的视频格式
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, 48, MAX_WIDTH, 2,
                  &f->fmt.pix.height, 32, MAX_HEIGHT, 0, 0);
    f->fmt.pix.bytesperline = (f->fmt.pix.width * mformat.depth) / 8;
    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 0;
}

// 获取支持的格式
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) / 8;
    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 0;
}

// 设置视频格式
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 < 0)
        return ret;
        // 存储用户空间传入的参数设置
    mformat.fourcc      =  V4L2_PIX_FMT_YUYV;
    mformat.pixelsize   =  mformat.depth / 8;
    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 0;
}

// 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 (0 == *nbuffers)
        *nbuffers = 32;

    while (size * *nbuffers > vid_limit * 1024 * 1024)
        (*nbuffers)--;

    *nplanes = 1;

    sizes[0] = size;
    return 0;
}

static int buffer_init(struct vb2_buffer *vb)
{
    return 0;
}

static int buffer_finish(struct vb2_buffer *vb)
{
    return 0;
}

static int buffer_prepare(struct vb2_buffer *vb)
{
    unsigned long size;
    size = mformat.width * mformat.height * mformat.pixelsize;
    vb2_plane_size(vb, 0);
    //vb2_set_plane_payload(&buf->vb, 0, size);
    return 0;
}

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, 0);
    static unsigned int t = 0;
    p = vbuf;

    for(j = 0; j < mformat.height; j++)
        for(i = 0; i < mformat.width; i++){
            if((j - 240)*(j - 240) + (i - 320)*(i - 320) < (t * t)){
                *(*(*(p+j)+i)+0) = (unsigned char)0xff;
                *(*(*(p+j)+i)+1) = (unsigned char)0xff;
            }else{
                *(*(*(p+j)+i)+0) = (unsigned char)0;
                *(*(*(p+j)+i)+1) = (unsigned char)0;
            }
        }
    t++;
    printk("%d\n",t);
    if( t >= mformat.height/2) t = 0;
}

// 内核线程每一次唤醒调用它
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[0].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 * 1000) / 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(1));

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

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

static int start_streaming(struct vb2_queue *vq, unsigned int count)
{
    vivi_start_generating();
    return 0;
}
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 0;
}
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  = 16;
    INIT_LIST_HEAD(&my_list);
    return 0;
}

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

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, -1);
    if(ret != 0){
        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");

原文地址:https://www.cnblogs.com/Lxk0825/p/10400509.html

时间: 2024-12-10 09:26:13

V4L2学习5--VIVI虚拟摄像头驱动的相关文章

二十四、V4L2框架分析和虚拟摄像头驱动编写

一.V4L2框架分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括四个部分: 1. 字符设备驱动程序核心:V4L2本身就是一个字符设备,上层连接用户空间 2. V4L2驱动核心:构造通用的视频设备驱动框架,为上层操作提供统一接口 3. 平台V4L2驱动:在V4L2框架下,根据平台自身特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev 4.

(一)V4L2学习流程

title: V4L2学习流程 date: 2019/4/23 18:00:00 toc: true --- V4L2学习流程 参考资料 关键资料,插图让人一下子就理解了 Linux摄像头驱动1--vivid Linux摄像头驱动2--UVC 重写uvc比较完整注释版本 从更大的角度去看V4L2框架,不局限在摄像头 V4L2框架概述 排版不错的笔记 USB摄像头驱动框架分析 从零写USB摄像头驱动之分析描述符 从零写USB摄像头驱动之实现数据传输1_框架 从零写USB摄像头驱动之实现数据传输2_

Linux摄像头驱动学习之:(一)V4L2_框架分析

这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux version 2虚拟视频驱动vivi.c分析:1.分配video_device2.设置3.注册:video_register_device vivi_init    vivi_create_instance        v4l2_device_register   // 不是主要, 只是用于初始

(四) 虚拟摄像头vivi体验

目录 虚拟摄像头vivi体验 源码下载 修改Makefile 安装xawtv 测试体验 title: 虚拟摄像头vivi体验 date: 2019/4/23 19:20:00 toc: true --- 虚拟摄像头vivi体验 vivid是虚拟的摄像头驱动.可以理解等同于UVC,只是说不需要USB总线驱动控制等 源码下载 # 查看内核版本 $ uname -a Linux 100ask 4.13.0-36-generic #40~16.04.1-Ubuntu SMP Fri Feb 16 23:

摄像头驱动的使能配置、V4L2编程接口的设计应用

摄像头采集子系统 一.摄像头驱动的使能配置 摄像头软件驱动构架 摄像头采集系统由上图所示,硬件(摄像头) -> 驱动(Linux内核配置中,选择支持V4L2的驱动选项) -> V4L2接口设计 -> 图像采集. 硬件:选择USB摄像头,内置芯片ZC30系列,Linux包含的万能驱动兼容: 驱动:配置Linux内核,选择万能摄像头驱动中ZC30系列驱动文件.支持V4L2接口,编译下载内核: 内核下载至开发板并挂载后,摄像头开发环境以搭建完成,以下即为应用采集. V4L2接口:编写基于V4L

V4L2学习4--VIVI分析

vivi 相对于后面要分析的 usb 摄像头驱动程序,它没有真正的硬件相关层的操作,也就是说抛开了复杂的 usb 层的相关知识,便于理解 V4L2 驱动框架,侧重于驱动和应用的交互. 前面我们提到,V4L2 的核心是 v4l2-dev.c 它向上提供统一的文件操作接口 v4l2_fops ,向下提供 video_device 注册接口 register_video_device ,作为一个具体的驱动,需要做的工作就是分配.设置.注册一个 video_device.框架很简单,复杂的是视频设备相关

Linux USB摄像头驱动【转】

本文转载自:http://www.itdadao.com/articles/c15a509940p0.html 在 cortex-a8 中,可接入摄像头的接口通常可以分为两种, CAMERA 接口和 USB 接口的摄像头.这一章主要是介绍 USB 摄像头的设备驱动程序.在我们印象中,驱动程序都是一个萝卜一个坑,拿到一个硬件就需要去安装它相对应的驱动程序.有时候稍有不对还会导致电脑崩溃,是不是让人很郁闷?这一章我们讲 USB 摄像头设备驱动,那么是不是支持所有的 USB 摄像头驱动呢?带着这个疑问

基于Linux 3.0.8 Samsung FIMC(S5PV210) 的摄像头驱动框架解读

作者:咕唧咕唧liukun321 来自:http://blog.csdn.net/liukun321 FIMC这个名字应该是从S5P100开始出现的,在s5pv210里面的定义是摄像头接口,但是它同样具有图像数据颜色空间转换的作用.而exynos4412对它的定义看起来更清晰些,摄像头接口被定义为FIMC-LITE .颜色空间转换的硬件结构被定义为FIMC-IS.不多说了,我们先来看看Linux3.0.8 三星的BSP包中与fimc驱动相关的文件. 上面的源码文件组成了整个fimc的驱动框架.通

STM32F103USB摄像头驱动

现在想想自己也很无语.一个月前一时冲动在淘宝买了块原子开发板(非广告),然后就开始奇妙的嵌入式生涯... 代码是从原子的触控鼠标实验改过来的,煎蛋实现了一个USB摄像头,可以将一帧320*240的JPG图片发送到HOST,所以并不包含摄像头驱动代码. 代码很简单,作为学习UVC或者参考也是不错的. PC上看到的图像 MDK项目文件: http://download.csdn.net/detail/funte/8560745 自己收集的USB学习资料:http://download.csdn.ne