PJSIP 检测通话过程中对方手机发送过来的in-bnad DTMF(no rfc2833)按键信息

最近做项目有用到在PJSIP客户端接收手机通过过程中发送过来的DTMF按键信息,刚开始查了一点资料发现pjsip有相应的方法,在pjsua_config  cfg; 这个结构体的通过设置此回调函数cfg.cb.on_call_state貌似就可以实现 ,之后就开始实践了。用pjsip客户端+freeswitch+x-lite进行试验,试验相当顺利,心情那个激动啊。但是没激动多久,当我放到正式的运营环境用我的pjsip客户端+IMS运营商+真实手机进行测试时彻底傻眼了,根本检测不出来手机的按键信息,查了资料才发现手机发送的DTMF信息应该是属于in-band DTMF no rfc2833 ,悲催的是PJSIP官方并没有支持这中DTMF格式。

还好没放弃经过一个多月的瞎折腾总算弄出点结果出来了。

PJSIP官网的FAQ有对in-band DTMF no rfc2833问题解释,我就不扯淡了,直接给链接 http://trac.pjsip.org/repos/wiki/FAQ#tone-detect  。解释到是挺简单的,看起来很容易,但是对于我这中新手,我就只能呵呵了。查了快一个月的资料到是发现三四种方法,一一去试,没一个成功的,心情郁闷啊。最后还是厚着脸皮,凭着自己一手烂英语邮件去问外国友人。还好,外国友人都是相当热情的,等了一天就有人回复了,在这里要感谢 Eize Slange 这位大哥,不仅很快就给出了回复而且也给了相应的实现代码,非常感谢啊。

参照着给出的代码来进行实现果然有效,高兴啊!!!

我就简单介绍一下他给出的示例代码吧。

要取得in-band DTMF 按键信息就要检测sip客户端接收到的RTP流数据,并对RTP数据流使用Spandsp的库分析其中的DTMF按键信息,然后就大功告成了。

代码流程如下:

1.pjsip客户端层

on_call_media_state 的回调函数中修改代码如下:

Note: pCallData / callData is some own class holding some info per call...


callback: on_call_media_state:

