(以下所说的都是基于微软的windows平台)       
        类似功能的产品,如著名的e2eSoft的 VCam,国内新浪的9518虚拟视频,
新浪的虚拟视频是DirectShow应用层上的视频模拟,大概的思路就是:
因为所有的摄像头WDM驱动,都需要通过运行在应用层的ksproxy.ax与DirectShow交互,
ksproxy.ax顾名思义,是WDM驱动同DShow的代理,
因此在应用层模拟虚拟摄像头,可在这上面做文章(但是有个遗憾就是老的VFW不支持这种方式)。
具体怎么实现,有兴趣的可搜索相关文档。
e2esoft的VCam是真正基于WDM驱动的虚拟摄像头,他有一个WDM驱动来模拟摄像头,
在应用程序看来,跟一个真正的摄像头没有分别,所以他的适用范围很广,基本上真正摄像头能干的事情,他都能做到。

这里要阐述的,就是基于WDM开发的虚拟摄像头驱动(基于老的流内核框架)
摄像头驱动是微端口驱动,是跟真正硬件打交道的一类驱动。
在DriverEntry里初始化 HW_INITIALIZATION_DATA 结构,填写相关回调函数,
调用StreamClassRegisterAdapter注册自己到类驱动。
在回调函数里完成大部分的工作,它依靠HW_STREAM_REQUEST_BLOCK(简称SRB块)跟流类驱动通讯。
流类驱动发送SRB_INITIALIZE_DEVICE命令初始化设备,
接着发送SRB_GET_STREAM_INFO命令获得摄像头支持的视频流。
然后发送SRB_INITIALIZATION_COMPLETE表示初始化完成,
当应用程序要打开摄像头的时候,比如DShow程序,会调用ksproxy.ax,ksproxy.ax接着调用流类驱动,
流类驱动发送 SRB_OPEN_STREAM 给摄像头驱动,表示要打开某个视频流,
当要关闭一个流的时候,流类驱动发送SRB_CLOSE_STREAM表示要关闭某个流,
当驱动卸载或者禁用时候,会收到类驱动发来的SRB_UNINITIALIZE_DEVICE,表示卸载设备。
在 SRB_OPEN_STREAM命令中,要填写两个重要的回调函数,一个是收发视频数据的回调,一个是流控制命令的回调。
收发视频数据的回调完成真正的视频数据传输。
摄像头驱动的基本工作流程就如上面所说,原理其实并不复杂,可能只是细节上处理的比较多些。
以上说的只是大致流程,对初学驱动的同学可能根本不理解,所以要完全理解并能自己开发一个虚拟摄像头驱动,
还是得靠自己查阅大量的相关资料。

在收发视频数据回调函数里,一共提供两个命令:
SRB_READ_DATA,从硬件获得视频数据到应用层,由流类驱动定时发送,
SRB_WRITE_DATA  流类驱动提供视频数据,要写到硬件中去。
而一般的摄像头只有从硬件采集数据到电脑,所以只有 SRB_READ_DATA命令。

像VCAM的驱动,两个命令都支持,他提供两个PIN(针脚),
一个输出PIN,用来把”硬件“采集来的数据输出到类驱动,对应SRB_READ_DATA命令。
一个输入PIN,用来把上层发来的数据写到“硬件”里去,对应SRB_WRITE_DATA命令。
在VCAM驱动内部把这两个PIN连接起来,就完成了一个虚拟摄像头,
简单的说就是把输入PIN的数据copy到输出PIN上去,
作为VCAM的视频源,只要在应用层用DShow打开对应的输入PIN,然后直接朝这个PIN写视频数据,
这些都是标准的DirectShow开发。

刚开发虚拟摄像头驱动的时候,我并不知道是采用这种方式来传输视频源数据。
而是想到一个经典的办法:在驱动里创建功能设备,然后依靠功能设备来传输视频源数据。
后来才渐渐体会出VCAM的处理办法,但是我坚持采用自己的办法来解决问题。
于是自己的处理办法如下:

在HW_INITIALIZATION_DATA命令中创建功能设备,在HW_UNINITIALIZATION_DATA中删除功能设备。
最麻烦的是 对这个功能设备的派遣回调函数的处理了, 功能设备只需要处理
IRP_MJ_CREATE,IRP_MJ_CLEANUP,IRP_MJ_CLOSE,IRP_MJ_DEVICE_CONTROL
4个派遣命令, 但是在DriverEntry里调用 StreamClassRegisterAdapter函数之后, 流类驱动已经hook了所有派遣函数。
因此唯一的解决办法,就是在调用StreamClassRegisterAdapter完之后,
保存它原来的派遣函数地址,替换成自己的派遣函数地址。类似如下代码

