Dela via


Hur man skriver en EVR-presentatör

[Komponenten som beskrivs på den här sidan, Enhanced Video Renderer, är en äldre funktion. Det har ersatts av Simple Video Renderer (SVR) som exponeras via komponenterna MediaPlayer och IMFMediaEngine. Om du vill spela upp videoinnehåll bör du skicka data till någon av dessa komponenter och låta dem instansiera den nya videoåtergivningen. Dessa komponenter har optimerats för Windows 10 och Windows 11. Microsoft rekommenderar starkt att ny kod använder MediaPlayer eller den lägre nivån IMFMediaEngine API:er för att spela upp videomedier i Windows i stället för EVR när det är möjligt. Microsoft föreslår att befintlig kod som använder äldre API:er skrivs om för att använda de nya API:erna om möjligt.]

Den här artikeln beskriver hur du skriver en anpassad presentatör för den förbättrade videoåtergivningen (EVR). En anpassad presentatör kan användas med både DirectShow och Media Foundation. gränssnitten och objektmodellen är desamma för båda teknikerna, även om den exakta åtgärdssekvensen kan variera.

Exempelkoden i det här avsnittet är anpassad från EVRPresenter Sample, som finns i Windows SDK.

Det här avsnittet innehåller följande avsnitt:

Förutsättningar

Innan du skriver en anpassad presentatör bör du känna till följande tekniker:

  • Den förbättrade videoåtergivningen. Se Förbättrad Videoåtergivning.
  • Direct3D-grafik. Du behöver inte förstå 3D-grafik för att skriva en presentatör, men du måste veta hur du skapar en Direct3D-enhet och hanterar Direct3D-ytor. Om du inte är bekant med Direct3D läser du avsnitten "Direct3D-enheter" och "Direct3D-resurser" i DirectX Graphics SDK-dokumentationen.
  • DirectShow-filterdiagram eller Media Foundation-pipelinen, beroende på vilken teknik ditt program använder för att återge video.
  • Media Foundation transformerar. EVR-mixern är en Media Foundation-transformering och presentatören anropar metoder direkt på mixern.
  • Implementera COM-objekt. Presentatören är ett processbaserat, fritt trådat COM-objekt.

Föredragshållarobjektmodell

Det här avsnittet innehåller en översikt över presentatörsobjektmodellen och gränssnitten.

Dataflöde inuti EVR

EVR använder två insticksmoduler för att återge video: -mixern och -presentatören. Mixern blandar videoströmmarna och avflätar videon om det behövs. Presentatören ritar (eller visar) videon på skärmen och schemalägger när varje bildruta ritas. Program kan ersätta något av dessa objekt med en anpassad implementering.

EVR har en eller flera indataströmmar och mixern har ett motsvarande antal indataströmmar. Stream 0 är alltid den referensströmmen. De andra strömmarna är delströmmar, som mixern alfa-blandar med referensströmmen. Referensströmmen avgör huvudramfrekvensen för den sammansatta videon. För varje referensram tar mixern den senaste ramen från varje underström, alfa-blandar dem till referensramen och matar ut en enda sammansatt ram. Mixern utför också deinterlacing och färgkonvertering från YUV till RGB om det behövs. EVR infogar alltid mixern i videopipelinen, oavsett antalet indataströmmar eller videoformatet. Följande bild illustrerar den här processen.

diagrammet som visar referensströmmen och underströmmen pekande mot mixern, som pekar mot presentatören, som pekar mot displayen

Presentatören utför följande uppgifter:

  • Anger utdataformatet på mixern. Innan direktuppspelningen börjar anger presentatören en medietyp på mixerns utdataström. Den här medietypen definierar formatet för den sammansatta bilden.
  • Skapar Direct3D-enheten.
  • Allokerar Direct3D-ytor. Mixern blitsar de sammansatta ramarna på dessa ytor.
  • Hämtar utdata från mixern.
  • Schemalägger när ramarna visas. EVR tillhandahåller presentationsklockan och presentatören schemalägger ramar enligt den här klockan.
  • Visar varje bildruta med Direct3D.
  • Utför ramstegning och rensning.

Presentatörstillstånd

Presentatören är när som helst i något av följande tillstånd:

  • Startade. EVR:s presentationsklocka går. Presentatören schemalägger videorutor för presentationen när de anländer.
  • pausad. Presentationsklockan är pausad. Presentatören presenterar inga nya exempel men behåller sin kö med schemalagda exempel. Om nya exempel tas emot lägger presentatören till dem i kön.
  • stoppades. Presentationsklockan stoppas. Presentatören tar bort alla exempel som har schemalagts.
  • Stäng av. Presentatören släpper alla resurser som är relaterade till strömning, till exempel Direct3D-ytor. Det här är presentatörens ursprungliga tillstånd och det sista tillståndet innan presentatören förstörs.

I exempelkoden i det här avsnittet representeras föredragshållartillståndet av en uppräkning:

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

Vissa åtgärder är inte giltiga när presentatören är i avstängningstillståndet. Exempelkoden söker efter det här tillståndet genom att anropa en hjälpmetod:

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

Föredragshållargränssnitt

En presentatör krävs för att implementera följande gränssnitt:

Gränssnitt Beskrivning
IMFClockStateSink Meddelar presentatören när EVR:s klocka ändrar tillstånd. Se Implementering av IMFClockStateSink.
IMFGetService Ger ett sätt för programmet och andra komponenter i pipelinen att hämta gränssnitt från presentatören.
IMFTopologyServiceLookupClient Gör att presentatören kan hämta gränssnitt från EVR eller mixern. Se implementera IMFTopologyServiceLookupClient.
IMFVideoDeviceID Säkerställer att presentatören och mixern använder kompatibla tekniker. Se Implementering av IMFVideoDeviceID.
IMFVideoPresenter Bearbetar meddelanden från EVR. Se implementera IMFVideoPresenter.

 

Följande gränssnitt är valfria:

Gränssnitt Beskrivning
IEVRTrustedVideoPlugin Gör att presentatören kan arbeta med skyddade medier. Implementera det här gränssnittet om din presentatör är en betrodd komponent utformad för att fungera inom den skyddade medieprogramvägen (PMP).
IMFRateSupport för IMF-räntestöd Rapporterar det intervall av uppspelningsfrekvenser som presentatören stöder. Se implementera IMFRateSupport.
IMFVideoPositionMapper Mappar koordinaterna för videoramen för utdata till koordinaterna för indatavideoramen.
IQualProp Rapporterar prestandainformation. EVR använder den här informationen för hantering av kvalitetskontroll. Det här gränssnittet dokumenteras i DirectShow SDK.

 

