BCM VOIP 基本呼叫流程分析

呼叫过程中各控制块的关联
 
A层:物理线路层,衔接底层驱动,及callmgr模块的线路对象
B层:CallMgr模块线路层,衔接物理线路层,呼叫资源控制块,以及CallCtrl模块的呼叫控制块。该层比较重要,在该层每个线路控制块对应唯一的物理线路控制块,以及N个呼叫资源(每个呼叫资源包含一个呼叫资源控制块和一个呼叫控制块)。
C层:呼叫资源层,包含CallMgr模块的呼叫资源控制块,和CallCtrl模块的呼叫控制块。
D层:RTP资源控制块层,每个CallMgr呼叫资源控制块分配一个RTP资源控制块。
E层:数据流层,其中rtpHandle控制块用于网络侧处理,cmStream与底层驱动的连接资源关联。

主叫流程
摘机
1、EndpointEventTask线程收到endpt模块底层上报的摘机事件
//调用用户设置的回调进行事件处理,该回调函数为cmEndptEventCb
endptState.lineId = tEventParm.lineId;
(endptUserCtrlBlock.pEventCallBack)(&endptState, tEventParm.cnxId, tEventParm.event, tEventParm.eventData, tEventParm.length, tEventParm.intData );

2、cmEndptEventCb,这里cmEndptEventCb被映射为宏
#define VRG_CMGR_ENDPOINT_EVENT_CALLBACK\
cmEndptEventCb( ENDPT_STATE *endptState, int cxid, EPEVT event, int data )

void VRG_CMGR_ENDPOINT_EVENT_CALLBACK
evntp.command = 0;
evntp.op1 = (endptState != NULL) ? endptState->lineId : CMENDPT_UNASSIGNED;
evntp.op2 = event;
evntp.op3 = data;
//将事件放入callmgr模块事件队列
cmQueueEvt( CMEVT_CLASS_EPT, &evntp);

3、callmgr线程处理CMEVT_CLASS_EPT类事件
cmProcessEptEvent( cmdp)
	//获取cm关联线路对象
	cmEndptIndex = cmMapPhysEndptToCm( cmdp->op1 );
	
	switch( cmdp->op2)
	case EPEVT_OFFHOOK:
		//映射为callmgr事件
		//event = CMEVT_OFFHOOK
		event = cmMapByEvt( cmEptCasMap, cmdp->op2);
		
		//处理endpt事件
		cmEndptNotify( cmEndptIndex, UNKNOWN, event, digit);
			//用户线路没有关闭才进行处理
			if( !(*((UINT8 *)cmProvisionGet(PROVIS_SIP_USER_DISABLED, endpt))) )
				//进入endpt状态引擎
				cmEndptStateEngine( endpt, cid, event, data );
					switch ( FSM( event, state))
					//挂机状态下处理摘机事件
					case FSM( CMEVT_OFFHOOK, CMST_ONHOOK):
						//如果当前线路没有注册,则放错误提示音,并将cm状态迁为
						// CMST_WAITONHOOK状态
						if ( ep->inservice == FALSE )
							cmTone( ep, EPSIG_REORDER,……)
							state = CMST_WAITONHOOK;
							break;
						
						//补充业务状态机处理,当前没有业务处理。
						cmSSStateEngine( endpt, CMEVT_OFFHOOK, ep->cmSsBlk.service );
						
						//当前没有针对CMEVT_OFFHOOK事件的处理
						cmPublishEvent( endpt, CMEVT_OFFHOOK, 0);
						
						//当前线路还没有资源连接,进入此分支处理
						if ( ep->cnxCount == 0 )
							//当前没有开启热线业务,不处理
							if ( ep->callwarmline )
								xxx
							
							//当前没有消息信息,也没有补充业务,放普通拨号音
							//在cmTone函数中,含有放音定时器超时时间,该值设置								//到ep->timer中,在callmgr主循环的cmEndptTimer中会
							//周期递减该值,超时后会调用
							// cmEndptStateEngine( ep, UNKNOWN, CMEVT_TIMEOUT, 0 );
							//给callmgr发送CMEVT_TIMEOUT事件。
							if ( !ep->newMsgWaiting )
								cmTone( ep, ((ep->callfwd_all || ep->donotdisturb) ? 
								EPSIG_SPECIAL_DIAL : EPSIG_DIAL),
cmProvisionGet2(PROVIS_COMMON_TONE_TIMEOUT,
ep->devid, APP_TONE_DIAL)) );
							
							//复位数图号码收集池ep->dialstr
							cmResetDigits( ep);
							
							//无处理
							cmDisplay( endpt, UNKNOWN, CMLCD_PROMPT);
							
							//cm状态迁为CMST_DIALING
							state = CMST_DIALING;
摘机后不按键直到DialTone超时
//在cmTone函数中,含有放音定时器超时时间,该值设置到ep->timer中,在callmgr主
//循环的cmEndptTimer中会周期递减该值,超时后会调用

cmEndptStateEngine( ep, UNKNOWN, CMEVT_TIMEOUT, 0 );
	switch ( FSM( event, state))
	//在拨号状态收到超时事件
	case FSM( CMEVT_TIMEOUT, CMST_DIALING):
		//当前没有设置热线业务,不进入此分支
		if ( ep->callwarmline && !ep->warmLineTimer && !strlen(ep->dialstr.digits) )
			xxx
		
		//一次性业务标记复位
		cmFeatClean( ep->devid );
		
		//放警示音
		cmTone( ep, EPSIG_OHWARN,……);
		
		//迁为CMST_WAITONHOOK状态,后继如果用户不挂机,则一直重复进入此状态
		state = CMST_WAITONHOOK;
用户按键
1、callmgr线程处理CMEVT_CLASS_EPT类事件
cmProcessEptEvent( cmdp)
	//获取cm关联线路对象
	cmEndptIndex = cmMapPhysEndptToCm( cmdp->op1 );
	
	switch( cmdp->op2)
	case EPEVT_DTMFX:
		//映射为对应按键字符
		digit = cmMapById( cmEptToneMap, cmdp->op2);
		
		//如果是键按下事件
		if( cmdp->op3 == EPDTMFACT_TONEON )
			//标记开始统计按键时长,统计处理在callmgr主循环的定时器代码中
			cmEndpt[cmEndptIndex].dtmfDurMs = 0;
			cmEndpt[cmEndptIndex].dtmfDurCollect = TRUE;
			
			//如果之前有放播号音,则停止
			if( (cmEndpt[cmEndptIndex].state == CMST_DIALING) &&
(cmEndpt[cmEndptIndex].curTone != EPSIG_NULL) )
	cmTone( &cmEndpt[cmEndptIndex], EPSIG_NULL,
cmEndpt[cmEndptIndex].timer );
		//如果是键松开事件
		else if( cmdp->op3 == EPDTMFACT_TONEOFF )
			//停止统计按键时长
			cmEndpt[cmEndptIndex].dtmfDurCollect = FALSE;
		
		//给callmgr发送CMEVT_DIGIT事件
		cmEndptNotify( cmEndptIndex, UNKNOWN, CMEVT_DIGIT, digit );
			//进行endpt状态引擎处理
			cmEndptStateEngine( endpt, cid, event, data );
				//在拨号状态收到按键事件
				switch ( FSM( event, state))
				case FSM( CMEVT_DIGIT, CMST_DIALING):
					//复位数图定时器
					ep->digitTimer = 0;
					
					//收到按键后,取消热线业务
					if ( ep->callwarmline )
						ep->warmLineTimer = 0;
					
					//号码收集处理
					rc = cmCollectDigits( ep, (char) data);
						//标记初始处理结果为CMDS_INCOMPLETE
						rc = CMDS_INCOMPLETE;
						
						//存储新的按键到号码池
						len = strlen(ds);
						ds[len] = newdigit;
						
						//遍历业务码,如果当前按键匹配到则设置featIx
						for( i = 0 ; i < MAX_FEATURE_CODES ; i++ )
							if( (cmFeatureCodeMap[ep->devid][i].id != UNKNOWN) &&
							(!strncmp( ds, cmFeatureCodeMap[ep->devid][i].dialString,
							strlen( cmFeatureCodeMap[ep->devid][i].dialString ))))
								featIx = i;
								break;
						
						//如果当前按键匹配到业务码,并且对应的业务已经开启,同时
						//该业务不需要继续收号,则返回CMDS_FEATURE处理结果。
