次の方法で共有


フレーム サーバーのカスタム メディア ソース

このトピックでは、フレーム サーバー アーキテクチャ内でのカスタム メディア ソースの実装に関する情報を提供します。

AV Stream とカスタム メディア ソースのオプション

フレーム サーバー アーキテクチャ内でビデオ キャプチャ ストリームのサポートを提供する方法を決定するときは、AV Stream とカスタム メディア ソースの 2 つのメイン オプションがあります。

AV Stream モデルは、AV Stream ミニポート ドライバー (カーネル モード ドライバー) を使用する標準のカメラ ドライバー モデルです。 通常、AV Stream ドライバーは、MIPI ベースのドライバーと USB ビデオ クラス ドライバーの 2 つのメイン カテゴリに分類されます。

カスタム メディア ソース オプションの場合、ドライバー モデルは完全にカスタム (専用) であるか、または従来以外のカメラ ソース (ファイル、ネットワーク ソースなど) に基づいている場合があります。

AV Stream Driver

AV Stream Driver アプローチの主な利点は、PnP と電源管理/デバイス管理が AV Stream フレームワークによって既に処理されていることです。

ただし、基になるソースは、ハードウェアとインターフェイスするためのカーネルモード ドライバーを備えた物理デバイスである必要もあります。 UVC デバイスの場合、Windows UVC 1.5 クラス ドライバーは受信トレイを提供するため、デバイスは単にファームウェアを実装する必要があります。

MIPI ベースのデバイスの場合、ベンダーは独自の AV Stream ミニポート ドライバーを実装する必要があります。

カスタム メディア ソース

デバイス ドライバーが既に使用可能なソース (ただし、AV Stream ミニポート ドライバーではない) または従来以外のカメラ キャプチャを使用するソースの場合、AV Stream Driver は実行可能でない可能性があります。 たとえば、ネットワーク経由で接続されている IP カメラは、AV Stream Driver モデルには適合しません。

このような状況では、フレーム サーバー モデルを使用するカスタム メディア ソースが代わりに使用されます。

機能 カスタム メディア ソース AV Stream Driver
PnP と電源管理 ソース ドライバーおよび/またはスタブ ドライバーで実装する必要があります AV Stream フレームワークによる提供
ユーザー モード プラグイン 使用できません。 カスタム メディア ソースには、OEM/IHV 固有のユーザー モード ロジックが組み込まれています。 レガシ実装用の DMFT、プラットフォーム DMFT、および MFT0
センサー グループ サポートされています サポートされています
カメラのプロファイル V2 サポートされています サポートされています
カメラのプロファイル V1 サポートされていません サポートされています

カスタム メディア ソースの要件

Windows カメラ フレーム サーバー (フレーム サーバーと呼ばれる) サービスの導入により、カスタム メディア ソースを使用してこれを実現できます。 これには、次の 2 つの主要なコンポーネントが必要です。

  • カメラ デバイス インターフェイスを登録/有効にするように設計されたスタブ ドライバーを含むドライバー パッケージ。

  • カスタム メディア ソースをホストする COM DLL。

最初の要件は次の 2 つの目的のために必要です。

  • カスタム メディア ソースが信頼できるプロセスを通じてインストールされていることを確認するための審査プロセス (ドライバー パッケージには WHQL 認定が必要です)。

  • "カメラ" の標準的な PnP 列挙と検出のサポート。

セキュリティ

フレーム サーバーのカスタム メディア ソースは、セキュリティの点で汎用のカスタム メディア ソースと次のように異なります。

  • フレーム サーバー カスタム メディア ソースはローカル サービスとして実行されます (ローカル システムと混同しないようご注意ください。ローカル サービスは、Windows マシン上の非常に低い特権アカウントです)。

  • フレーム サーバー カスタム メディア ソースはセッション 0 (System Service セッション) で実行され、ユーザー デスクトップと操作することはできません。

これらの制約により、フレーム サーバー カスタム メディア ソースは、ファイル システムやレジストリの保護された部分へのアクセスを試みないようにしてください。 通常、読み取りアクセスは許可されますが、書き込みアクセスは許可されません。

パフォーマンス

フレーム サーバー モデルの一部として、フレーム サーバーによってカスタム メディア ソースをインスタンス化する方法には、次の 2 つのケースがあります。

  • センサー グループの生成/発行中。

  • "カメラ" のアクティブ化中

センサー グループの生成は、通常、デバイスのインストール中および/または電源サイクル中に行われます。 このため、カスタム メディア ソースの作成時に重要な処理を回避し、そのようなアクティビティを IMFMediaSource::Start 関数に延期することを強くお勧めします。 センサー グループの生成では、カスタム メディア ソースの開始は試みられません。単に、使用可能なさまざまなストリーム/メディアの種類とソース/ストリームの属性情報に対してクエリを実行するだけです。

スタブ ドライバー

ドライバー パッケージとスタブ ドライバーには、2 つの最小要件があります。

スタブ ドライバーは、WDF (UMDF または KMDF) または WDM ドライバー モデルを使用して記述できます。

