Como escrever um apresentador do EVR

Este artigo descreve como escrever um apresentador personalizado para o renderizador de vídeo aprimorado (EVR). Um apresentador personalizado pode ser usado com DirectShow e Media Foundation; as interfaces e o modelo de objeto são os mesmos para ambas as tecnologias, embora a sequência exata de operações possa variar.

O código de exemplo neste tópico é adaptado do exemplo EVRPresenter, que é fornecido no SDK do Windows.

Este tópico contém as seguintes seções:

Pré-requisitos

Antes de escrever um apresentador personalizado, você deve estar familiarizado com as seguintes tecnologias:

  • O renderizador de vídeo aprimorado. Consulte o Renderizador de Vídeo Aprimorado.
  • Gráficos Direct3D. Você não precisa entender os gráficos 3D para escrever um apresentador, mas deve saber como criar um dispositivo Direct3D e gerenciar superfícies direct3D. Se você não estiver familiarizado com o Direct3D, leia as seções "Dispositivos Direct3D" e "Recursos Direct3D" na documentação do SDK do DirectX Graphics.
  • DirectShow grafos de filtro ou o pipeline do Media Foundation, dependendo de qual tecnologia seu aplicativo usará para renderizar vídeo.
  • Transformações do Media Foundation. O misturador EVR é uma transformação do Media Foundation e o apresentador chama métodos diretamente no misturador.
  • Implementando objetos COM. O apresentador é um objeto COM em processo e com thread livre.

Modelo de Objeto do Apresentador

Esta seção contém uma visão geral do modelo de objeto e interfaces do apresentador.

Fluxo de Dados dentro do EVR

O EVR usa dois componentes de plug-in para renderizar o vídeo: o misturador e o apresentador. O misturador mistura os fluxos de vídeo e desinterlaces o vídeo, se necessário. O apresentador desenha (ou apresenta) o vídeo na exibição e agenda quando cada quadro é desenhado. Os aplicativos podem substituir qualquer um desses objetos por uma implementação personalizada.

O EVR tem um ou mais fluxos de entrada e o misturador tem um número correspondente de fluxos de entrada. O Fluxo 0 é sempre o fluxo de referência. Os outros fluxos são substreams, que o mixer alpha-blends no fluxo de referência. O fluxo de referência determina a taxa de quadros mestre para o vídeo composto. Para cada quadro de referência, o misturador usa o quadro mais recente de cada substream, mistura-os alfa no quadro de referência e gera um único quadro composto. O mixer também executa a desinterlacização e a conversão de cores de YUV para RGB, se necessário. O EVR sempre insere o misturador no pipeline de vídeo, independentemente do número de fluxos de entrada ou do formato de vídeo. A imagem a seguir ilustra esse processo.

diagram showing the reference stream and substream pointing to the mixer, which points to the presenter, which points to the display

O apresentador executa as seguintes tarefas:

  • Define o formato de saída no misturador. Antes do streaming começar, o apresentador define um tipo de mídia no fluxo de saída do misturador. Esse tipo de mídia define o formato da imagem composta.
  • Cria o dispositivo Direct3D.
  • Aloca superfícies Direct3D. O misturador corta os quadros compostos nessas superfícies.
  • Obtém a saída do misturador.
  • Agenda quando os quadros são apresentados. O EVR fornece o relógio de apresentação e o apresentador agenda quadros de acordo com este relógio.
  • Apresenta cada quadro usando Direct3D.
  • Executa a depuração e a depuração de quadros.

Estados do Apresentador

A qualquer momento, o apresentador está em um dos seguintes estados:

  • Iniciado. O relógio de apresentação do EVR está em execução. O apresentador agenda quadros de vídeo para apresentação quando chegam.
  • Pausado. O relógio de apresentação está suspenso. O apresentador não apresenta novos exemplos, mas mantém sua fila de amostras agendadas. Se novos exemplos forem recebidos, o apresentador os adicionará à fila.
  • Parado. O relógio de apresentação foi interrompido. O apresentador descarta todos os exemplos que foram agendados.
  • Desligar. O apresentador libera todos os recursos relacionados ao streaming, como superfícies do Direct3D. Esse é o estado inicial do apresentador e o estado final antes do apresentador ser destruído.

No código de exemplo neste tópico, o estado do apresentador é representado por uma enumeração:

enum RENDER_STATE
{
    RENDER_STATE_STARTED = 1,
    RENDER_STATE_STOPPED,
    RENDER_STATE_PAUSED,
    RENDER_STATE_SHUTDOWN,  // Initial state.
};

Algumas operações não são válidas enquanto o apresentador está no estado de desligamento. O código de exemplo verifica esse estado chamando um método auxiliar:

    HRESULT CheckShutdown() const
    {
        if (m_RenderState == RENDER_STATE_SHUTDOWN)
        {
            return MF_E_SHUTDOWN;
        }
        else
        {
            return S_OK;
        }
    }

Interfaces do apresentador

Um apresentador é necessário para implementar as seguintes interfaces:

Interface Descrição
IMFClockStateSink Notifica o apresentador quando o relógio do EVR muda de estado. Consulte a implementação de IMFClockStateSink.
IMFGetService Fornece uma maneira para o aplicativo e outros componentes no pipeline obterem interfaces do apresentador.
IMFTopologyServiceLookupClient Permite que o apresentador obtenha interfaces do EVR ou do misturador. Consulte a implementação de IMFTopologyServiceLookupClient.
IMFVideoDeviceID Garante que o apresentador e o misturador usem tecnologias compatíveis. Consulte Implementando IMFVideoDeviceID.
IMFVideoPresenter Processa mensagens do EVR. Consulte Implementando IMFVideoPresenter.

 

As seguintes interfaces são opcionais:

Interface Descrição
IEVRTrustedVideoPlugin Permite que o apresentador trabalhe com mídia protegida. Implemente essa interface se o apresentador for um componente confiável projetado para funcionar no PMP (caminho de mídia protegido).
IMFRateSupport Relata o intervalo de taxas de reprodução que o apresentador dá suporte. Consulte a implementação de IMFRateSupport.
IMFVideoPositionMapper Mapas coordenadas no quadro de vídeo de saída para coordenadas no quadro de vídeo de entrada.
Iqualprop Relata informações de desempenho. O EVR usa essas informações para o gerenciamento de controle de qualidade. Essa interface está documentada no SDK do DirectShow.

 

Você também pode fornecer interfaces para que o aplicativo se comunique com o apresentador. O apresentador padrão implementa a interface IMFVideoDisplayControl para essa finalidade. Você pode implementar essa interface ou definir sua própria. O aplicativo obtém interfaces do apresentador chamando IMFGetService::GetService no EVR. Quando o GUID do serviço é MR_VIDEO_RENDER_SERVICE, o EVR passa a solicitação GetService para o apresentador.

Implementando IMFVideoDeviceID

A interface IMFVideoDeviceID contém um método, GetDeviceID, que retorna um GUID de dispositivo. O GUID do dispositivo garante que o apresentador e o misturador usem tecnologias compatíveis. Se os GUIDs do dispositivo não corresponderem, o EVR não será inicializado.

O mixer padrão e o apresentador usam o Direct3D 9, com o GUID do dispositivo igual a IID_IDirect3DDevice9. Se você pretende usar o apresentador personalizado com o misturador padrão, o GUID do dispositivo do apresentador deve ser IID_IDirect3DDevice9. Se você substituir os dois componentes, poderá definir um novo GUID de dispositivo. Para o restante deste artigo, supõe-se que o apresentador use o Direct3D 9. Aqui está a implementação padrão de GetDeviceID:

HRESULT EVRCustomPresenter::GetDeviceID(IID* pDeviceID)
{
    if (pDeviceID == NULL)
    {
        return E_POINTER;
    }

    *pDeviceID = __uuidof(IDirect3DDevice9);
    return S_OK;
}

O método deve ter êxito mesmo quando o apresentador é desligado.

Implementando IMFTopologyServiceLookupClient

A interface IMFTopologyServiceLookupClient permite que o apresentador obtenha ponteiros de interface do EVR e do misturador da seguinte maneira:

  1. Quando o EVR inicializa o apresentador, ele chama o método IMFTopologyServiceLookupClient::InitServicePointers do apresentador. O argumento é um ponteiro para a interface IMFTopologyServiceLookup do EVR.
  2. O apresentador chama IMFTopologyServiceLookup::LookupService para obter ponteiros de interface do EVR ou do misturador.

O método LookupService é semelhante ao método IMFGetService::GetService . Ambos os métodos tomam um GUID de serviço e um identificador de interface (IID) como entrada, mas LookupService retorna uma matriz de ponteiros de interface, enquanto GetService retorna um único ponteiro. Na prática, no entanto, você sempre pode definir o tamanho da matriz como 1. O objeto consultado depende do GUID de serviço:

  • Se o GUID do serviço for MR_VIDEO_RENDER_SERVICE, o EVR será consultado.
  • Se o GUID do serviço for MR_VIDEO_MIXER_SERVICE, o misturador será consultado.

Na implementação do InitServicePointers, obtenha as seguintes interfaces do EVR:

EVR Interface Descrição
Imediaeventsink Fornece uma maneira de o apresentador enviar mensagens para o EVR. Essa interface é definida no SDK DirectShow, portanto, as mensagens seguem o padrão para eventos DirectShow, não eventos do Media Foundation.
IMFClock Representa o relógio do EVR. O apresentador usa essa interface para agendar amostras para apresentação. O EVR pode ser executado sem relógio, portanto, essa interface pode não estar disponível. Caso contrário, ignore o código de erro do LookupService.
O relógio também implementa a interface IMFTimer . No pipeline do Media Foundation, o relógio implementa a interface IMFPresentationClock . Ele não implementa essa interface no DirectShow.

 

Obtenha as seguintes interfaces do misturador:

Interface Mixer Descrição
IMFTransform Permite que o apresentador se comunique com o misturador.
IMFVideoDeviceID Permite que o apresentador valide o GUID do dispositivo do misturador.

 

O código a seguir implementa o método InitServicePointers :

HRESULT EVRCustomPresenter::InitServicePointers(
    IMFTopologyServiceLookup *pLookup
    )
{
    if (pLookup == NULL)
    {
        return E_POINTER;
    }

    HRESULT             hr = S_OK;
    DWORD               dwObjectCount = 0;

    EnterCriticalSection(&m_ObjectLock);

    // Do not allow initializing when playing or paused.
    if (IsActive())
    {
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    // Ask for the clock. Optional, because the EVR might not have a clock.
    dwObjectCount = 1;

    (void)pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL,   // Not used.
        0,                          // Reserved.
        MR_VIDEO_RENDER_SERVICE,    // Service to look up.
        IID_PPV_ARGS(&m_pClock),    // Interface to retrieve.
        &dwObjectCount              // Number of elements retrieved.
        );

    // Ask for the mixer. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_pMixer), &dwObjectCount
        );

    if (FAILED(hr))
    {
        goto done;
    }

    // Make sure that we can work with this mixer.
    hr = ConfigureMixer(m_pMixer);
    if (FAILED(hr))
    {
        goto done;
    }

    // Ask for the EVR's event-sink interface. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_pMediaEventSink),
        &dwObjectCount
        );

    if (FAILED(hr))
    {
        goto done;
    }

    // Successfully initialized. Set the state to "stopped."
    m_RenderState = RENDER_STATE_STOPPED;

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Quando os ponteiros de interface obtidos do LookupService não são mais válidos, o EVR chama IMFTopologyServiceLookupClient::ReleaseServicePointers. Dentro desse método, libere todos os ponteiros da interface e defina o estado do apresentador para desligar:

HRESULT EVRCustomPresenter::ReleaseServicePointers()
{
    // Enter the shut-down state.
    EnterCriticalSection(&m_ObjectLock);

    m_RenderState = RENDER_STATE_SHUTDOWN;

    LeaveCriticalSection(&m_ObjectLock);

    // Flush any samples that were scheduled.
    Flush();

    // Clear the media type and release related resources.
    SetMediaType(NULL);

    // Release all services that were acquired from InitServicePointers.
    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    return S_OK;
}

O EVR chama ReleaseServicePointers por vários motivos, incluindo:

  • Desconectar ou reconectar pinos (DirectShow) ou adicionar ou remover coletores de fluxo (Media Foundation).
  • Alterando o formato.
  • Configurando um novo relógio.
  • Desligamento final do EVR.

Durante o tempo de vida do apresentador, o EVR pode chamar InitServicePointers e ReleaseServicePointers várias vezes.

Implementando IMFVideoPresenter

A interface IMFVideoPresenter herda IMFClockStateSink e adiciona dois métodos:

Método Descrição
GetCurrentMediaType Retorna o tipo de mídia dos quadros de vídeo compostos.
Processmessage Sinaliza o apresentador para executar várias ações.

 

O método GetCurrentMediaType retorna o tipo de mídia do apresentador. (Para obter detalhes sobre como definir o tipo de mídia, consulte Formatos de Negociação.) O tipo de mídia é retornado como um ponteiro de interface IMFVideoMediaType . O exemplo a seguir pressupõe que o apresentador armazene o tipo de mídia como um ponteiro IMFMediaType . Para obter a interface IMFVideoMediaType do tipo de mídia, chame QueryInterface:

HRESULT EVRCustomPresenter::GetCurrentMediaType(
    IMFVideoMediaType** ppMediaType
    )
{
    HRESULT hr = S_OK;

    if (ppMediaType == NULL)
    {
        return E_POINTER;
    }

    *ppMediaType = NULL;

    EnterCriticalSection(&m_ObjectLock);

    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    if (m_pMediaType == NULL)
    {
        hr = MF_E_NOT_INITIALIZED;
        goto done;
    }

    hr = m_pMediaType->QueryInterface(IID_PPV_ARGS(ppMediaType));

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

O método ProcessMessage é o mecanismo principal para que o EVR se comunique com o apresentador. As mensagens a seguir são definidas. Os detalhes da implementação de cada mensagem são dados no restante deste tópico.

Mensagem Descrição
MFVP_MESSAGE_INVALIDATEMEDIATYPE O tipo de mídia de saída do misturador é inválido. O apresentador deve negociar um novo tipo de mídia com o misturador. Consulte Formatos de Negociação.
MFVP_MESSAGE_BEGINSTREAMING O streaming foi iniciado. Nenhuma ação específica é exigida por esta mensagem, mas você pode usá-la para alocar recursos.
MFVP_MESSAGE_ENDSTREAMING O streaming terminou. Libere todos os recursos alocados em resposta à mensagem de MFVP_MESSAGE_BEGINSTREAMING .
MFVP_MESSAGE_PROCESSINPUTNOTIFY O misturador recebeu um novo exemplo de entrada e pode ser capaz de gerar um novo quadro de saída. O apresentador deve chamar IMFTransform::P rocessOutput no misturador. Consulte a saída de processamento.
MFVP_MESSAGE_ENDOFSTREAM A apresentação terminou. Consulte Fim do Fluxo.
MFVP_MESSAGE_FLUSH O EVR está liberando os dados em seu pipeline de renderização. O apresentador deve descartar todos os quadros de vídeo agendados para apresentação.
MFVP_MESSAGE_STEP Solicita que o apresentador avance N quadros. O apresentador deve descartar os próximos quadros N-1 e exibir o quadro Nth. Consulte a etapa de quadro.
MFVP_MESSAGE_CANCELSTEP Cancela a etapa do quadro.

 

Implementando IMFClockStateSink

O apresentador deve implementar a interface IMFClockStateSink como parte de sua implementação de IMFVideoPresenter, que herda IMFClockStateSink. O EVR usa essa interface para notificar o apresentador sempre que o relógio do EVR alterar o estado. Para obter mais informações sobre os estados do relógio, consulte Relógio de Apresentação.

Aqui estão algumas diretrizes para implementar os métodos nessa interface. Todos os métodos devem falhar se o apresentador for desligado.

Método Descrição
OnClockStart
  1. Defina o estado do apresentador como iniciado.
  2. Se o llClockStartOffset não for PRESENTATION_CURRENT_POSITION, libere a fila de exemplos do apresentador. (Isso equivale a receber uma mensagem MFVP_MESSAGE_FLUSH .)
  3. Se uma solicitação anterior da etapa de quadro ainda estiver pendente, processe a solicitação (consulte Frame Stepping). Caso contrário, tente processar a saída do misturador (consulte Saída de Processamento.
OnClockStop
  1. Defina o estado do apresentador para parado.
  2. Libere a fila de exemplos do apresentador.
  3. Cancele qualquer operação pendente de etapa de quadro.
OnClockPause Defina o estado do apresentador como pausado.
OnClockRestart Trate o mesmo que OnClockStart , mas não libere a fila de exemplos.
OnClockSetRate
  1. Se a taxa estiver mudando de zero para diferente de zero, cancele a etapa de quadro.
  2. Armazene a nova taxa de relógio. A taxa de relógio afeta quando os exemplos são apresentados. Para obter mais informações, consulte Exemplos de Agendamento.

 

Implementando IMFRateSupport

Para dar suporte a taxas de reprodução diferentes de 1× velocidade, o apresentador deve implementar a interface IMFRateSupport . Aqui estão algumas diretrizes para implementar os métodos nessa interface. Todos os métodos devem falhar depois que o apresentador for desligado. Para obter mais informações sobre essa interface, consulte Controle de Taxa.

Valor Descrição
GetSlowestRate Retornar zero para indicar nenhuma taxa de reprodução mínima.
GetFastestRate Para reprodução não fina, a taxa de reprodução não deve exceder a taxa de atualização do monitor: taxa de taxa de taxa = máxima (Hz) /taxa de quadros de vídeo (fps). A taxa de quadros de vídeo é especificada no tipo de mídia do apresentador.
Para reprodução limitada, a taxa de reprodução não está associada; retornar o valor FLT_MAX. Na prática, a origem e o decodificador serão os fatores limitantes durante a reprodução limitada.
Para reprodução inversa, retorne o negativo da taxa máxima.
IsRateSupported Retorne MF_E_UNSUPPORTED_RATE se o valor absoluto de flRate exceder a taxa de reprodução máxima do apresentador. Calcule a taxa de reprodução máxima, conforme descrito para GetFastestRate.

 

O exemplo a seguir mostra como implementar o método GetFastestRate :

float EVRCustomPresenter::GetMaxRate(BOOL bThin)
{
    // Non-thinned:
    // If we have a valid frame rate and a monitor refresh rate, the maximum
    // playback rate is equal to the refresh rate. Otherwise, the maximum rate
    // is unbounded (FLT_MAX).

    // Thinned: The maximum rate is unbounded.

    float   fMaxRate = FLT_MAX;
    MFRatio fps = { 0, 0 };
    UINT    MonitorRateHz = 0;

    if (!bThin && (m_pMediaType != NULL))
    {
        GetFrameRate(m_pMediaType, &fps);
        MonitorRateHz = m_pD3DPresentEngine->RefreshRate();

        if (fps.Denominator && fps.Numerator && MonitorRateHz)
        {
            // Max Rate = Refresh Rate / Frame Rate
            fMaxRate = (float)MulDiv(
                MonitorRateHz, fps.Denominator, fps.Numerator);
        }
    }

    return fMaxRate;
}

O exemplo anterior chama um método auxiliar, GetMaxRate, para calcular a taxa máxima de reprodução para frente:

O exemplo a seguir mostra como implementar o método IsRateSupported :

HRESULT EVRCustomPresenter::IsRateSupported(
    BOOL bThin,
    float fRate,
    float *pfNearestSupportedRate
    )
{
    EnterCriticalSection(&m_ObjectLock);

    float   fMaxRate = 0.0f;
    float   fNearestRate = fRate;  // If we support fRate, that is the nearest.

    HRESULT hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    // Find the maximum forward rate.
    // Note: We have no minimum rate (that is, we support anything down to 0).
    fMaxRate = GetMaxRate(bThin);

    if (fabsf(fRate) > fMaxRate)
    {
        // The (absolute) requested rate exceeds the maximum rate.
        hr = MF_E_UNSUPPORTED_RATE;

        // The nearest supported rate is fMaxRate.
        fNearestRate = fMaxRate;
        if (fRate < 0)
        {
            // Negative for reverse playback.
            fNearestRate = -fNearestRate;
        }
    }

    // Return the nearest supported rate.
    if (pfNearestSupportedRate != NULL)
    {
        *pfNearestSupportedRate = fNearestRate;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Enviando eventos para o EVR

O apresentador deve notificar o EVR de vários eventos. Para fazer isso, ele usa a interface IMediaEventSink do EVR, obtida quando o EVR chama o método IMFTopologyServiceLookupClient::InitServicePointers do apresentador. (A interface IMediaEventSink é originalmente uma interface DirectShow, mas é usada no EVR DirectShow e no Media Foundation.) O código a seguir mostra como enviar um evento para o EVR:

    // NotifyEvent: Send an event to the EVR through its IMediaEventSink interface.
    void NotifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2)
    {
        if (m_pMediaEventSink)
        {
            m_pMediaEventSink->Notify(EventCode, Param1, Param2);
        }
    }

A tabela a seguir lista os eventos que o apresentador envia, juntamente com os parâmetros de evento.

Evento Descrição
EC_COMPLETE O apresentador terminou de renderizar todos os quadros após a mensagem MFVP_MESSAGE_ENDOFSTREAM.
  • Param1: HRESULT que indica o status da operação.
  • Param2: não usado.
Para obter mais informações, consulte Fim do Fluxo.
EC_DISPLAY_CHANGED O dispositivo Direct3D foi alterado.
  • Param1: não usado.
  • Param2: não usado.
Para obter mais informações, consulte Gerenciando o dispositivo Direct3D.
EC_ERRORABORT Ocorreu um erro que requer que o streaming pare.
  • Param1: HRESULT indicando o erro que ocorreu.
  • Param2: não usado.
EC_PROCESSING_LATENCY Especifica a quantidade de tempo que o apresentador está levando para renderizar cada quadro. (Opcional).
  • Param1: ponteiro para um valor LONGLONG constante que contém a quantidade de tempo para processar o quadro, em unidades de 100 nanossegundos.
  • Param2: não usado.
Para obter mais informações, consulte Saída de Processamento.
EC_SAMPLE_LATENCY Especifica o tempo de atraso atual na renderização de exemplos. Se o valor for positivo, as amostras estarão atrasadas. Se o valor for negativo, os exemplos estarão adiantado. (Opcional).
  • Param1: ponteiro para um valor LONGLONG constante que contém o tempo de retardo, em unidades de 100 nanossegundos.
  • Param2: não usado.
EC_SCRUB_TIME Enviado imediatamente após EC_STEP_COMPLETE se a taxa de reprodução for zero. Esse evento contém o carimbo de data/hora do quadro que foi exibido.
  • Param1: 32 bits inferiores do carimbo de data/hora.
  • Param2: 32 bits superiores do carimbo de data/hora.
Para obter mais informações, consulte Frame Stepping.
EC_STEP_COMPLETE O apresentador concluiu ou cancelou uma etapa de quadro.
  • Param1: não usado.
  • Param2: não usado.
Para obter mais informações, consulte Frame Stepping.
[! Observação]
Uma versão anterior da documentação descreveu o Param1 incorretamente. Esse parâmetro não é usado para esse evento.

 

Formatos de negociação

Sempre que o apresentador recebe uma mensagem de MFVP_MESSAGE_INVALIDATEMEDIATYPE do EVR, ele deve definir o formato de saída no mixer, da seguinte maneira:

  1. Chame IMFTransform::GetOutputAvailableType no misturador para obter um tipo de saída possível. Esse tipo descreve um formato que o misturador pode produzir considerando os fluxos de entrada e os recursos de processamento de vídeo do dispositivo gráfico.

  2. Verifique se o apresentador pode usar esse tipo de mídia como seu formato de renderização. Aqui estão algumas coisas para verificar, embora sua implementação possa ter seus próprios requisitos:

    • O vídeo deve ser descompactado.
    • O vídeo deve ter apenas quadros progressivos. Verifique se o atributo MF_MT_INTERLACE_MODE é igual a MFVideoInterlace_Progressive.
    • O formato deve ser compatível com o dispositivo Direct3D.

    Se o tipo não for aceitável, retorne à etapa 1 e obtenha o próximo tipo proposto do misturador.

  3. Crie um novo tipo de mídia que seja um clone do tipo original e altere os seguintes atributos:

    • Defina o atributo MF_MT_FRAME_SIZE igual à largura e altura desejadas para as superfícies do Direct3D que você alocará.
    • Defina o atributo MF_MT_PAN_SCAN_ENABLED como FALSE.
    • Defina o atributo MF_MT_PIXEL_ASPECT_RATIO igual ao PAR da exibição (normalmente 1:1).
    • Defina a abertura geométrica (atributo MF_MT_GEOMETRIC_APERTURE ) igual a um retângulo dentro da superfície direct3D. Quando o misturador gera um quadro de saída, ele corta a imagem de origem nesse retângulo. A abertura geométrica pode ser tão grande quanto a superfície, ou pode ser um subrectangle dentro da superfície. Para obter mais informações, consulte Retângulos de origem e destino.
  4. Para testar se o misturador aceitará o tipo de saída modificado, chame IMFTransform::SetOutputType com o sinalizador MFT_SET_TYPE_TEST_ONLY . Se o misturador rejeitar o tipo, volte para a etapa 1 e obtenha o próximo tipo.

  5. Aloque um pool de superfícies Direct3D, conforme descrito na alocação de superfícies Direct3D. O misturador usará essas superfícies quando desenhar os quadros de vídeo compostos.

  6. Defina o tipo de saída no misturador chamando SetOutputType sem sinalizadores. Se a primeira chamada para SetOutputType tiver sido bem-sucedida na etapa 4, o método deverá ter êxito novamente.

Se o mixer ficar sem tipos, o método GetOutputAvailableType retornará MF_E_NO_MORE_TYPES. Se o apresentador não conseguir encontrar um tipo de saída adequado para o misturador, o fluxo não poderá ser renderizado. Nesse caso, DirectShow ou Media Foundation podem tentar outro formato de fluxo. Portanto, o apresentador pode receber várias mensagens MFVP_MESSAGE_INVALIDATEMEDIATYPE em uma linha até que um tipo válido seja encontrado.

O misturador automaticamente em caixas de correio do vídeo, levando em conta a par (taxa de proporção de pixel) da origem e do destino. Para obter melhores resultados, a largura e a altura da superfície e a abertura geométrica devem ser iguais ao tamanho real que você deseja que o vídeo apareça na exibição. A imagem a seguir ilustra esse processo.

diagram showing a composited fram leading to a direct3d surface, which leads to a window

O código a seguir mostra a estrutura de tópicos do processo. Algumas das etapas são colocadas em funções auxiliares, os detalhes exatos dos quais dependerão dos requisitos do apresentador.

HRESULT EVRCustomPresenter::RenegotiateMediaType()
{
    HRESULT hr = S_OK;
    BOOL bFoundMediaType = FALSE;

    IMFMediaType *pMixerType = NULL;
    IMFMediaType *pOptimalType = NULL;
    IMFVideoMediaType *pVideoType = NULL;

    if (!m_pMixer)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Loop through all of the mixer's proposed output types.
    DWORD iTypeIndex = 0;
    while (!bFoundMediaType && (hr != MF_E_NO_MORE_TYPES))
    {
        SafeRelease(&pMixerType);
        SafeRelease(&pOptimalType);

        // Step 1. Get the next media type supported by mixer.
        hr = m_pMixer->GetOutputAvailableType(0, iTypeIndex++, &pMixerType);
        if (FAILED(hr))
        {
            break;
        }

        // From now on, if anything in this loop fails, try the next type,
        // until we succeed or the mixer runs out of types.

        // Step 2. Check if we support this media type.
        if (SUCCEEDED(hr))
        {
            // Note: None of the modifications that we make later in CreateOptimalVideoType
            // will affect the suitability of the type, at least for us. (Possibly for the mixer.)
            hr = IsMediaTypeSupported(pMixerType);
        }

        // Step 3. Adjust the mixer's type to match our requirements.
        if (SUCCEEDED(hr))
        {
            hr = CreateOptimalVideoType(pMixerType, &pOptimalType);
        }

        // Step 4. Check if the mixer will accept this media type.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, MFT_SET_TYPE_TEST_ONLY);
        }

        // Step 5. Try to set the media type on ourselves.
        if (SUCCEEDED(hr))
        {
            hr = SetMediaType(pOptimalType);
        }

        // Step 6. Set output media type on mixer.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, 0);

            assert(SUCCEEDED(hr)); // This should succeed unless the MFT lied in the previous call.

            // If something went wrong, clear the media type.
            if (FAILED(hr))
            {
                SetMediaType(NULL);
            }
        }

        if (SUCCEEDED(hr))
        {
            bFoundMediaType = TRUE;
        }
    }

    SafeRelease(&pMixerType);
    SafeRelease(&pOptimalType);
    SafeRelease(&pVideoType);

    return hr;
}

Para obter mais informações sobre tipos de mídia de vídeo, consulte Tipos de Mídia de Vídeo.

Gerenciando o dispositivo Direct3D

O apresentador cria o dispositivo Direct3D e manipula qualquer perda de dispositivo durante o streaming. O apresentador também hospeda o gerenciador de dispositivos Direct3D, que fornece uma maneira de outros componentes usarem o mesmo dispositivo. Por exemplo, o misturador usa o dispositivo Direct3D para misturar substreams, desinterlace e executar ajustes de cor. Os decodificadores podem usar o dispositivo Direct3D para decodificação acelerada por vídeo. (Para obter mais informações sobre a aceleração de vídeo, consulte a Aceleração de Vídeo do DirectX 2.0.)

Para configurar o dispositivo Direct3D, execute as seguintes etapas:

  1. Crie o objeto Direct3D chamando Direct3DCreate9 ou Direct3DCreate9Ex.
  2. Crie o dispositivo chamando IDirect3D9::CreateDevice ou IDirect3D9Ex::CreateDevice.
  3. Crie o gerenciador de dispositivos chamando DXVA2CreateDirect3DDeviceManager9.
  4. Defina o dispositivo no gerenciador de dispositivos chamando IDirect3DDeviceManager9::ResetDevice.

Se outro componente de pipeline precisar do gerenciador de dispositivos, ele chamará IMFGetService::GetService no EVR, especificando MR_VIDEO_ACCELERATION_SERVICE para o GUID do serviço. O EVR passa a solicitação para o apresentador. Depois que o objeto obtém o ponteiro IDirect3DDeviceManager9 , ele pode obter um identificador para o dispositivo chamando IDirect3DDeviceManager9::OpenDeviceHandle. Quando o objeto precisa usar o dispositivo, ele passa o identificador do dispositivo para o método IDirect3DDeviceManager9::LockDevice , que retorna um ponteiro IDirect3DDevice9 .

Depois que o dispositivo for criado, se o apresentador destruir o dispositivo e criar um novo, o apresentador deverá chamar ResetDevice novamente. O método ResetDevice invalida todos os identificadores de dispositivo existentes, o que faz com que LockDevice retorne DXVA2_E_NEW_VIDEO_DEVICE. Esse código de erro sinaliza para outros objetos usando o dispositivo que eles devem abrir um novo identificador de dispositivo. Para obter mais informações sobre como usar o gerenciador de dispositivos, consulte Direct3D Gerenciador de Dispositivos.

O apresentador pode criar o dispositivo no modo com janelas ou no modo exclusivo de tela inteira. Para o modo com janelas, você deve fornecer uma maneira de o aplicativo especificar a janela de vídeo. O apresentador padrão implementa o método IMFVideoDisplayControl::SetVideoWindow para essa finalidade. Você deve criar o dispositivo quando o apresentador for criado pela primeira vez. Normalmente, você não conhecerá todos os parâmetros do dispositivo no momento, como a janela ou o formato de buffer traseiro. Você pode criar um dispositivo temporário e substituí-lo mais tarde&#;lembre-se de chamar ResetDevice no gerenciador de dispositivos.

Se você criar um novo dispositivo ou chamar IDirect3DDevice9::Reset ou IDirect3DDevice9Ex::ResetEx em um dispositivo existente, envie um evento EC_DISPLAY_CHANGED para o EVR. Esse evento notifica o EVR para renegociar o tipo de mídia. O EVR ignora os parâmetros de evento para esse evento.

Alocando superfícies do Direct3D

Depois que o apresentador definir o tipo de mídia, ele poderá alocar as superfícies do Direct3D, que o misturador usará para gravar os quadros de vídeo. A superfície deve corresponder ao tipo de mídia do apresentador:

  • O formato de superfície deve corresponder ao subtipo de mídia. Por exemplo, se o subtipo for MFVideoFormat_RGB24, o formato de superfície deverá ser D3DFMT_X8R8G8B8. Para obter mais informações sobre subtipos e formatos Direct3D, consulte GUIDs de subtipo de vídeo.
  • A largura e a altura da superfície devem corresponder às dimensões fornecidas no atributo MF_MT_FRAME_SIZE do tipo de mídia.

A maneira recomendada de alocar superfícies depende se o apresentador executa janelas ou tela inteira.

Se o dispositivo Direct3D estiver em janelas, você poderá criar várias cadeias de troca, cada uma com um único buffer traseiro. Usando essa abordagem, você pode apresentar cada superfície de forma independente, pois apresentar uma cadeia de troca não interferirá nas outras cadeias de troca. O misturador pode gravar dados em uma superfície enquanto outra superfície está agendada para apresentação.

Primeiro, decida quantas cadeias de troca criar. Um mínimo de três é recomendado. Para cada cadeia de troca, faça o seguinte:

  1. Chame IDirect3DDevice9::CreateAdditionalSwapChain para criar a cadeia de troca.
  2. Chame IDirect3DSwapChain9::GetBackBuffer para obter um ponteiro para a superfície de buffer traseiro da cadeia de troca.
  3. Chame MFCreateVideoSampleFromSurface e passe um ponteiro para a superfície. Essa função retorna um ponteiro para um objeto de exemplo de vídeo. O objeto de exemplo de vídeo implementa a interface IMFSample e o apresentador usa essa interface para entregar a superfície ao misturador quando o apresentador chama o método IMFTransform::P rocessOutput do mixer. Para obter mais informações sobre o objeto de exemplo de vídeo, consulte Exemplos de Vídeo.
  4. Armazene o ponteiro IMFSample em uma fila. O apresentador extrairá exemplos dessa fila durante o processamento, conforme descrito na Saída de Processamento.
  5. Mantenha uma referência ao ponteiro IDirect3DSwapChain9 para que a cadeia de troca não seja liberada.

No modo exclusivo de tela inteira, o dispositivo não pode ter mais de uma cadeia de troca. Essa cadeia de troca é criada implicitamente quando você cria o dispositivo de tela inteira. A cadeia de troca pode ter mais de um buffer traseiro. Infelizmente, no entanto, se você apresentar um buffer traseiro enquanto grava em outro buffer traseiro na mesma cadeia de troca, não há uma maneira fácil de coordenar as duas operações. Isso ocorre devido à maneira como o Direct3D implementa a inversão de superfície. Quando você chama Present, o driver gráfico atualiza os ponteiros de superfície na memória gráfica. Se você estiver segurando quaisquer ponteiros IDirect3DSurface9 quando chamar Present, eles apontarão para buffers diferentes após o retorno da chamada Presente .

A opção mais simples é criar um exemplo de vídeo para a cadeia de troca. Se você escolher essa opção, siga as mesmas etapas fornecidas para o modo com janelas. A única diferença é que a fila de exemplo contém um único exemplo de vídeo. Outra opção é criar superfícies fora da tela e, em seguida, ablitá-las no buffer traseiro. As superfícies criadas devem dar suporte ao método IDirectXVideoProcessor::VideoProcessBlt , que o mixer usa para compor os quadros de saída.

Exemplos de acompanhamento

Quando o apresentador aloca os exemplos de vídeo pela primeira vez, ele os coloca em uma fila. O apresentador tira dessa fila sempre que precisa obter um novo quadro do misturador. Depois que o misturador gera o quadro, o apresentador move o exemplo para uma segunda fila. A segunda fila é para exemplos que estão aguardando os horários de apresentação agendados.

Para facilitar o acompanhamento do status de cada exemplo, o objeto de exemplo de vídeo implementa a interface IMFTrackedSample . Você pode usar essa interface da seguinte maneira:

  1. Implemente a interface IMFAsyncCallback em seu apresentador.

  2. Antes de colocar um exemplo na fila agendada, consulte o objeto de exemplo de vídeo para a interface IMFTrackedSample .

  3. Chame IMFTrackedSample::SetAllocator com um ponteiro para sua interface de retorno de chamada.

  4. Quando o exemplo estiver pronto para apresentação, remova-o da fila agendada, apresente-o e libere todas as referências ao exemplo.

  5. O exemplo invoca o retorno de chamada. (O objeto de exemplo não é excluído nesse caso porque mantém uma contagem de referência em si mesmo até que o retorno de chamada seja invocado.)

  6. Dentro do retorno de chamada, retorne o exemplo para a fila disponível.

Um apresentador não é obrigado a usar IMFTrackedSample para acompanhar exemplos; você pode implementar qualquer técnica que funcione melhor para seu design. Uma vantagem do IMFTrackedSample é que você pode mover as funções de agendamento e renderização do apresentador em objetos auxiliares, e esses objetos não precisam de nenhum mecanismo especial para chamar o apresentador quando eles lançam amostras de vídeo porque o objeto de exemplo fornece esse mecanismo.

O código a seguir mostra como definir o retorno de chamada:

HRESULT EVRCustomPresenter::TrackSample(IMFSample *pSample)
{
    IMFTrackedSample *pTracked = NULL;

    HRESULT hr = pSample->QueryInterface(IID_PPV_ARGS(&pTracked));

    if (SUCCEEDED(hr))
    {
        hr = pTracked->SetAllocator(&m_SampleFreeCB, NULL);
    }

    SafeRelease(&pTracked);
    return hr;
}

No retorno de chamada, chame IMFAsyncResult::GetObject no objeto de resultado assíncrono para recuperar um ponteiro para o exemplo:

HRESULT EVRCustomPresenter::OnSampleFree(IMFAsyncResult *pResult)
{
    IUnknown *pObject = NULL;
    IMFSample *pSample = NULL;
    IUnknown *pUnk = NULL;

    // Get the sample from the async result object.
    HRESULT hr = pResult->GetObject(&pObject);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pObject->QueryInterface(IID_PPV_ARGS(&pSample));
    if (FAILED(hr))
    {
        goto done;
    }

    // If this sample was submitted for a frame-step, the frame step operation
    // is complete.

    if (m_FrameStep.state == FRAMESTEP_SCHEDULED)
    {
        // Query the sample for IUnknown and compare it to our cached value.
        hr = pSample->QueryInterface(IID_PPV_ARGS(&pUnk));
        if (FAILED(hr))
        {
            goto done;
        }

        if (m_FrameStep.pSampleNoRef == (DWORD_PTR)pUnk)
        {
            // Notify the EVR.
            hr = CompleteFrameStep(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        // Note: Although pObject is also an IUnknown pointer, it is not
        // guaranteed to be the exact pointer value returned through
        // QueryInterface. Therefore, the second QueryInterface call is
        // required.
    }

    /*** Begin lock ***/

    EnterCriticalSection(&m_ObjectLock);

    UINT32 token = MFGetAttributeUINT32(
        pSample, MFSamplePresenter_SampleCounter, (UINT32)-1);

    if (token == m_TokenCounter)
    {
        // Return the sample to the sample pool.
        hr = m_SamplePool.ReturnSample(pSample);
        if (SUCCEEDED(hr))
        {
            // A free sample is available. Process more data if possible.
            (void)ProcessOutputLoop();
        }
    }

    LeaveCriticalSection(&m_ObjectLock);

    /*** End lock ***/

done:
    if (FAILED(hr))
    {
        NotifyEvent(EC_ERRORABORT, hr, 0);
    }
    SafeRelease(&pObject);
    SafeRelease(&pSample);
    SafeRelease(&pUnk);
    return hr;
}

Processamento de saída

Sempre que o misturador recebe um novo exemplo de entrada, o EVR envia uma mensagem MFVP_MESSAGE_PROCESSINPUTNOTIFY ao apresentador. Esta mensagem indica que o misturador pode ter um novo quadro de vídeo para entregar. Em resposta, o apresentador chama IMFTransform::P rocessOutput no misturador. Se o método for bem-sucedido, o apresentador agenda o exemplo para apresentação.

Para obter a saída do misturador, execute as seguintes etapas:

  1. Verifique o estado do relógio. Se o relógio estiver em pausa, ignore a mensagem MFVP_MESSAGE_PROCESSINPUTNOTIFY , a menos que este seja o primeiro quadro de vídeo. Se o relógio estiver em execução ou se esse for o primeiro quadro de vídeo, continue.

  2. Obtenha um exemplo da fila de exemplos disponíveis. Se a fila estiver vazia, isso significa que todos os exemplos alocados estão agendados para apresentação no momento. Nesse caso, ignore a mensagem MFVP_MESSAGE_PROCESSINPUTNOTIFY no momento. Quando o próximo exemplo ficar disponível, repita as etapas listadas aqui.

  3. (Opcional.) Se o relógio estiver disponível, obtenha a hora atual do relógio (T1) chamando IMFClock::GetCorrelatedTime.

  4. Chame IMFTransform::P rocessOutput no misturador. Se o ProcessOutput for bem-sucedido, o exemplo conterá um quadro de vídeo. Se o método falhar, verifique o código de retorno. Os seguintes códigos de erro do ProcessOutput não são falhas críticas:

    Código do erro Descrição
    MF_E_TRANSFORM_NEED_MORE_INPUT O misturador precisa de mais entrada antes de produzir um novo quadro de saída.
    Se você receber esse código de erro, verifique se o EVR chegou ao final do fluxo e responda adequadamente, conforme descrito em End of Stream. Caso contrário, ignore esta mensagem MF_E_TRANSFORM_NEED_MORE_INPUT . O EVR enviará outro quando o misturador receber mais entrada.
    MF_E_TRANSFORM_STREAM_CHANGE O tipo de saída do mixer tornou-se inválido, possivelmente devido a uma alteração de formato upstream.
    Se você receber esse código de erro, defina o tipo de mídia do apresentador como NULL. O EVR solicitará um novo formato.
    MF_E_TRANSFORM_TYPE_NOT_SET O misturador requer um novo tipo de mídia.
    Se você receber esse código de erro, renegociar o tipo de saída do mixer, conforme descrito em Formatos de Negociação.

     

    Se o ProcessOutput for bem-sucedido, continue.

  5. (Opcional.) Se o relógio estiver disponível, obtenha a hora atual do relógio (T2). A quantidade de latência introduzida pelo misturador é (T2T1 - ). Envie um evento EC_PROCESSING_LATENCY com esse valor para o EVR. O EVR usa esse valor para controle de qualidade. Se nenhum relógio estiver disponível, não haverá motivo para enviar o evento EC_PROCESSING_LATENCY .

  6. (Opcional.) Consulte o exemplo de IMFTrackedSample e chame IMFTrackedSample::SetAllocator conforme descrito em Exemplos de Rastreamento.

  7. Agende o exemplo para apresentação.

Essa sequência de etapas pode ser encerrada antes que o apresentador obtenha qualquer saída do mixer. Para garantir que nenhuma solicitação seja descartada, repita as mesmas etapas quando ocorrer o seguinte:

  • O método IMFClockStateSink do apresentador::OnClockStart ou IMFClockStateSink::OnClockStart é chamado. Isso manipula o caso em que o misturador ignora a entrada porque o relógio está em pausa (etapa 1).
  • O retorno de chamada IMFTrackedSample é invocado. Isso manipula o caso em que o misturador recebe entrada, mas todos os exemplos de vídeo do apresentador estão em uso (etapa 2).

Os exemplos de código a seguir mostram essas etapas com mais detalhes. O apresentador chama o ProcessInputNotify método (mostrado no exemplo a seguir) quando obtém a mensagem MFVP_MESSAGE_PROCESSINPUTNOTIFY .

//-----------------------------------------------------------------------------
// ProcessInputNotify
//
// Attempts to get a new output sample from the mixer.
//
// This method is called when the EVR sends an MFVP_MESSAGE_PROCESSINPUTNOTIFY
// message, which indicates that the mixer has a new input sample.
//
// Note: If there are multiple input streams, the mixer might not deliver an
// output sample for every input sample.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessInputNotify()
{
    HRESULT hr = S_OK;

    // Set the flag that says the mixer has a new sample.
    m_bSampleNotify = TRUE;

    if (m_pMediaType == NULL)
    {
        // We don't have a valid media type yet.
        hr = MF_E_TRANSFORM_TYPE_NOT_SET;
    }
    else
    {
        // Try to process an output sample.
        ProcessOutputLoop();
    }
    return hr;
}

Esse ProcessInputNotify método define um sinalizador booliano para registrar o fato de que o misturador tem uma nova entrada. Em seguida, ele chama o ProcessOutputLoop método, mostrado no próximo exemplo. Esse método tenta extrair o máximo possível de amostras do misturador:

void EVRCustomPresenter::ProcessOutputLoop()
{
    HRESULT hr = S_OK;

    // Process as many samples as possible.
    while (hr == S_OK)
    {
        // If the mixer doesn't have a new input sample, break from the loop.
        if (!m_bSampleNotify)
        {
            hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
            break;
        }

        // Try to process a sample.
        hr = ProcessOutput();

        // NOTE: ProcessOutput can return S_FALSE to indicate it did not
        // process a sample. If so, break out of the loop.
    }

    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
    {
        // The mixer has run out of input data. Check for end-of-stream.
        CheckEndOfStream();
    }
}

O ProcessOutput método, mostrado no próximo exemplo, tenta obter um único quadro de vídeo do misturador. Se nenhum quadro de vídeo estiver disponível, ProcessSample retornará S_FALSE ou um código de erro, o que interromperá o loop em ProcessOutputLoop. A maior parte do trabalho é feita dentro do ProcessOutput método:

//-----------------------------------------------------------------------------
// ProcessOutput
//
// Attempts to get a new output sample from the mixer.
//
// Called in two situations:
// (1) ProcessOutputLoop, if the mixer has a new input sample.
// (2) Repainting the last frame.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessOutput()
{
    assert(m_bSampleNotify || m_bRepaint);  // See note above.

    HRESULT     hr = S_OK;
    DWORD       dwStatus = 0;
    LONGLONG    mixerStartTime = 0, mixerEndTime = 0;
    MFTIME      systemTime = 0;
    BOOL        bRepaint = m_bRepaint; // Temporarily store this state flag.

    MFT_OUTPUT_DATA_BUFFER dataBuffer;
    ZeroMemory(&dataBuffer, sizeof(dataBuffer));

    IMFSample *pSample = NULL;

    // If the clock is not running, we present the first sample,
    // and then don't present any more until the clock starts.

    if ((m_RenderState != RENDER_STATE_STARTED) &&  // Not running.
         !m_bRepaint &&             // Not a repaint request.
         m_bPrerolled               // At least one sample has been presented.
         )
    {
        return S_FALSE;
    }

    // Make sure we have a pointer to the mixer.
    if (m_pMixer == NULL)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Try to get a free sample from the video sample pool.
    hr = m_SamplePool.GetSample(&pSample);
    if (hr == MF_E_SAMPLEALLOCATOR_EMPTY)
    {
        // No free samples. Try again when a sample is released.
        return S_FALSE;
    }
    else if (FAILED(hr))
    {
        return hr;
    }

    // From now on, we have a valid video sample pointer, where the mixer will
    // write the video data.
    assert(pSample != NULL);

    // (If the following assertion fires, it means we are not managing the sample pool correctly.)
    assert(MFGetAttributeUINT32(pSample, MFSamplePresenter_SampleCounter, (UINT32)-1) == m_TokenCounter);

    if (m_bRepaint)
    {
        // Repaint request. Ask the mixer for the most recent sample.
        SetDesiredSampleTime(
            pSample,
            m_scheduler.LastSampleTime(),
            m_scheduler.FrameDuration()
            );

        m_bRepaint = FALSE; // OK to clear this flag now.
    }
    else
    {
        // Not a repaint request. Clear the desired sample time; the mixer will
        // give us the next frame in the stream.
        ClearDesiredSampleTime(pSample);

        if (m_pClock)
        {
            // Latency: Record the starting time for ProcessOutput.
            (void)m_pClock->GetCorrelatedTime(0, &mixerStartTime, &systemTime);
        }
    }

    // Now we are ready to get an output sample from the mixer.
    dataBuffer.dwStreamID = 0;
    dataBuffer.pSample = pSample;
    dataBuffer.dwStatus = 0;

    hr = m_pMixer->ProcessOutput(0, 1, &dataBuffer, &dwStatus);

    if (FAILED(hr))
    {
        // Return the sample to the pool.
        HRESULT hr2 = m_SamplePool.ReturnSample(pSample);
        if (FAILED(hr2))
        {
            hr = hr2;
            goto done;
        }
        // Handle some known error codes from ProcessOutput.
        if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
        {
            // The mixer's format is not set. Negotiate a new format.
            hr = RenegotiateMediaType();
        }
        else if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
        {
            // There was a dynamic media type change. Clear our media type.
            SetMediaType(NULL);
        }
        else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
        {
            // The mixer needs more input.
            // We have to wait for the mixer to get more input.
            m_bSampleNotify = FALSE;
        }
    }
    else
    {
        // We got an output sample from the mixer.

        if (m_pClock && !bRepaint)
        {
            // Latency: Record the ending time for the ProcessOutput operation,
            // and notify the EVR of the latency.

            (void)m_pClock->GetCorrelatedTime(0, &mixerEndTime, &systemTime);

            LONGLONG latencyTime = mixerEndTime - mixerStartTime;
            NotifyEvent(EC_PROCESSING_LATENCY, (LONG_PTR)&latencyTime, 0);
        }

        // Set up notification for when the sample is released.
        hr = TrackSample(pSample);
        if (FAILED(hr))
        {
            goto done;
        }

        // Schedule the sample.
        if ((m_FrameStep.state == FRAMESTEP_NONE) || bRepaint)
        {
            hr = DeliverSample(pSample, bRepaint);
            if (FAILED(hr))
            {
                goto done;
            }
        }
        else
        {
            // We are frame-stepping (and this is not a repaint request).
            hr = DeliverFrameStepSample(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        m_bPrerolled = TRUE; // We have presented at least one sample now.
    }

done:
    SafeRelease(&pSample);

    // Important: Release any events returned from the ProcessOutput method.
    SafeRelease(&dataBuffer.pEvents);
    return hr;
}

Algumas observações sobre este exemplo:

  • A variável m_SamplePool é considerada um objeto de coleção que contém a fila de exemplos de vídeo disponíveis. O método do GetSample objeto retornará MF_E_SAMPLEALLOCATOR_EMPTY se a fila estiver vazia.
  • Se o método ProcessOutput do mixer retornar MF_E_TRANSFORM_NEED_MORE_INPUT, isso significa que o misturador não pode produzir mais saída, portanto, o apresentador limpa o sinalizador m_fSampleNotify .
  • O TrackSample método, que define o retorno de chamada IMFTrackedSample , é mostrado na seção Acompanhamento de Exemplos.

Repinting Frames

Ocasionalmente, o apresentador pode precisar repintar o quadro de vídeo mais recente. Por exemplo, o apresentador padrão reintiza o quadro nas seguintes situações:

Use as seguintes etapas para solicitar que o misturador recrie o quadro mais recente:

  1. Obtenha um exemplo de vídeo da fila.
  2. Consulte o exemplo da interface IMFDesiredSample .
  3. Chame IMFDesiredSample::SetDesiredSampleTimeAndDuration. Especifique o carimbo de data/hora do quadro de vídeo mais recente. (Você precisará armazenar esse valor em cache e atualizá-lo para cada quadro.)
  4. Chame ProcessOutput no misturador.

Ao repinr um quadro, você pode ignorar o relógio de apresentação e apresentar o quadro imediatamente.

Exemplos de agendamento

Os quadros de vídeo podem alcançar o EVR a qualquer momento. O apresentador é responsável por apresentar cada quadro na hora correta, com base no carimbo de data/hora do quadro. Quando o apresentador obtém um novo exemplo do misturador, ele coloca o exemplo na fila agendada. Em um thread separado, o apresentador obtém continuamente o primeiro exemplo da cabeça da fila e determina se:

  • Apresente o exemplo.
  • Mantenha o exemplo na fila porque está adiantado.
  • Descarte o exemplo porque está atrasado. Embora você deva evitar descartar quadros, se possível, talvez seja necessário se o apresentador estiver continuamente ficando para trás.

Para obter o carimbo de data/hora de um quadro de vídeo, chame IMFSample::GetSampleTime no exemplo de vídeo. O carimbo de data/hora é relativo ao relógio de apresentação do EVR. Para obter a hora do relógio atual, chame IMFClock::GetCorrelatedTime. Se o EVR não tiver um relógio de apresentação ou se um exemplo não tiver um carimbo de data/hora, você poderá apresentar o exemplo imediatamente após obtê-lo.

Para obter a duração de cada exemplo, chame IMFSample::GetSampleDuration. Se o exemplo não tiver uma duração, você poderá usar a função MFFrameRateToAverageTimePerFrame para calcular a duração da taxa de quadros.

Ao agendar exemplos, tenha em mente o seguinte:

  • Se a taxa de reprodução for mais rápida ou mais lenta do que a velocidade normal, o relógio será executado em uma taxa mais rápida ou mais lenta. Isso significa que o carimbo de data/hora em um exemplo sempre dá o tempo de destino correto em relação ao relógio de apresentação. No entanto, se você traduzir os horários de apresentação em algum outro horário do relógio (por exemplo, o contador de execução de alta resolução), deverá dimensionar as horas com base na velocidade do relógio. Se a velocidade do relógio mudar, o EVR chamará o método IMFClockStateSink::OnClockSetRate do apresentador.
  • A taxa de reprodução pode ser negativa para a reprodução inversa. Quando a taxa de reprodução é negativa, o relógio de apresentação é executado para trás. Em outras palavras, o tempo N + 1 ocorre antes do tempo N.

O exemplo a seguir calcula o quão cedo ou tarde é uma amostra, em relação ao relógio de apresentação:

    LONGLONG hnsPresentationTime = 0;
    LONGLONG hnsTimeNow = 0;
    MFTIME   hnsSystemTime = 0;

    BOOL bPresentNow = TRUE;
    LONG lNextSleep = 0;

    if (m_pClock)
    {
        // Get the sample's time stamp. It is valid for a sample to
        // have no time stamp.
        hr = pSample->GetSampleTime(&hnsPresentationTime);

        // Get the clock time. (But if the sample does not have a time stamp,
        // we don't need the clock time.)
        if (SUCCEEDED(hr))
        {
            hr = m_pClock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
        }

        // Calculate the time until the sample's presentation time.
        // A negative value means the sample is late.
        LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow;
        if (m_fRate < 0)
        {
            // For reverse playback, the clock runs backward. Therefore, the
            // delta is reversed.
            hnsDelta = - hnsDelta;
        }

O relógio de apresentação geralmente é controlado pelo relógio do sistema ou pelo renderizador de áudio. (O renderizador de áudio deriva o tempo da taxa na qual a placa de som consome áudio.) Em geral, o relógio de apresentação não é sincronizado com a taxa de atualização do monitor.

Se os parâmetros de apresentação do Direct3D especificarem D3DPRESENT_INTERVAL_DEFAULT ou D3DPRESENT_INTERVAL_ONE para o intervalo de apresentação, a operação Presente aguardará o novo rastreamento vertical do monitor. Essa é uma maneira fácil de evitar o rasgo, mas reduz a precisão do algoritmo de agendamento. Por outro lado, se o intervalo de apresentação for D3DPRESENT_INTERVAL_IMMEDIATE, o método Present será executado imediatamente, o que causará o corte, a menos que o algoritmo de agendamento seja preciso o suficiente para chamar Present somente durante o período de retração vertical.

As seguintes funções podem ajudá-lo a obter informações precisas de tempo:

  • IDirect3DDevice9::GetRasterStatus retorna informações sobre o raster, incluindo a linha de verificação atual e se o raster está no período em branco vertical.
  • DwmGetCompositionTimingInfo retorna informações de tempo para o gerenciador de janelas da área de trabalho. Essas informações serão úteis se a composição da área de trabalho estiver habilitada.

Apresentando exemplos

Esta seção pressupõe que você criou uma cadeia de troca separada para cada superfície, conforme descrito em Alocar Superfícies Direct3D. Para apresentar um exemplo, obtenha a cadeia de troca do exemplo de vídeo da seguinte maneira:

  1. Chame IMFSample::GetBufferByIndex no exemplo de vídeo para obter o buffer.
  2. Consulte o buffer para a interface IMFGetService .
  3. Chame IMFGetService::GetService para obter a interface IDirect3DSurface9 da superfície direct3D. (Você pode combinar essa etapa e a etapa anterior em uma chamando MFGetService.)
  4. Chame IDirect3DSurface9::GetContainer na superfície para obter um ponteiro para a cadeia de troca.
  5. Chame IDirect3DSwapChain9::P resent na cadeia de troca.

O código a seguir mostra essas etapas:

HRESULT D3DPresentEngine::PresentSample(IMFSample* pSample, LONGLONG llTarget)
{
    HRESULT hr = S_OK;

    IMFMediaBuffer* pBuffer = NULL;
    IDirect3DSurface9* pSurface = NULL;
    IDirect3DSwapChain9* pSwapChain = NULL;

    if (pSample)
    {
        // Get the buffer from the sample.
        hr = pSample->GetBufferByIndex(0, &pBuffer);
        if (FAILED(hr))
        {
            goto done;
        }

        // Get the surface from the buffer.
        hr = MFGetService(pBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pSurface));
        if (FAILED(hr))
        {
            goto done;
        }
    }
    else if (m_pSurfaceRepaint)
    {
        // Redraw from the last surface.
        pSurface = m_pSurfaceRepaint;
        pSurface->AddRef();
    }

    if (pSurface)
    {
        // Get the swap chain from the surface.
        hr = pSurface->GetContainer(IID_PPV_ARGS(&pSwapChain));
        if (FAILED(hr))
        {
            goto done;
        }

        // Present the swap chain.
        hr = PresentSwapChain(pSwapChain, pSurface);
        if (FAILED(hr))
        {
            goto done;
        }

        // Store this pointer in case we need to repaint the surface.
        CopyComPointer(m_pSurfaceRepaint, pSurface);
    }
    else
    {
        // No surface. All we can do is paint a black rectangle.
        PaintFrameWithGDI();
    }

done:
    SafeRelease(&pSwapChain);
    SafeRelease(&pSurface);
    SafeRelease(&pBuffer);

    if (FAILED(hr))
    {
        if (hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG)
        {
            // We failed because the device was lost. Fill the destination rectangle.
            PaintFrameWithGDI();

            // Ignore. We need to reset or re-create the device, but this method
            // is probably being called from the scheduler thread, which is not the
            // same thread that created the device. The Reset(Ex) method must be
            // called from the thread that created the device.

            // The presenter will detect the state when it calls CheckDeviceState()
            // on the next sample.
            hr = S_OK;
        }
    }
    return hr;
}

Retângulos de origem e destino

O retângulo de origem é a parte do quadro de vídeo a ser exibida. Ele é definido em relação a um sistema de coordenadas normalizado, no qual todo o quadro de vídeo ocupa um retângulo com coordenadas {0, 0, 1, 1}. O retângulo de destino é a área dentro da superfície de destino em que o quadro de vídeo é desenhado. O apresentador padrão permite que um aplicativo defina esses retângulos chamando IMFVideoDisplayControl::SetVideoPosition.

Há várias opções para aplicar retângulos de origem e destino. A primeira opção é permitir que o misturador os aplique:

  • Defina o retângulo de origem usando o atributo VIDEO_ZOOM_RECT . O misturador aplicará o retângulo de origem quando o vídeo for cortado para a superfície de destino. O retângulo de origem padrão do misturador é o quadro inteiro.
  • Defina o retângulo de destino como a abertura geométrica no tipo de saída do misturador. Para obter mais informações, consulte Formatos de negociação.

A segunda opção é aplicar os retângulos quando você IDirect3DSwapChain9::P resent especificando os parâmetros pSourceRect e pDestRect no método Present . Você pode combinar essas opções. Por exemplo, você pode definir o retângulo de origem no misturador, mas aplicar o retângulo de destino no método Present .

Se o aplicativo alterar o retângulo de destino ou redimensionar a janela, talvez seja necessário alocar novas superfícies. Nesse caso, você deve ter cuidado para sincronizar essa operação com o thread de agendamento. Libere a fila de agendamento e descarte as amostras antigas antes de alocar novas superfícies.

Fim do Fluxo

Quando cada fluxo de entrada no EVR for encerrado, o EVR enviará uma mensagem de MFVP_MESSAGE_ENDOFSTREAM ao apresentador. No momento em que você recebe a mensagem, no entanto, pode haver alguns quadros de vídeo restantes a serem processados. Antes de responder à mensagem de fim de fluxo, você deve drenar toda a saída do misturador e apresentar todos os quadros restantes. Depois que o último quadro for apresentado, envie um evento de EC_COMPLETE para o EVR.

O exemplo a seguir mostra um método que envia o evento EC_COMPLETE se várias condições forem atendidas. Caso contrário, ele retornará S_OK sem enviar o evento:

HRESULT EVRCustomPresenter::CheckEndOfStream()
{
    if (!m_bEndStreaming)
    {
        // The EVR did not send the MFVP_MESSAGE_ENDOFSTREAM message.
        return S_OK;
    }

    if (m_bSampleNotify)
    {
        // The mixer still has input.
        return S_OK;
    }

    if (m_SamplePool.AreSamplesPending())
    {
        // Samples are still scheduled for rendering.
        return S_OK;
    }

    // Everything is complete. Now we can tell the EVR that we are done.
    NotifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0);
    m_bEndStreaming = FALSE;
    return S_OK;
}

Este método verifica os seguintes estados:

  • Se a variável m_fSampleNotify for TRUE, significará que o misturador tem um ou mais quadros que ainda não foram processados. (Para obter detalhes, consulte Saída de Processamento.)
  • A variável m_fEndStreaming é um sinalizador booliano cujo valor inicial FALSE. O apresentador define o sinalizador como TRUE quando o EVR envia a mensagem de MFVP_MESSAGE_ENDOFSTREAM .
  • Supõe-se AreSamplesPending que o método retorne TRUE desde que um ou mais quadros estejam aguardando na fila agendada.

No método IMFVideoPresenter::P rocessMessage , defina m_fEndStreaming como TRUE e chame CheckEndOfStream quando o EVR enviar a mensagem de MFVP_MESSAGE_ENDOFSTREAM :

HRESULT EVRCustomPresenter::ProcessMessage(
    MFVP_MESSAGE_TYPE eMessage,
    ULONG_PTR ulParam
    )
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&m_ObjectLock);

    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    switch (eMessage)
    {
    // Flush all pending samples.
    case MFVP_MESSAGE_FLUSH:
        hr = Flush();
        break;

    // Renegotiate the media type with the mixer.
    case MFVP_MESSAGE_INVALIDATEMEDIATYPE:
        hr = RenegotiateMediaType();
        break;

    // The mixer received a new input sample.
    case MFVP_MESSAGE_PROCESSINPUTNOTIFY:
        hr = ProcessInputNotify();
        break;

    // Streaming is about to start.
    case MFVP_MESSAGE_BEGINSTREAMING:
        hr = BeginStreaming();
        break;

    // Streaming has ended. (The EVR has stopped.)
    case MFVP_MESSAGE_ENDSTREAMING:
        hr = EndStreaming();
        break;

    // All input streams have ended.
    case MFVP_MESSAGE_ENDOFSTREAM:
        // Set the EOS flag.
        m_bEndStreaming = TRUE;
        // Check if it's time to send the EC_COMPLETE event to the EVR.
        hr = CheckEndOfStream();
        break;

    // Frame-stepping is starting.
    case MFVP_MESSAGE_STEP:
        hr = PrepareFrameStep(LODWORD(ulParam));
        break;

    // Cancels frame-stepping.
    case MFVP_MESSAGE_CANCELSTEP:
        hr = CancelFrameStep();
        break;

    default:
        hr = E_INVALIDARG; // Unknown message. This case should never occur.
        break;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Além disso, chame CheckEndOfStream se o método IMFTransform::P rocessOutput do misturador retornar MF_E_TRANSFORM_NEED_MORE_INPUT. Esse código de erro indica que o misturador não tem mais exemplos de entrada (consulte Saída de Processamento).

Quadro pisando

O EVR foi projetado para dar suporte à DirectShow de quadros e à limpeza no Media Foundation. A etapa e a limpeza de quadros são conceitualmente semelhantes. Em ambos os casos, o aplicativo solicita um quadro de vídeo por vez. Internamente, o apresentador usa o mesmo mecanismo para implementar ambos os recursos.

O passo a passo do quadro DirectShow funciona da seguinte maneira:

  • O aplicativo chama IVideoFrameStep::Step. O número de etapas é dado no parâmetro dwSteps . O EVR envia uma mensagem MFVP_MESSAGE_STEP para o apresentador, em que o parâmetro de mensagem (ulParam) é o número de etapas.
  • Se o aplicativo chamar IVideoFrameStep::CancelStep ou alterar o estado do grafo (em execução, pausado ou interrompido), o EVR enviará uma mensagem MFVP_MESSAGE_CANCELSTEP .

A limpeza no Media Foundation funciona da seguinte maneira:

  • O aplicativo define a taxa de reprodução como zero chamando IMFRateControl::SetRate.
  • Para renderizar um novo quadro, o aplicativo chama IMFMediaSession::Start com a posição desejada. O EVR envia uma mensagem MFVP_MESSAGE_STEP com ulParam igual a 1.
  • Para interromper a limpeza, o aplicativo define a taxa de reprodução como um valor diferente de zero. O EVR envia a mensagem MFVP_MESSAGE_CANCELSTEP .

Depois de receber a mensagem MFVP_MESSAGE_STEP , o apresentador aguarda a chegada do quadro de destino. Se o número de etapas for N, o apresentador descartará as próximas amostras (N – 1) e apresentará a amostra N . Quando o apresentador conclui a etapa de quadro, ele envia um evento EC_STEP_COMPLETE para o EVR com lParam1 definido como FALSE. Além disso, se a taxa de reprodução for zero, o apresentador enviará um evento EC_SCRUB_TIME . Se o EVR cancelar a etapa de quadro enquanto uma operação de etapa de quadro ainda estiver pendente, o apresentador enviará um evento EC_STEP_COMPLETE com lParam1 definido como TRUE.

O aplicativo pode enquadrar a etapa ou limpar várias vezes, para que o apresentador possa receber várias mensagens MFVP_MESSAGE_STEP antes de receber uma mensagem de MFVP_MESSAGE_CANCELSTEP . Além disso, o apresentador pode receber a mensagem MFVP_MESSAGE_STEP antes do relógio ser iniciado ou enquanto o relógio estiver em execução.

Implementando a etapa de quadro

Esta seção descreve um algoritmo para implementar a etapa de quadro. O algoritmo de etapa de quadro usa as seguintes variáveis:

  • step_count. Um inteiro sem sinal que especifica o número de etapas na operação de etapa de quadro atual.

  • step_queue. Uma fila de ponteiros IMFSample .

  • step_state. A qualquer momento, o apresentador pode estar em um dos seguintes estados em relação à etapa de quadro:

    Estado Descrição
    NOT_STEPPING Não a etapa de quadro.
    AGUARDANDO O apresentador recebeu a mensagem MFVP_MESSAGE_STEP , mas o relógio não foi iniciado.
    PENDING O apresentador recebeu a mensagem MFVP_MESSAGE_STEP e o relógio foi iniciado, mas o apresentador está esperando para receber o quadro de destino.
    AGENDADA O apresentador recebeu o quadro de destino e agendou-o para apresentação, mas o quadro não foi apresentado.
    COMPLETA O apresentador apresentou o quadro de destino e enviou o evento EC_STEP_COMPLETE e está aguardando a próxima mensagem MFVP_MESSAGE_STEP ou MFVP_MESSAGE_CANCELSTEP .

     

    Esses estados são independentes dos estados do apresentador listados na seção Estados do Apresentador.

Os procedimentos a seguir são definidos para o algoritmo de etapa de quadro:

Procedimento PrepareFrameStep

  1. Incremente step_count.
  2. Defina step_state como WAITING.
  3. Se o relógio estiver em execução, chame StartFrameStep.

Procedimento StartFrameStep

  1. Se step_state for igual a WAITING, defina step_state como PENDENTE. Para cada exemplo em step_queue, chame DeliverFrameStepSample.
  2. Se step_state for igual a NOT_STEPPING, remova os exemplos de step_queue e agende-os para apresentação.

Procedimento CompleteFrameStep

  1. Defina step_state como COMPLETE.
  2. Envie o evento EC_STEP_COMPLETE com lParam1FALSE = .
  3. Se a taxa do relógio for zero, envie o evento EC_SCRUB_TIME com o tempo de exemplo.

Procedimento DeliverFrameStepSample

  1. Se a taxa de relógio for zero e a hora de duração< de timesample + de exemplo, descarte o exemplo. Sair.
  2. Se step_state for igual a SCHEDULED ou COMPLETE, adicione o exemplo a step_queue. Sair.
  3. Decremento step_count.
  4. Se step_count> 0, descarte o exemplo. Sair.
  5. Se step_state for igual a WAITING, adicione o exemplo a step_queue. Sair.
  6. Agende o exemplo para apresentação.
  7. Defina step_state como AGENDADO.

Procedimento CancelFrameStep

  1. Definir step_state como NOT_STEPPING
  2. Redefina step_count para zero.
  3. Se o valor anterior de step_state era WAITING, PENDING ou SCHEDULED, envie EC_STEP_COMPLETE com lParam1TRUE = .

Chame estes procedimentos da seguinte maneira:

Mensagem ou método do apresentador Procedimento
MFVP_MESSAGE_STEP mensagem PrepareFrameStep
MFVP_MESSAGE_STEP mensagem CancelStep
IMFClockStateSink::OnClockStart StartFrameStep
IMFClockStateSink::OnClockRestart StartFrameStep
Retorno de chamada imfTrackedSample CompleteFrameStep
IMFClockStateSink::OnClockStop CancelFrameStep
IMFClockStateSink::OnClockSetRate CancelFrameStep

 

O gráfico de fluxo a seguir mostra os procedimentos de etapa de quadro.

flow chart showing paths that start with mfvp-message-step and mfvp-message-processinputnotify and end at

Configurando o Apresentador no EVR

Depois de implementar o apresentador, a próxima etapa é configurar o EVR para usá-lo.

Definindo o Apresentador no DirectShow

Em um aplicativo DirectShow, defina o apresentador no EVR da seguinte maneira:

  1. Crie o filtro EVR chamando CoCreateInstance. O CLSID é CLSID_EnhancedVideoRenderer.
  2. Adicione o EVR ao grafo de filtro.
  3. Crie uma instância do apresentador. O apresentador pode dar suporte à criação de objeto COM padrão por meio do IClassFactory, mas isso não é obrigatório.
  4. Consulte o filtro EVR para a interface IMFVideoRenderer .
  5. Chame IMFVideoRenderer::InitializeRenderer.

Configurando o apresentador no Media Foundation

No Media Foundation, você tem várias opções, dependendo se você cria o coletor de mídia EVR ou o objeto de ativação EVR. Para obter mais informações sobre objetos de ativação, consulte Objetos de Ativação.

Para o coletor de mídia EVR, faça o seguinte:

  1. Chame MFCreateVideoRenderer para criar o coletor de mídia.
  2. Crie uma instância do apresentador.
  3. Consulte o coletor de mídia EVR para a interface IMFVideoRenderer .
  4. Chame IMFVideoRenderer::InitializeRenderer.

Para o objeto de ativação EVR, faça o seguinte:

  1. Chame MFCreateVideoRendererActivate para criar o objeto de ativação.

  2. Defina um dos seguintes atributos no objeto de ativação:

    Atributo Descrição
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE Ponteiro para um objeto de ativação para o apresentador.
    Com esse sinalizador, você deve fornecer um objeto de ativação para o apresentador. O objeto de ativação deve implementar a interface IMFActivate .
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID do apresentador.
    Com esse sinalizador, o apresentador deve dar suporte à criação de objeto COM padrão por meio do IClassFactory.

     

  3. Opcionalmente, defina o atributo MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS no objeto de ativação.

Renderizador de vídeo aprimorado

Exemplo de EVRPresenter