本文提供有关框架服务器体系结构中自定义媒体源实现的信息。
AV 流和自定义媒体源选项
在决定如何在帧服务器体系结构中提供视频捕获流支持时,有两个主要选项:AV 流和自定义媒体源。
AV Stream 模型是使用 AV Stream 微型端口驱动程序(内核模式驱动程序)的标准相机驱动程序模型。 通常 AV Stream 驱动程序分为两个主要类别:基于 MIPI 的驱动程序和 USB 视频类驱动程序。
对于“自定义媒体源”选项,驱动程序模型可能是完全自定义的(专有),也可能基于非传统相机源(例如文件或网络源)。
AV 流驱动程序
AV 流驱动程序方法的主要好处是,PnP 和电源管理/设备管理已由 AV 流框架处理。
但是,这也意味着基础源必须是具有内核模式驱动程序的物理设备,才能与硬件进行交互。 对于 UVC 设备,Windows 提供内置的 UVC 1.5 类驱动程序,因此设备只需实现其固件即可。
对于基于 MIPI 的设备,供应商需要实现自己的 AV Stream 微型端口驱动程序。
自定义媒体源
对于设备驱动程序已可用(但不是 AV Stream 微型端口驱动程序)或使用非传统方式进行相机捕获的源,AV 流驱动程序可能不可行。 例如,通过网络连接的 IP 相机不适合 AV 流驱动程序模型。
在这种情况下,使用帧服务器模型的自定义媒体源将是一种替代方法。
特点 | 自定义媒体源 | 音视频流驱动程序 |
---|---|---|
PnP 和电源管理 | 必须由源代码和/或存根驱动程序实现 | 由 AV 流框架提供 |
用户模式插件 | 暂无 自定义媒体源包含特定于 OEM/IHV 的用户模式逻辑。 | 用于传统实现的 DMFT、平台 DMFT 和 MFT0 |
传感器组 | 已支持 | 已支持 |
相机规格 V2 | 已支持 | 已支持 |
相机配置文件 V1 | 不支持 | 已支持 |
自定义媒体源要求
通过引入 Windows 相机帧服务器服务(即 Frame Server),可以通过自定义媒体源完成这一操作。 这需要两个主要组件:
一个驱动程序包,其中包含用于注册或启用相机设备接口的桩驱动程序。
托管自定义媒体源的 COM DLL。
第一个要求是出于两个目的:
一个审核过程,以确保通过受信任的进程安装自定义媒体源(驱动程序包需要 WHQL 认证)。
支持标准即插即用 (PnP) 枚举和检测“相机”。
安全
Frame Server 的自定义媒体源在安全性方面不同于通用自定义媒体源,方式如下:
帧服务器自定义媒体源作为本地服务运行(不会与本地系统混淆);本地服务是 Windows 计算机上的低特权帐户。
帧服务器自定义媒体源在会话 0(系统服务会话)中运行,无法与用户桌面交互。
鉴于这些约束,Frame Server 自定义媒体源不得尝试访问文件系统和注册表的受保护部分。 通常,允许读取访问,但不允许写入访问。
性能
作为框架服务器模型的一部分,框架服务器将如何实例化自定义媒体源有两种情况:
在传感器组生成/发布期间。
在“相机”激活期间
传感器组生成通常在设备安装和/或电源周期期间完成。 鉴于此情况,我们强烈建议自定义媒体源在创建期间避免进行任何重大处理,并将任何此类活动推迟到 IMFMediaSource::Start 函数。 传感器组生成不会尝试启动自定义媒体源,只需查询各种可用的流/媒体类型和源/流属性信息。
存根驱动程序
驱动程序包和存根驱动程序有两个最低要求条件。
存根驱动程序可以使用 WDF(UMDF 或 KMDF)或 WDM 驱动程序模型编写。
驱动程序要求如下:
- 在KSCATEGORY_VIDEO_CAMERA类别下注册您的“相机”(自定义媒体来源)设备接口,以便能够对其进行枚举。
注释
若要允许旧版 DirectShow 应用程序的枚举,驱动程序还需要在 KSCATEGORY_VIDEO 和 KSCATEGORY_CAPTURE下注册。
- 在设备接口节点下添加注册表项(在驱动程序 INF DDInstall.Interface 节中使用 AddReg 指令),该节声明自定义媒体源 COM 对象的 CoCreate-able CLSID。 必须使用以下注册表值名称添加此值: CustomCaptureSourceClsid。
这使得应用程序能够发现“相机”源,并在激活时通知 Frame Server 服务,服务会截获激活调用并将其重新路由到 CoCreated 自定义媒体源。
示例 INF
以下示例展示了自定义媒体源存根驱动程序的典型 INF:
;/*++
;
;Module Name:
; SimpleMediaSourceDriver.INF
;
;Abstract:
; INF file for installing the Usermode SimpleMediaSourceDriver Driver
;
;Installation Notes:
; Using Devcon: Type "devcon install SimpleMediaSourceDriver.inf root\SimpleMediaSource" to install
;
;--*/
[Version]
Signature="$WINDOWS NT$"
Class=Sample
ClassGuid={5EF7C2A5-FF8F-4C1F-81A7-43D3CBADDC98}
Provider=%ProviderString%
DriverVer=01/28/2016,0.10.1234
CatalogFile=SimpleMediaSourceDriver.cat
PnpLockdown=1
[DestinationDirs]
DefaultDestDir = 13
UMDriverCopy=13 ; copy to DriverStore
CustomCaptureSourceCopy=13
; ================= Class section =====================
[ClassInstall32]
Addreg=SimpleMediaSourceClassReg
[SimpleMediaSourceClassReg]
HKR,,,0,%ClassName%
HKR,,Icon,,-24
[SourceDisksNames]
1 = %DiskId1%,,,""
[SourceDisksFiles]
SimpleMediaSourceDriver.dll = 1,,
SimpleMediaSource.dll = 1,,
;*****************************************
; SimpleMFSource Install Section
;*****************************************
[Manufacturer]
%StdMfg%=Standard,NTamd64.10.0...25326
[Standard.NTamd64.10.0...25326]
%SimpleMediaSource.DeviceDesc%=SimpleMediaSourceWin11, root\SimpleMediaSource
;---------------- copy files
[SimpleMediaSourceWin11.NT]
Include=wudfrd.inf
Needs=WUDFRD.NT
CopyFiles=UMDriverCopy, CustomCaptureSourceCopy
AddReg = CustomCaptureSource.ComRegistration
[SimpleMediaSourceWin11.NT.Interfaces]
AddInterface = %KSCATEGORY_VIDEO_CAMERA%, %CustomCaptureSource.ReferenceString%, CustomCaptureSourceInterface
AddInterface = %KSCATEGORY_VIDEO%, %CustomCaptureSource.ReferenceString%, CustomCaptureSourceInterface
AddInterface = %KSCATEGORY_CAPTURE%, %CustomCaptureSource.ReferenceString%, CustomCaptureSourceInterface
[CustomCaptureSourceInterface]
AddReg = CustomCaptureSourceInterface.AddReg, CustomCaptureSource.ComRegistration
[CustomCaptureSourceInterface.AddReg]
HKR,,CLSID,,%ProxyVCap.CLSID%
HKR,,CustomCaptureSourceClsid,,%CustomCaptureSource.CLSID%
HKR,,FriendlyName,,%CustomCaptureSource.Desc%
[CustomCaptureSource.ComRegistration]
HKR,Classes\CLSID\%CustomCaptureSource.CLSID%,,,%CustomCaptureSource.Desc%
HKR,Classes\CLSID\%CustomCaptureSource.CLSID%\InprocServer32,,%REG_EXPAND_SZ%,%CustomCaptureSource.Location%
HKR,Classes\CLSID\%CustomCaptureSource.CLSID%\InprocServer32,ThreadingModel,,Both
[UMDriverCopy]
SimpleMediaSourceDriver.dll,,,0x00004000 ; COPYFLG_IN_USE_RENAME
[CustomCaptureSourceCopy]
SimpleMediaSource.dll,,,0x00004000 ; COPYFLG_IN_USE_RENAME
;-------------- Service installation
[SimpleMediaSourceWin11.NT.Services]
Include=wudfrd.inf
Needs=WUDFRD.NT.Services
;-------------- WDF specific section -------------
[SimpleMediaSourceWin11.NT.Wdf]
UmdfService=SimpleMediaSource, SimpleMediaSource_Install
UmdfServiceOrder=SimpleMediaSource
[SimpleMediaSource_Install]
UmdfLibraryVersion=$UMDFVERSION$
ServiceBinary=%13%\SimpleMediaSourceDriver.dll
[Strings]
ProviderString = "Microsoft Corporation"
StdMfg = "(Standard system devices)"
DiskId1 = "SimpleMediaSource Disk \#1"
SimpleMediaSource.DeviceDesc = "SimpleMediaSource Capture Source" ; what you will see under SimpleMediaSource dummy devices
ClassName = "SimpleMediaSource dummy devices" ; device type this driver will install as in device manager
WudfRdDisplayName="Windows Driver Foundation - User-mode Driver Framework Reflector"
KSCATEGORY_VIDEO_CAMERA = "{E5323777-F976-4f5b-9B55-B94699C46E44}"
KSCATEGORY_CAPTURE="{65E8773D-8F56-11D0-A3B9-00A0C9223196}"
KSCATEGORY_VIDEO="{6994AD05-93EF-11D0-A3CC-00A0C9223196}"
ProxyVCap.CLSID="{17CCA71B-ECD7-11D0-B908-00A0C9223196}"
CustomCaptureSource.Desc = "SimpleMediaSource Source"
CustomCaptureSource.ReferenceString = "CustomCameraSource"
CustomCaptureSource.CLSID = "{9812588D-5CE9-4E4C-ABC1-049138D10DCE}"
CustomCaptureSource.Location = "%13%\SimpleMediaSource.dll"
CustomCaptureSource.Binary = "SimpleMediaSource.dll"
REG_EXPAND_SZ = 0x00020000
上述自定义媒体源在 KSCATEGORY_VIDEO、KSCATEGORY_CAPTURE 和 KSCATEGORY_VIDEO_CAMERA 下进行注册,以确保任何 UWP 和非 UWP 应用在搜索标准 RGB 相机时能够发现“相机”。
如果自定义媒体源还公开非 RGB 流(如 IR、深度等),则也可以选择在 KSCATEGORY_SENSOR_CAMERA 下进行注册。
注释
大多数基于 USB 的网络摄像头都公开 YUY2 和 MJPG 格式。 由于此行为,许多旧版 DirectShow 应用程序都以 YUY2/MJPG 可用为假设编写。 为了确保与此类应用程序的兼容性,如果需要旧版应用兼容性,建议从自定义媒体源获取 YUY2 媒体类型。
桩驱动程序实现
除了 INF,驱动程序存根还必须注册并启用相机设备接口。 这通常在 DRIVER_ADD_DEVICE 操作期间完成。
请参阅基于 WDM 的驱动程序的 DRIVER_ADD_DEVICE 回调函数和 UMDF/KMDF 驱动程序的 WdfDriverCreate 函数。
下面是处理此操作的 UMDF 驱动程序存根的代码片段:
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
/*++
Routine Description:
DriverEntry initializes the driver and is the first routine called by the
system after the driver is loaded. DriverEntry specifies the other entry
points in the function driver, such as EvtDevice and DriverUnload.
Parameters Description:
DriverObject - represents the instance of the function driver that is loaded
into memory. DriverEntry must initialize members of DriverObject before it
returns to the caller. DriverObject is allocated by the system before the
driver is loaded, and it is released by the system after the system unloads
the function driver from memory.
RegistryPath - represents the driver specific path in the Registry.
The function driver can use the path to store driver related data between
reboots. The path does not store hardware instance specific data.
Return Value:
STATUS_SUCCESS if successful,
STATUS_UNSUCCESSFUL otherwise.
--*/
{
WDF_DRIVER_CONFIG config;
NTSTATUS status;
WDF_DRIVER_CONFIG_INIT(&config,
EchoEvtDeviceAdd
);
status = WdfDriverCreate(DriverObject,
RegistryPath,
WDF_NO_OBJECT_ATTRIBUTES,
&config,
WDF_NO_HANDLE);
if (!NT_SUCCESS(status)) {
KdPrint(("Error: WdfDriverCreate failed 0x%x\n", status));
return status;
}
// ...
return status;
}
NTSTATUS
EchoEvtDeviceAdd(
IN WDFDRIVER Driver,
IN PWDFDEVICE_INIT DeviceInit
)
/*++
Routine Description:
EvtDeviceAdd is called by the framework in response to AddDevice
call from the PnP manager. We create and initialize a device object to
represent a new instance of the device.
Arguments:
Driver - Handle to a framework driver object created in DriverEntry
DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure.
Return Value:
NTSTATUS
--*/
{
NTSTATUS status;
UNREFERENCED_PARAMETER(Driver);
KdPrint(("Enter EchoEvtDeviceAdd\n"));
status = EchoDeviceCreate(DeviceInit);
return status;
}
NTSTATUS
EchoDeviceCreate(
PWDFDEVICE_INIT DeviceInit
/*++
Routine Description:
Worker routine called to create a device and its software resources.
Arguments:
DeviceInit - Pointer to an opaque init structure. Memory for this
structure will be freed by the framework when the WdfDeviceCreate
succeeds. Do not access the structure after that point.
Return Value:
NTSTATUS
--*/
{
WDF_OBJECT_ATTRIBUTES deviceAttributes;
PDEVICE_CONTEXT deviceContext;
WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
WDFDEVICE device;
NTSTATUS status;
UNICODE_STRING szReference;
RtlInitUnicodeString(&szReference, L"CustomCameraSource");
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
//
// Register pnp/power callbacks so that we can start and stop the timer as the device
// gets started and stopped.
//
pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = EchoEvtDeviceSelfManagedIoStart;
pnpPowerCallbacks.EvtDeviceSelfManagedIoSuspend = EchoEvtDeviceSelfManagedIoSuspend;
#pragma prefast(suppress: 28024, "Function used for both Init and Restart Callbacks")
pnpPowerCallbacks.EvtDeviceSelfManagedIoRestart = EchoEvtDeviceSelfManagedIoStart;
//
// Register the PnP and power callbacks. Power policy related callbacks will be registered
// later in SoftwareInit.
//
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
{
WDF_FILEOBJECT_CONFIG cameraFileObjectConfig;
WDF_OBJECT_ATTRIBUTES cameraFileObjectAttributes;
WDF_OBJECT_ATTRIBUTES_INIT(&cameraFileObjectAttributes);
cameraFileObjectAttributes.SynchronizationScope = WdfSynchronizationScopeNone;
WDF_FILEOBJECT_CONFIG_INIT(
&cameraFileObjectConfig,
EvtCameraDeviceFileCreate,
EvtCameraDeviceFileClose,
WDF_NO_EVENT_CALLBACK);
WdfDeviceInitSetFileObjectConfig(
DeviceInit,
&cameraFileObjectConfig,
&cameraFileObjectAttributes);
}
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (NT_SUCCESS(status)) {
//
// Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
// inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
// device.h header file. This function will do the type checking and return
// the device context. If you pass a wrong object handle
// it will return NULL and assert if run under framework verifier mode.
//
deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
deviceContext->PrivateDeviceData = 0;
//
// Create a device interface so that application can find and talk
// to us.
//
status = WdfDeviceCreateDeviceInterface(
device,
&CAMERA_CATEGORY,
&szReference // ReferenceString
);
if (NT_SUCCESS(status)) {
//
// Create a device interface so that application can find and talk
// to us.
//
status = WdfDeviceCreateDeviceInterface(
device,
&CAPTURE_CATEGORY,
&szReference // ReferenceString
);
}
if (NT_SUCCESS(status)) {
//
// Create a device interface so that application can find and talk
// to us.
//
status = WdfDeviceCreateDeviceInterface(
device,
&VIDEO_CATEGORY,
&szReference // ReferenceString
);
}
if (NT_SUCCESS(status)) {
//
// Initialize the I/O Package and any Queues
//
status = EchoQueueInitialize(device);
}
}
return status;
}
PnP操作
与任何其他物理相机一样,建议随着移除/连接基础源时,虚拟驱动程序至少管理启用和禁用设备的 PnP操作。 例如,如果自定义媒体源正在使用网络源(例如 IP 相机),则当该网络源不再可用时,可能需要触发设备删除。
这可确保应用程序通过 PnP API 侦听设备添加/删除以获取正确的通知。 并确保无法枚举不再可用的源。
有关 UMDF 和 KMDF 驱动程序,请参阅 WdfDeviceSetDeviceState 函数文档。
有关 WMD 驱动程序,请参阅 IoSetDeviceInterfaceState 函数文档。
自定义媒体源 DLL
自定义媒体源是一个标准 inproc COM 服务器,必须实现以下接口:
注释
IMFMediaSourceEx 继承自 IMFMediaSource,IMFMediaSource 继承自 IMFMediaEventGenerator。
自定义媒体源中的每个受支持的流都必须支持以下接口:
IMFMediaEventGenerator
IMFMediaStream2
注释
IMFMediaStream2 继承自 IMFMediaStream,IMFMediaStream 继承自 IMFMediaEventGenerator。
请参阅有关创建自定义 媒体源 的文档。 本部分的其余部分介绍了在框架服务器框架中支持自定义媒体源所需的差异。
IMFGetService
IMFGetService 是帧服务器自定义媒体源的必需接口。 如果自定义媒体源不需要公开任何其他服务接口,IMFGetService 可能会返回MF_E_UNSUPPORTED_SERVICE。
以下示例演示没有支持服务接口的 IMFGetService 实现:
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::GetService(
_In_ REFGUID guidService,
_In_ REFIID riid,
_Out_ LPVOID * ppvObject
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
if (!ppvObject)
{
return E_POINTER;
}
*ppvObject = NULL;
// We have no supported service, just return
// MF_E_UNSUPPORTED_SERVICE for all calls.
return MF_E_UNSUPPORTED_SERVICE;
}
IMFMediaEventGenerator
如上所述,源中的源和单个流必须支持自己的 IMFMediaEventGenerator 接口。 通过发送特定的 IMFMediaEvent,通过事件生成器管理来自源的整个 MF 管道数据和控制流。
若要实现 IMFMediaEventGenerator,自定义媒体源必须使用 MFCreateEventQueue API 创建 IMFMediaEventQueue 并将 IMFMediaEventGenerator 的所有方法路由到队列对象:
IMFMediaEventGenerator 具有以下方法:
// IMFMediaEventGenerator
IFACEMETHOD(BeginGetEvent)(_In_ IMFAsyncCallback *pCallback, _In_ IUnknown *punkState);
IFACEMETHOD(EndGetEvent)(_In_ IMFAsyncResult *pResult, _COM_Outptr_ IMFMediaEvent **ppEvent);
IFACEMETHOD(GetEvent)(DWORD dwFlags, _Out_ IMFMediaEvent **ppEvent);
IFACEMETHOD(QueueEvent)(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, _In_opt_ const PROPVARIANT *pvValue);
以下代码显示了 IMFMediaEventGenerator 接口的建议实现。 自定义媒体源实现公开 IMFMediaEventGenerator 接口,该接口的方法将请求路由到媒体源创建/初始化期间创建的 IMFMediaEventQueue 对象。
在下面的代码中,_spEventQueue对象是使用 MFCreateEventQueue 函数创建的 IMFMediaEventQueue:
// IMFMediaEventGenerator methods
IFACEMETHODIMP
SimpleMediaSource::BeginGetEvent(
_In_ IMFAsyncCallback *pCallback,
_In_ IUnknown *punkState
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
RETURN_IF_FAILED (_spEventQueue->BeginGetEvent(pCallback, punkState));
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::EndGetEvent(
_In_ IMFAsyncResult *pResult,
_COM_Outptr_ IMFMediaEvent **ppEvent
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
RETURN_IF_FAILED (_spEventQueue->EndGetEvent(pResult, ppEvent));
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::GetEvent(
DWORD dwFlags,
_COM_Outptr_ IMFMediaEvent **ppEvent
)
{
// NOTE:
// GetEvent can block indefinitely, so we do not hold the lock.
// This requires some juggling with the event queue pointer.
HRESULT hr = S_OK;
ComPtr<IMFMediaEventQueue> spQueue;
{
auto lock = _critSec.Lock();
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
spQueue = _spEventQueue;
}
// Now get the event.
RETURN_IF_FAILED (spQueue->GetEvent(dwFlags, ppEvent));
return hr;
}
IFACEMETHODIMP
SimpleMediaSource::QueueEvent(
MediaEventType eventType,
REFGUID guidExtendedType,
HRESULT hrStatus,
_In_opt_ PROPVARIANT const *pvValue
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
RETURN_IF_FAILED (_spEventQueue->QueueEventParamVar(eventType, guidExtendedType, hrStatus, pvValue));
return hr;
}
查找与暂停
框架服务器系统支持的自定义媒体源不支持寻址或暂停操作。 自定义媒体源不需要为这些作提供支持,并且不得发布 MFSourceSeeked 或 MEStreamSeeked 事件。
IMFMediaSource::Pause 应返回 MF_E_INVALID_STATE_TRANSITION (或如果源已关闭,则返回 MF_E_SHUTDOWN)。
IKsControl
IKsControl 是所有相机相关控件的标准控制接口。 如果自定义媒体源实现任何相机控件, IKsControl 接口就是管道如何路由控件 I/O。
有关详细信息,请参阅以下控制集文档文章:
控件是可选的,如果不支持,建议返回的错误代码是HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND)。
以下代码是没有受支持的控件的示例 IKsControl 实现:
// IKsControl methods
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::KsProperty(
_In_reads_bytes_(ulPropertyLength) PKSPROPERTY pProperty,
_In_ ULONG ulPropertyLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pPropertyData,
_In_ ULONG ulDataLength,
_Out_ ULONG* pBytesReturned
)
{
// ERROR_SET_NOT_FOUND is the standard error code returned
// by the AV Stream driver framework when a miniport
// driver does not register a handler for a KS operation.
// We want to mimic the driver behavior here if we do not
// support controls.
return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND);
}
_Use_decl_annotations_
IFACEMETHODIMP SimpleMediaSource::KsMethod(
_In_reads_bytes_(ulMethodLength) PKSMETHOD pMethod,
_In_ ULONG ulMethodLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pMethodData,
_In_ ULONG ulDataLength,
_Out_ ULONG* pBytesReturned
)
{
return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND);
}
_Use_decl_annotations_
IFACEMETHODIMP SimpleMediaSource::KsEvent(
_In_reads_bytes_opt_(ulEventLength) PKSEVENT pEvent,
_In_ ULONG ulEventLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pEventData,
_In_ ULONG ulDataLength,
_Out_opt_ ULONG* pBytesReturned
)
{
return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND);
}
IMFMediaStream2
如编写自定义媒体源中所述,在IMFMediaSource::Start方法完成期间,通过发布到源事件队列的MENewStream媒体事件,将IMFMediaStream2接口提供给框架。
IFACEMETHODIMP
SimpleMediaSource::Start(
_In_ IMFPresentationDescriptor *pPresentationDescriptor,
_In_opt_ const GUID *pguidTimeFormat,
_In_ const PROPVARIANT *pvarStartPos
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
DWORD count = 0;
PROPVARIANT startTime;
BOOL selected = false;
ComPtr<IMFStreamDescriptor> streamDesc;
DWORD streamIndex = 0;
if (pPresentationDescriptor == nullptr || pvarStartPos == nullptr)
{
return E_INVALIDARG;
}
else if (pguidTimeFormat != nullptr && *pguidTimeFormat != GUID_NULL)
{
return MF_E_UNSUPPORTED_TIME_FORMAT;
}
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
if (_sourceState != SourceState::Stopped)
{
return MF_E_INVALID_STATE_TRANSITION;
}
_sourceState = SourceState::Started;
// This checks the passed in PresentationDescriptor matches the member of streams we
// have defined internally and that at least one stream is selected
RETURN_IF_FAILED (_ValidatePresentationDescriptor(pPresentationDescriptor));
RETURN_IF_FAILED (pPresentationDescriptor->GetStreamDescriptorCount(&count));
RETURN_IF_FAILED (InitPropVariantFromInt64(MFGetSystemTime(), &startTime));
// Send event that the source started. Include error code in case it failed.
RETURN_IF_FAILED (_spEventQueue->QueueEventParamVar(MESourceStarted,
GUID_NULL,
hr,
&startTime));
// We are hardcoding this to the first descriptor
// since this sample is a single stream sample. For
// multiple streams, we need to walk the list of streams
// and for each selected stream, send the MEUpdatedStream
// or MENewStream event along with the MEStreamStarted
// event.
RETURN_IF_FAILED (pPresentationDescriptor->GetStreamDescriptorByIndex(0,
&selected,
&streamDesc));
RETURN_IF_FAILED (streamDesc->GetStreamIdentifier(&streamIndex));
if (streamIndex >= NUM_STREAMS)
{
return MF_E_INVALIDSTREAMNUMBER;
}
if (selected)
{
ComPtr<IUnknown> spunkStream;
MediaEventType met = (_wasStreamPreviouslySelected ? MEUpdatedStream : MENewStream);
// Update our internal PresentationDescriptor
RETURN_IF_FAILED (_spPresentationDescriptor->SelectStream(streamIndex));
RETURN_IF_FAILED (_stream.Get()->SetStreamState(MF_STREAM_STATE_RUNNING));
RETURN_IF_FAILED (_stream.As(&spunkStream));
// Send the MEUpdatedStream/MENewStream to our source event
// queue.
RETURN_IF_FAILED (_spEventQueue->QueueEventParamUnk(met,
GUID_NULL,
S_OK,
spunkStream.Get()));
// But for our stream started (MEStreamStarted), we post to our
// stream event queue.
RETURN_IF_FAILED (_stream.Get()->QueueEvent(MEStreamStarted,
GUID_NULL,
S_OK,
&startTime));
}
_wasStreamPreviouslySelected = selected;
return hr;
}
必须针对通过 IMFPresentationDescriptor 选择的每个流执行此作。
对于包含视频流的自定义媒体源,不应发送 MEEndOfStream 和 MEEndOfPresentation 事件。
流特性
所有自定义媒体源流都必须将 MF_DEVICESTREAM_STREAM_CATEGORY 设置为 PINNAME_VIDEO_CAPTURE。 PINNAME_VIDEO_PREVIEW 不支持自定义媒体源。
注释
不建议使用PINNAME_IMAGE(虽然受支持)。 使用 PINNAME_IMAGE 公开流需要自定义媒体源支持所有照片触发控制功能。 有关详细信息,请参阅下面的 照片流控件 部分。
MF_DEVICESTREAM_STREAM_ID 是所有流的必需属性。 它应该是基于 0 的索引。 因此,第一个流的 ID 为 0,第二个流 ID 为 1,依此等。
以下是流上推荐的属性列表:
MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES
MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES 是一个 UINT32 属性,表示流类型的位掩码值。 它可能设置为以下任一类型(虽然这些类型是位掩码标志,但建议在可能的情况下不混合源类型):
类型 | 旗帜 | DESCRIPTION |
---|---|---|
MFFrameSourceTypes_Color | 0x0001 | 标准 RGB 颜色流 |
MFFrameSourceTypes_红外 | 0x0002 | IR 流 |
MFFrameSourceTypes_深度 | 0x0004 | 深度流 |
MFFrameSourceTypes_Image | 0x0008 | 图像流(非video 子类型,通常是 JPEG) |
MFFrameSourceTypes_Custom | 0x0080 | 自定义流类型 |
MF_DEVICESTREAM_FRAMESERVER_SHARED
MF_DEVICESTREAM_FRAMESERVER_SHARED 是一个 UINT32 属性,可以设置为 0 或 1。 如果设置为 1,则它将流标记为“可共享”的帧服务器。 这允许应用程序以共享模式打开流,即使其他应用使用也是如此。
如果未设置此属性,则 Frame Server 允许共享第一个非标记流(如果自定义媒体源只有一个流,该流被标记为共享)。
如果此属性设置为 0,则 Frame Server 会阻止来自共享应用的流。 如果自定义媒体源将所有流的此属性设置为 0,则任何共享应用程序都无法初始化源。
示例分配
所有媒体帧都必须作为 IMFSample 生成。 自定义媒体源必须使用 MFCreateSample 函数来分配 IMFSample 的实例,并使用 AddBuffer 方法添加媒体缓冲区。
每个 IMFSample 必须设置样本时间和样本持续时间。 所有示例时间戳都必须基于 QPC 时间(QueryPerformanceCounter)。
建议自定义媒体源尽可能使用 MFGetSystemTime 函数。 此函数是对 QueryPerformanceCounter 的封装,并将 QPC 计时周期转换为 100 纳秒单位。
自定义媒体源可能使用内部时钟,但所有时间戳都必须与基于当前 QPC 的 100 纳秒单位相关联。
媒体缓冲区
添加到 IMFSample 的所有媒体缓冲区都必须使用标准 MF 缓冲区分配函数。 自定义媒体源不得实现自己的 IMFMediaBuffer 接口或尝试直接分配媒体缓冲区(例如,new/malloc/VirtualAlloc 等),不得用于帧数据。
使用以下任一 API 分配媒体帧:
MFCreateMemoryBuffer 和 MFCreateAlignedMemoryBuffer 应用于非字符串对齐媒体数据。 通常这些是自定义子类型或压缩子类型(如 H264/HEVC/MJPG)。
对于使用系统内存的已知未压缩媒体类型(如 YUY2、NV12 等),建议使用 MFCreate2DMediaBuffer。
若要使用 DX 图面(用于 GPU 加速操作(如呈现和/或编码),应使用 MFCreateDXGISurfaceBuffer)。
MFCreateDXGISurfaceBuffer 不会创建 DX 图面。 使用通过 IMFMediaSourceEx::SetD3DManager 方法传递到媒体源的 DXGI 管理器创建图面。
IMFDXGIDeviceManager::OpenDeviceHandle 提供与所选 D3D 设备关联的句柄。 然后可以使用 IMFDXGIDeviceManager::GetVideoService 方法获取 ID3D11Device 接口。
无论使用哪种类型的缓冲区,创建的IMFSample必须通过媒体流的IMFMediaEventGenerator上的MEMediaSample事件提供给管道。
虽然可以为自定义媒体源和 IMFMediaStream 的基础集合使用相同的 IMFMediaEventQueue,但应指出这样做将导致媒体源事件和流事件(包括媒体流)的序列化。 对于具有多个流的源,这并不可取。
以下代码截图显示了媒体流的示例实现:
IFACEMETHODIMP
SimpleMediaStream::RequestSample(
_In_ IUnknown *pToken
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
ComPtr<IMFSample> sample;
ComPtr<IMFMediaBuffer> outputBuffer;
LONG pitch = IMAGE_ROW_SIZE_BYTES;
BYTE *bufferStart = nullptr; // not used
DWORD bufferLength = 0;
BYTE *pbuf = nullptr;
ComPtr<IMF2DBuffer2> buffer2D;
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
RETURN_IF_FAILED (MFCreateSample(&sample));
RETURN_IF_FAILED (MFCreate2DMediaBuffer(NUM_IMAGE_COLS,
NUM_IMAGE_ROWS,
D3DFMT_X8R8G8B8,
false,
&outputBuffer));
RETURN_IF_FAILED (outputBuffer.As(&buffer2D));
RETURN_IF_FAILED (buffer2D->Lock2DSize(MF2DBuffer_LockFlags_Write,
&pbuf,
&pitch,
&bufferStart,
&bufferLength));
RETURN_IF_FAILED (WriteSampleData(pbuf, pitch, bufferLength));
RETURN_IF_FAILED (buffer2D->Unlock2D());
RETURN_IF_FAILED (sample->AddBuffer(outputBuffer.Get()));
RETURN_IF_FAILED (sample->SetSampleTime(MFGetSystemTime()));
RETURN_IF_FAILED (sample->SetSampleDuration(333333));
if (pToken != nullptr)
{
RETURN_IF_FAILED (sample->SetUnknown(MFSampleExtension_Token, pToken));
}
RETURN_IF_FAILED (_spEventQueue->QueueEventParamUnk(MEMediaSample,
GUID_NULL,
S_OK,
sample.Get()));
return hr;
}
用于公开 IMFActivate 的自定义媒体源扩展(在 Windows 10 版本 1809 中可用)
除了自定义媒体源必须支持的上述接口列表外,在帧服务器架构中运行的自定义媒体源的限制之一是,通过管道只能“激活”一个 UMDF 驱动程序实例。
例如,如果你有一个物理设备,除了安装非 AV 流驱动程序包之外,还安装了 UMDF 存根驱动程序,并且你将多个这样的物理设备连接到计算机上,那么尽管每个 UMDF 驱动程序实例都会获得一个唯一的符号链接名称,自定义媒体源的激活路径将没有方法在创建时传达与该自定义媒体源关联的符号链接名称。
自定义媒体源可能会在调用 IMFMediaSource::Start 时在自定义媒体源的属性存储(通过 IMFMediaSourceEx::GetSourceAttributes 方法从自定义媒体源返回的属性存储)中查找标准MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK属性。
但是,这可能会导致启动延迟较高,因为这会将 HW 资源获取延迟到开始时间,而不是创建/初始化时间。
因此,在 Windows 10 版本 1809 中,自定义媒体源可能会选择性地公开 IMFActivate 接口。
注释
IMFActivate 继承自 IMFAttributes。
IMFActivate
如果自定义媒体源的 COM 服务器支持 IMFActivate 接口,则设备初始化信息将通过 IMFActivate 继承的 IMFAttributes 提供给 COM 服务器。 因此,当调用 IMFActivate::ActivateObject 时, IMFActivate 的属性存储包含 UMDF 存根驱动程序的符号链接名称以及源创建/初始化时管道/应用程序提供的任何其他配置设置。
自定义媒体源应使用此方法调用来获取它所需的任何硬件资源。
注释
如果硬件资源获取时间超过 200 毫秒,则建议异步获取硬件资源。 自定义媒体源的激活不应阻止硬件资源获取。 相反,IMFMediaSource::Start 操作应序列化以协调硬件资源获取。
IMFActivate、DetachObject 和 ShutdownObject 公开的另外两种方法必须返回E_NOTIMPL。
自定义媒体源可以选择在与 IMFMediaSource 相同的 COM 对象内实现 IMFActivate 和 IMFAttributes 接口。 如果这样做,建议IMFMediaSourceEx::GetSourceAttributes 返回与IMFActivate中的接口相同的IMFAttributes接口。
如果自定义媒体源未使用同一对象实现 IMFActivate 和 IMFAttributes ,则自定义媒体源必须将 在 IMFActivate 属性存储上设置的所有属性复制到自定义媒体源的源属性存储中。
编码的相机流
自定义媒体源可能会公开压缩媒体类型(HEVC 或 H264 基本流),OS 管道完全支持自定义媒体源上的编码参数的源和配置(编码参数通过 ICodecAPI 进行通信,该调用路由为 IKsControl::KsProperty 调用):
// IKsControl methods
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::KsProperty(
_In_reads_bytes_(ulPropertyLength) PKSPROPERTY pProperty,
_In_ ULONG ulPropertyLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pPropertyData,
_In_ ULONG ulDataLength,
_Out_ ULONG* pBytesReturned
);
传递到 IKsControl::KsProperty 方法的 KSPROPERTY 结构具有以下信息:
KSPROPERTY.Set = Encoder Property GUID
KSPROPERTY.Id = 0
KSPROPERTY.Flags = (KSPROPERTY_TYPE_SET or KSPROPERTY_TYPE_GET)
其中,编码器属性 GUID 是在 编解码器 API 属性中定义的可用属性的列表。
编码器属性的有效负载将通过上面声明的 KsProperty 方法的 pPropertyData 字段传入。
捕获引擎要求
虽然框架服务器完全支持编码源,但 Windows.Media.Capture.MediaCapture 对象使用的客户端捕获引擎(IMFCaptureEngine)施加了额外的要求:
流必须全部编码(HEVC 或 H264)或全部未压缩(在此上下文中,MJPG 被视为未压缩)。
必须至少有一个未压缩的流可用。
注释
这些要求除了本文中所述的自定义媒体源要求之外。 但是,仅当客户端应用程序通过 IMFCaptureEngine 或 Windows.Media.Capture.MediaCapture API 使用自定义媒体源时,才强制实施捕获引擎要求。
相机配置文件(在 Windows 10 版本 1803 及更高版本中可用)
自定义媒体源支持相机配置文件。 建议的机制是通过源属性(IMFMediaSourceEx::GetSourceAttributes)的MF_DEVICEMFT_SENSORPROFILE_COLLECTION属性发布简介。
MF_DEVICEMFT_SENSORPROFILE_COLLECTION属性是 IMFSensorProfileCollection 接口的 IUnknown。 可以使用 MFCreateSensorProfileCollection 函数获取 IMFSensorProfileCollection:
IFACEMETHODIMP
SimpleMediaSource::GetSourceAttributes(
_COM_Outptr_ IMFAttributes** sourceAttributes
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
if (nullptr == sourceAttributes)
{
return E_POINTER;
}
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
*sourceAttributes = nullptr;
if (_spAttributes.Get() == nullptr)
{
ComPtr<IMFSensorProfileCollection> profileCollection;
ComPtr<IMFSensorProfile> profile;
// Create our source attribute store
RETURN_IF_FAILED (MFCreateAttributes(_spAttributes.GetAddressOf(), 1));
// Create an empty profile collection
RETURN_IF_FAILED (MFCreateSensorProfileCollection(&profileCollection));
// In this example since we have just one stream, we only have one
// pin to add: Pin0
// Legacy profile is mandatory. This is to ensure non-profile
// aware applications can still function, but with degraded
// feature sets.
RETURN_IF_FAILED (MFCreateSensorProfile(KSCAMERAPROFILE_Legacy, 0, nullptr,
profile.ReleaseAndGetAddressOf()));
RETURN_IF_FAILED (profile->AddProfileFilter(0, L"((RES==;FRT<=30,1;SUT==))"));
RETURN_IF_FAILED (profileCollection->AddProfile(profile.Get()));
// High Frame Rate profile will only allow >=60fps
RETURN_IF_FAILED (MFCreateSensorProfile(KSCAMERAPROFILE_HighFrameRate, 0, nullptr,
profile.ReleaseAndGetAddressOf()));
RETURN_IF_FAILED (profile->AddProfileFilter(0, L"((RES==;FRT>=60,1;SUT==))"));
RETURN_IF_FAILED (profileCollection->AddProfile(profile.Get()));
// See the profile collection to the attribute store of the IMFTransform
RETURN_IF_FAILED (_spAttributes->SetUnknown(MF_DEVICEMFT_SENSORPROFILE_COLLECTION,
profileCollection.Get()));
}
return _spAttributes.CopyTo(sourceAttributes);
}
人脸身份验证配置
如果自定义媒体源旨在支持 Windows Hello 人脸识别,则建议发布人脸身份验证配置文件。 人脸身份验证配置文件的要求如下:
必须在单个 IR 流上支持人脸身份验证 DDI 控制。 有关详细信息,请参阅 KSPROPERTY_CAMERACONTROL_EXTENDED_FACEAUTH_MODE。
IR 流必须至少为 340 x 340(15 fps)。 格式必须为 L8、NV12 或用 L8 压缩标记的 MJPG。
RGB 流必须至少为 480 x 480,且为 7.5 fps(仅当强制执行 Multispectrum 身份验证时才需要这样做)。
人脸身份验证配置文件的配置文件 ID 必须为:KSCAMERAPROFILE_FaceAuth_Mode.0。
建议在人脸身份验证配置文件中,每个 IR 和 RGB 流仅发布一种媒体类型。
照片流控件
如果通过将其中一个流的MF_DEVICESTREAM_STREAM_CATEGORY 标记为 PINNAME_IMAGE来公开独立照片流,则需要流类别 为PINNAME_VIDEO_CAPTURE 的流(例如,仅公开 PINNAME_IMAGE 的单个流不是有效的媒体源)。
通过 IKsControl 接口,必须支持 PROPSETID_VIDCAP_VIDEOCONTROL 属性集。 有关详细信息,请参阅 视频控件属性。