BCM VOIP 传真相关分析

一、	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 );

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值