Du kan också tillhandahålla gränssnitt för programmet för att kommunicera med presentatören. Standardpresentatören implementerar IMFVideoDisplayControl- gränssnitt för detta ändamål. Du kan implementera det här gränssnittet eller definiera ditt eget. Programmet hämtar gränssnitt från presentatören genom att anropa IMFGetService::GetService på EVR. När tjänstens GUID är MR_VIDEO_RENDER_SERVICEskickar EVR GetService- begäran till presentatören.

Implementera IMFVideoDeviceID

Gränssnittet IMFVideoDeviceID innehåller en metod, GetDeviceID, som returnerar ett enhets-GUID. Enhetens GUID säkerställer att presentatören och mixern använder kompatibla tekniker. Om enhetens GUID inte matchar kan EVR inte initieras.

Standardblandaren och presentatören använder båda Direct3D 9, med enhetens GUID lika med IID_IDirect3DDevice9. Om du tänker använda din anpassade presentatör med standardblandaren måste presentatörens enhets-GUID vara IID_IDirect3DDevice9. Om du ersätter båda komponenterna kan du definiera ett nytt enhets-GUID. För resten av den här artikeln antas det att presentatören använder Direct3D 9. Här är standardimplementeringen av GetDeviceID:

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

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

Metoden bör lyckas även när presentatören stängs av.

Implementera IMFTopologyServiceLookupClient

Med IMFTopologyServiceLookupClient--gränssnittet kan presentatören hämta gränssnittspekare från EVR och från mixern på följande sätt:

  1. När EVR initierar presentatören anropas presentatörens IMFTopologyServiceLookupClient::InitServicePointers metod. Argumentet är en pekare till EVR:s IMFTopologyServiceLookup- gränssnitt.
  2. Presentatören anropar IMFTopologyServiceLookup::LookupService för att hämta gränssnittspekare från antingen EVR eller mixern.

Metoden LookupService liknar metoden IMFGetService::GetService. Båda metoderna använder ett tjänst-GUID och ett gränssnittsidentifierare (IID) som indata, men LookupService- returnerar en matris med gränssnittspekare, medan GetService returnerar en enda pekare. I praktiken kan du dock alltid ange matrisstorleken till 1. Det objekt som efterfrågas beror på tjänstens GUID:

  • Om tjänstens GUID är MR_VIDEO_RENDER_SERVICEefterfrågas EVR.
  • Om tjänstens GUID är MR_VIDEO_MIXER_SERVICEefterfrågas mixern.

I implementeringen av InitServicePointershämtar du följande gränssnitt från EVR:

EVR-gränssnitt Beskrivning
IMediaEventSink Ger ett sätt för presentatören att skicka meddelanden till EVR. Det här gränssnittet definieras i DirectShow SDK, så meddelandena följer mönstret för DirectShow-händelser, inte Media Foundation-händelser.
IMFClock Representerar EVR-klockan. Presentatören använder det här gränssnittet för att schemalägga exempel för presentation. EVR kan köras utan en klocka, så det här gränssnittet kanske inte är tillgängligt. Annars ignorerar du felkoden från LookupService.
Klockan implementerar också IMFTimer- gränssnitt. I Media Foundation-pipelinen implementerar klockan gränssnittet IMFPresentationClock. Det implementerar inte det här gränssnittet i DirectShow.

 

Hämta följande gränssnitt från mixern:

Mixer-gränssnitt Beskrivning
IMFTransform Gör att presentatören kan kommunicera med mixern.
IMFVideoDeviceID Gör att presentatören kan verifiera mixerns enhets-GUID.

 

Följande kod implementerar metoden 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;
}

När gränssnittspekarna från LookupService inte längre är giltiga anropar EVR IMFTopologyServiceLookupClient::ReleaseServicePointers. I den här metoden släpper du alla gränssnittspekare och anger att presentatörstillståndet ska stängas av:

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 anropar ReleaseServicePointers av olika skäl, bland annat:

  • Koppla från eller återansluta stift (DirectShow) eller lägga till eller ta bort strömmottagare (Media Foundation).
  • Ändra format.
  • Ställa in en ny klocka.
  • Slutlig avstängning av EVR.

Under presentatörens livslängd kan EVR anropa InitServicePointers och ReleaseServicePointers flera gånger.

Implementera IMFVideoPresenter

Gränssnittet IMFVideoPresenter ärver IMFClockStateSink och lägger till två metoder:

Metod Beskrivning
GetCurrentMediaType Returnerar medietypen för de sammansatta bildrutorna.
ProcessMessage Signalerar presentatören att utföra olika åtgärder.

 

Metoden GetCurrentMediaType returnerar presentatörens medietyp. (Mer information om hur du anger medietyp finns i Förhandlingsformat.) Medietypen returneras som en IMFVideoMediaType gränssnittspekare. I följande exempel förutsätts att presentatören lagrar medietypen som en IMFMediaType- pekare. Om du vill hämta IMFVideoMediaType--gränssnittet från medietypen anropar du 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;
}

Metoden ProcessMessage är den primära mekanismen för EVR att kommunicera med presentatören. Följande meddelanden definieras. Information om hur du implementerar varje meddelande finns i resten av det här avsnittet.

Meddelande Beskrivning
MFVP_MESSAGE_INVALIDATEMEDIATYPE Mixerns utdatamedietyp är ogiltig. Presentatören bör förhandla fram en ny medietyp med mixern. Se förhandlingsformat.
MFVP_MEDDELANDE_STARTASTREAMING Direktuppspelningen har startat. Det krävs ingen särskild åtgärd i det här meddelandet, men du kan använda den för att allokera resurser.
MFVP_MESSAGE_ENDSTREAMING Strömningen har avslutats. Frigör alla resurser som du allokerade som svar på meddelandet MFVP_MESSAGE_BEGINSTREAMING.
MFVP_MESSAGE_PROCESSINPUTNOTIFY Mixern har fått ett nytt indataexempel och kan kanske generera en ny utdataram. Presentatören bör anropa IMFTransform::P rocessOutput på mixern. Se Bearbetningsresultat.
MFVP_MESSAGE_ENDOFSTREAM Presentationen har avslutats. Se Slutet av ström.
MFVP_MESSAGE_FLUSH EVR tömmer data i sin återgivningspipeline. Presentatören bör ta bort alla videorutor som är schemalagda för presentationen.
MFVP_MESSAGE_STEP Begär att presentatören flyttar fram N bildrutor. Presentatören bör ta bort nästa N-1-bildrutor och visa N:e bildrutan. Se Frame Stepping.
MFVP_MESSAGE_CANCELSTEP Avbryter ramstegning.

 

