您的位置:首页 > 其它

BCM VOIP 基本呼叫流程分析

2013-11-27 23:09 861 查看
呼叫过程中各控制块的关联



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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: