畫面伺服器自訂媒體來源
本主題提供 Frame Server 架構內自訂媒體來源實作的相關資訊。
AV 串流和自訂媒體來源選項
決定如何在畫面格伺服器架構中提供視訊擷取串流支援時,有兩個主要選項:AV 串流和自訂媒體來源。
AV Stream 模型是使用 AV Stream 迷你埠驅動程式的標準相機驅動程式模型, (核心模式驅動程式) 。 一般而言,AV Stream 驅動程式分為兩個主要類別:MIPI 型驅動程式和 USB 視訊類別驅動程式。
針對 [自訂媒體來源] 選項,驅動程式模型可能是完全自訂的 (專屬) ,或是根據非傳統相機來源 (例如檔案或網路來源) 。
AV 串流驅動程式
AV Stream Driver 方法的主要優點是 PnP 和電源管理/裝置管理已由 AV Stream 架構處理。
不過,這也表示基礎來源必須是具有核心模式驅動程式的實體裝置,才能與硬體互動。 針對 UVC 裝置,Windows UVC 1.5 類別驅動程式會提供收件匣,因此裝置只需要實作其韌體即可。
針對 MIPI 型裝置,廠商必須實作自己的 AV Stream 迷你埠驅動程式。
自訂媒體來源
對於裝置驅動程式已可供使用 (,但無法使用 AV Stream 迷你埠驅動程式) 或使用非傳統相機擷取的來源,可能無法使用 AV 串流驅動程式。 例如,透過網路連線的 IP 相機無法納入 AV 串流驅動程式模型。
在這種情況下,使用框架伺服器模型的自訂媒體來源會是替代方式。
功能 | 自訂媒體來源 | AV 串流驅動程式 |
---|---|---|
PnP 和電源管理 | 來源和/或存根驅動程式必須實作 | AV Stream 架構提供 |
使用者模式外掛程式 | 不適用。 自訂媒體來源會納入 OEM/IHV 特定的使用者模式邏輯。 | 適用于舊版實作的 DMFT、平臺 DMFT 和 MFT0 |
感應器群組 | 支援 | 支援 |
相機設定檔 V2 | 支援 | 支援 |
相機設定檔 V1 | 不支援 | 支援 |
自訂媒體來源需求
透過引進 Windows 相機 Frame Server (稱為 Frame Server) 服務,這可以透過自訂媒體來源來完成。 這需要兩個主要元件:
具有 Stubbed 驅動程式的驅動程式套件,其設計目的是註冊/啟用相機裝置介面。
裝載自訂媒體來源的 COM DLL。
有兩個用途需要第一個需求:
確認自訂媒體來源是透過信任的程式安裝, (驅動程式套件需要 WHQL 認證) 。
支援標準 PnP 列舉和探索「相機」。
安全性
Frame Server 的自訂媒體來源在安全性方面與一般自訂媒體來源不同,方式如下:
框架伺服器自訂媒體來源會以本機服務的形式執行, (不會與本機系統混淆;本機服務是 Windows 電腦上非常低的特殊許可權帳戶) 。
Frame Server 自訂媒體來源會在會話 0 (系統服務會話中執行) ,而且無法與使用者桌面互動。
基於這些條件約束,Frame Server 自訂媒體來源不得嘗試存取檔案系統或登錄的受保護部分。 一般而言,允許讀取權限,但寫入權限則不允許。
效能
框架伺服器模型的一部分有兩種案例,說明 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 下註冊,以確保搜尋標準 RGB 相機的任何 UWP 和非 UWP 應用程式都可探索「相機」。
如果自訂媒體來源也會在 IR、深度等 (公開非 RGB 資料流程,) 也可以選擇性地在 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。
請參閱 撰寫自訂媒體來源 檔,以瞭解如何建立自訂媒體來源。 本節的其餘部分將說明支援 Frame Server 架構內自訂媒體來源所需的差異。
IMFGetService
IMFGetService 是 Frame Server 自訂媒體來源的必要介面。 如果您的自訂媒體來源不需要公開任何其他服務介面,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 介面。 來自來源的整個 MF 管線資料和控制流程會透過事件產生器來管理,方法是傳送特定的 IMFMediaEvent。
若要實作 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::P ause 應該傳回 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
如撰寫自訂媒體來源中所述,IMFMediaStream2介面會透過在 IMFMediaSource::Start方法完成期間張貼至來源事件佇列的MENewStream媒體事件,提供給畫面格工作:
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 為基礎的索引。 因此,第一個資料流程的識別碼為 0、第二個數據流的識別碼為 1,依此類故。
以下是資料流程上建議的屬性清單:
MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES
MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES 是 UINT32 屬性,這是資料流程類型的位元遮罩值。 當這些類型是位元遮罩旗標時,它可以設定為下列任一 (,如果可能的話,建議不要混合來源類型) :
類型 | 旗標 | 描述 |
---|---|---|
MFFrameSourceTypes_Color | 0x0001 | 標準 RGB 色彩資料流程 |
MFFrameSourceTypes_Infrared | 0x0002 | IR 資料流程 |
MFFrameSourceTypes_Depth | 0x0004 | 深度資料流程 |
MFFrameSourceTypes_Image | 0x0008 | 影像資料流程 (非視訊子類型,通常是 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。
針對 GPU 加速作業使用 DX 表面 (,例如轉譯和/或編碼) ,應該使用 MFCreateDXGISurfaceBuffer 。
MFCreateDXGISurfaceBuffer 不會建立 DX 表面。 介面是使用透過 IMFMediaSourceEx::SetD3DManager 方法傳入媒體來源的 DXGI 管理員所建立。
IMFDXGIDeviceManager::OpenDeviceHandle會提供與所選 D3D 裝置相關聯的控制碼。 接著可以使用IMFDXGIDeviceManager::GetVideoService方法來取得ID3D11Device介面。
無論使用何種類型的緩衝區,都必須透過媒體串流之IMFMediaEventGenerator上的MEMediaSample事件,將建立的IMFSample提供給管線。
雖然可以針對自訂媒體來源和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;
}
自訂媒體來源延伸模組,以公開WINDOWS 10 版本 1809) 中提供的 IMFActivate (
除了必須支援自訂媒體來源的上述介面清單之外,框架伺服器架構內自訂媒體來源作業所加加的其中一項限制,就是只能透過管線「啟動」UMDF 驅動程式的一個實例。
例如,如果您的實體裝置除了非 AV Stream 驅動程式套件之外,還安裝 UMDF Stub 驅動程式,而且您將其中一個以上的實體裝置附加至電腦,而 UMDF 驅動程式的每個實例都會取得唯一的符號連結名稱,則自訂媒體來源的啟用路徑將不會有方法可以傳達與自訂媒體來源相關聯的符號連結名稱。建立時間。
自訂媒體來源可能會在自訂媒體來源的屬性存放區中尋找標準 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK 屬性, (透過 IMFMediaSourceEx::GetSourceAttributes 方法從自訂媒體來源傳回的屬性存放區,) 叫用 IMFMediaSource::Start 時。
不過,這可能會導致較高的啟動延遲,因為這會在 HW 資源擷取延遲到開始時間,而不是建立/初始化時間。
因此,在Windows 10 版本 1809中,自訂媒體來源可能會選擇性地公開IMFActivate介面。
注意
IMFActivate 繼承自 IMFAttributes。
IMFActivate
如果自訂媒體來源的 COM 伺服器支援IMFActivate介面,裝置初始化資訊將會透過IMFActivate所繼承的IMFAttributes提供給 COM 伺服器。 因此,叫用 IMFActivate::ActivateObject 時, IMFActivate 的屬性存放區會包含 UMDF 存根驅動程式的符號連結名稱,以及管線/應用程式在來源建立/初始化時所提供的任何其他組態設定。
自訂媒體來源應該使用此方法調用來取得它所需的任何硬體資源。
注意
如果硬體資源擷取需要超過 200 毫秒,建議以非同步方式取得硬體資源。 自訂媒體來源的啟用不應在硬體資源取得時封鎖。 相反地, IMFMediaSource::Start 作業應該根據硬體資源擷取進行序列化。
IMFActivate和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)
其中 Encoder 屬性 GUID 是 Codec API 屬性中定義的可用屬性清單。
編碼器屬性的承載將會透過上述KsProperty方法的pPropertyData欄位傳入。
擷取引擎需求
雖然 Frame Server 完全支援編碼的來源,但用戶端擷取引擎 (IMFCaptureEngine) Windows.Media.Capture.MediaCapture 物件會強制執行額外的需求:
資料流程必須是所有編碼 (HEVC 或 H264) ,或此內容中的所有未壓縮 (都會被視為未壓縮) 。
至少有一個可用的未壓縮資料流程。
注意
這些需求除了本主題中所述的自訂媒體來源需求之外。 不過,只有在用戶端應用程式透過 IMFCaptureEngine 或 Windows.Media.Capture.MediaCapture API 使用自訂媒體來源時,才會強制執行擷取引擎需求。
Windows 10 1803 版和更新版本中提供的相機設定檔 ()
相機設定檔支援適用于自訂媒體來源。 建議的機制是透過 MF_DEVICEMFT_SENSORPROFILE_COLLECTION 屬性將設定檔發佈至 (IMFMediaSourceEx::GetSourceAttributes) 來源屬性。
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 (只有在強制執行多重規格驗證) 時才需要此資料流程。
臉部驗證設定檔的設定檔識別碼必須是: KSCAMERAPROFILE_FaceAuth_Mode,0。
我們建議臉部驗證設定檔只會針對每個 IR 和 RGB 資料流程公告一個媒體類型。
相片串流控制項
如果獨立相片串流會藉由將其中一個資料流程MF_DEVICESTREAM_STREAM_CATEGORY 標示為 PINNAME_IMAGE來公開,則 (需要有串流 PINNAME_VIDEO_CAPTURE 類別的資料流程,例如,只公開 PINNAME_IMAGE 的單一串流不是有效的媒體來源) 。
透過 IKsControl,必須支援 PROPSETID_VIDCAP_VIDEOCONTROL 屬性集。 如需詳細資訊,請參閱 影片控制項屬性。