Implementera IMFClockStateSink

Presentatören måste implementera IMFClockStateSink gränssnitt som en del av dess implementering av IMFVideoPresenter, som ärver IMFClockStateSink. EVR använder det här gränssnittet för att meddela presentatören när EVR:s klocka ändras. Mer information om klocktillstånd finns i presentationsklocka.

Här följer några riktlinjer för att implementera metoderna i det här gränssnittet. Alla metoder bör misslyckas om presentatören stängs av.

Metod Beskrivning
OnClockStart
  1. Sätt presentatörens status till startad.
  2. Om llClockStartOffset inte är PRESENTATION_CURRENT_POSITION, töm presentatörens proverkö. (Detta motsvarar att ta emot ett MFVP_MESSAGE_FLUSH meddelande.)
  3. Om en tidigare ramstegsbegäran fortfarande väntar bearbetar du begäran (se Frame Stepping). Annars kan du försöka bearbeta utdata från mixern (se Processing Output.
OnClockStop
  1. Ställ in presentatörläget till stoppat läge.
  2. Töm presentatörens kö med exempel.
  3. Avbryt alla väntande operationer i ramstegsprocessen.
OnClockPause Ställ in presentationsläget på paus.
OnClockRestart Behandla samma sak som OnClockStart men rensa inte kön med samplar.
OnClockSetRate
  1. Om hastigheten ändras från noll till ett annat värde, avbryter du ramstegningen.
  2. Lagra den nya klockfrekvensen. Klockfrekvensen påverkar när prover visas. Mer information finns i Schemaläggningsexempel.

 

Implementera IMFRateSupport

För att stödja andra uppspelningshastigheter än 1× hastighet måste presentatören implementera IMFRateSupport-gränssnittet. Här följer några riktlinjer för att implementera metoderna i det här gränssnittet. Alla metoder bör misslyckas när presentatören har stängts av. Mer information om det här gränssnittet finns i Rate Control.

Värde Beskrivning
GetSlowestRate Returnera noll för att ange ingen minsta uppspelningshastighet.
GetFastestRate För icke-tunnad uppspelning bör uppspelningshastigheten inte överskrida bildskärmens uppdateringshastighet: maximal hastighet = uppdateringshastighet (Hz)/bildfrekvens (fps). Videobildrutefrekvensen anges i presentatörens medietyp.
För uttunnad uppspelning är uppspelningshastigheten obunden; returnera värdet FLT_MAX. I praktiken är källan och avkodaren de begränsande faktorerna under den slimmade uppspelningen.
För omvänd uppspelning returnerar du motsatsen till den maximala hastigheten.
ÄrHastighetStödd Returnera MF_E_UNSUPPORTED_RATE om det absoluta värdet för flRate överskrider presentatörens maximala uppspelningshastighet. Beräkna den maximala uppspelningshastigheten enligt beskrivningen för GetFastestRate.

 

I följande exempel visas hur du implementerar metoden 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;
}

I föregående exempel anropas en hjälpmetod, GetMaxRate, för att beräkna den maximala uppspelningshastigheten framåt:

I följande exempel visas hur du implementerar metoden 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;
}

Skicka händelser till EVR

Presentatören måste meddela EVR om olika händelser. För att göra det använder den EVR:s IMediaEventSink--gränssnitt som hämtas när EVR anropar presentatörens IMFTopologyServiceLookupClient::InitServicePointers-metoden. (Gränssnittet IMediaEventSink är ursprungligen ett DirectShow-gränssnitt, men används i både DirectShow EVR och Media Foundation.) Följande kod visar hur du skickar en händelse till 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);
        }
    }

I följande tabell visas de händelser som presentatören skickar, tillsammans med händelseparametrarna.

Händelse Beskrivning
EC_COMPLETE Presentatören har slutfört återgivningen av alla bildrutor efter MFVP_MESSAGE_ENDOFSTREAM meddelandet.
  • Param1: HRESULT som anger åtgärdens status.
  • Param2: Används inte.
Mer information finns i End of Stream.
EC_DISPLAY_CHANGED Direct3D-enheten har ändrats.
  • Param1: Används inte.
  • Param2: Används inte.
Mer information finns i Hantera Direct3D-enheten.
EC_ERRORABORT Ett fel har uppstått som kräver att direktuppspelningen stoppas.
  • Param1: HRESULT som anger felet som uppstod.
  • Param2: Används inte.
EC_PROCESSING_LATENCY Anger hur lång tid det tar för presentatören att återge varje bildruta. (Valfritt.)
  • Param1: Pekare till ett konstant LONGLONG- värde som innehåller tidsmängden för att bearbeta ramen, i enheter om 100 nanosekunder.
  • Param2: Används inte.
Mer information finns i Processing Output.
EC_SAMPLE_LATENCY Anger den aktuella fördröjningstiden i återgivningsexempel. Om värdet är positivt är exemplen försenade. Om värdet är negativt ligger exemplen före schemat. (Valfritt.)
  • Param1: Pekare till ett konstant LONGLONG- värde som anger fördröjningstiden i enheter av 100 nanosekunder.
  • Param2: Används inte.
EC_SCRUB_TIME Skickas omedelbart efter EC_STEP_COMPLETE om uppspelningshastigheten är noll. Den här händelsen innehåller tidsstämpeln för den bildruta som visades.
  • Param1: De lägre 32 bitarna av tidsstämpeln.
  • Param2: Övre 32 bitar av tidsstämpeln.
Mer information finns i Frame Stepping.
EC_STEP_COMPLETE Presentatören har slutfört eller avbrutit ett ramsteg.
- Param1: Används inte.
- Param2: Används inte.
Mer information finns i Frame Stepping.
Obs! En tidigare version av dokumentationen beskrev Param1 felaktigt. Den här parametern används inte för den här händelsen.

 

Förhandlingsformat

