最近在做SIP的接线员功能,类似于110这种,会有一些接线员提前上线;当外部人员拨打进来时,随机分配一个空闲的接线员来处理;若没有空闲的接线员,则系统自动发送一段系统正忙的声音给拨打方。

下面说说本人的实现,以及遇到的问题;系统接到外部呼叫后,首先查看有无可用的接线员,此时没有可用的接线员,则系统需要传送一段提示语给对端。关于如何传送提示语给对端,本人认为有如下两种方法:
第一:分配本地音频发送端口,构造sdp,SIP状态码为183,183通常被称为early media。系统将本地音频提示语发送到对端,发送完后,再发送个486状态码给对方,这种情况下,双方并未建立通话,拨打方即可听到提示语
第二:分配本地音频发送端口,构造sdp,SIP状态码为200,此时SIP呼叫建立,系统将本地音频提示语发送到对端,发送完后,再发送个BYE信令给对方。

这两种我都尝试过,都是可以的。

现在采取的是第二种方法,但是实际传送中碰到了一些问题,如下图所示,左边为拨打方,右边为本地系统。

freeswitch设置响铃超时时间 freeswitch 外呼命令_SOA


左边发起INVITE请求后,右边系统检测到无接线员,则分配本地端口,将本地端口随着200信令返回给对端,然后往对端的音频端口中发送数据,但是对端没等本端发完提示语,立即返回BYE信令给本端系统。现象上是双方呼叫刚建立,拨打方立即挂掉了电话,很是奇怪。

经排查,发现本端将提示语发给对端时,中间经过了freeswitch,freeswitch报如下错误:

2022-08-25 18:25:05.875183 99.87% [DEBUG] mod_sofia.c:671 SOFIA EXCHANGE_MEDIA
2022-08-25 18:25:05.915190 99.87% [DEBUG] switch_core_media.c:3314 alternate payload received (received 0, expecting 102)
2022-08-25 18:25:05.915190 99.87% [WARNING] switch_core_media.c:3325 Changing current codec to PCMU (payload type 0).
2022-08-25 18:25:05.935190 99.87% [DEBUG] switch_core_media.c:3768 Changing Codec from opus@20ms@48000hz to PCMU@20ms@8000hz
2022-08-25 18:25:05.955192 99.87% [DEBUG] mod_opus.c:725 Opus decoder stats: Frames[0] PLC[0] FEC[0]
2022-08-25 18:25:05.955192 99.87% [DEBUG] mod_opus.c:740 Opus encoder stats: Frames[0] Bytes encoded[0] Encoded length ms[0] Average encoded bitrate bps[0]
2022-08-25 18:25:05.955192 99.87% [DEBUG] mod_opus.c:725 Opus decoder stats: Frames[0] PLC[0] FEC[0]
2022-08-25 18:25:05.955192 99.87% [DEBUG] mod_opus.c:740 Opus encoder stats: Frames[0] Bytes encoded[0] Encoded length ms[0] Average encoded bitrate bps[0]
2022-08-25 18:25:05.955192 99.87% [DEBUG] switch_rtp.c:4494 RE-Starting timer [soft] 160 bytes per 20ms
2022-08-25 18:25:05.955192 99.87% [DEBUG] switch_core_media.c:3870 Set Codec sofia/internal/2022083102@60.12.13.106:28886 PCMU/8000 20 ms 160 samples 64000 bits 1 channels
2022-08-25 18:25:05.955192 99.87% [DEBUG] switch_core_codec.c:123 sofia/internal/2022083102@60.12.13.106:28886 Original read codec replaced with PCMU:0
2022-08-25 18:25:05.955192 99.87% [INFO] avcodec.c:1491 initializing encoder 352x288
2022-08-25 18:25:05.955192 99.87% [DEBUG] avcodec.c:1230 NVENC HW CODEC NOT PRESENT
2022-08-25 18:25:05.955192 99.87% [ERR] avcodec.c:1240 Cannot find encoder id: 27
2022-08-25 18:25:05.995170 99.87% [NOTICE] switch_core_media.c:16151 Activating write resampler
2022-08-25 18:25:05.995170 99.87% [CRIT] switch_core_media.c:16221 sofia/internal/2022083107@xx.xx.xx.98 not enough buffer space for required resample operation!
2022-08-25 18:25:05.995170 99.87% [NOTICE] switch_core_media.c:16223 Hangup sofia/internal/2022083107@xx.xx.xx.98 [CS_EXECUTE] [DESTINATION_OUT_OF_ORDER]