ドライバーの要件は次のとおりです。

  • "カメラ" (カスタム メディア ソース) デバイス インターフェイスを KSCATEGORY_VIDEO_CAMERA カテゴリに登録して、列挙できるようにします。

Note

レガシ DirectShow アプリケーションによる列挙を許可するには、ドライバーも KSCATEGORY_VIDEOKSCATEGORY_CAPTURE に登録する必要があります。

  • デバイス インターフェイス ノードの下に、カスタム メディア ソース COM オブジェクトの CoCreate-able CLSID を宣言するレジストリ エントリを追加します (ドライバーの INF DDInstall.Interface セクションの AddReg ディレクティブを使用)。 これは、次のレジストリ値の名前を使用して追加する必要があります: CustomCaptureSourceClsid

これにより、アプリケーションによって "カメラ" ソースを検出し、アクティブ化されると、アクティブ化呼び出しを傍受して 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_CAPTURE、および KSCATEGORY_VIDEO_CAMERA の下に登録され、標準の RGB カメラを検索する UWP アプリと 非 UWP アプリで "カメラ" を確実に検出できるようにします。

カスタム メディア ソースで RGB 以外のストリーム (IR、深度など) も公開されている場合は、必要に応じて、KSCATEGORY_SENSOR_CAMERA に登録することもできます。

Note

ほとんどの USB ベースの Web カメラでは、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 サーバーです。

Note

IMFMediaSourceExIMFMediaSource から継承し、IMFMediaSourceIMFMediaEventGenerator から継承します。

カスタム メディア ソース内でサポートされる各ストリームは、次のインターフェイスをサポートする必要があります。

Note

IMFMediaStream2IMFMediaStream から継承し、IMFMediaStreamIMFMediaEventGenerator から継承します。

カスタム メディア ソースを作成する方法については、カスタム メディア ソースの作成に関するドキュメントを参照してください。 このセクションの残りの部分では、フレーム サーバー フレームワーク内でカスタム メディア ソースをサポートするために必要な違いについて説明します。

IMFGetService

IMFGetService は、フレーム サーバー カスタム メディア ソースの必須インターフェイスです。 カスタム メディア ソースで追加のサービス インターフェイスを公開する必要がない場合、IMFGetServiceMF_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::PauseMF_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_CATEGORYPINNAME_VIDEO_CAPTURE に設定する必要があります。 PINNAME_VIDEO_PREVIEW は、カスタム メディア ソースではサポートされていません。

Note

PINNAME_IMAGE はサポートされていますが、推奨されません。 PINNAME_IMAGE を使用してストリームを公開するには、すべての写真トリガー コントロールをサポートするカスタム メディア ソースが必要です。 詳細については、以下の「フォト ストリームコントロール」セクションを参照してください。

MF_DEVICESTREAM_STREAM_ID は、すべてのストリームに必須の属性です。 0 から始まるインデックスである必要があります。 それで、最初のストリームの ID は 0、2 番目のストリームの ID は 1、というようになります。

ストリームで推奨される属性の一覧を次に示します。

MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES

MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES は、ストリーム型のビットマスク値である UINT32 属性です。 次のいずれかに設定できます (これらの型はビットマスク フラグですが、可能であればソース型を混在させないことをお勧めします)。

Type フラグ 説明
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 は、0 または 1 に設定できる UINT32 属性です。 1 に設定すると、ストリームはフレーム サーバーによって "共有可能" としてマークされます。 これにより、別のアプリで使用されている場合でも、アプリケーションは共有モードでストリームを開くことができるようになります。

この属性が設定されていない場合、フレーム サーバーは、マークされていない最初のストリームの共有を許可します (カスタム メディア ソースにストリームが 1 つしかない場合、そのストリームが共有としてマークされます)。

この属性が 0 に設定されている場合、フレーム サーバーは共有アプリからのストリームをブロックします。 カスタム メディア ソースがこの属性を 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 は、メディア ストリームの IMFMediaEventGeneratorMEMediaSample イベントを介してパイプラインに提供する必要があります。

カスタム メディア ソースと基になる 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 で利用可能)

カスタム メディア ソースでサポートする必要がある上記のインターフェイスの一覧に加えて、フレーム サーバー アーキテクチャ内のカスタム メディア ソース操作によって課される制限の 1 つは、パイプラインを介して UMDF ドライバーのインスタンスを 1 つだけ "アクティブ化" できることです。

たとえば、非 AV Stream ドライバー パッケージに加えて UMDF スタブ ドライバーをインストールする物理デバイスがあり、それらの物理デバイスを複数のコンピューターに接続する場合、UMDF ドライバーの各インスタンスは一意のシンボリック リンク名を取得しますが、カスタム メディア ソースのアクティブ化パスには、作成時にカスタム メディア ソースに関連付けられたシンボリック リンク名を伝達する手段はありません。

カスタム メディア ソースは、IMFMediaSource::Start が呼び出されたときに、カスタム メディア ソースの属性ストア (IMFMediaSourceEx::GetSourceAttributes メソッドを通じてカスタム メディア ソースから返される属性ストア) で標準のMF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK 属性を検索できます。

