Bagikan melalui


Cara Menulis Penyaji EVR

[Komponen yang dijelaskan di halaman ini, Enhanced Video Renderer, adalah fitur warisan. Ini telah digantikan oleh Simple Video Renderer (SVR) yang diekspos melalui komponen MediaPlayer dan IMFMediaEngine . Untuk memutar konten video, Anda harus mengirim data ke salah satu komponen ini dan memungkinkan mereka membuat instans perender video baru. Komponen-komponen ini telah dioptimalkan untuk Windows 10 dan Windows 11. Microsoft sangat menyarankan agar kode baru menggunakan MediaPlayer atau API IMFMediaEngine tingkat bawah untuk memutar media video di Windows alih-alih EVR, jika memungkinkan. Microsoft menyarankan agar kode yang ada yang menggunakan API warisan ditulis ulang untuk menggunakan API baru jika memungkinkan.]

Artikel ini menjelaskan cara menulis penyaji kustom untuk penyaji video yang ditingkatkan (EVR). Penyaji kustom dapat digunakan dengan DirectShow dan Media Foundation; antarmuka dan model objek sama untuk kedua teknologi, meskipun urutan operasi yang tepat mungkin bervariasi.

Contoh kode dalam topik ini diadaptasi dari Sampel EVRPresenter, yang disediakan di Windows SDK.

Topik ini berisi bagian berikut:

Prasyarat

Sebelum menulis penyaji kustom, Anda harus terbiasa dengan teknologi berikut:

  • Perender video yang disempurnakan. Lihat Perender Video yang Disempurnakan.
  • Grafik Direct3D. Anda tidak perlu memahami grafik 3-D untuk menulis penyaji, tetapi Anda harus tahu cara membuat perangkat Direct3D dan mengelola permukaan Direct3D. Jika Anda tidak terbiasa dengan Direct3D, baca bagian "Perangkat Direct3D" dan "Sumber Daya Direct3D" dalam dokumentasi DirectX Graphics SDK.
  • Grafik filter DirectShow atau alur Media Foundation, tergantung pada teknologi mana yang akan digunakan aplikasi Anda untuk merender video.
  • Transformasi Media Foundation. Mixer EVR adalah transformasi Media Foundation, dan penyaji memanggil metode langsung pada mixer.
  • Menerapkan objek COM. Penyaji adalah objek COM berulir bebas dalam proses.

Model Objek Penyaji

Bagian ini berisi gambaran umum model objek penyaji dan antarmuka.

Aliran Data Di Dalam EVR

EVR menggunakan dua komponen plug-in untuk merender video: mixer dan penyaji. Mixer memadukan streaming video dan mendeinterlaces video jika diperlukan. Penyaji menggambar (atau menyajikan) video ke tampilan dan jadwal saat setiap bingkai digambar. Aplikasi dapat mengganti salah satu objek ini dengan implementasi kustom.

EVR memiliki satu atau beberapa aliran input, dan mixer memiliki jumlah aliran input yang sesuai. Stream 0 selalu menjadi aliran referensi. Aliran lainnya adalah sub-aliran, yang dicampur alfa mixer ke aliran referensi. Aliran referensi menentukan kecepatan bingkai master untuk video yang dikomposit. Untuk setiap bingkai referensi, mixer mengambil bingkai terbaru dari setiap substream, alpha-blends ke bingkai referensi, dan menghasilkan satu bingkai yang dikomposit. Mixer juga melakukan deinterlacing dan konversi warna dari YUV ke RGB jika diperlukan. EVR selalu memasukkan mixer ke dalam alur video, terlepas dari jumlah aliran input atau format video. Gambar berikut mengilustrasikan proses ini.

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

Penyaji melakukan tugas-tugas berikut:

  • Mengatur format output pada mixer. Sebelum streaming dimulai, penyaji mengatur jenis media pada aliran output mixer. Jenis media ini mendefinisikan format gambar yang dikomposisikan.
  • Membuat perangkat Direct3D.
  • Mengalokasikan permukaan Direct3D. Mixer memadukan bingkai yang disusam ke permukaan ini.
  • Mendapatkan output dari mixer.
  • Menjadwalkan saat bingkai disajikan. EVR menyediakan jam presentasi, dan penyaji menjadwalkan bingkai sesuai dengan jam ini.
  • Menyajikan setiap bingkai menggunakan Direct3D.
  • Melakukan langkah bingkai dan menggosok.

Status Penyaji

Kapan saja, penyaji berada di salah satu status berikut:

  • Dimulai. Jam presentasi EVR sedang berjalan. Penyaji menjadwalkan bingkai video untuk presentasi saat tiba.
  • Dijeda. Jam presentasi ditangguhkan. Penyaji tidak menyajikan sampel baru tetapi mempertahankan antrean sampel terjadwalnya. Jika sampel baru diterima, penyaji menambahkannya ke antrean.
  • Dihentikan. Jam presentasi dihentikan. Penyaji membuang sampel apa pun yang dijadwalkan.
  • Matikan. Penyaji merilis sumber daya apa pun yang terkait dengan streaming, seperti permukaan Direct3D. Ini adalah status awal penyaji, dan status akhir sebelum penyaji dihancurkan.

Dalam contoh kode dalam topik ini, status penyaji diwakili oleh enumerasi:

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

Beberapa operasi tidak valid saat penyaji dalam status matikan. Contoh kode memeriksa status ini dengan memanggil metode pembantu:

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

Antarmuka Penyaji

Penyaji diperlukan untuk mengimplementasikan antarmuka berikut:

Antarmuka Deskripsi
IMFClockStateSink Memberi tahu penyaji ketika jam EVR berubah status. Lihat Menerapkan IMFClockStateSink.
IMFGetService Menyediakan cara bagi aplikasi dan komponen lain dalam alur untuk mendapatkan antarmuka dari penyaji.
IMFTopologyServiceLookupClient Memungkinkan penyaji untuk mendapatkan antarmuka dari EVR atau mixer. Lihat Menerapkan IMFTopologyServiceLookupClient.
IMFVideoDeviceID Memastikan bahwa penyaji dan mixer menggunakan teknologi yang kompatibel. Lihat Menerapkan IMFVideoDeviceID.
IMFVideoPresenter Memproses pesan dari EVR. Lihat Menerapkan IMFVideoPresenter.

 

Antarmuka berikut bersifat opsional:

Antarmuka Deskripsi
IEVRTrustedVideoPlugin Memungkinkan penyaji bekerja dengan media yang dilindungi. Terapkan antarmuka ini jika penyaji Anda adalah komponen tepercaya yang dirancang untuk bekerja di jalur media yang dilindungi (PMP).
IMFRateSupport Melaporkan rentang laju pemutaran yang didukung penyaji. Lihat Menerapkan IMFRateSupport.
IMFVideoPositionMapper Peta koordinat pada bingkai video output untuk berkoordinasi pada bingkai video input.
IQualProp Melaporkan informasi performa. EVR menggunakan informasi ini untuk manajemen kontrol kualitas. Antarmuka ini didokumenkan dalam DirectShow SDK.

 

