基于AVStream框架的虚拟摄像头开发实现

基于AVStream框架的虚拟摄像头开发实现

在前面文章中,我们分析过虚拟摄像头有两种实现方式:

  1. 基于DirectShow框架的虚拟摄像头实现。
  2. 基于驱动的虚拟摄像头实现。

并且在前面文章中分析了基于DirectShow框架的方案(可以参见基于DirectShow框架的虚拟摄像头开发实现),在这里我们分析基于AVStream驱动框架的摄像头方案,该方案可以做到稳定性好,兼容几乎所有的应用软件。

1. AVStream技术概览

AVStream是用来取代Kernel Stream的音视频开发的一种新框架,其大致架构如下:
在这里插入图片描述

如上图我们可以发现其都是基于KS框架来扩展的,扩展基于Port Class的驱动框架(用来开发音频驱动),基于AVStream的驱动框架(用来开发视频驱动)。这个框架有两个比较明显的优势:

  1. 开发更加便捷和简单。
  2. 为音视频的处理提供了统一的内核流处理模型。

在AVStream框架中,都是基于对象来进行抽象和管理,整个对象形成一个类似树状的结构,框架如下:
在这里插入图片描述

如上图中所示:

  1. 对于一个AVStream驱动的设备,使用AVStream Device对象作为整个结构的树根。
  2. AVStream Device对象可以设置多个AVStream Filter Factory对象,用来创建过滤器。
  3. 对于一个过滤器可以可以包含多个AVStream Pin Factory。
  4. AVStream Pin Factory可以用来创建引脚实例。

AVStream的主要工作就是组织这些对象,让其协同工作。

2. 描述符

通过上面我们可以看到整个AVStream的核心工作原理是各自的对象来实现的,AVStream框架已经为我们做好了所有的处理流程(例如IRP数据的处理),我们只需要定义这些对象即可。在AVStream框架中,使用描述符来定义各自的对象,可以分为三种描述符:

  1. 设备描述符。
  2. 过滤器描述符。
  3. 引脚描述符。

设备描述符包括设备的信息,例如设备的PNP回调函数,以及设备中默认的过滤器描述符,其定义如下:

typedef struct _KSDEVICE_DESCRIPTOR {
  const KSDEVICE_DISPATCH   *Dispatch;
  ULONG                     FilterDescriptorsCount;
  const KSFILTER_DESCRIPTOR const * * FilterDescriptors;
  ULONG                     Version;
  ULONG                     Flags;
  PVOID                     Alignment;
} KSDEVICE_DESCRIPTOR, *PKSDEVICE_DESCRIPTOR;

KSFILTER_DESCRIPTOR表示过滤器描述符,描述了过滤器工厂的特性,框架使用该特性创建过滤器,该结构定义如下:

typedef struct _KSFILTER_DESCRIPTOR {
  const KSFILTER_DISPATCH     *Dispatch;
  const KSAUTOMATION_TABLE    *AutomationTable;
  ULONG                       Version;
  ULONG                       Flags;
  const GUID                  *ReferenceGuid;
  ULONG                       PinDescriptorsCount;
  ULONG                       PinDescriptorSize;
  const KSPIN_DESCRIPTOR_EX   *PinDescriptors;
  ULONG                       CategoriesCount;
  const GUID                  *Categories;
  ULONG                       NodeDescriptorsCount;
  ULONG                       NodeDescriptorSize;
  const KSNODE_DESCRIPTOR     *NodeDescriptors;
  ULONG                       ConnectionsCount;
  const KSTOPOLOGY_CONNECTION *Connections;
  const KSCOMPONENTID         *ComponentId;
} KSFILTER_DESCRIPTOR, *PKSFILTER_DESCRIPTOR;

KSPIN_DESCRIPTOR_EX表示引脚的描述符,引脚描述符定义了引脚工厂的特性,框架使用这些特性来创建引脚实例,该结构定义如下:

typedef struct _KSPIN_DESCRIPTOR_EX {
  const KSPIN_DISPATCH         *Dispatch;
  const KSAUTOMATION_TABLE     *AutomationTable;
  KSPIN_DESCRIPTOR             PinDescriptor;
  ULONG                        Flags;
  ULONG                        InstancesPossible;
  ULONG                        InstancesNecessary;
  const KSALLOCATOR_FRAMING_EX *AllocatorFraming;
  PFNKSINTERSECTHANDLEREX      IntersectHandler;
} KSPIN_DESCRIPTOR_EX, *PKSPIN_DESCRIPTOR_EX;

