1. 程式人生 > >directshow原理分析之filter到filter的連線

directshow原理分析之filter到filter的連線

Filter是Directshow中最基本的概念。Directshow使用filter graph來管理filter。filter graph是filter的容器。

Filter一般由一個或者幾個Pin組成。filter之間通過Pin來連線,組成一條鏈。

PIN也是一種COM元件,每一個PIN都實現了IPin介面。

試圖連結的兩個Pin必須在一個filter graph中。


連線過程如下:

1.Filter Graph Manager在輸出pin上呼叫IPin::Connect

2.如果輸出Pin接受連線,則呼叫IPin::ReceiveConnection

3.如果輸入Pin也接受此次連線,則雙方連線成功

首先來分析基類函式CBasePin的Connect實現:

CBasePin繼承了IPin。

<pre name="code" class="cpp">HRESULT __stdcall CBasePin::Connect(IPin * pReceivePin, __in_opt const AM_MEDIA_TYPE *pmt   // optional media type)
{
    CheckPointer(pReceivePin,E_POINTER);
    ValidateReadPtr(pReceivePin,sizeof(IPin));
    CAutoLock cObjectLock(m_pLock);
    DisplayPinInfo(pReceivePin);

    /* 檢查該Pin是否早已連線 */
    if (m_Connected) {
        DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Already connected")));
        return VFW_E_ALREADY_CONNECTED;
    }

    /*一般Filter只在停止狀態下接受連線*/
    if (!IsStopped() && !m_bCanReconnectWhenActive) {
        return VFW_E_NOT_STOPPED;
    }

    /*開始媒體型別檢查,找出一種雙方均支援的型別*/
    const CMediaType * ptype = (CMediaType*)pmt;
    HRESULT hr = AgreeMediaType(pReceivePin, ptype);
    if (FAILED(hr)) {
        DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to agree type")));

        // Since the procedure is already returning an error code, there
        // is nothing else this function can do to report the error.
        EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) );

#ifdef DXMPERF
        PERFLOG_CONNECT( (IPin *) this, pReceivePin, hr, pmt );
#endif // DXMPERF

        return hr;
    }

    DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Connection succeeded")));

#ifdef DXMPERF
    PERFLOG_CONNECT( (IPin *) this, pReceivePin, NOERROR, pmt );
#endif // DXMPERF

    return NOERROR;
}


事實上,以上函式並沒有進行真正的連線,只是進行了型別檢查和狀態檢查。並且這個函式是從輸出Pin進入的。

Connect兩個引數分別代表:輸出Pin試圖連結的輸入Pin和指定連線用的媒體型別。

真正的連結是CBase::AgreeMediaType來實現的。

<pre name="code" class="cpp">HRESULT CBasePin::AgreeMediaType(IPin *pReceivePin, const CMediaType *pmt)
{
    ASSERT(pReceivePin);
    IEnumMediaTypes *pEnumMediaTypes = NULL;

    // 判斷pmt是不是一個完全指定的媒體型別
    if ( (pmt != NULL) && (!pmt->IsPartiallySpecified())) {
	//用這個指定的媒體型別去做連線,如果失敗不做任何處理。
        return AttemptConnection(pReceivePin, pmt);
    }


    /* 進行pin上支援的媒體型別的列舉 ,開始媒體型別的協商過程*/
    HRESULT hrFailure = VFW_E_NO_ACCEPTABLE_TYPES;

    for (int i = 0; i < 2; i++) {
        HRESULT hr;
        if (i == (int)m_bTryMyTypesFirst) {
            hr = pReceivePin->EnumMediaTypes(&pEnumMediaTypes);
        } else {
            hr = EnumMediaTypes(&pEnumMediaTypes);
        }
        if (SUCCEEDED(hr)) {
            ASSERT(pEnumMediaTypes);
            hr = TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes);
            pEnumMediaTypes->Release();
            if (SUCCEEDED(hr)) {
                return NOERROR;
            } else {
                // try to remember specific error codes if there are any
                if ((hr != E_FAIL) &&
                    (hr != E_INVALIDARG) &&
                    (hr != VFW_E_TYPE_NOT_ACCEPTED)) {
                    hrFailure = hr;
                }
            }
        }
    }

    return hrFailure;
}
從AgreeMediaType看到,該函式首先判斷媒體型別的有效性,如果pmt是一種完全指定的媒體型別,那麼就以這種媒體型別呼叫內部