if( (featIx != UNKNOWN) && !(((CMFEATUREACTION) cmProvisionGet2( PROVIS_COMMON_FEATURESTRING_ACTION,
ep->devid, cmFeatureCodeMap[ep->devid][featIx].provId )) == CMFEATACTION_COLLECT) || cmProvisionGet2( PROVIS_COMMON_FEATURESTRING_ACTION, ep->devid, cmFeatureCodeMap[ep->devid][featIx].provId )) == CMFEATACTION_SPECIAL)) && cmProvisionGet2( PROVIS_COMMON_FEATURESTRING_ENABLED, ep->devid, cmFeatureCodeMap[ep->devid][featIx].provId ) == TRUE))
	return CMDS_FEATURE;

//如果当前按键已经匹配到业务码,并且对应的业务已经开启,
//同时该业务需要继续收号,则进行处理。
if( (featIx != UNKNOWN) && (((CMFEATUREACTION)(*(UINT8 *)cmProvisionGet2( PROVIS_COMMON_FEATURESTRING_ACTION, ep->devid, cmFeatureCodeMap[ep->devid][featIx].provId )) == CMFEATACTION_COLLECT) || ((CMFEATUREACTION)(*(UINT8 *)cmProvisionGet2( PROVIS_COMMON_FEATURESTRING_ACTION, ep->devid, cmFeatureCodeMap[ep->devid][featIx].provId )) == CMFEATACTION_SPECIAL)) && (*(UINT8 *)cmProvisionGet2( PROVIS_COMMON_FEATURESTRING_ENABLED, ep->devid, cmFeatureCodeMap[ep->devid][featIx].provId ) == TRUE ))
	//如果当前按键已经超过当前匹配的业务码的长度,则标记
	//可以进行业务检测
	if( len >=
strlen( cmFeatureCodeMap[ep->devid][featIx].dialString ))
	featureDetected = TRUE;
	len= strlen( cmFeatureCodeMap[ep->devid][featIx].
dialString );
ds = &ds[len];
len = strlen( ds ) - 1;
							//否则返回CMDS_INCOMPLETE结果需要继续接收按键
							else
								return CMDS_INCOMPLETE;
						
						//如果当前按键长度已经超过业务码长度,并且匹配成功,则							//业务检测标记为TRUE,同时如果当前业务的动作为特定的
						// CMFEATACTION_SPECIAL,则进行业务匹配处理。
if( (featureDetected == TRUE) &&
cmProvisionGet2( PROVIS_COMMON_FEATURESTRING_ACTION,
ep->devid, cmFeatureCodeMap[ep->devid][featIx].provId )) ==
CMFEATACTION_SPECIAL) )
	val = cmFeatureMatch(ep, 
&cmFeatureCodeMap[ep->devid][featIx], strlen(ep->dialstr.digits) ));
	//默认处理结果
	ret = CMDS_INCOMPLETE;

	switch( pFeature->id )
	//处理呼叫限制业务
	case CMSS_CALL_BARRING:
		pinLen = strlen((char
 		*)cmProvisionGet(PROVIS_COMMON_USERPIN,
pEndpt->devid ));
		featLen = strlen( pFeature->dialString );
		
		//如果号码收集完全,则返回CMDS_FEATURE结果
		if( length == (pinLen + featLen + 1) )
			ret = CMDS_FEATURE;
		//如果号码收集已经超出,则返回CMDS_ERROR
		else if( length > (pinLen + featLen + 1) )
			ret = CMDS_ERROR;
	default:
		//其它该类型的业务处理都返回CMDS_ERROR
		ret = CMDS_ERROR;
	
	return 	ret;

return 	val;

						//进行普通数图匹配处理
						switch (callCheckDialString( ds, ep->dialPlan))
						//匹配失败
						case CCRSP_DSNOMATCH:
							//如果之前没有走到业务匹配流程,再次进行业务码匹配,
							//如果匹配不到则返回CMDS_ERROR,如果匹配到了则返回
							//默认处理回应CMDS_INCOMPLETE
							if( featureDetected == FALSE )
								featIx = UNKNOWN;
								for( i = 0 ; i < MAX_FEATURE_CODES ; i++ )
									if( (cmFeatureCodeMap[ep->devid][i].id !=
UNKNOWN) &&
(!strncmp( ds,
cmFeatureCodeMap[ep->devid][i].dialString,
strlen( ds ))))
	featIx = i;
	break;
								if( featIx == UNKNOWN )
									rc = CMDS_ERROR;
							//返回CMDS_ERROR
							else
								rc = CMDS_ERROR;
						
						//匹配成功
						case CCRSP_SUCCESS:
							if ( featureDetected == TRUE )
								//如果是业务检测流程则返回CMDS_FEATURE
								rc = CMDS_FEATURE;
							else
								//号码池保留,用于重拨业务
								ep->redial = ep->dialstr;
								
								//如果按键为“#”键,则认为是快速送号,删除最后
								//的“#”键
								if (newdigit == '#')
									ds[ len] = '\0';
								
								//返回 CMDS_NEWCALL
								rc = CMDS_NEWCALL;
						
						//部分匹配
						case CCRSP_DSPARTMATCH:
							//在部分匹配情况下,尝试数图规则中是否可以匹配到含有							//T定时器指示符的规则,如果匹配到了则返回
							// CMDS_DIGIT_TIMEOUT,否则返回默认的处理回应
							//CMDS_INCOMPLETE
							strcpy(dsTemp,ds);
							len = strlen(dsTemp);
							dsTemp[len] = 'T';
							switch (callCheckDialString( dsTemp, ep->dialPlan))
							case CCRSP_SUCCESS:
								rc = CMDS_DIGIT_TIMEOUT;
								break;
							default:
								break;
					
					//处理数图匹配返回结果
					switch (rc)
					
					//匹配成功
					case CMDS_NEWCALL:
						//下面单独分析数图匹配成功的流程。
					
					//匹配失败
					case CMDS_ERROR:
						//复位已经收集的用户按键
						cmResetDigits( ep);
						
						//放错误提示音,并设置线路定时器
						cmTone( ep, EPSIG_REORDER, 
						cmProvisionGet2(PROVIS_COMMON_TONE_TIMEOUT, ep->devid,
						APP_TONE_REORDER)) );
						
						//cm endpt迁移到CMST_WAITONHOOK状态
						state = CMST_WAITONHOOK;
					
					//匹配到业务码
					case CMDS_FEATURE:
						//基本呼叫分析中暂时不分析业务处理流程。
					
					//部分匹配
					case CMDS_INCOMPLETE:
						//停止放音,仅设置线路侧定时器
						cmTone( ep, EPSIG_NULL, 
						cmProvisionGet(PROVIS_COMMON_DIGITTIMER_PART,
ep->devid)) );
					
					//匹配到含定时器指示符的规则
					case CMDS_DIGIT_TIMEOUT:
						//停止放音,仅设置线路侧定时器
						cmTone( ep, EPSIG_NULL, 
						cmProvisionGet(PROVIS_COMMON_DIGITTIMER_PART,
ep->devid)) );

//设置线路侧数图定时器
ep->digitTimer=cmProvisionGet(
PROVIS_COMMON_DIGITTIMER_CRIT, ep->devid));
数图匹配定时器指示符,数图定时器超时
在callmgr主循环中,cmDigitTimer函数处理ep->digitTimer超时。

