EMIPLIB庫分析一

通過分析feedbackexample例程來了解EMIPLIB庫。feedbackexample例子可以實現在本機播放一個wav文件。但這種播放不是簡單的調用本機播放API來實現,而是利用EMIPLIB庫提供的RTP框架來完成。在不瞭解細節前,可以將這個過程簡單理解成這樣:讀取本地的一個wav文件,然後將其打包成RTP,再將這些RTP發送到feedbackexample監聽的一個UDP端口,feedbackexample再解析這些RTP包,並還原成可以播放的語音數據提交給本機聲卡。我通常在這種場合下使用這個例程。其他程序通過SIP協議註冊到一個語音交換平臺,只實現了SIP協議交互部分並未實現RTP。這時,就可以借用feedbackexample例程,只需將feedbackexample例程監聽的端口號以及對端的IP地址和端口號都設置成另一個程序中SIP協議交互得到的端口號即可。也就是說,通過這樣兩個簡單的程序可以實現一個非常簡易的SIP軟電話。一個程序負責SIP信令,另一個程序負責RTP語音傳輸。

這個例程很簡單,但也包含了足夠的信息去了解EMIPLIB的運作細節。接下來看看這些代碼究竟是如何做到的。

這個例程的代碼非常簡單,只有三個額外的輔助函數,以及一個類。這三個輔助函數完全可以不用去了解,這個類也只是爲了輸出一些日誌信息而已並沒有增加其他功能性方面的代碼。那關注點就可以落在main函數裏了。進入到main函數內,去除那些註釋後,真正有用的代碼從變量定義開始。

	MIPTime interval(0.020); // We'll use 20 millisecond intervals.
	MIPAverageTimer timer(interval);
	MIPWAVInput sndFileInput;
	MIPSamplingRateConverter sampConv, sampConv2;
	MIPSampleEncoder sampEnc, sampEnc2, sampEnc3;
	MIPULawEncoder uLawEnc;
	MIPRTPULawEncoder rtpEnc;
	MIPRTPComponent rtpComp;
	MIPRTPDecoder rtpDec;
	MIPRTPULawDecoder rtpULawDec;
	MIPULawDecoder uLawDec;
	MIPAudioMixer mixer;
	MyChain chain("Sound file player");

這些類型都是EMIPLIB庫提供的類。由於我們只是探究如何發送,所以這裏列出來的這麼多類型,我們只會關注一部分:

MIPTime
MIPAverageTimer
MIPWAVInput
MIPSamplingRateConverter
MIPSampleEncoder
MIPULawEncoder
MIPRTPULawEncoder
MIPRTPComponent

未列出的那些類型都是解碼相關的類。還得再重點說說MyChain類。這是個繼承類,父類是MIPComponentChain。名稱中最後的單詞是Chain。Chain是鏈、鏈條的意思。這個類是EMIPLIB運行時框架的核心類。是它將各個類組合在一塊,記錄相互關係,協調各個類的執行先後順序等。稍後我們將詳細研究這個類的源碼。

接下來的代碼就是初始化這些變量。中間會出現一些RTP開頭的類:

RTPSession
RTPUDPv4TransmissionParams
RTPSessionParams

這些是另一個庫jrtplib提供的類。jrtplib庫也是EMIPLIB庫的底層基礎。這三個類的目的就是爲了在網絡上傳輸接收RTP包。由於咱們這次研究的是EMIPLIB,所以這些類就不細說了。
初始化完後,接着就是調用MIPComponentChain的addConnection操作這些變量。類似於這樣:

	// Next, we'll create the chain
	returnValue = chain.setChainStart(&timer);
	checkError(returnValue, chain);
	returnValue = chain.addConnection(&timer, &sndFileInput);
	checkError(returnValue, chain);

註釋的意思是這是在創建鏈條。最後的啓動是調用MIPComponentChain的start。

chain.start();

main函數內的代碼基本情況就是這樣。分三個部分:初始化變量,構建一個鏈條,啓動這個鏈條。現在雖然沒有查看MIPTime和MIPWAVInput等類的聲明,基於鏈條的概念,以及MIPComponentChain類名的暗示,可以猜測出之前提到的那些MIP開頭的類應該都是類似於MIPComponent類的子類。還有一個可以解釋成鏈條、任務流的地方是這些類名。聲明的順序是MIPWAVInput、MIPSamplingRateConverter、MIPSampleEncoder、MIPULawEncoder、MIPRTPULawEncoder和MIPRTPComponent。依據這個順序我們分明看到了一個清晰的將本地wav文件打包成RTP數據包的任務流程。這也是一個佐證,MIPComponentChain是一個任務流執行體、任務鏈條。但這些畢竟是猜測,實際情況是不是正如猜測的那樣,還得分析了實現源碼再說。

MIPComponentChain

feedbackexample例程中使用了MIPComponentChain三個成員函數:setChainStart、addConnection和start。通過它們的名稱我們可以立即知曉這三個函數的意圖。第一個應該是設置一個起點。最後一個是啓動。第二個有點模糊,加一個connection。輸入參數是兩個Component(基於我們之前的猜測)。connection的意思是連接、聯繫。這麼來理解的話,這第二個函數的作用就是建立兩個component的關係。現在都是猜測,接下來看代碼。

setChainStart

這個函數確實很簡單。就是將輸入參數賦值給m_pInputChainStart成員變量。

bool MIPComponentChain::setChainStart(MIPComponent *startComponent)
{
	if (startComponent == 0)
	{
		setErrorString(MIPCOMPONENTCHAIN_ERRSTR_COMPONENTNULL);
		return false;
	}
	m_pInputChainStart = startComponent;
	return true;
}

注意到一點,輸入參數的類型是MIPComponent。猜測是對的,而且名稱完全猜對了。

addConnection

這個也很簡單,就是將兩個MIPComponent放入一個list內。inputConnections的類型是個std::list。之所以說簡單,是因爲暫時忽略後三個參數。

bool MIPComponentChain::addConnection(MIPComponent *pPullComponent, MIPComponent *pPushComponent, bool feedback,
		                     uint32_t allowedMessageTypes, uint32_t allowedSubmessageTypes)
{
	if (pPullComponent == 0 || pPushComponent == 0)
	{
		setErrorString(MIPCOMPONENTCHAIN_ERRSTR_COMPONENTNULL);
		return false;
	}
	
	m_inputConnections.push_back(MIPConnection(pPullComponent, pPushComponent, feedback, allowedMessageTypes, allowedSubmessageTypes));
	return true;
}

start

bool MIPComponentChain::start()
{
	if (JThread::IsRunning())        //如果已經在運行,不做其它操作,返回false。
	{setErrorString(MIPCOMPONENTCHAIN_ERRSTR_THREADRUNNING);return false;}
	if (m_pInputChainStart == 0)     //如果沒設置起始節點,不做其它操作,返回false。
	{setErrorString(MIPCOMPONENTCHAIN_ERRSTR_NOSTARTCOMPONENT);return false;}

	std::list<MIPConnection> orderedList;
	std::list<MIPComponent *> feedbackChain;
	
	if (!orderConnections(orderedList))
		return false;
	if (!buildFeedbackList(orderedList, feedbackChain))
		return false;
	copyConnectionInfo(orderedList, feedbackChain);

	m_stopLoop = false;
	if (JThread::Start() < 0)
	{setErrorString(MIPCOMPONENTCHAIN_ERRSTR_CANTSTARTTHREAD);return false;}
	return true;
}

起始處調用了JThread的IsRunning函數。難不成MIPComponentChain類繼承自JThread?查看這個類的頭文件。果然繼承自jthread類。JThread類也是由jrtplib庫提供。

class EMIPLIB_IMPORTEXPORT MIPComponentChain : private jthread::JThread, public MIPErrorBase

起始處的兩個判斷很容易理解。代碼中我已經添加了註釋。接着是定義兩個std::list。然後是依次調用orderConnections、buildFeedbackLis和copyConnectionInfo這三個成員函數。最後是調用JThread::Start()函數,這個函數很明顯目的就是啓動一個線程。在沒有具體分析orderConnections、buildFeedbackLis和copyConnectionInfo這三個成員函數前,對MIPComponnetChain::start函數的粗略認識就是最終要啓動一個線程,但在啓動線程前要做一些準備工作。那麼接下來就看看到底在啓動線程前都做了些什麼。先看看orderConnections。

orderConnections

這個函數內的第一步是遍歷m_inputConnections鏈表。之前這個鏈表在addConnection函數內出現過。現在可以回過頭再去看看,那是將兩個MIPComponent變量組合成一個MIPConnection變量,再放入m_inputConnections鏈表內。在這裏調用了MIPConnection的setMark函數,應該是將內部mark屬性置爲false。具體有何目的現在還不知道。

	for (it = m_inputConnections.begin() ; it != m_inputConnections.end() ; it++)
		(*it).setMark(false);

接着往下看。componentLayer是在函數起初處定義的,類型是std::list<MIPComponent *>。這是一個存儲MIPComponent指針的鏈表。m_pInputChainStart之前在setChainStart函數內出現過,這是存儲起始節點的變量。現在它被第一個放入了componentLayer鏈表內。

componentLayer.push_back(m_pInputChainStart);

然後是一個while循環。爲了看着方便,下面的代碼片斷去除了源碼中具有的日誌輸出語句。

while (!componentLayer.empty())
{
	std::list<MIPComponent *> newLayer;
	std::list<MIPComponent *>::const_iterator compit;
		
	for (compit = componentLayer.begin() ; compit != componentLayer.end() ; compit++)
	{
		for (it = m_inputConnections.begin() ; it != m_inputConnections.end() ; it++)
		{
			if (!(*it).isMarked()) // check that we haven't processed this connection yet
			{
				if ((*it).getPullComponent() == (*compit)) // check that this connection starts from the component under consideration
				{
					// mark the connection as processed
					(*it).setMark(true);
					// copy the connection in the ordered list
					orderedList.push_back(*it);

					// get the other end of the connection and add that
					// component to the new layer
					// we'll make sure that the component isn't already
					// in the list

					bool found = false;
					MIPComponent *component = (*it).getPushComponent();
					std::list<MIPComponent *>::const_iterator compit2;

					for (compit2 = newLayer.begin() ; !found && compit2 != newLayer.end() ; compit2++)
					{
						if ((*compit2) == component)
							found = true;
					}

					if (!found)
						newLayer.push_back(component);
				}
			}
		}
	}
		
	componentLayer = newLayer;
}

結束上面這個while循環的條件是componentLayer鏈表爲空。這個循環之前的一句是將起始節點放入這個鏈表內。也就是說,進入循環前鏈表的初始狀態是有一個元素在鏈表內。接着是兩個嵌套的for循環。循環的目的是將每個componentLayer鏈表內的元素取出後,再在m_inputConnections鏈表內遍歷一次做些操作。第二層for循環的第一句的註釋很清楚表明了目的,檢查這個MIPConnection是否被處理過。這也說明了這個函數內第一步的目的:確保m_inputConnections鏈表內所有的MIPConnection在處理前的處理標誌變量值都是“未處理”,也就是將mark置爲false。

如果這個MIPConnection未被處理過,繼續下面的判斷,檢查這個MIPConnection的“pull componnet”是不是當前正在檢查的MIPComponent。這個被檢查的MIPComponent是從componentLayer鏈表內取出來的。由於初始時componentLayer內只有初始節點,也就是說這兩個for循環的第一次迭代就是找到那個pull component是初始節點的MIPConnection。找到這個MIPConnection後將它的mark標誌置爲true,也就是說這個MIPConnection已經處理過了,後續再迭代時不要再處理它。然後將這個MIPConnection放入orderedList鏈表內。orderedList也是在函數開始處定義的,類型是std::list<MIPConnection>,也是個鏈表,存儲的是MIPConnection變量。然後取出這個MIPConnection內的“push component”,又進入另一個for循環,目的是在newLayer鏈表內尋找是否存在這個“push component”。如果newLayer內不存在這個“push component”,那麼將這個“push component”放入newLayer。執行完第一次兩個for循環的迭代後,再將newLayer賦值給componentLayer。此時,指的是第一次迭代,componentLayer內應該只有一個元素,就是與起始節點組合成一個MIPConnection的“push component”。由於componentLayer不爲空,所以while循環不會結束,繼續下一次迭代。第二次迭代的目的就是找到以這個“push component”爲“pull component”的MIPConnection,然後設置mark標誌,再取出另一個“push component”。至此,應該可以明白這個函數的目的了,就是以起始節點爲開端找到這個任務流內MIPConnection的執行順序,並依次放入orderedList內。最後還有一個for循環,目的是確保不再存在未被處理過的MIPConnection,如果有肯定是哪出錯了。最後將排過序的MIPConnection鏈表輸出到函數外。

由於最外圍的while循環肯定要在某種條件下結束。代碼裏顯示結束的標誌是componentLayer鏈表爲空,而每次循環最後一條語句是將newLayer變量賦值給componentLayer。也就是說while每次循環後如果newLayer爲空則while循環結束。換種說法就是每次whille循環如果沒有找到下一個push component那麼循環就結束了。按照這種思路倒推,在建立處理流時最後加入的MIPConnection變量裏的push component一定應該是個空指針。這樣的一個MIPConnection就是在告訴處理邏輯,處理流要結束了。

現在再回過頭去看start函數內調用這一函數的目的就是排序m_inputConnections鏈表:從起始的MIPComponent節點開始,依照MIPConnection類給出的前後依承關係,理清這個任務流的順序。現在已經搞明白了start函數內被調用的第一個函數,接着看下一個buildFeedbackList。

buildFeedbackList

這個函數需要兩個參數。一個是之前分析過的orderConnections函數生成的排過序的MIPConnection鏈表。另一個是輸出參數,是個存儲MIPComponent指針的鏈表。
函數起始處是個for循環。目的仍然是設置orderedList鏈表內MIPConnection元素的已處理標誌。如果MIPConnection的成員函數giveFeedback返回false,那麼設置MIPConnection已處理過,或者說這個MIPConnection可以不用再處理了。想詳細看看這個giveFeedback的具體實現如何,所有就去看看了。MIPConnection類定義竟然是在MIPComponentChain內部。giveFeedback只是簡單的返回內部成員變量m_feedback的值。這個成員變量值是在MIPConnection類生成時被賦的值。MIPConnection類都是在MIPComponentChain的addConnection函數內被創建的。追溯到這裏可以看到MIPConnection的m_feedback值的本源來自addConnection函數的feedback輸入參數。再檢查feedbackexample例程的代碼,幾乎所有編碼過程相關的MIPComponent的addConnection操作均沒有設置這個值,使用的是函數的缺省參數false。

chain.addConnection(&timer, &sndFileInput);
chain.addConnection(&sndFileInput, &sampConv);
chain.addConnection(&sampConv, &sampEnc);
chain.addConnection(&sampEnc, &uLawEnc);
chain.addConnection(&uLawEnc, &rtpEnc);
chain.addConnection(&rtpEnc, &rtpComp);

但大部分所解碼過程相關的MIPComponent的addConnection操作均設置了feedback輸入參數,並且使用的都是true。

chain.addConnection(&rtpComp, &rtpDec);
// This is where the feedback chain is specified: we want
// feedback from the mixer to reach the RTP audio decoder,
// so we'll specify that over the links in between, feedback
// should be transferred.
chain.addConnection(&rtpDec, &uLawDec, true);
chain.addConnection(&uLawDec, &sampEnc2, true);
chain.addConnection(&sampEnc2, &sampConv2, true);
chain.addConnection(&sampConv2, &mixer, true);
chain.addConnection(&mixer, &sampEnc3);
chain.addConnection(&sampEnc3, &sndCardOutput);

中間還夾着着一小段註釋。感覺是解釋feedback機制的,但看不太懂。到現在還是不知道何時以及爲何設置feedback標誌。接着看下面的代碼。接下來是一大段while。

while (!done)
{
	bool found = false;
	it = orderedList.begin();
	while (!found && it != orderedList.end()) {
		if (!(*it).isMarked() && (*it).giveFeedback()) found = true;
		else it++;
	}
	............
}

