最近在做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]
其中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,这个我测试过,也是可以的。