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;
相关文章推荐
- [原创] Megaco基本呼叫流程 - VoIP(Megaco/H.248)学习笔记系列之一
- 主叫基本呼叫流程分析
- BCM VOIP 注册流程分析
- 病毒分析必备工具及基本流程
- IMSDroid Http Stack分析 -- 基本流程
- 数据分析的基本流程和方法
- RxJava基本流程和lift源码分析
- 基于Asterisk的VoIP开发指南——(1)实现基本呼叫功能
- BCM芯片数据包转发基本流程
- BCM VOIP 线路统计分析
- BCM芯片数据包转发基本流程--ingress处理之vlan处理
- 【Nutch2.2.1源代码分析之5】索引的基本流程 分类: H3_NUTCH 2014-08-25 14:18 1042人阅读 评论(0) 收藏
- MySQL源码分析——代码结构与基本流程
- linux suspend的基本流程,最简单的流程分析
- Nutch1.7学习笔记2:基本工作流程分析
- MonoRail学习笔记四:MonoRail基本流程分析
- xen io tapdisk2基本流程分析
- VOIP之SIP呼叫过程分析
- BCM VOIP 传真相关分析
- 通过TCP回射服务器分析TCP协议基本流程(一)