//触发cm endpt状态引擎
cmEndptStateEngine( ep, UNKNOWN, CMEVT_DIGIT_TIMEOUT, 0 );
	switch ( FSM( event, state))
	//在拨号状态触发CMEVT_DIGIT_TIMEOUT事件
	case FSM( CMEVT_DIGIT_TIMEOUT, CMST_DIALING):
		//传入一个特殊参数,在cmCollectDigits中判断此特殊参数进行独立处理。大体处
		//理流程和上面分析的类似,不再进行详细描述,定时器指示符的处理结果仅返回
		//3种,CMDS_NEWCALL、CMDS_INCOMPLETE、CMDS_FEATURE
		rc = cmCollectDigits( ep, (char) 'T');
		switch (rc)
		
		//匹配成功
		case CMDS_NEWCALL:
		//该流程与上面数图匹配成功流程相同,不单独进行分析。
	
	//在指示符超时后处理流程中,除了匹配成功及匹配到业务码,都认为是失败
	case CMDS_ERROR:
	case CMDS_CONTINUE:
	case CMDS_INCOMPLETE:
		//放错误提示音
		cmTone( ep, EPSIG_REORDER,
cmProvisionGet2(PROVIS_COMMON_TONE_TIMEOUT, ep->devid,APP_TONE_REORDER)) );

//复位收集的用户按键,并将cm endpt迁为CMST_WAITONHOOK状态
cmResetDigits( ep);
state = CMST_WAITONHOOK;
		
		//匹配到业务码
		case CMDS_FEATURE:
			//基本呼叫分析中暂时不分析业务处理流程。
数图部分匹配,线路定时器超时
在callmgr主循环中,cmEndptTimer函数处理ep-> timer超时。

//触发cm endpt状态引擎
cmEndptStateEngine( ep, UNKNOWN, CMEVT_TIMEOUT, 0 );
	//在拨号状态,收到超时事件
	switch ( FSM( event, state))
	case FSM( CMEVT_TIMEOUT, CMST_DIALING):
		if ( ep->callwarmline && !ep->warmLineTimer && !strlen(ep->dialstr.digits) )
			//当前没有热线业务,不进此流程
		
		//复位一次性类型业务开关
		cmFeatClean( ep->devid );
		
		//放告警音,并将cm endpt迁为CMST_WAITONHOOK状态
		cmTone( ep, EPSIG_OHWARN, cmProvisionGet2(PROVIS_COMMON_TONE_TIMEOUT,
		ep->devid, APP_TONE_OFFHOOK_WARNING)) );
		state = CMST_WAITONHOOK;
数图匹配成功
rc = cmCollectDigits( ep, (char) data);
switch (rc)
case CMDS_NEWCALL:
//进行呼叫限制业务处理,如果有呼叫限制,则cid等于UNKNOWN,否则cid等于1
cid = cmEndptCallBarring( endpt, ep->dialstr.digits )

//当没有呼叫限制时
if (cid != UNKNOWN)
//进行呼出
cid = cmOriginate( endpt, ep->name, ep->dialstr.digits )
//调用协议栈创建一个呼叫控制块
callSetup( &cid, cmEndpt[endpt].regId, CCCIDTYPE_VOICEVIDEOFAX,
&calledparty);

//触发资源状态引擎
cmCnxStateEngine( endpt, cid, CMCMD_ORIGINATE, 0, NULL );
//注意协议栈分配的cid用于cmCnx控制块索引,cid最终在后面会存放
//cmEndpt控制块中来进行线路与呼叫资源的关联。
cx = &cmCnx[cid];
ep = &cmEndpt[endpt];

//如果有协议相关资源处理,处理完成后直接返回。当前没有相关处理。
if( cmProtCnxStateEngine( endpt, cid, event, data,packet, &rc ))
return rc;
				
				if( packet && packet->content )
					//当前没有传入packet参数,不处理此流程
				
				//在空闲状态,收到发起呼叫的事件
				switch ( FSM( event, cx->state))
				case FSM( CMCMD_ORIGINATE, CMST_IDLE):
					
					//分配一个cmRtpCnx控制块,并分配好使用的端口
					cx->rtpCnxId = cmAssignRtpCnx();
					
					//将cmCnx与cmEndpt相关联
					cx->endpt = endpt;
					
					//cmCnx控制块复位
cx->virtual = 0;
cx->deferConnect = 0;
cx->deferFaxEnd = 0;
cx->deferFax = 0;
cx->faxEnded = FALSE;

//根据用户配置设置callgcb.call[cid]->rfc3264Hold值,用于处理
//呼叫保持业务时,是否使用RFC3264的处理策略
bSendRecv = cmProvisionGet( PROVIS_SIP_HOLD_SEND_RECV,
ep->devid ));
callSetParm( cid, CCPARM_HOLDMETHOD, &holdMethod );

//设置from头域信息,及私有标识等。
cmSetCallingParty( endpt, cid );

//将本地支持的编码列表存储到RTP资源控制块的
//cmRtpCnx[rtpCnxId].cfgCodec中,这里会判断如果DSP支持更多非
//基础编码,则选用cmCfgCodec完整列表,如果DSP仅支持基础
//编码,则选用cmCfgCodecLite轻量级列表。这里cmCfgCodec和
//cmCfgCodecLite列表是在callmgr主线程初始化时cmSetCfgCodec中
//设置的。
cmUpdateCodec( endpt, cx->rtpCnxId );

//根据本地编码能力集转换成MT5协议栈的SDP结构,并存储在呼
//叫控制块中。
// callgcb.call[cid]->locSdp = xxx;
// callgcb.call[cid]->locSdpSet = true;
cmBuildLocalSdp( endpt, rtpcx, &localSdp );
callSetParm( cid, CCPARM_LOCAL_SDP, &localSdp );
cmCleanLocalSdp( &localSdp );

//设置协议栈的呼叫路由列表
cmSetServiceRoute( cid, endpt );

//cm cnx状态迁为CMST_CALLING
cx->state = CMST_CALLING;

//callctrl发起呼叫
callOriginate( cid, NULL );
	callcb = callgcb.call[cid];
	
	//当前content为空,不进行处理
	FormatContent(content, msgBody);
	
	sdpInRequest = callcb->locSdpSet;
	
	//分析代码,这里的callType其实是callctrl模块的状态机状态
	switch (callcb->callType)
	case CCTYPE_OUTSETUP:
		//呼叫控制块中有本地编码集设置,则转化为MT5协议栈
		//格式的SDP
		if (sdpInRequest)
			pCapsMgr = BRCM_NEW(MX_NS CSdpCapabilitiesMgr);
			callSdpConvertToMx(callcb->locSdp, &callcb->rtplist[0],
pCapsMgr, &ipAddr);
							
							//调用协议栈句柄发起INVITE请求
							callcb->stackHandle.call->InviteA(TO pCapsMgr, &ipAddr,
callcb->sdpVersion, TO msgBody, &syncSemaphore, &err);

//将callctrl状态迁为CCTYPE_OUTGOING
callcb->callType = CCTYPE_OUTGOING;

//如果有SDP发送,则在当前呼叫控制块中进行标记,并更
//新呼叫控制块中的SDP状态。
if (sdpInRequest)
	callcb->sdpSent = true;
	
	// callcb->sdpState = CCSDPSTATE_OFFER_SENT
	// callcb->isOfferer = true
	callSdpProcess(CCSDPEVENT_SDP_SENT, callcb->sdpState, 
	callcb->isOfferer);
		
		//关联cmEndpt控制块与cmCnx控制块和callgcb.call控制块
		ep->curCid = cid;
		ep->cnxCount++;
		
		//检查是否有丢失的CDR控制块,如果存在,则打印当前控制块的所有信息,并
		//释放该控制块。
		cmCdrLeakDetect( cmActCdr[cmCdrIx(cid)].pBlk );
		
		//分配一个CDR呼叫记录控制块,并启动计时
		cmActCdr[cmCdrIx(cid)].pBlk = cmCdrStart( endpt, cid, CMCDRLIST_OUTGOING );
		cmActCdr[cmCdrIx(cid)].list = CMCDRLIST_OUTGOING;
		
		//将被叫的号码及名称记录到CDR控制块中
		cmCdrClid( cmActCdr[cmCdrIx(cid)].pBlk, cmCallerGetName( ep->dialstr.digits ), 
ep->dialstr.digits );
		
		// publishEventCB中没有CMEVT_CALL_START事件处理。
		cmPublishEvent( endpt, CMEVT_CALL_START, 
		(int)&cmActCdr[cmCdrIx(cid)].pBlk->cdrInfo.identifier );
		
		//cm endpt状态迁为CMST_WAITANSWER
		state = CMST_WAITANSWER;