ただし、HW リソースの取得は、作成/初期化時間ではなく開始時刻に延期されるため、起動待機時間が長くなる可能性があります。

このため、Windows 10 バージョン 1809 では、カスタム メディア ソースは必要に応じて IMFActivate インターフェイスを公開できます。

Note

IMFActivateIMFAttributes から継承します。

IMFActivate

カスタム メディア ソースの COM サーバーが IMFActivate インターフェイスをサポートしている場合、デバイスの初期化情報は、IMFActivate によって継承された IMFAttributes を介して COM サーバーに提供されます。 そのため、IMFActivate::ActivateObject が呼び出されると、IMFActivate の属性ストアには、UMDF スタブ ドライバーのシンボリック リンク名と、ソースの作成/初期化時にパイプライン/アプリケーションによって提供される追加の構成設定が含まれます。

カスタム メディア ソースでは、このメソッド呼び出しを使用して、必要なハードウェア リソースを取得する必要があります。

Note

ハードウェア リソースの取得に 200 ミリ秒を超える時間がかかる場合は、ハードウェア リソースを非同期的に取得することをお勧めします。 カスタム メディア ソースのアクティブ化は、ハードウェア リソースの取得でブロックしないようにしてください。 代わりに、ハードウェア リソースの取得に対して IMFMediaSource::Start 操作をシリアル化する必要があります。

IMFActivateDetachObjectShutdownObject によって公開される 2 つの追加メソッドは、E_NOTIMPL を返す必要があります。

カスタム メディア ソースは、IMFMediaSource と同じ COM オブジェクト内に IMFActivate および IMFAttributes インターフェイスを実装することを選択できます。 これを行う場合は、IMFMediaSourceEx::GetSourceAttributesIMFActivate のインターフェイスと同じ IMFAttributes インターフェイスを返すことをお勧めします。

カスタム メディア ソースが同じオブジェクトを持つ IMFActivateIMFAttributes を実装していない場合、カスタム メディア ソースは、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 は、Codec API プロパティで定義されている使用可能なプロパティの一覧です。

エンコーダー プロパティのペイロードは、上記で宣言した KsProperty メソッドの pPropertyData フィールドを介して渡されます。

キャプチャ エンジンの要件

エンコードされたソースはフレーム サーバーで完全にサポートされていますが、Windows.Media.Capture.MediaCapture オブジェクトで使用されるクライアント側キャプチャ エンジン (IMFCaptureEngine) では、追加の要件が課されます。

  • ストリームは、すべてエンコード (HEVC または H264) またはすべての非圧縮である必要があります (このコンテキストでは、MJPG は非圧縮として扱われます)。

  • 少なくとも 1 つの非圧縮ストリームがある必要があります。

Note

これらの要件は、このトピックで説明されているカスタム メディア ソースの要件に追加されるものです。 ただし、キャプチャ エンジンの要件は、クライアント アプリケーションが IMFCaptureEngine または Windows.Media.Capture.MediaCapture API を介してカスタム メディア ソースを使用する場合にのみ適用されます。

カメラ プロファイル (Windows 10 バージョン 1803 以降で利用可能)

カメラ プロファイルのサポートは、カスタム メディア ソースで使用できます。 推奨されるメカニズムは、ソース属性 (IMFMediaSourceEx::GetSourceAttributes) から MF_DEVICEMFT_SENSORPROFILE_COLLECTION 属性を使用してプロファイルを発行することです。

MF_DEVICEMFT_SENSORPROFILE_COLLECTION 属性は、IMFSensorProfileCollection インターフェイスの IUnknown です。 IMFSensorProfileCollection は、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 コントロールは、1 つの IR ストリームでサポートされている必要があります。 詳細については、「KSPROPERTY_CAMERACONTROL_EXTENDED_FACEAUTH_MODE」を参照してください。

  • IR ストリームは、15 fps で 340 x 340 以上である必要があります。 形式は、L8 圧縮でマークされた L8、NV12、または MJPG である必要があります。

  • RGB ストリームは、7.5 fps で 480 x 480 以上である必要があります (これは、マルチスペクトル認証が適用されている場合にのみ必要です)。

  • 顔認証プロファイルには、プロファイル ID が KSCAMERAPROFILE_FaceAuth_Mode,0 である必要があります。

Face 認証プロファイルでは、IR ストリームと RGB ストリームごとに 1 つのメディア タイプのみをアドバタイズすることをお勧めします。

フォト ストリーム コントロール

ストリームの MF_DEVICESTREAM_STREAM_CATEGORY の 1 つを PINNAME_IMAGE としてマークして独立した写真ストリームを公開する場合は、ストリーム カテゴリが PINNAME_VIDEO_CAPTURE のストリームが必要です (たとえば、PINNAME_IMAGE のみを公開する単一のストリームは有効なメディア ソースではありません)。

IKsControl を使用して、PROPSETID_VIDCAP_VIDEOCONTROL プロパティ セットをサポートする必要があります。 詳細については、「ビデオ コントロールのプロパティ」を参照してください。