Compartilhar via


Fonte de mídia personalizada do Frame Server

Este artigo fornece informações sobre a implementação de uma fonte de mídia personalizada dentro da arquitetura do Servidor de Quadros.

Opções de Transmissão 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 principais: Fluxo AV e Fonte de Mídia Personalizada.

O modelo AV Stream é o modelo padrão de driver de câmera, utilizando um miniport driver AV Stream (driver em modo kernel). Normalmente, os drivers do AV Stream se enquadram em duas categorias principais: 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).

Driver de Fluxo de Áudio e Vídeo

O principal 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 incluído no sistema, de forma que os dispositivos simplesmente precisem implementar seu firmware.

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

Fonte de mídia personalizada

Para fontes cujo driver de dispositivo já está disponível (mas não um driver de miniporto do 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 se encaixaria 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.

Características Fonte de mídia personalizada Driver de Fluxo de Áudio e Vídeo
PnP e Gerenciamento de Energia Deve ser implementado pelo driver de origem e/ou stub Fornecido pela estrutura do AV Stream
Plugin do modo 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 Suportado Suportado
Perfil da Câmera V2 Suportado Suportado
Perfil da Câmera V1 Sem suporte Suportado

Requisitos de fonte de mídia personalizada

Com a introdução do serviço Windows Camera Frame Server (conhecido como "Frame Server"), isto pode ser feito por meio de uma Fonte de Mídia Personalizada. Isso requer dois componentes principais:

  • 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 detecção 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 (para não ser confundida com o Sistema Local; O Serviço Local é uma conta com baixo privilégio 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. Geralmente, 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 de um Grupo de Sensores.

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

Normalmente, a geração do Grupo de Sensores é 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 fluxos/tipos de 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 a interface do dispositivo "camera" (a Fonte de Mídia Personalizada) na categoria KSCATEGORY_VIDEO_CAMERA para que ela possa ser enumerada.

Observação

Para permitir a enumeração por meio de aplicativos DirectShow herdados, o driver também precisa se registrar em KSCATEGORY_VIDEO e KSCATEGORY_CAPTURE.

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

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

Exemplo de INF

O exemplo a seguir mostra um INF típico para um driver de stub de origem 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-se 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 procure uma câmera RGB padrão.

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

Observação

A maioria das webcams baseadas em USB expõe formatos YUY2 e MJPG. Devido a esse comportamento, muitos aplicativos DirectShow herdados são escritos com a suposição de que o YUY2/MJPG está disponível. Para garantir a compatibilidade com esse aplicativo, é recomendável disponibilizar o tipo de mídia YUY2 a partir da sua Fonte de Mídia Personalizada, caso seja desejada a compatibilidade com aplicativos legados.

Implementação do Driver stub

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

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

Veja a seguir um trecho de código de um stub de driver UMDF que manipula esta 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 convencional, é recomendável que o driver stub gerencie pelo menos as operações PnP de habilitar e desabilitar o dispositivo quando a fonte subjacente for removida/anexada. Por exemplo, se a 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 monitorem a adição/remoção de dispositivos por meio das APIs PnP para receber as notificações apropriadas. E garante que uma fonte que não está mais disponível não possa ser enumerada.

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

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

DLL da fonte 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 explica as diferenças necessárias para dar suporte à sua Fonte de Mídia Personalizada dentro da 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 outras interfaces de serviço.

O exemplo a seguir mostra uma implementação de 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 à sua própria interface IMFMediaEventGenerator . Todos os fluxos de controle e dados de pipeline do MF da origem são gerenciados por meio do envio de eventos específicos IMFMediaEvent.

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

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 expõe a interface IMFMediaEventGenerator e os métodos dessa interface roteiam as solicitações para o objeto IMFMediaEventQueue criado durante a criação/inicialização da fonte de mídia.

No código abaixo, _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

As fontes de mídia personalizadas com suporte por meio da estrutura do Servidor de Quadro não dão suporte a operações de busca ou pausa. 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::Pause 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 é o meio pelo qual o pipeline roteia a E/S do controle.

Para obter mais informações, consulte os seguintes artigos de documentação do Conjunto de Controle:

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 suporte a controles.

// 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 a estrutura de trabalho de sua Fonte de Mídia Personalizada por meio de um evento de mídia MENewStream postado na fila de eventos da fonte 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 é compatível com fontes de mídia personalizadas.

Observação

PINNAME_IMAGE, embora tenha suporte, não é recomendável. Expor um fluxo com PINNAME_IMAGE requer que a Fonte de Mídia Personalizada dê 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, o 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 Bandeira Descrição
MFFrameSourceTypes_Color 0x0001 Fluxo de cores RGB padrão
MFFrameSourceTypes_Infrared 0x0002 Fluxo de IR
TiposDeFonteDeQuadroMF_Profundidade 0x0004 Fluxo de profundidade
MFFrameSourceTypes_Image 0x0008 Fluxo de imagem (subtipo nãovideo, normalmente JPEG)
TiposDeFonteDeQuadroMF_Personalizado 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 marcará o fluxo como sendo "compartilhável" pelo Servidor de Quadros. Isso permite 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 nonmarked 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 amostra

Todos os frames de mídia devem ser produzidos como uma 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 exemplo e a duração da amostra definidos. Todos os marcadores temporais 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. Esta função atua como um encapsulamento ao redor de QueryPerformanceCounter e converte os ticks de QPC em unidades de 100 nanossegundos.

Fontes de mídia personalizadas podem usar um relógio interno, mas todos os carimbos de data/hora devem ser correlacionados a unidades de 100 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 stride. Normalmente, esses seriam subtipos personalizados ou subtipos compactados (como H264/HEVC/MJPG).

Para tipos de mídia não compactados 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 de 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 fornece 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 tanto para a fonte de mídia personalizada quanto para a coleção subjacente de IMFMediaStream, deve-se observar que isso resultará na serialização dos eventos de origem da 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 no Windows 10, versão 1809)

Além da lista acima de interfaces que precisam ter suporte para uma fonte de mídia personalizada, uma das limitações impostas pela operação de uma fonte de mídia personalizada dentro da arquitetura do Servidor de Quadros é que só pode haver uma instância do driver UMDF ativada através do pipeline.

Por exemplo, se você tiver um dispositivo físico que instale um driver de stub UMDF além de seu pacote de driver que não seja AV Stream 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 de link simbólico associado à Fonte de Mídia Personalizada no momento da criação.

A Fonte de Mídia Personalizada pode procurar o atributo padrão MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK no repositório de atributos dela (o repositório de atributos 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 maior latência de inicialização, pois adiará a aquisição de recursos de hardware para o momento de início, em vez do momento de criação ou inicialização.

Por isso, no 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 da 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 é invocado, o repositório de atributos do IMFActivate contém o nome de link simbólico do driver stub umDF e quaisquer outras configurações 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 outros dois métodos 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 a 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 tem as seguintes informações:

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

Onde o GUID de Propriedade do Codificador é a lista de propriedades disponíveis definidas nas 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 extras:

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

  • 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 artigo. 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 no Windows 10, versão 1803 e posterior)

O suporte ao Perfil da 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 ao Reconhecimento Facial do Windows Hello, é 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 ser suportado por 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ó é necessário se a autenticação Multispectrum for exigida).

  • 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 do Photo Stream

Se fluxos de fotos independentes forem expostos marcando um dos MF_DEVICESTREAM_STREAM_CATEGORY do fluxo como PINNAME_IMAGE, então é necessário um fluxo com categoria de fluxo de PINNAME_VIDEO_CAPTURE (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 ser suportado. Para obter mais informações, consulte Propriedades de Controle de Vídeo.