Condividi tramite


Come scrivere un presentatore EVR

[Il componente descritto in questa pagina, Enhanced Video Renderer, è una funzionalità legacy. È stato sostituito dal Simple Video Renderer (SVR) esposto tramite i componenti MediaPlayer e IMFMediaEngine. Per riprodurre contenuti video, è necessario inviare dati a uno di questi componenti e consentire loro di creare un'istanza del nuovo renderer video. Questi componenti sono stati ottimizzati per Windows 10 e Windows 11. Microsoft consiglia vivamente che il nuovo codice usi MediaPlayer o il livello inferiore IMFMediaEngine API per riprodurre contenuti multimediali video in Windows anziché EVR, quando possibile. Microsoft suggerisce che il codice esistente che usa le API legacy venga riscritto per usare le nuove API, se possibile.

Questo articolo descrive come scrivere un relatore personalizzato per il renderer video avanzato (EVR). Un relatore personalizzato può essere usato sia con DirectShow che con Media Foundation; le interfacce e il modello a oggetti sono gli stessi per entrambe le tecnologie, anche se la sequenza esatta delle operazioni può variare.

Il codice di esempio in questa sezione è adattato dal campione EVRPresenter , che è fornito nel Windows SDK.

Questo argomento contiene le sezioni seguenti:

Prerequisiti

Prima di scrivere un relatore personalizzato, è necessario avere familiarità con le tecnologie seguenti:

  • Renderer video avanzato. Vedere Enhanced Video Renderer.
  • Grafica Direct3D. Non è necessario comprendere la grafica 3D per scrivere un relatore, ma è necessario sapere come creare un dispositivo Direct3D e gestire le superfici Direct3D. Se non si ha familiarità con Direct3D, leggere le sezioni "Dispositivi Direct3D" e "Risorse Direct3D" nella documentazione di DirectX Graphics SDK.
  • DirectShow filtra grafici o pipeline di Media Foundation, a seconda della tecnologia che verrà usata dall'applicazione per il rendering del video.
  • Trasformazioni di Media Foundation. Il mixer EVR è una trasformazione di Media Foundation e il relatore chiama i metodi direttamente sul mixer.
  • Implementazione di oggetti COM. Il relatore è un oggetto COM a thread libero in-process.

Modello a oggetti del relatore

Questa sezione contiene una panoramica del modello a oggetti del relatore e delle interfacce.

Flusso di dati all'interno di EVR

EVR usa due componenti plug-in per eseguire il rendering del video: il mixer e il presenter. Il mixer miscela i flussi video e deinterlaccia il video, se necessario. Il relatore disegna (o presenta) il video sullo schermo e pianifica quando viene disegnato ogni fotogramma. Le applicazioni possono sostituire uno di questi oggetti con un'implementazione personalizzata.

L'EVR ha uno o più flussi di input e il mixer ha un numero corrispondente di flussi di input. Stream 0 è sempre il flusso di riferimento . Gli altri flussi sono sottostream, che il mixer sovrappone tramite fusione alfa al flusso di riferimento. La frequenza dei fotogrammi principale viene determinata dal flusso di riferimento per il video composito. Per ogni fotogramma di riferimento, il mixer prende il fotogramma più recente da ogni sottostream, li combina nel frame di riferimento e restituisce un singolo frame composito. Il mixer esegue anche il deinterlacciamento e la conversione dei colori da YUV a RGB, se necessario. EVR inserisce sempre il mixer nella pipeline video, indipendentemente dal numero di flussi di input o dal formato video. L'immagine seguente illustra questo processo.

diagramma che mostra il flusso di riferimento e il sottocanale che puntano al mixer, che punta al relatore, che punta al display

Il relatore esegue le attività seguenti:

  • Imposta il formato di output sul mixer. Prima dell'inizio dello streaming, il relatore imposta un tipo di supporto nel flusso di output del mixer. Questo tipo di supporto definisce il formato dell'immagine composita.
  • Crea il dispositivo Direct3D.
  • Alloca superfici Direct3D. Il mixer trasferisce i fotogrammi compositi su queste superfici.
  • Ottiene l'output dal mixer.
  • Pianifica quando vengono presentati i fotogrammi. L'EVR fornisce l'orologio della presentazione e il relatore pianifica i fotogrammi in base a questo orologio.
  • Presenta ogni fotogramma usando Direct3D.
  • Esegue lo scorrimento e scrubbing dei fotogrammi.

Stati del relatore

In qualsiasi momento, il relatore si trova in uno dei seguenti stati:

  • Avviato. L'orologio della presentazione di EVR sta funzionando. Il relatore pianifica i fotogrammi video per la presentazione non appena arrivano.
  • Sospeso. L'orologio della presentazione è sospeso. Il relatore non presenta nuovi esempi, ma mantiene la relativa coda di esempi pianificati. Se vengono ricevuti nuovi esempi, il relatore li aggiunge alla coda.
  • Fermato. L'orologio della presentazione è fermo. Il relatore rimuove tutti gli esempi pianificati.
  • Spegni. Il relatore rilascia tutte le risorse correlate allo streaming, ad esempio le superfici Direct3D. Questo è lo stato iniziale del relatore e lo stato finale prima che il relatore venga eliminato definitivamente.

Nel codice di esempio di questo argomento lo stato del relatore è rappresentato da un'enumerazione :

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

Alcune operazioni non sono valide mentre il relatore è nello stato di arresto. Il codice di esempio verifica la presenza di questo stato chiamando un metodo helper:

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

Interfacce del relatore

Per implementare le interfacce seguenti, è necessario un relatore:

Interfaccia Descrizione
IMFClockStateSink Notifica al relatore quando lo stato dell'orologio di EVR cambia. Consulta Implementazione di IMFClockStateSink.
IMFGetService Consente all'applicazione e ad altri componenti della pipeline di ottenere interfacce dal relatore.
IMFTopologyServiceLookupClient Consente al relatore di ottenere interfacce dall'EVR o dal mixer. Consultare Implementazione del IMFTopologyServiceLookupClient.
IMFVideoDeviceID Assicura che il relatore e il mixer usino tecnologie compatibili. Vedere Implementazione di IMFVideoDeviceID.
IMFVideoPresenter Elabora i messaggi dall'EVR. Vedere Implementazione di IMFVideoPresenter.

 

Le interfacce seguenti sono facoltative:

Interfaccia Descrizione
IEVRTrustedVideoPlugin Consente al relatore di lavorare con supporti protetti. Implementa questa interfaccia se il presentatore è un componente attendibile progettato per funzionare nel percorso multimediale protetto (PMP).
IMFRateSupport Segnala l'intervallo di velocità di riproduzione supportate dal presentatore. Consulta Implementazione di IMFRateSupport.
IMFVideoPositionMapper Mappa le coordinate sul fotogramma video della produzione alle coordinate sul fotogramma video dell'origine.
IQualProp Segnala informazioni sulle prestazioni. EVR usa queste informazioni per la gestione del controllo qualità. Questa interfaccia è documentata in DirectShow SDK.

 

È anche possibile fornire interfacce per l'applicazione per comunicare con il relatore. Il relatore standard implementa l'interfaccia IMFVideoDisplayControl a questo scopo. È possibile implementare questa interfaccia o definire le proprie. L'applicazione ottiene interfacce dal presentatore chiamando IMFGetService::GetService sull'EVR. Quando il GUID del servizio è MR_VIDEO_RENDER_SERVICE, l'EVR passa la richiesta di GetService al presentatore.

Implementazione di IMFVideoDeviceID

L'interfaccia IMFVideoDeviceID contiene un metodo, GetDeviceID, che restituisce un GUID del dispositivo. Il GUID del dispositivo garantisce che il relatore e il mixer usino tecnologie compatibili. Se i GUID del dispositivo non corrispondono, l'EVR non riesce a inizializzare.

Il mixer standard e il relatore usano entrambi Direct3D 9, con il GUID del dispositivo uguale a IID_IDirect3DDevice9. Se intendi usare il presentatore personalizzato con il mixer standard, il GUID del dispositivo del presentatore deve essere IID_IDirect3DDevice9. Se si sostituiscono entrambi i componenti, è possibile definire un nuovo GUID del dispositivo. Per il resto di questo articolo, si presuppone che il relatore usi Direct3D 9. Ecco l'implementazione standard di GetDeviceID:

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

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

Il metodo dovrebbe avere esito positivo anche quando il relatore viene arrestato.

Implementazione di IMFTopologyServiceLookupClient

L'interfaccia IMFTopologyServiceLookupClient consente al relatore di ottenere puntatori di interfaccia da EVR e dal mixer come indicato di seguito:

  1. Quando l'EVR inizializza il presentatore, chiama il metodo IMFTopologyServiceLookupClient::InitServicePointers del presentatore. L'argomento è un puntatore all'interfaccia IMFTopologyServiceLookup di EVR.
  2. Il relatore chiama IMFTopologyServiceLookup::LookupService per ottenere puntatori di interfaccia da EVR o dal mixer.

Il metodo LookupService è simile al metodo IMFGetService::GetService. Entrambi i metodi accettano un GUID del servizio e un identificatore di interfaccia (IID) come input, ma LookupService restituisce una matrice di puntatori di interfaccia, mentre GetService restituisce un singolo puntatore. In pratica, tuttavia, è sempre possibile impostare le dimensioni della matrice su 1. L'oggetto sottoposto a query dipende dal GUID del servizio:

  • Se il GUID del servizio è MR_VIDEO_RENDER_SERVICE, viene eseguita una query su EVR.
  • Se il GUID del servizio è MR_VIDEO_MIXER_SERVICE, viene interrogato il mixer.

Nell'implementazione di InitServicePointers, ottenere le seguenti interfacce dall'EVR:

Interfaccia EVR Descrizione
IMediaEventSink Consente al relatore di inviare messaggi all'EVR. Questa interfaccia è definita in DirectShow SDK, quindi i messaggi seguono il modello per gli eventi DirectShow, non gli eventi di Media Foundation.
IMFClock Rappresenta l'orologio dell'EVR. Il relatore usa questa interfaccia per pianificare gli esempi per la presentazione. L'EVR può essere eseguito senza un orologio, quindi questa interfaccia potrebbe non essere disponibile. In caso contrario, ignorare il codice di errore di LookupService.
L'orologio implementa anche l'interfaccia IMFTimer. Nella pipeline di Media Foundation l'orologio implementa l'interfaccia IMFPresentationClock. Non implementa questa interfaccia in DirectShow.

 

Ottenere le interfacce seguenti dal mixer:

Interfaccia mixer Descrizione
IMFTransform Consente al relatore di comunicare con il mixer.
IMFVideoDeviceID Consente al relatore di convalidare il GUID del dispositivo del mixer.

 

Il codice seguente implementa il metodoinInitServicePointers :

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 i puntatori di interfaccia ottenuti da LookupService non sono più validi, EVR chiama IMFTopologyServiceLookupClient::ReleaseServicePointers. All'interno di questo metodo, rilasciare tutti i puntatori di interfaccia e impostare lo stato del componente su arresto.

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

EVR chiama ReleaseServicePointers per diversi motivi, tra cui:

  • Disconnessione o riconnessione di pin (DirectShow) o aggiunta o rimozione di flussi di sink (Media Foundation).
  • Modifica del formato.
  • Impostazione di un nuovo orologio.
  • Spegnimento finale dell'EVR.

Durante il tempo di vita del presentatore, EVR può chiamare InitServicePointers e ReleaseServicePointers più volte.

Implementazione di IMFVideoPresenter

L'interfaccia IMFVideoPresenter eredita IMFClockStateSink e aggiunge due metodi:

Metodo Descrizione
GetCurrentMediaType Restituisce il tipo di supporto dei fotogrammi video compositi.
ProcessMessage Segnala al relatore di eseguire varie azioni.

 

Il metodo GetCurrentMediaType restituisce il tipo multimediale del relatore. Per informazioni dettagliate sull'impostazione del tipo di supporto, vedere Formati di negoziazione. Il tipo di supporto viene restituito come puntatore all'interfaccia IMFVideoMediaType. Nell'esempio seguente si presuppone che il relatore archivia il tipo di supporto come puntatore IMFMediaType. Per ottenere l'interfaccia IMFVideoMediaType dal tipo di supporto, chiamare 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;
}