...
    /* Handle media status */
    switch (call_info.media_status) 
    {
      case PJSUA_CALL_MEDIA_ACTIVE:
 ...

 pj_status_t   result     = PJ_SUCCESS;
 pjmedia_port* media_port = NULL;
 unsigned      slot_port  = 0;
 pjsua_dtmfdet_config dtmf_cfg;

 // Only attach once...              
 if ( NULL == callData->dtmfData.media_port )
 {
dtmf_cfg.call_id          = call_id;
dtmf_cfg.filter_dialtone  = PJ_FALSE;
dtmf_cfg.twist            = 8;   // Twist is set to 8 dB by default.
dtmf_cfg.reverse_twist    = 4;   // Reverse twist is set to 4 dB by default. This value can be safely increased up to 6 or 7 without a significant increase in talk-off to allow DTMFs that exceed this threshold to be detected.
dtmf_cfg.threshold        =-42;  // Threshold is set to -42 dBm0 by default.
dtmf_cfg.cb.on_dtmf_digit = &on_ib_dtmf_callback;    

log_writer(SLL_EVENT, "StackDll:on_call_media_state: Attach INBAND DTMF detection");
result =  pjsua_attach_inband_dtmf_detector( call_id,
 app_config.media_cfg.clock_rate, 
app_config.media_cfg.channel_count,
160,      // samples per frame
 16,       // bits per sample
&dtmf_cfg,
PJ_FALSE, // because this called from pjsua-callback
&media_port,
&slot_port );

if ( PJ_SUCCESS == result )
{
 callData->dtmfData.media_port = media_port;
 callData->dtmfData.slot_port  = slot_port;
}
...


 
callback: on_stream_destroyed::

...
    // Release/reset Inband DTMF
    pCallData = CallGetCallData(call_id);
    if ( NULL != pCallData )
    {
      if ( NULL != pCallData->dtmfData.media_port )
      {
        log_writer(SLL_LEVEL_2, "StackDll:on_stream_destroyed> Detach INBAND detector...");
        pjsua_detach_inband_dtmf_detector( pCallData->dtmfData.media_port,
                                           pCallData->dtmfData.slot_port,
                                           PJ_FALSE); // because called from callback
      }

      pCallData->dtmfData.detectedOutbound = PJ_FALSE;
      pCallData->dtmfData.lastDigit        = -1;
      pCallData->dtmfData.media_port       = NULL;
      pCallData->dtmfData.slot_port        = 0;
    }
...
static void on_ib_dtmf_callback(pjsua_call_id call_id, int dtmf)
{
  // Your callback code when inband DTMF digit has been detected.
  // Note: this is the same callback prototype as the normal DMTF callback of PJSIP,
  //       but to distinguish between RFC2833 DTMF and this inband a dedicated
  //       callback can be created.
}


2.在pjsua.h新增如下:
pjsua-lib\pjsua.h
-----------------
...
/**
 * Functionalities around IN-BAND DTMF detection. 
 */
typedef struct pjsua_dtmfdet_callback
{
  void (*on_dtmf_digit)(pjsua_call_id call_id, int digit);

} pjsua_dtmfdet_callback;


typedef struct pjsua_dtmfdet_config
{
  pjsua_call_id           call_id;
  pj_bool_t               filter_dialtone;
  int                     twist;            // dB
  int                     reverse_twist;    // dB
  int                     threshold;        // dBm0
  pjsua_dtmfdet_callback  cb;

} pjsua_dtmfdet_config;

PJ_DECL(pj_status_t) 
pjsua_attach_inband_dtmf_detector
(
  pjsua_call_id         call_id,
  unsigned              clock_rate,
  unsigned              channel_count, 
  unsigned              samples_per_frame, 
  unsigned              bits_per_sample,
  pjsua_dtmfdet_config* config,
  pj_bool_t             needs_lock, 
  pjmedia_port        **media_port, /* OUT */
  unsigned             *slot_port   /* OUT */
);

PJ_DEF(pj_status_t) pjsua_detach_inband_dtmf_detector
(
  pjmedia_port         *media_port, 
  unsigned              slot_port,  
  pj_bool_t             needs_lock
);


3.在pjsua_media.c中新增如下:

pjsua-lib\pjsua_media.c
-----------------------

 * Inband DTMF detection.
 */
PJ_DEF(pj_status_t) pjsua_attach_inband_dtmf_detector
(
  pjsua_call_id         call_id,
  unsigned              clock_rate,
  unsigned              channel_count, 
  unsigned              samples_per_frame, 
  unsigned              bits_per_sample,
  pjsua_dtmfdet_config* config,
  pj_bool_t             needs_lock,
  pjmedia_port        **media_port, /* OUT */
  unsigned             *slot_port   /* OUT */
)
{
    unsigned slot;
    pj_pool_t *pool;
    pjmedia_port *port;
    pj_status_t status;

    const pj_str_t name = pj_str("dtmfdet");

    if ( needs_lock ) PJSUA_LOCK();

    pool = pjsua_pool_create("dtmfdet", 1000, 1000);
    if (!pool) {
        if ( needs_lock ) PJSUA_UNLOCK();
        return PJ_ENOMEM;
    }

    status = pjmedia_dtmfdet_port_create( pool, 
                                          clock_rate, 
                                          channel_count,
                                          samples_per_frame, 
                                          bits_per_sample,
                                          config,
                                         &port);

    if (status != PJ_SUCCESS) {
        if ( needs_lock ) PJSUA_UNLOCK();
        pj_pool_release(pool);
        return status;
    }

    status = pjmedia_conf_add_port(pjsua_var.mconf, pool, 
                                   port, &name, &slot);

    if (status != PJ_SUCCESS) {
        pjmedia_port_destroy(port);
        if ( needs_lock ) PJSUA_UNLOCK();
        pj_pool_release(pool);
        return status;
    }
    
    status = pjsua_conf_connect( pjsua_call_get_conf_port(call_id),
                                 slot );

    if (status != PJ_SUCCESS) {
        pjmedia_conf_remove_port(pjsua_var.mconf, slot); 
        pjmedia_port_destroy(port);
        if ( needs_lock ) PJSUA_UNLOCK();
        pj_pool_release(pool);
        return status;
    }

    // Finalizing
    if (media_port)*media_port = port;
    if (slot_port) *slot_port  = slot;

    if ( needs_lock ) PJSUA_UNLOCK();

    return PJ_SUCCESS;
}

PJ_DEF(pj_status_t) pjsua_detach_inband_dtmf_detector
(
  pjmedia_port         *media_port, 
  unsigned              slot_port,  
  pj_bool_t             needs_lock
)
{
  if ( needs_lock ) PJSUA_LOCK();

  pjmedia_conf_remove_port(pjsua_var.mconf, slot_port); 
  pjmedia_port_destroy(media_port);

  if ( needs_lock ) PJSUA_UNLOCK();

  return PJ_SUCCESS;
}



4.在pjmedia库中新增文件如下:

And the new plugin, located in pjmedia/src
I named it ib_dtmfdet_port.c.
Here you interface towards SpanDSP, but I moved all required functions into a new file
--------------------------------------------------------------------------------------

#include "pjmedia/ib_dtmfdet_port.h"
//#include "pjmedia/dtmf_spandsp.h"   //这个对我没用

#include 
#include 
#include 

//下面两行新增spandsp的头文件包含
#include 
#include 

#define THIS_FILE "ib_dtmfdet_port.c"
//#define SIGNATURE PJMEDIA_PORT_SIGNATURE('D','T','M','F')   //这个编译不同过改为下面一行
#define SIGNATURE PJMEDIA_SIG_PORT_CB


struct dtmfdet_port{ pjmedia_port base; pjsua_dtmfdet_config cfg;// dtmf_rx_state_t state; //这个参数设置为这样的话编译一直不通过,好像是因为只定义了一个类型?不太清楚,不过换成下面一行就行了
 
     
  
  dtmf_rx_state_t      *state;
};static void dtmfdet_digits_rx (void *user_data, const char *digits, int len);static pj_status_t dtmfdet_put_frame (pjmedia_port *this_port, const pjmedia_frame *frame);static pj_status_t dtmfdet_get_frame (pjmedia_port *this_port, pjmedia_frame *frame);static pj_status_t dtmfdet_destroy (pjmedia_port *this_port);PJ_DEF(pj_status_t) pjmedia_dtmfdet_port_create( pj_pool_t *pool, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, pjsua_dtmfdet_config* cfg, pjmedia_port **p_port){ const pj_str_t name = pj_str("dtmfdet"); struct dtmfdet_port *dport; PJ_ASSERT_RETURN(pool && clock_rate && p_port, PJ_EINVAL); PJ_ASSERT_RETURN(clock_rate == 8000, PJ_EINVAL); PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); // Create and initialize port dport = PJ_POOL_ZALLOC_T(pool, struct dtmfdet_port); PJ_ASSERT_RETURN(dport != NULL, PJ_ENOMEM); //下面的都换掉 //pjmedia_port_info_init(&dport->base.info, &name, SIGNATURE, clock_rate, // channel_count, bits_per_sample, samples_per_frame); //dtmf_rx_init(&dport->state, dtmfdet_digits_rx, dport); // Configure port //dport->cfg = *cfg; //dtmf_rx_parms(&dport->state, dport->cfg.filter_dialtone, dport->cfg.twist, dport->cfg.reverse_twist, dport->cfg.threshold);
 
     

dport->state = dtmf_rx_init(dport->state, dtmfdet_digits_rx, dport);


	 //Configure port
	dport->cfg = *cfg;


	dtmf_rx_parms(dport->state, dport->cfg.filter_dialtone,
		dport->cfg.twist, 
		dport->cfg.reverse_twist,
		dport->cfg.threshold);
  // Media port interface
  dport->base.put_frame  = &dtmfdet_put_frame;
  dport->base.get_frame  = &dtmfdet_get_frame;
  dport->base.on_destroy = &dtmfdet_destroy;

  *p_port = &dport->base;

  PJ_LOG(3, (THIS_FILE, "Inband DTMF detector '%.*s' created: filter_dialtone=%s, twist=%d, reverse_twist=%d, threshold=%d",
    (int) dport->base.info.name.slen, dport->base.info.name.ptr,
    dport->cfg.filter_dialtone ? "yes" : "no",
    dport->cfg.twist,
    dport->cfg.reverse_twist,
    dport->cfg.threshold
    ));

  return PJ_SUCCESS;
}

static void dtmfdet_digits_rx(void *user_data, const char *digits, int len)
{
  struct dtmfdet_port *dport = (struct dtmfdet_port*) user_data;
  int i;

  PJ_LOG(3, (THIS_FILE, "Inband digit(s) received: '%.*s'", len, digits));

  if (dport->cfg.cb.on_dtmf_digit != NULL) {
    for (i = 0; i < len; i++) {
      dport->cfg.cb.on_dtmf_digit(dport->cfg.call_id, digits[i]);
    }
  }
}

static pj_status_t dtmfdet_put_frame(pjmedia_port *this_port, const pjmedia_frame *frame)
{
  struct dtmfdet_port *dport = (struct dtmfdet_port*) this_port;

  if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) {

	//    dtmf_rx(&dport->state, (const pj_int16_t*) frame->buf, dport->base.info.samples_per_frame); //换掉
	dtmf_rx(dport->state, (const pj_int16_t*) frame->buf, 160/*dport->base.info.samples_per_frame*/); //最后一个参数我也不清楚,现在写这个应该没问题
  }

  return PJ_SUCCESS;
}

