目錄
版權聲明:本文爲博主(寬簡厚重,Yuesichiu)原創文章,未經博主允許不得轉載。
https://blog.csdn.net/yuesichiu/article/details/106413998
一、問題描述
Asterisk版本號爲16.1.0(LTS),SIP協議棧爲PJSIP,對接IMS運營商。官方的Asterisk版本chan_pjsip是不支持IMS VoLTE(4G手機開啓VoLTE,SIP消息中是tel:+86<手機號碼>,已開啓了VoLTE功能是允許通話過程中可以上網),其呼出方向的現象是一接聽就掛斷(運營商回覆487),呼入方向的現象是直接就報416 unsupported URI scheme。
測試時發現部分手機接通電話後大概4秒鐘後自動掛機。Asterisk的報錯信息爲:
[ERROR] pjproject:0<?> sip_inv.c Error parsing/validating SDP body: Missing SDP rtpmap for dynamic payload type(PJMEDIA_SDP_EMISSINGRTPMAP)
二、分析過程
1、網絡抓SIP協議包
從圖中可以看出,在運營商回覆200 OK之後,IPPBX回覆了ACK,然後運營商發送BYE消息,導致掛斷。
分析這個200 OK的包,發現確實是少了101的RTPmap,所以才報錯的。
三、解決辦法
1、查看RFC2833和RFC4733文檔,發現RTPmap 101 telephony是DTMF收號方式。於是嘗試修改參數dtmf_mode爲inband,測試通過。
版權聲明:本文爲博主(寬簡厚重,Yuesichiu)原創文章,未經博主允許不得轉載。
https://blog.csdn.net/yuesichiu/article/details/106413998
(1)、RFC2833
爲帶內檢測方式,通過RTP傳輸,由特殊的rtpPayloadType即TeleponeEvent來標示RFC2833數據包。同一個DTMF按鍵通常會對應多個RTP包,這些RTP數據包的時間戳均相同,此可以作爲識別同一個按鍵的判斷依據,最後一包RTP數據包的end標誌置1表示DTMF數據結束。另外,很多SIP UA 包括IAD都提供TeleponeEvent的設置功能如3CX Phone,Billion-IAD,ZTE-IAD等默認的TeleponeEvent都爲101,但可以人爲修改,這時要求在進行RFC2833 DTMF檢測之前需事先獲取SDP協商的TeleponeEvent參數。
(2)、INBAND
爲帶內檢測方式,而且與普通的RTP語音包混在一起傳送。在進行INBAND DTMF檢測時唯一的辦法就是提取RTP數據包進行頻譜分析,經過頻譜分析得到高頻和低頻的頻率,然後查表得到對應的按鍵。在選擇壓縮比很高碼率很低的codec,比如G.723.1和G.729A等,建議不要使用INBAND模式,因爲INBAND DTMF數據在進行復雜編解碼後會產生失真,造成DTMF檢測發生偏差或失敗。
2、代碼分析
配置文件設置dtmf_mode=rfc4733/inband時,然後PJSIP endpoint被解析並存放在endpoint中。
static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
struct ast_sip_endpoint *endpoint = obj;
int dtmf = ast_sip_str_to_dtmf(var->value);
if (dtmf == -1) {
return -1;
}
endpoint->dtmf = dtmf;
return 0;
}
當有呼叫時會分配一個SIP session即ast_sip_session_alloc函數,該函數就會將endpoint->dtmf設置成session->dtmf。
然後在chan_pjsip_digit_begin和chan_pjsip_digit_end中被調用。
/*! \brief PBX interface structure for channel registration */
struct ast_channel_tech chan_pjsip_tech = {
.type = channel_type,
.description = "PJSIP Channel Driver",
.requester = chan_pjsip_request,
.requester_with_stream_topology = chan_pjsip_request_with_stream_topology,
.send_text = chan_pjsip_sendtext,
.send_text_data = chan_pjsip_sendtext_data,
.send_digit_begin = chan_pjsip_digit_begin,
.send_digit_end = chan_pjsip_digit_end,
.call = chan_pjsip_call,
.hangup = chan_pjsip_hangup,
.answer = chan_pjsip_answer,
.read_stream = chan_pjsip_read_stream,
.write = chan_pjsip_write,
.write_stream = chan_pjsip_write_stream,
.exception = chan_pjsip_read_stream,
.indicate = chan_pjsip_indicate,
.transfer = chan_pjsip_transfer,
.fixup = chan_pjsip_fixup,
.devicestate = chan_pjsip_devicestate,
.queryoption = chan_pjsip_queryoption,
.func_channel_read = pjsip_acf_channel_read,
.get_pvt_uniqueid = chan_pjsip_get_uniqueid,
.properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER | AST_CHAN_TP_SEND_TEXT_DATA
};
chan_pjsip實現了自己的send_digit_begin和send_digit_end函數。這裏以chan_pjsip_digit_begin爲例。
/*! \brief Function called by core to start a DTMF digit */
static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
struct ast_sip_session_media *media;
int res = 0;
media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
switch (channel->session->dtmf) {
case AST_SIP_DTMF_RFC_4733:
if (!media || !media->rtp) {
return -1;
}
ast_rtp_instance_dtmf_begin(media->rtp, digit);
break;
case AST_SIP_DTMF_AUTO:
if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
return -1;
}
ast_rtp_instance_dtmf_begin(media->rtp, digit);
break;
case AST_SIP_DTMF_AUTO_INFO:
if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_NONE)) {
return -1;
}
ast_rtp_instance_dtmf_begin(media->rtp, digit);
break;
case AST_SIP_DTMF_NONE:
break;
case AST_SIP_DTMF_INBAND:
res = -1;
break;
default:
break;
}
return res;
}
可以看出,INBAND下沒有去設置RTP DTMF,但是RTC4733/AUTO下就設置了。進一步分析ast_rtp_instance_dtmf_begin函數。該函數會調用RTP engine中的ast_rtp_dtmf_begin函數。