一、 DSP上报传真事件
cmProcessEptEvent
switch( cmdp->op2)
//传真事件,主要从以下几个方面触发该事件
//1、检测到本地HDLC
//2、检测到远端CNG,如果用户设置需要检测CNG,则通知用户FAX事件,当前默认
//设置不检测CNG
//3、在T38模式下检测到本地V21前导,则通知FAX事件
//4、在T38模式下检测到CNG,则通知用户FAX事件
case EPEVT_FAX:
//上报传真事件
cmCnxNotify( cmEndptIndex, cid, CMEVT_FAX, 0);
//T38 STOP事件
//1、如果用户修改资源连接,原来T38编码改为非T38编码,则触发T38_STOP事件
//2、如果在T38模式,收到传送结束事件,则根据是否成功的结果,触发T38_STOP或
// T38_FAILURE事件
case EPEVT_T38_STOP:
case EPEVT_T38_FAILURE:
//上报传真结束事件
cmCnxNotify( cmEndptIndex, cid, CMEVT_FAXEND, 0);
//1、检测到本端数据含有VBD,则触发VBD_START事件
//2、检测到远端数据含有VBD,则触发VBD_START事件
case EPEVT_VBD_START:
//不做处理
//1、检测到音频低能量的事件,触发VBD_STOP
//2、检测到本端在VBD模式,数据含有语音,则触发VBD_STOP事件
//3、在T38模式下,检测到本地V21前导,触发VBD_STOP事件
//4、检测到远端在VBD模式,数据含有语音,则触发VBD_STOP事件
case EPEVT_VBD_STOP:
//检测到低能量信号,上报传真结束事件
if(cmdp->op3 == (int)EVEVTVDBSTOP_EXTLOWENERGY )
cmCnxNotify( cmEndptIndex, cid, CMEVT_FAXEND, 0);
//1、检测到相位反转的应答信号
//2、检测到应答信号
//3、检测到远端相位反转的应答信号
//4、检测到远端应答信号
//5、在T38模式,检测到CED信号
case EPEVT_MODEM:
//不做处理
二、处理CMEVT_FAX事件(这里有两种情况,一种情况是呼叫已经建立,然后收到传真事件;另一种情况是呼叫还没有完全建立,但收到了传真事件,这种情况下会标记延时传真处理,待语音呼叫建立后,由call manager模块发出一个FAX事件。这里只考虑分析第一种情况)
cmCnxNotify( cmEndptIndex, cid, CMEVT_FAX, 0);
cmCnxStateEngine( endpt, cid, event, data, NULL )
switch ( FSM( event, cx->state))
case FSM( CMEVT_FAX, CMST_CONNECTED):
//启保护作用,只处理一次FAX事件
if ( rtpcx->faxCodec == CODEC_UNKNOWN)
//发送传真协商,如果用户配置数据模式为t38,并且当前非传真结束状 //态,则发送T38协商,否则发送VBD协商,并传入用户配置的VBD使
//用编码。
cmStreamTxFax(cid,
((ep->cfgDataMode == EPDATAMODE_T38) && (!cx->faxEnded)) ?
CODEC_T38 : ep->cfgFaxPassCodec) )
for( i = 0; i < cmCfgCodec[endpt].num; i++ )
//记录t38类型编码在本地编码集中的索引
if( cmCfgCodec[endpt].codec[i].type == CODEC_T38 )
if( t38Idx == UNKNOWN )
t38Idx = i;
//记录vbd使用的编码在本地编码集中的索引
else if ( cmCfgCodec[endpt].codec[i].type ==
cmEndpt[endpt].cfgFaxPassCodec )
if( pcmIdx == UNKNOWN )
pcmIdx = i;
//记录另一个PCM编码,假如上面vbd使用的是pcmu,这里
//就是将pcma也保存在临时列表中
else if( codecCheckClass( cmCfgCodec[endpt].codec[i].type,
CODEC_PCMx ) &&(cmCfgCodec[endpt].codec[i].type !=
cmEndpt[endpt].cfgFaxPassCodec) )
if( pcmOtherIdx == UNKNOWN )
pcmOtherIdx = i;
//暂时备份老的编码列表
rtpcx = &cmRtpCnx[cx->rtpCnxId];
currentList = rtpcx->cfgCodec;
//如果要切换到T38编码
if ( codec == CODEC_T38 )
//当前非t38时才进行处理
if( rtpcx->faxCodec != CODEC_T38 )
//设置当前临时编码列表
codecList.codec[0] = cmCfgCodec[endpt].codec[t38Idx]
codecList.num = 1;
//将当前T38的编码列表存储到呼叫控制块中,在这里T38
//编码会做一些特殊处理,比如需要添加一些t38特有SDP
//属性
rtpcx->cfgCodec = &codecList;
cmBuildLocalSdp( endpt, rtpcx, &localSdp );
callSetParm( cid, CCPARM_LOCAL_SDP, &localSdp );
cmCleanLocalSdp( &localSdp );
//发送含有t38的reinvite
callOriginate( cid );
//恢复本地编码集的原语音编码列表
rtpcx->cfgCodec = currentList;
//如果编码是PCM系列编码
else if ( codecCheckClass( codec, CODEC_PCMx ) )
//如果当前语音编码和要处理的VBD编码相同,则不进行处理
callGetParm( cid, CCPARM_MEDIAINFO, &mediaInfo )
cmStreamGetMediaIx( cid, mediaInfo.tx, &saIdx, &sfIdx, FALSE );
if( (saIdx != UNKNOWN)
&&(mediaInfo.tx->streamlist.stream[saIdx]->media.fmt.list.rtp[0] !=
NULL) &&(mediaInfo.tx->streamlist.stream[saIdx]->
media.fmt.list.rtp[0]->type ==cmMapByEvt( cmEptCodecMap,
cmEndpt[endpt].cfgFaxPassCodec ))
return VRGCMGR_STATUS_OK;
//将要处理的VBD编码设置到呼叫控制块中
codecList.codec[0] = &cmCfgCodec[endpt].codec[pcmIdx]
codecList.codec[0].silsupp = CCSDPSILSUPP_OFF;
codecList.num = 1;
codecList.codec[1] = &cmCfgCodec[endpt].codec[pcmOtherIdx]
codecList.codec[1].silsupp = CCSDPSILSUPP_OFF;
codecList.num = 2;
rtpcx->cfgCodec = &codecList;
cmBuildLocalSdp( endpt, rtpcx, &localSdp );
callSetParm( cid, CCPARM_LOCAL_SDP, &localSdp );
//发送reinvite
callOriginate( cid );
//恢复本地编码集的原语音编码列表
rtpcx->cfgCodec = currentList;
//记录已经发送的传真编码
rtpcx->faxCodec = (ep->cfgDataMode == EPDATAMODE_T38 ? CODEC_T38 :
ep->cfgFaxPassCodec);
三、接收含有SDP信息的reinvite请求
//无MT5协议栈源码,这里应该会触发
EvSdpReceivedA
//如果当前收到含有SDP的update,同时之前的媒体并没有协商完成,则返回错误
if (eSdpMsgType == MX_NS IUABasicCallMgr::eSDPUPDATE)
switch (call->sdpState)
case CCSDPSTATE_NO_OFFER:
break;
case CCSDPSTATE_OFFER_SENT:
*pbIsOffer = false;
case CCSDPSTATE_OFFER_RECEIVED:
res = resFE_FAIL;
break;
return res;
//获取消息中的BODY
GetPacket(*pPacket, outPacket);
//将radvision sdp信息转换为callctrl格式的sdp,存储到callcb->remSdp中,并记载当
//前的媒体信息是否和原来不同。
change = GetMediaParm(call, pRemoteSdp, &unholdOnly);
//当前为CCRSN_SDP_OFFER
reason = callSdpOfferOrAnswer(call->sdpState);
//如果当前收到含有SDP的reinvite
if (eSdpMsgType == MX_NS IUABasicCallMgr::eSDPREINVITE)
//如果远端希望进行呼叫保持
if (callSdpIsOnHold(call->remSdp))
//如果当前没有进入保持,则给callmanager发送保持事件
if (call->remHold == CCSTS_NOHOLD)
GCBEVTSERVICE(cid, CCRSN_SRV_HOLD, pp);
call->remHold = CCSTS_HOLD;
//标记后继操作不在进行callmanager的通知
notifyClient = false;
//如果之前已经被远端保持,当前需要进行解除保持,如果只是IP地址变更的话,则只
//进行SRV_UNHOLD事件的通知处理,后继操作不在进行callmanager的通知
else
if (call->remHold == CCSTS_HOLD)
GCBEVTSERVICE(cid, CCRSN_SRV_UNHOLD, pp);
call->remHold = CCSTS_NOHOLD;
if (unholdOnly)
notifyClient = false;
//如果上面没有进行保持相关处理,则需要通知callmanager进行对应处理,
if (notifyClient && change)
//给callmanger发送CCEVT_STATUS/CCRSN_SDP_OFFER事件
GCBEVTSTATUS(cid, reason, NULL);
//如果远端发送媒体协商请求,当前有媒体参数变更,并且已经通知了callmanager
//层来处理,则标记需要延时发送媒体协商的应答。
rbDeferResponse = (reason == CCRSN_SDP_OFFER);
//标记媒体协商应答需要延时处理,在后续callmanager模块处理时,根据此值进
//行协商应答。当前为deferMsgType为eSDPREINVITE
if (rbDeferResponse)
call->deferMsgType = eSdpMsgType;
//如果远端发送媒体协商请求,媒体信息并没有什么变化,则不通知callmanager进行
//处理,在这里直接给予媒体协商应答
if (!rbDeferResponse && reason == CCRSN_SDP_OFFER)
if ( call->ansSdp )
callSdpDelete( call->ansSdp );
//进行媒体协商生成应答SDP
call->ansSdp = callSdpNew( (UINT8)call->remSdp->streamlist.num )
success = callSdpGenerateAnswer(call->remSdp, call->locSdp, &call->rtplist[0],
call->ansSdp)
//发送媒体协商响应
callSendSdpAnswer(call, eSdpMsgType, success);
---------------------------------------------------------------------------------------------------------------------------------
Callmanager处理CCEVT_STATUS/CCRSN_SDP_OFFER事件
cmCnxStateEngine
switch ( FSM( event, cx->state))
case FSM( CMEVT_SDP_OFFER, CMST_CONNECTED):
//获取远端sdp到临时变量remSdp中
callGetParm( cid, CCPARM_REMOTE_SDP, &remSdp )
//重新进行编码协商,更新本端SDP信息
cmStreamRenegotiateMedia( cid, remSdp, &localSdp );
//获取远端SDP的媒体类型
cmStreamGetMediaIx( cid, pRemSdp, &saIdx, &sfIdx, FALSE );
//如果远端媒体信息中仅含有T38编码,假设我们当前在此场景中
if( (saIdx == UNKNOWN) && (sfIdx != UNKNOWN) )
//找到本地编码能力集中T38编码的位置索引
for ( i = 0; i < cmCfgCodec[cx->endpt].num; i++ )
if ( cmCfgCodec[cx->endpt].codec[i].type == CODEC_T38 )
if( t38Idx == UNKNOWN )
t38Idx = i;
//构成外部临时变量pLocalSdp,之后将rtp控制块中的编码参数还原
codecList.codec[0] = &cmCfgCodec[cx->endpt].codec[t38Idx]
codecList.num = 1;
rtpcx->cfgCodec = &codecList;
cmBuildLocalSdp( cx->endpt, rtpcx, pLocalSdp );
rtpcx->cfgCodec = currentList;
//返回协商的imange成功
return CMSTREAM_NEGO_IMG_OK;
//如果远端媒体类型为语音、或者语音传真都支持,则呼叫控制块中的本地
//编码集不进行更新。
//将刚才重新处理的本地sdp信息存储到呼叫控制块中
callSetParm( cid, CCPARM_LOCAL_SDP, &localSdp );
if( (res == CMSTREAM_NEGO_AUD_OK) && (cx->rtpCnxId != UNKNOWN) )
//当前处于T38传输中,远端要求切换到语音模式,则在资源控制块
//中标记延时传真结束处理cx->deferFaxEnd = 1,在这里仅仅使用
// callGetParm( cid, CCPARM_MEDIAINFO, &mediaInfo )进行呼叫控制块的
//编码参数更新,之后退出当前流程处理,不在进行DSP参数下发。
//当DSP检测本地传真结束时,通知上层处理,上层会判断如果deferFaxEnd=1
//则完成后继的切语音编码操作。
//进行编码协商处理,如果之前延时给远端的媒体协商应答,则回应应答,同时将
//更新后的编码下发到DSP。
cmStreamInfo( cid, 1, 1 );
//更新媒体信息
callGetParm( cid, CCPARM_MEDIAINFO, &mediaInfo )
offer = callcb->remSdp;
//释放之前的应答SDP
callSdpDelete( callcb->ansSdp );
callcb->ansSdp = NULL;
//生成协商的应答SDP,当远端只有t38时,本端SDP在上面已经更新成
//和只有一个t38编码的SDP了。
callSdpGenerateAnswer(offer, callcb->locSdp, &callcb->rtplist[0], answer)
//之前收到reinvite请求,还没有给予应答,在此产生应答。
if (callcb->deferMsgType != eSDPNONE)
callSendSdpAnswer(callcb, callcb->deferMsgType, success);
callcb->deferMsgType = eSDPNONE;
//获取媒体发送信息,存储到临时变量mediaInfo->tx中
callSdpGetTxMedia(isOfferer, offer, answer, &callcb->rtplist[0], mediaInfo->tx)
//获取媒体接收信息,存储到临时变量mediaInfo->rx中
callSdpGetRxMedia(isOfferer, offer, answer, &callcb->rtplist[0],
mediaInfo->rx)
//进行发送媒体信息更新
cmStreamTxInfo( cid, rtpcx, mediaInfo.tx )
//提取当前媒体类型
cmStreamGetMediaIx( cid, pSdp, &saIdx, &sfIdx, FALSE );
//假设当前仅t38
if( (sfIdx != UNKNOWN) && (saIdx == UNKNOWN) )
//媒体类型校验
if( (pSdp->streamlist.stream[sfIdx]->media.type == CCSDPMTYPE_IMAGE) &&(pSdp->streamlist.stream[sfIdx]->media.transport ==
CCSDPTTYPE_UDPTL) &&!strcmp( pSdp->streamlist.stream[sfIdx]->media.fmt.other, "t38" )
//更新RTP控制块信息
memcpy( &pRtpCx->remoteT38.addr, &pSdp->ipaddr,
sizeof(CCIPADDR) );
pRtpCx->remoteT38.port =
pSdp->streamlist.stream[sfIdx]->media.port;
rtpSetRTPRemote( pRtpCx->rtpHandle,
&bosIp,pSdp->streamlist.stream[sfIdx]->media.port );
//设置T38编码信息
pRtpCx->ingressMap.numCodecs = 1;
pRtpCx->ingressMap.codecs[0].type = CODEC_T38;
//设置通道数据模式
pRtpCx->parm.dataMode = EPDATAMODE_T38;
//设置收发模式
pRtpCx->parm.mode = cmMapById( cmEndptModeMap,
pSdp->streamlist.stream[sfIdx]->alist.mode);
//将发送编码列表设置到pRtpCx->parm下,因为最终后面向DSP发送媒
//体参数时,是将pRtpCx->parm传入。
pRtpCx->parm.cnxParmList.send = pRtpCx->ingressMap;
pRtpCx->parm.cnxParmList.send.period[0] =
pRtpCx->parm.cnxParmList.recv.period[0];
//判断之前是否已经创建stream,这里的stream是和DSP关联的,如果
//有stream则表示已经向DSP发起过通道创建,这里仅仅进行通道参数
//更新。
if( pRtpCx->stream != UNKNOWN )
endptModifyConnection( &cmEndpt[cx->endpt].endptObjState,
pRtpCx->stream, &pRtpCx->parm );
//进行接收媒体信息更新,与上面相同,不再进行分析。
cmStreamRxInfo( cid, rtpcx, mediaInfo.rx )
四、协商T38传真的invite请求被远端拒绝
EvFailedA
//进行SDP协商状态更新,之前我们发送含有sdp的invite,当前状态已经为
// CCSDPSTATE_OFFER_SENT,这里状态会根据REJECTED事件变迁为
// CCSDPSTATE_NO_OFFER,即状态复原。
if (call->sdpState != CCSDPSTATE_NO_OFFER)
callSdpProcess(CCSDPEVENT_SDP_REJECTED, &call->sdpState, &call->isOfferer);
//标记下面流程需要给callmanager发送CCEVT_STATUS/CCRSN_REINVITE_REJECT事
//件,并且不释放当前正在使用的语音对话
if (call->callType != CCTYPE_OUTGOING)
call->locHold = CCSTS_NOHOLD;
event = CCEVT_STATUS;
reason = CCRSN_REINVITE_REJECT;
bReleaseCall = false;
//发送CCEVT_STATUS/CCRSN_REINVITE_REJECT事件
GCBCCEVT(event, cid, reason, uStatus, reasonPhrase, pp);
------------------------------------------------------------------------------------------------------------------------------
Callmanager处理CCEVT_STATUS/CCRSN_REINVITE_REJECT事件
cmCnxStateEngine
switch ( FSM( event, cx->state))
case FSM( CMEVT_REINVITE_STATUS, CMST_CONNECTED):
if ( data == CCRSN_REINVITE_REJECT )
//如果VBD传真协商方式失败,则保持当前语音通话状态
if ( rtpcx->faxCodec == ep->cfgFaxPassCodec )
rtpcx->faxCodec = CODEC_UNKNOWN;
//如果T38传真协商方式失败,则尝试使用VBD方式再次协商
else if ( rtpcx->faxCodec == CODEC_T38 )
cx->faxEnded = TRUE;
//重新发送INVITE请求
cmStreamTxFax( cid, ep->cfgFaxPassCodec );
rtpcx->faxCodec = ep->cfgFaxPassCodec;
//仅更新DSP发送方媒体信息
cmStreamInfo( cid, 1, 0 );
五、处理T38传真结束
cmCnxNotify( cmEndptIndex, cid, CMEVT_FAXEND, 0);
cmCnxStateEngine( endpt, cid, event, data, NULL );
switch ( FSM( event, cx->state))
case FSM( CMEVT_FAXEND, CMST_CONNECTED):
//标记传真已经结束
cx->faxEnded = TRUE;
//如果在传真结束之前,已经收到远端需要切换到语音的请求,则deferFaxEnd
//为true,这里向callmanager发送CCEVT_STATUS/CCRSN_SDP_ANSWER事件, //给远端应答,并将本端DSP切为语音模式。
if( cx->deferFaxEnd )
cx->deferFaxEnd = 0;
cmEventCallback( CCEVT_STATUS, cid, CCRSN_SDP_ANSWER, -1, NULL,
NULL );
//远端还没有协商切为语音,则本端发送切回语音的reinvite请求
else if (cx->rtpCnxId != UNKNOWN)
//清除还在进行传真的标记
rtpcx = &cmRtpCnx[cx->rtpCnxId];
rtpcx->faxCodec = CODEC_UNKNOWN;
//重新设置本地SDP,并发送reinvite
cmBuildLocalSdp( endpt, rtpcx, &localSdp );
callSetParm( cid, CCPARM_LOCAL_SDP, &localSdp );
callOriginate( cid );