收到18X,不含SDP
1、callctrl事件回调
SIPCB::EvProgressA
	//获取关联的呼叫控制块
	cid = (CCREF)pCall->GetOpaqueS();
	call = callgcb.call[cid];
	
	status = (UINT32)rResponse.GetStatusLine()->GetCode();
	switch (status)
	//处理18X应答
	case MX_NS uRINGING:
		reason = CCRSN_ALERTING;
		
		//获取Alert-Info头域并存储到call->alertInfo
		GetAlertInfo(call->alertInfo, (MX_NS CSipPacket*)(&rResponse));
		
		//转化待处理的数据包
		GetPacket(rResponse, outPacket);
		
		//触发用户层处理,这里GCBCCEVT是用户层事件回调函数,cmEventCallback
		GCBCCEVT(CCEVT_PROGRESS, cid, reason, status, phrase, pp);
			cmdp.command = CCEVT_PROGRESS;
			cmdp.op1 = cid
			cmdp.op2 = reason
			cmdp.op3 = (SINT32)cmAllocatePacket( packet );
			
			//给callmgr发送CMEVT_CLASS_CALLCTRL队列事件
			cmQueueEvt( CMEVT_CLASS_CALLCTRL, &cmdp );

2、callmgr处理CMEVT_CLASS_CALLCTRL事件队列
cmProcessCallEvent( cmdp );
	//获取callmgr资源控制块,这里的cid索引是从callctrl传来,呼叫控制块索引和callmgr
	//的资源控制块索引是相同的。
	endpt = cmCnx[cid].endpt;
	
	switch (event)
	case CCEVT_PROGRESS:
		//映射为callmgr模块的事件,当前为CMEVT_ALERTING
		cnxevent = cmMapById( cmCallEvtMap, reason);
	
	//进行资源状态引擎处理。
	cmCnxStateEngine( endpt, cid, cnxevent, reason, packet );
		switch ( FSM( event, cx->state))
		case FSM( CMEVT_ALERTING, CMST_CALLING):
			//callmgr资源控制块状态迁为CMST_OUTGOING
			cx->state = CMST_OUTGOING;
			
			//区别回铃默认类型
			ep->rgbkPattern = EPSIG_RINGBACK;
			
			//如果没有SDP信息,则根据用户是否配置了自定义回调开关,来设置
			//回铃默认类型是否为EPSIG_RINGBACK_CUST1
			if( ((sdp == CCRSP_SUCCESS) && !remSdp->streamlist.num) ||
			(sdp != CCRSP_SUCCESS) )
				if(cmProvisionGet( PROVIS_COMMON_USE_CUST_RBK, ep->devid)) )
					ep->rgbkPattern = EPSIG_RINGBACK_CUST1;
			
			//检测SIP报文,是否含有Allow: INFO头域,并更新到cx->allowInfo中
			cmCheckAllowInfo( cid, packet );
			
			//进行callmgr线路侧处理
			cmEndptNotify( endpt, cid, CMEVT_ALERT, data);
				cmEndptStateEngine( endpt, cid, event, data );
					switch ( FSM( event, state))
					//在等待应答状态下,处理CMEVT_ALERT事件
					case FSM( CMEVT_ALERT, CMST_WAITANSWER):
						//如果当前呼叫并不虚拟呼叫,同时没有早期媒体,同时当前放
						//音与回铃音类型不同,则放回铃音。这里同时启动了,线路侧
						//的timer,呼叫不应答的超时时间为
//cmProvisionGet2(PROVIS_COMMON_TONE_TIMEOUT, ep->devid,
//APP_TONE_STUTTER_DIAL)。
						if ( !cmCnx[ep->curCid].virtual )
							if ( !ep->earlyMedia )
								if ( ep->curTone != ep->rgbkPattern )
									cmTone( ep, ep->rgbkPattern,……);
						
						//当前没有补充业务处理,忽略ALERT事件。
						cmSSStateEngine( endpt, CMEVT_ALERT, ep->cmSsBlk.service );
	
	//注册相关处理,当前呼叫处理流程不进入。
	cmProtProcessCallEvent( endpt, cnxevent, reason, packet );

被叫不应答,呼叫超时
cmEndptStateEngine( ep, UNKNOWN, CMEVT_TIMEOUT, 0 );
	switch ( FSM( event, state))
	//在等待应答情况下,远方无应答超时
case FSM( CMEVT_TIMEOUT, CMST_WAITANSWER):
	//仅仅放提示音,告知用户对方应答超时。
	if ( ep->curTone == ep->rgbkPattern )
		cmTone( ep, EPSIG_REORDER, 
		cmProvisionGet2(PROVIS_COMMON_TONE_TIMEOUT, endpt,
APP_TONE_REORDER)) );
收到200,含SDP
1、callctrl事件回调
SIPCB::EvAnsweredA
	//获取关联的呼叫控制块
	cid = (CCREF)pCall->GetOpaqueS();
	call = callgcb.call[cid];
	
	//获取SIP报文中的Allow头域,并存储到呼叫控制块call->allowevts中。
	GetAllowEvents(call->allowevts, resp);
	
	//转化消息包
	GetPacket(rResponse, outPacket);
	
	//当前没有该业务处理。
	if (HandleMusicOnHold(cid, pRemoteSdp, sendack))
		return sendack;
	
	//如果有呼叫转移处理的对话存在,则原转移方通知当前替换对话的状态信息。
	if (call->transcid != CCID_UNUSED && NULL != callgcb.call[call->transcid] &&
callgcb.call[call->transcid]->status != CCSTS_NOTUSED &&
callgcb.call[call->transcid]->transcid == cid && callgcb.call[call->transcid]->isTransferee)
	callgcb.call[call->transcid]->stackHandle.call->ReportTransferStatusA(MX_NS uOK);

//获取SDP信息
change = GetMediaParm(call, pRemoteSdp, &unholdOnly);
	//备注老的SDP
	oldSdp = callcb->remSdp;
	
	//转换为callctrl的SDP格式
	callcb->remSdp = callSdpNew((UINT8)capsMgr->GetNbStreams());
	callSdpConvertFromMx(capsMgr, callcb->remSdp);
	
	isPrevOfferer = callcb->isOfferer;
	//更新SDP状态,当前为
	// callcb->sdpState = CCSDPSTATE_NO_OFFER
	// callcb->isOfferer= true
	callSdpProcess(CCSDPEVENT_SDP_RECEIVED, callcb->sdpState, callcb->isOfferer);
	
	//SDP状态不同,则认为SDP信息已经改变
	change = (callcb->isOfferer != isPrevOfferer);
	
	//当前还没有老的SDP信息,停止后继的处理。
	if ( oldSdp == NULL )
		return( true );

//如果SDP有更变,并且本地没有正在呼叫保持的处理,则给用户发送事件通知。
if (change && call->locHold != CCSTS_HOLDING)
	sdpNotified = true;
	
	//给callmgr模块发送CCEVT_STATUS/ CCRSN_SDP_ANSWER事件。
	GCBEVTSTATUS(cid, callSdpOfferOrAnswer(call->sdpState), NULL);
		cmdp.command =CCEVT_STATUS;
cmdp.op1 = cid;
cmdp.op2 =CCRSN_SDP_ANSWER;
cmQueueEvt( CMEVT_CLASS_CALLCTRL, &cmdp );

//如果收到远端SDP,并且本地处理OK,则打上sendack标记,后继协议栈根据此标记
//发送ACK处理。
if (pRemoteSdp && callSdpOfferOrAnswer(call->sdpState) == CCRSN_SDP_ANSWER)
	sendack = true;

//更新callctrl状态,并通知用户
if (call->callType == CCTYPE_OUTGOING)
	//callctrl状态变迁为CCTYPE_OUTCONNECT
	call->callType = CCTYPE_OUTCONNECT;
	
	//触发用户层处理,这里GCBCCEVT是用户层事件回调函数,cmEventCallback
	GCBEVTCONNECT(cid, CCRSN_NOTUSED, pp);
		cmdp.command = CCEVT_CONNECT;
cmdp.op1 = cid;
		cmdp.op2 = CCRSN_NOTUSED