通过这几个描述符,我们就定义了整个驱动的对象属性信息,AVStream框架就可以通过这些属性创建各自的设备对象,过滤器工厂,过滤器实例,引脚工厂,引脚实例等结构。

3. 分发表

在AVStrem架构中框架已经替我们做好了大部分的事情了,但是我们怎么和框架进行交互呢?这个就是分发表的作用,在每个描述符中都有一个KSXXX_DISPATCH定义着各种回调函数。

KSDEVICE_DISPATCH表示设备创建和PNP处理相关的回调接口,定义如下:

typedef struct _KSDEVICE_DISPATCH {
  PFNKSDEVICECREATE            Add;
  PFNKSDEVICEPNPSTART          Start;
  PFNKSDEVICE                  PostStart;
  PFNKSDEVICEIRP               QueryStop;
  PFNKSDEVICEIRPVOID           CancelStop;
  PFNKSDEVICEIRPVOID           Stop;
  PFNKSDEVICEIRP               QueryRemove;
  PFNKSDEVICEIRPVOID           CancelRemove;
  PFNKSDEVICEIRPVOID           Remove;
  PFNKSDEVICEQUERYCAPABILITIES QueryCapabilities;
  PFNKSDEVICEIRPVOID           SurpriseRemoval;
  PFNKSDEVICEQUERYPOWER        QueryPower;
  PFNKSDEVICESETPOWER          SetPower;
  PFNKSDEVICEIRP               QueryInterface;
} KSDEVICE_DISPATCH, *PKSDEVICE_DISPATCH;

KSFILTER_DISPATCHKSPIN_DISPATCH分别表示过滤器和引脚相关的回调函数,定义如下:

typedef struct _KSFILTER_DISPATCH {
  PFNKSFILTERIRP     Create;
  PFNKSFILTERIRP     Close;
  PFNKSFILTERPROCESS Process;
  PFNKSFILTERVOID    Reset;
} KSFILTER_DISPATCH, *PKSFILTER_DISPATCH;

typedef struct _KSPIN_DISPATCH {
  PFNKSPINIRP                Create;
  PFNKSPINIRP                Close;
  PFNKSPIN                   Process;
  PFNKSPINVOID               Reset;
  PFNKSPINSETDATAFORMAT      SetDataFormat;
  PFNKSPINSETDEVICESTATE     SetDeviceState;
  PFNKSPIN                   Connect;
  PFNKSPINVOID               Disconnect;
  const KSCLOCK_DISPATCH     *Clock;
  const KSALLOCATOR_DISPATCH *Allocator;
} KSPIN_DISPATCH, *PKSPIN_DISPATCH;

通过上面KSFILTER_DISPATCHKSPIN_DISPATCH的定义,我们可以发现两个分发表中都有一个Process处理函数,那么具体处理数据的是哪一个呢?这里就涉及到数据的两种处理模式:

  1. 基于过滤器的处理模式。
  2. 基于引脚的处理模式。

在我们的实际情况中,我们很少使用基于过滤器的处理模式,都是在各自的引脚中处理其数据。

4. KSOBJECT_BAG

KSOBJECT_BAG是用来管理对象的一个容器,在上述的KSDEVICEKSFILTERFACTORYKSFILTERKSPIN结构中都存在KSOBJECT_BAG,我们以其中一个为例,如下:

typedef struct _KSDEVICE {
  const KSDEVICE_DESCRIPTOR *Descriptor;
  KSOBJECT_BAG              Bag;
  PVOID                     Context;
  PDEVICE_OBJECT            FunctionalDeviceObject;
  PDEVICE_OBJECT            PhysicalDeviceObject;
  PDEVICE_OBJECT            NextDeviceObject;
  BOOLEAN                   Started;
  SYSTEM_POWER_STATE        SystemPowerState;
  DEVICE_POWER_STATE        DevicePowerState;
} KSDEVICE, *PKSDEVICE;

我们使用KsAddItemToObjectBag将对象添加到KSOBJECT_BAG,并且指定释放函数,当KSOBJECT_BAG被删除的时候,释放函数就会自动调用,该函数声明如下:

