この記事では、フレーム サーバー アーキテクチャ内でのカスタム メディア ソースの実装について説明します。
AV ストリームとカスタム メディア ソースのオプション
フレーム サーバー アーキテクチャ内でビデオ キャプチャ ストリームのサポートを提供する方法を決定するときは、AV ストリームとカスタム メディア ソースの 2 つの主要なオプションがあります。
AV ストリーム モデルは、AV ストリーム ミニポート ドライバー (カーネル モード ドライバー) を使用する標準のカメラ ドライバー モデルです。 通常、AV ストリーム ドライバーは、MIPI ベースのドライバーと USB ビデオ クラス ドライバーの 2 つの主要なカテゴリに分類されます。
カスタム メディア ソース オプションの場合、ドライバー モデルは完全にカスタム (独自) であるか、または従来以外のカメラ ソース (ファイル、ネットワーク ソースなど) に基づいている可能性があります。
AV ストリーム ドライバ
AV Stream Driver アプローチの主な利点は、PnP と電源管理/デバイス管理が AV Stream フレームワークによって既に処理されていることです。
ただし、基になるソースは、ハードウェアとインターフェイスするためのカーネル モード ドライバーを備えた物理デバイスである必要もあります。 UVC デバイス用には、Windows UVC 1.5 クラスドライバーが標準で提供されるため、デバイスは単にファームウェアを実装するだけで済みます。
MIPI ベースのデバイスの場合、ベンダーは独自の AV ストリーム ミニポート ドライバーを実装する必要があります。
カスタム メディア ソース
デバイス ドライバーが既に使用可能なソース (ただし、AV ストリーム ミニポート ドライバーではない) または非既存のカメラ キャプチャを使用するソースの場合、AV ストリーム ドライバーは実行可能でない可能性があります。 たとえば、ネットワーク経由で接続されている IP カメラは、AV ストリーム ドライバー モデルには適合しません。
このような状況では、フレーム サーバー モデルを使用するカスタム メディア ソースが代わりに使用されます。
特徴 | カスタム メディア ソース | AVストリームドライバー |
---|---|---|
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 カテゴリに登録して、列挙できるようにします。
注
レガシ DirectShow アプリケーションによる列挙を許可するには、ドライバーも KSCATEGORY_VIDEO と KSCATEGORY_CAPTUREに登録する必要があります。
- カスタム メディア ソース COM オブジェクトの CoCreate 可能 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_VIDEO、 KSCATEGORY_CAPTURE、 KSCATEGORY_VIDEO_CAMERA の下に登録され、標準の RGB カメラを検索するすべての UWP アプリと UWP 以外のアプリで "カメラ" を検出できるようにします。
カスタム メディア ソースで RGB 以外のストリーム (IR、深度など) も公開されている場合は、必要に応じて 、KSCATEGORY_SENSOR_CAMERAに登録することもできます。
注
ほとんどの 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 サーバーです。
注
IMFMediaSourceEx は IMFMediaSource から継承し、 IMFMediaSource はIMFMediaEventGenerator から継承します。
カスタム メディア ソース内でサポートされる各ストリームは、次のインターフェイスをサポートする必要があります。
IMFMediaEventGenerator の
IMFMediaStream の
IMFMediaStream2
注
IMFMediaStream2 は IMFMediaStream から継承し、 IMFMediaStream は IMFMediaEventGenerator から継承します。
カスタム メディア ソースを作成する方法については、 カスタム メディア ソース の作成に関するドキュメントを参照してください。 このセクションの残りの部分では、フレーム サーバー フレームワーク内でカスタム メディア ソースをサポートするために必要な違いについて説明します。
IMFGetService
IMFGetService は、フレーム サーバー カスタム メディア ソースの必須インターフェイスです。 カスタム メディア ソースが他のサービス インターフェイスを追加で公開する必要がない場合、IMFGetService はMF_E_UNSUPPORTED_SERVICEを返す可能性があります。
次の例は、サポート サービス インターフェイスのない IMFGetService 実装を示しています。
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::GetService(
_In_ REFGUID guidService,
_In_ REFIID riid,
_Out_ LPVOID * ppvObject
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
if (!ppvObject)
{
return E_POINTER;
}
*ppvObject = NULL;
// We have no supported service, just return
// MF_E_UNSUPPORTED_SERVICE for all calls.
return MF_E_UNSUPPORTED_SERVICE;
}
IMFMediaEventGenerator
上に示すように、ソース内のソースストリームと個々のストリームの両方で、独自の IMFMediaEventGenerator インターフェイスをサポートする必要があります。 ソースからの 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;
}
検索と一時停止
フレーム サーバー フレームワークでサポートされているカスタム メディア ソースでは、Seek 操作または Pause 操作はサポートされていません。 カスタム メディア ソースは、これらの操作のサポートを提供する必要はありません。 また、MFSourceSeeked イベントまたは MEStreamSeeked イベントを投稿してはなりません。
IMFMediaSource::Pause は MF_E_INVALID_STATE_TRANSITION を返すべきです(ソースがすでにシャットダウンされている場合は MF_E_SHUTDOWN )。
IKsControl
IKsControl は、すべてのカメラ関連コントロールの標準コントロール インターフェイスです。 カスタム メディア ソースでカメラ コントロールが実装されている場合、 IKsControl インターフェイスは、パイプラインがコントロール I/O をルーティングする方法です。
詳細については、次のコントロール セットのドキュメント記事を参照してください。
コントロールは省略可能であり、サポートされていない場合、返す推奨エラー コードは HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND)です。
次のコードは、サポートされているコントロールのない IKsControl 実装の例です。
// IKsControl methods
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::KsProperty(
_In_reads_bytes_(ulPropertyLength) PKSPROPERTY pProperty,
_In_ ULONG ulPropertyLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pPropertyData,
_In_ ULONG ulDataLength,
_Out_ ULONG* pBytesReturned
)
{
// ERROR_SET_NOT_FOUND is the standard error code returned
// by the AV Stream driver framework when a miniport
// driver does not register a handler for a KS operation.
// We want to mimic the driver behavior here if we do not
// support controls.
return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND);
}
_Use_decl_annotations_
IFACEMETHODIMP SimpleMediaSource::KsMethod(
_In_reads_bytes_(ulMethodLength) PKSMETHOD pMethod,
_In_ ULONG ulMethodLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pMethodData,
_In_ ULONG ulDataLength,
_Out_ ULONG* pBytesReturned
)
{
return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND);
}
_Use_decl_annotations_
IFACEMETHODIMP SimpleMediaSource::KsEvent(
_In_reads_bytes_opt_(ulEventLength) PKSEVENT pEvent,
_In_ ULONG ulEventLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pEventData,
_In_ ULONG ulDataLength,
_Out_opt_ ULONG* pBytesReturned
)
{
return HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND);
}
IMFMediaStream2
「カスタム メディア ソースの書き込み」で説明したように、IMFMediaSource::Start メソッドの完了時にソース イベント キューにポストされた MENewStream メディア イベントを介して、IMFMediaStream2 インターフェイスがカスタム メディア ソースのフレーム作業に提供されます。
IFACEMETHODIMP
SimpleMediaSource::Start(
_In_ IMFPresentationDescriptor *pPresentationDescriptor,
_In_opt_ const GUID *pguidTimeFormat,
_In_ const PROPVARIANT *pvarStartPos
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
DWORD count = 0;
PROPVARIANT startTime;
BOOL selected = false;
ComPtr<IMFStreamDescriptor> streamDesc;
DWORD streamIndex = 0;
if (pPresentationDescriptor == nullptr || pvarStartPos == nullptr)
{
return E_INVALIDARG;
}
else if (pguidTimeFormat != nullptr && *pguidTimeFormat != GUID_NULL)
{
return MF_E_UNSUPPORTED_TIME_FORMAT;
}
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
if (_sourceState != SourceState::Stopped)
{
return MF_E_INVALID_STATE_TRANSITION;
}
_sourceState = SourceState::Started;
// This checks the passed in PresentationDescriptor matches the member of streams we
// have defined internally and that at least one stream is selected
RETURN_IF_FAILED (_ValidatePresentationDescriptor(pPresentationDescriptor));
RETURN_IF_FAILED (pPresentationDescriptor->GetStreamDescriptorCount(&count));
RETURN_IF_FAILED (InitPropVariantFromInt64(MFGetSystemTime(), &startTime));
// Send event that the source started. Include error code in case it failed.
RETURN_IF_FAILED (_spEventQueue->QueueEventParamVar(MESourceStarted,
GUID_NULL,
hr,
&startTime));
// We are hardcoding this to the first descriptor
// since this sample is a single stream sample. For
// multiple streams, we need to walk the list of streams
// and for each selected stream, send the MEUpdatedStream
// or MENewStream event along with the MEStreamStarted
// event.
RETURN_IF_FAILED (pPresentationDescriptor->GetStreamDescriptorByIndex(0,
&selected,
&streamDesc));
RETURN_IF_FAILED (streamDesc->GetStreamIdentifier(&streamIndex));
if (streamIndex >= NUM_STREAMS)
{
return MF_E_INVALIDSTREAMNUMBER;
}
if (selected)
{
ComPtr<IUnknown> spunkStream;
MediaEventType met = (_wasStreamPreviouslySelected ? MEUpdatedStream : MENewStream);
// Update our internal PresentationDescriptor
RETURN_IF_FAILED (_spPresentationDescriptor->SelectStream(streamIndex));
RETURN_IF_FAILED (_stream.Get()->SetStreamState(MF_STREAM_STATE_RUNNING));
RETURN_IF_FAILED (_stream.As(&spunkStream));
// Send the MEUpdatedStream/MENewStream to our source event
// queue.
RETURN_IF_FAILED (_spEventQueue->QueueEventParamUnk(met,
GUID_NULL,
S_OK,
spunkStream.Get()));
// But for our stream started (MEStreamStarted), we post to our
// stream event queue.
RETURN_IF_FAILED (_stream.Get()->QueueEvent(MEStreamStarted,
GUID_NULL,
S_OK,
&startTime));
}
_wasStreamPreviouslySelected = selected;
return hr;
}
これは、 IMFPresentationDescriptor を介して選択された各ストリームに対して行う必要があります。
ビデオ ストリームを含むカスタム メディア ソースの場合、 MEEndOfStream イベントと MEEndOfPresentation イベントを送信しないでください。
ストリーム属性
すべてのカスタム メディア ソース ストリームには 、MF_DEVICESTREAM_STREAM_CATEGORY が PINNAME_VIDEO_CAPTUREに設定されている必要があります。 PINNAME_VIDEO_PREVIEW は、カスタム メディア ソースではサポートされていません。
注
PINNAME_IMAGEはサポートされていますが、推奨されません。 PINNAME_IMAGEを使用してストリームを公開するには、カスタム メディア ソースですべての写真トリガー コントロールをサポートする必要があります。 詳細については、以下 の「フォトストリームコントロール」 セクションを参照してください。
MF_DEVICESTREAM_STREAM_ID は、すべてのストリームに必須の属性です。 0 から始まるインデックスである必要があります。 そのため、最初のストリームの ID は 0、2 番目のストリームの ID は 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 に設定すると、ストリームはフレーム サーバーによって "共有可能" としてマークされます。 これにより、別のアプリで使用されている場合でも、アプリケーションは共有モードでストリームを開きます。
この属性が設定されていない場合、フレーム サーバーは、マークされていない最初のストリームの共有を許可します (カスタム メディア ソースにストリームが 1 つしかない場合、そのストリームは共有としてマークされます)。
この属性が 0 に設定されている場合、フレーム サーバーは共有アプリからのストリームをブロックします。 カスタム メディア ソースがこの属性を 0 に設定してすべてのストリームをマークする場合、共有アプリケーションはソースを初期化できません。
サンプルの割り当て
すべてのメディア フレームは IMFSample として生成する必要があります。 カスタム メディア ソースでは 、MFCreateSample 関数を使用して IMFSample のインスタンスを割り当て、 AddBuffer メソッドを使用してメディア バッファーを追加する必要があります。
各 IMFSample には、サンプル時間とサンプル期間が設定されている必要があります。 すべてのサンプル タイムスタンプは、QPC 時刻 (QueryPerformanceCounter) に基づいている必要があります。
可能な場合は、カスタム メディア ソースで MFGetSystemTime 関数を使用することをお勧めします。 この関数は QueryPerformanceCounter のラッパーであり、QPC ティックを 100 ナノ秒単位に変換します。
カスタム メディア ソースでは内部クロックが使用される場合がありますが、すべてのタイムスタンプは、現在の QPC に基づいて 100 ナノ秒単位に関連付ける必要があります。
メディア バッファー
IMFSample に追加されるすべてのメディア バッファーは、標準の MF バッファー割り当て関数を使用する必要があります。 カスタム メディア ソースでは、独自の IMFMediaBuffer インターフェイスを実装したり、メディア バッファーを直接割り当てないようにする必要があります (たとえば、new/malloc/VirtualAlloc などをフレーム データに使用することはできません)。
メディア フレームを割り当てるには、次のいずれかの API を使用します。
MFCreateMemoryBuffer と MFCreateAlignedMemoryBuffer は、非整列メディア データに使用する必要があります。 通常、これらはカスタム サブタイプまたは圧縮サブタイプ (H264/HEVC/MJPG など) です。
システム メモリを使用する既知の非圧縮メディアの種類 (YUY2、NV12 など) の場合は、 MFCreate2DMediaBuffer を使用することをお勧めします。
DX サーフェスを使用する場合 (レンダリングやエンコードなどの GPU アクセラレータ操作の場合)、 MFCreateDXGISurfaceBuffer を使用する必要があります。
MFCreateDXGISurfaceBuffer は DX サーフェスを作成しません。 サーフェスは、 IMFMediaSourceEx::SetD3DManager メソッドを使用してメディア ソースに渡される DXGI マネージャーを使用して作成されます。
IMFDXGIDeviceManager::OpenDeviceHandle は、選択した D3D デバイスに関連付けられたハンドルを提供します。 その後、IMFDXGIDeviceManager::GetVideoService メソッドを使用して ID3D11Device インターフェイスを取得できます。
使用されるバッファーの種類に関係なく、作成された IMFSample は、メディア ストリームの IMFMediaEventGenerator の MEMediaSample イベントを介してパイプラインに提供する必要があります。
カスタム メディア ソースと基になる IMFMediaStream コレクションの両方に同じ IMFMediaEventQueue を使用できますが、これを行うと、メディア ソース イベントとストリーム イベント (メディア フローを含む) がシリアル化されることに注意してください。 複数のストリームを含むソースの場合、これは望ましくありません。
次のコード切り取りは、メディア ストリームの実装例を示しています。
IFACEMETHODIMP
SimpleMediaStream::RequestSample(
_In_ IUnknown *pToken
)
{
HRESULT hr = S_OK;
auto lock = _critSec.Lock();
ComPtr<IMFSample> sample;
ComPtr<IMFMediaBuffer> outputBuffer;
LONG pitch = IMAGE_ROW_SIZE_BYTES;
BYTE *bufferStart = nullptr; // not used
DWORD bufferLength = 0;
BYTE *pbuf = nullptr;
ComPtr<IMF2DBuffer2> buffer2D;
RETURN_IF_FAILED (_CheckShutdownRequiresLock());
RETURN_IF_FAILED (MFCreateSample(&sample));
RETURN_IF_FAILED (MFCreate2DMediaBuffer(NUM_IMAGE_COLS,
NUM_IMAGE_ROWS,
D3DFMT_X8R8G8B8,
false,
&outputBuffer));
RETURN_IF_FAILED (outputBuffer.As(&buffer2D));
RETURN_IF_FAILED (buffer2D->Lock2DSize(MF2DBuffer_LockFlags_Write,
&pbuf,
&pitch,
&bufferStart,
&bufferLength));
RETURN_IF_FAILED (WriteSampleData(pbuf, pitch, bufferLength));
RETURN_IF_FAILED (buffer2D->Unlock2D());
RETURN_IF_FAILED (sample->AddBuffer(outputBuffer.Get()));
RETURN_IF_FAILED (sample->SetSampleTime(MFGetSystemTime()));
RETURN_IF_FAILED (sample->SetSampleDuration(333333));
if (pToken != nullptr)
{
RETURN_IF_FAILED (sample->SetUnknown(MFSampleExtension_Token, pToken));
}
RETURN_IF_FAILED (_spEventQueue->QueueEventParamUnk(MEMediaSample,
GUID_NULL,
S_OK,
sample.Get()));
return hr;
}
IMFActivate を公開するためのカスタム メディア ソース拡張機能 (Windows 10 バージョン 1809 で利用可能)
カスタム メディア ソースでサポートする必要がある上記のインターフェイスの一覧に加えて、フレーム サーバー アーキテクチャ内のカスタム メディア ソース操作によって課される制限の 1 つは、パイプラインを介して UMDF ドライバーのインスタンスを 1 つだけ "アクティブ化" できることです。
たとえば、UMDF スタブドライバーをインストールする物理デバイスがあり、非 AV ストリーム ドライバー パッケージに加えてそれを使用する場合、それらの物理デバイスを複数コンピューターに接続するとします。UMDF ドライバーは各インスタンスで一意のシンボリックリンク名を取得しますが、カスタムメディアソースのアクティベーションパスには、そのカスタムメディアソースの作成時に関連するシンボリックリンク名を伝達する手段がありません。
カスタム メディア ソースでは、IMFMediaSource::Start が呼び出されたときに、カスタム メディア ソースの属性ストア (IMFMediaSourceEx::GetSourceAttributes メソッドを通じてカスタム メディア ソースから返される属性ストア) で標準のMF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK属性を検索できます。
ただし、これにより、HW リソースの取得が作成/初期化時間ではなく開始時刻に遅延するため、起動待ち時間が長くなる可能性があります。
このため、Windows 10 バージョン 1809 では、カスタム メディア ソースが必要に応じて IMFActivate インターフェイスを公開する場合があります。
注
IMFActivate は IMFAttributes から継承します。
IMFActivate
カスタム メディア ソースの COM サーバーが IMFActivate インターフェイスをサポートしている場合、デバイス初期化情報は、IMFActivate によって継承された IMFAttributes を介して COM サーバーに提供されます。 そのため、 IMFActivate::ActivateObject が呼び出されると、 IMFActivate の属性ストアには、UMDF スタブ ドライバーのシンボリック リンク名と、ソースの作成/初期化時にパイプライン/アプリケーションによって提供されるその他の構成設定が含まれます。
カスタム メディア ソースでは、このメソッド呼び出しを使用して、必要なハードウェア リソースを取得する必要があります。
注
ハードウェア リソースの取得に 200 ミリ秒を超える時間がかかる場合は、ハードウェア リソースを非同期的に取得することをお勧めします。 カスタム メディア ソースのアクティブ化は、ハードウェア リソースの取得をブロックしないでください。 代わりに、ハードウェア リソースの取得に対して IMFMediaSource::Start 操作をシリアル化する必要があります。
IMFActivate、DetachObject、ShutdownObject によって公開される他の 2 つのメソッドは、E_NOTIMPLを返す必要があります。
カスタム メディア ソースは、IMFMediaSource と同じ COM オブジェクト内に IMFActivate および IMFAttributes インターフェイスを実装することを選択できます。 これを行う場合は、IMFMediaSourceEx::GetSourceAttributes が IMFActivate のインターフェイスと同じ IMFAttributes インターフェイスを返すことをお勧めします。
カスタム メディア ソースが同じオブジェクトを使用して IMFActivate と IMFAttributes を 実装していない場合、カスタム メディア ソースは、 IMFActivate 属性ストアに設定されているすべての属性をカスタム メディア ソースのソース属性ストアにコピーする必要があります。
エンコードされたカメラ ストリーム
カスタム メディア ソースでは、圧縮メディアの種類 (HEVC または H264 基本ストリーム) が公開される場合があり、OS パイプラインはカスタム メディア ソースのエンコード パラメーターのソースと構成を完全にサポートします (エンコード パラメーターは ICodecAPI を介して通信され、 IKsControl::KsProperty 呼び出しとしてルーティングされます)。
// IKsControl methods
_Use_decl_annotations_
IFACEMETHODIMP
SimpleMediaSource::KsProperty(
_In_reads_bytes_(ulPropertyLength) PKSPROPERTY pProperty,
_In_ ULONG ulPropertyLength,
_Inout_updates_to_(ulDataLength, *pBytesReturned) LPVOID pPropertyData,
_In_ ULONG ulDataLength,
_Out_ ULONG* pBytesReturned
);
IKsControl::KsProperty メソッドに渡される KSPROPERTY 構造体には、次の情報があります。
KSPROPERTY.Set = Encoder Property GUID
KSPROPERTY.Id = 0
KSPROPERTY.Flags = (KSPROPERTY_TYPE_SET or KSPROPERTY_TYPE_GET)
エンコーダー プロパティ GUID は、 コーデック API プロパティで定義されている使用可能なプロパティの一覧です。
Encoder プロパティのペイロードは、上記で宣言した KsProperty メソッドの pPropertyData フィールドを介して渡されます。
キャプチャーエンジンの要件
エンコードされたソースはフレーム サーバーで完全にサポートされていますが、Windows.Media.Capture.MediaCapture オブジェクトで使用されるクライアント側キャプチャ エンジン (IMFCaptureEngine) では、追加の要件が課されます。
ストリームは、すべてエンコード (HEVC または H264) またはすべての非圧縮である必要があります (このコンテキストでは、MJPG は非圧縮として扱われます)。
使用できる非圧縮ストリームが少なくとも 1 つ必要です。
注
これらの要件は、この記事で説明するカスタム メディア ソースの要件に加えて行われます。 ただし、キャプチャ エンジンの要件は、クライアント アプリケーションが 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 顔認識をサポートするように設計されている場合は、Face Authentication プロファイルを発行することをお勧めします。 Face 認証プロファイルの要件は次のとおりです。
Face Authentication 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 以上である必要があります (これは、マルチスペクトル認証が適用されている場合にのみ必要です)。
Face 認証プロファイルには、プロファイル 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 プロパティ セットをサポートする必要があります。 詳細については、「 ビデオ コントロールのプロパティ」を参照してください。