Linux摄像头驱动第一篇之虚拟摄像头驱动vivi.c

虚拟视频驱动vivi.c分析(linux-3.4.2版本) cank 虚拟视频驱动vivi.c分析(linux-3.4.2版本): vivi_init      vivi_create_instance(i); { struct vivi_dev *dev; struct video_device *vfd; struct v4l2_ctrl_handler *hdl;         // 不是主要, 只是用于初始化一些东 阅读详情

本文学习自韦东山老师的摄像头驱动模块


目录

一 摄像头驱动程序学习切入点以及V4L2模型概览
二 简析虚拟视频驱动 VIVI.C
  • 2.1 初始化、设置、注册过程
  • 2.2 简析vivi.c的open,read,write,ioctl过程
三 虚拟摄像头驱动的启动过程简析
  • 3.1 查看虚拟摄像头应用程序启动虚拟摄像头过程中都做了什么
  • 3.2 分析数据的获取过程
四 编写摄像头驱动程序
  • 4.1 第一步 :仿照vivi.c,搭建摄像头驱动程序框架
  • 4.2 第二步 : 创建v4l2_ioctl_ops 并实现成员函数 vidioc_querycap()
  • 4.3 第三步:填充 v4l2_ioctl_ops 结构体中 列举、获得、测试、设置 摄像头的数据的格式的相关函数
  • 4.4 第四步 : 增加 缓冲区操作: 申请/查询/放入队列/取出队列 相关函数
  • 4.5 第五步 : 增加 启动/停止
  • 4.6 第六步 : 定时器定时填充数据并唤醒进程

一 摄像头驱动程序学习切入点以及V4L2模型概览

V4L2 : Video for Linux 2,是linux为视频设备提供的一套标准接口。我学习V4L2 的切入点,首先学习虚拟摄像头驱动程序vivi.c。学习vivi.c虚拟摄像头驱动的切入点如下:

打开虚拟机,将虚拟机置于最前台,插上USB摄像头,dmesg 看kernel输出LOG。有关键信息 :Found UVC 1.00 … 搜索该关键字,经过搜索,定位在 uvc_driver.c ,粗略的看一下代码,总结出如下模糊框架:

V4L2 大致框架:

硬件相关层
uvc_driver.c 

uvc_probe
	v4l2_device_register
	uvc_register_chains
		uvc_register_terms
			uvc_register_video
				uvc_register_video_device 
					//   从硬件相关层到核心层;
					video_register_device ==  __video_register_device //drivers\media\v4l2-core\v4l2-dev.c

核心层
v4l2-dev.c 
__video_register_device 
	//以下三步是字符设备相关步骤,v4l2_fops是特殊的字符设备fops
	vdev->cdev = cdev_alloc();
	vdev->cdev->ops = &v4l2_fops;
	cdev_add


应用程序:
	open read write

二 简析虚拟视频驱动 VIVI.C

2.1 初始化、设置、注册过程

主要分为三步

1.分配video_device
2.设置
3.注册:video_register_device


vivi_init
    vivi_create_instance
        v4l2_device_register   // 不是主要, 只是用于初始化一些东西,比如自旋锁、引用计数
        video_device_alloc
        // 设置
          1. vfd:
            .fops           = &vivi_fops,
            .ioctl_ops 	= &vivi_ioctl_ops,
            .release	= video_device_release,
          2.
            vfd->v4l2_dev = &dev->v4l2_dev;
          3. 设置"ctrl属性"(用于APP的ioctl):
            	v4l2_ctrl_handler_init(hdl, 11);
            	dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
            	dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
            	dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            			V4L2_CID_CONTRAST, 0, 255, 1, 16);                        
        video_register_device(video_device, type:VFL_TYPE_GRABBER, nr)
            __video_register_device
                vdev->cdev = cdev_alloc();
                vdev->cdev->ops = &v4l2_fops;
                cdev_add
                
                video_device[vdev->minor] = vdev;

        		if (vdev->ctrl_handler == NULL)
        			vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
2.2 简析vivi.c的open,read,write,ioctl过程

1 open

app:     open("/dev/video0",....)
------------------------------------------------------------------
drv:     v4l2_fops.v4l2_open
            vdev = video_devdata(filp);  // 根据次设备号从数组中得到video_device
            ret = vdev->fops->open(filp);
                        vivi_ioctl_ops.open
                            v4l2_fh_open

2 read

