- 示例工程
【免费下载链接】Windows-driver-samples
This repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.
导读
本文以 avstream/samplemft0 为例,系统讲解 Windows 驱动开发者如何实现一个Driver MFT(又称 MFT0)——即摄像头视频捕获管线中第一个被应用的 Media Foundation Transform。读完本文,你将掌握 MFT0 的概念定位、面向 UWP 设备应用的控制接口设计、IMFTransform 全接口实现套路、视频帧逐行处理原理,以及如何将 MFT0 随驱动包分发并用于实现实时视频特效。
一、什么是 Driver MFT(MFT0)
在 Windows 10 及以上的摄像头驱动架构中,当应用通过 Media Foundation(MF)从摄像头捕获视频时,视频流会经过一串变换(Transform)。Driver MFT 是这一串变换中第一个被应用到摄像头视频流的 MFT,因此也常被称为MFT0。
- 它在设备驱动侧运行,随摄像头驱动包一起分发,属于“驱动扩展”而非纯应用层组件;
- 它可以在从摄像头捕获照片或视频时提供视频特效或其他图像处理,并且无需修改驱动本身;
- 它的启用、参数调节可以交由配套的UWP 设备应用(UWP device app)来控制。
在 README 中,官方给出的定位描述是:“A driver MFT is a Media Foundation Transform that's used with a specific camera when capturing video … It can be distributed along with the driver package for a camera.”也就是说,MFT0 是与某个特定摄像头绑定的,它只在系统识别到该摄像头并开始捕获时才被加载。
二、示例效果:绿色/纯色框替换视频局部
本示例的演示效果非常直观:当 MFT0 被启用时,它会把捕获视频的一部分替换为一个纯色框(README 描述为 green box)。剩余画面原样透传,从而证明:
- MFT0 已成功挂接到摄像头捕获管线;
- MFT0 有能力对每一帧视频数据做实际的像素级改写;
- 特效的开启与范围可以由外部(UWP 设备应用)动态控制。
从源码看,这一效果在CMft0::OnProcessOutput中实现(Mft0.cpp):输入帧与输出帧分别被锁定后,按行复制像素,凡位于lines阈值之下的行(即画面底部区域)用memset填零,形成一个纯色块;其余行memcpy原样拷贝。lines由成员变量m_percentOfScreen(默认 50,即画面 50%)计算:
long lines = uiHeight; if(m_percentOfScreen != -1 && m_percentOfScreen != 0) { lines = (UINT)(uiHeight * (1.0 - m_percentOfScreen / 100.00)); }注意:当m_percentOfScreen为-1或0时lines = uiHeight,整帧被完整透传,相当于特效关闭;因此 UWP 设备应用可以通过把该值设为 0 来“消除”遮挡。框的“颜色”对应零值填充数据,开发者可自行替换为任意 RGB 填充逻辑以实现真正的绿色或其他颜色框。
三、运行与测试的前提条件
README 明确列出,要真正运行本示例,需要以下配套条件(缺一不可):
| 前提 | 说明 |
|---|---|
| 配套 UWP 设备应用(Device app for camera sample) | 提供 UWP 设备应用,用于控制 Driver MFT 实现的特效(即调用本文第五节介绍的 IMft0 控制接口) |
| 摄像头捕获 UI 示例(Camera Capture UI sample) | 提供一种方式去调用上述 UWP 设备应用,从而在真实拍摄界面里看到特效开关效果 |
| 特定摄像头的设备 ID | 本示例“绑定”特定相机,运行时需要相机的设备 ID 才能把 MFT0 关联到该设备 |
| 设备元数据包(device metadata package) | 用于把 UWP 设备应用与特定摄像头关联起来,是设备应用被系统按设备调用的关键 |
也就是说,MFT0 本身无法独立演示——它必须与 UWP 设备应用、捕获 UI 以及一台具体摄像头(或对应的模拟环境)组成完整链路。构建与部署时,还需将生成的 DLL 放进相机的驱动包(见下文打包章节)。
四、工程结构与构建方式
4.1 文件清单
avstream/samplemft0/目录下各文件职责如下:
| 文件 | 职责 |
|---|---|
| Mft0.cpp / Mft0.h | CMft0类的全部实现:IMFTransform 各方法、IMft0 控制方法、图像处理核心 |
| SampleMft0.idl | MIDL 接口定义:声明IMft0控制接口与Mft0coclass、类型库 GUID |
| SampleMft0.cpp | DLL 导出入口:DllCanUnloadNow、DllGetClassObject、DllRegisterServer等 |
| SampleMft0.def | 模块导出表,声明上述 5 个 DLL 导出函数 |
| SampleMft0.rgs / Mft0.rgs | ATL 注册脚本,写入 COM CLSID、InprocServer32、ThreadingModel 等注册表项 |
| dllmain.cpp / dllmain.h | DllMain与 ATL 模块类CSampleMft0Module |
| SampleHelpers.h | 公共辅助:错误检查宏、VideoBufferLock缓冲锁定工具类 |
| SampleMft0.vcxproj | 主工程,产出SampleMft0.dll |
| Package/package.vcxproj | 打包工程,产出驱动安装包 |
| SampleMft0.sln | 解决方案,包含上述两个工程 |
4.2 构建配置要点
从 SampleMft0.vcxproj 可以看到:
- 使用 WDK 的
WindowsApplicationForDrivers10.0工具集,ConfigurationType为DynamicLibrary(生成 DLL); - 支持x64 与 ARM64两种平台,Debug/Release 均以Windows 10为目标版本;
- 输出目标名
TargetName为SampleMft0,即最终产物为SampleMft0.dll。
Package/package.vcxproj 则以WindowsKernelModeDriver10.0工具集、DriverType为Package的 Utility 工程形式,将生成的 DLL 与驱动包元数据一起封装,便于随相机驱动包分发。这正对应 README 中“driver MFT 可以随相机驱动包一起分发”的定位。
五、核心源码剖析
5.1 COM 对象模型:一个类,三重身份
CMft0(Mft0.h)基于 ATL 实现,同时实现三个接口:
class ATL_NO_VTABLE CMft0 : public CComObjectRootEx<CComMultiThreadModel>, public CComCoClass<CMft0, &CLSID_Mft0>, public IMft0, // 面向 UWP 设备应用的控制接口 public IMFTransform, // Media Foundation Transform 核心接口 public IInspectable // WinRT 可检查性接口- IMFTransform:让对象成为标准 MFT,被媒体管线按 Transform 协议调用;
- IMft0:示例自定义的控制接口,供 UWP 设备应用调用(见 5.2);
- IInspectable:使对象可作为 WinRT 对象被元数据检查(
GetIids、GetRuntimeClassName、GetTrustLevel,其中信任级别返回BaseTrust,见 Mft0.cpp)。
构造函数初始化了关键状态:m_bEnableEffects = TRUE、m_percentOfScreen = 50,并创建临界区m_critSec保护所有状态,所有核心方法都通过EnterCriticalSection/LeaveCriticalSection保证线程安全。
5.2 控制接口 IMft0:UWP 设备应用如何遥控特效
SampleMft0.idl 定义了四个方法,其语义与实现(Mft0.cpp)一一对应:
interface IMft0 : IUnknown{ [id(1)] HRESULT UpdateDsp([in] UINT32 uiPercentOfScreen); [id(2)] HRESULT Enable(void); [id(3)] HRESULT Disable(void); [id(4)] HRESULT GetDspSetting([out] UINT* puiPercentOfScreen, [out] BOOL* pIsEnabled); };| 方法 | 作用 | 实现要点 |
|---|---|---|
UpdateDsp(uiPercentOfScreen) | 设置特效覆盖屏幕的比例(0~100,0 或 -1 表示全透传) | 直接写入m_percentOfScreen |
Enable() | 开启特效 | 置m_bEnableEffects = TRUE |
Disable() | 关闭特效 | 置m_bEnableEffects = FALSE |
GetDspSetting(...) | 查询当前比例与开关状态 | 同时回读m_percentOfScreen与m_bEnableEffects |
这就是 README 所说“UWP 设备应用控制 Driver MFT 实现的特效”的底层通道:设备应用拿到 MFT0 对象后,通过这 4 个方法即可实时调整画面中色块的大小和开关,无需重启捕获会话。
5.3 流模型:固定 1 进 1 出
CMft0是一个最简单的单进单出 MFT:
GetStreamLimits返回输入/输出均为最小 1、最大 1(Mft0.cpp);GetStreamCount同样返回 1 进 1 出;GetStreamIDs直接返回E_NOTIMPL,表示流 ID 固定为 0(按 MFT 协议,单流 MFT 可省略流 ID);IsValidInputStream/IsValidOutputStream均只接受dwInputStreamID == 0。
输入流信息声明了MFT_INPUT_STREAM_WHOLE_SAMPLES | MFT_INPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER,输出流声明了MFT_OUTPUT_STREAM_WHOLE_SAMPLES | MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER | MFT_OUTPUT_STREAM_PROVIDES_SAMPLES | MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE——即每个样本携带一整帧,且 MFT 自行提供输出样本(见 GetInputStreamInfo 与 GetOutputStreamInfo)。
5.4 与底层相机源的绑定
MFT0 之所以能拿到“这个摄像头支持的媒体类型”,关键在于它通过输入流属性反向找到底层相机源:
- 从输入属性中读取
MFT_CONNECTED_STREAM_ATTRIBUTE(Mft0.cpp); - 再从该属性中取出
MF_DEVICESTREAM_EXTENSION_PLUGIN_CONNECTION_POINT,QueryInterface得到IMFTransform类型的m_pSourceTransform——即相机的源变换; - 读取
MF_DEVICESTREAM_STREAM_CATEGORY判断流类型:代码打印PINNAME_VIDEO_CAPTURE/PINNAME_VIDEO_PREVIEW/PINNAME_VIDEO_STILL/PINNAME_IMAGE,并且只接受PINNAME_VIDEO_PREVIEW或PINNAME_VIDEO_CAPTURE(其他类型返回E_UNEXPECTED); - 调用
GenerateMFMediaTypeListFromDevice(Mft0.cpp)遍历源变换的所有GetOutputAvailableType,筛出可处理的视频格式后存入m_listOfMediaTypes。
GetInputAvailableType/GetOutputAvailableType之后都从m_listOfMediaTypes返回候选媒体类型(GetMediaType,Mft0.cpp)。SetInputType则通过IsMediaTypeSupported校验(支持MFT_SET_TYPE_TEST_ONLY试设模式),并把完整媒体类型同时缓存为输入/输出类型。
5.5 数据流:ProcessInput → ProcessOutput
处理流程是 MFT 的教科书式实现:
- ProcessInput(Mft0.cpp):校验流 ID、校验类型已设置(否则
MF_E_NOTACCEPTING)、校验缓冲数(0 个返回E_FAIL,多于 1 个返回MF_E_SAMPLE_HAS_TOO_MANY_BUFFERS),然后缓存输入样本m_pSample并 AddRef; - GetInputStatus / GetOutputStatus(Mft0.cpp):以
m_pSample是否为 NULL 作为是否可接受输入/是否有输出就绪的唯一判据; - ProcessOutput(Mft0.cpp):无缓存样本时返回
MF_E_TRANSFORM_NEED_MORE_INPUT;否则创建输出样本、把输入/输出缓冲转成连续缓冲,调用OnProcessOutput做像素处理,并将输入样本的时间戳与时长拷贝到输出样本后释放输入样本; - ProcessMessage(Mft0.cpp):
MFT_MESSAGE_COMMAND_FLUSH触发OnFlush(释放缓存样本),MFT_MESSAGE_COMMAND_DRAIN无需额外动作,MFT_MESSAGE_SET_D3D_MANAGER返回E_NOTIMPL(未声明 D3D 感知)。
5.6 像素级处理:帧格式、stride 与 VideoBufferLock
OnProcessOutput只在m_bEnableEffects为 TRUE 且子类型属于受支持的 29 种视频格式时才做处理,格式清单涵盖:
- RGB 系:
RGB8、RGB555、RGB565、RGB24、RGB32、ARGB32、AI44; - YUV 平面/半平面:
I420、IYUV、NV11、NV12、YV12、P010、P016、P210、P216; - YUV 打包/交织:
AYUV、UYVY、Y41P、Y41T、Y42T、YUY2、v210、v216、v410、Y210、Y216、Y410、Y416。
(完整条件见 Mft0.cpp 与 GenerateMFMediaTypeListFromDevice。)
行步长(stride)的处理是本例最值得借鉴的部分:
GetDefaultStride(Mft0.cpp)优先读取MF_MT_DEFAULT_STRIDE;未设置时用MFGetStrideForBitmapInfoHeader按子类型与宽度计算,并回写属性缓存;- SampleHelpers.h 中的
VideoBufferLock封装了缓冲锁定:优先QueryInterface到IMF2DBuffer走Lock2D;否则走IMFMediaBuffer::Lock,并处理bottom-up(负 stride,返回内存中最后一行即图像顶行)与 top-down 两种布局; OnProcessOutput按行memcpy上半部分、memset下半部分,同时正确处理负 stride 场景(Mft0.cpp)。
这一套“默认 stride 计算 + 2D 缓冲回退 + 方向处理”的模式,是编写任何逐像素 MFT 的通用基础设施。
5.7 全局属性:异步关闭、动态格式更改与硬件 URL
GetAttributes(Mft0.cpp)创建并返回三个关键属性:
| 属性 | 值 | 含义 |
|---|---|---|
MF_TRANSFORM_ASYNC | FALSE | MFT 为同步处理模式,管线按传统 ProcessInput/ProcessOutput 轮询 |
MFT_ENUM_HARDWARE_URL_Attribute | L"Sample_CameraExtensionMft" | 硬件 MFT 枚举 URL,供系统按名称识别/枚举该扩展 MFT |
MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE | TRUE | 支持动态格式更改(配合SetInputType的校验逻辑) |
输入流属性同样携带MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE与相同的硬件 URL(Mft0.cpp)。需要说明的是,虽然示例在全局/输入属性中声明支持动态格式更改,SetOutputType只是转发到SetInputType,且DeleteInputStream/AddInputStreams均返回E_NOTIMPL——流数量固定,不允许动态增删。
六、COM 注册与驱动打包
- Mft0.rgs 注册
CLSID {7BB640D9-33A4-4759-B290-F41A31DCF848}(Mft0 Class),InprocServer32指向%MODULE%,ThreadingModel为Both,并关联类型库{DE05674A-C564-4C0E-9B7C-E1519F7AA767}(SampleMft0Libv1.0); - 类型库与 coclass 定义见 SampleMft0.idl,
OBJECT_ENTRY_AUTO(Mft0.h)让 ATL 在DllRegisterServer时自动写入这些项; - SampleMft0.def 仅导出 5 个标准 COM 入口;
DllRegisterServer/DllUnregisterServer/DllInstall实现在 SampleMft0.cpp。
部署层面,编译出的SampleMft0.dll通过 Package 工程(DriverType=Package)与相机驱动包一起封装、签名并分发——这正是 README 强调的“随驱动包分发”能力。在实际驱动安装时,还需要在设备的驱动扩展配置(inf 中引用 MFT0 CLSID/硬件 URL)中声明该 MFT,MFT0 才会在该相机捕获时被管线加载。
七、测试链路与配套示例
按照 README 的说明,验证本示例的完整链路为:
- 构建
SampleMft0.sln,得到SampleMft0.dll并随驱动包安装到目标相机; - 部署Device app for camera sample(UWP 设备应用):它在拍摄界面里通过
IMft0调用Enable/Disable/UpdateDsp/GetDspSetting,实现对特效的实时控制; - 运行Camera Capture UI sample:它作为宿主捕获界面调用 UWP 设备应用,让用户能直观看到“画面中一部分被色块替换/恢复”的效果;
- 前提是拥有该相机的设备 ID与设备元数据包,系统才会把 UWP 设备应用与这台相机、这个 MFT0 关联起来。
在调试阶段,代码内置了流类型打印(wprintf(L"Stream type: PINNAME_VIDEO_CAPTURE\n")等)与CHK_LOG_BRK错误宏(SampleHelpers.h 输出HR=%08x File: %S Ln: %d),配合调试器即可确认 MFT0 是否被正确挂接、媒体类型协商是否成功。
八、相关主题与延伸阅读
- 摄像头 UWP 设备应用设计:Device app for cameras(本示例配套设备应用的控制主体);
- Media Foundation Transforms:
IMFTransform接口与 MFT 处理协议(本文 5.3~5.5 节即其实战实现); - Streaming media device driver design guide:摄像头驱动与流媒体设备驱动设计(对应本文 5.4 节“与底层源变换绑定”机制);
- Universal camera driver design guide:Windows 10 通用摄像头驱动设计,解释了 MFT0 在通用相机驱动架构中的挂载位置。
如需深入,可继续阅读本仓库中avstream/samplemft0/目录下的 Mft0.cpp、Mft0.h、SampleMft0.idl 与 SampleHelpers.h,对照本文各节即可获得完整的源码级证据链。
- 示例工程
【免费下载链接】Windows-driver-samples
This repo contains driver samples prepared for use with Microsoft Visual Studio and the Windows Driver Kit (WDK). It contains both Universal Windows Driver and desktop-only driver samples.
相关推荐
Stoic模型数据预处理:如何准备FASTA文件进行化学计量预测
Stoic模型数据预处理:如何准备FASTA文件进行化学计量预测 Stoic模型(HuggingFace镜像/PickyBinders)是一款强大的化学计量预测
Obsidian美化终极指南:从基础到高级的完整个性化方案
Obsidian美化终极指南:从基础到高级的完整个性化方案 Obsidian作为一款功能强大的Markdown笔记工具,其核心魅力不仅在于强大的知识管理能力,更
文档知识管理libusb摄像头设备通信:基于UVC协议的视频流采集方案
libusb摄像头设备通信:基于UVC协议的视频流采集方案 引言:突破USB摄像头开发的底层难题 你是否曾在开发USB摄像头应用时遭遇这些困境?调用系统API时
驱动开发系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考