status = StreamClassRegisterAdapter( DriverObject, RegistryPath, &HwInitData );
if( NT_SUCCESS(status)){
        org_create_function = DriverObject->MajorFunction[IRP_MJ_CREATE];
        org_cleanup_function = DriverObject->MajorFunction[IRP_MJ_CLEANUP];
        org_close_function = DriverObject->MajorFunction[IRP_MJ_CLOSE];
        org_ioctl_function = DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL];
       
        DriverObject->MajorFunction[IRP_MJ_CREATE] =
        DriverObject->MajorFunction[IRP_MJ_CLEANUP] =
        DriverObject->MajorFunction[IRP_MJ_CLOSE] = ioctl_createclose;
        DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ioctl_control;

}

在自己的派遣函数里,如果不是本功能设备的命令,就调用原来的派遣函数,否则就自己处理。
至于如何判断命令是本功能设备发送的,还是其他设备发送的,
我最早使用的办法是保存本功能设备的PDEVICE_OBJECT对象指针,
在派遣函数里判断传递的DEVICE对象是不是自己创建的设备,从而判定是不是本设备发送的命令,
但是这种做法,每次当应用程序打开这个设备,系统卸载驱动的时候,就会造成系统蓝屏崩溃,
最后想到一个更好的处理办法:在IoCreateDevice的时候,创建一个自定义的设备类型,
每次在派遣函数里判断传递进来的设备类型是不是自己创建的功能设备,
是的话就自己处理,否则交给原始派遣函数处理,类似如下代码:
NTSTATUS ioctl_control( PDEVICE_OBJECT obj, PIRP Irp)
{
       if(obj->DeviceType != FILE_DEVICE_MY_VCAMERA_DEVICE)
           return org_ioctl_function( obj, Irp); //FILE_DEVICE_MY_VCAMERA_DEVICE 是自定义的一个宏
        ///是自己设备发送的命令,处理...
        ........      
}
从而这个问题得到很好的解决。

接着就是视频数据传输的问题了,视频数据都是相当庞大的,不可能把数据从用户空间copy到内核空间,
再从内核空间copy到流类驱动请求的 SRB_READ_DATA的SRB块里,如果这样做的话,跟找死差不多。
因此采用共享内存的办法, 直接把SRB块里的空间映射到应用层程序空间里,具体做法就是:
每当应用程序调用DeviceIoControl发送请求地址命令,
驱动的派遣函数查找排队的SRB块(每个流类驱动发送的SRB_READ_DATA的SRB块,都会被排队等待处理)
如果找到,调用MmMapLockedPagesSpecifyCache等函数,把SRB块里的视频数据内存地址,映射到用户空间地址。
并把这个地址返回给用户, 用户获得这个地址之后,就可以直接朝这个地址填写视频数据了。
当用户填写完视频数据之后,再用DeviceIoControl发送完成命令,
驱动的派遣函数接着把这个Srb提交给流类驱动,这样就完成了一次视频数据帧的操作。

应用程序代码大致如下:
hFile = CreateFile(功能设备名,...);
///
struct ioctl_vcamarg_t
{
    ULARGE_INTEGER   inter_handle; 
   
    int                stream_number;   
    int                video_type;//视频类型,比如RGB24,YV12等类型
    int                width;//视频帧的宽度
    int                height; //高度
    int                bit_count;//一个数据点占用多少位
    int                buffer_size;//映射的地址空间大小
    ULARGE_INTEGER   buffer_address;//映射的用户空间地址
};
struct ioctl_vcamarg_t arg;//用于同驱动传递信息的数据结构,里边有映射的用户空间地址
DeviceIoCrontrol( hFile, IOCTL_USE_VCAM_BUFFER,&arg,...); //获得视频数据地址
//接着就可以直接操作这个地址地址空间,比如如下,把整个视频帧填写为灰色。
memcpy( (VOID*)(ULONG_PTR)arg.buffer_address.Quad, 0x80, arg.buffer_size);

DeviceIoControl(和File,IOCTL_END_VCAM_BUFFER,&arg.inter_handle,....); //完成视频数据帧。

