Asterisk PJSIP中继(IMS)呼叫开启VoLTE手机出现一接通就自动挂机问题

目录

一、问题描述

二、分析过程

1、网络抓SIP协议包

三、解决办法

版权声明:本文为博主(宽简厚重,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的报错信息为:

1
[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中。

1
2
3
4
5
6
7
8
9
10
11
12
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中被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*! \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为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*! \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函数。