När presentatören får ett MFVP_MESSAGE_INVALIDATEMEDIATYPE meddelande från EVR måste den ange utmatningsformatet på mixern enligt följande:

  1. Anropa IMFTransform::GetOutputAvailableType i mixern för att få en möjlig utdatatyp. Den här typen beskriver ett format som mixern kan producera med tanke på indataströmmarna och grafikenhetens videobearbetningsfunktioner.

  2. Kontrollera om presentatören kan använda den här medietypen som återgivningsformat. Här följer några saker att kontrollera, även om implementeringen kan ha egna krav:

    • Videon måste vara okomprimerad.
    • Videon får endast ha progressiva ramar. Kontrollera att attributet MF_MT_INTERLACE_MODE är lika med MFVideoInterlace_Progressive.
    • Formatet måste vara kompatibelt med Direct3D-enheten.

    Om typen inte är acceptabel går du tillbaka till steg 1 och hämtar mixerns nästa föreslagna typ.

  3. Skapa en ny medietyp som är en klon av den ursprungliga typen och ändra sedan följande attribut:

    • Ange attributet MF_MT_FRAME_SIZE lika med den bredd och höjd som du vill använda för direct3D-ytorna som du ska allokera.
    • Ange attributet MF_MT_PAN_SCAN_ENABLED till FALSE.
    • Ange attributet MF_MT_PIXEL_ASPECT_RATIO lika med PAR för displayen (vanligtvis 1:1).
    • Ange den geometriska bländaren (MF_MT_GEOMETRIC_APERTURE attribut) lika med en rektangel i Direct3D-ytan. När mixern genererar en utdataram, överförs källbilden till denna rektangel. Den geometriska bländaren kan vara lika stor som ytan, eller så kan den vara en subrektangel inom ytan. Mer information finns i Käll- och målrektanglar.
  4. Om du vill testa om mixern accepterar den ändrade utdatatypen anropar du IMFTransform::SetOutputType med flaggan MFT_SET_TYPE_TEST_ONLY. Om mixern avvisar typen går du tillbaka till steg 1 och hämtar nästa typ.

  5. Allokera en pool med Direct3D-ytor enligt beskrivningen i Allokera Direct3D-ytor. Mixern använder dessa ytor när den ritar de sammansatta videoramarna.

  6. Ange utdatatypen på mixern genom att anropa SetOutputType utan flaggor. Om det första anropet till SetOutputType lyckades i steg 4 bör metoden lyckas igen.

Om mixern får slut på typer returnerar metoden GetOutputAvailableTypeMF_E_NO_MORE_TYPES. Om presentatören inte kan hitta en lämplig utdatatyp för mixern kan strömmen inte återges. I så fall kan DirectShow eller Media Foundation prova ett annat stream-format. Därför kan presentatören få flera MFVP_MESSAGE_INVALIDATEMEDIATYPE meddelanden i följd tills en giltig typ hittas.

Mixern letterboxar automatiskt videon med hänsyn till pixelproportionen (PAR) för källan och målet. För bästa resultat bör ytbredden och höjden och den geometriska bländaren vara lika med den faktiska storlek som du vill att videon ska visas på skärmen. Följande bild illustrerar den här processen.

diagram som visar en sammansatt inramning som leder till en direct3d-yta, vilket leder till ett fönster

Följande kod visar processens disposition. Några av stegen placeras i hjälpfunktioner, vars exakta information beror på kraven för din presentatör.

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

Mer information om typer av videomedier finns i Video Media Types.

Hantera Direct3D-enheten

Presentatören skapar Direct3D-enheten och hanterar eventuella enhetsförluster under strömning. Presentatören är också värd för Direct3D-enhetshanteraren, vilket ger ett sätt för andra komponenter att använda samma enhet. Till exempel använder mixern Direct3D-enheten för att blanda underströmmar, deinterlace och utföra färgjusteringar. Avkodare kan använda Direct3D-enheten för videoaccelererad avkodning. (Mer information om videoacceleration finns i DirectX Video Acceleration 2.0.)

Utför följande steg för att konfigurera Direct3D-enheten:

  1. Skapa Direct3D-objektet genom att anropa Direct3DCreate9 eller Direct3DCreate9Ex.
  2. Skapa enheten genom att anropa IDirect3D9::CreateDevice eller IDirect3D9Ex::CreateDevice.
  3. Skapa enhetshanteraren genom att anropa DXVA2CreateDirect3DDeviceManager9.
  4. Ställ in enheten på enhetshanteraren genom att anropa IDirect3DDeviceManager9::ResetDevice.

Om en annan pipelinekomponent behöver enhetshanteraren, den anropar IMFGetService::GetService på EVR och specificerar MR_VIDEO_ACCELERATION_SERVICE för tjänstens GUID. EVR skickar begäran till presentatören. När objektet har fått pekaren IDirect3DDeviceManager9 kan det få ett handtag till enheten genom att anropa IDirect3DDeviceManager9::OpenDeviceHandle. När objektet behöver använda enheten skickas enhetshandtaget till metoden IDirect3DDeviceManager9::LockDevice, som returnerar en IDirect3DDevice9 pekare.

När enheten har skapats, om presentatören förstör enheten och skapar en ny, måste presentatören anropa ResetDevice igen. Metoden ResetDevice ogiltigförklarar alla befintliga enhetshandtag, vilket gör att LockDevice returnerar DXVA2_E_NEW_VIDEO_DEVICE. Den här felkoden signalerar till andra objekt med hjälp av enheten att de ska öppna ett nytt enhetshandtag. Mer information om hur du använder enhetshanteraren finns i Direct3D Device Manager.

Presentatören kan skapa enheten i fönsterläge eller exklusivt helskärmsläge. För fönsterläge bör du ange ett sätt för programmet att ange videofönstret. Standardpresentatören implementerar metoden IMFVideoDisplayControl::SetVideoWindow för detta ändamål. Du måste skapa enheten när presentatören först skapas. Vanligtvis känner du inte till alla enhetsparametrar just nu, som fönstret eller bakbuffertformatet. Du kan skapa en tillfällig enhet och ersätta den senare&#; Kom bara ihåg att anropa ResetDevice på enhetshanteraren.

Om du skapar en ny enhet eller om du anropar IDirect3DDevice9::Återställ eller IDirect3DDevice9Ex::ResetEx på en befintlig enhet skickar du en EC_DISPLAY_CHANGED händelse till EVR. Den här händelsen meddelar EVR att den ska omförhandla medietypen. EVR ignorerar händelseparametrarna för den här händelsen.

Allokera Direct3D-ytor

När presentatören har konfigurerat medietypen kan den allokera Direct3D-ytorna, som mixern använder för att skriva videoramarna. Ytan måste matcha presentatörens medietyp:

  • Ytformatet måste matcha medieundertypen. Om undertypen till exempel är MFVideoFormat_RGB24måste ytformatet vara D3DFMT_X8R8G8B8. Mer information om undertyper och Direct3D-format finns i videoundertyps-GUID:er.
  • Ytbredden och höjden måste matcha de dimensioner som anges i MF_MT_FRAME_SIZE attribut för medietypen.

Det rekommenderade sättet att allokera ytor beror på om programvaran körs i fönsterläge eller helskärmsläge.

Om Direct3D-enheten är i fönsterläge kan du skapa flera växlingskedjor, var och en med en enda backbuffer. Med den här metoden kan du presentera varje yta separat och oberoende, eftersom presentationen av en växlingskedja inte påverkar de andra växlingskedjorna. Mixern kan skriva data till en yta medan en annan yta är schemalagd för presentation.

Bestäm först hur många växlingskedjor som ska skapas. Minst tre rekommenderas. Gör följande för varje växlingskedja:

  1. Anropa IDirect3DDevice9::CreateAdditionalSwapChain för att skapa växlingskedjan.
  2. Anropa IDirect3DSwapChain9::GetBackBuffer för att få en pekare till växlingskedjans buffertyta.
  3. Anropa MFCreateVideoSampleFromSurface och skicka en pekare till ytan. Den här funktionen returnerar en pekare till ett videoexempelobjekt. Videoexempelobjektet implementerar IMFSample--gränssnittet, och presentatören använder detta gränssnitt för att leverera ytan till mixern när presentatören anropar mixerns IMFTransform::ProcessOutput-metoden. Mer information om videoexempelobjektet finns i videoexempel.
  4. Lagra IMFSample pekare i en kö. Presentatören hämtar prover från denna kö under bearbetningen enligt beskrivningen i Bearbetningsresultat.
  5. Behåll en referens till IDirect3DSwapChain9 pekare så att växlingskedjan inte släpps.

I exklusivt fullskärmsläge kan enheten inte ha fler än en växlingskedja. Den här växlingskedjan skapas implicit när du skapar helskärmsenheten. Växlingskedjan kan ha mer än en buffert på baksidan. Tyvärr, om du presenterar en tillbaka-buffert medan du skriver till en annan tillbaka-buffert i samma växlingskedja finns det inget enkelt sätt att samordna de två åtgärderna. Detta beror på hur Direct3D implementerar ytvändning. När du anropar Present uppdaterar grafikdrivrutinen ytpekarna i grafikminnet. Om du håller några IDirect3DSurface9 pekare när du anropar Presentpekar de på olika buffertar när Present-anropet returnerar.

Det enklaste alternativet är att skapa ett videoexempel för växlingskedjan. Om du väljer det här alternativet följer du samma steg som anges för fönsterläge. Den enda skillnaden är att provkön innehåller ett enda videoprov. Ett annat alternativ är att skapa ytor utanför skärmen och sedan kopiera dem till den bakre bufferten. De ytor som du skapar måste ha stöd för metoden IDirectXVideoProcessor::VideoProcessBlt, som mixern använder för att sammansatta utdataramarna.

Spårningsexempel

När presentatören först allokerar videoexemplen placeras de i en kö. Presentatören hämtar från kön när den behöver få en ny bildruta från mixern. När mixern har matat ut ramen flyttar presentatören exemplet till en andra kö. Den andra kön är för prover som väntar på sina schemalagda presentationstidpunkter.

För att göra det enklare att spåra status för varje exempel implementerar videoexempelobjektet IMFTrackedSample--gränssnittet. Du kan använda det här gränssnittet på följande sätt:

  1. Implementera IMFAsyncCallback--gränssnittet i presentatören.

  2. Innan du placerar ett prov i den schemalagda kön, begär du videoexempelobjektet för gränssnittet IMFTrackedSample.

  3. Anropa IMFTrackedSample::SetAllocator med en pekare till ditt callback-gränssnitt.

  4. När exemplet är klart för presentationen tar du bort det från den schemalagda kön, presenterar det och släpper alla referenser till exemplet.

  5. Exemplet anropar återanropet. (Exempelobjektet tas inte bort i det här fallet eftersom det innehåller ett referensantal på sig självt tills återanropet anropas.)

  6. I återanropet returnerar du provet till den tillgängliga kön.

En presentatör behöver inte använda IMFTrackedSample- för att spåra exempel. du kan implementera vilken teknik som helst som passar bäst för din design. En fördel med IMFTrackedSample- är att du kan flytta presentatörens schemaläggnings- och återgivningsfunktioner till hjälpobjekt, och dessa objekt behöver ingen särskild mekanism för att anropa presentatören när de släpper videoexempel eftersom exempelobjektet tillhandahåller den mekanismen.

Följande kod visar hur du ställer in återanropet:

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

I återanropet anropar du IMFAsyncResult::GetObject på det asynkrona resultatobjektet för att hämta en pekare till exemplet:

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

Bearbeta utdata

När mixern tar emot ett nytt indataexempel skickar EVR ett MFVP_MESSAGE_PROCESSINPUTNOTIFY meddelande till presentatören. Det här meddelandet anger att mixern kan ha en ny videoram att leverera. Som svar anropar presentatören IMFTransform::P rocessOutput på mixern. Om metoden lyckas schemalägger presentatören exemplet för presentationen.

Utför följande steg för att hämta utdata från mixern:

  1. Kontrollera klocktillståndet. Om klockan har pausats ignorerar du MFVP_MESSAGE_PROCESSINPUTNOTIFY meddelandet om det inte är den första videoramen. Om klockan är igång, eller om det här är den första videobildrutan, fortsätt.

  2. Hämta ett exempel från kön med tillgängliga exempel. Om kön är tom innebär det att alla allokerade exempel för närvarande är schemalagda för presentation. I så fall ignorerar du meddelandet MFVP_MESSAGE_PROCESSINPUTNOTIFY just nu. När nästa exempel blir tillgängligt upprepar du stegen som anges här.

  3. (Valfritt.) Om klockan är tillgänglig hämtar du den aktuella klocktiden (T1) genom att anropa IMFClock::GetCorrelatedTime.

  4. Ring IMFTransform::ProcessOutput på mixern. Om ProcessOutput- lyckas innehåller exemplet en videoram. Om metoden misslyckas kontrollerar du returkoden. Följande felkoder från ProcessOutput är inte kritiska fel:

    Felkod Beskrivning
    MF_E_TRANSFORM_NEED_MORE_INPUT Mixern behöver mer indata innan den kan skapa en ny utdataram.
    Om du får den här felkoden kontrollerar du om EVR har nått slutet av dataströmmen och svarar därefter enligt beskrivningen i End of Stream. Annars kan du ignorera det här MF_E_TRANSFORM_NEED_MORE_INPUT meddelandet. EVR skickar en till när mixern får mer indata.
    MF_E_TRANSFORM_STREAM_CHANGE Mixerns utdatatyp har blivit ogiltig, möjligen på grund av en formatändring uppströms.
    Om du får den här felkoden anger du presentatörens medietyp till NULL-. EVR begär ett nytt format.
    MF_E_TRANSFORM_TYPE_NOT_SET Mixern kräver en ny medietyp.
    Om du får den här felkoden omförhandlar du mixerns utdatatyp enligt beskrivningen i Förhandlingsformat.

     

    Om ProcessOutput- lyckas, fortsätt.

  5. (Valfritt.) Om klockan är tillgänglig hämtar du den aktuella klocktiden (T2). Mängden svarstid som införs av mixern är (T2 - T1). Skicka en EC_PROCESSING_LATENCY händelse med det här värdet till EVR. EVR använder det här värdet för kvalitetskontroll. Om ingen klocka är tillgänglig finns det ingen anledning att skicka händelsen EC_PROCESSING_LATENCY.

  6. (Valfritt.) Förfråga IMFTrackedSample och anropa IMFTrackedSample::SetAllocator enligt instruktionerna i Spårning av prover.

  7. Schemalägg exemplet för presentationen.

Den här stegsekvensen kan avslutas innan presentatören får utdata från mixern. För att säkerställa att inga begäranden tas bort bör du upprepa samma steg när följande inträffar:

  • Presentatörens IMFClockStateSink::OnClockStart eller IMFClockStateSink::OnClockStart-metoden anropas. Detta hanterar fallet där mixern ignorerar indata eftersom klockan är pausad (steg 1).
  • IMFTrackedSample återanrop anropas. Detta hanterar fallet där mixern tar emot indata men alla presentatörens videoexempel används (steg 2).

I nästa flera kodexempel visas de här stegen mer detaljerat. Presentatören anropar metoden ProcessInputNotify (visas i följande exempel) när den får meddelandet 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;
}

Den här ProcessInputNotify-metoden anger en boolesk flagga för att registrera det faktum att mixern har nya indata. Sedan anropas metoden ProcessOutputLoop, som visas i nästa exempel. Den här metoden försöker hämta så många exempel som möjligt från mixern:

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

Metoden ProcessOutput, som visas i nästa exempel, försöker få en enda videoram från mixern. Om ingen videoram är tillgänglig returnerar ProcessSample S_FALSE eller en felkod, som avbryter loopen i ProcessOutputLoop. Det mesta av arbetet utförs i metoden 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;
}

Några kommentarer om det här exemplet:

  • Variabeln m_SamplePool antas vara ett samlingsobjekt som innehåller en kö av tillgängliga videosamplar. Objektets GetSample-metod returnerar MF_E_SAMPLEALLOCATOR_EMPTY om kön är tom.
  • Om mixerns ProcessOutput--metod returnerar MF_E_TRANSFORM_NEED_MORE_INPUT innebär det att mixern inte kan producera mer utdata, så presentatören rensar flaggan m_fSampleNotify.
  • Metoden TrackSample, som anger IMFTrackedSample återanrop, visas i avsnittet Spåra Exempel.

Ommåla ramar

Ibland kan presentatören behöva måla om den senaste videoramen. Standardpresentatören ommålar till exempel ramen i följande situationer:

Använd följande steg för att begära att mixern återskapar den senaste ramen:

  1. Hämta ett videoexempel från kön.
  2. Utför en förfrågan på exemplet för IMFDesiredSample-gränssnittet.
  3. Anropa IMFDesiredSample::SetDesiredSampleTimeAndDuration. Ange tidsstämpeln för den senaste videoramen. (Du måste cachelagra det här värdet och uppdatera det för varje bildruta.)
  4. Anropa ProcessOutput på mixern.

När du ommålar en ram kan du ignorera presentationsklockan och presentera ramen omedelbart.

Schemaläggningsexempel

Videoramar kan nå EVR när som helst. Presentatören ansvarar för att presentera varje bildruta vid rätt tidpunkt, baserat på bildrutans tidsstämpel. När presentatören får ett nytt prov från mixern, lägger den provet i det schemalagda kösystemet. I en separat tråd hämtar presentatören kontinuerligt det första exemplet från början av kön och avgör vad som ska göras.

  • Presentera exemplet.
  • Behåll exemplet i kön eftersom det är tidigt.
  • Ta bort exemplet eftersom det är sent. Även om du bör undvika att släppa bildrutor om möjligt kan du behöva göra det om presentatören ständigt halkar efter.

Om du vill hämta tidsstämpeln för en videoram anropar du IMFSample::GetSampleTime på videoexemplet. Tidsstämpeln är relativ till EVR:s presentationsklocka. För att få den aktuella klocktiden anropar du IMFClock::GetCorrelatedTime. Om EVR inte har någon presentationsklocka, eller om ett exempel inte har någon tidsstämpel, kan du presentera exemplet direkt efter att du har fått det.

Om du vill hämta varaktigheten för varje exempel anropar du IMFSample::GetSampleDuration. Om exemplet inte har någon varaktighet kan du använda funktionen MFFrameRateToAverageTimePerFrame för att beräkna varaktigheten från bildrutefrekvensen.

Tänk på följande när du schemalägger exempel:

  • Om uppspelningshastigheten är snabbare eller långsammare än normalt, går klockan också snabbare eller långsammare. Det innebär att tidsstämpeln för ett prov alltid ger rätt måltid i förhållande till presentationsklockan. Men om du översätter presentationstider till en annan klocktid (till exempel prestandaräknaren med hög upplösning) måste du skala tiderna baserat på klockhastigheten. Om klockhastigheten ändras anropar EVR presentatörens IMFClockStateSink::OnClockSetRate-metoden.
  • Uppspelningshastigheten kan vara negativ för omvänd uppspelning. När uppspelningshastigheten är negativ körs presentationsklockan bakåt. Med andra ord inträffar tiden N + 1 före tiden N.

I följande exempel beräknas hur tidigt eller sent ett exempel är i förhållande till presentationsklockan:

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

Presentationsklockan drivs vanligtvis av systemklockan eller ljudåtergivningen. (Ljudåtergivningen härleder tiden från den hastighet med vilken ljudkortet förbrukar ljud.) I allmänhet synkroniseras inte presentationsklockan med bildskärmens uppdateringshastighet.

