DSP

BCM VOIP 传真相关分析

2019-07-13 20:56发布

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