Anda juga dapat menyediakan antarmuka untuk aplikasi untuk berkomunikasi dengan penyaji. Penyaji standar mengimplementasikan antarmuka IMFVideoDisplayControl untuk tujuan ini. Anda dapat menerapkan antarmuka ini atau menentukan antarmuka Anda sendiri. Aplikasi ini mendapatkan antarmuka dari penyaji dengan memanggil IMFGetService::GetService pada EVR. Ketika GUID layanan MR_VIDEO_RENDER_SERVICE, EVR meneruskan permintaan GetService ke penyaji.

Menerapkan IMFVideoDeviceID

Antarmuka IMFVideoDeviceID berisi satu metode, GetDeviceID, yang mengembalikan GUID perangkat. GUID perangkat memastikan bahwa penyaji dan mixer menggunakan teknologi yang kompatibel. Jika GUID perangkat tidak cocok, EVR gagal diinisialisasi.

Mixer dan penyaji standar menggunakan Direct3D 9, dengan GUID perangkat sama dengan IID_IDirect3DDevice9. Jika Anda ingin menggunakan penyaji kustom Anda dengan mixer standar, GUID perangkat penyaji harus IID_IDirect3DDevice9. Jika Anda mengganti kedua komponen, Anda dapat menentukan GUID perangkat baru. Untuk sisa artikel ini, diasumsikan bahwa penyaji menggunakan Direct3D 9. Berikut adalah implementasi standar GetDeviceID:

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

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

Metode harus berhasil bahkan ketika penyaji dimatikan.

Menerapkan IMFTopologyServiceLookupClient

Antarmuka IMFTopologyServiceLookupClient memungkinkan penyaji mendapatkan penunjuk antarmuka dari EVR dan dari mixer sebagai berikut:

  1. Ketika EVR menginisialisasi penyaji, EVR memanggil metode IMFTopologyServiceLookupClient::InitServicePointers penyaji. Argumen adalah penunjuk ke antarmuka IMFTopologyServiceLookup EVR.
  2. Penyaji memanggil IMFTopologyServiceLookup::LookupService untuk mendapatkan penunjuk antarmuka dari EVR atau mixer.

Metode LookupService mirip dengan metode IMFGetService::GetService. Kedua metode mengambil GUID layanan dan pengidentifikasi antarmuka (IID) sebagai input, tetapi LookupService mengembalikan array pointer antarmuka, sementara GetService mengembalikan satu pointer. Namun, dalam praktiknya, Anda selalu dapat mengatur ukuran array ke 1. Objek yang dikueri tergantung pada GUID layanan:

  • Jika GUID layanan MR_VIDEO_RENDER_SERVICE, EVR akan dikueri.
  • Jika GUID layanan MR_VIDEO_MIXER_SERVICE, mixer akan dikueri.

Dalam implementasi InitServicePointers Anda, dapatkan antarmuka berikut dari EVR:

Antarmuka EVR Deskripsi
IMediaEventSink Menyediakan cara bagi penyaji untuk mengirim pesan ke EVR. Antarmuka ini didefinisikan dalam DirectShow SDK, sehingga pesan mengikuti pola untuk peristiwa DirectShow, bukan peristiwa Media Foundation.
IMFClock Mewakili jam EVR. Penyaji menggunakan antarmuka ini untuk menjadwalkan sampel untuk presentasi. EVR dapat berjalan tanpa jam, sehingga antarmuka ini mungkin tidak tersedia. Jika tidak, abaikan kode kesalahan dari LookupService.
Jam juga mengimplementasikan antarmuka IMFTimer . Dalam alur Media Foundation, jam mengimplementasikan antarmuka IMFPresentationClock . Ini tidak mengimplementasikan antarmuka ini di DirectShow.

 

Dapatkan antarmuka berikut dari mixer:

Antarmuka Mixer Deskripsi
IMFTransform Memungkinkan penyaji untuk berkomunikasi dengan mixer.
IMFVideoDeviceID Memungkinkan penyaji memvalidasi GUID perangkat mixer.

 

Kode berikut mengimplementasikan metode 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;
}

Ketika pointer antarmuka yang diperoleh dari LookupService tidak lagi valid, EVR memanggil IMFTopologyServiceLookupClient::ReleaseServicePointers. Di dalam metode ini, lepaskan semua penunjuk antarmuka dan atur status penyaji untuk mematikan:

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 memanggil ReleaseServicePointers karena berbagai alasan, termasuk:

  • Memutuskan atau menyambungkan kembali pin (DirectShow), atau menambahkan atau menghapus sink aliran (Media Foundation).
  • Mengubah format.
  • Mengatur jam baru.
  • Penonaktifan akhir EVR.

Selama masa pakai penyaji, EVR mungkin memanggil InitServicePointers dan ReleaseServicePointers beberapa kali.

Menerapkan IMFVideoPresenter

Antarmuka IMFVideoPresenter mewarisi IMFClockStateSink dan menambahkan dua metode:

Metode Deskripsi
GetCurrentMediaType Mengembalikan jenis media dari bingkai video yang disusam.
ProcessMessage Memberi sinyal kepada penyaji untuk melakukan berbagai tindakan.

 

Metode GetCurrentMediaType mengembalikan jenis media penyaji. (Untuk detail tentang pengaturan jenis media, lihat Menegosiasikan Format.) Jenis media dikembalikan sebagai penunjuk antarmuka IMFVideoMediaType. Contoh berikut mengasumsikan bahwa penyaji menyimpan jenis media sebagai penunjuk IMFMediaType. Untuk mendapatkan antarmuka IMFVideoMediaType dari jenis media, panggil 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;
}

Metode ProcessMessage adalah mekanisme utama bagi EVR untuk berkomunikasi dengan penyaji. Pesan berikut ditentukan. Detail penerapan setiap pesan diberikan di sisa topik ini.

Pesan Deskripsi
MFVP_MESSAGE_INVALIDATEMEDIATYPE Jenis media output mixer tidak valid. Penyaji harus menegosiasikan jenis media baru dengan mixer. Lihat Menegosiasikan Format.
MFVP_MESSAGE_BEGINSTREAMING Streaming telah dimulai. Tidak ada tindakan tertentu yang diperlukan oleh pesan ini, tetapi Anda dapat menggunakannya untuk mengalokasikan sumber daya.
MFVP_MESSAGE_ENDSTREAMING Streaming telah berakhir. Rilis sumber daya apa pun yang Anda alokasikan sebagai respons terhadap pesan MFVP_MESSAGE_BEGINSTREAMING .
MFVP_MESSAGE_PROCESSINPUTNOTIFY Mixer telah menerima sampel input baru dan mungkin dapat menghasilkan bingkai output baru. Penyaji harus memanggil IMFTransform::P rocessOutput pada mixer. Lihat Output Pemrosesan.
MFVP_MESSAGE_ENDOFSTREAM Presentasi telah berakhir. Lihat Akhir Aliran.
MFVP_MESSAGE_FLUSH EVR membersihkan data dalam alur penyajiannya. Penyaji harus membuang bingkai video apa pun yang dijadwalkan untuk presentasi.
MFVP_MESSAGE_STEP Meminta penyaji untuk melangkah maju bingkai N. Penyaji harus membuang bingkai N-1 berikutnya dan menampilkan bingkai Nth. Lihat Melangkah Bingkai.
MFVP_MESSAGE_CANCELSTEP Membatalkan langkah bingkai.

 