函式AttempConnection進行連線。

但是如果pmt是一個空指標或者不是完全指定的媒體型別,那麼真正的協商過程也就開始了。注意,for迴圈的次數為2,輸出Pin上的成員變數

m_bTryMyTypesFirst初始值為false,也就是說,連線過程進行到這裡,會首先得到輸入pin上的媒體型別列舉器的試連線,如果連線不成功,再去得到

輸出Pin上的媒體型別列舉器的試連線。

下面看一下TryMediaTypes函式的實現:

HRESULT CBasePin::TryMediaTypes(IPin *pReceivePin, __in_opt const CMediaType *pmt, IEnumMediaTypes *pEnum)
{
    /* 復位列舉器內部狀態 */

    HRESULT hr = pEnum->Reset();
    if (FAILED(hr)) {
        return hr;
    }

    CMediaType *pMediaType = NULL;
    ULONG ulMediaCount = 0;

    // attempt to remember a specific error code if there is one
    HRESULT hrFailure = S_OK;

    for (;;) {

        /* Retrieve the next media type NOTE each time round the loop the
           enumerator interface will allocate another AM_MEDIA_TYPE structure
           If we are successful then we copy it into our output object, if
           not then we must delete the memory allocated before returning */

        hr = pEnum->Next(1, (AM_MEDIA_TYPE**)&pMediaType,&ulMediaCount);
        if (hr != S_OK) {
            if (S_OK == hrFailure) {
                hrFailure = VFW_E_NO_ACCEPTABLE_TYPES;
            }
            return hrFailure;
        }


        ASSERT(ulMediaCount == 1);
        ASSERT(pMediaType);

        // 檢查當前列舉得到的型別是否與不完全指定的型別引數匹配

        if (pMediaType &&
            ((pmt == NULL) ||
            pMediaType->MatchesPartial(pmt))) {
<span style="white-space:pre">	</span>    //進行試連線
            hr = AttemptConnection(pReceivePin, pMediaType);

            // attempt to remember a specific error code
            if (FAILED(hr) &&
            SUCCEEDED(hrFailure) &&
            (hr != E_FAIL) &&
            (hr != E_INVALIDARG) &&
            (hr != VFW_E_TYPE_NOT_ACCEPTED)) {
                hrFailure = hr;
            }
        } else {
            hr = VFW_E_NO_ACCEPTABLE_TYPES;
        }

        if(pMediaType) {
            DeleteMediaType(pMediaType);
            pMediaType = NULL;
        }

        if (S_OK == hr) {
            return hr;
        }
    }
}

當連線程序進入TryMediaTypes函式後,會使用媒體型別列舉器列舉Pin上提供的所有的媒體型別,然後一種一種的進行試連線。如果有一種成功,則整個Pin連線成功。

最後需要看一下AttempConnection函式的跟Pin連線相關的虛擬函式的呼叫的順序,對與自己實現filter有指導意義:

<pre name="code" class="cpp">HRESULT CBasePin::AttemptConnection(IPin* pReceivePin,      // connect to this pin
  					const CMediaType* pmt   // using this type
)
{
    // The caller should hold the filter lock becasue this function
    // uses m_Connected.  The caller should also hold the filter lock
    // because this function calls SetMediaType(), IsStopped() and
    // CompleteConnect().
    ASSERT(CritCheckIn(m_pLock));

    // Check that the connection is valid  -- need to do this for every
    // connect attempt since BreakConnect will undo it.
    HRESULT hr = CheckConnect(pReceivePin);
    if (FAILED(hr)) {
        DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("CheckConnect failed")));

        // Since the procedure is already returning an error code, there
        // is nothing else this function can do to report the error.
        EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) );

        return hr;
    }
    //可以顯示媒體型別,有時候挺有用
    DisplayTypeInfo(pReceivePin, pmt);

    /* Pin上的媒體型別檢查 */

    hr = CheckMediaType(pmt);
    if (hr == NOERROR) {

        /*  Make ourselves look connected otherwise ReceiveConnection
            may not be able to complete the connection
        */
        m_Connected = pReceivePin;
        m_Connected->AddRef();
        hr = SetMediaType(pmt);//在Pin上儲存媒體型別
        if (SUCCEEDED(hr)) {
            /* 詢問連線對方Pin是否能接受當前的媒體型別 */

            hr = pReceivePin->ReceiveConnection((IPin *)this, pmt);
	    //連線成功
            if (SUCCEEDED(hr)) {
                /* Complete the connection */

                hr = CompleteConnect(pReceivePin);
                if (SUCCEEDED(hr)) {
                    return hr;
                } else {
                    DbgLog((LOG_TRACE,
                            CONNECT_TRACE_LEVEL,
                            TEXT("Failed to complete connection")));
                    pReceivePin->Disconnect();
                }
            }
        }
    } else {
        // we cannot use this media type

        // return a specific media type error if there is one
        // or map a general failure code to something more helpful
        // (in particular S_FALSE gets changed to an error code)
        if (SUCCEEDED(hr) ||
            (hr == E_FAIL) ||
            (hr == E_INVALIDARG)) {
            hr = VFW_E_TYPE_NOT_ACCEPTED;
        }
    }

    // BreakConnect and release any connection here in case CheckMediaType
    // failed, or if we set anything up during a call back during
    // ReceiveConnection.

    // Since the procedure is already returning an error code, there
    // is nothing else this function can do to report the error.
    EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) );

    /*  If failed then undo our state */
    if (m_Connected) {
        m_Connected->Release();
        m_Connected = NULL;
    }

    return hr;
}

至此:連線雙方Pin上的使用的媒體型別協商完了,但是並不能真正的傳輸資料。因為記憶體還沒分配唄。

這些均是在輸出Pin上的CompleteConnect中完成的。

CBaseOutPin繼承自CBasePin

HRESULT CBaseOutputPin::CompleteConnect(IPin *pReceivePin)
{
    UNREFERENCED_PARAMETER(pReceivePin);
    return DecideAllocator(m_pInputPin, &m_pAllocator);
}
DecideAllocator就是Pin之間資料傳送所使用的記憶體分配器的協商。在dshow中資料傳輸的單元叫做Sample(也是一種COM元件,管理記憶體用的)。而Sample是由分配器Allocator(也是COM元件)來管理的。連線雙方必須使用同一個分配器,該分配器由誰來建立也需要協商。
<pre name="code" class="cpp">HRESULT CBaseOutputPin::DecideAllocator(IMemInputPin *pPin, __deref_out IMemAllocator **ppAlloc)
{
    HRESULT hr = NOERROR;
    *ppAlloc = NULL;

    // get downstream prop request
    // the derived class may modify this in DecideBufferSize, but
    // we assume that he will consistently modify it the same way,
    // so we only get it once
    ALLOCATOR_PROPERTIES prop;
    ZeroMemory(&prop, sizeof(prop));

    // 詢問輸入Pin對於分配器的需求
    pPin->GetAllocatorRequirements(&prop);

    // if he doesn't care about alignment, then set it to 1
    if (prop.cbAlign == 0) {
        prop.cbAlign = 1;
    }

    /* 詢問輸入Pin是否提供一個分配器 */

    hr = pPin->GetAllocator(ppAlloc);
    if (SUCCEEDED(hr)) {
       //決定Sample使用的記憶體的大小,以及分配器管理的Sample的數量
        hr = DecideBufferSize(*ppAlloc, &prop);
        if (SUCCEEDED(hr)) {
            //通知輸入Pin最終使用的分配器的物件
            hr = pPin->NotifyAllocator(*ppAlloc, FALSE);
            if (SUCCEEDED(hr)) {
                return NOERROR;
            }
        }
    }

    /* 如果輸入Pin上不提供分配器,則必須在輸出Pin上提供 */

    if (*ppAlloc) {
        (*ppAlloc)->Release();
        *ppAlloc = NULL;
    }

    /* 建立一個輸出Pin上的分配器 */

    hr = InitAllocator(ppAlloc);
    if (SUCCEEDED(hr)) {

        // note - the properties passed here are in the same
        // structure as above and may have been modified by
        // the previous call to DecideBufferSize
        hr = DecideBufferSize(*ppAlloc, &prop);
        if (SUCCEEDED(hr)) {
            hr = pPin->NotifyAllocator(*ppAlloc, FALSE);
            if (SUCCEEDED(hr)) {
                return NOERROR;
            }
        }
    }

    /* Likewise we may not have an interface to release */

    if (*ppAlloc) {
        (*ppAlloc)->Release();
        *ppAlloc = NULL;
    }
    return hr;
}
當Pin上的資料傳送記憶體分配器協商成功後,並沒有馬上分配記憶體,實際上是等Filter Graph執行後,呼叫輸出Pin上的Active函式時進行的。
HRESULT CBaseOutputPin::Active(void)
{
    if (m_pAllocator == NULL) {
        return VFW_E_NO_ALLOCATOR;
    }
    return m_pAllocator->Commit();
}

