Come scrivere un relatore EVR

[Il componente descritto in questa pagina, Il renderer video avanzato è una funzionalità legacy. È stato sostituito dal Simple Video Renderer (SVR) esposto attraverso 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 le API IMFMediaEngine di livello inferiore 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 questo argomento è adattato dall'esempio EVRPresenter, fornito in Windows SDK.

Questo argomento include le sezioni seguenti:

Prerequisiti

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

  • Renderer video avanzato. Vedere Enhanced Video Renderer (Renderer video avanzato).
  • 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 dell'EVR

L'EVR usa due componenti plug-in per eseguire il rendering del video: il mixer e il relatore. Se necessario, il mixer fonde i flussi video e denterlazza il video. 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 alfa-fonde nel flusso di riferimento. Il flusso di riferimento determina la frequenza dei fotogrammi master 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 la conversione di colori e di denterlacing 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.

diagram showing the reference stream and substream pointing to the mixer, which points to the presenter, which points to the 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 le superfici Direct3D. Il mixer blitta le cornici composte 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 l'esecuzione di istruzioni 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 è in esecuzione. 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.
  • Arrestato. L'orologio della presentazione viene arrestato. 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. Vedere 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. Vedere Implementazione di 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. Implementare questa interfaccia se il relatore è un componente attendibile progettato per funzionare nel percorso multimediale protetto (PMP).
IMFRateSupport Segnala l'intervallo di frequenze di riproduzione supportate dal relatore. Vedere Implementazione di IMFRateSupport.
IMFVideoPositionMapper Mappe coordinate sul fotogramma video di output per le coordinate sul fotogramma video di input.
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 le interfacce dal relatore chiamando IMFGetService::GetService su EVR. Quando il GUID del servizio è MR_VIDEO_RENDER_edizione Standard RVICE, EVR passa la richiesta GetService al relatore.

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 si intende usare il relatore personalizzato con il mixer standard, il GUID del dispositivo del relatore 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 EVR inizializza il relatore, chiama il metodo IMFTopologyServiceLookupClient::InitServicePointers del relatore. L'argomento è un puntatore all'interfaccia IMFTopologyServiceLookup di EVR.
  2. Il relatore chiama IMFTopologyServiceLookup::LookupService per ottenere puntatori di interfaccia da EVR o 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_edizione Standard RVICE, viene eseguita una query su EVR.
  • Se il GUID del servizio è MR_VIDEO_MIXER_edizione Standard RVICE, viene eseguita una query sul mixer.

Nell'implementazione di InitServicePointers ottenere le interfacce seguenti da 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.
FMIClock 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 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 metodo InitServicePointers :

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

    HRESULT             hr = S_OK;
    DWORD               dwObjectCount = 0;

    EnterCriticalSection(&m_ObjectLock);

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

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

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

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

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

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

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

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

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

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

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

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

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

Quando 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 relatore per arrestare:

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 vari motivi, tra cui:

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

Durante la durata del relatore, EVR potrebbe chiamare Più volte InitServicePointers e ReleaseServicePointers.

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 di supporto del relatore. Per informazioni dettagliate sull'impostazione del tipo di supporto, vedere Negozia formati.) 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 consentire a 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.

Message Descrizione
MFVP_MESSAGE_INVALIDATEMEDIATYPE Il tipo di supporto di output del mixer non è valido. Il relatore deve negoziare un nuovo tipo di supporto con il mixer. Vedere Negozia formati.
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. Rilasciare 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::P rocessOutput sul mixer. Vedere Elaborazione dell'output.
MFVP_MESSAGE_ENDOFSTREAM La presentazione è terminata. Vedere Fine del flusso.
MFVP_MESSAGE_FLUSH EVR scarica i dati nella pipeline di rendering. Il relatore deve eliminare tutti i fotogrammi video pianificati per la presentazione.
MFVP_MESSAGE_STEP Richiede al relatore di inoltrare n fotogrammi. Il relatore deve eliminare i fotogrammi N-1 successivi e visualizzare il Nth frame. Vedere Istruzioni frame.
MFVP_MESSAGE_CANCELSTEP Annulla l'esecuzione dei fotogrammi.

 

Implementazione di IMFClockStateSink

Il relatore 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 altre informazioni sugli stati dell'orologio, vedere Clock 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. Impostare lo stato del relatore su avviato.
  2. Se llClockStartOffset non è PRE edizione StandardNTATION_CURRENT_POSITION, scaricare la coda di esempi del relatore. (Equivale a ricevere un MFVP_MESSAGE_FLUSH messaggio.
  3. Se una richiesta precedente in fase di fotogramma è ancora in sospeso, elaborare la richiesta (vedere Frame Stepping). In caso contrario, provare a elaborare l'output dal mixer (vedere Elaborazione dell'output.
OnClockStop
  1. Impostare lo stato del relatore su arrestato.
  2. Scaricare la coda di esempi del relatore.
  3. Annullare qualsiasi operazione in sospeso in fase di fotogramma.
OnClockPause Impostare lo stato del relatore su sospeso.
OnClockRestart Considera come OnClockStart ma non scarica la coda di esempi.
OnClockSetRate
  1. Se la frequenza cambia da zero a diverso da zero a zero, annullare l'istruzione dei fotogrammi.
  2. Archiviare la nuova frequenza di clock. La frequenza di clock influisce sulla presentazione dei campioni. Per altre informazioni, vedere Scheduling Samples.For more information, see Scheduling Samples.

 

Implementazione di IMFRateSupport

Per supportare le frequenze di riproduzione diverse da 1× velocità, 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 Controllo frequenza.

Valore Descrizione
GetSlowestRate Restituisce zero per indicare la frequenza di riproduzione minima.
GetFastestRate Per la riproduzione non sottile, la frequenza di riproduzione non deve superare la frequenza di aggiornamento del monitor: frequenza massima di aggiornamento della frequenza = (Hz) /frequenza dei fotogrammi video (fps). La frequenza dei fotogrammi video viene specificata nel tipo di supporto del relatore.
Per la riproduzione con thinned, la frequenza di riproduzione non è associato; restituisce il valore FLT_MAX. In pratica, l'origine e il decodificatore saranno i fattori di limitazione durante la riproduzione sfoltita.
Per la riproduzione inversa, restituisce il valore negativo della frequenza massima.
IsRateSupported Restituisce MF_E_UNSUPPORTED_RATE se il valore assoluto di flRate supera la frequenza di riproduzione massima del relatore. Calcolare la frequenza di riproduzione massima come descritto per GetFastestRate.

 

L'esempio seguente illustra come implementare il metodo GetFastestRate:

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

    // Thinned: The maximum rate is unbounded.

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

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

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

    return fMaxRate;
}

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

L'esempio seguente illustra 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 il EVR chiama il metodo IMFTopologyServiceLookupClient::InitServicePointers del relatore. (L'oggetto 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 relatore ha completato il rendering di tutti i fotogrammi dopo il messaggio di MFVP_MESSAGE_ENDOFSTREAM.
  • Param1: HRESULT che indica lo stato dell'operazione.
  • Param2: non usato.
Per altre informazioni, vedere Fine del flusso.
EC_DISPLAY_CHANGED Il dispositivo Direct3D è cambiato.
  • Param1: non usato.
  • Param2: non usato.
Per altre informazioni, vedere Gestione del dispositivo Direct3D.
EC_ERRORABORT Si è verificato un errore che richiede l'arresto dello streaming.
  • Param1: HRESULT che indica l'errore che 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 un valore LONGLONG costante che contiene la quantità di tempo per elaborare il fotogramma, in unità di 100 nanosecondi.
  • Param2: non usato.
Per altre informazioni, vedere Elaborazione dell'output.
EC_SAMPLE_LATENCY Specifica il tempo di ritardo corrente negli esempi di rendering. Se il valore è positivo, i campioni sono in ritardo nella pianificazione. Se il valore è negativo, i campioni sono in anticipo rispetto alla pianificazione. (Facoltativo)
  • Param1: puntatore a un valore LONGLONG costante che contiene il tempo di ritardo, in unità di 100 nanosecondi.
  • Param2: non usato.
EC_SCRUB_TIME Inviato immediatamente dopo EC_STEP_COMPLETE se la frequenza 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.For more information, see Frame Stepping.
EC_STEP_COMPLETE Il relatore ha completato o annullato un passaggio frame.
- Param1: non usato.
- Param2: non usato.
Per altre informazioni, vedere Frame Stepping.For more information, see Frame Stepping.
Nota: una versione precedente della documentazione descritta in modo non corretto param1 . Questo parametro non viene usato per questo evento.

 

Negoziazione dei formati

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

  1. Chiama 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 da allocare.
    • Impostare l'attributo MF_MT_PAN_SCAN_ENABLED su FAL edizione Standard.
    • Impostare l'attributo MF_MT_PIXEL_ASPECT_RATIO uguale a PAR dello schermo (in genere 1:1).
    • Impostare l'apertura geometrica (attributo MF_MT_GEOMETRIC_APERTURE ) su un rettangolo all'interno della superficie Direct3D. Quando il mixer genera un frame di output, blitta l'immagine di origine in questo rettangolo. L'apertura geometrica può essere grande come la superficie oppure può essere un subrettangle all'interno della superficie. Per altre informazioni, vedere Rettangoli di origine e di destinazione.
  4. Per verificare se il mixer accetterà il tipo di output modificato, chiamare IMFTransform::SetOutputType con il flag MFT_edizione StandardT_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 Allocazione di 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 ha avuto esito positivo nel passaggio 4, il metodo dovrebbe avere esito positivo.

Se il mixer esaurisce i tipi, il metodo GetOutputAvailableType restituisce 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 relatore potrebbe ricevere diversi messaggi MFVP_MESSAGE_INVALIDATEMEDIATYPE in una riga fino a quando non viene trovato un tipo valido.

Il mixer scrive automaticamente il video, tenendo conto delle proporzioni 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.

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

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 usa il dispositivo Direct3D per combinare sottostream, deinterlace ed eseguire regolazioni dei colori. I decodificatori possono usare il dispositivo Direct3D per la decodifica con accelerazione video. Per altre informazioni sull'accelerazione video, vedere Accelerazione video DirectX 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. Creare la gestione dispositivi chiamando DXVA2CreateDirect3DDeviceManager9.
  4. Impostare il dispositivo nella gestione dispositivi chiamando IDirect3DDeviceManager9::ResetDevice.

Se un altro componente della pipeline richiede gestione dispositivi, chiama IMFGetService::GetService in EVR, specificando MR_VIDEO_ACCELERATION_edizione Standard RVICE per il GUID del servizio. L'EVR passa la richiesta al relatore. Dopo che l'oggetto ottiene il puntatore IDirect3DDeviceManager9, può ottenere un handle per il dispositivo chiamando IDirect3DDeviceManager9::OpenDeviceHandle. Quando l'oggetto deve usare il dispositivo, passa l'handle del dispositivo al metodo IDirect3DDeviceManager9::LockDevice, che restituisce un puntatore IDirect3DDevice9.

Dopo aver creato il dispositivo, se il relatore distrugge il dispositivo e ne crea uno nuovo, il relatore deve chiamare nuovamente ResetDevice . Il metodo ResetDevice invalida qualsiasi handle di dispositivo esistente, che fa sì che LockDevice restituisca 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 altre informazioni sull'uso di Gestione dispositivi, vedere Direct3D Gestione dispositivi.

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. È necessario creare il dispositivo quando 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 seguito&#; 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 supporto. EVR ignora i parametri dell'evento per questo evento.

Allocazione di superfici Direct3D

Dopo che il relatore imposta il tipo di supporto, può allocare le superfici Direct3D, che il mixer userà per scrivere i fotogrammi video. La superficie deve corrispondere al tipo di supporto del 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 relatore esegua finestre o 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 in una superficie mentre è pianificata un'altra superficie 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. Questa funzione restituisce un puntatore a un oggetto di esempio video. L'oggetto di esempio video implementa l'interfaccia IMFSample e il relatore usa questa interfaccia per recapitare la superficie al mixer quando il relatore chiama il metodo IMFTransform::P rocessOutput del mixer. Per altre informazioni sull'oggetto di esempio video, vedere Esempi video.
  4. Archiviare il puntatore IMFSample in una coda. Il relatore eseguirà il pull di campioni da questa coda durante l'elaborazione, come descritto in Elaborazione dell'output.
  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 nascosto. 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 contengono puntatori IDirect3DSurface9 quando si chiama Present, questi punteranno a buffer diversi dopo la restituzione della chiamata Present .

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 relatore disegna da questa coda ogni volta che deve ottenere un nuovo frame dal mixer. Dopo che il mixer restituisce il fotogramma, il relatore sposta l'esempio in una seconda coda. La seconda coda è per i campioni in attesa dei tempi di presentazione pianificati.

Per semplificare la traccia dello stato di ogni esempio, l'oggetto di esempio video implementa l'interfaccia IMFTrackedSample . È possibile usare questa interfaccia come indicato di seguito:

  1. Implementare l'interfaccia IMFAsyncCallback nel relatore.

  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 con un puntatore all'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 richiama 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 restituire l'esempio alla coda disponibile.

Un relatore non è necessario per usare IMFTrackedSample per tenere traccia dei campioni. È possibile implementare qualsiasi tecnica che funzioni meglio per la progettazione. Un vantaggio 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 esempio di input, EVR invia un messaggio MFVP_MESSAGE_PROCESSINPUTNOTIFY al relatore. Questo messaggio indica che il mixer potrebbe avere un nuovo fotogramma video da recapitare. In risposta, il relatore chiama IMFTransform::P rocessOutput 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 è in esecuzione o se si tratta del primo fotogramma video, continuare.

  2. Ottenere un esempio dalla coda di esempi disponibili. Se la coda è vuota, significa che tutti i campioni allocati sono attualmente pianificati per la presentazione. 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 di clock corrente (T1) chiamando IMFClock::GetCorrelatedTime.

  4. Chiama IMFTransform::P rocessOutput sul mixer. Se ProcessOutput ha esito positivo , l'esempio contiene un fotogramma 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_Nedizione EnterpriseD_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 Fine del flusso. In caso contrario, ignorare questo messaggio di MF_E_TRANSFORM_Nedizione EnterpriseD_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 viene visualizzato questo codice di errore, impostare il tipo di supporto del relatore su NULL. EVR richiederà un nuovo formato.
    MF_E_TRANSFORM_TYPE_NOT_edizione Standard T Il mixer richiede un nuovo tipo di supporto.
    Se viene visualizzato questo codice di errore, rinegoziare il tipo di output del mixer come descritto in Negozia formati.

     

    Se ProcessOutput ha esito positivo , continuare.

  5. (Facoltativo. Se l'orologio è disponibile, ottenere l'ora dell'orologio corrente (T2). La latenza introdotta dal mixer è (T2 - T1). Inviare un evento EC_PROCESSING_LATENCY con questo valore a 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 Esempi di rilevamento.

  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 del relatore. In questo modo viene gestito il caso in cui il mixer ignora l'input perché l'orologio viene sospeso (passaggio 1).
  • Viene richiamato 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 ProcessInputNotify metodo (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 ProcessInputNotify metodo imposta un flag booleano per registrare il fatto che il mixer abbia un nuovo input. Chiama quindi il ProcessOutputLoop metodo , 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 ProcessOutput metodo, illustrato nell'esempio successivo, tenta di ottenere un singolo fotogramma video dal mixer. Se non è disponibile alcun fotogramma video, ProcessSample restituisce S_FALedizione Standard o un codice di errore, che interrompe il ciclo in ProcessOutputLoop. La maggior parte del lavoro viene eseguita all'interno del ProcessOutput metodo :

//-----------------------------------------------------------------------------
// 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 dell'oggetto GetSample restituisce MF_E_SAMPLEALLOCATOR_EMPTY se la coda è vuota.
  • Se il metodo ProcessOutput del mixer restituisce MF_E_TRANSFORM_Nedizione EnterpriseD_MORE_INPUT, significa che il mixer non può produrre più output, quindi il relatore cancella il flag m_fSampleNotify.
  • Il TrackSample metodo, che imposta il callback IMFTrackedSample , è illustrato nella sezione Esempi di rilevamento.

Ripainting Frame

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

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'interfaccia IMFDesiredSample .
  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 aggiorna una cornice, è possibile ignorare l'orologio della presentazione e presentare immediatamente il fotogramma.

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 relatore ottiene un nuovo campione dal mixer, inserisce l'esempio nella coda pianificata. In un thread separato, il relatore ottiene continuamente il primo campione dall'inizio della coda e determina se:

  • Presentare l'esempio.
  • Mantenere l'esempio nella coda perché è presto disponibile.
  • Eliminare l'esempio 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, chiama IMFSample::GetSampleTime nell'esempio video. Il timestamp è relativo all'orologio della presentazione di EVR. Per ottenere l'ora di clock 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 dalla frequenza dei fotogrammi.

Quando si pianificano esempi, tenere presente quanto segue:

  • Se la velocità di riproduzione è più veloce o più lenta della velocità normale, l'orologio viene eseguito a una velocità più veloce o più lenta. 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 metodo EVR chiama il metodo IMFClockStateSink::OnClockSetRate del relatore.
  • La frequenza di riproduzione può essere negativa per la riproduzione inversa. Quando la frequenza di riproduzione è negativa, l'orologio della presentazione viene eseguito all'indietro. In altre parole, l'ora N + 1 si verifica prima dell'ora N.

Nell'esempio seguente viene calcolato il tempo o la fine di un campione 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 dall'ora in cui la scheda audio utilizza l'audio. In generale, l'orologio della presentazione non è sincronizzato con la frequenza di aggiornamento del monitor.

Se i parametri della presentazione Direct3D specificano D3DPRE edizione StandardNT_INTERVAL_DEFAULT o D3DPRE edizione StandardNT_INTERVAL_ONE per l'intervallo di presentazione, l'operazione Present attende la ritracciamento verticale del monitor. Si tratta di un modo semplice per evitare la disinstallazione, ma riduce l'accuratezza dell'algoritmo di pianificazione. Viceversa, se l'intervallo di presentazione è D3DPRE edizione StandardNT_INTERVAL_IMMEDIATE, il metodo Present viene eseguito immediatamente, causando l'strappo 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 sulla raster, inclusa la riga di analisi corrente e se il raster si trova nel punto vuoto verticale.
  • DwmGetCompositionTimingInfo restituisce informazioni sulla tempistica per gestione finestre 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. Chiama IMFSample::GetBufferByIndex nell'esempio video per ottenere il buffer.
  2. Eseguire una query sul buffer per l'interfaccia IMFGetService .
  3. Chiama IMFGetService::GetService per ottenere l'interfaccia IDirect3DSurface9 della superficie Direct3D. È possibile combinare questo passaggio e il passaggio precedente in uno chiamando MFGetService.
  4. Chiama IDirect3DSurface9::GetContainer sulla superficie per ottenere un puntatore alla catena di scambio.
  5. Chiama IDirect3DSwapChain9::P resent sulla catena di scambio.

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 altre informazioni, vedere Negoziazione dei formati.

La seconda opzione consiste nell'applicare i rettangoli quando si specificano IDirect3DSwapChain9::P resent 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. Scaricare la coda di pianificazione ed eliminare i vecchi campioni prima di allocare nuove superfici.

Fine del flusso

Al termine di ogni flusso di input in EVR, EVR invia un messaggio di 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 svuotare 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 Elaborazione dell'output.
  • La variabile m_fEndStreaming è un flag booleano il cui valore iniziale FAL edizione Standard. Il relatore imposta il flag su TRUE quando EVR invia il messaggio di MFVP_MESSAGE_ENDOFSTREAM .
  • Si presuppone che il AreSamplesPending metodo restituisca TRUE , purché uno o più fotogrammi siano in attesa nella coda pianificata.

Nel metodo IMFVideoPresenter::P rocessMessage impostare m_fEndStreaming su TRUE e chiamare CheckEndOfStream quando EVR invia il messaggio 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::P rocessOutput del mixer restituisce MF_E_TRANSFORM_Nedizione EnterpriseD_MORE_INPUT. Questo codice di errore indica che il mixer non include altri esempi di input (vedere Elaborazione dell'output).

Esecuzione di fotogrammi

EVR è progettato per supportare l'esecuzione di fotogrammi in DirectShow e scrubbing in Media Foundation. Le istruzioni e lo scrubbing dei fotogrammi 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'istruzione 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 MFVP_MESSAGE_STEP al relatore, dove il parametro message (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. L'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 relatore attende l'arrivo del frame di destinazione. Se il numero di passaggi è N, il relatore rimuove gli esempi successivi (N - 1) e presenta l'N campione. Quando il relatore completa il passaggio del fotogramma, invia un evento EC_STEP_COMPLETE a EVR con lParam1 impostato su FAL edizione Standard. Inoltre, se la frequenza di riproduzione è zero, il relatore invia un evento EC_SCRUB_TIME. Se EVR annulla l'esecuzione dei fotogrammi mentre un'operazione in fase di fotogramma è ancora in sospeso, il relatore invia un evento EC_STEP_COMPLETE con lParam1 impostato su TRUE.

L'applicazione può incorniciare più volte il passaggio o lo scrub, 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'avvio dell'orologio o mentre l'orologio è in esecuzione.

Implementazione dell'istruzione frame

Questa sezione descrive un algoritmo per implementare l'istruzione frame. L'algoritmo di esecuzione dei fotogrammi usa le variabili seguenti:

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

  • step_queue. Coda di puntatori IMFSample.

  • step_state. In qualsiasi momento, il relatore può trovarsi in uno degli stati seguenti per quanto riguarda l'esecuzione di fotogrammi:

    Provincia Descrizione
    NOT_STEPPING Non eseguire l'istruzione frame.
    ATTESA Il relatore ha ricevuto il messaggio MFVP_MESSAGE_STEP , ma l'orologio non è stato avviato.
    PENDING Il relatore ha ricevuto il messaggio di MFVP_MESSAGE_STEP e l'orologio è stato avviato, ma il relatore è in attesa di ricevere il frame di destinazione.
    PROGRAMMATO Il relatore ha ricevuto il frame di destinazione e l'ha pianificata per la presentazione, ma il frame non è stato presentato.
    COMPLETA Il relatore ha presentato il frame di destinazione e ha inviato l'evento EC_STEP_COMPLETE ed è in attesa del successivo MFVP_MESSAGE_STEP o MFVP_MESSAGE_CANCELSTEP messaggio.

     

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

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

Routine 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 campioni da step_queue e pianificarli per la presentazione.

Procedura CompleteFrameStep

  1. Impostare step_state su COMPLETE.
  2. Inviare l'evento EC_STEP_COMPLETE con lParam1 = FAL edizione Standard.
  3. Se la frequenza di clock è zero, inviare l'evento EC_SCRUB_TIME con l'ora del campione.

Procedura DeliverFrameStepSample

  1. Se la frequenza di clock è zero e l'ora + di tempo del campione di tempo campione<, eliminare il campione. Uscire.
  2. Se step_state è uguale a SCHEDULED o COMPLETE, aggiungere l'esempio a step_queue. Uscire.
  3. Decrementazione step_count.
  4. Se step_count> 0, eliminare l'esempio. Uscire.
  5. Se step_state è uguale a WAITING, aggiungere l'esempio a step_queue. Uscire.
  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 Procedura
MFVP_MESSAGE_STEP messaggio PrepareFrameStep
MFVP_MESSAGE_STEP messaggio CancelStep
IMFClockStateSink::OnClockStart StartFrameStep
IMFClockStateSink::OnClockRestart StartFrameStep
Callback IMFTrackedSample CompleteFrameStep
IMFClockStateSink::OnClockStop CancelFrameStep
IMFClockStateSink::OnClockSetRate CancelFrameStep

 

Il grafico di flusso seguente illustra le procedure di esecuzione delle istruzioni dei fotogrammi.

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

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. ClSID è CLSID_EnhancedVideoRenderer.
  2. Aggiungere L'EVR al grafico del filtro.
  3. Creare un'istanza del relatore. Il relatore può supportare la creazione di oggetti COM standard tramite IClassFactory, ma questo non è obbligatorio.
  4. Eseguire una query sul filtro EVR per l'interfaccia IMFVideoRenderer .
  5. Chiama IMFVideoRenderer::InitializeRenderer.

Impostazione del relatore 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 attivazione.

Per il sink multimediale EVR, eseguire le operazioni seguenti:

  1. Chiama MFCreateVideoRenderer per creare il sink multimediale.
  2. Creare un'istanza del relatore.
  3. Eseguire una query sul sink multimediale EVR per l'interfaccia IMFVideoRenderer .
  4. Chiama 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_PREedizione StandardNTER_ACTIVATE Puntatore a un oggetto di attivazione per il relatore.
    Con questo flag, è necessario fornire un oggetto di attivazione per il relatore. L'oggetto attivazione deve implementare l'interfaccia IMFActivate .
    MF_ACTIVATE_CUSTOM_VIDEO_PREedizione StandardNTER_CLSID CLSID del relatore.
    Con questo flag, il relatore deve supportare la creazione di oggetti COM standard tramite IClassFactory.

     

  3. Facoltativamente, impostare l'attributo MF_ACTIVATE_CUSTOM_VIDEO_PREedizione StandardNTER_FLAGS sull'oggetto di attivazione.

Renderer video avanzato

Esempio di EVRPresenter