Il metodo ProcessMessage è il meccanismo principale per l'EVR di comunicare con il relatore. Vengono definiti i messaggi seguenti. I dettagli dell'implementazione di ogni messaggio vengono forniti nella parte restante di questo argomento.

Messaggio Descrizione
MFVP_MESSAGE_INVALIDATEMEDIATYPE Il tipo di supporto di output del mixer non è valido. Il presentatore deve negoziare un nuovo tipo di media con il mixer. Consulta Formati di negoziazione.
MFVP_MESSAGE_BEGINSTREAMING Lo streaming è stato avviato. Non è richiesta alcuna azione specifica da questo messaggio, ma è possibile usarla per allocare le risorse.
MFVP_MESSAGE_ENDSTREAMING Lo streaming è terminato. Rilascia tutte le risorse allocate in risposta al messaggio di MFVP_MESSAGE_BEGINSTREAMING.
MFVP_MESSAGE_PROCESSINPUTNOTIFY Il mixer ha ricevuto un nuovo esempio di input e potrebbe essere in grado di generare un nuovo frame di output. Il relatore deve chiamare IMFTransform::ProcessOutput sul mixer. Vedi elaborazione dei risultati.
MFVP_MESSAGE_ENDOFSTREAM La presentazione è terminata. Consulta Fine del flusso.
MFVP_MESSAGE_FLUSH L'EVR sta svuotando i dati nella sua pipeline di rendering. Il relatore deve eliminare tutti i fotogrammi video pianificati per la presentazione.
MFVP_MESSAGE_STEP Chiede al relatore di avanzare N fotogrammi. Il relatore deve eliminare i fotogrammi N-1 successivi e visualizzare il Nth frame. Vedere Frame Stepping.
MFVP_MESSAGE_CANCELSTEP Annulla l'avanzamento dei fotogrammi.

 

Implementazione di IMFClockStateSink