app:    read ....
---------------------------------------------------
drv:    v4l2_fops.v4l2_read
            struct video_device *vdev = video_devdata(filp);
            ret = vdev->fops->read(filp, buf, sz, off);

3 ioctl

app:   ioctl
----------------------------------------------------
drv:   v4l2_fops.unlocked_ioctl
            v4l2_ioctl
                struct video_device *vdev = video_devdata(filp);
                ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
                            video_ioctl2
                                video_usercopy(file, cmd, arg, __video_do_ioctl);
                                    __video_do_ioctl
                                        struct video_device *vfd = video_devdata(file);
                                        根据APP传入的cmd来获得、设置"某些属性"

三 虚拟摄像头驱动的启动过程简析

3.1 查看虚拟摄像头应用程序启动虚拟摄像头过程中都做了什么

前期环境搭建步骤后面补充,这边直接记录陈述结果,测试虚拟摄像头,发现虚拟摄像头应用程序调用虚拟摄像头驱动的ioctl过程如下:

// 1~7都是在v4l2_open里调用
1. open
2. ioctl(4, VIDIOC_QUERYCAP

// 3~7 都是在get_device_capabilities里调用
3. for()
        ioctl(4, VIDIOC_ENUMINPUT   // 列举输入源,VIDIOC_ENUMINPUT/VIDIOC_G_INPUT/VIDIOC_S_INPUT不是必需的
4. for()
        ioctl(4, VIDIOC_ENUMSTD  // 列举标准(制式), 不是必需的
5. for()        
        ioctl(4, VIDIOC_ENUM_FMT // 列举格式

6. ioctl(4, VIDIOC_G_PARM
7. for()
        ioctl(4, VIDIOC_QUERYCTRL    // 查询属性(比如说亮度值最小值、最大值、默认值)

// 8~10都是通过v4l2_read_attr来调用的        
8. ioctl(4, VIDIOC_G_STD            // 获得当前使用的标准(制式), 不是必需的
9. ioctl(4, VIDIOC_G_INPUT 
10. ioctl(4, VIDIOC_G_CTRL           // 获得当前属性, 比如亮度是多少

11. ioctl(4, VIDIOC_TRY_FMT          // 试试能否支持某种格式
12. ioctl(4, VIDIOC_S_FMT            // 设置摄像头使用某种格式


// 13~16在v4l2_start_streaming
13. ioctl(4, VIDIOC_REQBUFS          // 请求系统分配缓冲区
14. for()
        ioctl(4, VIDIOC_QUERYBUF         // 查询所分配的缓冲区
        mmap        
15. for ()
        ioctl(4, VIDIOC_QBUF             // 把缓冲区放入队列        
16. ioctl(4, VIDIOC_STREAMON             // 启动摄像头


// 17里都是通过v4l2_write_attr来调用的
17. for ()
        ioctl(4, VIDIOC_S_CTRL           // 设置属性
    ioctl(4, VIDIOC_S_INPUT              // 设置输入源
    ioctl(4, VIDIOC_S_STD                // 设置标准(制式), 不是必需的

// v4l2_nextframe > v4l2_waiton    
18. v4l2_queue_all
    v4l2_waiton    
        for ()
        {
            select(5, [4], NULL, NULL, {5, 0})      = 1 (in [4], left {4, 985979})
            ioctl(4, VIDIOC_DQBUF                // de-queue, 把缓冲区从队列中取出
            // 处理, 之以已经通过mmap获得了缓冲区的地址, 就可以直接访问数据        
            ioctl(4, VIDIOC_QBUF                 // 把缓冲区放入队列
        }

由此可知,虚拟摄像头应用程序经过一系列的 ioctl() 最终将虚拟摄像头驱动启动成功。那么应用程序调用了那么多vivi.c中的 ioctl(),是不是每一个ioctl()都是必要的呢,经过删减测试。发现如下11个 ioctl() 是必要的,其余的ioctl()可删减。

摄像头驱动程序必需的11个ioctl:

// 表示它是一个摄像头设备
.vidioc_querycap      = vidioc_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,	
3.2 分析数据的获取过程

1 请求分配缓冲区:

ioctl(4, VIDIOC_REQBUFS    // 请求系统分配缓冲区
  videobuf_reqbufs(队列, v4l2_requestbuffers) // 队列在open函数用videobuf_queue_vmalloc_init()初始化

2 查询映射缓冲区:

ioctl(4, VIDIOC_QUERYBUF  // 查询所分配的缓冲区
	videobuf_querybuf()    // 获得缓冲区的数据格式、大小、每一行长度、高度         
	 
	mmap(参数里有"大小")   // 在这里才分配缓存
	v4l2_mmap
            vivi_mmap
                videobuf_mmap_mapper
                    videobuf-vmalloc.c里的__videobuf_mmap_mapper
                            mem->vmalloc = vmalloc_user(pages);   // 在这里才给缓冲区分配空间

3 把缓冲区放入队列:

ioctl(4, VIDIOC_QBUF             // 把缓冲区放入队列    
	videobuf_qbuf()
		q->ops->buf_prepare(q, buf, field);  // 调用驱动程序提供的函数做些预处理
        list_add_tail(&buf->stream, &q->stream);  // 把缓冲区放入队列的尾部
        q->ops->buf_queue(q, buf);           // 调用驱动程序提供的"入队列函数"

4 启动摄像头

ioctl(4, VIDIOC_STREAMON
    videobuf_streamon
        q->streaming = 1;

5 用select查询是否有数据

      // 驱动程序里必定有: 产生数据、唤醒进程
      v4l2_poll
            vdev->fops->poll
                vivi_poll   
                    videobuf_poll_stream
                        // 从队列的头部获得缓冲区
            			buf = list_entry(q->stream.next, struct videobuf_buffer, stream);
                        
                        // 如果没有数据则休眠                			
            			poll_wait(file, &buf->done, wait);

谁来产生数据、谁来唤醒它?
内核线程vivi_thread每30MS执行一次,它调用
vivi_thread_tick
    vivi_fillbuff(fh, buf);  // 构造数据 
    wake_up(&buf->vb.done);  // 唤醒进程

6 有数据后从队列里取出缓冲区

// 有那么多缓冲区,APP如何知道哪一个缓冲区有数据?调用VIDIOC_DQBUF
ioctl(4, VIDIOC_DQBUF 
    vidioc_dqbuf   
        // 在队列里获得有数据的缓冲区
        retval = stream_next_buffer(q, &buf, nonblocking);
        
        // 把它从队列中删掉
        list_del(&buf->stream);
        
        // 把这个缓冲区的状态返回给APP
        videobuf_status(q, b, buf, q->type);

7

应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据 就去读对应的地址(该地址来自前面的mmap)

四 编写摄像头驱动程序

4.1 第一步 :仿照vivi.c,搭建摄像头驱动程序框架

仿照vivi.c,搭建摄像头驱动程序框架

工作:

1 分配一个video_device结构体 myvivi_device
2 创建 v4l2_file_operations 并且绑定到 myvivi_device 
3 注册 myvivi_device

/* 仿照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 const struct v4l2_file_operations myvivi_fops = {
	.owner		= THIS_MODULE,
};


static struct video_device *myvivi_device;

static void myvivi_release(struct video_device *vdev)
{
}


static int myvivi_init(void)
{
    int error;
    
    /* 1. 分配一个video_device结构体 */
    myvivi_device = video_device_alloc();

    /* 2. 设置 */
    myvivi_device->release = myvivi_release;
    myvivi_device->fops    = &myvivi_fops;

    /* 3. 注册 */                                 //注册摄像头类型    which device number
	//注意:教程版本内核 该注册函数内部还会判断 时候已经编写release函数,和fops 结构。如果没有的话 注册失败
    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
    
    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");
4.2 第二步 : 创建v4l2_ioctl_ops 并实现成员函数 vidioc_querycap()

仿照vivi.c ,创建v4l2_ioctl_ops 并实现成员函数 vidioc_querycap(),实现该函数可以代表当前为一个摄像头设备。
工作:

 1 构建 v4l2_ioctl_ops结构体
 	1.1 实现成员函数  vidioc_querycap ioctl(),表示是一个摄像头设备 
 
 2 设置 myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
 
 3 设置 v4l2_file_operations结构体的 .ioctl = video_ioctl2 ;
   说明:此项是参考 vivi.c,当应用程序调用v4l2_file_operations 的 .ioctl时候,就会调用video_ioctl2,
   		而video_ioctl2最终调用到 v4l2_ioctl_ops结构体中的成员函数

#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 int myvivi_vidioc_querycap(struct file *file, void  *priv,
					struct v4l2_capability *cap)
{
	strcpy(cap->driver, "myvivi");
	strcpy(cap->card, "myvivi");
	cap->version = 0x0001;

	// 读取方式 目前使用 V4L2_CAP_STREAMING 读取方式
	cap->capabilities =	V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
	return 0;
}

static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
        // 表示它是一个摄像头设备
        .vidioc_querycap      = myvivi_vidioc_querycap,
#if 0    
        /* 用于列举、获得、测试、设置摄像头的数据的格式 */
        .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,   
#endif
};

static const struct v4l2_file_operations myvivi_fops = {
	.owner		= THIS_MODULE,
    .ioctl      = video_ioctl2, /* V4L2 ioctl handler V4L2 提供的表针的ioctl 最终就会调用到 myvivi_ioctl_ops*/
};


static struct video_device *myvivi_device;

static void myvivi_release(struct video_device *vdev)
{
}


static int myvivi_init(void)
{
    int error;
    
    /* 1. 分配一个video_device结构体 */
    myvivi_device = video_device_alloc();

    /* 2. 设置 */
    myvivi_device->release = myvivi_release;
    myvivi_device->fops    = &myvivi_fops;
    myvivi_device->ioctl_ops = &myvivi_ioctl_ops;

    /* 3. 注册 */
    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
    
    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");
4.3 第三步:填充 v4l2_ioctl_ops 结构体中 列举、获得、测试、设置 摄像头的数据的格式的相关函数

仿照vivi.c

工作:继续填充 v4l2_ioctl_ops 结构体 : 填充 用于 列举、获得、测试、设置 摄像头的数据的格式的相关函数

1 实现 vidioc_enum_fmt_vid_cap 
	 说明 : 例举摄像头格式,当应用程序想查询我们的摄像头支持哪种格式的时候,发送 ioctl(4, VIDIOC_ENUMINPUT 指令,最终
			调用到 vidioc_enum_fmt_vid_cap()
2 实现 vidioc_g_fmt_vid_cap
	 说明 : 返回当前摄像头所使用的格式
	 
	 2.1 定义 struct v4l2_format
	 	 说明:用于存储当前格式,当应用程序想要获取当前摄像头格式的时候,直接返回该结构体

3 实现 vidioc_try_fmt_vid_cap
	 说明 : 测试摄像头驱动程序是否支持某种数据格式,即判断应用程序传递进来的摄像头格式:
		 	struct v4l2_format->fmt.pix.pixelformat,是否等于 我们驱动程序唯一支持的格式:v4l2_fmtdesc->pixelformat = V4L2_PIX_FMT_YUYV
	 
4 实现 vidioc_s_fmt_vid_cap
	 说明 : 设置摄像头数据格式
	
	4.1 先测试是否支持应用程序传递进来的摄像头格式 struct v4l2_format,调用 vidioc_try_fmt_vid_cap函数,判断应用程
		序传递进来的摄像头格式 f->fmt.pix.pixelformat是否等于我们驱动程序唯一支持的格式 v4l2_fmtdesc->pixelformat = V4L2_PIX_FMT_YUYV

	4.2 将应用程序传递进来的格式 拷贝到 我们自定义的 struct v4l2_format

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

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

/* 列举支持哪种格式  固定只支持一种格式 V4L2_PIX_FMT_YUYV */
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");//先设置只支持一种格式,这种格式是 YUYV,描述为 “4:2:2, packed, YUYV”
	f->pixelformat = V4L2_PIX_FMT_YUYV;//先设置只支持一种格式,这种格式是 YUYV
	return 0;
}

/* 返回当前所使用的格式 */
static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
					struct v4l2_format *f)
{
	// 将结构体 myvivi_format 数据拷贝回 应用程序
    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;

	//格式,目前我们的驱动程序只支持  V4L2_PIX_FMT_YUYV 格式
    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;
}

//设置摄像头数据格式 由于此时没有硬件支持,所以将应用传递下来的数据格式保存在 myvivi_format
static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
					struct v4l2_format *f)
{
	//测试驱动程序是否支持某种(f)格式 
	int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);
	if (ret < 0)
		return ret;
	//将 f 拷贝到 myvivi_format
    memcpy(&myvivi_format, f, sizeof(myvivi_format));
    
	return ret;
}

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,

#if 0    
        
        /* 缓冲区操作: 申请/查询/放入队列/取出队列 */
        .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,   
#endif
};

static const struct v4l2_file_operations myvivi_fops = {
	.owner		= THIS_MODULE,
    .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
};


static struct video_device *myvivi_device;

static void myvivi_release(struct video_device *vdev)
{
}


static int myvivi_init(void)
{
    int error;
    
    /* 1. 分配一个video_device结构体 */
    myvivi_device = video_device_alloc();

    /* 2. 设置 */
    myvivi_device->release = myvivi_release;
    myvivi_device->fops    = &myvivi_fops;
    myvivi_device->ioctl_ops = &myvivi_ioctl_ops;

    /* 3. 注册 */
    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);
    
    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");
4.4 第四步 : 增加 缓冲区操作: 申请/查询/放入队列/取出队列 相关函数

工作:继续填充 v4l2_ioctl_ops 结构体 : 填充 用于 列举、获得、测试、设置 摄像头的数据的格式的相关函数

1 
	1.1 定义 videobuf_queue 队列
	
	1.2 定义、初始化 spinlock_t 自旋锁 :
		1.2.1 定义  spinlock_t 自旋锁:static spinlock_t myvivi_queue_slock;
		1.2.2 初始化  spinlock_t 自旋锁: pin_lock_init(&myvivi_queue_slock);

2 在 v4l2_file_operations  .open中 初始化 videobuf_queue 队列 

	 说明 :在 v4l2_file_operations  .open中 初始化 videobuf_queue 队列 : videobuf_queue_vmalloc_init()

	 videobuf_queue_vmalloc_init()参数分析如下
	 
	2.1 参数1:videobuf_queue 队列
	2.2 参数2: videobuf_queue_ops 结构体, 初始化 videobuf_queue_ops结构体,定义初始化其中的操作函数
		
		说明:填充videobuf_queue_ops结构体
		
		2.2.1 填充 buf_setup()
			说明 : APP调用ioctl VIDIOC_REQBUFS(请求系统分配缓冲区) 时,会调用驱动的结构体v4l2_ioctl_ops
				   的 .vidioc_reqbufs函数,在vidioc_reqbufs函数中,他会调用该函数buf_setup(),重新调整count
				   和size

		2.2.2 填充 buf_prepare()
			说明 : 在结构体v4l2_ioctl_ops的 .vidioc_qbuf函数(入队列操作)中调用,设置状态 
					struct videobuf_buffer->state = VIDEOBUF_PREPARED;
		
		2.2.3 填充 buf_queue()
			说明 : 设置状态 struct videobuf_buffer->state = VIDEOBUF_QUEUED; APP调用 ioctl VIDIOC_QBUF时,
					2.2.3.1 首先调用 buf_prepare 进行一些准备工作,
					2.2.3.2 把buf放入队列
					2.2.3.3 调用   buf_queue (起通知作用),设置

		2.2.4 填充 buf_release()
			说明:  APP不再使用队列时, 用它来释放内存 

	2.3 参数3:v4l2_buf_type ,选择 V4L2_BUF_TYPE_VIDEO_CAPTURE 类型
	
	2.4 参数4:v4l2_field, V4L2_FILED_INTERLACED : 略 参考vivi.c代码参数

	2.5 参数5:videobuf_buffer,buffer的头部大小
	
	2.6 参数6:私有数据 NULL

3 添加 v4l2_file_operations .release函数,销毁队列
	3.1 停止队列操作 videobuf_stop(&myvivi_vb_vidqueue);
	
 	3.2 删除队列  videobuf_mmap_free(&myvivi_vb_vidqueue);
		
4 实现缓冲区操作函数:        
		/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
        .vidioc_reqbufs       = myvivi_vidioc_reqbufs,
        .vidioc_querybuf      = myvivi_vidioc_querybuf,
        .vidioc_qbuf          = myvivi_vidioc_qbuf,
        .vidioc_dqbuf         = myvivi_vidioc_dqbuf,

/* 继续填充 v4l2_ioctl_ops 结构体 :增加 缓冲区操作: 申请/查询/放入队列/取出队列 相关函数 
 * 1. vidioc_reqbufs  : 申请缓冲区操作
 * 2. vidioc_querybuf  : 查询缓冲区
 * 3. vidioc_qbuf  : 放入队列操作
 * 4. vidioc_dqbuf  : 取出队列操作
 */

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



/* 参考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)
{
    /* 1. 做些准备工作 */

#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调用ioctlVIDIOC_QBUF时:
 * 1. 先调用buf_prepare进行一些准备工作
 * 2. 把buf放入队列
 * 3. 调用buf_queue(起通知作用)
 */
static void myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
	vb->state = VIDEOBUF_QUEUED;
	//list_add_tail(&buf->vb.queue, &vidq->active);
}

/* 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: 初始化 */
	/*  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的头部大小 */

	return 0;
}


static int myvivi_close(struct file *file)
{
	//队列销毁
	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 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 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,

#if 0    
        
        // 启动/停止
        .vidioc_streamon      = myvivi_vidioc_streamon,
        .vidioc_streamoff     = myvivi_vidioc_streamoff,   
#endif
};


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


static struct video_device *myvivi_device;

static void myvivi_release(struct video_device *vdev)
{
}


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);
    
    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");
4.5 第五步 : 增加 启动/停止
/* 仿照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;



/* 参考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)
{
    /* 1. 做些准备工作 */

#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调用ioctlVIDIOC_QBUF时:
 * 1. 先调用buf_prepare进行一些准备工作
 * 2. 把buf放入队列
 * 3. 调用buf_queue(起通知作用)
 */
static void myvivi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
{
	vb->state = VIDEOBUF_QUEUED;
	//list_add_tail(&buf->vb.queue, &vidq->active);
}

/* 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的头部大小 */

	return 0;
}


static int myvivi_close(struct file *file)
{
	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 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);
    
    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");
4.6 定时器定时填充数据并唤醒进程
/* 仿照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");
【转载】linux摄像头驱动 Linux摄像头驱动学习第一篇,对虚拟视频驱动Video Driver(vivid)进行测试、分析、编写。V4L2(Video for Linux two)是Linux内核中关于的内核,为上层的访问底层的视频设备提供了统一的接口。 阅读详情

相关推荐

【虚拟摄像机】akvcam安装以及使用初试

今天逛网站了解到VCAM(虚拟摄像机),突发奇想在Linux验证下,是不是和我想的一样。经过测试,确定如此。

qq_38189542的博客 2722

linux video input子系统驱动实践1-图像处理基础

是多种媒体的综合,一般包括文本,声音和图像等多种媒体形式,多媒体技术就是通过计算机对语言文字、数据、音频、视频等各种信息进行存储和管理,所谓的媒体就是指承载和传输某种信息或物质的载体。每个颜色通道表示一种颜色,所有颜色通道中的颜色叠加混合产生图像中像素的颜色。每个颜色通道表示一种颜色,所有颜色通道中的颜色叠加混合产生图像中像素的颜色。在彩色图像中,每个像素的数据应包含其亮度和颜色信息,其中,颜色信息由不同的颜色通道。在彩色图像中,每个像素的数据应包含其亮度和颜色信息,其中,颜色信息由不同的颜色通道。

main_476的博客 1370

LINUX摄像驱动二:虚拟驱动VIVI测试及彻底分析

虚拟驱动VIVI测试及彻底分析

Dongxiem 2116

【Linux驱动】Linux--虚拟摄像头vivid驱动分析(基于5.4内核)

分配video_device:video_device_alloc设置.fops.ioctl_ops (里面需要设置11项)如果要用内核提供的缓冲区操作函数,还需要构造一个videobuf_queue_ops注册: video_register_device。

szembed的博客 1558

韦东山第3期嵌入式Linux项目-视频监控-1-v4l2视频框架分析

韦东山第3期嵌入式Linux项目-视频监控-1 硬 件 :电脑、 JZ2440 开发板、 UVC 摄像头(推荐使用) 系 统 : Ubuntu9.10// 即光盘提供的那个虚拟机系统 Uboot : u-boot-2012.04.01 // 即毕业班移植新 uboot 那个 Kernel : linux-3.4.2 // 即毕业班移植新内核那个文件系统: fs_mini_mdev_new.tar...

Iron_man的博客 6945

LINUX摄像驱动三:从零开始写虚拟驱动

虚拟摄像驱动vivi

Dongxiem 1609

测试虚拟驱动vivi

安装自带vivid及依赖,这里先使用modprobr加载vivid,会将其依赖一并加载,然后再卸载vivid,最后加载上我们编译的vivid.ko。但是在正则表达式去搜索文件定义的时候发现vivid目录下所有文件都没有定义过,所以我直接选择进行出问题的地方进行注释。修改Makefile,也就是加入vivid-cec.o,因为如果不加入的话,会提示缺少符号。1.让VMWAER处于前台,接上USB摄像头,可以看到生成了/dev/video0。在这个网站创建新的sources.list。

weixin_52849254的博客 793

jz2440 的Linux内核vivi.c框架分析

内核文档: V4L2-framework.txt 一、测试虚拟驱动vivi 准备工作:安装xawtv sudo apt-get install xawtv 源码xawtv-3.95.tar.gz http://www.kraxel.org/releases/xawtv/ 在这个网站创建新的sources.list http://repogen.simplylinux.ch/ 1. 选择...

583

linux_驱动_camera_架构_V4L2_摄像头驱动2_虚拟驱动vivi的测试

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

weixin_40452895的博客 366

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

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_queu

qingkongyeyue的博客 3128

akvcam:akvcam,适用于Linux的虚拟相机

akvcam,适用于Linux的虚拟相机驱动程序 akvcam是用于Linux的完全兼容的V4L2虚拟摄像机驱动程序。 特征 灵活的配置与简单的INI文件类似。 支持映射,用户指针和读/写模式。 可以吸引并回声到设备。 支持捕获设备中模拟的相机控件(亮度,对比度,饱和度等)。 如果没有可用的输入信号,则可配置默认图片。 拒绝M2M设备的程序不能拒绝该设备。 完全符合V4L2标准。 支持LTS内核。 编译安装 访问以获取全面的编译和安装说明。 资料下载 捐献 我接受加密货币的捐款,欢迎任何捐款,我们非常感谢。 地位 报告错误 在问题跟踪器中报告所有问题。

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

1、结合应用程序分析涉及到的调用 (1)如怎么得到里面的数据、怎么控制摄像头的亮度等信息。 (2)现在PC机上做实验 在ubuntu上安装一个应用程序的测试程序xawtv 安装xawtv:sudo apt-get install xawtv apt-get主要用于自动从互联网的软件仓库中搜索、安装、升级、卸载软件或操作系统。 如果无法上网 (3)下面不能ping通,需要加上路由(设置默认网关命令...

440

Mac本地大模型实战指南:Ollama+Metal+Apple Silicon深度优化

大语言模型(LLM)本地化部署正成为开发者关键技术能力,其核心在于推理效率、硬件适配与成本控制。Apple Silicon凭借统一内存架构(UMA)和Neural Engine,为本地LLM运行提供了独特优势;Metal加速则显著提升GPU计算吞吐,使Gemma 4、Qwen 3.5等模型在M系列Mac上实现低延迟、高精度、离线可用的工程落地。相比云端API,本地方案规避了token计费、网络依赖与数据外泄风险,特别适用于企业文档处理、实时语音分析、私有知识库检索等强隐私、低时延场景。本文聚焦Mac平台,系

weixin_33443333的博客 351

linux摄像头c程序,以vivi.c为例分析摄像头驱动程序思路

以vivi.c为例分析摄像头驱动程序思路:Virtual Video driver - This code emulates a real video device with v4l2 api入口:static int __init vivi_init(void)ret = vivi_create_instance(i) //创建设备struct vivi_dev *dev; //定义一个vivi...

weixin_39646412的博客 344

洛奇 服务器文件,MabinogiG20S2 [洛奇G20服务端]

Code:Titles:>set_title /id: /add /remove Parameters for titles>set_title /id:60001 /add add devcat title>set_title /id:60001 /remove remove devcat title===========================...

weixin_30813385的博客 4301

PCIE6.0协议学习小记-Flit_Mode

PCIE6.0学习

code_aJack的博客 4633

从vivi虚拟摄像头驱动开始分析v4l2

vivi 代码在线查看 https://elixir.bootlin.com/linux/v3.0.101/source/drivers/media/video/vivi.c 这是一个比较老的版本,但是比较适合我们入门v4l2。

ldl617的专栏 3567

合规批量下载美国上市公司年报数据的一些经验和总结

本文总结了使用Python脚本批量下载和处理美国上市公司SEC报告的技术经验。通过CIK代码查询SEC EDGAR系统,生成特定日期和报告类型的URL,并使用Selenium和Requests库下载报告内容。流程包括数据获取、文件下载、Excel处理和多线程优化,确保高效处理几百条数据。关键技术挑战包括SEC访问限制、动态网页内容和数据完整性,解决方案包括设置合规的User-Agent、使用Selenium模拟浏览器行为以及错误日志记录。最佳实践包括模块化设计、多线程优化和严格遵守SEC合规性。该自动化流程

jyfool的博客 1635

2.2 vivi虚拟视频驱动测试

学习目标:在linux终端安装xawtv,并测试vivi.ko驱动程序。 一、安装xawtv 1)ubuntu能上网情况下,使用命令:# sudo apt-get install xawtv 2)如果不能上网,需要先下载源码:xawtv-3.95.tar.gz: http://www.kraxel.org/releases/xawtv/ 步骤: 在这个网站创建新的sources.list...

weixin_34161032的博客 168

三轴云台稳定控制算法优化实战:从PID到模糊自适应

本文深入探讨了三轴云台稳定控制算法的优化实践,从经典PID控制到模糊自适应算法的进阶应用。通过实战案例和代码示例,详细解析了PID调参技巧、模糊控制实现及多传感器数据融合方法,帮助工程师在复杂环境下提升云台抗干扰能力和响应速度,特别适用于无人机航拍等场景。

green的博客 908

Claude Skills 深度讲解

阶段 1:Function Call(2023)├─ 大模型可以调用外部工具├─ 问题:缺乏对任务的深度理解└─ 局限:工具调用简单线性阶段 2:MCP 上下文工程(2024)├─ 大模型有了更丰富的上下文和工具├─ 问题:仍然需要手动编写复杂的 Prompt└─ 局限:每次对话都需要重新加载上下文阶段 3:Claude Skills(2025)├─ 大模型有了「能力模块」的概念├─ 可以动态加载、组合、执行专门的工作流├─ 真正实现了「知识的模块化和复用」

u013134676的博客 1464

第三阶段应用层——2.2 视频监控—根据虚拟驱动vivi的使用过程彻底分析摄像头驱动

视频监控—根据虚拟驱动vivi的使用过程彻底分析摄像头驱动 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3) 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统 参考资料:Video for Linux Two - Driver Writer’s Guide、《Android驱动开发权威指南》 开发环境:Linux 3.4.2内核、arm-linux-gcc 4.3.2工具链 源码仓库:https://gitee.com/.

weixin_42813232的博客 749

【OpenClaw从入门到精通】:Web控制台使用全解析——可视化配置与监控(2026实操版)

在OpenClaw的多种管理方式中,Web控制台提供了最直观、最友好的用户体验。通过图形化界面,用户可以轻松完成复杂的配置任务,实时监控系统状态,以及进行各种管理操作。对于不熟悉命令行的用户来说,Web控制台是最佳选择。

一键难忘的博客 1926

Linux摄像头V4L2驱动框架分析1

学习V4L2(V4L2:vidio for linux version 2)摄像头驱动框架,需要分析vivi.c(虚拟视频硬件相关)驱动源码程序, vivi.c(虚拟视频驱动程序的分析) https://download.csdn.net/my 通过分析vivi.c结构如下 vivi_init -->vivi_create_instance -->v4...

yilongdashi的博客 923

uniapp [全端兼容] - 最新详细实现拍摄视频录像并可播放预览视频,调起本机摄像头开启录像录制完毕后保存到本机临时存放,可对拍摄的视频进行压缩处理减少文件大小不损失画质,详解录制视频并读取播放!

uni-app,h5移动端网页,微信小程序,支付宝小程序,Android安卓,苹果ios,app,nvue,录视频,拍视频,拍摄录制视频后上传服务器,调起摄像头录视频并可预览,录视频上传,录像,uniapp录制视频并上传,录制视频并保存到本地设备,如何本地保存并播放预览,提供给用户观看MP4视频录像并上传到后端服务器完整功能源码,视频压缩,mp4文件,删除视频,录像后怎么预览,临时视频文件怎么减少压缩大小,视频压缩及上传,自己录视频并生成可播放的文件,录视频并能打开查看,视频压缩后上传,本机相册,录完怎么预

🏆 前端领域优质创作者 1万+

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

linux虚拟摄像头 开源,(四) 虚拟摄像头vivi体验

[TOC]虚拟摄像头vivi体验vivid是虚拟的摄像头驱动.可以理解等同于UVC,只是说不需要USB总线驱动控制等源码下载# 查看内核版本$ uname -aLinux 100ask 4.13.0-36-generic #40~16.04.1-Ubuntu SMP Fri Feb 16 23:25:58 UTC 2018 x86_64 x86_64 x86_64 GNU/Linuxtar xzf...

weixin_26845891的博客 796

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

上一篇: Content Provider组件简析。。。待续
下一篇: 数据存储方案-持久化存储
Ma浩然
博客等级 码龄9年 1万+粉丝 462原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ma浩然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值