cmQueueEvt( CMEVT_CLASS_CALLCTRL, &cmdp );

return 	sendack;

2、callmgr处理从callctrl发来的CCEVT_STATUS/ CCRSN_SDP_ANSWER事件
cmProcessCallEvent
	//事件映射为CMEVT_SDP_ANSWER
	cnxevent = cmMapById( cmCallEvtMap, reason);
	
	cmCnxStateEngine
		switch ( FSM( event, cx->state))
		case FSM( CMEVT_SDP_ANSWER, CMST_OUTGOING):
			//给callmgr endpt发送CMEVT_EARLYMEDIA事件
			cmEndptNotify( endpt, cid, CMEVT_EARLYMEDIA, 0);
				cmEndptStateEngine
					switch ( FSM( event, state))
					//在等待应答状态处理CMEVT_EARLYMEDIA事件
					case FSM( CMEVT_EARLYMEDIA, CMST_WAITANSWER):
						//标记在早期媒体处理流程
						ep->earlyMedia = TRUE;
						
						//DSP停止放进展音
						cmTone( ep, EPSIG_NULL, 0 );
			
			//获取媒体信息,并存储到rtp资源控制块中
			cmStreamInfo( cid, 1, 1 )
			
			//根据SIP报文是否含有Allow: INFO,来设置cx->allowInfo
			cmCheckAllowInfo( cid, packet );

			//创建媒体流
			cmStreamCreate( cid);
				//获取当前callmgr资源控制块相关联的RTP资源控制块
				rtpcx = &cmRtpCnx[cx->rtpCnxId];
			
				//创建RTP/RTCP的SOCKET,并设置好TOS值,同时给rtpHandle控制块
				//传入处理RTP/RTCP接收处理的回调函数cmEgressPktRecvCb和
				// cmEgressPktRecvCb,这两个回调函数在收到远端的RTP/RTCP后,会调
				//用ENDPT驱动模块的处理接口进行处理。
				rtpOpen( (int)cmCfgBlk.ifNo, rtpcx->localRtp.port, rtpcx->localRtp.port+1,
				bosIp, htskId, rtcpDisablerTaskId, (RTPRECVCB)cmEgressPktRecvCb,
				(RTPRECVCB)cmEgressRtcpRecvCb, tos, &rtpcx->rtpSharedSendSocket,
				&rtpcx->rtpSharedRTCPSocket, &rtpcx->rtpHandle );
			
				//获取当前接收的媒体信息类型
				callGetParm( cid, CCPARM_MEDIAINFO, &mediaInfo );
				cmStreamGetMediaIx( cid, mediaInfo.tx, &saIdx, &sfIdx, FALSE );
			
				//如果接收的媒体是语音,则从远端SDP信息中获取收发模式
				if ( saIdx != UNKNOWN )
					rtpcx->parm.mode = cmMapById( cmEndptModeMap,
mediaInfo.tx->streamlist.stream[saIdx]->alist.mode);
			
				//获取用户配置的二次拨号模式
				rtpcx->parm.digitRelayType = cmGetToneRelayType(cx->endpt);
			
				//如果远端不支持INFO信令方法,本端用户配置的二次拨号模式为
//SIPINFO,则将二次拨号模式修正为INBAND。
				if( cx->allowInfo != TRUE 
&& cmProvisionGet(PROVIS_COMMON_DTMFRELAY, cx->endpt)) ==
VRGCMGR_DTMF_RELAY_SIPINFO )
					rtpcx->parm.digitRelayType = EPDTMFRFC2833_DISABLED;
			
				//分配一个cmStream控制块
				rtpcx->stream = cmAssignStream( cx->endpt, cx->rtpCnxId);
			
				rtpcx->parm.vbdparam.vbdMode = EPVBDMODE_LEGACY;
				rtpcx->parm.vbdparam.vbdRedMode = EPVBDRED_OFF;
			
				//更新rtpHandle控制块的远端地址信息
				rtpSetRTPRemote( rtpcx->rtpHandle, &bosIp,
mediaInfo.tx->streamlist.stream[saIdx]->media.port );
rtpSetRTCPRemote( rtpcx->rtpHandle, &bosIp,
(mediaInfo.tx->streamlist.stream[saIdx]->media.port + 1) );
	
//开启rtpHandle控制块中的RTCP开关标识
rtcpEnable( rtpcx->rtpHandle );
	
//复位RTCP统计信息
rtcpInitStat( rtpcx->rtpHandle );
	
//在防火墙规则中加入当前RTP端口访问控制
cmFirewallControl( UNIQUE_ID(cid, rtpcx->localRtp.port), &bosIp, 
rtpcx->localRtp.port, VRGCMGR_TRANSPORT_UDP, 1 );
	
//向ENDPT驱动模块创建连接资源
endptCreateConnection( &cmEndpt[cx->endpt].endptObjState,
rtpcx->stream, &rtpcx->parm );
	
//保留预期编码
rtpcx->expectedCodec = cmMapByEvt( cmEptCodecMap, rtpcx->codec);
			
			//打印编码改变调试信息
			cmDisplay( endpt, cid, CMLCD_CODECCHG)
	
3、callmgr处理从callctrl发来的CCEVT_CONNECT/ CCRSN_NOTUSED事件
cmProcessCallEvent
	cmCnxStateEngine( endpt, cid, cnxevent, reason, packet )
	switch ( FSM( event, cx->state))
	//在CMST_OUTGOING状态处理CMEVT_CONNECT事件
	case FSM( CMEVT_CONNECT, CMST_OUTGOING):
		//给callmgr终端对象发送CMEVT_ANSWER事件
		cmEndptNotify( endpt, cid, CMEVT_ANSWER, data);
			cmEndptStateEngine( endpt, cid, event, data );
				switch ( FSM( event, state))
				//在等待应答状态收到CMEVT_ANSWER事件
				case FSM( CMEVT_ANSWER, CMST_WAITANSWER):
					//真实呼叫情况下,远端摘机,停止本端DSP放进展音
					if ( !cmCnx[ep->curCid].virtual )
						cmTone( ep, EPSIG_NULL, 0 );
					
					//打印编码更换的提示信息
					cmDisplay( endpt, ep->curCid, CMLCD_CODECCHG);
					
					//callmgr endpt控制块状态迁为CMST_TALK
					ep->earlyMedia = FALSE;
					state = CMST_TALK;
		
		//从SIP报文检测是否含有Allow: INFO字段,根据是否含有该值来设置
		// cx->allowInfo
		cmCheckAllowInfo( cid, packet );
		
		//将SIP对话的call-id字段记录的cmActCdr控制块中
		cmCdrSetCallId( endpt, cmActCdr[cmCdrIx(cid)].pBlk, cmActCdr[cmCdrIx(cid)].list );
		
		//将callmgr资源控制块状态迁为CMST_CONNECTED
		cx->state = CMST_CONNECTED;
		
		//上面那步流程已经创建媒体流对象,此处不会现处理。
		cmStreamCreate( cid);