Menerapkan IMFClockStateSink

Penyaji harus menerapkan antarmuka IMFClockStateSink sebagai bagian dari implementasi IMFVideoPresenter, yang mewarisi IMFClockStateSink. EVR menggunakan antarmuka ini untuk memberi tahu penyaji setiap kali jam EVR berubah status. Untuk informasi selengkapnya tentang status jam, lihat Jam Presentasi.

Berikut adalah beberapa panduan untuk menerapkan metode dalam antarmuka ini. Semua metode harus gagal jika penyaji dimatikan.

Metode Deskripsi
OnClockStart
  1. Atur status penyaji ke dimulai.
  2. Jika llClockStartOffset tidak PRESENTATION_CURRENT_POSITION, bersihkan antrean sampel penyaji. (Ini setara dengan menerima MFVP_MESSAGE_FLUSH pesan.)
  3. Jika permintaan langkah bingkai sebelumnya masih tertunda, proses permintaan (lihat Langkah Bingkai). Jika tidak, coba proses output dari mixer (lihat Output Pemrosesan.
OnClockStop
  1. Atur status penyaji untuk dihentikan.
  2. Bersihkan antrean sampel penyaji.
  3. Batalkan operasi langkah bingkai yang tertunda.
OnClockPause Atur status penyaji ke dijeda.
OnClockRestart Perlakukan sama seperti OnClockStart tetapi jangan buang antrean sampel.
OnClockSetRate
  1. Jika tarif berubah dari nol ke nonzero, batalkan langkah bingkai.
  2. Simpan laju jam baru. Laju jam memengaruhi saat sampel disajikan. Untuk informasi selengkapnya, lihat Sampel Penjadwalan.

 

Menerapkan IMFRateSupport

Untuk mendukung laju pemutaran selain 1× kecepatan, penyaji harus mengimplementasikan antarmuka IMFRateSupport . Berikut adalah beberapa panduan untuk menerapkan metode dalam antarmuka ini. Semua metode harus gagal setelah penyaji dimatikan. Untuk informasi selengkapnya tentang antarmuka ini, lihat Kontrol Laju.

Nilai Deskripsi
GetSlowestRate Mengembalikan nol untuk menunjukkan tidak ada laju pemutaran minimum.
GetFastestRate Untuk pemutaran yang tidak di-thinned, laju pemutaran tidak boleh melebihi laju refresh monitor: kecepatan refresh laju = maksimum (Hz) / kecepatan bingkai video (fps). Kecepatan bingkai video ditentukan dalam jenis media penyaji.
Untuk pemutaran yang ditipiskan, laju pemutaran tidak terbatas; mengembalikan nilai FLT_MAX. Dalam praktiknya, sumber dan dekoder akan menjadi faktor pembatas selama pemutaran yang ditipiskan.
Untuk pemutaran terbalik, kembalikan negatif dari laju maksimum.
IsRateSupported Mengembalikan MF_E_UNSUPPORTED_RATE jika nilai absolut flRate melebihi laju pemutaran maksimum penyaji. Hitung laju pemutaran maksimum seperti yang dijelaskan untuk GetFastestRate.

 

Contoh berikut menunjukkan cara menerapkan metode 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;
}

Contoh sebelumnya memanggil metode pembantu, GetMaxRate, untuk menghitung laju pemutaran terusan maksimum:

Contoh berikut menunjukkan cara menerapkan metode 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;
}

Mengirim Peristiwa ke EVR

Penyaji harus memberi tahu EVR tentang berbagai peristiwa. Untuk melakukannya, ia menggunakan antarmuka IMediaEventSink EVR, yang diperoleh ketika EVR memanggil metode IMFTopologyServiceLookupClient::InitServicePointers penyaji. (Antarmuka IMediaEventSink awalnya adalah antarmuka DirectShow, tetapi digunakan di DirectShow EVR dan Media Foundation.) Kode berikut menunjukkan cara mengirim peristiwa ke 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);
        }
    }

Tabel berikut mencantumkan peristiwa yang dikirim penyaji, bersama dengan parameter peristiwa.

Aktivitas Deskripsi
EC_COMPLETE Penyaji telah selesai merender semua bingkai setelah pesan MFVP_MESSAGE_ENDOFSTREAM.
  • Param1: HRESULT menunjukkan status operasi.
  • Param2: Tidak digunakan.
Untuk informasi selengkapnya, lihat Akhir Streaming.
EC_DISPLAY_CHANGED Perangkat Direct3D telah berubah.
  • Param1: Tidak digunakan.
  • Param2: Tidak digunakan.
Untuk informasi selengkapnya, lihat Mengelola Perangkat Direct3D.
EC_ERRORABORT Terjadi kesalahan yang mengharuskan streaming berhenti.
  • Param1: HRESULT menunjukkan kesalahan yang terjadi.
  • Param2: Tidak digunakan.
EC_PROCESSING_LATENCY Menentukan jumlah waktu yang dibutuhkan penyaji untuk merender setiap bingkai. (Opsional.)
  • Param1: Penunjuk ke nilai LONGLONG konstan yang berisi jumlah waktu untuk memproses bingkai, dalam unit 100 nanodetik.
  • Param2: Tidak digunakan.
Untuk informasi selengkapnya, lihat Output Pemrosesan.
EC_SAMPLE_LATENCY Menentukan waktu jeda saat ini dalam sampel penyajian. Jika nilainya positif, sampel berada di belakang jadwal. Jika nilainya negatif, sampel lebih awal dari jadwal. (Opsional.)
  • Param1: Penunjuk ke nilai LONGLONG konstan yang berisi waktu jeda, dalam unit 100 nanodetik.
  • Param2: Tidak digunakan.
EC_SCRUB_TIME Dikirim segera setelah EC_STEP_COMPLETE jika laju pemutaran adalah nol. Kejadian ini berisi stempel waktu bingkai yang ditampilkan.
  • Param1: 32 bit lebih rendah dari stempel waktu.
  • Param2: 32 bit atas stempel waktu.
Untuk informasi selengkapnya, lihat Melangkah Bingkai.
EC_STEP_COMPLETE Penyaji telah menyelesaikan atau membatalkan langkah bingkai.
- Param1: Tidak digunakan.
- Param2: Tidak digunakan.
Untuk informasi selengkapnya, lihat Melangkah Bingkai.
Catatan: Versi dokumentasi sebelumnya yang dijelaskan Param1 salah. Parameter ini tidak digunakan untuk kejadian ini.

 

Menegosiasikan Format

Setiap kali penyaji menerima pesan MFVP_MESSAGE_INVALIDATEMEDIATYPE dari EVR, penyaji harus mengatur format output pada mixer, sebagai berikut:

  1. Panggil IMFTransform::GetOutputAvailableType pada mixer untuk mendapatkan jenis output yang mungkin. Jenis ini menjelaskan format yang dapat dihasilkan mixer mengingat aliran input dan kemampuan pemrosesan video perangkat grafis.

  2. Periksa apakah penyaji dapat menggunakan tipe media ini sebagai format penyajiannya. Berikut adalah beberapa hal yang perlu diperiksa, meskipun implementasi Anda mungkin memiliki persyaratannya sendiri:

    • Video harus tidak dikompresi.
    • Video harus memiliki bingkai progresif saja. Periksa apakah atribut MF_MT_INTERLACE_MODE sama dengan MFVideoInterlace_Progressive.
    • Format harus kompatibel dengan perangkat Direct3D.

    Jika jenis tidak dapat diterima, kembali ke langkah 1 dan dapatkan jenis mixer yang diusulkan berikutnya.

  3. Buat jenis media baru yang merupakan klon jenis asli lalu ubah atribut berikut:

  4. Untuk menguji apakah mixer akan menerima jenis output yang dimodifikasi, panggil IMFTransform::SetOutputType dengan bendera MFT_SET_TYPE_TEST_ONLY. Jika mixer menolak jenisnya, kembali ke langkah 1 dan dapatkan jenis berikutnya.

  5. Alokasikan kumpulan permukaan Direct3D, seperti yang dijelaskan dalam Mengalokasikan Permukaan Direct3D. Mixer akan menggunakan permukaan ini ketika menggambar bingkai video yang disusam.

  6. Atur jenis output pada mixer dengan memanggil SetOutputType tanpa bendera. Jika panggilan pertama ke SetOutputType berhasil di langkah 4, metode akan berhasil lagi.

Jika mixer kehabisan jenis, metode GetOutputAvailableType mengembalikan MF_E_NO_MORE_TYPES. Jika penyaji tidak dapat menemukan jenis output yang sesuai untuk mixer, aliran tidak dapat dirender. Dalam hal ini, DirectShow atau Media Foundation mungkin mencoba format aliran lain. Oleh karena itu, penyaji mungkin menerima beberapa pesan MFVP_MESSAGE_INVALIDATEMEDIATYPE berturut-turut hingga jenis yang valid ditemukan.

Mixer secara otomatis membuat kotak surat video, dengan mempertimbangkan rasio aspek piksel (PAR) sumber dan tujuan. Untuk hasil terbaik, lebar dan tinggi permukaan dan bukaan geometris harus sama dengan ukuran aktual yang Anda inginkan agar video muncul di layar. Gambar berikut mengilustrasikan proses ini.

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

Kode berikut menunjukkan kerangka proses. Beberapa langkah ditempatkan dalam fungsi pembantu, detail yang tepat akan tergantung pada persyaratan penyaji Anda.

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

Untuk informasi selengkapnya tentang jenis media video, lihat Jenis Media Video.

Mengelola Perangkat Direct3D

Penyaji membuat perangkat Direct3D dan menangani kehilangan perangkat apa pun selama streaming. Penyaji juga menghosting manajer perangkat Direct3D, yang menyediakan cara bagi komponen lain untuk menggunakan perangkat yang sama. Misalnya, mixer menggunakan perangkat Direct3D untuk mencampur substream, deinterlace, dan melakukan penyesuaian warna. Dekode dapat menggunakan perangkat Direct3D untuk dekode yang dipercepat video. (Untuk informasi selengkapnya tentang akselerasi video, lihat Akselerasi Video DirectX 2.0.)

Untuk menyiapkan perangkat Direct3D, lakukan langkah-langkah berikut:

  1. Buat objek Direct3D dengan memanggil Direct3DCreate9 atau Direct3DCreate9Ex.
  2. Buat perangkat dengan memanggil IDirect3D9::CreateDevice atau IDirect3D9Ex::CreateDevice.
  3. Buat manajer perangkat dengan memanggil DXVA2CreateDirect3DDeviceManager9.
  4. Atur perangkat pada manajer perangkat dengan memanggil IDirect3DDeviceManager9::ResetDevice.

Jika komponen alur lain membutuhkan manajer perangkat, komponen ini memanggil IMFGetService::GetService pada EVR, menentukan MR_VIDEO_ACCELERATION_SERVICE untuk GUID layanan. EVR meneruskan permintaan ke penyaji. Setelah objek mendapatkan pointer IDirect3DDeviceManager9, objek bisa mendapatkan handel ke perangkat dengan memanggil IDirect3DDeviceManager9::OpenDeviceHandle. Ketika objek perlu menggunakan perangkat, objek meneruskan handel perangkat ke metode IDirect3DDeviceManager9::LockDevice, yang mengembalikan pointer IDirect3DDevice9.

Setelah perangkat dibuat, jika penyaji menghancurkan perangkat dan membuat yang baru, penyaji harus memanggil ResetDevice lagi. Metode ResetDevice membatalkan handel perangkat yang ada, yang menyebabkan LockDevice kembali DXVA2_E_NEW_VIDEO_DEVICE. Kode kesalahan ini memberi sinyal ke objek lain menggunakan perangkat bahwa mereka harus membuka handel perangkat baru. Untuk informasi selengkapnya tentang menggunakan manajer perangkat, lihat Direct3D Device Manager.

Penyaji dapat membuat perangkat dalam mode berjendela atau mode eksklusif layar penuh. Untuk mode berjendela, Anda harus menyediakan cara bagi aplikasi untuk menentukan jendela video. Penyaji standar mengimplementasikan metode IMFVideoDisplayControl::SetVideoWindow untuk tujuan ini. Anda harus membuat perangkat ketika penyaji pertama kali dibuat. Biasanya, Anda tidak akan tahu semua parameter perangkat saat ini, seperti format jendela atau buffer belakang. Anda dapat membuat perangkat sementara dan menggantinya nanti&#; ingatlah untuk memanggil ResetDevice di manajer perangkat.

Jika Anda membuat perangkat baru, atau jika Anda memanggil IDirect3DDevice9::Reset atau IDirect3DDevice9Ex::ResetEx pada perangkat yang ada, kirim peristiwa EC_DISPLAY_CHANGED ke EVR. Kejadian ini memberi tahu EVR untuk menegosiasikan ulang jenis media. EVR mengabaikan parameter peristiwa untuk peristiwa ini.

Mengalokasikan Permukaan Direct3D

Setelah penyaji mengatur jenis media, penyaji dapat mengalokasikan permukaan Direct3D, yang akan digunakan mixer untuk menulis bingkai video. Permukaan harus cocok dengan jenis media penyaji:

  • Format permukaan harus cocok dengan subjenis media. Misalnya, jika subjenis MFVideoFormat_RGB24, format permukaan harus D3DFMT_X8R8G8B8. Untuk informasi selengkapnya tentang subjenis dan format Direct3D, lihat GUID Subjenis Video.
  • Lebar dan tinggi permukaan harus cocok dengan dimensi yang diberikan dalam atribut MF_MT_FRAME_SIZE jenis media.

Cara yang disarankan untuk mengalokasikan permukaan tergantung pada apakah penyaji berjalan berjendela atau layar penuh.

