pjsip 实现 DTMF 数据获取,并解析按键信息

背景:

业务需要在 android 设备上添加支持通过网关拨打客户电话,并根据客户按键反馈执行相应的操作

平台:

RK3399 + Android 7.1 + pjproject-2.4

步骤:

对 sip 这块小白一枚,接到任务后做了如下操作:

1 了解 pjsip 、DTMF,并编译出所需要的库

这里参考了:《 https://www.cnblogs.com/lijingcheng/p/4454928.html DTMF三种模式(SIPINFO,RFC2833,INBAND)》介绍,了解了什么是 DTMF,内容如下:

1、DTMF(双音多频)定义:由高频音和低频音的两个正弦波合成表示数字按键(0~9 * # A B C D)。

2、SIP中检测DTMF数据的方法:SIPINFO、RFC2833、INBAND

1)SIPINFO

为带外检测方式,通过SIP信令通道传输DTMF数据。没有统一的实现标准,目前以Cisco SIPINFO为标准,通过SIPINFO包中的signal字段识别DTMF按键。注意当DTMF为“*”时不同的标准实现对应的signal=*或signal=10。SIPINFO的好处就是不影响RTP数据包的传输,但可能会造成不同步。

2)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参数。

3)INBAND

为带内检测方式,而且与普通的RTP语音包混在一起传送。在进行INBAND DTMF检测时唯一的办法就是提取RTP数据包进行频谱分析,经过频谱分析得到高频和低频的频率,然后查表得到对应的按键,进行频谱分析的算法一般为Goertzel,这种算法的实现也很简单,网上有很多可以下到,但建议采用定点算法,浮点算法效率很低。

在选择压缩比很高码率很低的codec,比如G.723.1和G.729A等,建议不要使用INBAND模式,因为INBAND DTMF数据在进行复杂编解码后会产生失真,造成DTMF检测发生偏差或失败。

另外,还特别需要注意的一点就是很多SIP UA中INBAND都是伴随着RFC2833和SIPINFO同时发生的,这时需要区别对待,最好选择RFC2833和SIPINFO

2 抓sip通话过程中手机按键的数据包

通过 Wireshark 分析,在sip通过过程中,对方按键时,网关回传给 Android 设备的 DTMF 属于 RFC2833 的模式,截图如下:

pjsip 实现 DTMF 数据获取,并解析按键信息_第1张图片

 

3 pjsip 是否支持DTMF?

知道了当前网关是以 RFC2833 方式回传用户按键信息了,接下来就是一波网络检索的操作,要先确认下 pjsip 是否支持 DTMF?

找到一篇博客《 https://www.twblogs.net/a/5b8076cb2b71772165a7c25b PJSIP 檢測通話過程中對方手機發送過來的in-bnad DTMF(no rfc2833)按鍵信息》了解到 pjsip 不支持 in-band DTMF no rfc2833 这种 DTMF,然后看了下该文档中记录的操作,大概意思就是:检测 SIP 客户端接收到的数据流 ——> 用 Spandsp的库分析该数据流获取 DIMF 数据 ——> 添加回调得到按键信息

奈何我对 sip 的了解应该还没有该博主多,看了下添加的 code 也较繁琐,就没有去实验了。但是其实现的大体思路和我一开始想的一样,若是 pjsip 不支持的话,就直接对数据流进行分析

 

4 开始寻找数据流的旅程

了解到 pjsip 不支持 DTMF 情况,暂敲定的方案是找到 rtp 获取数据流的函数,看下是否容易进行分析。

在这里为了方便实验看数据,我在 ./pjlib/include/pj/log.h 添加了如下code(要同步修改编译文件,加上 -llog 库),方便自己打 log:

#include

#define SEEINER_TAG "twz_sip"

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,SEEINER_TAG,__VA_ARGS__)

//#define LOGE(...)

折腾到快要下班的时间,终于截取到了 DTMF 数据流,用Android设备通过网关打自己手机,Android设备log同步打印出我手机的拨号按键信息,中间的寻找过程就不做叙述了,直接上结果:

1 transport_udp.c 中 on_rx_rtp 函数能获取每一帧的 RTP 数据

 

2 on_rx_rtp 会将取到的数据丢给 ioqueue_common_abs.c 文件中的 pj_ioqueue_recvfrom 函数进行分析

 

3 pj_ioqueue_recvfrom 函数将收到的数据进一步丢给 sock_bsd.c 文件中的 pj_sock_recvfrom 函数进行分析

 

4 在 pj_sock_recvfrom 函数中调用 recvfrom函数进一步对数据进行处理,这里打印 recvfrom 返回值 len 发现,每当我手机拨号时就会收到几帧 len = 16 的字节数据,对比之前抓包的 RTP EVENT 数据,怀疑这 16 字节包含按键信息,后经过打 log 发现,其第 13 个字节即为拨号键,第14字节标志点击某个键结束(这里第14 字节对我来说有两个作用:1 判断按了某个键,且用户已按完该键了 2 可以分析用户连续按了某个键次数)

即以上4步RTP 数据流通过程:

on_rx_rtp ——> pj_ioqueue_recvfrom ——> pj_sock_recvfrom ——> recvfrom

 

5 在 RTP 流中找到了按键数据后,因为对 pjsip 不够了解,且为了最小化对 sip 业务流程影响,我直接在 pj_sock_recvfrom 函数里添加了回调函数,并在 sock_bsd.c 中实现了 注册该回调的函数

 

6 在 pjsua2_wrap.cpp jni 文件实现回调函数,再写个 jni 函数回调 java 函数将每次的按键数据给实时的上报上去

至此,Android设备端就可以通过 pjsip 实时获取通话过程中用户的按键信息了

 

你可能感兴趣的:(pjsip 实现 DTMF 数据获取,并解析按键信息)