主动挂机
1、callmgr处理endpt驱动模块送来的挂机事件
cmProcessEptEvent
	switch( cmdp->op2)
	case EPEVT_ONHOOK:
		//映射事件,当前为 CMEVT_ONHOOK
		event = cmMapByEvt( cmEptCasMap, cmdp->op2);
		
		//给callmgr发送终端事件通知
		cmEndptNotify( cmEndptIndex, UNKNOWN, event, digit);
			cmEndptStateEngine( endpt, cid, event, data );
				switch ( FSM( event, state))
				//在通话状态,收到CMEVT_ONHOOK事件
				case FSM( CMEVT_ONHOOK, CMST_TALK):
					if (ep->confCid != UNKNOWN)
						//当前没有会议业务,不进入此流程。
					
					//给callmgr资源状态机发送CMCMD_RELEASE事件
					cmCnxNotify( endpt, ep->curCid, CMCMD_RELEASE, CCRSN_CALLCMPL);
						cmCnxStateEngine( endpt, cid, event, data, NULL );
							//在连接状态收到CMCMD_RELEASE事件
							switch ( FSM( event, cx->state))
							case FSM( CMCMD_RELEASE, CMST_CONNECTED):
								//callmgr资源控制块状态迁为CMST_CLEARING
								cx->state = CMST_CLEARING;
								
								//释放呼叫
								callRelease( cid, data);
									switch (call->cidType)
									case CCCIDTYPE_VOICEVIDEOFAX:
										//这里callType就是callctrl的状态机,这里调
										//用协议栈句柄发送呼叫终止。
										if (call->callType != CCTYPE_INCOMING)
											call->stackHandle.call->TerminateA();
								
								//删除媒体
								cmStreamDelete( cid);
									//获取关联的rtp资源控制块
									rtpcx = &cmRtpCnx[cx->rtpCnxId];
									
									//从SDP中获取RTP的统计信息,并保存到
									// rtpcx->cnxStats中。
									cmStreamUpdateRtpStats( cid )
									rtpReportStat( rtpcx->rtpHandle, &rtpcx->cnxStats );
									
									//RTP资源控制块复位
									rtpcx->codec = CODEC_UNKNOWN;
									rtpcx->egressCodec = CODEC_UNKNOWN;
									rtpcx->expectedCodec = CODEC_UNKNOWN;
									rtpcx->faxCodec = CODEC_UNKNOWN;
									
									//cmStream控制块复位
									cmStream[rtpcx->stream].cid = UNKNOWN;
									cmStream[rtpcx->stream].endpt = UNKNOWN;
									
									//调用endpt驱动模块接口,删除驱动模块的连接
									//资源。
									endptDeleteConnection( 
&cmEndpt[cx->endpt].endptObjState, 
rtpcx->stream );

//RTCP发包开关关闭
//rtpHandle[handle].enabled = FALSE
rtcpDisable( rtpcx->rtpHandle );
									
									//关闭RTP/RTCP使用的系统SOCKET资源,并复位
									//rtpHandle控制块。
									rtpClose(&rtpcx->rtpHandle);
									
									//去除rtp资源控制块与rtphandle控制块的关联
									rtpcx->rtpHandle =
 CMSTREAM_RTPHANDLE_UNASSIGNED;
									
									bosIpAddrCreateZero( cmCfgBlk.ipv6Enabled ?
BOS_IPADDRESS_TYPE_V6 :
BOS_IPADDRESS_TYPE_V4, &bosIp );

//删除涉及媒体端口的防火墙规则
									cmFirewallControl( UNIQUE_ID(cid,
rtpcx->localRtp.port), &bosIp, rtpcx->localRtp.port, VRGCMGR_TRANSPORT_UDP, 0 );
									
									//释放之前占用的RTP端口列表
									rtpReleaseMediaPortNums(rtpcx->localRtp.port);
					
					//停止呼叫记录管理通话计时,并统计当前呼叫记录信息。
					cmCdrEnd( endpt, cmActCdr[cmCdrIx(ep->curCid)].list,
cmActCdr[cmCdrIx(ep->curCid)].pBlk, VRGCMGR_CALLTERMREASON_NORMAL, ep->curCid );

cmActCdr[cmCdrIx(ep->curCid)].pBlk = NULL;

//去除cmEndpt控制块与cmCnx控制块的关联
cmStackRemove( ep, ep->curCid );
ep->cnxCount--;
ep->curCid = UNKNOWN;

ep->timer = 0;

// cmCfgBlk.publishEvent回调中,如果此事件的处理。
cmPublishEvent( endpt, CMEVT_ONHOOK, 0);

//停止放音
cmTone( ep, EPSIG_NULL, ep->timer );

//打印线路空闲信息
cmDisplay( endpt, UNKNOWN, CMLCD_IDLE);

//去除cmPhysEndpt控制块指向cmEndpt控制块的关联
cmUnMapCmEndpt( endpt );

//cmEndpt控制块状态迁为CMST_ONHOOK
state = CMST_ONHOOK;

//cmEndpt控制块参数复位
ep->earlyMedia = FALSE;
ep->callwaitingOnce = ep->callwaiting;
收到远端挂机响应
1、callctrl事件回调
SIPCB:: EvTerminatedA
	//调用协议栈句柄执行资源释放
	pCall->Release();
	
	//释放callctrl呼叫控制块
	callFreeCallInfo(cid);
		//释放callctrl呼叫控制块资源
		ccdefDeleteCALLCB( callgcb.call[cid] );
		callgcb.call[cid] = NULL;
	
	//给callmgr发送CCEVT_STATUS/ CCRSN_RELEND事件
	GCBEVTSTATUS(cid, CCRSN_RELEND, pp);

2、callmgr处理CCEVT_STATUS/ CCRSN_RELEND事件
cmProcessCallEvent
	//事件是转换成CMEVT_CLEARED
	cnxevent = cmMapById( cmCallEvtMap, reason);
	
	cmCnxStateEngine( endpt, cid, cnxevent, reason, packet )
		switch ( FSM( event, cx->state))
		//在释放状态,收到CMEVT_CLEARED事件
		case FSM( CMEVT_CLEARED, CMST_CLEARING):
			//释放cmCnx控制块与rtp资源控制块的关联
			if (cx->rtpCnxId != UNKNOWN)
				rtpcx = &cmRtpCnx[cx->rtpCnxId];
				rtpcx->localHold = rtpcx->remoteHold = FALSE;
				rtpcx->holdState = NONE_PENDING;
				rtpcx->queued = 0;
				rtpcx->inUse = FALSE;
			
			cx->rtpCnxId = UNKNOWN;
			
			//释放cmCnx控制块与cmEndpt控制块的关联
			cx->endpt = UNKNOWN;
			
			//cmCnx控制块状态迁为CMST_IDLE
			cx->state = CMST_IDLE;
			
			//给callmgr endpt发送CMEVT_TERMINATED事件,该事件后面处理,仅仅用
			//callmgr模块的restart流程,当callmgr准备restart时,如果还有呼叫则阻塞
			//restart流程,但呼叫资源释放后,收到CMEVT_TERMINATED在进行restart。
			cmEndptNotify( 0, UNKNOWN, CMEVT_TERMINATED, UNKNOWN );
被叫流程
远端呼入
1、callctrl事件回调
SIPCB::EvCalledA
	//创建呼叫控制块
	CreateNewCall(CCCIDTYPE_VOICEVIDEOFAX, pCall, rRequest);
		//分配一个callgcb控制块
		cid = callCreateCallId();
		
		//设置呼叫控制块初始值
		callcb = callgcb.call[cid];
		callcb->cidType = cidType;	// CCCIDTYPE_VOICEVIDEOFAX
		callcb->callType = CCTYPE_INCOMING;
		
		//保存协议栈对话句柄
		switch (cidType)
		case CCCIDTYPE_VOICEVIDEOFAX:
			callcb->stackHandle.call = (MX_NS CUABasicCall*)pUaComponent;
	
	//将SIP信息保存在呼叫控制块中,其中涉及如下
	// callcb->inReqUri
	// callcb->sipcallid
	// callcb->allowevts
	// callcb->cgParty 这里是从FROM头域取的值
	// callcb->cdParty	这里是从TO头域取的值
	// callcb->remContact
	// ipHdr->GetParamList
	UpdateDialogInfo(cid, rRequest);
	
	//获取SIP的AlertInfo头域信息并保存到呼叫控制块中
	GetAlertInfo(call->alertInfo, (MX_NS CSipPacket*)(&rRequest));
	
	//获取Require头域列表信息
	GetRequire(call->require, (MX_NS CSipPacket*)(&rRequest));
	
	//获取SDP信息
	GetMediaParm(call, pRemoteSdp);
		oldSdp = callcb->remSdp;
		
		//把SDP信息转换为callctrl格式,并存储到callcb->remSdp中
		callcb->remSdp = callSdpNew((UINT8)capsMgr->GetNbStreams());
		callSdpConvertFromMx(capsMgr, callcb->remSdp);
		
		//更新呼叫控制块中的SDP状态
		// callcb->sdpState  = CCSDPSTATE_OFFER_RECEIVED
		// callcb->isOfferer = false;
		callSdpProcess(CCSDPEVENT_SDP_RECEIVED, callcb->sdpState, callcb->isOfferer);
		
		//之前老的SDP不存在,这里直接返回
		if ( oldSdp == NULL )
			return( true );
	
	//给callmgr发送CCEVT_SETUP/ CCRSN_NEWCALL事件
	// reason = CCRSN_NEWCALL;
	GCBEVTSETUP(cid, reason, pp);
	
	//给callmgr发CCEVT_STATUS/ CCRSN_SDP_OFFER事件
	GCBEVTSTATUS(cid, callSdpOfferOrAnswer(call->sdpState), NULL);

