v4l2子系统学习(三)编写虚拟摄像头驱动

news2025/2/26 17:38:27

文章目录

  • 1、声明
  • 2、前言
  • 3、虚拟摄像头驱动编写
    • 3.1、编写硬件相关代码
    • 3.2、程序示例

1、声明

本文是在学习韦东山《驱动大全》V4L2子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。

韦老师的《驱动大全》:商品详情

其对应的讲义资料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git

2、前言

前面两章,我们基于v4l2在应用层实现了获取摄像头数据,了解了应用层中获取摄像头数据应该有的流程。接着,借着应用层的每一步操作来了解其驱动是如何实现的,从而了解整个v4l2的驱动框架。

从宏观的角度来看,前期的所有准备,无非就是想知道在编写设备驱动程序时要去分配设置哪个结构体,实现哪些关键函数。对于其它子系统的学习也是如此,再者,如今编写驱动的机会只会越来越少,学习驱动的首要目的还是为了养兵千日,用兵一时。

3、虚拟摄像头驱动编写

3.1、编写硬件相关代码

ioctl相关:

1、分配设置注册video_device结构体。

2、实现相关的ioctl函数(如获取设备能力、枚举格式、枚举分辨率、设置分辨率等)。

buffer相关:

3、分配设置vb2_queue结构体。

4、实现相关的vb2_ops函数(如queue_setup()、buf_queue()、start_streaming()、stop_streaming()等函数)。

数据传输:

5、实现start_streaming()、stop_streaming()

3.2、程序示例

#include <linux/version.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-vmalloc.h>

extern unsigned char red[8230];
extern unsigned char blue[8267];
extern unsigned char green[8265];

static int g_copy_cnt = 0;

static struct list_head g_queued_bufs;
static struct timer_list g_virtual_timer;
static struct mutex g_vb_queue_lock;  /* Protects vb_queue and capt_file */
static struct mutex g_v4l2_lock;      /* Protects everything else */

/* intermediate buffers with raw data from the USB device */
struct virtual_frame_buf {
	/* common v4l buffer stuff -- must be first */
	struct vb2_v4l2_buffer vb;
	struct list_head list;
};

/* Private functions */
static struct virtual_frame_buf *virtual_get_next_buf(void)
{
	//unsigned long flags;
	struct virtual_frame_buf *buf = NULL;

	//spin_lock_irqsave(&s->queued_bufs_lock, flags);
	if (list_empty(&g_queued_bufs))
		goto leave;

	buf = list_entry(g_queued_bufs.next, struct virtual_frame_buf, list);
	list_del(&buf->list);
leave:
	//spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
	return buf;
}


#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
static void virtual_timer_expire(unsigned long data)
#else
static void virtual_timer_expire(struct timer_list *t)
#endif
{
	/* 从硬件上读到数据(使用red/green/blue数组来模拟) */
	
	/* 获得第1个空闲buffer */
	struct virtual_frame_buf *buf = virtual_get_next_buf();
	void *ptr;

	if (buf)
	{
		/* 写入buffer */
		ptr = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);

		if (g_copy_cnt <= 60)
		{
			memcpy(ptr, red, sizeof(red));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(red));
		}
		else if(g_copy_cnt <= 120)
		{
			memcpy(ptr, green, sizeof(green));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(green));
		}
		else 
		{
			memcpy(ptr, blue, sizeof(blue));
			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, sizeof(blue));
		}
		
		/* vb2_buffer_done */
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
	}

	g_copy_cnt++;
	if (g_copy_cnt > 180)
		g_copy_cnt = 0;

	/* 再次设置timer的超时时间 */
	mod_timer(&g_virtual_timer, jiffies + HZ/30);
}

static int virtual_querycap(struct file *file, void *fh,
		struct v4l2_capability *cap)
{
	strlcpy(cap->driver, "100ask_virtual_video", sizeof(cap->driver));
	strlcpy(cap->card, "no-card", sizeof(cap->card));
	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_READWRITE ;
	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;

	return 0;
}