其中not enough buffer space for required resample operation!是freeswitch的控制台是标红的,即可认为是freeswitch向本地系统发送了BYE信令。

为此查看了音频提示文件发送这块

gpointer janus_feedback_voice_thread(gpointer user_data)
{
	janus_sip_session *session = (janus_sip_session *)user_data;
	if (!session)
	{
		g_thread_unref(g_thread_self());
		return NULL;
	}
	FILE* fp_record = fopen("/opt/janus/etc/janus/output.pcm", "r+");
	int total_time = 0;
	session->audio_send_account = 0;
	while (TRUE)
	{
		//char buffer[3840] = { 0 };
		char buffer[320] = { 0 };
		int nread = fread(buffer, sizeof(buffer), 1, fp_record);
		if (nread != 0)
		{
			janus_push_voice_to_sip(session, buffer, sizeof(buffer));
			usleep(18000);//防止读取过快,对方还没听完,下面的BYE信令都发过去了,导致只播放了前面一点声音
		}
		else
		{
			if (fp_record)
			{
				fclose(fp_record);
				fp_record = NULL;
			}
			break;
		}
	}
	///系统繁忙的提示语发送完毕后,自动挂断电话
	nua_bye(session->stack->s_nh_i, TAG_END());
	janus_refcount_increase(&session->ref);
	//session->
}

之前定义的buffer为3840大小,后面改成320就可以了。

janus_push_voice_to_sip的代码如下:

void janus_push_voice_to_sip(janus_sip_session *session, char *buf, int len)
{
	janus_rtp_header rtp_header;
	memset(&rtp_header, 0, sizeof(rtp_header));
	rtp_header.version = 2;
	rtp_header.markerbit = 0;	/* FIXME Should be 1 for the first packet */
	rtp_header.seq_number = htons(session->audio_send_account);
	rtp_header.timestamp = htonl(session->audio_send_account * 160);
	rtp_header.ssrc = htonl(10);
	///这个type是编码类型,0代表ULAW,8代表ALAW,目前我们找到的SIP厂商过来的编码是ULAW ,所以我们给过去,也需要ULAW
	rtp_header.type = 0;
	int outlen = 0;
	char *buffer = (char*)malloc(len / 2);
	outlen = G711EnCode(buffer, buf, len, G711ULAW);
	//char* buffer = opus_pcm_encoder_pcmu(&(session->opus_decoder_), buf, len, &outlen, rtp_header.type);
	char* sendbuf = (char*)malloc(outlen + RTP_HEADER_SIZE);
	if (buffer)
	{
		memcpy(sendbuf, &rtp_header, RTP_HEADER_SIZE);
		memcpy(sendbuf + RTP_HEADER_SIZE, buffer, outlen);
		free(buffer);
	}
	if (session->media.audio_ssrc == 0)
	{
		janus_rtp_header *header = (janus_rtp_header *)sendbuf;
		session->media.audio_ssrc = ntohl(header->ssrc);
		JANUS_LOG(LOG_VERB, "Got SIP audio SSRC: %"SCNu32"\n", session->media.audio_ssrc);
	}
	if (session->media.has_audio && session->media.audio_rtp_fd != -1)
	{
		/* Forward the frame to the peer */
		len = outlen + RTP_HEADER_SIZE;
		if (send(session->media.audio_rtp_fd, sendbuf, len, 0) < 0)
		{
			janus_rtp_header *header = (janus_rtp_header *)buf;
			guint32 timestamp = ntohl(header->timestamp);
			guint16 seq = ntohs(header->seq_number);
			JANUS_LOG(LOG_HUGE, "[SIP-%s] Error sending RTP audio packet... %s (len=%d, ts=%"SCNu32", seq=%"SCNu16")...\n",
				session->account.username, strerror(errno), len, timestamp, seq);
		}
		else
		{
			session->audio_send_account++;
		}
		if (sendbuf)
		{
			free(sendbuf);
			sendbuf = NULL;
		}
	}
}

最后,说下我们的本地提示语文件的实际情况,采样率为8000,单通道,s16le格式(单个采样2个字节,小端模式)。
通过rtp_header.timestamp = htonl(session->audio_send_account * 160),可以发现每次发送160个采样,1秒钟需要发送50次,即20毫秒发送一次。

下面说下为何要定义char buffer[320] = { 0 };