Om Direct3D-presentationsparametrarna anger D3DPRESENT_INTERVAL_DEFAULT eller D3DPRESENT_INTERVAL_ONE för presentationsintervallet väntar operationen Present på skärmens lodräta retrace. Det här är ett enkelt sätt att förhindra rivning, men minskar noggrannheten för schemaläggningsalgoritmen. Om presentationsintervallet däremot är D3DPRESENT_INTERVAL_IMMEDIATEkörs metoden Present omedelbart, vilket orsakar avbrott om inte schemaläggningsalgoritmen är tillräckligt exakt för att anropa Presentera endast under den lodräta retrace-perioden.

Följande funktioner kan hjälpa dig att få korrekt tidsinformation:

  • IDirect3DDevice9::GetRasterStatus returnerar information om rastern, inklusive den aktuella genomsökningslinjen och om rastern är i den lodräta tomma perioden.
  • DwmGetCompositionTimingInfo returnerar tidsinformation för skrivbordsfönsterhanteraren. Den här informationen är användbar om skrivbordskomposition är aktiverad.

Presentera exempel

Det här avsnittet förutsätter att du har skapat en separat växlingskedja för varje yta enligt beskrivningen i Allokera Direct3D-ytor. Om du vill presentera ett exempel hämtar du växlingskedjan från videoexemplet på följande sätt:

  1. Anropa IMFSample::GetBufferByIndex på videoexemplet för att hämta bufferten.
  2. Fråga bufferten för IMFGetService--gränssnittet.
  3. Anropa IMFGetService::GetService för att hämta IDirect3DSurface9-gränssnittet för Direct3D-ytan. (Du kan kombinera det här steget och föregående steg till ett genom att anropa MFGetService.)
  4. Anropa IDirect3DSurface9::GetContainer på ytan för att få en pekare till växlingskedjan.
  5. Anropa IDirect3DSwapChain9::Present i swap-kedjan.

Följande kod visar följande steg:

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

Käll- och målrektanglar

Den källrektangeln är den del av videobilden som ska visas. Den definieras i förhållande till ett normaliserat koordinatsystem, där hela videoramen upptar en rektangel med koordinaterna {0, 0, 1, 1}. Den målrektangeln är det område inom målytan där videoramen ritas. Med standardpresentatören kan ett program ange dessa rektanglar genom att anropa IMFVideoDisplayControl::SetVideoPosition.

Det finns flera alternativ för att tillämpa käll- och målrektanglar. Det första alternativet är att låta mixern tillämpa dem:

  • Ange källrektangeln med hjälp av attributet VIDEO_ZOOM_RECT. Mixern applicerar källrektangeln när videon blir blit på målytan. Mixerns standardrektangeln för källan är hela ramen.
  • Ange målrektangeln som geometrisk öppning i mixerns utgångstyp. Mer information finns i förhandlingsformat.

Det andra alternativet är att tillämpa rektanglarna när du IDirect3DSwapChain9::Present genom att ange pSourceRect och pDestRect parametrar i metoden Present. Du kan kombinera dessa alternativ. Du kan till exempel ange källrektangeln på mixern men använda målrektangeln i metoden Present.

Om programmet ändrar målrektangeln eller ändrar storlek på fönstret kan du behöva allokera nya ytor. I så fall måste du vara noga med att synkronisera den här åtgärden med din schemaläggningstråd. Rensa schemaläggningskön och ta bort de gamla proverna innan du allokerar nya ytor.

Slut på Stream

När varje indataström på EVR har avslutats skickar EVR ett MFVP_MESSAGE_ENDOFSTREAM meddelande till presentatören. När du får meddelandet kan det dock finnas några videorutor kvar att bearbeta. Innan du svarar på slutströmmeddelandet måste du tömma alla utdata från mixern och presentera alla återstående bildrutor. När den sista ramen har presenterats skickar du en EC_COMPLETE händelse till EVR.

I nästa exempel visas en metod som skickar händelsen EC_COMPLETE om olika villkor uppfylls. Annars returneras S_OK utan att händelsen skickas:

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

Den här metoden kontrollerar följande tillstånd:

  • Om m_fSampleNotify variabeln är TRUE-innebär det att mixern har en eller flera bildrutor som inte har bearbetats ännu. (Mer information finns i Processing Output.)
  • Variabeln m_fEndStreaming är en boolesk flagga vars ursprungliga värde FALSE. Presentatören ställer in flaggan på TRUE- när EVR skickar MFVP_MESSAGE_ENDOFSTREAM-meddelandet.
  • Metoden AreSamplesPending antas returnera TRUE- så länge en eller flera bildrutor väntar i den schemalagda kön.

I metoden IMFVideoPresenter::P rocessMessage anger du m_fEndStreaming till TRUE och anropar CheckEndOfStream när EVR skickar MFVP_MESSAGE_ENDOFSTREAM meddelande:

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

Anropa dessutom CheckEndOfStream om mixerns IMFTransform::ProcessOutput-metoden returnerar MF_E_TRANSFORM_NEED_MORE_INPUT. Den här felkoden anger att mixern inte har fler indataexempel (se Processing Output).

Bildrutestegning

EVR är utformat för att stödja ramstegning i DirectShow och skrollning i Media Foundation. Ramsteg och rensning är begreppsmässigt lika. I båda fallen begär programmet en videoruta i taget. Internt använder presentatören samma mekanism för att implementera båda funktionerna.

Bildrutestegning i DirectShow fungerar så här:

  • Programmet anropar IVideoFrameStep::Step. Antalet steg anges i parametern dwSteps. EVR skickar ett MFVP_MESSAGE_STEP meddelande till presentatören, där meddelandeparametern (ulParam) är antalet steg.
  • Om programmet anropar IVideoFrameStep::CancelStep eller ändrar graftillståndet (körs, pausas eller stoppas) skickar EVR ett MFVP_MESSAGE_CANCELSTEP meddelande.

Rensning i Media Foundation fungerar på följande sätt:

  • Programmet anger uppspelningshastigheten till noll genom att anropa IMFRateControl::SetRate.
  • För att rendera en ny bildruta anropar programmet IMFMediaSession::Start med önskad position. EVR skickar ett meddelande MFVP_MESSAGE_STEP med ulParam som är lika med 1.
  • Om du vill sluta rensa ställer programmet in uppspelningshastigheten till ett värde som inte är noll. EVR skickar meddelandet MFVP_MESSAGE_CANCELSTEP.

