Partilhar via


Como escrever um apresentador EVR

[O componente descrito nesta página, Enhanced Video Renderer, é um recurso herdado. Ele foi substituído pelo Simple Video Renderer (SVR) disponibilizado através dos componentes MediaPlayer e IMFMediaEngine. Para reproduzir conteúdo de vídeo, você deve enviar dados para um desses componentes e permitir que eles instanciem o novo renderizador de vídeo. Esses componentes foram otimizados para Windows 10 e Windows 11. A Microsoft recomenda enfaticamente que o novo código use MediaPlayer ou o nível inferior IMFMediaEngine APIs para reproduzir mídia de vídeo no Windows em vez do EVR, quando possível. A Microsoft sugere que o código existente que usa as APIs herdadas seja reescrito para usar as novas APIs, se possível.]

Este artigo descreve como escrever um apresentador personalizado para o renderizador de vídeo avançado (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 EVRPresenter Sample, 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 Renderizador de vídeo avançado.
  • Gráficos Direct3D. Você não precisa entender 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 DirectX Graphics SDK.
  • Gráficos de filtro DirectShow ou o pipeline do Media Foundation, dependendo da tecnologia que a sua aplicação usará para apresentar vídeo.
  • Media Foundation transforma. O misturador EVR é uma transformação da Media Foundation, e o apresentador invoca métodos diretamente no misturador.
  • Implementação de objetos COM. O apresentador é um objeto COM de thread livre em processo.

Modelo de objeto do Presenter

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

Fluxo de dados dentro do EVR

O EVR usa dois componentes de plug-in para renderizar vídeo: o misturador e o apresentador. O mixer mistura os fluxos de vídeo e desentrelaça o vídeo, se necessário. O apresentador exibe (ou apresenta) o vídeo no ecrã e programa o momento em que cada fotograma é apresentado. 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 subfluxos, que o misturador mistura através de alfa 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 combinador seleciona o quadro mais recente de cada subfluxo, faz uma composição alfa sobre o quadro de referência e produz um único quadro composto. O misturador também realiza desentrelaçamento e conversão de cores de YUV para RGB, se necessário. O EVR insere sempre o mixer no pipeline de vídeo, independentemente do número de canais de entrada ou do formato de vídeo. A imagem seguinte ilustra este processo.

diagrama mostrando o fluxo de referência e o subfluxo apontando para o mixer, que aponta para o apresentador, que aponta para o ecrã

O apresentador executa as seguintes tarefas:

  • Define o formato de saída no misturador. Antes do início do streaming, o apresentador define um tipo de mídia no fluxo de saída do mixer. Esse tipo de mídia define o formato da imagem composta.
  • Cria o dispositivo Direct3D.
  • Aloca superfícies Direct3D. O misturador coloca as armações compostas 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 os quadros de acordo com esse relógio.
  • Apresenta cada quadro usando Direct3D.
  • Executa a revisão e depuração de quadros.

Estados do apresentador

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

  • começou. O relógio de apresentação do EVR está funcionando. O apresentador agenda quadros de vídeo para apresentação à medida que eles chegam.
  • Está em pausa. O relógio de apresentação está suspenso. O apresentador não apresenta novos exemplos, mas mantém sua fila de amostras agendadas. Se novas amostras forem recebidas, o apresentador as adicionará à fila.
  • Parou. O relógio de apresentação está parado. O apresentador descarta todas as amostras que foram agendadas.
  • Desligue. O apresentador libera todos os recursos relacionados ao streaming, como superfícies Direct3D. Este é o estado inicial do apresentador e o estado final antes de o 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 estiver 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 Implementando o IMFClockStateSink.
IMFGetService Fornece uma maneira para que o aplicativo e outros componentes no pipeline obtenham interfaces do apresentador.
IMFTopologyServiceLookupClient Permite que o apresentador obtenha interfaces do EVR ou do misturador. Consulte Implementing IMFTopologyServiceLookupClient.
IMFVideoDeviceID Garante que o apresentador e o misturador utilizem tecnologias compatíveis. Consulte Implementando o IMFVideoDeviceID.
IMFVideoPresenter Processa mensagens do EVR. Consulte Implementando o IMFVideoPresenter.

 

As seguintes interfaces são opcionais:

Interface gráfica 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 caminho de mídia protegido (PMP).
IMFRateSupport Informa a gama de velocidades de reprodução suportadas pelo apresentador. Veja Implementando o IMFRateSupport.
IMFVideoPositionMapper Mapeia coordenadas no quadro de vídeo de saída para coordenadas no quadro de vídeo de entrada.
IQualProp Transmite informações de desempenho. O EVR utiliza estas informações para a gestão do controlo 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 esta finalidade. Você pode implementar essa interface ou definir a sua própria. O aplicativo obtém as interfaces do apresentador ao chamar IMFGetService::GetService no EVR. Quando o GUID do serviço é MR_VIDEO_RENDER_SERVICE, o EVR passa a solicitação GetService ao apresentador.

Implementando IMFVideoDeviceID

A interface do 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 misturador padrão e o apresentador usam o Direct3D 9, com o GUID do dispositivo igual a IID_IDirect3DDevice9. Se você pretende usar seu apresentador personalizado com o mixer padrão, o GUID do dispositivo do apresentador deve ser IID_IDirect3DDevice9. Se você substituir ambos os componentes, poderá definir um novo GUID de dispositivo. Para o restante deste artigo, presume-se que o apresentador usa o Direct3D 9. Aqui está a implementação padrão do GetDeviceID:

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

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

O método deve ser bem-sucedido mesmo quando o apresentador é desligado.

Implementando IMFTopologyServiceLookupClient

A interfaceIMFTopologyServiceLookupClientpermite 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 usam 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 do serviço:

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

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

EVR Interface Descrição
IMediaEventSink Fornece uma maneira para o apresentador enviar mensagens para o EVR. Essa interface é definida no SDK do 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 um relógio, por isso esta 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 a partir do misturador:

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

 

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

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 a partir do LookupService deixam de ser válidos, o EVR chama IMFTopologyServiceLookupClient::ReleaseServicePointers. Dentro desse método, solte 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:

  • Desconectando ou reconectando pinos (DirectShow) ou adicionando ou removendo coletores de fluxo (Media Foundation).
  • Alteração de formato.
  • Configurar 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 interfaceIMFVideoPresenter 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 GetCurrentMediaType método 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 IMFVideoMediaType ponteiro de interface. O exemplo a seguir pressupõe que o apresentador armazena o tipo de mídia como um ponteiroIMFMediaType. Para obter a interface IMFVideoMediaType do tipo de media, 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 principal mecanismo para o EVR se comunicar com o apresentador. As seguintes mensagens são definidas. Os detalhes da implementação de cada mensagem são fornecidos 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 mixer. Consulte Formatos de negociação.
MFVP_MESSAGE_BEGINSTREAMING O streaming já começou. 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 MFVP_MESSAGE_BEGINSTREAMING.
MFVP_MESSAGE_PROCESSINPUTNOTIFY O misturador recebeu uma nova amostra de entrada e pode ser capaz de gerar um novo quadro de saída. O apresentador deve chamar IMFTransform::ProcessOutput no misturador. Consulte Processamento de saída.
MFVP_MESSAGE_ENDOFSTREAM A apresentação terminou. Consulte Fim do fluxo.
MFVP_MESSAGE_FLUSH O EVR está a purgar os dados no pipeline de renderização. O apresentador deve descartar todos os quadros de vídeo agendados para apresentação.
MFVP_MESSAGE_STEP Solicita ao apresentador que avance com N fotogramas. O apresentador deve descartar os próximos quadros N-1 e exibir o quadro N. Consulte Frame Stepping.
MFVP_MESSAGE_CANCELSTEP Cancela o avanço de quadros.

 

Implementando IMFClockStateSink

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

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

Método Descrição
AoIniciarRelógio
  1. Defina o estado do apresentador como iniciado.
  2. Se o llClockStartOffset não estiver PRESENTATION_CURRENT_POSITION, libere a fila de amostras do apresentador. (Isso equivale a receber uma mensagem MFVP_MESSAGE_FLUSH.)
  3. Se um pedido anterior de avanço de fotograma ainda estiver pendente, processe o pedido (consulte Avanço de Fotograma). Caso contrário, tente processar a saída do misturador (consulte Processing Output.
OnClockStop
  1. Defina o estado do apresentador como interrompido.
  2. Esvazie a fila de amostras do apresentador.
  3. Cancele toda operação pendente de passo de fotograma.
OnClockPause Defina o estado do apresentador como pausado.
OnClockRestart Trate da mesma forma que OnClockStart, mas não esvazie a fila de amostras.
OnClockSetRate
  1. Se a taxa estiver mudando de zero para diferente de zero, cancele o avanço de frame.
  2. Armazenar a nova frequência de relógio. A frequência de relógio afeta quando as amostras são apresentadas. Para obter mais informações, consulte Agendamento de amostras.

 

Implementando o IMFRateSupport

Para suportar velocidades de reprodução diferentes de 1×, o apresentador deve implementar a interface IMFRateSupport . Aqui estão algumas diretrizes para implementar os métodos nesta interface. Todos os métodos deverão falhar depois de o apresentador ser desligado. Para obter mais informações sobre essa interface, consulte Rate Control.

Valor Descrição
ObterTaxaMaisLenta Retorne zero para indicar que não há taxa mínima de reprodução.
ObterTaxaMaisRápida Para reprodução sem redução, a taxa de reprodução não deve exceder a taxa de atualização do monitor: taxa máxima = taxa de atualização (Hz) / taxa de quadros de vídeo (fps). A taxa de quadros do vídeo é especificada no tipo de mídia do apresentador.
Para reprodução reduzida, a taxa de reprodução é ilimitada; retornar o valor FLT_MAX. Na prática, a fonte e o descodificador serão os fatores limitantes durante a reprodução diluída.
Para reprodução inversa, retorne o negativo da velocidade máxima.
IsRateSupported Retorne MF_E_UNSUPPORTED_RATE se o valor absoluto de flRate exceder a taxa máxima de reprodução do apresentador. Calcule a taxa máxima de reprodução como descrito para GetFastestRate.

 

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

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

No exemplo anterior, o método auxiliar GetMaxRate é chamado para calcular a taxa máxima de reprodução avançada.

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

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

Envio de 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 DirectShow EVR 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 enviados pelo apresentador, juntamente com os parâmetros do evento.

Evento Descrição
EC_COMPLETE O apresentador terminou de renderizar todos os quadros após a mensagem MFVP_MESSAGE_ENDOFSTREAM.
  • Param1: HRESULT indicando o status da operação.
  • Param2: Não utilizado.
Para obter mais informações, consulte Fim do fluxo.
EC_DISPLAY_CHANGED O dispositivo Direct3D foi alterado.
  • Param1: Não utilizado.
  • Param2: Não utilizado.
Para obter mais informações, consulte Gerenciando o dispositivo Direct3D.
EC_ERRORABORT Ocorreu um erro que requer a interrupção do streaming.
  • Param1: HRESULT indicando o erro que ocorreu.
  • Param2: Não utilizado.
EC_PROCESSING_LATENCY Especifica a quantidade de tempo que o apresentador está levando para renderizar cada quadro. (Opcional.)
  • Param1: Ponteiro para um valor constante de LONGLONG que contém a duração para processar o quadro, em unidades de 100 nanossegundos.
  • Param2: Não utilizado.
Para obter mais informações, consulte Processing Output.
LATÊNCIA_DE_AMOSTRA_EC Especifica o tempo de atraso atual em amostras de renderização. Se o valor for positivo, as amostras estão atrasadas. Se o valor for negativo, as amostras estão adiantadas em relação ao cronograma. (Opcional.)
  • Param1: Ponteiro para um valor constante LONGLONG que contém o tempo de atraso, em unidades de 100 nanossegundos.
  • Param2: Não utilizado.
EC_SCRUB_TIME Enviado imediatamente após EC_STEP_COMPLETE se a taxa de reprodução for zero. Este evento contém a marca temporal do quadro que foi exibido.
  • Param1: Menor 32 bits do carimbo de data e 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 utilizado.
- Param2: Não utilizado.
Para obter mais informações, consulte Frame Stepping.
Nota: Uma versão anterior da documentação descreveu Param1 de forma incorreta. Este parâmetro não é usado para este evento.

 

Formatos de negociação

Sempre que o apresentador receber uma mensagem MFVP_MESSAGE_INVALIDATEMEDIATYPE do EVR, deve definir o formato de saída no misturador, da seguinte forma:

  1. Chame IMFTransform::GetOutputAvailableType no misturador para obter um tipo de saída possível. Este tipo descreve um formato que o misturador pode produzir, dados os fluxos de entrada e as capacidades 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, volte para a etapa 1 e obtenha o próximo tipo proposto pelo 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 que pretende para as superfícies Direct3D que irá 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 (atributoMF_MT_GEOMETRIC_APERTURE) igual a um retângulo dentro da superfície Direct3D. Quando o misturador gera um quadro de saída, ele coloca a imagem de origem nesse retângulo. A abertura geométrica pode ser tão grande quanto a superfície, ou pode ser um subretângulo 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 em Alocando superfícies Direct3D. O misturador usará estas superfícies quando gerar 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 tenha sido bem-sucedida na etapa 4, o método deverá ser novamente bem-sucedido.

Se o misturador 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 pode tentar outro formato de fluxo. Portanto, o apresentador poderá receber várias mensagens MFVP_MESSAGE_INVALIDATEMEDIATYPE seguidas até que seja encontrado um tipo válido.

O misturador adapta automaticamente o vídeo ao formato letterbox, tendo em conta a relação de aspeto do pixel (PAR) 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 tela. A imagem seguinte ilustra este processo.

diagrama mostrando um frame composto que conduz a uma superfície Direct3D, que leva a uma janela

O código a seguir mostra o esboço do processo. Algumas das etapas são colocadas em funções auxiliares, cujos detalhes exatos dependerão dos requisitos do seu 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 lida com qualquer perda de dispositivo durante o streaming. O apresentador também hospeda o gerenciador de dispositivos Direct3D, que fornece uma maneira para outros componentes usarem o mesmo dispositivo. Por exemplo, o misturador usa o dispositivo Direct3D para misturar subfluxos, desentrelaçar e executar ajustes de cor. Os descodificadores podem utilizar o dispositivo Direct3D para descodificação acelerada por vídeo. (Para obter mais informações sobre aceleração de vídeo, consulte DirectX Video Acceleration 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. Coloque o dispositivo no gestor de dispositivos chamando IDirect3DDeviceManager9::ResetDevice.

Se outro componente do pipeline precisar do gestor de dispositivos, este chamará IMFGetService::GetService no EVR, especificando MR_VIDEO_ACCELERATION_SERVICE para o GUID do serviço. O EVR passa o pedido ao apresentador. Depois de o objeto obter 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.

Quando o dispositivo é criado, se o apresentador destruir o dispositivo e criar um novo, ele deve chamar ResetDevice novamente. O método ResetDevice invalida quaisquer identificadores de dispositivo existentes, o que faz com que LockDevice retorne DXVA2_E_NEW_VIDEO_DEVICE. Esse código de erro sinaliza aos outros objetos que usam o dispositivo que devem abrir um novo identificador de dispositivo. Para obter mais informações sobre como usar o gerenciador de dispositivos, consulte Gerenciador de dispositivos Direct3D.

O apresentador pode criar o dispositivo no modo de janela ou no modo exclusivo de tela cheia. Para o modo de janela, você deve fornecer uma maneira para 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 assim que o apresentador for criado pela primeira vez. Normalmente, você não saberá 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. Este evento notifica o EVR para renegociar o tipo de mídia. O EVR ignora os parâmetros de evento para este evento.

Alocação de Superfícies Direct3D

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

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

A maneira recomendada de alocar superfícies depende se o apresentador executa em janela ou em tela cheia.

Se o dispositivo Direct3D estiver no modo de janela, pode criar várias cadeias de permuta, cada uma com um único buffer de fundo. Usando essa abordagem, você pode apresentar cada superfície independentemente, porque apresentar uma cadeia de permuta não interferirá com as outras cadeias de permuta. O misturador pode gravar dados em uma superfície enquanto outra superfície está programada para apresentação.

Primeiro, decida quantas cadeias de permuta criar. Recomenda-se um mínimo de três. Para cada cadeia de permuta, faça o seguinte:

  1. Chame IDirect3DDevice9::CreateAdditionalSwapChain para criar a cadeia de permuta.
  2. Chame IDirect3DSwapChain9::GetBackBuffer para obter um ponteiro para a superfície do buffer traseiro da cadeia de permuta.
  3. Chame MFCreateVideoSampleFromSurface e passe um ponteiro para a superfície. Esta 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::ProcessOutput do misturador. 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á amostras dessa fila durante o processamento, conforme descrito em Processing Output.
  5. Mantenha uma referência ao ponteiro de IDirect3DSwapChain9 para que o swap chain não seja liberado.

No modo exclusivo de tela cheia, o dispositivo não pode ter mais de uma cadeia de troca. Essa cadeia de permuta é criada implicitamente quando você cria o dispositivo de tela cheia. A cadeia de permuta pode ter mais de um buffer de retorno. Infelizmente, no entanto, se você apresentar um buffer de retorno enquanto grava em outro buffer de retorno na mesma cadeia de permuta, 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 de gráficos atualiza os ponteiros de superfície na memória gráfica. Se estiveres a segurar qualquer ponteiro IDirect3DSurface9 quando chamares Present, eles apontarão para buffers diferentes após o retorno da chamada Present.

A opção mais simples é criar uma amostra de vídeo para a cadeia de permuta. Se escolher esta opção, siga os mesmos passos indicados para o modo de janela. A única diferença é que a fila de exemplo contém uma única amostra de vídeo. Outra opção é criar superfícies fora da tela e, em seguida, copiar para o back buffer. As superfícies que cria devem suportar o método IDirectXVideoProcessor::VideoProcessBlt, que o misturador usa para compor os quadros de saída.

Rastreio de Amostras

Quando o apresentador aloca as amostras de vídeo pela primeira vez, ele as coloca em uma fila. O emissor tira dessa fila sempre que precisa obter um novo fotograma do mixer. Depois que o misturador produz o quadro, o apresentador move a amostra para uma segunda fila. A segunda fila é para amostras que estão aguardando seus horários de apresentação agendados.

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

  1. Implemente a interface IMFAsyncCallback no seu apresentador.

  2. Antes de colocar um exemplo na fila agendada, interrogue o objeto de exemplo de vídeo pela interface IMFTrackedSample.

  3. Chame IMFTrackedSample::SetAllocator com um ponteiro para a 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 a função de retorno. (O objeto de exemplo não é excluído neste caso porque mantém uma contagem de referência em si mesmo até que o retorno de chamada seja invocado.)

  6. Dentro da função de retorno, devolva a amostra para a fila disponível.

Um apresentador não é obrigado a usar IMFTrackedSample para rastrear amostras; Você pode implementar qualquer técnica que funcione melhor para o seu design. Uma vantagem de IMFTrackedSample é que você pode mover as funções de agendamento e renderização do apresentador para objetos auxiliares, e esses objetos não precisam de nenhum mecanismo especial para chamar de volta ao apresentador quando eles liberam 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 a amostra:

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

Saída de processamento

Sempre que o misturador recebe uma nova amostra 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::ProcessOutput no misturador. Se o método for bem-sucedido, o apresentador agendará a amostra 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 pausado, ignore a mensagem MFVP_MESSAGE_PROCESSINPUTNOTIFY, a menos que este seja o primeiro quadro de vídeo. Se o relógio estiver a funcionar, ou for o primeiro quadro do vídeo, continue.

  2. Obtenha uma amostra da fila de amostras disponíveis. Se a fila estiver vazia, isso significa que todas as amostras alocadas estão atualmente agendadas para apresentação. Nesse caso, ignore a mensagem MFVP_MESSAGE_PROCESSINPUTNOTIFY neste momento. Quando a próxima amostra estiver disponível, repita as etapas listadas aqui.

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

  4. Chamar IMFTransform::ProcessOutput no misturador. Se ProcessOutput for bem-sucedida, a amostra 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 de erro Descrição
    MF_E_TRANSFORM_NEED_MORE_INPUT O misturador precisa de mais entrada antes de poder produzir um novo quadro de saída.
    Se você receber esse código de erro, verifique se o EVR atingiu o final do fluxo e responda de acordo, conforme descrito em End of Stream. Caso contrário, ignore a 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 misturador tornou-se inválido, possivelmente devido a uma mudança de formato a montante.
    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, renegocie o tipo de saída do misturador conforme descrito em Formatos de negociação.

     

    Se ProcessOutput for bem-sucedido, continue.

  5. (Opcional.) Se o relógio estiver disponível, obtenha a hora do relógio atual (T2). A quantidade de latência introduzida pelo misturador é (T2 - T1). Enviar um evento EC_PROCESSING_LATENCY com este valor para o EVR. O EVR utiliza este valor para o controlo de qualidade. Se não houver relógio disponível, não há motivo para enviar o EC_PROCESSING_LATENCY evento.

  6. (Opcional.) Consulte a amostra de IMFTrackedSample e chame IMFTrackedSample::SetAllocator conforme descrito em Tracking Samples.

  7. Agende a amostra para apresentação.

Esta sequência de passos pode terminar antes de o apresentador obter qualquer saída do misturador. Para garantir que nenhuma solicitação seja descartada, repita as mesmas etapas quando ocorrer o seguinte:

  • O método do apresentador IMFClockStateSink::OnClockStart ou IMFClockStateSink::OnClockStart é chamado. Isso lida com o caso em que o misturador ignora a entrada porque o relógio está pausado (etapa 1).
  • A callback IMFTrackedSample é invocado. Este processo lida com o caso em que o mixer recebe um sinal de entrada, mas todas as amostras de vídeo do apresentador estão em uso (passo 2).

Os próximos exemplos de código mostram essas etapas com mais detalhes. O apresentador chama o método ProcessInputNotify (mostrado no exemplo a seguir) quando recebe 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;
}

Este método ProcessInputNotify define um sinalizador booleano para registrar o fato de que o misturador tem nova entrada. Em seguida, ele chama o método ProcessOutputLoop, mostrado no próximo exemplo. Este método tenta extrair o maior número 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 método ProcessOutput, mostrado no exemplo a seguir, tenta obter um único quadro de vídeo do mixer. Se nenhum quadro de vídeo estiver disponível, ProcessSample retornará S_FALSE ou um código de erro, que interrompe o loop no ProcessOutputLoop. A maior parte do trabalho é feito dentro do método ProcessOutput:

//-----------------------------------------------------------------------------
// 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 é assumida como um objeto de coleção que contém a fila de amostras de vídeo disponíveis. O método GetSample do objeto retorna MF_E_SAMPLEALLOCATOR_EMPTY se a fila estiver vazia.
  • Se o método deProcessOutputdo misturador retornar MF_E_TRANSFORM_NEED_MORE_INPUT, isso significa que o misturador não pode produzir mais saída, de modo que o apresentador limpa o sinalizador de m_fSampleNotify.
  • O método TrackSample, que define o retorno de chamada IMFTrackedSample, é mostrado na seção Tracking Samples.

Repintura de molduras

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

Siga as seguintes etapas para solicitar que o mixer recrie o frame mais recente:

  1. Obtenha uma amostra de vídeo da fila.
  2. Consulte o exemplo para a interface IMFDesiredSample.
  3. Chamada 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 repintar um quadro, você pode ignorar o relógio de apresentação e apresentá-lo imediatamente.

Agendamento de amostras

Os quadros de vídeo podem chegar ao EVR a qualquer momento. O apresentador é responsável por apresentar cada quadro no momento correto, com base no carimbo de data/hora do quadro. Quando o apresentador obtém uma nova amostra do misturador, ele coloca a amostra na fila agendada. Em um thread separado, o apresentador obtém continuamente a primeira amostra do início da fila e determina o que fazer a seguir:

  • Apresente a amostra.
  • Mantenha a amostra na fila porque é cedo.
  • Elimine a amostra porque está atrasada. Embora deva evitar perder frames, se possível, talvez seja necessário se o apresentador estiver continuamente a ficar atrasado.

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

Para obter a duração de cada amostra, ligue IMFSample::GetSampleDuration. Se a amostra não tiver uma duração, você pode usar a função MFFrameRateToAverageTimePerFrame para calcular a duração a partir da taxa de quadros.

Ao agendar amostras, 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 funciona a uma taxa mais rápida ou mais lenta. Isso significa que a marca temporal em uma amostra sempre fornece a hora alvo correta em relação ao relógio de apresentação. No entanto, se você traduzir os tempos de apresentação em algum outro horário de relógio (por exemplo, o contador de desempenho de alta resolução), deverá dimensionar os tempos com base na velocidade do relógio. Se a velocidade do relógio mudar, o EVR chamará o método IMFClockStateSink::OnClockSetRatedo apresentador.
  • A taxa de reprodução pode ser negativa para reprodução inversa. Quando a taxa de reprodução é negativa, o relógio de apresentação funciona ao contrário. 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 acionado 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 está 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 Apresentar aguardará o retraçado vertical do monitor. Esta é uma maneira fácil de evitar rasgões, mas reduz a precisão do seu 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 causa rasgamento, a menos que seu algoritmo de agendamento seja preciso o suficiente para que você chame Present apenas durante o período de retrace vertical.

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

  • 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. Estas informações são úteis se a composição do ambiente de trabalho estiver ativada.

Apresentação de amostras

Esta seção presume que foi criada uma cadeia de permuta separada para cada superfície, conforme descrito em Alocação de superfícies Direct3D. Para apresentar um exemplo, obtenha a cadeia de permuta do exemplo de vídeo da seguinte maneira:

  1. Chame IMFSample::GetBufferByIndex no exemplo de vídeo para obter o buffer.
  2. Pesquise o buffer para a interface IMFGetService.
  3. Ligue IMFGetService::GetService para obter a interface IDirect3DSurface9 da Direct3D surface. (Você pode combinar esta etapa e a etapa anterior em uma chamando MFGetService.)
  4. Chame IDirect3DSurface9::GetContainer na superfície para obter um ponteiro para a cadeia de permuta.
  5. Chame IDirect3DSwapChain9::Present na cadeia de permuta.

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

Os retângulos de origem e de destino

O retângulo de origem é a parte do quadro de vídeo a ser exibida. É 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 onde o frame de vídeo é desenhado. O apresentador padrão permite que um aplicativo defina esses retângulos chamando IMFVideoDisplayControl::SetVideoPosition.

Existem várias opções para aplicar retângulos de origem e destino. A primeira opção é deixar o misturador aplicá-los:

  • Defina o retângulo de origem usando o atributo VIDEO_ZOOM_RECT. O misturador aplicará o retângulo de origem quando transferir o vídeo 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 o pSourceRect e pDestRect parâmetros no Present método. 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. Em caso afirmativo, você deve ter cuidado para sincronizar essa operação com seu thread de agendamento. Limpe a fila de agendamento e descarte as amostras antigas antes de alocar novas superfícies.

Fim do fluxo

Quando todos os fluxos de entrada no EVR terminam, o EVR envia uma mensagem MFVP_MESSAGE_ENDOFSTREAM para o 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. Após a apresentação do último quadro, envie um evento EC_COMPLETE para o EVR.

O próximo exemplo mostra um método que envia o evento EC_COMPLETE se várias condições forem atendidas. Caso contrário, ele retorna 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, significa que o misturador possui um ou mais quadros que ainda não foram processados. (Para obter detalhes, consulte Processing Output.)
  • A variável m_fEndStreaming é um sinalizador booleano cujo valor inicial FALSE. O apresentador define o sinalizador como TRUE quando o EVR envia a mensagem MFVP_MESSAGE_ENDOFSTREAM.
  • Presume-se que o método AreSamplesPending retorne TRUE desde que um ou mais quadros estejam aguardando na fila agendada.

No método IMFVideoPresenter::ProcessMessage, defina m_fEndStreaming para TRUE e chame CheckEndOfStream quando o EVR enviar a mensagem 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, ligue para CheckEndOfStream se o método IMFTransform::ProcessOutput do misturador retornar MF_E_TRANSFORM_NEED_MORE_INPUT. Esse código de erro indica que o misturador não tem mais amostras de entrada (consulte Processing Output).

Passo a passo do quadro

O EVR foi projetado para suportar o passo a passo de quadros no DirectShow e a navegação temporal no Media Foundation. A pisa e a limpeza do quadro são conceitualmente semelhantes. Em ambos os casos, o aplicativo solicita um quadro de vídeo de cada vez. Internamente, o apresentador usa o mesmo mecanismo para implementar ambos os recursos.

O passo a passo de quadros no DirectShow funciona da seguinte maneira:

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

A depuração na Media Foundation funciona da seguinte forma:

  • O aplicativo define a taxa de reprodução para 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 parar a depuração, o aplicativo define a taxa de reprodução para 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 frame de destino. Se o número de passos for N, o apresentador descarta as amostras seguintes (N - 1) e apresenta a N ésima amostra. 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 envia um evento EC_SCRUB_TIME. Se o EVR cancelar o procedimento de avanço de quadros enquanto uma operação de avanço de quadro ainda estiver pendente, o apresentador enviará um evento de EC_STEP_COMPLETE com lParam1 definido como TRUE.

A aplicação pode avançar passo a passo ou saltar várias vezes, o que significa que o apresentador poderá receber várias mensagens MFVP_MESSAGE_STEP antes de receber uma mensagem MFVP_MESSAGE_CANCELSTEP. Além disso, o apresentador pode receber a mensagem de MFVP_MESSAGE_STEP antes do relógio começar ou enquanto o relógio estiver funcionando.

Implementação do Frame Stepping

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

  • step_count. Um inteiro não assinado que especifica o número de etapas na operação de avanço de fotogramas atual.

  • step_queue. Uma fila de IMFSample ponteiros.

  • step_state. A qualquer momento, o apresentador pode estar num dos seguintes estados em relação ao avançar quadro a quadro:

    Estado Descrição
    NÃO A CAMINHAR Sem avanço por fotogramas.
    AGUARDANDO O apresentador recebeu a mensagem MFVP_MESSAGE_STEP, mas o relógio não começou ainda.
    PENDENTE O apresentador recebeu a mensagem MFVP_MESSAGE_STEP e o relógio foi iniciado, mas está à espera de receber o frame de destino.
    PROGRAMADO O apresentador recebeu o quadro alvo e agendou-o para apresentação, mas o quadro não foi apresentado.
    COMPLETO O apresentador apresentou o quadro de destino e enviou o evento EC_STEP_COMPLETE, e agora aguarda pela 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 seguintes procedimentos são definidos para o algoritmo de avanço de quadro:

Procedimento PrepareFrameStep

  1. Incrementar contagem_de_passos.
  2. Defina step_state para AGUARDAR.
  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 PENDING. Para cada exemplo no step_queue, chame DeliverFrameStepSample.
  2. Se step_state for igual a NOT_STEPPING, remova todas as amostras do step_queue e agende-as para apresentação.

Procedimento CompleteFrameStep

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

Procedimento DeliverFrameStepSample

  1. Se a frequência do relógio for zero e tempo de amostragem + duração da amostragem<hora do relógio, descarte a amostra. Saída.
  2. Se step_state for igual a SCHEDULED ou COMPLETE, adicione o exemplo a step_queue. Sair.
  3. Decrementar contagem_de_passos.
  4. Se step_count> 0, descarte a amostra. Sair.
  5. Se step_state for igual a WAITING, adicione a amostra a step_queue. Sair.
  6. Agende a amostra 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 AGUARDANDO, PENDENTE ou AGENDADO, envie EC_STEP_COMPLETE com lParam1 = TRUE.

Chame estes procedimentos da seguinte maneira:

Mensagem ou método do apresentador Tramitação processual
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 fluxograma a seguir mostra os procedimentos de passo a passo de fotogramas.

fluxograma mostrando caminhos que começam com mfvp-message-step e mfvp-message-processinputnotify e terminam em

Definindo o apresentador no EVR

Depois de implementar o apresentador, o próximo passo é 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 gráfico de filtro.
  3. Crie uma instância do seu apresentador. Seu apresentador pode suportar a criação de objeto COM padrão através IClassFactory, mas isso não é obrigatório.
  4. Consulte o filtro EVR para a interface IMFVideoRenderer.
  5. Invoque IMFVideoRenderer::InitializeRenderer.

Definir o apresentador na 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 Activation Objects.

Para o receptor de mídia EVR, siga os passos abaixo:

  1. Chame MFCreateVideoRenderer para criar o media sink.
  2. Crie uma instância do seu apresentador.
  3. Consulte o escoadouro de mídia EVR para a interface IMFVideoRenderer.
  4. Chamar 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 seu apresentador. O objeto de ativação deve implementar o IMFActivate interface.
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID do apresentador.
    Com esse sinalizador, seu apresentador deve oferecer suporte à criação de objeto COM padrão por meio 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