//Encode,type为0时,表示转alaw,1表示ulaw,这里面的type不是媒体负荷类型(payload type)
int G711EnCode(char* pCodecBits, char* pBuffer, int BufferSize, int type)  
{  
    int nBufferSize = BufferSize;
    if(pCodecBits == NULL || pBuffer == NULL || BufferSize <= 0)
        return -1;
    unsigned char* codecbits = (unsigned char*)pCodecBits;
    short* buffer = (short*)pBuffer;
    int i=0;
    if(type == 0){
        for(i=0; i<nBufferSize/2; i++)  {  
            codecbits[i] = linear2alaw(buffer[i]);  
        }  
    } else {
        for( i=0; i<nBufferSize/2; i++)  {  
            codecbits[i] = linear2ulaw(buffer[i]);  
        } 
    }
    
    return BufferSize/2;  
}

如上代码所示:从pcm转pcma或者pcmu时,相当于两个字节转换成一个字节,故而读取音频文件时,定义的结构体大小是char buffer[320] = { 0 };
当然,定义成char buffer[640] = { 0 };也是可以的,此时需要将rtp的时间戳改为320的倍数,并且usleep(18000)需要改为usleep(36000)。

关于linear2alaw和linear2ulaw的代码,网上有,这里就不列举了。

最后本人给出本端系统分配本地端口,并回复对端200,并创建线程,给对端发送音频的函数:

void janus_create_feedback_voice_thread(janus_sip_session *session, int code)
{
	//分配本地端口
	if (janus_sip_allocate_local_ports(session, FALSE) < 0)
	{
		return;
	}
	char szerror[100] = { 0 };
	char mysdp[2048] = { 0 };
	snprintf(mysdp, sizeof(mysdp), "v=0\r\n"
		"o=- 8451242493215333233 2 IN IP4 1.1.1.1\r\n"
		"s=-\r\n"
		"c=IN IP4 10.0.0.109\r\n"
		"t=0 0\r\n"
		"m=audio %d RTP/AVP 102 0 8 103 101\r\n"
		"a=rtpmap:102 opus/48000/2\r\n"
		"a=fmtp:102 minptime=10;useinbandfec=1\r\n"
		"a=rtpmap:0 PCMU/8000\r\n"
		"a=rtpmap:8 PCMA/8000\r\n"
		"a=rtpmap:103 telephone-event/48000\r\n"
		"a=rtpmap:101 telephone-event/8000\r\n"
		"a=mid:0\r\n"
		"m=video %d RTP/AVP 96\r\n"
		"a=rtpmap:96 H264/90000\r\n"
		"a=mid:1\r\n"
		"a=rtcp-fb:96 ccm fir\r\n"
		"a=rtcp-fb:96 nack\r\n"
		"a=rtcp-fb:96 nack pli\r\n"
		"a=fmtp:96 level-asymmetry-allowed=1;profile-level-id=42001f;packetization-mode=1\r\n"
		, session->media.local_audio_rtp_port, session->media.local_video_rtp_port);

	//char* msg_sdp = "v=0\r\no=- 5333580434084322102 2 IN IP4 1.1.1.1\r\ns=-\r\nt=0 0\r\nm=audio 9 UDP/TLS/RTP/SAVPF 0 8 101\r\nc=IN IP4 1.1.1.1\r\na=sendrecv\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:101 telephone-event/8000\r\n";
	janus_sdp *parsed_sdp = janus_sdp_parse(mysdp, szerror, sizeof(szerror));
	char *sdp = janus_sip_sdp_manipulate(session, parsed_sdp, TRUE);
	nua_respond(session->stack->s_nh_i, 200, sip_status_phrase(200), SOATAG_USER_SDP_STR(sdp),
		SOATAG_RTP_SELECT(SOA_RTP_SELECT_COMMON),
		NUTAG_AUTOANSWER(0),
		TAG_END());
	/*
	nua_respond(session->stack->s_nh_i, 183, sip_status_phrase(183), SOATAG_USER_SDP_STR(sdp),
		SOATAG_RTP_SELECT(SOA_RTP_SELECT_COMMON),
		NUTAG_AUTOANSWER(0),
		TAG_END());
		*/

	//session->status = janus_sip_call_status_incall;
	GError *error = NULL;
	char tname[64];
	g_snprintf(tname, sizeof(tname), "janus_sip_relay_thread");
	janus_refcount_increase(&session->ref);
	///这里接收对端的声音
	g_thread_try_new(tname, janus_sip_relay_thread, session, &error);

	janus_refcount_increase(&session->ref);
	g_snprintf(tname, sizeof(tname), "feedback voice thread");
	///这里将提示语发送给对端
	g_thread_try_new(tname, janus_feedback_voice_thread, session, &error);
	register_public_sip();
}

可以看到nua_respond有采取的是200,被注释掉的是183,这个我测试过,也是可以的。