因此我对视频源的这种做法,跟通用的做法是有些不同的,但是实际上完成之后,发现效率一点也不差,
而且在视频源的用户层程序的开发,简单了许多,因为没使用一堆的DirectShow的乱七八糟的复杂的接口。

如上,这是整个虚拟摄像头驱动的开发,如果觉得这种这种做法有不对劲的地方,请高手指出,不吝赐教。

虚拟摄像头驱动原理及开发相关推荐

  1. V4L2(三)编写虚拟摄像头驱动

    内核版本:linux-3.5 开发板:tiny4412 概述 前面简单分析了内核中虚拟摄像头驱动 vivi 的框架与实现,本文参考 vivi 来写一个虚拟摄像头驱动,查询.设置视频格式相对简单,难点在 ...

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

    本文学习自韦东山老师的摄像头驱动模块 目录 一 摄像头驱动程序学习切入点以及V4L2模型概览 二 简析虚拟视频驱动 VIVI.C 2.1 初始化.设置.注册过程 2.2 简析vivi.c的open,r ...

  3. 二十四、V4L2框架主要结构体分析和虚拟摄像头驱动编写

    一.V4L2框架主要结构体分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括两层 ...

  4. 深入学习Linux摄像头(三)虚拟摄像头驱动分析

    深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...

  5. 写一个虚拟摄像头驱动3

    /* 仿照vivi.c */ #include <linux/module.h> #include <linux/delay.h> #include <linux/err ...

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

    Linux v4l2架构学习总链接 内核代码是基于linux4.9分析 vivi 代码在线查看 vivi.c - drivers/media/video/vivi.c - Linux source c ...

  7. 第1.4节_摄像头驱动_从零写一个虚拟驱动

    1.1th(搭建起虚拟摄像头驱动框架) /* 仿照vivi.c */ #include <linux/module.h> #include <linux/delay.h> #i ...

  8. Linux虚拟摄像头vivid配置

    总述 最近在看摄像头驱动,需要配置虚拟摄像头的驱动,但是教程里面是linux2.6内核的,实际电脑的是Ubuntu16,内核是linux4.15版本,从2.6到4.15内核好多文件发生了变化,所以我们 ...

  9. 【Ubuntu】虚拟摄像头,并应用在Teams上

    本文目的 本文意在记录笔者在Ubuntu22.04上配置虚拟摄像头,并应用在Teams通信软件上的全部过程. 操作系统:Ubuntu22.04 虚拟摄像头:OBS.Akvcam 最终成果: 使用v4l ...

最新文章

  1. 信标节能电路模块第二版本调试-无线充电-2021-3-21
  2. mysql 子查询添加索引_mysql – 你能索引子查询吗?
  3. 运算放大器基本公式_运算放大器积分器的些微差异
  4. 系统调用和库函数的区别
  5. 【英语学习】【Level 08】U04 What I love L2 My favorite sport
  6. Graphviz神经网络的绘图
  7. c语言windows窗口程序,C语言编写windows窗口程序
  8. 如何调整eclipse字体大小
  9. xrd连续扫描和步进扫描_深度解析XRD
  10. 单个dcm文件含有多帧数据,如何拆分成多个dcm文件
  11. [离散数学]集合论基础P_4:运算定律及其证明
  12. XboxOne和PS4
  13. Windows10启动缓慢的原因以及解决方法
  14. 所谓“生活的艺术“, 就是悠闲二字
  15. python销毁线程_python线程销毁
  16. java颜色识别_Java颜色检测
  17. Dungeon Master(地牢大师、底下城主)三维bfs
  18. ssoj4015: 永琳的竹林迷径(path)
  19. 基因表达微阵列数据分类的多目标启发式算法
  20. DB-数据库基本概念(一)

热门文章

  1. 陶朗集团任命新总裁兼CEO;开利完成对广东积微集团的收购;宁德时代获大众集团电芯测试实验室认证 | 能动...
  2. 从耦合微带线到近、远端串扰
  3. Thread.setDaemon详解
  4. PetaLinux使用Gstreamer传输USB摄像头到DP显示屏
  5. Matlab函数、子函数的定义方法
  6. 干货分享 | 代谢组学数据分析,常见图形制作分享-百趣生物
  7. 嵌入式裸机课程之C语言程序调用和重定位学习笔记
  8. [实践篇]13.8 如何解析gcore?
  9. Linux lseek函数
  10. 6.2 阈值处理-- 自适应阈值处理和 阈值Otsu处理