Jika perangkat Direct3D berjendela, Anda dapat membuat beberapa rantai pertukaran, masing-masing dengan satu buffer belakang. Dengan menggunakan pendekatan ini, Anda dapat menyajikan setiap permukaan secara independen, karena menyajikan satu rantai pertukaran tidak akan mengganggu rantai pertukaran lainnya. Mixer dapat menulis data ke permukaan sementara permukaan lain dijadwalkan untuk presentasi.

Pertama, putuskan berapa banyak rantai pertukaran yang akan dibuat. Minimal tiga disarankan. Untuk setiap rantai pertukaran, lakukan hal berikut:

  1. Panggil IDirect3DDevice9::CreateAdditionalSwapChain untuk membuat rantai pertukaran.
  2. Panggil IDirect3DSwapChain9::GetBackBuffer untuk mendapatkan pointer ke permukaan buffer belakang rantai pertukaran.
  3. Panggil MFCreateVideoSampleFromSurface dan teruskan penunjuk ke permukaan. Fungsi ini mengembalikan penunjuk ke objek sampel video. Objek sampel video mengimplementasikan antarmuka IMFSample, dan penyaji menggunakan antarmuka ini untuk mengirimkan permukaan ke mixer ketika penyaji memanggil metode IMFTransform::P rocessOutput mixer. Untuk informasi selengkapnya tentang objek sampel video, lihat Sampel Video.
  4. Simpan penunjuk IMFSample dalam antrean. Penyaji akan menarik sampel dari antrean ini selama pemrosesan seperti yang dijelaskan dalam Output Pemrosesan.
  5. Simpan referensi ke pointer IDirect3DSwapChain9 sehingga rantai pertukaran tidak dirilis.

Dalam mode eksklusif layar penuh, perangkat tidak dapat memiliki lebih dari satu rantai pertukaran. Rantai pertukaran ini dibuat secara implisit saat Anda membuat perangkat layar penuh. Rantai pertukaran dapat memiliki lebih dari satu buffer belakang. Namun, sayangnya, jika Anda menyajikan satu buffer kembali saat Anda menulis ke buffer belakang lain dalam rantai pertukaran yang sama, tidak ada cara mudah untuk mengoordinasikan dua operasi. Ini karena cara Direct3D mengimplementasikan permukaan membalik. Saat Anda memanggil Sajikan, driver grafis memperbarui penunjuk permukaan dalam memori grafis. Jika Anda memegang pointer IDirect3DSurface9 saat Anda memanggil Present, mereka akan menunjuk ke buffer yang berbeda setelah panggilan Sekarang kembali.

Opsi paling sederhana adalah membuat satu sampel video untuk rantai pertukaran. Jika Anda memilih opsi ini, ikuti langkah yang sama yang diberikan untuk mode berjendela. Satu-satunya perbedaan adalah bahwa antrean sampel berisi satu sampel video. Opsi lain adalah membuat permukaan di luar layar dan kemudian menyambungkannya ke buffer belakang. Permukaan yang Anda buat harus mendukung metode IDirectXVideoProcessor::VideoProcessBlt , yang digunakan mixer untuk menyusun bingkai output.

Sampel Pelacakan

Saat penyaji pertama kali mengalokasikan sampel video, penyaji menempatkannya dalam antrean. Penyaji menarik dari antrean ini setiap kali perlu mendapatkan bingkai baru dari mixer. Setelah mixer menghasilkan bingkai, penyaji memindahkan sampel ke dalam antrean kedua. Antrean kedua adalah untuk sampel yang menunggu waktu presentasi terjadwal mereka.

Untuk mempermudah melacak status setiap sampel, objek sampel video mengimplementasikan antarmuka IMFTrackedSample . Anda dapat menggunakan antarmuka ini sebagai berikut:

  1. Terapkan antarmuka IMFAsyncCallback di penyaji Anda.

  2. Sebelum Anda menempatkan sampel dalam antrean terjadwal, kueri objek sampel video untuk antarmuka IMFTrackedSample.

  3. Panggil IMFTrackedSample::SetAllocator dengan pointer ke antarmuka panggilan balik Anda.

  4. Saat sampel siap untuk presentasi, hapus dari antrean terjadwal, sajikan, dan rilis semua referensi ke sampel.

  5. Sampel memanggil panggilan balik. (Objek sampel tidak dihapus dalam kasus ini karena menyimpan jumlah referensi pada dirinya sendiri hingga panggilan balik dipanggil.)

  6. Di dalam panggilan balik, kembalikan sampel ke antrean yang tersedia.

Penyaji tidak diharuskan menggunakan IMFTrackedSample untuk melacak sampel; Anda dapat menerapkan teknik apa pun yang paling sesuai untuk desain Anda. Salah satu keuntungan dari IMFTrackedSample adalah Anda dapat memindahkan fungsi penjadwalan dan penyaji ke dalam objek pembantu, dan objek ini tidak memerlukan mekanisme khusus untuk memanggil kembali ke penyaji saat mereka merilis sampel video karena objek sampel menyediakan mekanisme tersebut.

Kode berikut menunjukkan cara mengatur panggilan balik:

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

Dalam panggilan balik, panggil IMFAsyncResult::GetObject pada objek hasil asinkron untuk mengambil pointer ke sampel:

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

Output Pemrosesan

Setiap kali mixer menerima sampel input baru, EVR mengirimkan pesan MFVP_MESSAGE_PROCESSINPUTNOTIFY ke penyaji. Pesan ini menunjukkan bahwa mixer mungkin memiliki bingkai video baru untuk dikirimkan. Sebagai respons, penyaji memanggil IMFTransform::P rocessOutput pada mixer. Jika metode berhasil, penyaji menjadwalkan sampel untuk presentasi.

Untuk mendapatkan output dari mixer, lakukan langkah-langkah berikut:

  1. Periksa status jam. Jika jam dijeda, abaikan pesan MFVP_MESSAGE_PROCESSINPUTNOTIFY kecuali ini adalah bingkai video pertama. Jika jam berjalan, atau jika ini adalah bingkai video pertama, lanjutkan.

  2. Dapatkan sampel dari antrean sampel yang tersedia. Jika antrean kosong, itu berarti bahwa semua sampel yang dialokasikan saat ini dijadwalkan untuk disajikan. Dalam hal ini, abaikan pesan MFVP_MESSAGE_PROCESSINPUTNOTIFY saat ini. Saat sampel berikutnya tersedia, ulangi langkah-langkah yang tercantum di sini.

  3. (Opsional.) Jika jam tersedia, dapatkan waktu jam saat ini (T1) dengan memanggil IMFClock::GetCorrelatedTime.

  4. Panggil IMFTransform::P rocessOutput pada mixer. Jika ProcessOutput berhasil, sampel berisi bingkai video. Jika metode gagal, periksa kode pengembalian. Kode kesalahan berikut dari ProcessOutput bukanlah kegagalan kritis:

    Kode kesalahan Deskripsi
    MF_E_TRANSFORM_NEED_MORE_INPUT Mixer membutuhkan lebih banyak input sebelum dapat menghasilkan bingkai output baru.
    Jika Anda mendapatkan kode kesalahan ini, periksa apakah EVR telah mencapai akhir aliran dan merespons yang sesuai, seperti yang dijelaskan di Akhir Aliran. Jika tidak, abaikan pesan MF_E_TRANSFORM_NEED_MORE_INPUT ini. EVR akan mengirim yang lain ketika mixer mendapatkan lebih banyak input.
    MF_E_TRANSFORM_STREAM_CHANGE Jenis output mixer menjadi tidak valid, mungkin karena perubahan format di hulu.
    Jika Anda mendapatkan kode kesalahan ini, atur jenis media penyaji ke NULL. EVR akan meminta format baru.
    MF_E_TRANSFORM_TYPE_NOT_SET Mixer memerlukan jenis media baru.
    Jika Anda mendapatkan kode kesalahan ini, negosiasikan ulang jenis output mixer seperti yang dijelaskan dalam Menegosiasikan Format.

     

    Jika ProcessOutput berhasil, lanjutkan.

  5. (Opsional.) Jika jam tersedia, dapatkan waktu jam saat ini (T2). Jumlah latensi yang diperkenalkan oleh mixer adalah (T2 - T1). Kirim peristiwa EC_PROCESSING_LATENCY dengan nilai ini ke EVR. EVR menggunakan nilai ini untuk kontrol kualitas. Jika tidak ada jam yang tersedia, tidak ada alasan untuk mengirim peristiwa EC_PROCESSING_LATENCY .

  6. (Opsional.) Kueri sampel untuk IMFTrackedSample dan panggil IMFTrackedSample::SetAllocator seperti yang dijelaskan dalam Sampel Pelacakan.

  7. Jadwalkan sampel untuk presentasi.

Urutan langkah ini dapat berakhir sebelum penyaji mendapatkan output apa pun dari mixer. Untuk memastikan bahwa tidak ada permintaan yang dihilangkan, Anda harus mengulangi langkah yang sama ketika hal berikut terjadi:

  • Metode IMFClockStateSink::OnClockStart atau IMFClockStateSink::OnClockStart penyaji dipanggil. Ini menangani kasus di mana mixer mengabaikan input karena jam dijeda (langkah 1).
  • Panggilan balik IMFTrackedSample dipanggil. Ini menangani kasus di mana mixer menerima input tetapi semua sampel video penyaji sedang digunakan (langkah 2).

Beberapa contoh kode berikutnya menunjukkan langkah-langkah ini secara lebih rinci. Penyaji memanggil ProcessInputNotify metode (diperlihatkan dalam contoh berikut) saat mendapatkan pesan 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;
}

Metode ini ProcessInputNotify mengatur bendera Boolean untuk merekam fakta bahwa mixer memiliki input baru. Kemudian memanggil ProcessOutputLoop metode , yang ditunjukkan dalam contoh berikutnya. Metode ini mencoba menarik sampel sebanyak mungkin dari 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();
    }
}

Metode ini ProcessOutput , yang ditunjukkan dalam contoh berikutnya, mencoba mendapatkan satu bingkai video dari mixer. Jika tidak ada bingkai video yang tersedia, ProcessSample mengembalikan S_FALSE atau kode kesalahan, salah satunya mengganggu perulangan di ProcessOutputLoop. Sebagian besar pekerjaan dilakukan di ProcessOutput dalam metode :

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

Beberapa komentar tentang contoh ini:

Bingkai Pengecatan Ulang

Terkadang penyaji mungkin perlu mengecat ulang bingkai video terbaru. Misalnya, penyaji standar mengecat ulang bingkai dalam situasi berikut:

Gunakan langkah-langkah berikut untuk meminta mixer membuat ulang bingkai terbaru:

  1. Dapatkan sampel video dari antrean.
  2. Kueri sampel untuk antarmuka IMFDesiredSample.
  3. Panggil IMFDesiredSample::SetDesiredSampleTimeAndDuration. Tentukan stempel waktu bingkai video terbaru. (Anda perlu menyimpan nilai ini dan memperbaruinya untuk setiap bingkai.)
  4. Panggil ProcessOutput pada mixer.

Saat mengecat ulang bingkai, Anda dapat mengabaikan jam presentasi dan segera menyajikan bingkai.

Sampel Penjadwalan

Bingkai video dapat mencapai EVR kapan saja. Penyaji bertanggung jawab untuk menyajikan setiap bingkai pada waktu yang benar, berdasarkan stempel waktu bingkai. Ketika penyaji mendapatkan sampel baru dari mixer, penyaji menempatkan sampel pada antrean terjadwal. Pada utas terpisah, penyaji terus mendapatkan sampel pertama dari kepala antrean dan menentukan apakah akan:

  • Sajikan sampel.
  • Simpan sampel pada antrean karena lebih awal.
  • Buang sampel karena terlambat. Meskipun Anda harus menghindari menghilangkan bingkai jika memungkinkan, Anda mungkin perlu jika penyaji terus tertinggal.

Untuk mendapatkan stempel waktu untuk bingkai video, panggil IMFSample::GetSampleTime pada sampel video. Stempel waktu relatif terhadap jam presentasi EVR. Untuk mendapatkan waktu jam saat ini, panggil IMFClock::GetCorrelatedTime. Jika EVR tidak memiliki jam presentasi, atau jika sampel tidak memiliki stempel waktu, Anda dapat menyajikan sampel segera setelah Anda mendapatkannya.

Untuk mendapatkan durasi setiap sampel, hubungi IMFSample::GetSampleDuration. Jika sampel tidak memiliki durasi, Anda dapat menggunakan fungsi MFFrameRateToAverageTimePerFrame untuk menghitung durasi dari kecepatan bingkai.

Saat Anda menjadwalkan sampel, ingatlah hal berikut:

  • Jika laju pemutaran lebih cepat atau lebih lambat dari kecepatan normal, jam berjalan pada laju yang lebih cepat atau lebih lambat. Itu berarti stempel waktu pada sampel selalu memberikan waktu target yang benar relatif terhadap jam presentasi. Namun, jika Anda menerjemahkan waktu presentasi ke dalam beberapa waktu jam lainnya (misalnya, penghitung kinerja resolusi tinggi), maka Anda harus menskalakan waktu berdasarkan kecepatan jam. Jika kecepatan jam berubah, EVR memanggil metode IMFClockStateSink::OnClockSetRate penyaji.
  • Laju pemutaran bisa negatif untuk pemutaran terbalik. Ketika laju pemutaran negatif, jam presentasi berjalan mundur. Dengan kata lain, waktu N + 1 terjadi sebelum waktu N.

Contoh berikut menghitung seberapa awal atau terlambat sampel, relatif terhadap jam presentasi:

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

Jam presentasi biasanya digerakkan oleh jam sistem atau perender audio. (Perender audio memperoleh waktu dari kecepatan kartu suara menggunakan audio.) Secara umum, jam presentasi tidak disinkronkan dengan laju refresh monitor.

Jika parameter presentasi Direct3D Anda menentukan D3DPRESENT_INTERVAL_DEFAULT atau D3DPRESENT_INTERVAL_ONE untuk interval presentasi, operasi Sajikan menunggu jejak vertikal monitor. Ini adalah cara mudah untuk mencegah robek, tetapi mengurangi akurasi algoritma penjadwalan Anda. Sebaliknya, jika interval presentasi D3DPRESENT_INTERVAL_IMMEDIATE, metode Sajikan segera dijalankan, yang menyebabkan merobek kecuali algoritma penjadwalan Anda cukup akurat sehingga Anda memanggil Hadir hanya selama periode pelacakan ulang vertikal.

Fungsi berikut dapat membantu Anda mendapatkan informasi waktu yang akurat:

  • IDirect3DDevice9::GetRasterStatus mengembalikan informasi tentang raster, termasuk baris pemindaian saat ini dan apakah raster berada dalam periode kosong vertikal.
  • DwmGetCompositionTimingInfo mengembalikan informasi waktu untuk manajer jendela desktop. Informasi ini berguna jika komposisi desktop diaktifkan.

Menyajikan Sampel

Bagian ini mengasumsikan bahwa Anda membuat rantai pertukaran terpisah untuk setiap permukaan seperti yang dijelaskan dalam Mengalokasikan Permukaan Direct3D. Untuk menyajikan sampel, dapatkan rantai pertukaran dari sampel video sebagai berikut:

  1. Panggil IMFSample::GetBufferByIndex pada sampel video untuk mendapatkan buffer.
  2. Kueri buffer untuk antarmuka IMFGetService.
  3. Panggil IMFGetService::GetService untuk mendapatkan antarmuka IDirect3DSurface9 dari permukaan Direct3D. (Anda dapat menggabungkan langkah ini dan langkah sebelumnya menjadi satu dengan memanggil MFGetService.)
  4. Panggil IDirect3DSurface9::GetContainer di permukaan untuk mendapatkan penunjuk ke rantai pertukaran.
  5. Hubungi IDirect3DSwapChain9::P resent pada rantai pertukaran.

Kode berikut menunjukkan langkah-langkah ini:

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

Persegi Sumber dan Tujuan

Persegi panjang sumber adalah bagian dari bingkai video yang akan ditampilkan. Ini didefinisikan relatif terhadap sistem koordinat yang dinormalisasi, di mana seluruh bingkai video menempati persegi panjang dengan koordinat {0, 0, 1, 1}. Persegi panjang tujuan adalah area di dalam permukaan tujuan tempat bingkai video digambar. Penyaji standar memungkinkan aplikasi untuk mengatur persegi panjang ini dengan memanggil IMFVideoDisplayControl::SetVideoPosition.

Ada beberapa opsi untuk menerapkan persegi panjang sumber dan tujuan. Opsi pertama adalah membiarkan mixer menerapkannya:

  • Atur persegi panjang sumber menggunakan atribut VIDEO_ZOOM_RECT. Mixer akan menerapkan persegi panjang sumber saat memadukan video ke permukaan tujuan. Persegi panjang sumber default mixer adalah seluruh bingkai.
  • Atur persegi panjang tujuan sebagai bukaan geometrik dalam jenis output mixer. Untuk informasi selengkapnya, lihat Menegosiasikan Format.

Opsi kedua adalah menerapkan persegi panjang saat Anda IDirect3DSwapChain9::P resent dengan menentukan parameter pSourceRect dan pDestRect dalam metode Sekarang. Anda dapat menggabungkan opsi ini. Misalnya, Anda dapat mengatur persegi panjang sumber pada mixer tetapi menerapkan persegi panjang tujuan dalam metode Sajikan .

Jika aplikasi mengubah persegi panjang tujuan atau mengubah ukuran jendela, Anda mungkin perlu mengalokasikan permukaan baru. Jika demikian, Anda harus berhati-hati untuk menyinkronkan operasi ini dengan utas penjadwalan Anda. Bersihkan antrean penjadwalan dan buang sampel lama sebelum mengalokasikan permukaan baru.

Akhir Aliran

Ketika setiap aliran input pada EVR telah berakhir, EVR mengirim pesan MFVP_MESSAGE_ENDOFSTREAM ke penyaji. Namun, pada titik ketika Anda menerima pesan, mungkin ada beberapa bingkai video yang tersisa untuk diproses. Sebelum menanggapi pesan akhir aliran, Anda harus menguras semua output dari mixer dan menyajikan semua bingkai yang tersisa. Setelah bingkai terakhir disajikan, kirim peristiwa EC_COMPLETE ke EVR.

Contoh berikutnya menunjukkan metode yang mengirim peristiwa EC_COMPLETE jika berbagai kondisi terpenuhi. Jika tidak, ia mengembalikan S_OK tanpa mengirim peristiwa:

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

Metode ini memeriksa status berikut:

  • Jika variabel m_fSampleNotify TRUE, itu berarti mixer memiliki satu atau beberapa bingkai yang belum diproses. (Untuk detailnya, lihat Output Pemrosesan.)
  • Variabel m_fEndStreaming adalah bendera Boolean yang nilai awalnya FALSE. Penyaji mengatur bendera ke TRUE saat EVR mengirim pesan MFVP_MESSAGE_ENDOFSTREAM .
  • Metode AreSamplesPending ini diasumsikan untuk mengembalikan TRUE selama satu atau beberapa bingkai menunggu dalam antrean terjadwal.

Dalam metode IMFVideoPresenter::P rocessMessage, atur m_fEndStreaming ke TRUE dan panggil CheckEndOfStream saat EVR mengirim pesan 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;
}

Selain itu, panggil CheckEndOfStream jika metode IMFTransform::P rocessOutput mixer mengembalikan MF_E_TRANSFORM_NEED_MORE_INPUT. Kode kesalahan ini menunjukkan bahwa mixer tidak memiliki lebih banyak sampel input (lihat Output Pemrosesan).

Melangkah Bingkai

EVR dirancang untuk mendukung langkah bingkai di DirectShow dan menggosok di Media Foundation. Bingkai melangkah dan menggosok secara konseptual mirip. Dalam kedua kasus, aplikasi meminta satu bingkai video pada satu waktu. Secara internal, penyaji menggunakan mekanisme yang sama untuk mengimplementasikan kedua fitur.

Langkah bingkai di DirectShow berfungsi sebagai berikut:

  • Aplikasi memanggil IVideoFrameStep::Step. Jumlah langkah diberikan dalam parameter dwSteps . EVR mengirim pesan MFVP_MESSAGE_STEP ke penyaji, di mana parameter pesan (ulParam) adalah jumlah langkah.
  • Jika aplikasi memanggil IVideoFrameStep::CancelStep atau mengubah status grafik (berjalan, dijeda, atau dihentikan), EVR akan mengirim pesan MFVP_MESSAGE_CANCELSTEP.

Scrubbing di Media Foundation berfungsi sebagai berikut:

  • Aplikasi mengatur laju pemutaran ke nol dengan memanggil IMFRateControl::SetRate.
  • Untuk merender bingkai baru, aplikasi memanggil IMFMediaSession::Start dengan posisi yang diinginkan. EVR mengirim pesan MFVP_MESSAGE_STEP dengan ulParam sama dengan 1.
  • Untuk berhenti menggosok, aplikasi mengatur laju pemutaran ke nilai bukan nol. EVR mengirim pesan MFVP_MESSAGE_CANCELSTEP .