2、callmgr处理CCEVT_SETUP/ CCRSN_NEWCALL事件
cmProcessCallEvent
cmCnxStateEngine
		switch ( FSM( event, cx->state))
		//在空闲状态收到CMEVT_SETUP事件
		case FSM( CMEVT_SETUP, CMST_IDLE):
			//校验callmgr是否支持远端协带的Require支持列表
			cmValidateRequire( cid );
			
			//根据请求URL查找对应的cmEndpt对象
			endpt = cmMapCalledParty( cid);
			
			//分配一个rtp资源控制块
			cx->rtpCnxId = cmAssignRtpCnx();
			
			cx->deferConnect = 0;
			cx->deferFaxEnd = 0;
			cx->deferFax = 0;
			cx->faxEnded = FALSE;
			
			//将资源控制块状态迁为CMST_INCOMING
			cx->state = CMST_INCOMING;
			
			//将callmgr资源控制块与cm终端对象关联
			cx->endpt = endpt;
			
			rtpcx = &cmRtpCnx[ cx->rtpCnxId ];
			
			//关联当前线路的注册索引与呼叫索引
			regId = ep->regId | CM_REGID_MASK;
			callSetParm( cid, CCPARM_RID, ®Id );
			
			//将本地编码支持列表设置到rtp资源控制块中
			// cmRtpCnx[rtpCnxId].cfgCodec = (CMCODEC *)&cmCfgCodec[endpt]
			cmUpdateCodec( endpt, cx->rtpCnxId );
			//对比远端SDP 的编码列表与当前rtp资源控制块中的本地编码列表是否
			//有交集。
			cmSupportedMedia( cid, endpt, rtpcx);
			
			//构建本地编码能力集,并存储到呼叫控制块中
			// callgcb.call[cid]->locSdp = xxxxxx
			// callgcb.call[cid]->locSdpSet = true;
			cmBuildLocalSdp( endpt, rtpcx, &localSdp );
			callSetParm( cid, CCPARM_LOCAL_SDP, &localSdp );
			cmCleanLocalSdp( &localSdp );
			
			//设置振铃类型,优先取sip消息的ALERTINFO头域,如果没有则取默认的振
			//铃类型。
			ringId = cmTxtMapByTxtStr( cmRingAlertInfoMap, alertInfo );
			ep->ringPattern = (ringId != UNKNOWN) ? ringId : EPSIG_RINGING;
			
			//根据用户配置设置呼叫控制块的呼叫保持处理方式。
			// callgcb.call[cid]->rfc3264Hold = true或false
			holdMethod.bSendRecv =cmProvisionGet( PROVIS_SIP_HOLD_SEND_RECV,
ep->devid ));
callSetParm( cid, CCPARM_HOLDMETHOD, &holdMethod );
			
			//将SIP消息存储到资源控制块中
			cx->networkInfo = ((packet != NULL) ? (void *)packet->hdrs : NULL);
			
			//给callmgr终端对象发送CMEVT_NEWCALL事件
			cmEndptNotify( endpt, cid, CMEVT_NEWCALL, data );
				cmEndptStateEngine( endpt, cid, event, data );
					switch ( FSM( event, state))
					//在挂机状态收到CMEVT_NEWCALL事件
					case FSM( CMEVT_NEWCALL, CMST_ONHOOK):
						//从sip报文from头域中获取对端号码并存储到callmgr终端对
						//象控制块中。ep->lastcall.digits
						cmSaveCallReturn( ep, cid);
						
						//检查是否有丢失的CDR控制块,如果存在,则打印当前控制块
						//的所有信息,并释放该控制块。
						cmCdrLeakDetect( cmActCdr[cmCdrIx(cid)].pBlk );
						
						//分配一个CDR呼叫记录控制块,并启动计时
						cmActCdr[cmCdrIx(cid)].pBlk = cmCdrStart( endpt, cid, 								CMCDRLIST_INCOMING );
						cmActCdr[cmCdrIx(cid)].list = CMCDRLIST_INCOMING;
						
						//将被叫的号码及名称记录到CDR控制块中
						cmCdrClid( cmActCdr[cmCdrIx(cid)].pBlk, 
						cmCallerGetName( ep->lastcall.digits ), ep->lastcall.digits );
						
						//一些是否允许呼入的业务处理,当前大体处理条件如下:
						//1、当前线路是否已经注册
						//2、cmEndpt关联的物理线路对象是否有效
						//3、当前是否没有restart的阻塞
						//4、当前没有免打扰
						//5、当前没有拒绝匿名呼入
						//6、当前没有CALLER_FEATURES业务拒绝处理
						cmEndptCallerDisposed( endpt, cid )
						
						//当前没有业务处理,忽略CMEVT_NEWCALL事件
						cmSSStateEngine( endpt, CMEVT_NEWCALL, ep->cmSsBlk.service );
						
						//关联cmEndpt对象与cmCnx对象
						ep->cnxCount++;
						ep->curCid = cid;
						
						//给callmgr资源处理状态机发送CMCMD_ALERTING事件
						cmCnxNotify( endpt, cid, CMCMD_ALERTING, 0);
							cmCnxStateEngine( endpt, cid, event, data, NULL )
								switch ( FSM( event, cx->state))
								//在呼入状态收到CMCMD_ALERTING命令
								case FSM( CMCMD_ALERTING, CMST_INCOMING):
									//检测sip报文是否含有Allow:Info支持,并将结果
									//设置到cx->allowInfo中
									cmCheckAllowInfo( cid, packet );
									
									//调用MT5协议栈句柄发送180
									callProgress( cid, CCRSN_ALERTING);
									
				//根据本地编码能力集转换成MT5协议栈的
//SDP结构,并存储在呼叫控制块中。
									rtpcx = &cmRtpCnx[ cx->rtpCnxId ];
									cmUpdateCodec( endpt, cx->rtpCnxId );
									cmBuildLocalSdp( endpt, rtpcx, &localSdp );
									callSetParm( cid, CCPARM_LOCAL_SDP, &localSdp );
									cmCleanLocalSdp( &localSdp );
						
						//调用endpt驱动模块给用户话机发送振铃
						cmTone( ep, ep->ringPattern, 
cmProvisionGet2(PROVIS_COMMON_TONE_TIMEOUT, ep->devid,
APP_TONE_RINGING)) );
						
						//打印电话呼入者信息
						cmDisplay( endpt, cid, CMLCD_CALLERID);
						
						// publishEventCB回调没有对应事件处理
						cmPublishEvent( endpt, CMEVT_ALERT, 1);
						
						//将cm endpt状态机迁为CMST_RINGING
						state = CMST_RINGING;
			
			cx->networkInfo = NULL;
			
			//检测sip报文是否含有Allow:Info支持,并将结果设置到cx->allowInfo中
			cmCheckAllowInfo( cid, packet );

3、callmgr处理CCEVT_STATUS/ CCRSN_SDP_OFFER事件
cmProcessCallEvent
	//映射为CMEVT_SDP_OFFER事件
	cnxevent = cmMapById( cmCallEvtMap, reason);
	
	cmCnxStateEngine( endpt, cid, cnxevent, reason, packet );
	switch ( FSM( event, cx->state))
		//在呼入状态收到CMEVT_SDP_OFFER事件
		case FSM( CMEVT_SDP_OFFER, CMST_INCOMING):
			//该函数主要进行媒体协商,将协商后的结果存储到呼叫控制块中
			// callcb->ansSdp
			cmStreamInfo( cid, 1, 1 );
被叫回铃超时
cmEndptStateEngine( ep, UNKNOWN, CMEVT_TIMEOUT, 0 )
	switch ( FSM( event, state))
	//在振铃状态,收到CMEVT_TIMEOUT事件
	case FSM( CMEVT_TIMEOUT, CMST_RINGING):
		if (ep->callfwd_noans)
			//当前流程只有无应答转移业务处理,基本呼叫不走此流程。
