共用方式為


框架伺服器自定義媒體來源

本文提供框架伺服器架構內自定義媒體來源實作的相關信息。

AV 數據流和自訂媒體來源選項

決定如何在 Frame Server 架構內提供視訊擷取串流支援時,有兩個主要選項: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流驅動程式模型。

在這種情況下,使用 Frame Server 模型的自定義媒體來源是替代方式。

特徵 自訂媒體來源 AV 串流驅動程式
PnP 和電源管理 來源和/或存根驅動程序必須實作 由AV Stream架構提供
使用者模式外掛程式 不可用 自訂媒體來源會納入 OEM/IHV 特定的使用者模式邏輯。 舊版實作的 DMFT、平臺 DMFT 和 MFT0
感測器群組 支持 支持
相機設定檔 V2 支持 支持
相機設定檔 V1 不支援 支持

自訂媒體來源需求

透過引進 Windows Camera Frame Server(稱為 Frame Server)服務,即可透過自定義媒體來源來完成此作業。 這需要兩個主要元件:

  • 具有 Stubbed 驅動程式的驅動程式套件,其設計目的是註冊/啟用相機裝置介面。

  • 承載自定義媒體來源的 COM DLL。

第一個需求用於兩個目的:

  • 驗證過程,以確保自訂媒體來源是透過可靠的程序安裝(驅動程式套件需要通過 WHQL 認證)。

  • 支援標準 PnP 列舉和探索「相機」。

安全

Frame Server 的自定義媒體來源在安全性方面與一般自定義媒體來源不同,方式如下:

  • 框架伺服器自定義媒體來源會以本地服務的形式執行(不會與本機系統混淆;本地服務是 Windows 電腦上的低特殊許可權帳戶。

  • 畫面伺服器自定義媒體來源會在會話 0 中執行(系統服務會話),且無法與使用者桌面互動。

鑒於這些條件約束,Frame Server 自定義媒體來源不得嘗試存取文件系統或登錄的受保護部分。 一般而言,允許讀取存取,但不允許寫入存取。

績效

作為 Frame Server 模型的一部分,框架伺服器將如何具現化自定義媒體來源有兩種案例:

  • 在感測器群組產生/發佈期間。

  • 在「相機」啟用期間

感測器群組的產生通常是在裝置安裝和/或電源周期期間完成。 為此,強烈建議自定義媒體來源避免在建立期間進行任何重大處理,並將任何這類活動延遲至 IMFMediaSource::Start 函式。 感測器群組產生不會嘗試啟動自定義媒體來源,只會查詢各種可用的數據流/媒體類型和來源/數據流屬性資訊。

Stub 驅動程式

驅動程式套件和存根驅動程式有兩個最低需求。

存根驅動程式可以使用 WDF (UMDF 或 KMDF) 或 WDM 驅動程式模型來撰寫。

驅動程式需求如下:

  • KSCATEGORY_VIDEO_CAMERA 類別下註冊您的「相機」(自定義媒體來源)裝置介面,以便加以列舉。

備註

若要允許舊版 DirectShow 應用程式的列舉,您的驅動程式也必須在 KSCATEGORY_VIDEOKSCATEGORY_CAPTURE 下註冊。

  • 在裝置介面節點下新增一個登錄專案(在驅動程式 INF DDInstall.Interface 區段中使用 AddReg 指令),以宣告可用於 CoCreate 的自定義媒體來源 COM 物件的 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_VIDEOKSCATEGORY_CAPTUREKSCATEGORY_VIDEO_CAMERA 下註冊,以確保任何 UWP 和非 UWP 應用程式搜尋標準 RGB 相機,都能夠找到「相機」。

如果自定義媒體來源也會公開非 RGB 串流(IR、Depth 等等),它可能會選擇性地在 KSCATEGORY_SENSOR_CAMERA下註冊。

備註

大部分的 USB 網路攝影機都會公開 YUY2 和 MJPG 格式。 由於此行為,許多舊版 DirectShow 應用程式會以 YUY2/MJPG 可用假設撰寫。 為了確保與這類應用程式的相容性,如果想要舊版應用程式相容性,建議您從自定義媒體來源取得 YUY2 媒體類型。

Stub 驅動程式實作

除了 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

自訂媒體來源內每個支援的數據流都必須支援下列介面:

備註

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;
}

