Compartilhar via


Fonte de Mídia Personalizada do Servidor de Quadros

Este tópico fornece informações sobre a implementação de uma Fonte de Mídia Personalizada dentro da arquitetura do Servidor de Quadros.

Opções de Fluxo de AV e Fonte de Mídia Personalizada

Ao decidir como fornecer suporte ao fluxo de captura de vídeo dentro da arquitetura do Servidor de Quadros, há duas opções de main: Fluxo AV e Fonte de Mídia Personalizada.

O modelo do AV Stream é o modelo de driver de câmera padrão usando um driver de miniporto AV Stream (driver de modo kernel). Normalmente, os drivers AV Stream se enquadram em duas categorias de main: drivers baseados em MIPI e drivers de classe de vídeo USB.

Para a opção Fonte de Mídia Personalizada, o modelo de driver pode ser completamente personalizado (proprietário) ou pode ser baseado em uma fonte de câmera não tradicional (como fontes de rede ou arquivo).

AV Stream Driver

O main benefício de uma abordagem do AV Stream Driver é que o PnP e o Gerenciamento de Energia/Gerenciamento de Dispositivos já são tratados pela estrutura do AV Stream.

No entanto, isso também significa que a origem subjacente deve ser um dispositivo físico com um driver de modo kernel para interface com o hardware. Para dispositivos UVC, um driver de classe UVC 1.5 do Windows é fornecido na caixa de entrada para que os dispositivos simplesmente precisem implementar seu firmware.

Para dispositivos baseados em MIPI, o fornecedor precisará implementar seu próprio driver de miniporto AV Stream.

Fonte de mídia personalizada

Para fontes cujo driver de dispositivo já está disponível (mas não um driver de miniporto AV Stream) ou fontes que usam captura de câmera não tradicional, um Driver de Fluxo de AV pode não ser viável. Por exemplo, uma câmera IP conectada pela rede não caberia em um modelo do AV Stream Driver.

Nessas situações, uma Fonte de Mídia Personalizada usando o modelo do Servidor de Quadros seria uma alternativa.

Recursos Fonte de mídia personalizada AV Stream Driver
PnP e Gerenciamento de Energia Deve ser implementado pelo driver de origem e/ou stub Fornecido pela estrutura do AV Stream
Plug-in de modo de usuário Não disponível. A Fonte de Mídia Personalizada incorpora a lógica de modo de usuário específica do OEM/IHV. DMFT, Platform DMFT e MFT0 para implementação herdada
Grupo de sensores Com suporte Com suporte
Perfil da Câmera V2 Com suporte Com suporte
Perfil da Câmera V1 Sem suporte Com suporte

Requisitos de fonte de mídia personalizada

Com a introdução do serviço Câmera do Windows Frame Server (conhecido como Servidor de Quadros), isso pode ser feito por meio de uma Fonte de Mídia Personalizada. Isso requer dois componentes main:

  • Um pacote de driver com um driver stubbed projetado para registrar/habilitar uma interface de dispositivo de câmera.

  • Uma DLL COM que hospeda a Fonte de Mídia Personalizada.

O primeiro requisito é necessário para duas finalidades:

  • Um processo de verificação para garantir que a Fonte de Mídia Personalizada seja instalada por meio de um processo confiável (o pacote de driver requer certificação WHQL).

  • Suporte para a enumeração PnP padrão e a descoberta da "câmera".

Segurança

A Fonte de Mídia Personalizada para o Servidor de Quadros difere da Fonte de Mídia Personalizada genérica em termos de segurança da seguinte maneira:

  • A Fonte de Mídia Personalizada do Servidor de Quadros é executada como Serviço Local (não deve ser confundida com o Sistema Local; O Serviço Local é uma conta com privilégios muito baixos em computadores Windows).

  • A Fonte de Mídia Personalizada do Servidor de Quadros é executada na Sessão 0 (sessão do Serviço do Sistema) e não pode interagir com a área de trabalho do usuário.

Dadas essas restrições, as Fontes de Mídia Personalizadas do Servidor de Quadros não devem tentar acessar partes protegidas do sistema de arquivos nem do Registro. Em geral, o acesso de leitura é permitido, mas o acesso de gravação não é.

Desempenho

