• 关于sip呼叫成功后,对方立马挂断的情况说明


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

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

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

    现在采取的是第二种方法,但是实际传送中碰到了一些问题,如下图所示,左边为拨打方,右边为本地系统。
    在这里插入图片描述
    左边发起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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    其中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->
    }
    
    • 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

    之前定义的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;
    		}
    	}
    }
    
    • 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
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    最后,说下我们的本地提示语文件的实际情况,采样率为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;  
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    如上代码所示:从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();
    }
    
    • 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
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

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

  • 相关阅读:
    用树莓派PICO做一个桌面时钟超详细教程!
    并发容器介绍(一)
    Linux虚拟机部署运行OSU Micro Benchmark
    从 C 到 C++ 编程 — 基于 template 的泛型编程
    大数据技术学习笔记(二)—— Hadoop 运行环境的搭建
    信息系统项目管理师 第四版 第5章 信息系统工程
    使用Object.key和delete来将对象中值为空的属性删除。
    土壤氮磷钾传感器
    天津大数据培训学校 大数据可从事的行业
    Jmeter接口测试响应数据中文显示为Unicode码的解决方法
  • 原文地址:https://blog.csdn.net/tusong86/article/details/126530677