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, bukan 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 presenter kustom untuk renderer 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
- Model Objek Penyaji
- Format Negosiasi
- Mengelola Perangkat Direct3D
- Pemrosesan Output
- Langkah Bingkai
- Mengatur Penyaji pada EVR
- Topik terkait
Prasyarat
Sebelum menulis penyaji kustom, Anda harus terbiasa dengan teknologi berikut:
- Perender video yang disempurnakan. Lihat Perender Video yang Ditingkatkan.
- Grafik Direct3D. Anda tidak perlu memahami grafik 3-D untuk menulis program penyaji, tetapi Anda harus tahu cara membuat perangkat pada 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.
- Media Foundation Mengubah. Mixer EVR adalah transformasi Media Foundation, dan penyaji memanggil metode langsung pada mixer.
- Menerapkan objek COM. Penyaji adalah objek COM dalam proses yang menggunakan threading bebas.
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 aliran video dan menghilangkan interlacing video jika diperlukan. Penyaji menggambar (atau menyajikan) video ke layar dan menjadwalkan kapan setiap bingkai ditampilkan. 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 merupakan aliran referensi . Aliran lainnya adalah sub-aliran , yang dimixer dengan teknik alpha-blend ke dalam aliran referensi. Aliran referensi menentukan kecepatan bingkai master untuk video yang dikomposit. Untuk setiap bingkai referensi, mixer mengambil bingkai terbaru dari setiap subaliran, menggabungkannya secara alfa ke bingkai referensi, dan menghasilkan satu bingkai hasil komposit. 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
Pembawa acara 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 lapisan Direct3D. Mixer memindahkan bingkai komposit ke permukaan-permukaan ini.
- Memperoleh output dari mixer.
- Menjadwalkan saat bingkai disajikan. EVR menyediakan jam presentasi, dan penampil menjadwalkan frame sesuai dengan jam ini.
- Menyajikan setiap frame menggunakan Direct3D.
- Melakukan langkah bingkai dan menggosok.
Status Penyaji
Kapan saja, penyaji berada di salah satu kondisi berikut:
- Memulai. Jam presentasi EVR sedang berjalan. Penyaji menjadwalkan frame video untuk presentasi saat mereka tiba.
- Ditunda. 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. Presenter membuang sampel yang dijadwalkan.
- Matikan. Penyaji melepas sumber daya apa pun yang terkait dengan proses streaming, seperti permukaan Direct3D. Ini adalah status awal pembawa acara, dan status akhir sebelum pembawa acara dihapus.
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 pembicara dalam keadaan dimatikan. 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
Seorang presenter diperlukan untuk mengimplementasikan antarmuka berikut:
Antarmuka | Deskripsi |
---|---|
IMFClockStateSink | Menyampaikan kepada penyaji ketika penghitung waktu EVR berubah kondisi. Lihat Menerapkan IMFClockStateSink. |
IMFGetService | Menyediakan cara bagi aplikasi dan komponen lain dalam alur untuk mendapatkan antarmuka dari penyaji. |
IMFTopologyServiceLookupClient | Memungkinkan presenter 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). |
DukunganSukuBungaIMF | Melaporkan rentang laju pemutaran yang didukung penyaji. Lihat Menerapkan IMFRateSupport. |
IMFVideoPositionMapper | Memetakan koordinat pada bingkai video keluaran ke koordinat pada bingkai video masukan. |
IQualProp | Melaporkan informasi tentang kinerja. 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. Presentasi 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 adalah MR_VIDEO_RENDER_SERVICE, EVR meneruskan permintaan GetService ke penyaji.
Menerapkan IMFVideoDeviceID
AntarmukaIMFVideoDeviceIDberisi satu metode, GetDeviceID, yang mengembalikan GUID perangkat. GUID perangkat memastikan bahwa presenter dan mixer menggunakan teknologi yang kompatibel. Jika GUID perangkat tidak cocok, EVR gagal diinisialisasi.
Mixer dan penyaji standar keduanya menggunakan Direct3D 9, dengan GUID perangkat yang 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 pointer antarmuka dari EVR dan dari mixer sebagai berikut:
- Ketika EVR menginisialisasi penyaji, itu memanggil metode IMFTopologyServiceLookupClient::InitServicePointers. Argumen adalah penunjuk ke antarmuka EVR IMFTopologyServiceLookup.
- Penyaji memanggil IMFTopologyServiceLookup::LookupService untuk mendapatkan pointer 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 penunjuk 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 adalah MR_VIDEO_RENDER_SERVICE, maka EVR diperiksa.
- Jika GUID layanan adalah MR_VIDEO_MIXER_SERVICE, maka mixer diakses.
Dalam implementasi InitServicePointersAnda, peroleh antarmuka-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 metodeInitServicePointers :
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 tempat 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
AntarmukaIMFVideoPresentermewarisi IMFClockStateSink dan menambahkan dua metode:
Metode | Deskripsi |
---|---|
GetCurrentMediaType | Mengembalikan jenis media dari bingkai video yang telah digabungkan. |
ProsesPesan | Memberi sinyal kepada penyaji untuk melakukan berbagai tindakan. |
Metode GetCurrentMediaType mengembalikan jenis media penampil. (Untuk detail tentang mengatur jenis media, lihat Menegosiasikan Format.) Jenis media dikembalikan sebagai penunjuk antarmukaIMFVideoMediaType. Contoh berikut mengasumsikan bahwa penyaji menyimpan jenis media sebagai penunjuk IMFMediaType. Dari jenis media, untuk mendapatkan antarmuka IMFVideoMediaType, 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-pesan berikut telah ditentukan. Detail penerapan setiap pesan diberikan di sisa topik ini.
Pesan | Deskripsi |
---|---|
MFVP_MESSAGE_INVALIDATEMEDIATYPE | Jenis media output mixer tidak valid. Pembicara harus berunding mengenai jenis media baru dengan mixer. Lihat Menegosiasikan Format. |
PESAN_MULAI_STREAMING | 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::ProcessOutput 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 mengabaikan semua bingkai video yang dijadwalkan untuk ditampilkan. |
MFVP_MESSAGE_STEP | Meminta penyaji untuk maju ke depan sebanyak N frame. Penyaji harus membuang bingkai N-1 berikutnya dan menampilkan bingkai Nth. Lihat Langkah Bingkai. |
MFVP_MESSAGE_CANCELSTEP | Membatalkan langkah per frame. |
Menerapkan IMFClockStateSink
Penyaji harus menerapkan antarmuka IMFClockStateSink sebagai bagian dari implementasinya atas 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 |
|
OnClockStop |
|
OnClockPause | Atur status penyaji ke dijeda. |
OnClockRestart | Perlakukan sama seperti OnClockStart tetapi jangan buang antrean sampel. |
OnClockSetRate |
|
Menerapkan IMFRateSupport
Untuk mendukung laju pemutaran selain kecepatan normal 1×, penyaji harus menerapkan 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 |
---|---|
DapatkanTarifTerbabat | Mengembalikan nol untuk menunjukkan tidak ada laju pemutaran minimum. |
DapatkanTarifTercepat | Untuk pemutaran yang tidak dipercepat, laju pemutaran tidak boleh melebihi laju penyegaran monitor: laju maksimum = laju penyegaran (Hz) / laju 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 membalikkan pemutaran, gunakan kebalikan dari laju maksimum. |
yang didukung IsRateSupported | Kembalikan MF_E_UNSUPPORTED_RATE jika nilai mutlak dari 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 mengimplementasikan 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 milik EVR, yang diperoleh ketika EVR memanggil metode penyaji IMFTopologyServiceLookupClient::InitServicePointers. (Antarmuka IMediaEventSink awalnya adalah antarmuka DirectShow, tetapi digunakan dalam 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.
Peristiwa | Deskripsi |
---|---|
EC_COMPLETE | Penyaji telah menyelesaikan pemrosesan semua bingkai setelah pesan MFVP_MESSAGE_ENDOFSTREAM.
|
TAMPILAN_EC_DIUBAH | Perangkat Direct3D telah berubah.
|
EC_ERRORABORT | Terjadi kesalahan yang mengharuskan streaming berhenti.
|
EC_PROCESSING_LATENCY | Menentukan jumlah waktu yang dihabiskan penyaji untuk merender setiap bingkai. (Opsional.)
|
EC_SAMPLE_LATENCY | Menentukan waktu tunda saat ini dalam rendering sampel. Jika nilainya positif, sampel berada di belakang jadwal. Jika nilainya negatif, sampel lebih awal dari jadwal. (Opsional.)
|
EC_SCRUB_TIME | Dikirim segera setelah EC_STEP_COMPLETE jika laju pemutaran adalah nol. Kejadian ini berisi stempel waktu bingkai yang ditampilkan.
|
EC_STEP_COMPLETE | Penyaji telah menyelesaikan atau membatalkan langkah bingkai. - Param1: Tidak digunakan. - Param2: Tidak digunakan. Untuk informasi selengkapnya, lihat Frame Stepping. Catatan: Versi dokumentasi sebelumnya menjelaskan Param1 dengan cara yang salah. Parameter ini tidak digunakan untuk kejadian ini. |
Negosiasi Format
Setiap kali penyaji menerima pesan MFVP_MESSAGE_INVALIDATEMEDIATYPE dari EVR, penyaji harus mengatur format output pada mixer, sebagai berikut:
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.
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 berikutnya yang diusulkan oleh mixer.
Buat jenis media baru yang merupakan klon jenis asli lalu ubah atribut berikut:
- Atur atribut MF_MT_FRAME_SIZE sama dengan lebar dan tinggi yang Anda inginkan untuk permukaan Direct3D yang akan Anda alokasikan.
- Atur atribut MF_MT_PAN_SCAN_ENABLED ke FALSE.
- Atur atribut MF_MT_PIXEL_ASPECT_RATIO sama dengan PAR tampilan (biasanya 1:1).
- Atur bukaan geometris (atributMF_MT_GEOMETRIC_APERTURE) sama dengan persegi panjang dalam permukaan Direct3D. Ketika mixer menghasilkan bingkai output, itu menyalin gambar sumber ke persegi panjang ini. Aperture geometrik dapat sebesar permukaan, atau dapat berupa subsegi panjang di dalam permukaan. Untuk informasi selengkapnya, lihat Sumber dan Persegi Tujuan.
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.
Alokasikan kumpulan permukaan Direct3D, seperti yang dijelaskan dalam Mengalokasikan Permukaan Direct3D. Mixer akan menggunakan permukaan ini ketika menampilkan bingkai video yang terkomposit.
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, stream 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 ditemukan jenis yang valid.
Mixer secara otomatis memberi bingkai hitam pada video, dengan mempertimbangkan rasio aspek piksel (PAR) dari 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
Kode berikut menunjukkan kerangka proses. Beberapa langkah ditempatkan dalam fungsi pembantu, dan perincian tepatnya 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
Presenter membuat perangkat Direct3D dan menangani kehilangan koneksi 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. Decoder dapat menggunakan perangkat Direct3D untuk pemrosesan video yang dipercepat. (Untuk informasi selengkapnya tentang akselerasi video, lihat DirectX Video Acceleration 2.0.)
Untuk menyiapkan perangkat Direct3D, lakukan langkah-langkah berikut:
- Buat objek Direct3D dengan memanggil Direct3DCreate9 atau Direct3DCreate9Ex.
- Buat perangkat dengan memanggil IDirect3D9::CreateDevice atau IDirect3D9Ex::CreateDevice.
- Buat manajer perangkat dengan memanggil DXVA2CreateDirect3DDeviceManager9.
- 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 pointerIDirect3DDeviceManager9, objek bisa mendapatkan pegangan ke perangkat dengan memanggil IDirect3DDeviceManager9::OpenDeviceHandle. Ketika objek perlu menggunakan perangkat, objek meneruskan pegangan perangkat ke metode IDirect3DDeviceManager9::LockDevice, yang mengembalikan penunjuk IDirect3DDevice9.
Setelah perangkat dibuat, jika penyaji menghancurkan perangkat dan membuat perangkat baru, penyaji harus memanggil ResetDevice lagi. Metode ResetDevice membatalkan handle perangkat yang ada, yang menyebabkan LockDevice mengembalikan DXVA2_E_NEW_VIDEO_DEVICE. Kode kesalahan ini memberi sinyal kepada objek lain yang menggunakan perangkat bahwa mereka harus membuka pegangan 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 presenter 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 memanggilResetDevice 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. Peristiwa ini memberitahu 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 lebih lanjut 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 mendistribusikan permukaan tergantung pada apakah aplikasi berjalan dalam mode 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:
- Panggil IDirect3DDevice9::CreateAdditionalSwapChain untuk membuat rantai pertukaran.
- Panggil IDirect3DSwapChain9::GetBackBuffer untuk mendapatkan pointer ke permukaan buffer belakang rantai pertukaran.
- 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 saat penyaji memanggil metode IMFTransform::ProcessOutput mixer. Untuk informasi selengkapnya tentang objek sampel video, lihat Sampel Video.
- Simpan pointer IMFSample dalam antrean. Penyaji akan menarik sampel dari antrean ini pada saat pemrosesan seperti yang dijelaskan dalam Pemrosesan Output.
- Simpan referensi ke penunjuk IDirect3DSwapChain9 agar swap chain tidak dilepas.
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 tukar dapat memiliki lebih dari satu penyangga belakang. Namun, sayangnya, jika Anda menyajikan satu buffer belakang saat Anda menulis ke buffer belakang lain dalam rantai swap yang sama, tidak ada cara mudah untuk mengkoordinasikan dua operasi. Ini karena cara Direct3D mengimplementasikan pembalikan permukaan. Saat Anda memanggil Present, driver grafis memperbarui pointer permukaan dalam memori grafis. Jika Anda memiliki pointer IDirect3DSurface9 saat memanggil Present, mereka akan mengarah ke buffer yang berbeda setelah panggilan Present selesai.
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.
Pelacakan Sampel
Saat penyaji pertama kali mengalokasikan sampel video, ia menempatkannya dalam antrean. Penyaji acara mengambil dari antrean ini ketika perlu mendapatkan frame 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 antarmukaIMFTrackedSample. Anda dapat menggunakan antarmuka ini sebagai berikut:
Terapkan antarmuka IMFAsyncCallback di presenter Anda.
Sebelum Anda menempatkan sampel dalam antrean terjadwal, kueri objek sampel video untuk antarmuka IMFTrackedSample.
Panggil IMFTrackedSample::SetAllocator dengan pointer ke antarmuka panggilan balik Anda.
Saat sampel siap untuk presentasi, hapus dari antrean terjadwal, sajikan, dan rilis semua referensi ke sampel.
Sampel memanggil panggilan balik. (Objek sampel tidak dihapus dalam kasus ini karena menyimpan jumlah referensi pada dirinya sendiri hingga panggilan balik dipanggil.)
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 ketika 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::ProcessOutput pada mixer. Jika metode berhasil, penyaji menjadwalkan sampel untuk presentasi.
Untuk mendapatkan output dari mixer, lakukan langkah-langkah berikut:
Periksa status jam. Jika penghitung waktu dijeda, abaikan pesan MFVP_MESSAGE_PROCESSINPUTNOTIFY kecuali ini adalah frame video pertama. Jika jam berjalan, atau jika ini adalah frame video pertama, lanjutkan.
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.
(Opsional.) Jika jam tersedia, dapatkan waktu jam saat ini (T1) dengan memanggil IMFClock::GetCorrelatedTime.
Panggil IMFTransform::P rocessOutput pada mixer. Jika ProcessOutput berhasil, sampel berisi bingkai video. Jika metode gagal, periksa kode pengembalian. Kode kesalahan berikut dari ProcessOutput bukan 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 dari aliran dan menanggapinya dengan 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 pemateri 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.
(Opsional.) Jika jam tersedia, dapatkan waktu jam saat ini (T2). Jumlah latensi yang diperkenalkan oleh mixer adalah (T2 - T1). Kirim event EC_PROCESSING_LATENCY menggunakan nilai ini ke EVR. EVR menggunakan nilai ini untuk kontrol kualitas. Jika tidak ada pengatur waktu yang tersedia, tidak ada alasan untuk mengirim peristiwa EC_PROCESSING_LATENCY.
(Opsional.) Kueri sampel untuk IMFTrackedSample dan panggil IMFTrackedSample::SetAllocator seperti yang dijelaskan dalam Sampel Pelacakan .
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 penyaji IMFClockStateSink::OnClockStart atau IMFClockStateSink::OnClockStart dipanggil. Ini menangani kasus di mana mixer mengabaikan input karena jam dijeda (langkah 1).
- Panggilan balik IMFTrackedSample dijalankan. Ini menangani kasus di mana mixer menerima input tetapi semua sampel video dari presenter sedang digunakan (langkah 2).
Beberapa contoh kode berikutnya menunjukkan langkah-langkah ini secara lebih rinci. Penyaji memanggil metode ProcessInputNotify
(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 ProcessInputNotify
ini mengatur bendera Boolean untuk merekam fakta bahwa mixer memiliki input baru. Kemudian memanggil metode ProcessOutputLoop
, 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 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 menghentikan perulangan di ProcessOutputLoop
. Sebagian besar pekerjaan dilakukan di dalam metode 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;
}
Beberapa komentar tentang contoh ini:
- Variabel m_SamplePool diasumsikan sebagai objek koleksi yang menyimpan antrean sampel video yang tersedia. Metode
GetSample
dari objek mengembalikan MF_E_SAMPLEALLOCATOR_EMPTY jika antrian kosong. - Jika metode ProcessOutput mixer mengembalikan MF_E_TRANSFORM_NEED_MORE_INPUT, itu berarti mixer tidak bisa menghasilkan output lagi, sehingga penyaji menghapus penanda m_fSampleNotify.
- Metode
TrackSample
, yang mengatur callback IMFTrackedSample, ditampilkan di bagian Pelacakan Sampel.
Mengecat Ulang Bingkai
Terkadang penyaji mungkin perlu mengecat ulang bingkai video terbaru. Misalnya, presenter standar mengecat ulang bingkai dalam situasi berikut:
- Dalam mode berjendela, sebagai respons terhadap pesan WM_PAINT yang diterima oleh jendela aplikasi. Lihat IMFVideoDisplayControl::RepaintVideo.
- Jika persegi panjang tujuan berubah saat aplikasi memanggil IMFVideoDisplayControl::SetVideoPosition.
Gunakan langkah-langkah berikut untuk meminta mixer membuat ulang bingkai terbaru:
- Dapatkan sampel video dari antrean.
- Kueri sampel untuk antarmuka IMFDesiredSample.
- Panggil IMFDesiredSample::SetDesiredSampleTimeAndDuration. Tentukan penanda waktu pada bingkai video terbaru. (Anda perlu menyimpan nilai ini dan memperbaruinya untuk setiap frame.)
- 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 tepat, berdasarkan penanda waktu bingkai. Ketika penyaji mendapatkan sampel baru dari mixer, penyaji menempatkan sampel pada antrean terjadwal. Pada utas terpisah, penyaji terus mendapatkan sampel pertama dari awal antrean dan memutuskan apakah akan:
- Sajikan sampel.
- Simpan sampel pada antrean karena masih awal.
- Buang sampel karena terlambat. Meskipun Anda harus menghindari mengurangi jumlah bingkai jika memungkinkan, Anda mungkin perlu melakukannya 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, panggil IMFSample::GetSampleDuration. Jika sampel tidak memiliki durasi, Anda dapat menggunakan fungsi MFFrameRateToAverageTimePerFrame untuk menghitung durasi dari frame rate.
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 dari penyaji.
- Laju pemutaran bisa negatif untuk pemutaran terbalik. Ketika laju pemutaran negatif, jam presentasi akan 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. (Renderer audio mengambil waktu dari kecepatan konsumsi audio oleh kartu suara.) Secara umum, jam presentasi tidak disinkronkan dengan refresh rate 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 Sajikan 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:
- Panggil IMFSample::GetBufferByIndex pada sampel video untuk mendapatkan buffer.
- Kueri buffer untuk antarmukaIMFGetService.
- Panggil IMFGetService::GetService untuk mendapatkan antarmuka IDirect3DSurface9 dari permukaan Direct3D. (Anda dapat menggabungkan langkah ini dan langkah sebelumnya menjadi satu dengan memanggil MFGetService.)
- Panggil IDirect3DSurface9::GetContainer di permukaan untuk mendapatkan penunjuk ke rantai pertukaran.
- Panggil IDirect3DSwapChain9::P resent pada rantai pertukaran.
Kode berikut menunjukkan langkah-langkah berikut:
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 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 dalam permukaan tujuan di mana bingkai video digambar. Penyaji standar memungkinkan aplikasi untuk mengatur persegi panjang ini dengan memanggil IMFVideoDisplayControl::SetVideoPosition.
Ada beberapa pilihan untuk menerapkan persegi panjang sumber dan persegi panjang tujuan. Opsi pertama adalah membiarkan mixer menerapkannya:
- Atur persegi panjang sumber menggunakan atribut VIDEO_ZOOM_RECT. Mixer akan menerapkan persegi panjang sumber saat menyalin video ke permukaan tujuan. Persegi panjang sumber default mixer adalah keseluruhan bingkai.
- Tetapkan persegi panjang tujuan sebagai bukaan geometrik dalam tipe output pencampur. Untuk informasi selengkapnya, lihat Format Negosiasi.
Opsi kedua adalah menerapkan persegi panjang saat Anda IDirect3DSwapChain9::Present dengan menentukan parameter pSourceRect dan pDestRect dalam metode Present. Anda dapat menggabungkan opsi ini. Misalnya, Anda dapat menetapkan persegi panjang sumber pada mixer tetapi menerapkan persegi panjang tujuan dalam metode Tampilkan.
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 Arus
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 stream, Anda harus menyelesaikan semua output dari mixer dan menampilkan semua bingkai yang tersisa. Setelah bingkai terakhir disajikan, kirim event 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 bernilai TRUE, itu berarti mixer memiliki satu atau beberapa frame yang belum diproses. (Untuk detailnya, lihat Processing Output.)
- Variabel m_fEndStreaming adalah bendera Boolean yang nilai awalnya FALSE. Penyaji mengatur bendera ke TRUE saat EVR mengirim pesan MFVP_MESSAGE_ENDOFSTREAM.
- Metode
AreSamplesPending
diasumsikan mengembalikan TRUE selama satu atau beberapa frame menunggu dalam antrean terjadwal.
Dalam metode IMFVideoPresenter::ProcessMessage, 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::ProcessOutput mixer mengembalikan MF_E_TRANSFORM_NEED_MORE_INPUT. Kode kesalahan ini menunjukkan bahwa mixer tidak lagi memiliki sampel input (silakan lihat Memproses Output).
Melangkah Bingkai
EVR dirancang untuk mendukung pemutaran langkah demi langkah di DirectShow dan penelusuran 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 per-frame 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 mengirimkan pesan MFVP_MESSAGE_CANCELSTEP.
Scrubbing di Media Foundation berfungsi sebagai berikut:
- Aplikasi menetapkan 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 mengirimkan pesan MFVP_MESSAGE_CANCELSTEP.
Setelah menerima pesan MFVP_MESSAGE_STEP, pengirim menunggu frame target tiba. Jika jumlah langkah N, penyaji akan membuang (N - 1) sampel berikutnya dan menyajikan sampel ke-N. Ketika penyaji menyelesaikan langkah frame, ia 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 frame saat operasi langkah frame masih tertunda, presenter 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 pemindahan bingkai. Algoritma langkah bingkai menggunakan variabel berikut:
jumlah_langkah. Bilangan bulat tanpa tanda yang menentukan jumlah langkah dalam perpindahan bingkai saat ini.
step_state. Setiap saat, penyaji dapat berada dalam salah satu dari kondisi berikut sehubungan dengan pemrosesan bingkai:
Negara Deskripsi TIDAK MELANGKAH Tidak melakukan pemutaran frame secara bertahap. MENUNGGU Pembawa acara telah menerima pesan MFVP_MESSAGE_STEP, tetapi jam belum dimulai. TERTUNDA Penyaji telah menerima pesan MFVP_MESSAGE_STEP dan jam telah dimulai, tetapi penyaji masih menunggu untuk mendapatkan bingkai target. DIJADWALKAN Penyaji presentasi telah menerima kerangka target dan telah menjadwalkannya untuk presentasi, tetapi kerangka belum ditampilkan. LENGKAP Penyaji telah mempresentasikan target frame dan mengirimkan event EC_STEP_COMPLETE, serta menunggu pesan MFVP_MESSAGE_STEP atau MFVP_MESSAGE_CANCELSTEP berikutnya. Status ini independen dari status penyaji yang disebutkan dalam bagian Status Penyaji.
Prosedur berikut didefinisikan untuk algoritma langkah bingkai:
Prosedur PrepareFrameStep
- Kenaikan pada jumlah langkah.
- Atur step_state ke MENUNGGU.
- Jika jam berjalan, panggil StartFrameStep.
Prosedur StartFrameStep
- Jika step_state sama dengan WAITING, atur step_state ke PENDING. Untuk setiap sampel di step_queue, panggil DeliverFrameStepSample.
- Jika step_state sama dengan NOT_STEPPING, hapus segala sampel dari step_queue dan jadwalkan mereka untuk presentasi.
Prosedur CompleteFrameStep
- Atur step_state ke SELESAI.
- Kirim peristiwa EC_STEP_COMPLETE dengan lParam1 = FALSE.
- Jika laju jam adalah nol, kirim peristiwa EC_SCRUB_TIME dengan waktu sampel.
Prosedur DeliverFrameStepSample
- Jika laju jam adalah nol dan waktu sampel + durasi sampel<waktu jam, buang sampel tersebut. Keluar.
- Jika step_state sama dengan SCHEDULED atau COMPLETE, tambahkan sampel ke step_queue. Keluar.
- Kurangi step_count.
- Jika step_count> 0, buang sampel. Keluar.
- Jika step_state sama dengan WAITING, tambahkan sampel ke step_queue. Keluar.
- Jadwalkan sampel untuk presentasi.
- Atur step_state ke TERJADWAL.
Prosedur CancelFrameStep
- Atur step_state ke TIDAK_MEMIJAK
- Atur ulang step_count ke nol.
- 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 |
pemanggilan balik IMFTrackedSample | CompleteFrameStep |
IMFClockStateSink::OnClockStop | CancelFrameStep |
IMFClockStateSink::OnClockSetRate | CancelFrameStep |
Bagan alur berikut menunjukkan prosedur langkah bingkai.
Mengatur Penyaji pada EVR
Setelah menerapkan penyaji, langkah selanjutnya adalah mengonfigurasi EVR untuk menggunakannya.
Mengatur Presenter di DirectShow
Dalam aplikasi DirectShow, atur penyaji pada EVR sebagai berikut:
- Buat filter EVR dengan memanggil CoCreateInstance. CLSID CLSID_EnhancedVideoRenderer.
- Tambahkan EVR ke grafik filter.
- Buat contoh penyaji Anda. Penyaji Anda dapat mendukung pembuatan objek COM standar melalui IClassFactory, tetapi ini tidak wajib.
- Kueri filter EVR untuk antarmuka IMFVideoRenderer .
- Panggil IMFVideoRenderer::InitializeRenderer.
Menyetel Presenter 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 media sink EVR, lakukan hal berikut:
- Panggil MFCreateVideoRenderer untuk membuat sink media.
- Buat instans penyaji Anda.
- Kueri media sink EVR untuk antarmuka IMFVideoRenderer.
- Panggil IMFVideoRenderer::InitializeRenderer.
Untuk objek aktivasi EVR, lakukan hal berikut:
Panggil MFCreateVideoRendererActivate untuk membuat objek aktivasi.
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 antarmukaIMFActivate.MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID penyaji.
Dengan bendera ini, penyaji Anda harus mendukung pembuatan objek COM standar melalui IClassFactory.Secara opsional, atur atribut MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS pada objek aktivasi.
Topik terkait