IMF媒體事件生成器

如上所述,來源和來源內的個別數據流都必須支援自己的 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;
}

搜尋和暫停

透過 Frame Server 架構支援的自訂媒體來源不支援搜尋或暫停作業。 您的自定義媒體來源不需要提供這些作業的支援,而且不得張貼 MFSourceSeekedMEStreamSeeked 事件。

IMFMediaSource::Pause 應傳回 MF_E_INVALID_STATE_TRANSITION(如果來源已經關閉,則傳回 MF_E_SHUTDOWN)。

IKsControl

IKsControl 是所有相機相關控件的標準控件介面。 如果您的自定義媒體來源實作任何相機控件,IKsControl 介面是管線路由控制輸入/輸出的方式。

如需詳細資訊,請參閱下列控件集檔文章:

控件是選擇性的,如果不支持,建議傳回的錯誤碼是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 選取的每個數據流完成。

對於具有視訊串流的自定義媒體來源,不應該傳送 MEEndOfStreamMEEndOfPresentation 事件。

數據流屬性

所有自定義媒體來源數據流都必須將 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_紅外線 0x0002 IR 數據流
MFFrameSourceTypes_Depth (深度來源類型) 0x0004 深度流
MFFrameSourceTypes_Image 0x0008 影像串流 (非video 子類型,通常是 JPEG)
MFFrameSourceTypes_Custom 0x0080 自訂數據流類型

MF_DEVICESTREAM_FRAMESERVER_SHARED

MF_DEVICESTREAM_FRAMESERVER_SHARED 是UINT32屬性,可以設定為0或1。 如果設定為 1,它會將數據流標示為 Frame Server 的「可共用」。 這可讓應用程式以共用模式開啟串流,即使其他應用程式也一起使用。

如果未設定此屬性,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 來設定媒體畫面:

MFCreateMemoryBufferMFCreateAlignedMemoryBuffer 應該用於非字串對齊的媒體數據。 這些通常是自定義子類型或壓縮的子類型(例如 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 中使用)

除了必須支援自定義媒體來源的上述介面清單之外,Frame Server 架構中自定義媒體來源作業所施加的其中一項限制在於,只有一個 UMDF 驅動程式實例可以透過管線「啟動」。

例如,如果您的實體裝置除了其非AV Stream驅動程式套件之外,還安裝了UMDF存根驅動程式,並且您將多個這樣的實體裝置連接到電腦上,則每個UMDF驅動程式的實例都會獲得唯一的符號連結名稱,但在自定義媒體來源的啟用路徑中,沒有方法可以在創建時傳達與自定義媒體來源相關聯的符號連結名稱。

自訂媒體來源可能會在呼叫 IMFMediaSource::Start 時,在其屬性存放區中尋找標準MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK屬性(透過IMFMediaSourceEx::GetSourceAttributes方法從自訂媒體來源返回的屬性存放區)。

不過,這可能會導致較高的啟動延遲,因為這會將 HW 資源的擷取延後到啟動時間,而不是建立/初始化時間。

因此,在 Windows 10 版本 1809 中,自定義媒體來源可能會選擇性地公開 IMFActivate 介面。

備註

IMFActivate 繼承自 IMFAttributes

IMFActivate

如果自定義媒體來源的 COM 伺服器支援 IMFActivate 介面,則裝置初始化資訊會透過 IMFActivate 繼承的 IMFAttributes 提供給 COM 伺服器。 因此,當 叫用IMFActivate::ActivateObject 時, IMFActivate 的屬性存放區會包含UMDF存根驅動程式的符號連結名稱,以及管線/應用程式在來源建立/初始化時所提供的任何其他組態設定。