static int virtual_enum_fmt_cap(struct file *file, void *priv,
        struct v4l2_fmtdesc *f)
{
    if (f->index > 0)
        return -EINVAL;

    strlcpy(f->description, "100ask motion jpeg", sizeof(f->description));
    f->pixelformat = V4L2_PIX_FMT_MJPEG;

    return 0;
}
     
static int virtual_s_fmt_cap(struct file *file, void *priv,
        struct v4l2_format *f)
{
    /* 分辨用户传入的参数是否可用
     * 如果不可用, 给APP提供最接近的、硬件支持的参数
     */

    if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
        return -EINVAL;
    if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG)
        return -EINVAL;
    
    f->fmt.pix.width = 800;
    f->fmt.pix.height = 600;

    return 0;
}

static int virtual_enum_framesizes(struct file *file, void *fh,
                     struct v4l2_frmsizeenum *fsize)
{
    if (fsize->index > 0)
        return -EINVAL;

    fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
    fsize->discrete.width = 800;
    fsize->discrete.height = 600;
    return 0;
}

 static int virtual_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
{
	struct v4l2_pix_format *pix = &f->fmt.pix;

	pix->width = 800;
	pix->height = 600;
	pix->field = V4L2_FIELD_NONE;
	pix->pixelformat = V4L2_PIX_FMT_MJPEG;
	pix->bytesperline = 0;
    return 0;
}

static const struct v4l2_file_operations virtual_fops = {
	.owner                    = THIS_MODULE,
	.open                     = v4l2_fh_open,
	.release                  = vb2_fop_release,
	.read                     = vb2_fop_read,
	.poll                     = vb2_fop_poll,
	.mmap                     = vb2_fop_mmap,
	.unlocked_ioctl           = video_ioctl2,
};

static const struct v4l2_ioctl_ops virtual_ioctl_ops = {
	.vidioc_querycap          = virtual_querycap,

	.vidioc_enum_fmt_vid_cap  = virtual_enum_fmt_cap,
	.vidioc_s_fmt_vid_cap     = virtual_s_fmt_cap,
	.vidioc_enum_framesizes   = virtual_enum_framesizes,
	.vidioc_g_fmt_vid_cap     = virtual_g_fmt,

	.vidioc_reqbufs           = vb2_ioctl_reqbufs,
	.vidioc_create_bufs       = vb2_ioctl_create_bufs,
	.vidioc_prepare_buf       = vb2_ioctl_prepare_buf,
	.vidioc_querybuf          = vb2_ioctl_querybuf,
	.vidioc_qbuf              = vb2_ioctl_qbuf,
	.vidioc_dqbuf             = vb2_ioctl_dqbuf,

	.vidioc_streamon          = vb2_ioctl_streamon,
	.vidioc_streamoff         = vb2_ioctl_streamoff,
};

static struct video_device g_vdev = {
	.name                     = "100ask_virtual_video",
	.release                  = video_device_release_empty,
	.fops                     = &virtual_fops,			// v4l2接口层相关的ioctl操作函数
	.ioctl_ops                = &virtual_ioctl_ops,		// 与硬件相关的ioctl操作函数
};

static struct v4l2_device g_v4l2_dev;

static struct vb2_queue g_vb_queue;