這段while的最前部是又一個while循環。遍歷檢查orderedList鏈表,找到第一個未被處理過、且giveFeedback返回true的MIPConnection。如果找到了,就立即結束這部分處理。從大的while循環角度看,每次都得再做這麼一次處理。也就是說,每次大的循環開始時,都得在orderedList鏈表內找出一個未被處理過、且giveFeedback返回true的MIPConnection。如果有一次遍歷找不到滿足條件的MIPConnection,那麼遍歷結束。此時有個猜測,那就是每次遍歷時必定會對被處理的MIPConnection執行setMark(true)操作。因此,就預先向下多看了一些代碼果然發現了這條語句:(*ite).setMark(true)。

現在我們找到了一個需要處理的MIPConnection。下面列出的代碼去除了一些註釋,重新做了些排布工作。首先是設置這個MIPConnection的處理標誌爲true,下次就不會再找到它了。然後是清空subChain,這是個鏈表,存儲的是指向MIPComponent的指針。然後將這個正在處理的MIPConnection的“pull component”和“push component”依次放入這個subChain。

然後是另一個while循環。這次遍歷是從剛纔在orderedList中找到的那個MIPConnection的後一個MIPConnection對象開始。首先這個MIPConnection的feedback標誌爲true,其次這個被檢查的MIPConnection的“pull component”等於這個被找到的MIPConnection(最外層遍歷每次得找到一個未被處理過、且feedback標誌爲true的MIPConnection)的“push component”,這個被檢查的MIPConnection的處理標誌被置爲true,且它的“push component”被放入subChain鏈表內。但如果後面有兩個滿足這樣條件的MIPConnection,說明有錯。如果還需遍歷,會從在這次遍歷中找到的MIPConnection的後面一個MIPConnection開始,且startIt存的是這次遍歷中找到的MIPConnection。也就是說,subChain存的是那些MIPConnection feedback標誌爲true的所有MIPComponent,且先放入“pull component”再放入“push component”。

