數(shù)據(jù)與數(shù)據(jù)緩沖區(qū)
數(shù)據(jù)保存在緩沖區(qū)中,緩沖區(qū)只是字節(jié)數(shù)組。 每個緩沖區(qū)由名為 MediaSample的 COM 對象包裝,該對象實現(xiàn) IMediaSample 接口。 MediaSample由另一種類型的對象(稱為分配器)創(chuàng)建,該對象實現(xiàn) IMemAllocator 接口。 雖然兩個或更多個pin連接可能共享同一分配器,但為每個pin連接分配一個分配器。 下圖演示了此過程。

MediaSample是承載數(shù)據(jù)的載體
每個分配器創(chuàng)建MediaSample池,并為每個MediaSample分配緩沖區(qū)。 每當(dāng)filter需要使用數(shù)據(jù)填充緩沖區(qū)時,它都會通過調(diào)用 IMemAllocator::GetBuffer 從分配器請求MediaSample。 如果分配器具有其他filter當(dāng)前未使用的任何MediaSample, GetBuffer 方法將立即返回,并返回指向MediaSample的指針。 如果分配器的所有MediaSample都在使用中,則 方法將阻塞,直到MediaSample可用。 當(dāng) 方法返回MediaSample時,filter會將數(shù)據(jù)放入MediaSample里面的緩沖區(qū), (設(shè)置相應(yīng)的標(biāo)志,通常包括時間戳) ,并將MediaSample傳送到下游。
當(dāng)呈現(xiàn)器filter收到MediaSample時,它會檢查時間戳并保留MediaSample,直到filter圖的引用時鐘指示應(yīng)呈現(xiàn)數(shù)據(jù)。 filter呈現(xiàn)數(shù)據(jù)后,會釋放MediaSample。 在MediaSample的引用計數(shù)為零之前,MediaSample不會返回到分配器的MediaSample池中,這意味著每個filter都釋放了MediaSample。 下圖演示了此過程。

MediaSample可以作為上游生產(chǎn)者和下游消費者中間的緩沖幀
上游filter可能在呈現(xiàn)器之前運行,也就是說,它填充緩沖區(qū)的速度可能比呈現(xiàn)器使用緩沖區(qū)快。 即便如此,MediaSample也不會提前呈現(xiàn),因為呈現(xiàn)器會保留每個MediaSample,直到其呈現(xiàn)時間為止。 此外,上游filter不會意外覆蓋緩沖區(qū),因為 GetSample 僅返回未使用的示例。 上游filter可以提前運行的數(shù)量取決于分配器池中的MediaSample數(shù)。
上圖僅顯示一個分配器,但通常每個流有多個分配器。 因此,當(dāng)呈現(xiàn)器釋放示例時,它可以具有級聯(lián)效果。 下圖顯示了解碼器在等待呈現(xiàn)器釋放示例時保存壓縮的視頻幀的情況。 分析器filter也在等待解碼器發(fā)布示例。

當(dāng)呈現(xiàn)器釋放其示例時,解碼器對 GetBuffer 的掛起調(diào)用將返回。 然后,解碼器可以解碼壓縮的視頻幀并釋放它所持有的MediaSample,從而解除阻塞程序掛起的 GetBuffer 調(diào)用。
本地內(nèi)存的數(shù)據(jù)傳輸機制
DirectShow 定義了本地內(nèi)存?zhèn)鬏數(shù)膬煞N機制:推送模型和拉取模型。
在推送模型中,源filter生成數(shù)據(jù)并將其傳遞到下游的下一個filter。 該filter被動接收數(shù)據(jù)、處理數(shù)據(jù),并將數(shù)據(jù)發(fā)送到下游。
在拉取模型中,源filter連接到分析器filter。 分析程序filter從源filter請求數(shù)據(jù)。 源filter通過傳遞數(shù)據(jù)來響應(yīng)請求。
推送模型使用 IMemInputPin 接口,拉取模型使用 IAsyncReader 接口。
推送模型比拉取模型更常見。 因此,以下文章采用推送模型。 本部分的最后一篇文章 “拉取模型”介紹了 IAsyncReader 接口與 IMemInputPin 的區(qū)別。
當(dāng)pin將媒體數(shù)據(jù)傳送到另一個pin時,它不會將直接指針傳遞給內(nèi)存緩沖區(qū)。 相反,它傳遞指向管理內(nèi)存的 COM 對象的指針。 此對象稱為 MediaSample,公開 IMediaSample 接口。 接收pin通過調(diào)用 IMediaSample::GetPointer、IMediaSample::GetSize 和 IMediaSample::GetActualDataLength 等方法來訪問內(nèi)存緩沖區(qū)。
MediaSample始終從輸出pin到輸入pin向下游移動。 在推送模型中,輸出pin通過在輸入pin上調(diào)用 IMemInputPin::Receive 來提供MediaSample。 輸入pin將完全在 Receive 方法內(nèi)同步 (處理數(shù)據(jù)) ,或在工作線程上異步處理數(shù)據(jù)。 如果需要等待資源,則允許輸入pin在 Receive 方法中阻塞。
在 IMemInputPin 接口中,上游filter確定要發(fā)送的數(shù)據(jù),并將數(shù)據(jù)推送到下游filter。 對于某些filter, 拉取模型更合適。 此處,下游filter從上游filter請求數(shù)據(jù)。 MediaSample仍會從輸出pin到輸入pin向下游移動,但下游filter會啟動數(shù)據(jù)流。 這種類型的連接使用 IAsyncReader 接口。
拉取模型的典型用途是在文件播放中。 例如,在 AVI 播放圖中, 異步文件源 filter執(zhí)行一般文件讀取操作,并將數(shù)據(jù)作為字節(jié)流傳遞,而沒有格式信息。 AVI 拆分器filter讀取 AVI 標(biāo)頭并將流分析為視頻和音頻MediaSample。 與異步文件源filter相比,AVI 拆分器可以確定所需的數(shù)據(jù),因此它使用 IAsyncReader 而不是 IMemInputPin。
若要從輸出pin請求數(shù)據(jù),輸入pin調(diào)用以下方法之一:
IAsyncReader::Request
IAsyncReader::SyncRead
IAsyncReader::SyncReadAligned。
第一種方法是異步的,用于支持多個重疊讀取。 其他是同步的。
理論上,任何filter都可以支持 IAsyncReader,但在實踐中,它專為連接到分析程序filter的源filter而設(shè)計。 分析程序的作用非常類似于推送模型中的源filter。 暫停時,它會創(chuàng)建一個流式處理線程,該線程從 IAsyncReader 連接拉取數(shù)據(jù)并將其推送到下游。 輸出pin使用 IMemInputPin,圖形的其余部分使用標(biāo)準(zhǔn)推送模型。