EMIPLIB库分析一

sofia sip开发九 使用EMIPLIB库增加RTP流处理 sofia sip开发 阅读详情

通过分析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类源码中一探究竟。





 

EMIPLIB-1.0.0的使用(二) 2011.03.31  By Pafone 这次来编译soundfileplayer,新建一个工程。像EMIPLIB-1.0.0的使用(一)那样在工程属性加入include的目录、库目录和库,还要额外加一个库winmm.lib,这个库不用指定路径。 编译: 1.编译时,说出找不到unistd.h,注释掉即可,原因在上篇文章已经说过。 2.  链接错 1>正在链接... 1>emiplib-1.0.0.lib(mipwinmmoutput.obj) : error LNK2001: 无法解析的外 阅读详情

相关推荐

libSamplerate-重采样库lib.zip

音频重采样音频重采样音频重采样音频重采样音频重采样音频重采样

EMIPLIB使用入门

                                                                       mwg_arden 的草稿,转载需注明。这个库遵守LGPS,所以文档也遵守。  EMIPLIB(http://research.edm.uhasselt.be/emiplib)的全称是EDM Media over IP libray 。EDM是Has

4212

libsamplerate:音频采样率转换库

libsamplerate:音频采样率转换库

EMIPLIB库分析二

前一篇中详细分析了MIPComponentChain类。

Neohan的专栏 6957

emiplib chm文档

方便查阅的emiplib 1.1.1 chm文档,由dot+htmpworkshop+doxygen完成。

Proteus仿真实战:用AT89C51+L298N搭建直流电机PID调速系统(附完整工程文件)

本文详细介绍了使用Proteus仿真平台结合AT89C51单片机和L298N驱动模块搭建直流电机PID闭环调速系统的全过程。从硬件选型、电路连接到PID算法实现与参数整定,提供了完整的开发指南和工程文件,帮助读者快速掌握闭环调速系统的设计与调试技巧。

weixin_28724661的博客 447

EMIPLIB简介

 EMIPLIB(http://research.edm.uhasselt.be/emiplib)的全称是EDM Media over IP libray 。EDM是Hasselt University大学的Expertise Centre for Digital Media  ,这个库就是这里开发的。 这个库能做什么呢?官方是这么描述的(版本0.14.0):·     Sound

GZFStudy的专栏 2130

车载面试题目(ADAS)

多数功能都是依靠的前置传感器(经过Lidar、Radar、Camera融合),比如ACC、AEB、LKA、NOA、TJA、FCW等,只有少数涉及周视传感器,比如BSD、CVW、DOW。③如果前面有车切速度小于自车车速,自车减速对减速度有要求,比如3.5m/m2,④如果前车静止了,自车有个跟停距离要求,比如2-3m。试验场有不同曲率的弯道,比如125m、250m、500m的,根据需求跟车行驶,看是否会减速,能不能驶出弯道等。迭代内容:底层通讯、网络管理、诊断、刷写、信息安全、OTA。

qq_43475097的博客 2297

EMIPLIB的使用及扩展(二)

2.EMIPLIB的消息机制 EMIPLIB组件对象间,采用消息(Message)驱动机制实现数据的交流与互通。换句话说,组件间数据的交互,只能依靠消息的传递实现。 依据本节第一部分描述的“EMIPLIB的功能模块划分”,将组件分为不同功能类别,对应的,也将消息划分为不同种类,只有相同或者相近功能的组件间才共享同一定义的消息。具体的系统消息结构见下图所示: ...

weixin_34296641的博客 252

自相关互相关卷积的_相关性(Correlation)和相干性(Coherence)

1. 概念相关性(Correlation,或称相关系数或关联系数),显示两相关变量之间线性关系的强度和方向。在统计学中,相关的意义是用来衡量两个变量相对于其相互独立的距离。相干性(Coherence),与相关性计算得到的信息非常相似,都是衡量两个变量之间的相关程度,只是相干性多用于频域计算,可以在基于频率上给出更多的信息,而相关性多用于普通的数组计算和时域信号。这里其实应该有三个概念,c...

weixin_39963053的博客 3332

[cryoEM] cryoSPARC简介

cryoSPARC系统结构概述 cryoSPARC是一个后端和前端高性能计算软件系统,为单颗粒冷冻电镜提供数据处理和图像分析能力,以及丰富的基于浏览器的用户截面和命令行工具。 Master-worker模式 系统基于主用户模式。 主进程(web应用,核心应用程序和MongoDB数据库)一起在主节点上运行。主节点需要相对轻量级的资源(2+ CPU< 16GB+ RAM)。 工作程序进程可以在具有GPU的任何可用节点上生成,负责所有实际计算和数据处理,并由主节点调度。注意:同一节点可以同时充当主节点和辅

进阶中的程序员吃吃的博客 7795

EMIPLIB的使用及扩展(一)

1.1.3 EMIPLIB简介 EMIPLIB全称为“EDM Media over IP Library”,是比利时哈瑟尔大学下属研究机构Expertise Centre for Digital Media (EDM)研发的开源代码,此库用于串联流媒体系统所必须的各个功能模块:音视频数据采集(Input)与播放(Output)、编码(Encode)与解码(Decode)、网...

weixin_34115824的博客 198

DSI3接口介绍

DSI3(distributed system interface)分布式系统接口,是一种强大且通用的总线协议,旨在将多个遥感器和执行器互连到中央控制模块。在单总线中集成了电压信号、电流信号还有电源,甚至引用了3位状态机概念(反向数据),从逻辑0/1扩展到了0/1/2。

Airer_00的博客 4295

EMIPLIB库分析三 MIPWAVInput

dfd

Neohan的专栏 811

把慢点掰开看:在 SAP HANA 中启用 SQL 优化耗时跟踪 SqlOptTime 的实战指南

摘要:SAP HANA 性能排查中,常出现SQL查询时快时慢的现象,表面看似执行问题,实则是编译阶段耗时过长。通过SQL优化跟踪工具SqlOptStep和SqlOptTime,可精准区分编译与执行时间。SqlOptStep展示优化树变化过程,而SqlOptTime则记录各阶段耗时,帮助定位问题根源。启用SqlOptTime需配置traceprofile,建议限定特定用户以减少系统负载。实际案例显示,报表响应慢常由计算视图编译(CeQo)导致,而非执行阶段问题。解决方案包括调整计算视图展开方式或优化基础视图结

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1005

Python爬虫:抓取餐饮平台顾客评分与评价的完整教程

本文介绍了如何使用Python爬虫技术抓取餐饮平台上的餐厅评分和评论数据。我们通过三种主要的抓取方法:静态网页抓取(requests)、动态网页抓取(Selenium)和API抓取,展示了如何抓取餐饮平台的数据。还介绍了如何使用pandas对抓取的数据进行存储、清洗、分析,并进行可视化和情感分析。通过本文的学习,您应该能够掌握如何使用Python爬虫抓取餐饮平台的评论数据,并将这些数据应用于市场分析、顾客反馈和服务优化等领域。

2201_76125261的博客 1144

EMIPLIB 简介

EMIPLIB()http://research.edm.uhasselt.be/emiplib的全称是EDM Media over IP libray 。EDM是Hasselt University大学的Expertise Centre for Digital Media  ,这个库就是这里开发的。 这个库能做什么呢?官方是这么描述的(版本0.14.0):·       Soundc

GZFStudy的专栏 3543

C# 从数秒到毫秒:MySQL+Redis百万数据优化实战全解析

在处理百万级数据时,传统单体数据库常面临查询效率低下、数据库负载过高和用户体验差等问题。为解决这些性能瓶颈,可以采用MySQL分库分表、Redis缓存和异步统计的组合策略。 数据库分片:通过水平分表和分库实现,将单表数据分散到多个物理表或数据库实例中,降低单点负载。 Redis缓存:利用Redis缓存高频数据,减少数据库查询压力,提升响应速度。 异步统计:通过消息队列(如RabbitMQ)解耦统计任务,后台处理非实时任务,避免阻塞主流程。 这些优化策略能够显著提升系统性能,确保在高并发和大数据量场景下的稳定

墨夶的博客 852

EMIPLIB1.0.0的编译

EMIPLIB是一个用RTP传输音视频的开源库,totally free。 目前最新版是1.0.0  在这里下载http://research.edm.uhasselt.be/emiplib/emiplib.html emiplib-1.0.0.tar.gz 依赖项:基本的支持声音的用前三个便可以了 1.jrtplib http://research.edm.uhasselt.be/~jori/page/index.php?n=CS.Jrtplib 2.jthread http://researc

pafone的专栏 1816

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

EMIPLIB1.0.0的使用(一)

上次已经编译通过了EMIPLIB-1.0.0,下面来学习怎么使用EMIPLIB。先从里面的例子开始。 先编译下最简单的例子。 example文件夹里的simplechain.cpp 新建一个VC2008工程,源文件里添加 simplechain.cpp,在项目属性里设置包含头文件目录,库文件目录,库文件名 这个例子有用到的头文件:jthread-1.2.1, jrtplib-3.7.1, emiplib-1.0.0  用到的三个库也是对应的。 暂时没有用到speex。 编译时提示找不到unistd.

pafone的专栏 2077

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

学习小结-关于emiplib

EMIPLIB的全称是“EDM Media over IP Iibray”,是Hasselt University 大学开发的。这个库的目的是使各种多媒体能在网络上方便传输, 此库提供了从底层的采集,编码解码,传输等功能。加上jrtplib和jthread ,还有如在windows 下开发时的对音频要有winmm.lib库的支持, 对视频的有dshow 以及qt for windous 之内的软件

2227

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

emiplib调用声卡失败

<br />应用程序使用emiplib的API编写,但打开设备(指声卡)失败。/dev/dsp与/dev/audio也不存在。<br />使用padsp命令运行程序即可,具体内容看padsp手册。<br />例: padsp ./soundfileplayer

湘江鸿的技术专栏 732
上一篇: 正则表达式示例
下一篇: EMIPLIB库分析二
Neohan
博客等级 码龄26年 33粉丝 82原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值