static pj_status_t dtmfdet_get_frame(pjmedia_port *this_port, pjmedia_frame *frame)
{
  PJ_UNUSED_ARG(this_port);
  PJ_UNUSED_ARG(frame);

  return PJ_EINVALIDOP;
}

static pj_status_t dtmfdet_destroy(pjmedia_port *this_port)
{
  PJ_UNUSED_ARG(this_port);

  return PJ_SUCCESS;
}

相应的.h文件我字节写的不保证准确性
#ifndef __IB_DTMFDET_PORT_H__
#define __IB_DTMFDET_PORT_H__

//#include "pjsua-lib/pjsua.h"
#include "pjsua-lib/pjsua.h"
#include 
PJ_BEGIN_DECL



PJ_DEF(pj_status_t) pjmedia_dtmfdet_port_create
(
 pj_pool_t *pool, 
 unsigned clock_rate,
 unsigned channel_count, 
 unsigned samples_per_frame, 
 unsigned bits_per_sample,
 pjsua_dtmfdet_config* cfg, 
 pjmedia_port **p_port
 );



PJ_END_DECL


#endif	/* __IB_DTMFDET_PORT_H__ */

最后要记得连接Spandsp动态库,网上这个东西貌似有点难找,可以直接下载Freeswitch源码,里面有spandsp源码和动态库。这样修改完的pjsip 库就能检测in-band DTMF 信息了。
还有可以在PJSIP的FAQ找到如何发送in-band DTMF按键的示例代码,这样就可以自己模拟了,不用非得正式的环境了,不过不确保能百分之百识别哦,毕竟是模拟的DTMF按键。http://trac.pjsip.org/repos/wiki/FAQ#inband-dtmf 这个是pjsip发送DTMF的链接地址。

忙前忙后忙了一个多月就这样一个小小的功能实在有点汗颜啊。下面还有我试过的其他方法,不过我都没试出结果列一下等以后看看吧
A.参考网址如下:  http://article.gmane.org/gmane.comp.voip.pjsip/19289/match=pjsip+inband+dtmf 这位外国友人貌似也实现了inband DTMF的检测,但是我试下来一直在调用我的回调函数最终溢出,谁有兴趣可以在试试看。
B.我在查资料的过程中又发现貌似pjsua_media_config media_config; 中设置的media_config.on_aud_prev_play_frame = &play_frame;回调函数可以获取playback方向的frame不过我用Spandsp进行分析时却无法分析出DTMF信息,不知道什么原因。
C. 参考网址如下:  http://comments.gmane.org/gmane.comp.voip.pjsip/9276 一开始就是试的这个方法,但是我感觉作者应该用 pjmedia_mem_player_create    这套函数来获取playback方向的frame,然而我也没试成功我感觉我应该是在pjmedia_mem_player_create    函数中的buff设置时出现的问题,因为我的回调函数一直在被调用。谁能弄出来麻烦告知一下,我快被他折磨死了。


就这样吧,水平有限多担待点吧,哈哈!


你可能感兴趣的:(SIP)