static int virtual_queue_setup(struct vb2_queue *vq,
		unsigned int *nbuffers,
		unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
{
    /* 假装:至少需要8buffer, 每个buffer只有1个plane */

	/* Need at least 8 buffers */
	if (vq->num_buffers + *nbuffers < 8)
		*nbuffers = 8 - vq->num_buffers;
	*nplanes = 1;
	sizes[0] = PAGE_ALIGN(800*600*2);

	return 0;
}
  
static void virtual_buf_queue(struct vb2_buffer *vb)
{

    /* 把这个buffer告诉硬件相关的驱动程序 */

    struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
    struct virtual_frame_buf *buf =
            container_of(vbuf, struct virtual_frame_buf, vb);
    //unsigned long flags;

    //spin_lock_irqsave(&s->queued_bufs_lock, flags);
    list_add_tail(&buf->list, &g_queued_bufs);		//buffer放到驱动程序里的链表
    //spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
}

static int virtual_start_streaming(struct vb2_queue *vq, unsigned int count)
{
    /* 启动硬件传输 */

	/* 使用timer来模拟硬件中断
	 * 创建timer
	 * 启动timer
	 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0)
	setup_timer(&g_virtual_timer, virtual_timer_expire, 0);
#else
	timer_setup(&g_virtual_timer, virtual_timer_expire, 0);
#endif

	g_virtual_timer.expires = jiffies + HZ/30;
	add_timer(&g_virtual_timer);

    return 0;
}

static void virtual_stop_streaming(struct vb2_queue *vq)
{
    /* 停止硬件传输 */
	del_timer(&g_virtual_timer);

	while (!list_empty(&g_queued_bufs)) {
		struct virtual_frame_buf *buf;

		buf = list_entry(g_queued_bufs.next,
				struct virtual_frame_buf, list);
		list_del(&buf->list);
		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
	}
}

/* buffer管理要用到的vb2_ops */
static const struct vb2_ops virtul_vb2_ops = {
	.queue_setup            = virtual_queue_setup,		// APP调用ioctl VIDIOC_REQBUFS或VIDIOC_CREATE_BUFS时,驱动程序在分配内存之前,会调用此函数。
	.buf_queue              = virtual_buf_queue,		//buffer传送给驱动
	.start_streaming        = virtual_start_streaming,	// 启动硬件传输(操作硬件)
	.stop_streaming         = virtual_stop_streaming,	// 关闭硬件传输(操作硬件)
	.wait_prepare           = vb2_ops_wait_prepare,
	.wait_finish            = vb2_ops_wait_finish,
};

static void virtual_video_release(struct v4l2_device *v)
{
}

static int virtual_video_drv_init(void)
{
    int ret;
    
    /* 分配/设置/注册video_device */
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	/* 1、设置vb2_queue结构体 */
    g_vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    g_vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
    g_vb_queue.drv_priv = NULL;
    g_vb_queue.buf_struct_size = sizeof(struct virtual_frame_buf);  /* 分配vb时, 分配的空间大小为buf_struct_size */
    g_vb_queue.ops = &virtul_vb2_ops;			// 设置buffer管理要用到的vb2_ops
    g_vb_queue.mem_ops = &vb2_vmalloc_memops;	// 设置buffer管理要用到的vb2_mem_ops
    g_vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
	ret = vb2_queue_init(&g_vb_queue);
	if (ret) {
		printk("Could not initialize vb2 queue\n");
		return -1;;
	}
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	mutex_init(&g_vb_queue_lock);
	
	/* 2、设置video_device结构体 */
    g_vdev.queue = &g_vb_queue;     
    g_vdev.queue->lock = &g_vb_queue_lock;

	/* Register the v4l2_device structure(辅助作用) */
	g_v4l2_dev.release = virtual_video_release;
    strcpy(g_v4l2_dev.name, "virtual_v4l2");
	ret = v4l2_device_register(NULL, &g_v4l2_dev);
	if (ret) {
		printk("Failed to register v4l2-device (%d)\n", ret);
		return -1;
	}
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	g_vdev.v4l2_dev = &g_v4l2_dev;
	g_vdev.lock = &g_v4l2_lock;
	mutex_init(&g_v4l2_lock);
	
	/* 3、注册video_device结构体 */ 
	ret = video_register_device(&g_vdev, VFL_TYPE_GRABBER, -1);
	if (ret) {
		printk("Failed to register as video device (%d)\n", ret);
		return -1;
	}
    printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	INIT_LIST_HEAD(&g_queued_bufs);
    
    return 0;
}

static void virtual_video_drv_exit(void)
{
    /* 反注册/释放video_device */
    v4l2_device_unregister(&g_v4l2_dev);
    video_unregister_device(&g_vdev);
}

module_init(virtual_video_drv_init);
module_exit(virtual_video_drv_exit);

MODULE_LICENSE("GPL");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2306491.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【虚拟仪器技术】labview操作指南和虚拟仪器技术习题答案(一)

今天是2025年2月24日&#xff0c;画的是fate/Grand Order里面的阿尔托莉雅.卡斯特&#xff0c;武内老师的画。 目录 第1章 第2章 第3章 第4章 第5章 关注作者了解更多 我的其他CSDN专栏 毕业设计 求职面试 大学英语 过程控制系统 工程测试技术 虚拟仪器技术 可编程…

LabVIEW电能质量分析软件

随着电力系统的复杂性增加&#xff0c;电能质量问题日益突出&#xff0c;传统的电能质量检测装置多采用DSP技术&#xff0c;不仅开发周期长、功能单一&#xff0c;而且在多功能集成方面存在局限性。基于LabVIEW虚拟仪器开发平台的电能质量分析软件利用FFT、STFT、WT、HHT等多种…

视频裂变加群推广分享引流源码

源码介绍 视频裂变加群推广分享引流源码 最近网上很火&#xff0c;很多人都在用&#xff0c;适合引流裂变推广 测试环境&#xff1a;PHP7.4(PHP版本不限制) 第一次访问送五次观看次数&#xff0c;用户达到观看次数后需要分享给好友或者群,好友必须点击推广链接后才会增加观看次…

项目一 - 任务3:搭建Java集成开发环境IntelliJ IDEA

通过本次实战&#xff0c;我们成功搭建了Java集成开发环境IntelliJ IDEA&#xff0c;并完成了多个任务。首先&#xff0c;安装了IntelliJ IDEA并进行了个性化设置&#xff0c;如选择主题、调整字体和编码等。接着&#xff0c;创建了Java项目、包和类&#xff0c;编写并运行了简…

RoCBert:具有多模态对比预训练的健壮中文BERT

摘要 大规模预训练语言模型在自然语言处理&#xff08;NLP&#xff09;任务上取得了最新的最优结果&#xff08;SOTA&#xff09;。然而&#xff0c;这些模型容易受到对抗攻击的影响&#xff0c;尤其是对于表意文字语言&#xff08;如中文&#xff09;。 在本研究中&#xff0…

DeepSeek开源周Day2:DeepEP - 专为 MoE 模型设计的超高效 GPU 通信库

项目地址&#xff1a;https://github.com/deepseek-ai/DeepEP 开源日历&#xff1a;2025-02-24起 每日9AM(北京时间)更新&#xff0c;持续五天 (2/5)&#xff01; ​ ​ 引言 在大模型训练中&#xff0c;混合专家模型&#xff08;Mixture-of-Experts, MoE&#xff09;因其动…

六十天前端强化训练之第二天CSS选择器与盒模型深度解析

欢迎来到编程星辰海的博客讲解 目录 一、CSS 核心概念 1. 三种引入方式 2. CSS 注释 3. 常见单位系统 二、CSS选择器核心知识 1. 基础选择器类型 2. 组合选择器 3. 伪类选择器&#xff08;部分示例&#xff09; 4. 优先级计算规则 三、盒模型深度解析 1. 标准盒模型图…

分享httprunner 结合django实现平台接口自动化方案

说明&#xff0c;可以直接在某个视图集定义自定义接口来验证。 调试1&#xff1a;前端界面直接编写yaml文件. 新增要实现存数据到mysql&#xff0c;同时存文件到testcase下, 如test.yaml 更新yaml数据&#xff0c;同时做到更新 testcase下的文件&#xff0c;如test.yaml acti…

本地大模型编程实战(22)用langchain实现基于SQL数据构建问答系统(1)

使 LLM(大语言模型) 系统能够查询结构化数据与非结构化文本数据在性质上可能不同。后者通常生成可在向量数据库中搜索的文本&#xff0c;而结构化数据的方法通常是让 LLM 编写和执行 DSL&#xff08;例如 SQL&#xff09;中的查询。 我们将演练在使用基于 langchain 链 &#x…

速通HTML

目录 HTML基础 1.快捷键 2.标签 HTML进阶 1.列表 a.无序列表 b.有序列表 c.定义列表 2.表格 a.内容 b.合并单元格 3.表单 a.input标签 b.单选框 c.上传文件 4.下拉菜单 5.文本域标签 6.label标签 7.按钮标签 8.无语义的布局标签div与span 9.字符实体 HTML…

博客系统完整开发流程

前言 通过前⾯课程的学习, 我们掌握了Spring框架和MyBatis的基本使用, 并完成了图书管理系统的常规功能开发, 接下来我们系统的从0到1完成⼀个项⽬的开发. 企业开发的流程 1. 需求评审(产品经理(PM)会和运营(想口号),UI,测试,开发等沟通) ,会涉及到背景/目标/怎么做,可能会有多…

【C语言】指针笔试题

前言&#xff1a;上期我们介绍了sizeof与strlen的辨析以及sizeof&#xff0c;strlen相关的一些笔试题&#xff0c;这期我们主要来讲指针运算相关的一些笔试题&#xff0c;以此来巩固我们之前所学的指针运算&#xff01; 文章目录 一&#xff0c;指针笔试题1&#xff0c;题目一…

【Qt】桌面应用开发 ------ 绘图事件和绘图设备 文件操作

文章目录 9、绘图事件和绘图设备9.1 QPainter9.2 手动触发绘图事件9.3 绘图设备9.3.1 QPixmap9.3.2 QImage9.3.3 QImage与QPixmap的区别9.3.4 QPicture 10、文件操作10.1 文件读写10.2 二进制文件读写10.3 文本文件读写10.4 综合案例 9、绘图事件和绘图设备 什么时候画&#x…

python与C系列语言的差异总结(3)

与其他大部分编程语言不一样&#xff0c;Python使用空白符&#xff08;whitespace&#xff09;和缩进来标识代码块。也就是说&#xff0c;循环体、else条件从句之类的构成&#xff0c;都是由空白符加上冒号(:)来确定的。大部分编程语言都是使用某种大括号来标识代码块的。下面的…

OpenCV(9):视频处理

1 介绍 视频是由一系列连续的图像帧组成的&#xff0c;每一帧都是一幅静态图像。视频处理的核心就是对这些图像帧进行处理。常见的视频处理任务包括视频读取、视频播放、视频保存、视频帧处理等。 视频分析: 通过视频处理技术&#xff0c;可以分析视频中的运动、目标、事件等。…

【C++设计模式】观察者模式(1/2):从基础到优化实现

1. 引言 在 C++ 软件与设计系列课程中,观察者模式是一个重要的设计模式。本系列课程旨在深入探讨该模式的实现与优化。在之前的课程里,我们已对观察者模式有了初步认识,本次将在前两次课程的基础上,进一步深入研究,着重解决观察者生命周期问题,提升代码的安全性、灵活性…

在 CentOS 7.9上部署 Oracle 11.2.0.4.0 数据库

目录 在 CentOS 7.9上部署 Oracle 11.2.0.4.0 数据库引言安装常见问题vim粘贴问题 环境情况环境信息安装包下载 初始环境准备关闭 SELinux关闭 firewalld 安装前初始化工作配置主机名安装依赖优化内核参数限制 Oracle 用户的 Shell 权限配置 PAM 模块配置swap创建用户组与用户,…

计算机毕业设计SpringBoot+Vue.js足球青训俱乐部管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

基于 DeepSeek LLM 本地知识库搭建开源方案(AnythingLLM、Cherry、Ragflow、Dify)认知

写在前面 博文内容涉及 基于 Deepseek LLM 的本地知识库搭建使用 ollama 部署 Deepseek-R1 LLM知识库能力通过 Ragflow、Dify 、AnythingLLM、Cherry 提供理解不足小伙伴帮忙指正 &#x1f603;,生活加油 我站在人潮中央&#xff0c;思考这日日重复的生活。我突然想&#xff0c…

QSplashScreen --软件启动前的交互

目录 QSplashScreen 类介绍 使用方式 项目中使用 THPrinterSplashScreen头文件 THPrinterSplashScreen实现代码 使用代码 使用效果 QSplashScreen 类介绍 QSplashScreen 是 Qt 中的一个类&#xff0c;用于显示启动画面。它通常在应用程序启动时显示&#xff0c;以向用户显…