Como parte do modelo do Servidor de Quadros, há dois casos em como as Fontes de Mídia Personalizadas serão instanciadas pelo Servidor de Quadros:

  • Durante a geração/publicação do Grupo de Sensores.

  • Durante a ativação da "câmera"

A geração do Grupo de Sensores normalmente é feita durante a instalação do dispositivo e/ou o ciclo de energia. Considerando isso, recomendamos fortemente que as Fontes de Mídia Personalizadas evitem qualquer processamento significativo durante sua criação e adiem qualquer atividade desse tipo para a função IMFMediaSource::Start . A geração do Grupo de Sensores não tentará iniciar a Fonte de Mídia Personalizada, apenas consultar os vários tipos de fluxos/mídia disponíveis e informações de atributo de origem/fluxo.

Stub Driver

Há dois requisitos mínimos para o pacote de driver e o driver stub.

O driver stub pode ser gravado usando o WDF (UMDF ou KMDF) ou o modelo de driver WDM.

Os requisitos do driver são:

  • Registre sua interface de dispositivo "câmera" (a Fonte de Mídia Personalizada) na categoria KSCATEGORY_VIDEO_CAMERA para que possa ser enumerada.

Observação

Para permitir a enumeração por aplicativos DirectShow herdados, o driver também precisará se registrar no KSCATEGORY_VIDEO e KSCATEGORY_CAPTURE.

  • Adicione uma entrada do Registro no nó da interface do dispositivo (use a diretiva AddReg na seção INF DDInstall.Interface do driver) que declara o CLSID cocreate-able do objeto COM de origem de mídia personalizada. Isso deve ser adicionado usando o seguinte nome de valor do Registro: CustomCaptureSourceClsid.

Isso permite que a origem da "câmera" seja descoberta por aplicativos e, quando ativada, informa o serviço Servidor de Quadros para interceptar a chamada de ativação e roteá-la novamente para a Fonte de Mídia Personalizada CoCreada.

INF de exemplo

O exemplo a seguir mostra um INF típico para um driver stub de Fonte de Mídia Personalizada:

;/*++
;
;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

A Fonte de Mídia Personalizada acima registra em KSCATEGORY_VIDEO, KSCATEGORY_CAPTURE e KSCATEGORY_VIDEO_CAMERA para garantir que a "câmera" seja detectável por qualquer aplicativo UWP e não UWP que pesquise uma câmera RGB padrão.

Se a Fonte de Mídia Personalizada também expor fluxos não RGB (IR, Profundidade e assim por diante), opcionalmente, ela também poderá se registrar no KSCATEGORY_SENSOR_CAMERA.

Observação

A maioria das webcams baseadas em USB exporá os formatos YUY2 e MJPG. Devido a esse comportamento, muitos aplicativos DirectShow herdados são escritos com a suposição de que YUY2/MJPG está disponível. Para garantir a compatibilidade com esse aplicativo, é recomendável que o tipo de mídia YUY2 seja disponibilizado na Fonte de Mídia Personalizada se a compatibilidade do aplicativo herdado for desejada.

Implementação do driver stub

Além do INF, o stub do driver também deve registrar e habilitar as interfaces do dispositivo da câmera. Normalmente, isso é feito durante a operação de DRIVER_ADD_DEVICE .

Consulte a função de retorno de chamada DRIVER_ADD_DEVICE para drivers baseados em WDM e a função WdfDriverCreate para drivers UMDF/KMDF.

Veja a seguir uma captura de código de um stub de driver UMDF que manipula essa operação:

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

Operação PnP

Assim como qualquer outra câmera física, é recomendável que o driver stub gerencie pelo menos as operações PnP de habilitar e desabilitar o dispositivo quando a origem subjacente for removida/anexada. Por exemplo, se sua Fonte de Mídia Personalizada estiver usando uma fonte de rede (como uma câmera IP), talvez você queira disparar uma remoção de dispositivo quando essa fonte de rede não estiver mais disponível.

Isso garante que os aplicativos escutem a adição/remoção do dispositivo por meio das APIs PnP para obter as notificações adequadas. E garante que uma fonte que não está mais disponível não possa ser enumerada.

Para drivers UMDF e KMDF, consulte a documentação da função WdfDeviceSetDeviceState .

Para drivers WMD, consulte a documentação da função IoSetDeviceInterfaceState .

DLL de origem de mídia personalizada

A Fonte de Mídia Personalizada é um servidor COM inproc padrão que deve implementar as seguintes interfaces:

Observação

IMFMediaSourceEx herda de IMFMediaSource e IMFMediaSource herda de IMFMediaEventGenerator.

Cada fluxo com suporte na Fonte de Mídia Personalizada deve dar suporte às seguintes interfaces:

Observação

IMFMediaStream2 herda de IMFMediaStream e IMFMediaStream herda de IMFMediaEventGenerator.

Consulte a documentação Escrevendo uma fonte de mídia personalizada sobre como criar uma fonte de mídia personalizada. O restante desta seção explicará as diferenças necessárias para dar suporte à Fonte de Mídia Personalizada na estrutura do Servidor de Quadros.

IMFGetService

IMFGetService é uma interface obrigatória para a Fonte de Mídia Personalizada do Servidor de Quadros. IMFGetService poderá retornar MF_E_UNSUPPORTED_SERVICE se sua Fonte de Mídia Personalizada não precisar expor nenhuma interface de serviço adicional.

O exemplo a seguir mostra uma implementação imfGetService sem interfaces de serviço de suporte:

_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

Conforme mostrado acima, tanto a origem quanto os fluxos individuais dentro da origem devem dar suporte à própria interface IMFMediaEventGenerator . Todos os fluxos de controle e dados de pipeline do MF da origem são gerenciados por meio do gerador de eventos enviando IMFMediaEvent específico.

Para implementar IMFMediaEventGenerator, a Fonte de Mídia Personalizada deve usar a API MFCreateEventQueue para criar um IMFMediaEventQueue e rotear todos os métodos para IMFMediaEventGenerator para o objeto queue:

IMFMediaEventGenerator tem os seguintes métodos:

// 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);

O código a seguir mostra a implementação recomendada da interface IMFMediaEventGenerator . A implementação da Fonte de Mídia Personalizada exporá a interface IMFMediaEventGenerator e os métodos dessa interface encaminharão as solicitações para o objeto IMFMediaEventQueue criado durante a criação/inicialização da fonte de mídia.

No código a seguir, _spEventQueue objeto é o IMFMediaEventQueue criado usando a função MFCreateEventQueue :

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

Buscando e pausando

Fontes de mídia personalizadas com suporte por meio da estrutura do Servidor de Quadros não dão suporte a operações Seek ou Pause. Sua Fonte de Mídia Personalizada não precisa fornecer suporte para essas operações e não deve postar o evento MFSourceSeeked ou MEStreamSeeked .

IMFMediaSource::P ause deve retornar MF_E_INVALID_STATE_TRANSITION (ou MF_E_SHUTDOWN se a origem já tiver sido desligada).

IKsControl

IKsControl é a interface de controle padrão para todos os controles relacionados à câmera. Se a Fonte de Mídia Personalizada implementar qualquer controle de câmera, a interface IKsControl será como o pipeline roteará a E/S do controle.

Para obter mais informações, consulte os seguintes tópicos da documentação do Conjunto de Controles:

Os controles são opcionais e, se não houver suporte, o código de erro recomendado a ser retornado é HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND).

O código a seguir é um exemplo de implementação IKsControl sem controles com suporte:

// 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

Conforme explicado em Escrevendo uma fonte de mídia personalizada, a interface IMFMediaStream2 é fornecida para o trabalho de quadro de sua Fonte de Mídia Personalizada por meio de um evento de mídia MENewStream postado na fila de eventos de origem durante a conclusão do método IMFMediaSource::Start :

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

Isso deve ser feito para cada fluxo selecionado por meio do IMFPresentationDescriptor.

Para fontes de mídia personalizadas com fluxo de vídeo, os eventos MEEndOfStream e MEEndOfPresentation não devem ser enviados.

Atributos de fluxo

Todos os fluxos de Fonte de Mídia Personalizada devem ter o MF_DEVICESTREAM_STREAM_CATEGORY definido como PINNAME_VIDEO_CAPTURE. PINNAME_VIDEO_PREVIEW não tem suporte para fontes de mídia personalizadas.

Observação

PINNAME_IMAGE, embora haja suporte, não é recomendado. Expor um fluxo com PINNAME_IMAGE requer a Fonte de Mídia Personalizada para dar suporte a todos os controles de gatilho de foto. Consulte a seção Controles de Fluxo de Fotos abaixo para obter mais detalhes.

MF_DEVICESTREAM_STREAM_ID é um atributo obrigatório para todos os fluxos. Deve ser um índice baseado em 0. Portanto, o primeiro fluxo tem uma ID de 0, um segundo fluxo uma ID de 1 e assim por diante.

Veja a seguir uma lista de atributos recomendados no fluxo:

MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES

MF_DEVICESTREAM_ATTRIBUTE_FRAMESOURCE_TYPES é um atributo UINT32 que é um valor bitmasked do tipo de fluxo. Ele pode ser definido como qualquer um dos seguintes (embora esses tipos sejam um sinalizador de máscara de bits, é recomendável que os tipos de origem não sejam misturados se possível):

Tipo Sinalizador Descrição
MFFrameSourceTypes_Color 0x0001 Fluxo de cores RGB padrão
MFFrameSourceTypes_Infrared 0x0002 Fluxo de IR
MFFrameSourceTypes_Depth 0x0004 Fluxo de profundidade
MFFrameSourceTypes_Image 0x0008 Fluxo de imagem (subtipo não vídeo, normalmente JPEG)
MFFrameSourceTypes_Custom 0x0080 Tipo de fluxo personalizado

MF_DEVICESTREAM_FRAMESERVER_SHARED

MF_DEVICESTREAM_FRAMESERVER_SHARED é um atributo UINT32 que pode ser definido como 0 ou 1. Se definido como 1, ele marca o fluxo como sendo "compartilhável" pelo Servidor de Quadros. Isso permitirá que os aplicativos abram o fluxo em um modo compartilhado, mesmo quando usados por outro aplicativo.

Se esse atributo não estiver definido, o Servidor de Quadros permitirá que o primeiro fluxo não marcado seja compartilhado (se a Fonte de Mídia Personalizada tiver apenas um fluxo, esse fluxo será marcado como compartilhado).

Se esse atributo for definido como 0, o Servidor de Quadros bloqueará o fluxo de aplicativos compartilhados. Se a Fonte de Mídia Personalizada marcar todos os fluxos com esse atributo definido como 0, nenhum aplicativo compartilhado poderá inicializar a origem.

Alocação de exemplo

Todos os quadros de mídia devem ser produzidos como um IMFSample. Fontes de mídia personalizadas devem usar a função MFCreateSample para alocar uma instância de IMFSample e usar o método AddBuffer para adicionar buffers de mídia.

Cada IMFSample deve ter o tempo de amostra e a duração da amostra definidos. Todos os carimbos de data/hora de exemplo devem ser baseados no tempo de QPC (QueryPerformanceCounter).

É recomendável que fontes de mídia personalizadas usem a função MFGetSystemTime sempre que possível. Essa função é um wrapper em torno de QueryPerformanceCounter e converte os tiques de QPC em 100 unidades de nanossegundos.

As Fontes de Mídia Personalizadas podem usar um relógio interno, mas todos os carimbos de data/hora devem ser correlacionados a 100 unidades de nanossegundos com base no QPC atual.

Buffer de Mídia

Todos os buffers de mídia adicionados ao IMFSample devem usar as funções de alocação de buffer de MF padrão. As Fontes de Mídia Personalizadas não devem implementar suas próprias interfaces IMFMediaBuffer ou tentar alocar o buffer de mídia diretamente (por exemplo, new/malloc/VirtualAlloc e assim por diante, não devem ser usadas para dados de quadro).

Use qualquer uma das seguintes APIs para alocar quadros de mídia:

MFCreateMemoryBuffer e MFCreateAlignedMemoryBuffer devem ser usados para dados de mídia alinhados sem avanço. Normalmente, esses seriam subtipos personalizados ou subtipos compactados (como H264/HEVC/MJPG).

Para tipos de mídia descompactados conhecidos (como YUY2, NV12 e assim por diante) usando memória do sistema, é recomendável usar MFCreate2DMediaBuffer.

Para usar superfícies DX (para operações aceleradas por GPU, como renderização e/ou codificação), MFCreateDXGISurfaceBuffer deve ser usado.

MFCreateDXGISurfaceBuffer não cria a superfície DX. A superfície é criada usando o Gerenciador DXGI passado para a fonte de mídia por meio do método IMFMediaSourceEx::SetD3DManager .

O IMFDXGIDeviceManager::OpenDeviceHandle fornecerá o identificador associado ao dispositivo D3D selecionado. A interface ID3D11Device pode ser obtida usando o método IMFDXGIDeviceManager::GetVideoService .

Independentemente do tipo de buffer usado, o IMFSample criado deve ser fornecido ao pipeline por meio do evento MEMediaSample no IMFMediaEventGenerator do fluxo de mídia.

Embora seja possível usar o mesmo IMFMediaEventQueue para a Fonte de Mídia Personalizada e a coleção subjacente de IMFMediaStream, deve-se observar que isso resultará na serialização dos eventos de origem de mídia e eventos de fluxo (que inclui o fluxo de mídia). Para fontes com vários fluxos, isso não é desejável.

O snip de código a seguir mostra uma implementação de exemplo do fluxo de mídia:

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

Extensão de Fonte de Mídia Personalizada para expor IMFActivate (disponível em Windows 10, versão 1809)

Além da lista acima de interfaces que devem ter suporte para uma Fonte de Mídia Personalizada, uma das limitações impostas pela operação Fonte de Mídia Personalizada dentro da arquitetura do Servidor de Quadros é que só pode haver uma instância do driver UMDF "ativado" por meio do pipeline.

Por exemplo, se você tiver um dispositivo físico que instala um driver stub umDF além de seu pacote de driver stream não AV e anexar mais de um desses dispositivos físicos a um computador, enquanto cada instância do driver UMDF obterá um nome de link simbólico exclusivo, o caminho de ativação para a Fonte de Mídia Personalizada não terá meios para comunicar o nome simbólico do link associado à Fonte de Mídia Personalizada em o momento da criação.

A Fonte de Mídia Personalizada pode procurar o atributo MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK padrão no repositório de atributos da Fonte de Mídia Personalizada (o repositório de atributo retornado da Fonte de Mídia Personalizada por meio do método IMFMediaSourceEx::GetSourceAttributes ) no momento em que IMFMediaSource::Start é invocado.

No entanto, isso pode resultar em uma latência de inicialização mais alta, pois isso adiará a aquisição de recursos do HW para iniciar a hora em vez do tempo de criação/inicialização.

Por isso, em Windows 10, versão 1809, fontes de mídia personalizadas podem, opcionalmente, expor uma interface IMFActivate.

Observação

IMFActivate herda de IMFAttributes.

IMFActivate

Se o servidor COM para a Fonte de Mídia Personalizada der suporte à interface IMFActivate , as informações de inicialização do dispositivo serão fornecidas ao servidor COM por meio dos IMFAttributes herdados pelo IMFActivate. Portanto, quando IMFActivate::ActivateObject for invocado, o repositório de atributos do IMFActivate conterá o nome simbólico do link do driver stub da UMDF e quaisquer configurações adicionais fornecidas pelo pipeline/aplicativo no momento da criação/inicialização da origem.

A Fonte de Mídia Personalizada deve usar essa invocação de método para adquirir todos os recursos de hardware necessários.

Observação

Se a aquisição de recursos de hardware levar mais de 200 milissegundos, é recomendável que o recurso de hardware seja adquirido de forma assíncrona. A ativação da Fonte de Mídia Personalizada não deve bloquear a aquisição de recursos de hardware. Em vez disso, a operação IMFMediaSource::Start deve ser serializada em relação à aquisição de recursos de hardware.

Os dois métodos adicionais expostos por IMFActivate, DetachObject e ShutdownObject devem retornar E_NOTIMPL.

A Fonte de Mídia Personalizada pode optar por implementar a interface IMFActivate e IMFAttributes dentro do mesmo objeto COM que o IMFMediaSource. Se isso for feito, é recomendável que o IMFMediaSourceEx::GetSourceAttributes retorne a mesma interface IMFAttributes que as do IMFActivate.

Se a Fonte de Mídia Personalizada não implementar IMFActivate e IMFAttributes com o mesmo objeto, a Fonte de Mídia Personalizada deverá copiar todos os atributos definidos no repositório de atributos IMFActivate no repositório de atributos de origem da Fonte de Mídia Personalizada.

Fluxo de câmera codificado

Uma Fonte de Mídia Personalizada pode expor tipos de mídia compactados (fluxos elementares HEVC ou H264) e o pipeline do sistema operacional dá suporte total à origem e à configuração dos parâmetros de codificação na Fonte de Mídia Personalizada (os parâmetros de codificação são comunicados por meio do ICodecAPI, que é roteado como uma chamada 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
    );

A estrutura KSPROPERTY passada para o método IKsControl::KsProperty terá as seguintes informações:

KSPROPERTY.Set = Encoder Property GUID
KSPROPERTY.Id = 0
KSPROPERTY.Flags = (KSPROPERTY_TYPE_SET or KSPROPERTY_TYPE_GET)

Onde o GUID da Propriedade do Codificador é a lista de propriedades disponíveis definidas em Propriedades da API codec.

O conteúdo da Propriedade Encoder será passado por meio do campo pPropertyData do método KsProperty declarado acima.

Requisitos do Mecanismo de Captura

Embora as fontes codificadas sejam totalmente compatíveis com o Frame Server, o MECANISMO de Captura do lado do cliente (IMFCaptureEngine) usado pelo objeto Windows.Media.Capture.MediaCapture impõe requisitos adicionais:

  • O fluxo deve ser todo codificado (HEVC ou H264) ou todos descompactados (nesse contexto, o MJPG é tratado como descompactado).

  • Deve haver pelo menos um fluxo descompactado disponível.

Observação

Esses requisitos são além dos requisitos de Fonte de Mídia Personalizada descritos neste tópico. No entanto, os Requisitos do Mecanismo de Captura só são impostos quando o aplicativo cliente usa a Fonte de Mídia Personalizada por meio da API IMFCaptureEngine ou Windows.Media.Capture.MediaCapture .

Perfis de câmera (disponíveis em Windows 10, versão 1803 e posterior)

O suporte ao Perfil de Câmera está disponível para Fontes de Mídia Personalizadas. O mecanismo recomendado é publicar o perfil por meio do atributo MF_DEVICEMFT_SENSORPROFILE_COLLECTION do atributo de origem (IMFMediaSourceEx::GetSourceAttributes).

O atributo MF_DEVICEMFT_SENSORPROFILE_COLLECTION é um IUnknown da interface IMFSensorProfileCollection . IMFSensorProfileCollection pode ser obtido usando a função 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);
}

Perfil de Autenticação Facial

Se a Fonte de Mídia Personalizada for projetada para dar suporte a Windows Hello Reconhecimento Facial, é recomendável publicar um Perfil de Autenticação Facial. Os requisitos de um Perfil de Autenticação Facial são:

  • O Controle DDI de Autenticação Facial deve ter suporte em um único fluxo de IR. Para obter mais informações, consulte KSPROPERTY_CAMERACONTROL_EXTENDED_FACEAUTH_MODE.

  • O fluxo de IR deve ter pelo menos 340 x 340 a 15 fps. O formato deve ser L8, NV12 ou MJPG marcado com compactação L8.

  • O fluxo RGB deve ter pelo menos 480 x 480 a 7,5 fps (isso só será necessário se a autenticação Multispectrum for imposta).

  • O Perfil de Autenticação Facial deve ter a ID do Perfil de: KSCAMERAPROFILE_FaceAuth_Mode,0.

Recomendamos que o Perfil de Autenticação Facial anuncie apenas um tipo de mídia para cada um dos fluxos IR e RGB.

Controles de fluxo de fotos

Se fluxos de fotos independentes forem expostos marcando um dosMF_DEVICESTREAM_STREAM_CATEGORY do fluxo como PINNAME_IMAGE, um fluxo com categoria de fluxo de PINNAME_VIDEO_CAPTURE será necessário (por exemplo, um único fluxo expondo apenas o PINNAME_IMAGE não é uma fonte de mídia válida).

Por meio de IKsControl, o conjunto de propriedades PROPSETID_VIDCAP_VIDEOCONTROL deve ter suporte. Para obter mais informações, consulte Propriedades de controle de vídeo.