Il presentatore deve implementare l'interfaccia IMFClockStateSink come parte della sua implementazione di IMFVideoPresenter, che eredita IMFClockStateSink. EVR usa questa interfaccia per notificare al relatore ogni volta che lo stato dell'orologio di EVR cambia. Per ulteriori informazioni sugli stati dell'orologio, vedere Orologio di presentazione.

Ecco alcune linee guida per l'implementazione dei metodi in questa interfaccia. Se il relatore viene arrestato, tutti i metodi devono avere esito negativo.

Metodo Descrizione
OnClockStart
  1. Imposta lo stato del presentatore su inizia.
  2. Se il ll llClockStartOffset non è PRESENTATION_CURRENT_POSITION, scaricare la coda di esempi del relatore. Equivale a ricevere un messaggio di MFVP_MESSAGE_FLUSH.
  3. Se una richiesta precedente di passo di fotogramma è in sospeso, gestire la richiesta (vedere Frame Stepping). In caso contrario, provare a elaborare l'output dal mixer (vedere output di elaborazione.
OnClockStop
  1. Impostare lo stato del relatore su interrotto.
  2. Svuotare la coda di campioni del presentatore.
  3. Annullare qualsiasi operazione di avanzamento a fotogramma in sospeso.
OnClockPause Mettere lo stato del relatore in pausa.
OnClockRestart Tratta come OnClockStart ma non scaricare la coda di campioni.
OnClockSetRate
  1. Se la frequenza cambia da zero a un valore diverso da zero, annullare l'avanzamento fotogramma.
  2. Archiviare la nuova frequenza di clock. La frequenza di clock influisce su quando i campioni vengono presentati. Per altre informazioni, vedere esempi di pianificazione .

 

Implementazione di IMFRateSupport

Per supportare velocità di riproduzione diverse da 1×, il relatore deve implementare l'interfaccia IMFRateSupport. Ecco alcune linee guida per l'implementazione dei metodi in questa interfaccia. Tutti i metodi devono avere esito negativo dopo l'arresto del relatore. Per altre informazioni su questa interfaccia, vedere Rate Control.

Valore Descrizione
OttieniTariffaPiùLenta Restituire zero per indicare l'assenza di una frequenza di riproduzione minima.
OttieniTariffaPiùVeloce Per la riproduzione senza riduzione, la frequenza di riproduzione non deve superare la frequenza di aggiornamento monitor: frequenza massima = frequenza di aggiornamento (Hz) / frequenza dei fotogrammi video (fps). La frequenza dei fotogrammi video viene specificata nel tipo di media del presentatore.
Per la riproduzione ridotta, la frequenza di riproduzione non è limitata; restituire il valore FLT_MAX. In pratica, l'origine e il decodificatore saranno i fattori limitanti durante la riproduzione ridotta.
Per la riproduzione inversa, restituire il valore negativo della frequenza massima.
ÈSupportatoIlTasso Restituisci MF_E_UNSUPPORTED_RATE se il valore assoluto di flRate supera la velocità di riproduzione massima del presentatore. Calcolare la frequenza di riproduzione massima come descritto per GetFastestRate.

 

Nell'esempio seguente viene illustrato come implementare il metodoGetFastestRate:

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

L'esempio precedente chiama un metodo helper, GetMaxRate, per calcolare la frequenza massima di riproduzione in avanti:

Nell'esempio seguente viene illustrato come implementare il metodo 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;
}

Invio di eventi a EVR

Il relatore deve notificare all'EVR vari eventi. A tale scopo, usa l'interfaccia IMediaEventSink di EVR, ottenuta quando l'EVR chiama il metodo IMFTopologyServiceLookupClient::InitServicePointers. L'interfaccia IMediaEventSink è originariamente un'interfaccia DirectShow, ma viene usata sia in DirectShow EVR che in Media Foundation. Il codice seguente illustra come inviare un evento a 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);
        }
    }

Nella tabella seguente sono elencati gli eventi inviati dal relatore, insieme ai parametri dell'evento.

Evento Descrizione
EC_COMPLETE Il presentatore ha terminato il rendering di tutti i fotogrammi dopo il messaggio MFVP_MESSAGE_ENDOFSTREAM.
  • Param1: HRESULT che indica lo stato dell'operazione.
  • Param2: non usato.
Per altre informazioni, vedere End of Stream.
EC_VISUALIZZAZIONE_CAMBIATA Il dispositivo Direct3D è cambiato.
  • Param1: non usato.
  • Param2: non usato.
Per altre informazioni, vedere Managing the Direct3D Device.
EC_ERRORABORT Si è verificato un errore che richiede l'arresto dello streaming.
  • Param1: HRESULT che indica che l'errore si è verificato.
  • Param2: non usato.