被叫不应答,主叫取消
//当前手上没有板子调试,MT5协议栈没有源码,目前仅通过走读代码猜测该流程会从此入
//口触发。

1、callctrl事件回调
SIPCB::EvTerminatedA
	//获取呼叫控制块
	cid = (CCREF)pCall->GetOpaqueS();
	call = callgcb.call[cid];
	
	//调用协议栈释放呼叫资源
	pCall->Release();
	
	//控制呼叫控制块资源
	callFreeCallInfo(cid);
	
	//给callmgr模块发送CCEVT_RELEASE/ CCRSN_CALLCMPL事件
	GCBEVTRELEASE(cid, CCRSN_CALLCMPL, pp);

2、callmgr模块处理CCEVT_RELEASE/ CCRSN_CALLCMPL事件
cmProcessCallEvent
	cmCnxStateEngine
		switch ( FSM( event, cx->state))
		//在呼入状态,收到CMEVT_RELEASE事件
		case FSM( CMEVT_RELEASE, CMST_INCOMING):
			//释放RTP资源控制块
			rtpcx = &cmRtpCnx[cx->rtpCnxId];
			rtpcx->localHold = rtpcx->remoteHold = FALSE;
			rtpcx->holdState = NONE_PENDING;
			rtpcx->queued = 0;
			rtpcx->inUse = FALSE;
			
			//释放流控制块,之前没有创建,该函数中不做什么处理。
			cmStreamDelete( cid);
			
			//给cmEndpt发送CMEVT_DISCONNECT事件通知
			cmEndptNotify( endpt, cid, CMEVT_DISCONNECT, data);
				cmEndptStateEngine( endpt, cid, event, data );
					switch ( FSM( event, state))
					//在振铃状态收到CMEVT_DISCONNECT事件
					case FSM( CMEVT_DISCONNECT, CMST_RINGING):
						//关联cmCnx资源控制块计数递减
						ep->cnxCount--;
						
						//当前没有业务处理,忽略CMEVT_DISCONNECT事件
						cmSSStateEngine( endpt, CMEVT_DISCONNECT, 
						ep->cmSsBlk.service );
						
						//去除与cmCnx资源控制块的连接
						ep->curCid = UNKNOWN;
						
						//当前publishEventCB回调没有CMEVT_ALERT事件处理
						cmPublishEvent( endpt, CMEVT_ALERT, 0);
						
						//调用endpt驱动模块停止振铃
						cmTone( ep, EPSIG_NULL, 0 );
						
						//打印线路空闲信息
						cmDisplay( endpt, UNKNOWN, CMLCD_IDLE);
						
						//停止呼叫记录管理通话计时,并统计当前呼叫记录信息。
						cmCdrEnd( endpt, cmActCdr[cmCdrIx(cid)].list, 
cmActCdr[cmCdrIx(cid)].pBlk, VRGCMGR_CALLTERMREASON_MISSED, cid );
						cmActCdr[cmCdrIx(cid)].pBlk = NULL;
						
//去除cmPhysEndpt控制块指向cmEndpt控制块的关联
						cmUnMapCmEndpt( endpt );
						
						//cm endpt状态迁为CMST_ONHOOK
						state = CMST_ONHOOK;
			
			//cmCnx资源控制块复位
			cx->state = CMST_IDLE;
			cx->endpt = UNKNOWN;
			cx->rtpCnxId = UNKNOWN;
被叫摘机应答
1、callmgr处理endpt驱动模块送来的摘机事件
cmProcessEptEvent
	switch( cmdp->op2)
	case EPEVT_OFFHOOK:
		//映射事件,当前为 CMEVT_OFFHOOK
		event = cmMapByEvt( cmEptCasMap, cmdp->op2);
		
		//给callmgr发送终端事件通知
		cmEndptNotify( cmEndptIndex, UNKNOWN, event, digit);
			cmEndptStateEngine( endpt, cid, event, data );
				switch ( FSM( event, state))
				//在振铃状态,收到CMEVT_OFFHOOK事件
				case FSM( CMEVT_OFFHOOK, CMST_RINGING):
					//通过代码分析,这种情况,是两个cmEndpt控制块为兄弟关系,						//两个cmEndpt控制块关联同一个 cmCnx控制块,这里把两个
					//cmEndpt控制块分别叫做A控制块和B控制块,此时cmCnx关联着
					//B控制块,但是当前是A控制块摘机,此处处理流程为,停止B控
					//制块振铃,并复位B控制块参数,同时将cmCnx重新关联到A控
					//制块。
					sibIx = cmEndptAlertSibbling( endpt, ep->curCid, FALSE );
					if ( (sibIx != UNKNOWN) && !cmSibblingOwner( endpt, ep->curCid ))
						cmTrans2Sibbling( sibIx, ep->curCid, endpt );
					
					//给资源控制块状态机发送CMCMD_CONNECT事件
					cmCnxNotify( endpt, ep->curCid, CMCMD_CONNECT, 0);
						cmCnxStateEngine( endpt, cid, event, data, NULL );
							switch ( FSM( event, cx->state))
							//在呼入状态,收到CMCMD_CONNECT事件
							case FSM( CMCMD_CONNECT, CMST_INCOMING):
								//cmCnx状态迁为CMST_CONNECTED
								cx->state = CMST_CONNECTED;
								
								//检测SIP报文,是否含有Allow: INFO头域,并更新到
								//cx->allowInfo中
								cmCheckAllowInfo( cid, packet );
								
								//将SIP对话的call-id字段记录的cmActCdr控制块中
								cmCdrSetCallId( endpt, cmActCdr[cmCdrIx(cid)].pBlk,
								cmActCdr[cmCdrIx(cid)].list );
								
								//关联当前线路的注册索引与呼叫索引
								callSetParm( cid, CCPARM_RID, &(ep->regId));
								
								//设置sip相关私有标识字段
								cmSetPrivacy( endpt, cid );
								cmSetPreferredId( endpt, cid );
								
								//发起呼叫
								callConnect( cid, NULL );
									call = callgcb.call[cid];
									
									//之前没有给对端发过SDP,则走此流程处理
									if (!call->sdpSent)
										//将已经协商好的能力集转换为MT5能力对
										//象
										if (call->sdpState == 
										CCSDPSTATE_OFFER_RECEIVED)
											sdpToSend = call->ansSdp;
										version = call->sdpVersion;
										callSdpConvertToMx(sdpToSend, 
										&call->rtplist[0], capsMgr, &ipAddr);
										
										//更新呼叫控制块中的sdpsent标记,以及
										//SDP状态机。
										//call->sdpState  = CCSDPSTATE_NO_OFFER
										//call->isOfferer = false
										call->sdpSent = true;
										callSdpProcess(CCSDPEVENT_SDP_SENT, 
										call->sdpState, call->isOfferer);
										
										//当前content为空,不做任何处理
										FormatContent(content, msgBody);
										
										//调用协议栈句柄,发送200应答。
										call->stackHandle.call->AnswerA(TO capsMgr, 
										&ipAddr, version, TO msgBody);
										
										//呼叫控制块的状态机迁为
										// CCTYPE_INCONNECT
										call->callType = CCTYPE_INCONNECT;
								
								//创建媒体流,主要向endpt驱动模块创建连接资源,
								//以及创建传送媒体的SOCKET,这里不详细分析了,
								//上面流程已经分析过该函数。
								cmStreamCreate( cid)
					
					//当前publishEventCB回调没有CMEVT_ALERT处理
					cmPublishEvent( endpt, CMEVT_ALERT, 0);
					
					//当前publishEventCB回调没有CMEVT_OFFHOOK处理
					cmPublishEvent( endpt, CMEVT_OFFHOOK, 0);
					
					//打印编码改变调试信息
					cmDisplay( endpt, ep->curCid, CMLCD_CODECCHG);
					
					//调用endpt驱动模块停止振铃
					cmTone( ep, EPSIG_NULL, 0 );
					
					//cm endpt状态迁为CMST_TALK
					state = CMST_TALK;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值