while (!done)
{	............
	if (found) // ok, found a starting point, build the subchain
	{
		(*it).setMark(true);
		subChain.clear();subChain.push_back((*it).getPullComponent());subChain.push_back((*it).getPushComponent());
		(*it).setMark(true);
		std::list<MIPConnection>::iterator startIt = it;bool done2 = false;
		while (!done2) {
			std::list<MIPConnection>::iterator nextStartIt;
			bool foundNextStartIt = false;  it = startIt;  it++;
			while (it != orderedList.end()) {
				if ( (*it).giveFeedback() ) {
					if ((*it).getPullComponent() == (*startIt).getPushComponent()) {
						if (foundNextStartIt)
						{setErrorString(MIPCOMPONENTCHAIN_ERRSTR_CANTMERGEFEEDBACK);return false;}
						foundNextStartIt = true;
						nextStartIt = it;
						subChain.push_back((*it).getPushComponent());
						(*it).setMark(true);
					}
				}
				it++;
			}
			if ( !foundNextStartIt ) done2 = true;
			else startIt = nextStartIt;
		}
		// add the subchain to the feedbacklist in reverse		
		if (!feedbackChain.empty())
			feedbackChain.push_front(0); // mark new subchain
		std::list<MIPComponent *>::const_iterator it2;
		for (it2 = subChain.begin() ; it2 != subChain.end() ; it2++)
			feedbackChain.push_front(*it2);
	}
	else
		done = true;

在每次大的循環迭代處理過後,subChain存的是那些在這次迭代開始時在orderedList中找到的第一個feedback爲true的MIPConnection的“pull component”,且以這個MIPComponent爲起點的子任務鏈。這個子任務鏈是整個任務鏈的一小部分,它是整體的子集。這個子任務鏈的結束標誌有兩個,一個是到達了鏈尾,另一個是遇到了一個feedback標誌爲false的MIPConnection。我想這個處理過程應該是想找到所有這樣的子鏈。這樣的存在有它自身的意義,現在還不得而知。

最後一部分有點意思。將某次迭代中找到的subChain內元素按倒序方式放入feedbackChain鏈表內。兩兩子鏈以一個0做爲分割。最後將feedbackChain內元素拷貝給函數的第二個參數feedbackComponentChain。

這個函數的目的稍微清晰了點。找出一些子鏈,這些子鏈再按倒序放入一個鏈表內,子鏈間以0分隔。現在可以繼續start函數內出現的最後一個函數了,copyConnectionInfo。

copyConnectionInfo 

可以說這個函數非常簡單一目瞭然。就是將之前兩個函數orderConnections和buildFeedbackList處理的結果保存在成員變量中。

void MIPComponentChain::copyConnectionInfo(const std::list<MIPConnection> &orderedList, const std::list<MIPComponent *> &feedbackChain)
{
	std::list<MIPConnection>::const_iterator it;
	std::list<MIPComponent *>::const_iterator it2;

	m_chainMutex.Lock();
	
	m_orderedConnections.clear();
	m_feedbackChain.clear();
	
	for (it = orderedList.begin() ; it != orderedList.end() ; it++)
		m_orderedConnections.push_back(*it);
	for (it2 = feedbackChain.begin() ; it2 != feedbackChain.end() ; it2++)
		m_feedbackChain.push_back(*it2);
	
	m_pInternalChainStart = m_pInputChainStart;

	m_chainMutex.Unlock();
}	


 

至此,start函數內牽涉到的三個成員函數都看了一遍。大致瞭解了他們的作用。這些都只是爲了啓動線程前的準備工作。我們知道start函數的最終目的是啓動一個線程。那麼真正線程內的處理都是些什麼呢,哪個函數是做這件事的呢?掃一遍mipcomponentchain.cpp文件後發現Thread函數有點像。再進入這個函數內部看到起始處有這麼一句日誌輸出:

#ifdef MIPDEBUG
	std::cout << "MIPComponentChain::Thread started" << std::endl;
#endif // MIPDEBUG

這應該毫無疑問就是線程的入口函數了。接下來就看看它到底做了些什麼。

 

Thread

大致看了一眼,再加上之前有過線程編碼的經歷,可以推測出這個函數內最主要的部分應該就是一個循環。但不可能是個無限循環,肯定有退出機制。進入循環前應該有一些初始化操作。

bool done = false;//感覺這句賦值沒必要,下面有一句賦值語句。
bool error = false;//初始值爲false。
int64_t iteration = 1;
std::string errorComponent, errorString;
	
m_loopMutex.Lock();
done = m_stopLoop;//在啓動線程前,這個值被賦值爲false。
m_loopMutex.Unlock();
	
JThread::ThreadStarted();//調用父類的函數,這應該是JThread類的使用規範。
	
MIPSystemMessage startMsg(MIPSYSTEMMESSAGE_TYPE_WAITTIME);

上面這些初始化操作大部分都很好理解。只是又多了一個之前未遇到過的類:MIPSystemMessage。既然出現了,那就看看他的定義。這個類的頭文件裏有一段描述這個類作用的文字。

/** A system message.
 *  This kind of message is used to instruct a component to wait until messages can be
 *  distributed in the chain or to inform a component that an interval has elapsed.
 */

大致的意思是說,這個類的作用是消耗掉特定時長。有點類似於多線程編程中經常使用的Sleep函數,只不過在這將其封裝成了類。同時還發現,MIPSystemMessage繼承自MIPMessage。MIPMessage類也有一段解釋文字。從中可以看出,MIPMessage是個基類。MIPMessage消息將會從一個MIPComponent傳遞給另一個MIPComponent。

/** Base class of messages passed in a MIPComponentChain instance.
 *  This is the base class of messages distributed in a MIPComponentChain
 *  instance. Messages are distributed from component to component using
 *  the MIPComponent::pull and MIPComponent::push functions. The
 *  message type numbers can be found in mipmessage.h
 */

這同時也說明了鏈條中各個MIPComponent之間是如何通信的。同時,也應該注意到MIPComponentChain會生成一個子類型是MIPSYSTEMMESSAGE_TYPE_WAITTIME的MIPSystemMessage對象,並提供給鏈條中的第一個MIPComponent。也就是說,任何一個可以充當鏈條中第一個元素的MIPComponent必須可以處理子類型是MIPSYSTEMMESSAGE_TYPE_WAITTIME的MIPSystemMessage對象。以上就是循環前的初始化操作。接下來進入循環內部。此循環大致由三部分組成。一,針對鏈條初始節點的操作。二,遍歷m_orderedConnections鏈表。三,遍歷m_feedbackChain鏈表。這三步中任何一步執行完後都會檢查錯誤標誌,用來判斷是否需要立即結束循環。

第一階段。

第一步是調用初始節點的push函數。向其傳入初始化階段生成的startMsg變量。其他加鎖操作就不做分析了。

MIPTime::wait(MIPTime(0,0));
m_chainMutex.Lock();
m_pInternalChainStart->lock();
if (!m_pInternalChainStart->push(*this, iteration, &startMsg))
{
	error = true;
	errorComponent = m_pInternalChainStart->getComponentName();
	errorString = m_pInternalChainStart->getErrorString();
	m_pInternalChainStart->unlock();
	m_chainMutex.Unlock();
	break;
}
m_pInternalChainStart->unlock();

push操作如果失敗就結束整個循環,也就是說結束線程。這一步最關鍵的一點就是push操作都做了哪些事。必須以一個具體的實例來做說明才能對這一步有清晰的認識。正好再回到feedbackexample例程中代碼結合着來分析。在例程中設置的初始節點類型是MIPAverageTimer。打開mipaveragetimer.h文件看看這個類是如何定義的。下面這段內容很有幫助。

/** A simple timing component.
 *  This is a simple timing component which accepts MIPSYSTEMMESSAGE_WAITTIME system
 *  messages. It generates a MIPSYSTEMMESSAGE_ISTIME system message each time the
 *  specified interval has elapsed. Note that this is only on average after each interval:
 *  fluctuation will be present.
 */

意思是接收MIPSYSTEMMESSAGE_WAITTIME消息,產生MIPSYSTEMMESSAGE_ISTIME消息。產生消息前必須是經過了多少秒後。再看看push和pull兩個函數的實現。因爲這個類也是繼承自MIPComponent。所謂的“接收MIPSYSTEMMESSAGE_WAITTIME消息”是在push函數內實現的。push函數內會判斷輸入的MIPMessage變量類型是否滿足要求。其次,push函數內還實現了“經過了多少秒後”,可以在源碼中看到下面這句:

MIPTime::wait(MIPTime(diff));

diff的值由下面方法生成:

	MIPTime curTime = MIPTime::getCurrentTime();
	real_t diff = (m_startTime.getValue()+((real_t)iteration)*m_interval.getValue())-curTime.getValue();

m_interval的值在創建MIPAverageTimer時指定,例程中給出的值是0.02。m_startTime的值在創建MIPAverageimer時指定,實際值是創建時的系統時間。iteration是迭代值,每次Thread的for循環執行一次累加這個值,並傳給所有的MIPComponent對象。curTime是MIPAverageTimer的push函數被調用時的時間。diff值的含義是,如果每次迭代都消耗了0.02秒,那麼diff值就是這次push被調用時的時間與理想情況下應該被消耗的時間的差值。如果這個值大於零,說明實際情況是之前的處理有些快需要減慢,所以就休眠一段時間。
現在再回到第一部分的處理場景中。加上之前針對MIPAverageTimer類的分析。現在能夠明白這第一部分都做了些什麼:執行了MIPAverageTimer的push函數。startMsg變量正是push函數所需要的類類型。由於滿足了這些條件,所以push函數內還執行了休眠操作。也就是說,這第一部分的真正作用就是休眠了固定時長。也就是說每次迭代的第一步都是休眠固定時長。

第二階段/
接着看第二步:遍歷m_orderedConnections。現在我們知道這個鏈表存儲的是一個有序的MIPConnectiont集合。其實也是有序的MIPComponent集合。因爲前一個MIPConnection的pull component就是後一個MIPConnection的push component。

for (it = m_orderedConnections.begin() ; !error && it != m_orderedConnections.end() ; it++)
{
	MIPComponent *pPullComp = (*it).getPullComponent();		MIPComponent *pPushComp = (*it).getPushComponent();
	uint32_t mask1 = (*it).getMask1();		uint32_t mask2 = (*it).getMask2();
	pPullComp->lock();		pPushComp->lock();
	MIPMessage *msg = 0;
	do {
		if (!pPullComp->pull(*this, iteration, &msg))
		{error = true;errorComponent = pPullComp->getComponentName();errorString = pPullComp->getErrorString();}
		else {
			if ( msg ) {
				uint32_t msgType = msg->getMessageType();uint32_t msgSubtype = msg->getMessageSubtype();
				if ( ( msgType&mask1 ) && ( msgSubtype&mask2 ) ) {
					if ( !pPushComp->push(*this, iteration, msg) )
					{error = true;errorComponent = pPushComp->getComponentName();errorString = pPushComp->getErrorString();}
				}
			}
		}
	} while (!error && msg);
			
	pPullComp->unlock();
	if (pPushComp->getComponentPointer() != pPullComp->getComponentPointer())
		pPushComp->unlock();
}

每次遍歷都會取出一個MIPConnection,目的是得到這個MIPConnection的pull component和push component。然後每次從pull component中取出一個MIPMessage再提供給push component。結束某一個MIPConnection的處理條件是無法再從pull component取出MIPMessage,或者出錯了。我們隱約能感覺到消息在整個處理鏈條中被傳遞的過程。

第三階段。
現在進入第三步,feedback鏈條。處理feedback鏈條會用到一個類MIPFeedback。應該還記得,m_feedbackChain存儲的是多個feedback鏈條,鏈條間以空指針分隔。所以在遍歷處理過程中也看到了針對這個情況的處理。每次重新一個新的子鏈條的處理前要重置feedback變量。除此之外每次遍歷時,都是調用鏈條中MIPComponent的processFeedback函數。processFeedback函數接收的參數中就包括MIPFeedback類型的變量。之前已經提到過,每次一個新的子鏈條處理前都會重置MIPFeedback變量。也就是說,每個子鏈條內MIPComponent間傳遞消息是通過MIPFeedback類。目前爲止還只是猜測。接着看MIPFeedback類的說明。

/** Message passed through a feedback chain.
 *  A MIPFeedback object is used in a feedback chain of a MIPComponentChain instance.
 *  Each object in the same chain can inspect and/or modify the information in the
 *  MIPFeedback instance.
 */

這段關於類用途的註釋佐證了我們之前的猜測。第三部分的框架其實很簡單。處理m_feedbackChain內保存的每個feedback鏈條。鏈條內MIPComponent間通過MIPFeedback傳遞消息。但是,EMIPLIB庫設計這個feedback鏈條的目的是什麼仍然不清晰。

通過分析例程代碼,以及庫源碼,大致瞭解了該如何使用EMIPLIB庫和EMIPLIB庫內部實現。現在知道了爲了向網絡的一個端點發送語音,該使用哪些MIPCompnent組件,該如何建立這些組件間在運行期間的關係。同時,也知曉在運行期EMIPLIB將會創建一個線程在後臺執行語音數據的轉換和發送。基本的過程是這樣的。先創建一些語音數據轉換和發送用的MIPCompnent組件。然後初始化這些組件實例。接着創建一個MIPComponentChain類實例。指定一個初始MIPComponent節點。接下來依照順序加入MIPComponent對象實例。最後一步就是調用MIPComponentChain類的start函數。

經過上述的分析,現在知道了該如何啓動一個將本地文件打包成RTP併發送給特定網絡地址端口的過程。可以在任何時間暫停或者停止這樣一個過程嗎?我們再看看MIPComponentChain類的源碼,是否存在一個這樣的成員函數。發現還有這樣幾個成員函數未仔細分析:

stop()
rebuild()
clearChain()
deleteConnection()


從函數名稱中可以大致知曉這幾個成員函數的用途。沒有找到暫停這樣一個過程的函數,只是找到了結束這樣一個過程的函數,stop。這四個函數的內部邏輯都非常簡單,就不一一羅列了。除了stop函數外,其他三個函數都未在類內部被調用過。

 

現在已經瞭解到,MIPComponentChain類在EMIPLIB庫中所處的核心位置。這個類實現了EMIPLIB庫的處理框架。它負責協同調用各個MIPComponent組件。MIPComponent組件間通過MIPMessage消息類傳遞信息。這篇分析文章只是揭示了EMIPLIB在高層是如何運作的。接下來有必要深入到具體的MIPComponent類源碼中一探究竟。





 

發佈了82 篇原創文章 · 獲贊 2 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章