KSDDKAPI NTSTATUS KsAddItemToObjectBag(
  KSOBJECT_BAG           ObjectBag,
  __drv_aliasesMem PVOID Item,
  PFNKSFREE              Free
);

一般来说我们可以使用如下:

Status = KsAddItemToObjectBag(Pin->Bag, 
    reinterpret_cast <PVOID> (CapPin),
    reinterpret_cast <PFNKSFREE>(CCapturePin::Cleanup));

5. StreamPoint

当我们用户层发起读取(写入)的IRP请求之后,AVStream就会将IRP分解成多个数据帧,然后放入队列,并且使用流指针来指向数据帧的抽象,整个框架如下:
在这里插入图片描述

如上图,整个队列中存在一个头和尾的指针,分别使用如下函数获取:

KSDDKAPI PKSSTREAM_POINTER KsPinGetLeadingEdgeStreamPointer(
  [in] PKSPIN                 Pin,
  [in] KSSTREAM_POINTER_STATE State
);

KSDDKAPI PKSSTREAM_POINTER KsPinGetTrailingEdgeStreamPointer(
  [in] PKSPIN                 Pin,
  [in] KSSTREAM_POINTER_STATE State
);

KSSTREAM_POINTER描述着数据帧的情况如下:

typedef struct _KSSTREAM_POINTER {
  PVOID                    Context;
  PKSPIN                   Pin;
  PKSSTREAM_HEADER         StreamHeader;
  PKSSTREAM_POINTER_OFFSET Offset;
  KSSTREAM_POINTER_OFFSET  OffsetIn;
  KSSTREAM_POINTER_OFFSET  OffsetOut;
} KSSTREAM_POINTER, *PKSSTREAM_POINTER;

typedef struct {
  ULONG    Size;
  ULONG    TypeSpecificFlags;
  KSTIME   PresentationTime;
  LONGLONG Duration;
  ULONG    FrameExtent;
  ULONG    DataUsed;
  PVOID    Data;
  ULONG    OptionsFlags;
  ULONG    Reserved;
} KSSTREAM_HEADER, *PKSSTREAM_HEADER;

其中KSSTREAM_HEADER表示数据帧,里面有数据的地址和其他信息。对于KSSTREAM_POINTER指向的数据,我们可以分多次处理,如果我们处理了部分数据,那么就需要对KSSTREAM_POINTER_OFFSET进行处理,如下:

KSDDKAPI NTSTATUS KsStreamPointerAdvanceOffsets(
  [in] PKSSTREAM_POINTER StreamPointer,
  [in] ULONG             InUsed,
  [in] ULONG             OutUsed,
  [in] BOOLEAN           Eject
);

6. 实现

虚拟摄像头的开发大致可以分为三个部分:

  1. 整个驱动框架的处理。
  2. 各个描述符的定义。
  3. 数据的模拟处理。

下面分别看一下这三个模块的实现过程。

6.1 框架实现

基于AVStream框架实现的虚拟摄像头实现特别简单,使用KsInitializeDriver初始化驱动即可,如下:

extern "C"
NTSTATUS
DriverEntry(
	IN PDRIVER_OBJECT DriverObject,
	IN PUNICODE_STRING RegistryPath
)
{
	return KsInitializeDriver(DriverObject, RegistryPath, &CaptureDeviceDescriptor);
}

这里我们只需要提供好设备描述符信息即可,我们定义设备描述符如下:

const
KSDEVICE_DESCRIPTOR
CaptureDeviceDescriptor = 
{
	&CaptureDeviceDispatch,
	0,
	NULL
};

6.2 描述符的实现

我们在设备启动IRP_MN_START_DEVICE的回调函数CCaptureDevice::PnpStart中,动态创建过滤器工厂,代码如下:

Status = KsCreateFilterFactory(m_Device->FunctionalDeviceObject,
	&CaptureFilterDescriptor,
	L"GLOBAL",
	NULL,
	KSCREATE_ITEM_FREEONSTOP,
	NULL,
	NULL,
	NULL);

这里我们只需要提供过滤器工厂的描述符,框架就会完成其他工作了,描述定义如下:

