spandsp_start_dtmf的bug及修复

概述

freeswitch是一款简单好用的VOIP开源软交换平台。

之前的文章中介绍过DTMF从2833到inband的转换,其中inband到2833的转换使用了"spandsp_start_dtmf",这个函数在转换的过程中有缺陷。

环境

CentOS 7.9

freeswitch 1.10.7

问题描述

在fs桥接的呼叫,经过"spandsp_start_dtmf"处理inband中的dtmf按键,转换到otherleg的时候,2833的dtmf可以正常产生,但是inband中的波形删除的不够干净,如果后续的语音节点会检测inband的话,就会产生重码的问题。

解决方案

修改思路,在spandsp对dtmf波形的处理过程中,缓存2个媒体包,当检测到DTMF时,将缓存的媒体包清空。

修改mod_dtptools.c

case SWITCH_DTMF_RTP:
    switch_channel_set_variable(switch_core_session_get_channel(session), "deduplicate_dtmf_seen_rtp", "true");
    /* change state to only allow RTP events */
    filter->only_rtp = 1;

    //modify by zr, 20241021, for DTMF inband to 2833
    /* stop inband detector */
    // switch_ivr_broadcast(switch_core_session_get_uuid(session), "spandsp_stop_dtmf::", SMF_ECHO_ALEG);
    break;

修改mod_spandsp_dsp.c

//modify by zr, 20241021, for DTMF inband to 2833
#define INBAND_DTMF_BUF_SIZE (2)
#define INBAND_DTMF_BUF_LEN (512)

typedef struct {
	switch_core_session_t *session;
	dtmf_rx_state_t *dtmf_detect;
	int verbose;
	char last_digit;
	uint32_t samples;
	uint32_t last_digit_end;
	uint32_t digit_begin;
	uint32_t min_dup_digit_spacing;
	int twist;
	int reverse_twist;
	int filter_dialtone;
	int threshold;
	switch_audio_resampler_t *resampler;
//modify by zr, 20241021, for DTMF inband to 2833
	char data_buf[INBAND_DTMF_BUF_SIZE][INBAND_DTMF_BUF_LEN];
	int buf_index;
} switch_inband_dtmf_t;

...

			dtmf_rx(pvt->dtmf_detect, dp, samples);

			//modify by zr, 20241021, for DTMF inband to 2833
			// switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "dtmf_rx return, last_hit=%d, in_digit=%d, current_sample=%d,"
			// 														"duration=%d, lost_digits=%d, current_digits=%d\n", 
			// 														pvt->dtmf_detect->last_hit, pvt->dtmf_detect->in_digit, pvt->dtmf_detect->current_sample, 
			// 														pvt->dtmf_detect->duration, pvt->dtmf_detect->lost_digits, pvt->dtmf_detect->current_digits);
			
			if(pvt->dtmf_detect->filter_dialtone)
			{	
				// double buffer mode
				if(pvt->dtmf_detect->last_hit > 0 || pvt->dtmf_detect->in_digit > 0) 
				{
					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(pvt->session), SWITCH_LOG_DEBUG, 
						"inband_dtmf_callback, memset frame to 0x00, pvt->dtmf_detect->last_hit=%d, pvt->dtmf_detect->in_digit=%d\n", 
						pvt->dtmf_detect->last_hit, pvt->dtmf_detect->in_digit);
					memset(pvt->data_buf, 0x00, INBAND_DTMF_BUF_LEN*INBAND_DTMF_BUF_SIZE);
					memset(frame->data, 0x00, frame->datalen);
				}
				else
				{
					char data_tmp[INBAND_DTMF_BUF_LEN] = {0};
					memcpy(data_tmp, frame->data, INBAND_DTMF_BUF_LEN);
					memcpy(frame->data, pvt->data_buf[pvt->buf_index], frame->datalen);
					memcpy(pvt->data_buf[pvt->buf_index], data_tmp, datalen);
				}
				pvt->buf_index = (1 - pvt->buf_index);
			}
			
			switch_core_media_bug_set_read_replace_frame(bug, frame);

...

	pvt->session = session;
	//modify by zr, 20241021, for DTMF inband to 2833
	pvt->buf_index = 0;

修改switch_core_media.c

				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Set 2833 dtmf send payload to %u recv payload to %u\n",
								  switch_channel_get_name(session->channel), smh->mparams->te, smh->mparams->recv_te);

				//add by zr 20241018, for 2833 to inband, update method
				//如果在183的协商中已经设置了inband模式,后续的update协商中需要取消inband模式的函数设置
				if (switch_true(switch_channel_get_variable(session->channel, "inband_flag"))) 
				{
					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "reset inband_flag and channel variables.\n");
					//B路
					switch_channel_set_variable(session->channel, "inband_flag", NULL);
					switch_channel_set_variable(session->channel, "spandsp_dtmf_rx_filter_dialtone", NULL);
					switch_channel_set_variable(session->channel, "execute_on_answer_101", NULL);
					switch_channel_set_variable(session->channel, "execute_on_answer_102", NULL);

					//A路,2833 to inband
					if( switch_core_session_get_partner(session, &other_session) == SWITCH_STATUS_SUCCESS )
					{
						switch_channel_set_variable(other_session->channel, "execute_on_answer_101", NULL);
						switch_core_session_rwunlock(other_session);
					}
				}

			} else {
				/* by default, use SIP INFO if 2833 is not in the SDP */
				if (!switch_false(switch_channel_get_variable(channel, "rtp_info_when_no_2833"))) {
					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "No 2833 in SDP.  Disable 2833 dtmf and switch to INFO\n");
					switch_channel_set_variable(session->channel, "dtmf_type", "info");
					smh->mparams->dtmf_type = DTMF_INFO;
					smh->mparams->recv_te = smh->mparams->te = 0;
				} else {
					// switch_channel_set_variable(session->channel, "dtmf_type", "none");
					// smh->mparams->dtmf_type = DTMF_NONE;
					// smh->mparams->recv_te = smh->mparams->te = 0;
					//add by zr 20241018, for 2833 to inband, update method
					switch_channel_set_variable(session->channel, "dtmf_type", "inband");
					smh->mparams->dtmf_type = DTMF_AUTO;
					smh->mparams->recv_te = smh->mparams->te = 0;
					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "set inband_flag, No 2833 in SDP. Disable 2833 dtmf and switch to INBAND.\n");
					
					//TODO: add inband dtmf
					//A路,2833 to inband
					if( switch_core_session_get_partner(session, &other_session) == SWITCH_STATUS_SUCCESS )
					{
						switch_channel_set_variable(other_session->channel, "execute_on_answer_101", "start_dtmf_generate");
						switch_core_session_rwunlock(other_session);
					}

					//B路,inband to 2833
					switch_channel_set_variable(session->channel, "inband_flag", "true");
					switch_channel_set_variable(session->channel, "spandsp_dtmf_rx_filter_dialtone", "true");
					switch_channel_set_variable(session->channel, "execute_on_answer_101", "deduplicate_dtmf");
					switch_channel_set_variable(session->channel, "execute_on_answer_102", "spandsp_start_dtmf");
				}
			}

测试

新的fs桥接的呼叫,经过"spandsp_start_dtmf"处理inband中的dtmf按键,在A路的波形中没有遗留。

总结

因为需要对inband的媒体流做缓存,所以该处理会产生40ms左右的语音时延。

空空如常

求真得真