EC_PROCESSING_LATENCY Specifica la quantità di tempo che il relatore sta impiegando per eseguire il rendering di ogni fotogramma. (Facoltativo.
  • Param1: puntatore a una costante LONGLONG, che contiene la quantità di tempo necessaria per elaborare il fotogramma, in unità di 100 nanosecondi.
  • Param2: non usato.
Per altre informazioni, vedere processing output.
EC_SAMPLE_LATENCY Specifica il tempo di ritardo corrente negli esempi di rendering. Se il valore è positivo, i campioni sono in ritardo rispetto al programma. Se il valore è negativo, i campioni sono in anticipo rispetto alla pianificazione. (Facoltativo.
  • Param1: puntatore a un valore costante LONGLONG che contiene il tempo di ritardo, in unità di 100 nanosecondi.
  • Param2: non usato.
EC_SCRUB_TIME Viene inviato subito dopo EC_STEP_COMPLETE se la velocità di riproduzione è zero. Questo evento contiene il timestamp del frame visualizzato.
  • Param1: 32 bit inferiori del timestamp.
  • Param2: 32 bit superiori del timestamp.
Per altre informazioni, vedere Frame Stepping.
EC_STEP_COMPLETE Il presentatore ha completato o annullato una fase del frame.
- Param1: non usato.
- Param2: non usato.
Per altre informazioni, vedere Frame Stepping.
Nota: Una versione precedente della documentazione descriveva Param1 in modo non corretto. Questo parametro non viene usato per questo evento.

 

Negoziazione dei formati

Ogni volta che il relatore riceve un messaggio di MFVP_MESSAGE_INVALIDATEMEDIATYPE dall'EVR, deve impostare il formato di output sul mixer, come indicato di seguito:

  1. Chiamare IMFTransform::GetOutputAvailableType sul mixer per ottenere un possibile tipo di output. Questo tipo descrive un formato che il mixer può produrre in base ai flussi di input e alle funzionalità di elaborazione video del dispositivo grafico.

  2. Controllare se il relatore può usare questo tipo di supporto come formato di rendering. Ecco alcuni aspetti da controllare, anche se l'implementazione potrebbe avere requisiti specifici:

    • Il video deve essere decompresso.
    • Il video deve avere solo fotogrammi progressivi. Verificare che l'attributo MF_MT_INTERLACE_MODE sia uguale a MFVideoInterlace_Progressive.
    • Il formato deve essere compatibile con il dispositivo Direct3D.

    Se il tipo non è accettabile, tornare al passaggio 1 e ottenere il tipo proposto successivo del mixer.

  3. Creare un nuovo tipo di supporto che sia un clone del tipo originale e quindi modificare gli attributi seguenti:

    • Impostare l'attributo MF_MT_FRAME_SIZE uguale alla larghezza e all'altezza desiderate per le superfici Direct3D che verranno allocate.
    • Impostare l'attributo MF_MT_PAN_SCAN_ENABLED su FALSE.
    • Impostare l'attributo MF_MT_PIXEL_ASPECT_RATIO uguale a PAR dello schermo (in genere 1:1).
    • Impostare l'apertura geometrica (attributoMF_MT_GEOMETRIC_APERTURE) uguale a un rettangolo all'interno della superficie Direct3D. Quando il mixer genera un frame di output, trasferisce l'immagine di origine in questo rettangolo. L'apertura geometrica può essere grande quanto la superficie oppure può essere un sottoinsieme rettangolare all'interno della superficie. Per ulteriori informazioni, vedere Rettangoli di origine e destinazione.
  4. Per verificare se il mixer accetterà il tipo di output modificato, chiamare IMFTransform::SetOutputType con il flag MFT_SET_TYPE_TEST_ONLY. Se il mixer rifiuta il tipo, tornare al passaggio 1 e ottenere il tipo successivo.

  5. allocare un pool di superfici Direct3D, come descritto in Allocare superfici Direct3D. Il mixer utilizzerà queste superfici quando disegna i fotogrammi video compositi.

  6. Impostare il tipo di output nel mixer chiamando SetOutputType senza flag. Se la prima chiamata a SetOutputType è stata completata con successo nel passaggio 4, il metodo dovrebbe riuscire di nuovo.

Se il mixer esaurisce i tipi, il metodoGetOutputAvailableTyperestituisce MF_E_NO_MORE_TYPES. Se il relatore non riesce a trovare un tipo di output appropriato per il mixer, non è possibile eseguire il rendering del flusso. In tal caso, DirectShow o Media Foundation potrebbe provare un altro formato di flusso. Pertanto, il presentatore potrebbe ricevere diversi messaggi MFVP_MESSAGE_INVALIDATEMEDIATYPE di seguito fino a quando non viene trovato un tipo valido.

Il mixer applica automaticamente il letterbox al video, tenendo conto del rapporto d'aspetto dei pixel (PAR) dell'origine e della destinazione. Per ottenere risultati ottimali, la larghezza e l'altezza della superficie e l'apertura geometrica devono essere uguali alle dimensioni effettive che si desidera che il video venga visualizzato sullo schermo. L'immagine seguente illustra questo processo.

diagramma che mostra un frame composito che porta a una superficie Direct3D, che porta a una finestra

Il codice seguente illustra la struttura del processo. Alcuni passaggi vengono inseriti nelle funzioni helper, i dettagli esatti dei quali dipendono dai requisiti del relatore.

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

Per altre informazioni sui tipi di supporti video, vedere Tipi di supporti video.

Gestione del dispositivo Direct3D

Il relatore crea il dispositivo Direct3D e gestisce qualsiasi perdita di dispositivo durante lo streaming. Il relatore ospita anche la gestione dispositivi Direct3D, che consente ad altri componenti di usare lo stesso dispositivo. Ad esempio, il mixer utilizza il dispositivo Direct3D per mescolare sottostringhe, deinterlacciare ed eseguire regolazioni cromatiche. I decodificatori possono usare il dispositivo Direct3D per la decodifica con accelerazione video. Per altre informazioni sull'accelerazione video, vedere DirectX Video Acceleration 2.0.)

Per configurare il dispositivo Direct3D, seguire questa procedura:

  1. Creare l'oggetto Direct3D chiamando Direct3DCreate9 o Direct3DCreate9Ex.
  2. Creare il dispositivo chiamando IDirect3D9::CreateDevice o IDirect3D9Ex::CreateDevice.
  3. Crea il gestore di dispositivi chiamando DXVA2CreateDirect3DDeviceManager9.
  4. Impostare il dispositivo nel gestore dispositivi chiamando IDirect3DDeviceManager9::ResetDevice.

Se un altro componente della pipeline richiede il gestore dei dispositivi, chiama IMFGetService::GetService sull'EVR, specificando MR_VIDEO_ACCELERATION_SERVICE per il GUID del servizio. L'EVR passa la richiesta al relatore. Dopo che l'oggetto ottiene il puntatore IDirect3DDeviceManager9, può ottenere un handle del dispositivo chiamando IDirect3DDeviceManager9::OpenDeviceHandle. Quando l'oggetto ha bisogno di utilizzare il dispositivo, passa l'handle del dispositivo al metodo IDirect3DDeviceManager9::LockDevice, che restituisce un puntatore IDirect3DDevice9.

Dopo aver creato il dispositivo, se il relatore elimina definitivamente il dispositivo e ne crea uno nuovo, il relatore deve chiamare nuovamente ResetDevice. Il metodo ResetDevice invalida qualsiasi handle di dispositivo esistente, il che causa che LockDevice ritorni DXVA2_E_NEW_VIDEO_DEVICE. Questo codice di errore segnala ad altri oggetti che usano il dispositivo che devono aprire un nuovo handle di dispositivo. Per ulteriori informazioni sull'uso del gestore dispositivi, vedere Gestione dispositivi Direct3D.

Il relatore può creare il dispositivo in modalità finestra o in modalità esclusiva a schermo intero. Per la modalità finestra, è necessario fornire all'applicazione un modo per specificare la finestra video. Il relatore standard implementa il metodo IMFVideoDisplayControl::SetVideoWindow a questo scopo. Devi creare il dispositivo nel momento in cui il relatore viene creato per la prima volta. In genere, non si conoscono tutti i parametri del dispositivo in questo momento, ad esempio la finestra o il formato back buffer. È possibile creare un dispositivo temporaneo e sostituirlo in un secondo momento&#; ricordarsi di chiamare ResetDevice nella gestione dispositivi.

Se si crea un nuovo dispositivo o si chiama IDirect3DDevice9::Reset o IDirect3DDevice9Ex::ResetEx in un dispositivo esistente, inviare un evento EC_DISPLAY_CHANGED all'EVR. Questo evento notifica all'EVR di rinegoziare il tipo di contenuto multimediale. EVR ignora i parametri dell'evento per questo evento.

Allocazione di superfici Direct3D

Dopo che il presentatore ha impostato il tipo di supporto, può allocare le superfici Direct3D, che il mixer utilizzerà per scrivere i fotogrammi video. La superficie deve corrispondere al tipo di media utilizzato dal relatore.

  • Il formato della superficie deve corrispondere al sottotipo multimediale. Ad esempio, se il sottotipo è MFVideoFormat_RGB24, il formato della superficie deve essere D3DFMT_X8R8G8B8. Per altre informazioni sui sottotipi e sui formati Direct3D, vedere GUID del sottotipo video.
  • La larghezza e l'altezza della superficie devono corrispondere alle dimensioni specificate nell'attributo MF_MT_FRAME_SIZE del tipo di supporto.

Il modo consigliato per allocare superfici dipende dal fatto che il programma venga eseguito in finestra o a schermo intero.

Se il dispositivo Direct3D è finestrato, è possibile creare diverse catene di scambio, ognuna con un singolo buffer nascosto. Usando questo approccio, è possibile presentare ogni superficie in modo indipendente, perché la presentazione di una catena di scambio non interferisce con le altre catene di scambio. Il mixer può scrivere dati su una superficie mentre un'altra superficie è programmata per la presentazione.

In primo luogo, decidere il numero di catene di scambio da creare. È consigliabile almeno tre. Per ogni catena di scambio, eseguire le operazioni seguenti:

  1. Chiama IDirect3DDevice9::CreateAdditionalSwapChain per creare la catena di scambio.
  2. Chiama IDirect3DSwapChain9::GetBackBuffer per ottenere un puntatore alla superficie back buffer della catena di scambio.
  3. Chiama MFCreateVideoSampleFromSurface e passa un puntatore alla superficie indicata. Questa funzione restituisce un puntatore a un oggetto di esempio video. L'oggetto di esempio video implementa l'interfaccia IMFSample e il presentatore usa questa interfaccia per consegnare la superficie al mixer quando il presentatore chiama il metodo IMFTransform::ProcessOutput del mixer. Per altre informazioni sull'oggetto di esempio video, vedere Video Samples.
  4. Archivia il puntatore IMFSample in una coda. Il presentatore prenderà i campioni da questa coda durante l'elaborazione, come descritto in Risultato dell'elaborazione.
  5. Mantenere un riferimento al puntatore IDirect3DSwapChain9 in modo che la catena di scambio non venga rilasciata.

In modalità esclusiva a schermo intero, il dispositivo non può avere più di una catena di scambio. Questa catena di scambio viene creata in modo implicito quando si crea il dispositivo a schermo intero. La catena di scambio può avere più buffer di fondo. Sfortunatamente, tuttavia, se si presenta un buffer nascosto mentre si scrive in un altro buffer nascosto nella stessa catena di scambio, non esiste un modo semplice per coordinare le due operazioni. Questo è dovuto al modo in cui Direct3D implementa il capovolgimento della superficie. Quando chiami Present, il driver grafico aggiorna i puntatori di superficie nella memoria grafica. Se si tengono dei puntatori IDirect3DSurface9 quando si chiama Present, essi punteranno a buffer diversi dopo che la chiamata Present termina.

L'opzione più semplice consiste nel creare un esempio video per la catena di scambio. Se si sceglie questa opzione, seguire la stessa procedura specificata per la modalità finestra. L'unica differenza è che la coda di esempio contiene un singolo esempio video. Un'altra opzione consiste nel creare superfici fuori schermo e quindi copiarle nel buffer nascosto. Le superfici create devono supportare il metodo IDirectXVideoProcessor::VideoProcessBlt, usato dal mixer per comporre i fotogrammi di output.

Esempi di rilevamento

Quando il relatore alloca per la prima volta gli esempi video, li inserisce in una coda. Il presentatore attinge da questa coda ogni volta che deve ottenere un nuovo frame dal mixer. Dopo che il mixer restituisce il fotogramma, il presentatore sposta il campione in una seconda coda. La seconda coda è per i campioni in attesa dei tempi di presentazione pianificati.

Per semplificare la tracciabilità dello stato di ciascun campione, l'oggetto campione video implementa l'interfaccia IMFTrackedSample. È possibile usare questa interfaccia come indicato di seguito:

  1. Implementare l'interfaccia IMFAsyncCallback nel presentatore.

  2. Prima di inserire un esempio nella coda pianificata, eseguire una query sull'oggetto di esempio video per l'interfaccia IMFTrackedSample.

  3. Chiamare IMFTrackedSample::SetAllocator utilizzando un puntatore alla vostra interfaccia di callback.

  4. Quando l'esempio è pronto per la presentazione, rimuoverlo dalla coda pianificata, presentarlo e rilasciare tutti i riferimenti all'esempio.

  5. L'esempio invoca il callback. L'oggetto di esempio non viene eliminato in questo caso perché contiene un conteggio dei riferimenti su se stesso fino a quando non viene richiamato il callback.

  6. All'interno del callback, restituisci il campione alla coda disponibile.

Un relatore non deve utilizzare IMFTrackedSample per tenere traccia dei campioni; è possibile implementare qualsiasi tecnica che funzioni meglio per la progettazione. Uno dei vantaggi di IMFTrackedSample è che è possibile spostare le funzioni di pianificazione e rendering del relatore in oggetti helper e questi oggetti non necessitano di alcun meccanismo speciale per richiamare il relatore quando rilasciano campioni video perché l'oggetto di esempio fornisce tale meccanismo.

Il codice seguente illustra come impostare il callback:

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

Nel callback chiamare IMFAsyncResult::GetObject sull'oggetto risultato asincrono per recuperare un puntatore all'esempio:

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

Elaborazione dell'output

Ogni volta che il mixer riceve un nuovo campione di input, EVR invia un messaggio di MFVP_MESSAGE_PROCESSINPUTNOTIFY al presentatore. Questo messaggio indica che il mixer potrebbe avere un nuovo fotogramma video da consegnare. In risposta, il relatore chiama IMFTransform::ProcessOutput sul mixer. Se il metodo ha esito positivo, il relatore pianifica l'esempio per la presentazione.

Per ottenere l'output dal mixer, seguire questa procedura:

  1. Controllare lo stato dell'orologio. Se l'orologio viene sospeso, ignorare il messaggio MFVP_MESSAGE_PROCESSINPUTNOTIFY a meno che non si tratta del primo fotogramma video. Se l'orologio sta funzionando, o se si tratta del primo fotogramma video, continuare.

  2. Ottieni un campione dalla coda di campioni disponibili. Se la coda è vuota, significa che tutti i campioni allocati sono attualmente programmati per essere presentati. In tal caso, ignorare il messaggio MFVP_MESSAGE_PROCESSINPUTNOTIFY in questo momento. Quando l'esempio successivo diventa disponibile, ripetere i passaggi elencati qui.

  3. (Facoltativo.) Se l'orologio è disponibile, ottenere l'ora dell'orologio corrente (T1) chiamando IMFClock::GetCorrelatedTime.

  4. Chiama IMFTransform::ProcessOutput sul miscelatore. Se ProcessOutput ha esito positivo, il campione contiene un fotogramma di un video. Se il metodo non riesce, controllare il codice restituito. I codici di errore seguenti di ProcessOutput non sono errori critici:

    Codice di errore Descrizione
    MF_E_TRANSFORM_NEED_MORE_INPUT Il mixer richiede più input prima di poter produrre un nuovo frame di output.
    Se viene visualizzato questo codice di errore, verificare se L'EVR ha raggiunto la fine del flusso e rispondere di conseguenza, come descritto in End of Stream. Altrimenti, ignora questo messaggio di MF_E_TRANSFORM_NEED_MORE_INPUT. L'EVR invierà un altro quando il mixer ottiene più input.
    MF_E_TRANSFORM_STREAM_CHANGE Il tipo di output del mixer è diventato non valido, probabilmente a causa di una modifica del formato upstream.
    Se visualizzi questo codice di errore, imposta il tipo di supporto del presentatore su NULL. EVR richiederà un nuovo formato.
    MF_E_TRANSFORM_TYPE_NOT_SET Il mixer richiede un nuovo tipo di supporto.
    Se viene visualizzato questo codice di errore, rinegoziare il formato di output del mixer secondo quanto descritto in Negozia formati.

     

    Se ProcessOutput ha esito positivo, continua.

  5. (Facoltativo. Se l'orologio è disponibile, ottenere l'ora corrente (T2). La quantità di latenza introdotta dal mixer è (T2 - T1). Invia un evento EC_PROCESSING_LATENCY con questo valore all'EVR. L'EVR usa questo valore per il controllo qualità. Se non è disponibile alcun orologio, non esiste alcun motivo per inviare l'evento EC_PROCESSING_LATENCY.

  6. (Facoltativo.) Eseguire una query sull'esempio per IMFTrackedSample e chiamare IMFTrackedSample::SetAllocator come descritto in Tracking Samples.

  7. Pianificare l'esempio per la presentazione.

Questa sequenza di passaggi può terminare prima che il relatore ottenga qualsiasi output dal mixer. Per assicurarsi che nessuna richiesta venga eliminata, è necessario ripetere gli stessi passaggi quando si verifica quanto segue:

  • Viene chiamato il metodo IMFClockStateSink::OnClockStart o IMFClockStateSink::OnClockStart. In questo modo viene gestito il caso in cui il mixer ignora l'input perché l'orologio viene sospeso (passaggio 1).
  • È invocato il callback IMFTrackedSample. Questo gestisce il caso in cui il mixer riceve l'input, ma tutti gli esempi video del relatore sono in uso (passaggio 2).

I successivi esempi di codice mostrano questi passaggi in modo più dettagliato. Il relatore chiama il metodo ProcessInputNotify (illustrato nell'esempio seguente) quando ottiene il messaggio 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;
}

Questo metodo ProcessInputNotify imposta un flag booleano per registrare il fatto che il mixer abbia un nuovo input. Chiama quindi il metodo ProcessOutputLoop, illustrato nell'esempio successivo. Questo metodo tenta di estrarre il maggior numero possibile di campioni dal mixer:

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

Il metodo ProcessOutput, illustrato nell'esempio seguente, tenta di ottenere un singolo fotogramma video dal mixer. Se non è disponibile alcun fotogramma video, ProcessSample restituisce S_FALSE o un codice di errore, che interrompe il ciclo in ProcessOutputLoop. La maggior parte del lavoro viene eseguita all'interno del metodo 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;
}

Alcune osservazioni su questo esempio:

  • Si presuppone che la variabile m_SamplePool sia un oggetto raccolta che contiene la coda di esempi video disponibili. Il metodo GetSample dell'oggetto restituisce MF_E_SAMPLEALLOCATOR_EMPTY se la coda è vuota.
  • Se il metodo ProcessOutput del mixer restituisce MF_E_TRANSFORM_NEED_MORE_INPUT, significa che il mixer non può produrre più output, quindi il relatore cancella il flag m_fSampleNotify.
  • Il metodo TrackSample, che imposta il callback IMFTrackedSample, viene visualizzato nella sezione tracking samples.

Riverniciatura dei telai

In alcuni casi il relatore potrebbe dover ridipingere il fotogramma video più recente. Ad esempio, il presentatore standard ridisegna il frame nelle seguenti situazioni:

Seguire questa procedura per richiedere al mixer di ricreare il fotogramma più recente:

  1. Ottenere un esempio video dalla coda.
  2. Eseguire una query sull'esempio per l'interfacciaIMFDesiredSample.
  3. Chiamare IMFDesiredSample::SetDesiredSampleTimeAndDuration. Specificare il timestamp del fotogramma video più recente. Sarà necessario memorizzare nella cache questo valore e aggiornarlo per ogni fotogramma.
  4. Chiama ProcessOutput sul mixer.

Quando si rivernicia una cornice, è possibile ignorare l'orologio della presentazione e presentare immediatamente la cornice.

Esempi di pianificazione

I fotogrammi video possono raggiungere L'EVR in qualsiasi momento. Il relatore è responsabile della presentazione di ogni fotogramma al momento corretto, in base al timestamp del frame. Quando il presentatore ottiene un nuovo campione dal mixer, mette il campione nella coda pianificata. In un thread separato, il presentatore ottiene continuamente il primo campione dall'inizio della coda e determina se:

  • Presentare l'esempio.
  • Mantenere il campione nella coda perché è presto.
  • Eliminare il campione perché è in ritardo. Anche se è consigliabile evitare di eliminare fotogrammi, se possibile, potrebbe essere necessario se il relatore è costantemente in ritardo.

Per ottenere il timestamp per un fotogramma video, chiamare IMFSample::GetSampleTime nell'esempio video. Il timestamp è relativo all'orologio della presentazione di EVR. Per ottenere l'ora corrente, chiamare IMFClock::GetCorrelatedTime. Se L'EVR non ha un orologio di presentazione o se un campione non ha un timestamp, è possibile presentare l'esempio immediatamente dopo averlo ottenuto.

Per ottenere la durata di ogni esempio, chiamare IMFSample::GetSampleDuration. Se l'esempio non ha una durata, è possibile usare la funzione MFFrameRateToAverageTimePerFrame per calcolare la durata in base alla frequenza dei fotogrammi.

Quando si pianificano esempi, tenere presente quanto segue:

  • Se la velocità di riproduzione è più veloce o più lenta del normale, l'orologio funziona ad un ritmo più rapido o più lento. Ciò significa che il timestamp di un campione fornisce sempre l'ora di destinazione corretta rispetto all'orologio della presentazione. Tuttavia, se si convertono i tempi di presentazione in un'altra ora dell'orologio (ad esempio, il contatore delle prestazioni ad alta risoluzione), è necessario ridimensionare i tempi in base alla velocità dell'orologio. Se la velocità dell'orologio cambia, il EVR chiama il metodo IMFClockStateSink::OnClockSetRate.
  • La frequenza di riproduzione può essere negativa per la riproduzione inversa. Quando la frequenza di riproduzione è negativa, il clock di presentazione scorre all'indietro. In altre parole, tempo N + 1 avviene prima di tempo N.

Nell'esempio seguente, viene calcolato se un campione è in anticipo o in ritardo rispetto all'orologio della presentazione.

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

L'orologio della presentazione è in genere guidato dall'orologio di sistema o dal renderer audio. (Il renderer audio deriva il tempo dalla velocità con cui la scheda audio consuma l'audio.) In generale, l'orologio di presentazione non è sincronizzato con la frequenza di aggiornamento del monitor.

Se i parametri della presentazione Direct3D specificano D3DPRESENT_INTERVAL_DEFAULT o D3DPRESENT_INTERVAL_ONE per l'intervallo di presentazione, l'operazione Present attende il ritracciamento verticale del monitor. Questo è un modo semplice per prevenire l'effetto tearing, ma riduce la precisione del tuo algoritmo di pianificazione. Viceversa, se l'intervallo di ripresentazione è D3DPRESENT_INTERVAL_IMMEDIATE, il metodo Present viene eseguito immediatamente, causando tearing, a meno che l'algoritmo di pianificazione non sia abbastanza accurato da chiamare Present solo durante il periodo di ritracciamento verticale.

Le funzioni seguenti consentono di ottenere informazioni precise sulla tempistica:

  • IDirect3DDevice9::GetRasterStatus restituisce informazioni sul raster, inclusa la linea di scansione corrente e se il raster si trova nell'intervallo di blanking verticale.
  • DwmGetCompositionTimingInfo restituisce informazioni sulla tempistica per il gestore delle finestre del desktop. Queste informazioni sono utili se la composizione del desktop è abilitata.

Presentazione di esempi

Questa sezione presuppone che sia stata creata una catena di scambio separata per ogni superficie, come descritto in Allocazione di superfici Direct3D. Per presentare un esempio, ottenere la catena di scambio dall'esempio video come indicato di seguito:

  1. Chiamare IMFSample::GetBufferByIndex nell'esempio video per ottenere il buffer.
  2. Eseguire una query sul buffer per l'interfaccia IMFGetService.
  3. Chiamare IMFGetService::GetService per ottenere l'interfaccia IDirect3DSurface9 della superficie Direct3D. È possibile combinare questo passaggio e il passaggio precedente in uno chiamando MFGetService.)
  4. Chiamare IDirect3DSurface9::GetContainer sulla superficie per ottenere un puntatore alla catena di scambio.
  5. Chiama IDirect3DSwapChain9::Present sulla catena di swap.

Il codice seguente illustra questi passaggi:

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

Rettangoli di origine e di destinazione

Il rettangolo di origine è la parte del fotogramma video da visualizzare. Viene definito in relazione a un sistema di coordinate normalizzato, in cui l'intero fotogramma video occupa un rettangolo con coordinate {0, 0, 1, 1}. Il rettangolo di destinazione è l'area all'interno della superficie di destinazione in cui viene disegnato il fotogramma video. Il relatore standard consente a un'applicazione di impostare questi rettangoli chiamando IMFVideoDisplayControl::SetVideoPosition.

Sono disponibili diverse opzioni per l'applicazione di rettangoli di origine e di destinazione. La prima opzione consiste nel consentire al mixer di applicarli:

  • Impostare il rettangolo di origine usando l'attributo VIDEO_ZOOM_RECT. Il mixer applicherà il rettangolo di origine quando blitrà il video sulla superficie di destinazione. Il rettangolo di origine predefinito del mixer è l'intero fotogramma.
  • Impostare il rettangolo di destinazione come apertura geometrica nel tipo di output del mixer. Per ulteriori informazioni, vedere Formati di negoziazione.

La seconda opzione consiste nell'applicare i rettangoli quando si usa IDirect3DSwapChain9::Present specificando i parametri pSourceRect e pDestRect nel metodo Present. È possibile combinare queste opzioni. Ad esempio, è possibile impostare il rettangolo di origine sul mixer, ma applicare il rettangolo di destinazione nel metodo Present.

Se l'applicazione modifica il rettangolo di destinazione o ridimensiona la finestra, potrebbe essere necessario allocare nuove superfici. In tal caso, è necessario prestare attenzione a sincronizzare questa operazione con il thread di pianificazione. Svuotare la coda di pianificazione ed eliminare i vecchi campioni prima di allocare nuove superfici.

Fine del flusso

Quando ogni flusso di input nell'EVR è terminato, l'EVR invia un messaggio contenente MFVP_MESSAGE_ENDOFSTREAM al relatore. Nel momento in cui si riceve il messaggio, tuttavia, potrebbero essere presenti alcuni fotogrammi video rimanenti per l'elaborazione. Prima di rispondere al messaggio di fine flusso, è necessario scaricare tutto l'output dal mixer e presentare tutti i fotogrammi rimanenti. Dopo aver presentato l'ultimo fotogramma, inviare un evento EC_COMPLETE all'EVR.

Nell'esempio seguente viene illustrato un metodo che invia l'evento EC_COMPLETE se vengono soddisfatte diverse condizioni. In caso contrario, restituisce S_OK senza inviare l'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;
}

Questo metodo controlla gli stati seguenti:

  • Se la variabile m_fSampleNotify è TRUE, significa che il mixer ha uno o più fotogrammi che non sono ancora stati elaborati. Per informazioni dettagliate, vedere l'output di elaborazione .
  • La variabile m_fEndStreaming è un flag booleano il cui valore iniziale FALSE. Il presentatore imposta il flag su TRUE quando EVR invia il messaggio MFVP_MESSAGE_ENDOFSTREAM.
  • Si presuppone che il metodo AreSamplesPending restituisca TRUE finché uno o più fotogrammi sono in attesa nella coda pianificata.

Nel metodo IMFVideoPresenter::P rocessMessage impostare m_fEndStreaming su TRUE e chiamare CheckEndOfStream quando EVR invia il messaggio di 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;
}

Inoltre, chiamare CheckEndOfStream se il metodo IMFTransform::ProcessOutput del mixer restituisce MF_E_TRANSFORM_NEED_MORE_INPUT. Questo codice di errore indica che il mixer non ha più campioni di input (vedere Output di Elaborazione).

Esecuzione di fotogrammi

EVR è progettato per supportare l'avanzamento di fotogrammi in DirectShow e lo scorrimento in Media Foundation. L'avanzamento fotogramma e lo scorrimento sono concettualmente simili. In entrambi i casi, l'applicazione richiede un fotogramma video alla volta. Internamente, il relatore usa lo stesso meccanismo per implementare entrambe le funzionalità.

L'avanzamento frame in DirectShow funziona nel modo seguente:

  • L'applicazione chiama IVideoFrameStep::Step. Il numero di passaggi viene specificato nel parametro dwSteps. L'EVR invia un messaggio di MFVP_MESSAGE_STEP al relatore, in cui il parametro del messaggio (ulParam) è il numero di passaggi.
  • Se l'applicazione chiama IVideoFrameStep::CancelStep o modifica lo stato del grafo (in esecuzione, sospeso o arrestato), EVR invia un messaggio MFVP_MESSAGE_CANCELSTEP.

Lo scrubbing in Media Foundation funziona come segue:

  • L'applicazione imposta la frequenza di riproduzione su zero chiamando IMFRateControl::SetRate.
  • Per eseguire il rendering di un nuovo frame, l'applicazione chiama IMFMediaSession::Start con la posizione desiderata. EVR invia un messaggio di MFVP_MESSAGE_STEP con ulParam uguale a 1.
  • Per interrompere lo scrubbing, l'applicazione imposta la frequenza di riproduzione su un valore diverso da zero. L'EVR invia il messaggio di MFVP_MESSAGE_CANCELSTEP.

Dopo aver ricevuto il messaggio di MFVP_MESSAGE_STEP, il presentatore attende che il frame di destinazione arrivi. Se il numero di passaggi è N, il relatore rimuove gli esempi successivi (N - 1) e presenta l'esempio N. Quando il presentatore completa il passaggio del frame, invia un evento di EC_STEP_COMPLETE al EVR con lParam1 impostato su FALSE. Inoltre, se la frequenza di riproduzione è zero, il presentatore invia un evento EC_SCRUB_TIME. Se l'EVR annulla l'avanzamento dei fotogrammi mentre un'operazione di avanzamento è ancora in sospeso, il presentatore invia un evento EC_STEP_COMPLETE con lParam1 impostato su TRUE.

L'applicazione può inquadrare il passo o cancellare più volte, in modo che il relatore possa ricevere più messaggi MFVP_MESSAGE_STEP prima di ottenere un messaggio di MFVP_MESSAGE_CANCELSTEP. Inoltre, il relatore può ricevere il messaggio MFVP_MESSAGE_STEP prima dell'inizio del conto alla rovescia o mentre il conto alla rovescia è in corso.

Implementazione dell'avanzamento dei frame

Questa sezione descrive un algoritmo per implementare lo scorrimento di fotogrammi. L'algoritmo di avanzamento dei fotogrammi utilizza le seguenti variabili:

  • step_count. Intero senza segno che specifica il numero di passaggi nell'operazione di esecuzione dei fotogrammi corrente.

  • step_queue. Una coda di puntatori IMFSample.

  • step_state. In qualsiasi momento, il relatore può essere in uno dei seguenti stati per quanto riguarda il passaggio dei fotogrammi.

    Stato Descrizione
    NOT_STEPPING Non eseguire l'istruzione frame.
    ATTESA Il presentatore ha ricevuto il messaggio di MFVP_MESSAGE_STEP, ma l'orologio non è stato avviato.
    IN SOSPESO Il presentatore ha ricevuto il messaggio di MFVP_MESSAGE_STEP e l'orologio è stato avviato, ma il presentatore è in attesa di ricevere il frame obiettivo.
    PROGRAMMATO Il presentatore ha ricevuto il frame di destinazione e ha programmato di presentarlo, ma il frame non è stato presentato.
    COMPLETO Il presentatore ha presentato il frame di destinazione e ha inviato l'evento EC_STEP_COMPLETE, ed è in attesa del prossimo messaggio MFVP_MESSAGE_STEP o MFVP_MESSAGE_CANCELSTEP.

     

    Questi stati sono indipendenti dagli stati del relatore elencati nella sezione Stati relatore.

Per l'algoritmo frame-stepping vengono definite le procedure seguenti:

Procedura PrepareFrameStep

  1. Incremento step_count.
  2. Impostare step_state su WAITING.
  3. Se l'orologio è in esecuzione, chiamare StartFrameStep.

Procedura StartFrameStep

  1. Se step_state è uguale a WAITING, impostare step_state su PENDING. Per ogni esempio in step_queue, chiamare DeliverFrameStepSample.
  2. Se step_state è uguale a NOT_STEPPING, rimuovere eventuali esempi da step_queue e pianificarli per la presentazione.

Procedura di CompleteFrameStep

  1. Impostare step_state su COMPLETE.
  2. Invia l'evento EC_STEP_COMPLETE con lParam1 = FALSE.
  3. Se la frequenza di clock è zero, inviare l'evento EC_SCRUB_TIME con il tempo campione.

Procedura di esempio passo per passo DeliverFrame

  1. Se la frequenza di clock è zero e ora di campionamento + durata del campionamento<orario di clock, eliminare il campione. Uscita.
  2. Se step_state è uguale a SCHEDULED o COMPLETE, aggiungere l'esempio a step_queue. Uscita.
  3. Decrementazione step_count.
  4. Se step_count> 0, eliminare l'esempio. Uscita.
  5. Se step_state è uguale a WAITING, aggiungere l'esempio a step_queue. Uscita.
  6. Pianificare l'esempio per la presentazione.
  7. Impostare step_state su SCHEDULED.

Procedura CancelFrameStep

  1. Impostare step_state su NOT_STEPPING
  2. Reimpostare step_count su zero.
  3. Se il valore precedente di step_state era WAITING, PENDING o SCHEDULED, inviare EC_STEP_COMPLETE con lParam1 = TRUE.

Chiamare queste procedure come indicato di seguito:

Messaggio o metodo del relatore Procedimento
MFVP_MESSAGE_STEP messaggio PrepareFrameStep
MFVP_MESSAGE_STEP messaggio CancelStep
IMFClockStateSink::OnClockStart StartFrameStep
IMFClockStateSink::OnClockRestart StartFrameStep
IMFTrackedSample callback CompleteFrameStep
IMFClockStateSink::OnClockStop CancelFrameStep
IMFClockStateSink::OnClockSetRate CancelFrameStep

 

Il seguente diagramma di flusso mostra le procedure di avanzamento dei fotogrammi.

diagramma di flusso che mostra i percorsi che iniziano con mfvp-message-step e mfvp-message-processinputnotify e terminano in

Impostazione del relatore in EVR

Dopo aver implementato il relatore, il passaggio successivo consiste nel configurare EVR per usarlo.

Impostazione del relatore in DirectShow

In un'applicazione DirectShow impostare il relatore su EVR come indicato di seguito:

  1. Creare il filtro EVR chiamando CoCreateInstance. Il CLSID è CLSID_EnhancedVideoRenderer.
  2. Aggiungere l'EVR al grafico del filtro.
  3. Creare un'istanza del presentatore. Il presentatore può supportare la creazione di comuni oggetti COM tramite IClassFactory, ma non è obbligatorio.
  4. Eseguire una query sul filtro EVR per l'interfaccia IMFVideoRenderer.
  5. Chiamare IMFVideoRenderer::InitializeRenderer.

Impostazione del presentatore in Media Foundation

In Media Foundation sono disponibili diverse opzioni, a seconda che si crei il sink multimediale EVR o l'oggetto di attivazione EVR. Per altre informazioni sugli oggetti di attivazione, vedere Oggetti di Attivazione.

Per il ricevitore multimediale EVR, eseguire le operazioni seguenti:

  1. Chiama MFCreateVideoRenderer per creare il sink multimediale.
  2. Crea un'istanza del presentatore.
  3. Effettuare una query sul sink multimediale EVR per l'interfaccia IMFVideoRenderer.
  4. Chiamare IMFVideoRenderer::InitializeRenderer.

Per l'oggetto attivazione EVR, eseguire le operazioni seguenti:

  1. Chiama MFCreateVideoRendererActivate per creare l'oggetto di attivazione.

  2. Impostare uno degli attributi seguenti nell'oggetto di attivazione:

    Attributo Descrizione
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE Puntatore a un oggetto di attivazione per il relatore.
    Con questo flag, è necessario fornire un oggetto di attivazione per il presentatore. L'oggetto di attivazione deve implementare l'interfaccia IMFActivate.
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID del relatore.
    Con questo flag, il presentatore deve supportare la creazione standard di oggetti COM tramite IClassFactory.

     

  3. Facoltativamente, impostare l'attributo MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS sull'oggetto attivazione.

del renderer video avanzato

Esempio EVRPresenter