const
KSFILTER_DESCRIPTOR
CaptureFilterDescriptor = 
{
	&CaptureFilterDispatch,                 // Dispatch Table
	NULL,                                   // Automation Table
	KSFILTER_DESCRIPTOR_VERSION,            // Version
	0,                                      // Flags
	&KSNAME_Filter,                         // Reference GUID
	DEFINE_KSFILTER_PIN_DESCRIPTORS(CaptureFilterPinDescriptors),
	DEFINE_KSFILTER_CATEGORIES(CaptureFilterCategories),
	0,
	sizeof(KSNODE_DESCRIPTOR),
	NULL,
	0,
	NULL,
	NULL                                    // Component ID
};

6.3 数据处理

这里我们分析基于引脚的处理模型,在引脚中提供了分发函数如下:

const
KSPIN_DISPATCH
CapturePinDispatch = {
	CCapturePin::DispatchCreate,            // Pin Create
	NULL,                                   // Pin Close
	CCapturePin::DispatchProcess,           // Pin Process
	NULL,                                   // Pin Reset
	CCapturePin::DispatchSetFormat,         // Pin Set Data Format
	CCapturePin::DispatchSetState,          // Pin Set Device State
	NULL,                                   // Pin Connect
	NULL,                                   // Pin Disconnect
	NULL,                                   // Clock Dispatch
	NULL                                    // Allocator Dispatch
};

只需要在CCapturePin::DispatchProcess对数据帧进行图像数据提供即可,如下:

NTSTATUS
CCapturePin::Process(
)
{

	PAGED_CODE();

	NTSTATUS Status = STATUS_SUCCESS;
	PKSSTREAM_POINTER Leading;
	Leading = KsPinGetLeadingEdgeStreamPointer(m_Pin, KSSTREAM_POINTER_STATE_LOCKED);

	while (NT_SUCCESS(Status) && Leading) 
	{

		PKSSTREAM_POINTER ClonePointer;
		PSTREAM_POINTER_CONTEXT SPContext = NULL;

		if (NULL == Leading->StreamHeader->Data) 
		{
			Status = KsStreamPointerAdvance(Leading);
			continue;
		}

		if (!m_PreviousStreamPointer)
		{
			Status = KsStreamPointerClone(Leading,
				NULL,
				sizeof(STREAM_POINTER_CONTEXT),
				&ClonePointer);
			if (NT_SUCCESS(Status))
			{
				ClonePointer->StreamHeader->DataUsed = 0;
				SPContext = reinterpret_cast<PSTREAM_POINTER_CONTEXT>(ClonePointer->Context);
				SPContext->BufferVirtual = reinterpret_cast <PUCHAR>(ClonePointer->StreamHeader->Data);
			}
		}
		else 
		{
			ClonePointer = m_PreviousStreamPointer;
			SPContext = reinterpret_cast<PSTREAM_POINTER_CONTEXT>(ClonePointer->Context);
			Status = STATUS_SUCCESS;
		}

		if (!NT_SUCCESS(Status)) 
		{
			KsStreamPointerUnlock(Leading, FALSE);
			break;
		}
		ULONG MappingsUsed = m_Device->ProgramScatterGatherMappings(ClonePointer,
				&(SPContext->BufferVirtual),
				Leading->OffsetOut.Mappings,  
				Leading->OffsetOut.Remaining);  
		if (MappingsUsed == Leading->OffsetOut.Remaining) 
		{
			m_PreviousStreamPointer = NULL;
		}
		else 
		{
			m_PreviousStreamPointer = ClonePointer;
		}

		if (MappingsUsed) 
		{
			Status = KsStreamPointerAdvanceOffsets(Leading,
					0,
					MappingsUsed,
					FALSE);
		}
		else 
		{
			Status = STATUS_PENDING;
			break;
		}
	}

	if (!Leading) 
	{
		m_PendIo = TRUE;
		Status = STATUS_PENDING;
	}

	if (NT_SUCCESS(Status) && Leading)
	{
		KsStreamPointerUnlock(Leading, FALSE);
	}
	else 
	{
		if (Status == STATUS_DEVICE_NOT_READY)
		{
			Status = STATUS_SUCCESS;
		}
	}

	if (!NT_SUCCESS(Status) || Status == STATUS_PENDING)
	{
		m_PendIo = TRUE;
	}

	return Status;
}

7. 总结

在数据处理函数CCapturePin::Process中,我们提供自己的图像数据,就可以完成定制化的虚拟摄像头了,虚拟摄像头如下:
在这里插入图片描述

可以使用GraphEdit来渲染虚拟摄像头的数据,如下:
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值