至此整個Pin的連結才算真正完成。

相關推薦

directshow原理分析filter到filter的連線

Filter是Directshow中最基本的概念。Directshow使用filter graph來管理filter。filter graph是filter的容器。 Filter一般由一個或者幾個Pin組成。filter之間通過Pin來連線,組成一條鏈。 PIN也是一種CO

netty原始碼分析連線接入全解析

本文收穫 通讀本文,你會了解到 1.netty如何接受新的請求 2.netty如何給新請求分配reactor執行緒 3.netty如何給每個新連線增加ChannelHandler 其實,遠不止這些~ 前序背景 讀這篇文章之前,最好掌握一些前序知識,包括netty中的r

Android Broadcast原理分析LocalBroadcast(三)

目錄 LocalBroadcastManager簡介 LocalBroadcastManager使用 原始碼解析 總結 1. LocalBroadcastManager簡介 前面的文章介紹了常用的Broadcast原理,這種廣播是經過系統排程的,不論是動態註

Android Broadcast原理分析Android新版本限制(四)

目錄 概述 官方文件介紹 原始碼解析 適配 1. 概述 隨著Android版本不斷迭代,Android對後臺的管控越來越嚴格,對於APP而言,通常來講後臺活躍的主要是廣播以及service,而Google對於後臺的管控也著重就在這兩個元件上,本篇文章主要介紹

Android P zygote 原理分析SystemServer的啟動

SystemServer 在android中的核心服務之一,系統中的大多數服務都執行在這個程序,所以當zygote 啟動後第一個啟動的就是SystemServer ,因為SystemServer 的重要性,如果SystemServer啟動失敗或者中間出現異常導致崩潰,都會引起

Java虛擬機器原理分析Win10下VS2017編譯OpenJDK8與單步除錯HotSpot VM過程詳細記錄

在上一篇文章《Java虛擬機器原理分析之Win7下VS2010編譯OpenJDK8與單步除錯HotSpot VM過程詳細記錄》中,我們在Win7+VS2010環境下成功編譯出了x86版本的OpenJDK。然而VS2010畢竟有些年頭了,我也只是在開發機上才裝了這

深入理解HTTP協議及原理分析快取

3.2 快取的實現原理 3.2.1什麼是Web快取 WEB快取(cache)位於Web伺服器和客戶端之間。 快取會根據請求儲存輸出內容的副本,例如html頁面,圖片,檔案,當下一個請求來到的時候:如果是相同的URL,快取直接使用副本響應訪問請求,而不是向源伺服器再次傳送請求