Setelah menerima pesan MFVP_MESSAGE_STEP , penyaji menunggu bingkai target tiba. Jika jumlah langkah adalah N, penyaji akan membuang sampel berikutnya (N - 1) dan menyajikan sampel N th. Ketika penyaji menyelesaikan langkah bingkai, penyaji mengirimkan peristiwa EC_STEP_COMPLETE ke EVR dengan lParam1 diatur ke FALSE. Selain itu, jika laju pemutaran adalah nol, penyaji mengirimkan peristiwa EC_SCRUB_TIME. Jika EVR membatalkan langkah bingkai saat operasi langkah bingkai masih tertunda, penyaji mengirim peristiwa EC_STEP_COMPLETE dengan lParam1 diatur ke TRUE.

Aplikasi dapat membingkai langkah atau scrub beberapa kali, sehingga penyaji mungkin menerima beberapa pesan MFVP_MESSAGE_STEP sebelum mendapatkan pesan MFVP_MESSAGE_CANCELSTEP . Selain itu, penyaji dapat menerima pesan MFVP_MESSAGE_STEP sebelum jam dimulai atau saat jam berjalan.

Mengimplementasikan Frame Stepping

Bagian ini menjelaskan algoritma untuk menerapkan langkah bingkai. Algoritma langkah bingkai menggunakan variabel berikut:

  • step_count. Bilangan bulat yang tidak ditandatangani yang menentukan jumlah langkah dalam operasi langkah bingkai saat ini.

  • step_queue. Antrean pointer IMFSample .

  • step_state. Kapan saja, penyaji dapat berada di salah satu status berikut sehubungan dengan langkah bingkai:

    Provinsi Deskripsi
    NOT_STEPPING Bukan bingkai melangkah.
    MENUNGGU Penyaji telah menerima pesan MFVP_MESSAGE_STEP , tetapi jam belum dimulai.
    TERTUNDA Penyaji telah menerima pesan MFVP_MESSAGE_STEP dan jam telah dimulai, tetapi penyaji sedang menunggu untuk menerima bingkai target.
    DIJADWALKAN Penyaji telah menerima bingkai target dan telah menjadwalkannya untuk presentasi, tetapi bingkai belum disajikan.
    SELESAI Penyaji telah menyajikan bingkai target dan mengirim peristiwa EC_STEP_COMPLETE, dan menunggu pesan MFVP_MESSAGE_STEP atau MFVP_MESSAGE_CANCELSTEP berikutnya.

     

    Status ini independen dari status penyaji yang tercantum di bagian Status Penyaji.

Prosedur berikut didefinisikan untuk algoritma langkah bingkai:

Prosedur PrepareFrameStep

  1. Kenaikan step_count.
  2. Atur step_state ke MENUNGGU.
  3. Jika jam berjalan, panggil StartFrameStep.

Prosedur StartFrameStep

  1. Jika step_state sama dengan WAITING, atur step_state ke PENDING. Untuk setiap sampel di step_queue, panggil DeliverFrameStepSample.
  2. Jika step_state sama dengan NOT_STEPPING, hapus sampel apa pun dari step_queue dan jadwalkan untuk presentasi.

Prosedur CompleteFrameStep

  1. Atur step_state ke SELESAI.
  2. Kirim peristiwa EC_STEP_COMPLETE dengan lParam1 = FALSE.
  3. Jika laju jam adalah nol, kirim peristiwa EC_SCRUB_TIME dengan waktu sampel.

Prosedur DeliverFrameStepSample

  1. Jika laju jam adalah nol dan sampel waktu + sampel durasi<waktu jam, buang sampel. Keluar.
  2. Jika step_state sama dengan SCHEDULED atau COMPLETE, tambahkan sampel ke step_queue. Keluar.
  3. Keputusan step_count.
  4. Jika step_count> 0, buang sampel. Keluar.
  5. Jika step_state sama dengan WAITING, tambahkan sampel ke step_queue. Keluar.
  6. Jadwalkan sampel untuk presentasi.
  7. Atur step_state ke TERJADWAL.

Prosedur CancelFrameStep

  1. Atur step_state ke NOT_STEPPING
  2. Reset step_count ke nol.
  3. Jika nilai sebelumnya dari step_state ADALAH WAITING, PENDING, atau SCHEDULED, kirim EC_STEP_COMPLETE dengan lParam1 = TRUE.

Panggil prosedur ini sebagai berikut:

Pesan atau metode penyaji Prosedur
pesan MFVP_MESSAGE_STEP PrepareFrameStep
pesan MFVP_MESSAGE_STEP CancelStep
IMFClockStatesink::onClockStart StartFrameStep
IMFClockStateSink::OnClockRestart StartFrameStep
Panggilan balik IMFTrackedSample CompleteFrameStep
IMFClockStatesink::onclockstop CancelFrameStep
IMFClockStatesink::onclockSetRate CancelFrameStep

 

Bagan alur berikut menunjukkan prosedur langkah bingkai.

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

Mengatur Penyaji pada EVR

Setelah menerapkan penyaji, langkah selanjutnya adalah mengonfigurasi EVR untuk menggunakannya.

Mengatur Penyaji di DirectShow

Dalam aplikasi DirectShow, atur penyaji pada EVR sebagai berikut:

  1. Buat filter EVR dengan memanggil CoCreateInstance. CLSID CLSID_EnhancedVideoRenderer.
  2. Tambahkan EVR ke grafik filter.
  3. Buat instans penyaji Anda. Penyaji Anda dapat mendukung pembuatan objek COM standar melalui IClassFactory, tetapi ini tidak wajib.
  4. Kueri filter EVR untuk antarmuka IMFVideoRenderer.
  5. Panggil IMFVideoRenderer::InitializeRenderer.

Mengatur Penyaji di Media Foundation

Di Media Foundation, Anda memiliki beberapa opsi, tergantung pada apakah Anda membuat sink media EVR atau objek aktivasi EVR. Untuk informasi selengkapnya tentang objek aktivasi, lihat Objek Aktivasi.

Untuk sink media EVR, lakukan hal berikut:

  1. Panggil MFCreateVideoRenderer untuk membuat sink media.
  2. Buat instans penyaji Anda.
  3. Kueri sink media EVR untuk antarmuka IMFVideoRenderer.
  4. Panggil IMFVideoRenderer::InitializeRenderer.

Untuk objek aktivasi EVR, lakukan hal berikut:

  1. Panggil MFCreateVideoRendererActivate untuk membuat objek aktivasi.

  2. Atur salah satu atribut berikut pada objek aktivasi:

    Atribut Deskripsi
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE Penunjuk ke objek aktivasi untuk penyaji.
    Dengan bendera ini, Anda harus menyediakan objek aktivasi untuk penyaji Anda. Objek aktivasi harus mengimplementasikan antarmuka IMFActivate .
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID penyaji.
    Dengan bendera ini, penyaji Anda harus mendukung pembuatan objek COM standar melalui IClassFactory.

     

  3. Secara opsional, atur atribut MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS pada objek aktivasi.

Perender Video yang Ditingkatkan

Sampel EVRPresenter