自訂媒體來源應該使用此方法來取得它所需的任何硬件資源。

備註

如果硬體資源擷取需要超過 200 毫秒,建議以異步方式取得硬體資源。 自訂媒體來源的激活不應該因為硬體資源的取得而被阻擋。 相較之下,IMFMediaSource::Start 作業應該根據硬體資源取得進行序列化。

IMFActivateDetachObjectShutdownObject 所公開的兩個其他方法必須傳回E_NOTIMPL

自定義媒體來源可以選擇在與 IMFMediaSource 相同的 COM 物件內實作 IMFActivateIMFAttributes 介面。 如果這樣做,建議IMFMediaSourceEx::GetSourceAttributes傳回與IMFActivate中的介面相同的IMFAttributes 介面。

如果自定義媒體來源未使用相同物件實作 IMFActivateIMFAttributes ,則自定義媒體來源必須將 IMFActivate 屬性存放區上設定的所有屬性複製到自定義媒體來源的來源屬性存放區。

編碼的相機串流

自定義媒體來源可能會公開壓縮的媒體類型(HEVC 或 H264 基本串流),而 OS 管線完全支援自定義媒體來源上的編碼參數來源和設定(編碼參數是透過 ICodecAPI 進行通訊,而 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 完全支援編碼來源,但 Windows.Media.Capture.MediaCapture 物件所使用的用戶端擷取引擎 (IMFCaptureEngine) 會施加額外的需求:

  • 數據流必須全部編碼(HEVC 或 H264)或全部未壓縮(在此內容中,MJPG 被視為未壓縮)。

  • 至少必須有一個未壓縮的數據流可供使用。

備註

除了本文中所述的自定義媒體來源需求之外,還有這些額外的需求。 不過,只有在用戶端應用程式透過 IMFCaptureEngineWindows.Media.Capture.MediaCapture API 使用自定義媒體來源時,才會強制執行擷取引擎需求。

相機設定檔 (適用於 Windows 10 版本 1803 和更新版本)

自訂媒體來源提供相機配置檔支援。 建議的機制是透過來源屬性的MF_DEVICEMFT_SENSORPROFILE_COLLECTION屬性來發布配置檔,來源屬性是由IMFMediaSourceEx::GetSourceAttributes提供。

MF_DEVICEMFT_SENSORPROFILE_COLLECTION屬性是IMFSensorProfileCollection 介面的IUnknownIMFSensorProfileCollection 可以使用 MFCreateSensorProfileCollection 函式來取得:

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 臉部辨識,則建議發佈臉部驗證配置檔。 臉部驗證設定檔的要求如下:

  • 臉部驗證 DDI 控制件必須在單一 IR 數據流上得到支援。 如需詳細資訊,請參閱 KSPROPERTY_CAMERACONTROL_EXTENDED_FACEAUTH_MODE

  • IR 數據流必須至少有 340 x 340,15 fps。 格式必須是 L8、NV12 或標示為 L8 壓縮的 MJPG。

  • RGB 數據流必須至少為 480 x 480,以 7.5 幀每秒的速度運行(只有在強制執行多光譜驗證時才需要此數據流)。

  • 臉部驗證配置檔必須具有:KSCAMERAPROFILE_FaceAuth_Mode,0 的配置檔標識碼。

我們建議臉部驗證設定檔只針對每個 IR 和 RGB 數據流宣告一個媒體類型。

相片串流控件

如果將其中一個串流MF_DEVICESTREAM_STREAM_CATEGORY 標示為 PINNAME_IMAGE來公開獨立相片串流,則需要串流類別為 PINNAME_VIDEO_CAPTURE 的數據流(例如,只公開 PINNAME_IMAGE 的單一數據流不是有效的媒體來源)。

透過 IKsControl,必須支援 PROPSETID_VIDCAP_VIDEOCONTROL 屬性集。 如需詳細資訊,請參閱 影片控件屬性