Android Service原理分析startService(一)

1. Service概述 Service作為Android四大元件之一,在開發過程中非常常用,它雖然沒有ui,但是同樣可以在後臺

Android 65K問題Multidex原理分析及NoClassDefFoundError的解決方法

bottom mini ati ... types auto weight right for Android 65K問題相信困惑了不少人,盡管AS的出來能夠通過分dex高速解決65K問題,可是同一時候也easy由於某些代碼沒有打包到MainDex裏

編譯原理(五)語法分析自底向上分析算符優先分析

logs cnblogs div mar 分析法 clas pos block mark 語法分析之自頂向下分析 說明:以老師PPT為標準,借鑒部分教材內容,AlvinZH學習筆記。 先看看PPT吧! 引用說明 - 邵老師課堂PDF - 《編譯原理級編譯程序構造》 編譯

編譯原理(六)自底向上分析LR分析

markdown lr分析 編譯原理 lock mar blog pre 分析法 logs 自底向上分析之LR分析法 說明:以老師PPT為標準,借鑒部分教材內容,AlvinZH學習筆記。 本節內容太多了,考完再寫了,對不起~ 引用說明 - 邵老師課堂PDF - 《編譯原

SparkTask原理分析

finish lease finall .com 反序 eap wrap setresult add 在Spark中,一個應用程序要想被執行,肯定要經過以下的步驟: 從這個路線得知,最終一個job是依賴於分布在集群不同節點中的task,通過並行或者並發的運

JVM源碼分析System.currentTimeMillis及nanoTime原理詳解

atime status bin lease col void 奇怪 pro http JDK7

Netty框架原理分析(一)

https://blog.csdn.net/qq_18603599/article/details/80768390 netty是典型基於reatctor模型的程式設計,主要用於完成網路底層通訊的,java本身也是提供各種io的操作,但是使用起來api會很繁瑣,同時效能有很難有保證,經常會出現莫

java定時器Timer使用與原理分析

Timer和TimerTask Timer是jdk中提供的一個定時器工具,使用的時候會在主執行緒之外起一個單獨的執行緒執行指定的計劃任務,可以指定執行一次或者反覆執行多次。 TimerTask是一個實現了Runnable介面的抽象類,代表一個可以被Timer執行的任務。 【使用舉例】

Android核心分析GUI框架的原理

在Android中Window 是個弱化了的概念,更多的表現在View這個概念上。在很大程度上,Android 的View的概念可以代替Microsoft Window 這個概念。不過是換了一個側重點有點不一樣而已。 原始GUI基本框架 首先我們從Android 的SDK 外特性空間開始,在

K8S 原始碼探祕 nginx-ingress 工作原理分析

一、引言        Nginx-ingress 是 Kubernetes 生態中的重要成員,主要負責向外暴露服務,同時提供負載均衡等附加功能;        截至目前,nginx-ingress 已經能夠

現代編譯原理——第二章:語法分析LL(K)

  轉自: http://www.cnblogs.com/BlackWalnut/p/4472122.html   LL(K)語法分析技術是建立在預測分析的技術之上的。我們先來了解預測分析技術。考慮以下文法:         當使用該文法對(1*2-3)+4和(1

Spark效能調優原理分析

spark效能調優之前先明白原理,具體如下: 使用spark-submit提交一個Spark作業之後,這個作業就會啟動一個對應的Driver程序。根據使用的部署模式(deploy-mode)不同,Driver程序可能在本地啟動,也可能在叢集中某個工作節點上啟動。Driver程序本身會根

Linux記憶體洩露kmemleak原理分析與使用

    1. kmemleak原理:  通過分析記憶體塊是否存在引用(指標)來判斷記憶體洩露. 1.1  掃描區域   首先要理解整個核心虛擬地址空間是怎麼分佈的, 核心地址空間分佈: Virtual kernel memory layout: vma