När MFVP_MESSAGE_STEP meddelandet har tagits emot väntar presentatören på att målramen ska anlända. Om antalet steg är Nkastar presentatören bort nästa (N - 1) prov och presenterar N:e provet. När presentatören slutför ramsteget skickar den en EC_STEP_COMPLETE händelse till EVR med lParam1 inställd på FALSE. Om uppspelningshastigheten är noll skickar presentatören dessutom en EC_SCRUB_TIME händelse. Om EVR avbryter bildramssteget medan en bildramstegsåtgärd fortfarande är under behandling skickar presentatören en EC_STEP_COMPLETE händelse med lParam1 inställd på TRUE-.

Programmet kan rama in steg eller skrubba flera gånger, så presentatören kan ta emot flera MFVP_MESSAGE_STEP meddelanden innan det får ett MFVP_MESSAGE_CANCELSTEP meddelande. Presentatören kan också ta emot meddelandet MFVP_MESSAGE_STEP innan klockan startar eller när klockan körs.

Implementera bildruta för steg

I det här avsnittet beskrivs en algoritm för att utföra ramstegning. Algoritmen för ramstegning använder följande variabler:

  • step_count. Ett osignerat heltal som anger antalet steg i den aktuella ramstegsåtgärden.

  • step_queue. En kö med IMFSampel pekare.

  • step_state. Presentatören kan när som helst vara i något av följande tillstånd vad gäller stegvis visning av bildrutor:

    Stat Beskrivning
    INGEN_STEGRING Inte stegvis uppspelning.
    VÄNTAN Presentatören har fått meddelandet MFVP_MESSAGE_STEP, men klockan har inte startats.
    VÄNTAR Presentatören har fått meddelandet MFVP_MESSAGE_STEP och klockan har startat, men presentatören väntar på att få målbilden.
    SCHEMALAGD Presentatören har tagit emot målramen och har schemalagt den för presentation, men ramen har inte presenterats.
    KOMPLETT Presentatören har presenterat målramen och skickat EC_STEP_COMPLETE-händelsen och väntar på nästa MFVP_MESSAGE_STEP eller MFVP_MESSAGE_CANCELSTEP-meddelande.

     

    Dessa tillstånd är oberoende av de föredragshållartillstånd som anges i avsnittet Föredragshållartillstånd.

Följande procedurer definieras för ramstegsalgoritmen:

PrepareFrameStep-procedur

  1. Öka step_count.
  2. Ställ in step_state på VÄNTANDE.
  3. Om klockan körs anropar du StartFrameStep.

StartFrameStep-procedur

  1. Om step_state är lika med VÄNTAR, sätt step_state till PÅGÅR. För varje exempel i step_queueanropar du DeliverFrameStepSample.
  2. Om step_state är lika med NOT_STEPPING tar du bort alla exempel från step_queue och schemalägger dem för presentation.

CompleteFrameStep-procedur

  1. Ställ in step_state till SLUTFÖRD.
  2. Skicka händelsen EC_STEP_COMPLETE med lParam1 = FALSE.
  3. Om klockfrekvensen är noll skickar du EC_SCRUB_TIME händelsen med exempeltiden.

DeliverFrameStepSample-procedur

  1. Om klockfrekvensen är noll och provtagningstid + provtagningsvaraktighet<klocktid, kassera provet. Utgång.
  2. Om step_state är lika med SCHEDULED eller COMPLETE lägger du till exemplet i step_queue. Utgång.
  3. Minska step_count.
  4. Om step_count> 0, kassera provet. Utgång.
  5. Om step_state är lika med WAITING lägger du till exemplet i step_queue. Utgång.
  6. Schemalägg exemplet för presentationen.
  7. Ange step_state till SCHEMALAGD.

CancelFrameStep-procedur

  1. Ange step_state till NOT_STEPPING
  2. Återställ step_count till noll.
  3. Om det tidigare värdet för step_state var WAITING, PENDING eller SCHEDULED skickar du EC_STEP_COMPLETE med lParam1 = TRUE.

Anropa dessa procedurer på följande sätt:

Föredragshållarmeddelande eller -metod Förfarande
MFVP_MESSAGE_STEP meddelande PrepareFrameStep
MFVP_MESSAGE_STEP meddelande CancelStep
IMFClockStateSink::OnClockStart StartFrameStep
IMFClockStateSink::OnClockRestart StartFrameStep
IMFTrackedSample återanrop CompleteFrameStep
IMFClockStateSink::OnClockStop CancelFrameStep
IMFClockStateSink::OnClockSetRate CancelFrameStep

 

Följande flödesdiagram visar steg-för-steg-procedurerna för ramstegning.

flödesdiagram som visar sökvägar som börjar med mfvp-message-step och mfvp-message-processinputnotify och slutar vid

Ställa in presentatören på EVR

När du har implementerat presentatören är nästa steg att konfigurera EVR att använda den.

Ställa in presentatören i DirectShow

I ett DirectShow-program ställer du in presentatören på EVR enligt följande:

  1. Skapa EVR-filtret genom att anropa CoCreateInstance. CLSID är CLSID_EnhancedVideoRenderer.
  2. Lägg till EVR i filterdiagrammet.
  3. Skapa en instans av presentatören. Presentatören kan ha stöd för standardskapande av COM-objekt via IClassFactory, men detta är inte obligatoriskt.
  4. Fråga EVR-filtret efter gränssnittet IMFVideoRenderer.
  5. Ring IMFVideoRenderer::InitializeRenderer.

Ställa in presentatören i Media Foundation

I Media Foundation har du flera alternativ, beroende på om du skapar EVR-mediemottagaren eller EVR-aktiveringsobjektet. Mer information om aktiveringsobjekt finns i aktiveringsobjekt.

Gör följande för EVR-medieutgången:

  1. Anropa MFCreateVideoRenderer för att skapa mediesänkan.
  2. Skapa en instans av presentatören.
  3. Fråga EVR-mediemottagaren efter gränssnittet IMFVideoRenderer.
  4. Samtal IMFVideoRenderer::InitializeRenderer.

Gör följande för EVR-aktiveringsobjektet:

  1. Anropa MFCreateVideoRendererActivate för att skapa aktiveringsobjektet.

  2. Ange något av följande attribut för aktiveringsobjektet:

    Attribut Beskrivning
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE Pekare till ett aktiveringsobjekt för presentatören.
    Med den här flaggan måste du ange ett aktiveringsobjekt för presentatören. Aktiveringsobjektet måste implementera gränssnittet IMFActivate.
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID för presentatören.
    Med den här flaggan måste presentatören ha stöd för standardskapande av COM-objekt via IClassFactory-.

     

  3. Du kan också ange attributet MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS för aktiveringsobjektet.

förbättrad videoåtergivning

EVRPresenter Exempel