1、问题:
广播终端作为被叫方,接听时,只看对方的视频,本端的视频是recvonly,webrtc生成sdp时是recvonly,但是janus转到freeswitch后,freeswitch收到的sdp的video部分却成了sendrecv;
janus生成的sdp video部分:a=recvonly
代码语言:javascript复制Detected video codec: 96 (opus)
Prepared sofia sip tag SDP for 200 OK:
v=0
o=- 1414340834323651842 2 IN IP4 1.1.1.1
s=-
t=0 0
m=audio 28382 RTP/AVP 0 8 96 9 112
c=IN IP4 192.168.16.83
a=sendrecv
a=mid:0
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:96 opus/48000/2
a=fmtp:96 minptime=10;useinbandfec=1
a=rtpmap:9 G722/8000
a=rtpmap:112 telephone-event/8000
m=video 38998 RTP/AVP 96 97
c=IN IP4 192.168.16.83
a=recvonly
a=mid:1
a=rtpmap:96 H264/90000
a=rtcp-fb:96 nack pli
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:97 VP8/90000
a=rtcp-fb:97 nack pli
[4444] Call status change: [invited]-->[incall]
freeswitch接收到的sdp:a=recvonly却没有了,那默认就成了a=sendrecv
代码语言:javascript复制v=0
o=- 1414340834323651842 2 IN IP4 1.1.1.1
s=-
t=0 0
m=audio 28382 RTP/AVP 0 8 96 9 112
c=IN IP4 192.168.16.83
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:96 opus/48000/2
a=fmtp:96 minptime=10;useinbandfec=1
a=rtpmap:9 G722/8000
a=rtpmap:112 telephone-event/8000
a=mid:0
m=video 38998 RTP/AVP 96 97
c=IN IP4 192.168.16.83
a=rtpmap:96 H264/90000
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:97 VP8/90000
a=mid:1
a=rtcp-fb:96 nack pli
a=rtcp-fb:97 nack pli
问题定位:
janus已经打印出来sdp是带着a=recvonly的,那就出在janus发送sdp回应包到freeswitch的过程出问题了,发送代码:
代码语言:javascript复制 JANUS_LOG(LOG_VERB, "Prepared sofia sip tag SDP for 200 OK:n%s", sdp);
/* If the user negotiated simulcasting, just stick with the base substream */
json_t *msg_simulcast = json_object_get(msg->jsep, "simulcast");
if(msg_simulcast) {
JANUS_LOG(LOG_WARN, "Client negotiated simulcasting which we don't do here, falling back to base substream...n");
json_t *s = json_object_get(msg_simulcast, "ssrcs");
if(s && json_array_size(s) > 0)
session->media.simulcast_ssrc = json_integer_value(json_array_get(s, 0));
}
/* Also notify event handlers */
if(notify_events && gateway->events_is_enabled()) {
json_t *info = json_object();
json_object_set_new(info, "event", json_string(answer ? "accepted" : "accepting"));
if(session->callid)
json_object_set_new(info, "call-id", json_string(session->callid));
gateway->notify_event(&janus_sip_plugin, session->handle, info);
}
/* Check if the OK needs to be enriched with custom headers */
char custom_headers[2048];
janus_sip_parse_custom_headers(root, (char *)&custom_headers, sizeof(custom_headers));
/* Send 200 OK */
if(!answer) {
if(session->transaction)
g_free(session->transaction);
session->transaction = msg->transaction ? g_strdup(msg->transaction) : NULL;
}
g_atomic_int_set(&session->hangingup, 0);
janus_sip_call_update_status(session, janus_sip_call_status_incall);
if(session->stack->s_nh_i == NULL) {
JANUS_LOG(LOG_WARN, "NUA Handle for 200 OK still null??n");
}
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),
NUTAG_AUTOACK(FALSE),
TAG_IF(strlen(custom_headers) > 0, SIPTAG_HEADER_STR(custom_headers)),
TAG_END());
初步定位是nua_respond方法发送时,修改了sdp,具体是哪里修改的,还需要进一步定位;
nua_respond使用的是sofia-sip的协议栈,要定位出来具体是哪里修改的还不是那么容易,所以先打开了libsofia-sip的日志开关:
[root@lyz-VirtualBox sofia-sip-master]# vim ./libsofia-sip-ua/su/su_default_log.c
代码语言:javascript复制#ifdef SU_DEBUG
#undef SU_DEBUG
#define SU_DEBUG 9
#define SOFIA_DEBUG_ SU_DEBUG
#else
#define SOFIA_DEBUG_ 9
#endif
[root@lyz-VirtualBox sofia-sip-master]# vim ./libsofia-sip-ua/soa/soa_static.c
看了看SOATAG_USER_SDP_STR 是需要有协商的过程,然后打印日志,大概找到了问题的地方:
代码语言:javascript复制typedef enum {
sdp_inactive = 0,
sdp_sendonly = 1,
sdp_recvonly = 2,
sdp_sendrecv = sdp_sendonly | sdp_recvonly
} sdp_mode_t;
soa_static.c:1030 soa_sdp_mode_set() soa_sdp_mode_set(0x7f3cdcda9930, 0x7f3cf001b150, ""): called
soa_static.c:1095 soa_sdp_mode_set() soa_sdp_mode_set recv_mode:2, um->m_mode:3 send_mode:1,rm->m_mode:3)
soa_static.c:1095 soa_sdp_mode_set() soa_sdp_mode_set recv_mode:2, um->m_mode:2 send_mode:1,rm->m_mode:3)
soa_static.c:1030 soa_sdp_mode_set() soa_sdp_mode_set(0x7f3cdcda9930, 0x7f3cf001b150, ""): called
soa_static.c:1095 soa_sdp_mode_set() soa_sdp_mode_set recv_mode:2, um->m_mode:3 send_mode:1,rm->m_mode:3)
soa_static.c:1095 soa_sdp_mode_set() soa_sdp_mode_set recv_mode:2, um->m_mode:2 send_mode:1,rm->m_mode:3)
soa_static.c:1451 offer_answer_step() soa_static(0x7f3cf0018b60, soa_generate_answer): storing local description
a=rtpmap:soa_static.c:1095 soa_sdp_mode_set() soa_sdp_mode_set sm->m_mode:3 recv_mode:2, um->m_mode:3 send_mode:1,rm->m_mode:3)
soa_static.c:1095 soa_sdp_mode_set() soa_sdp_mode_set sm->m_mode:2 recv_mode:2, um->m_mode:2 send_mode:1,rm->m_mode:3)
soa_static.c:1451 offer_answer_step() soa_static(0x7f52600192d0, soa_generate_answer): storing local description
soa.c:1730 soa_activate() soa_activate(static::0x7f52600192d0, (nil)) called
soa.c:1270 soa_get_local_sdp() soa_get_local_sdp(static::0x7f52600192d0, [(nil)], [0x7f524b968a20], [0x7f524b968a1c]) called
tport.c:3286 tport_tsend() tport_tsend(0x7f5260006f80) tpn = UDP/192.168.16.83:5060
tport.c:4075 tport_resolve() tport_resolve addrinfo = 192.168.16.83:5060
//soa_static.c的offer_answer_step方法中:
static int offer_answer_step(soa_session_t *ss,
enum offer_answer_action action,
char const *by)
{
case generate_offer:
case generate_answer:
case process_answer:
s2u_ = s2u;
if (!s2u_) s2u_ = sss->sss_s2u;
if (soa_sdp_mode_set(user, s2u_, local, remote, ss->ss_hold, 1)) {
if (local != local0) {
*local0 = *local, local = local0;
DUP_LOCAL(local);
}
soa_sdp_mode_set(user, s2u_, local, remote, ss->ss_hold, 0);
}
break;
static
int soa_sdp_mode_set(sdp_session_t const *user,
int const *s2u,
sdp_session_t *session,
sdp_session_t const *remote,
char const *hold,
int dryrun)
int soa_get_local_sdp(soa_session_t const *ss,
struct sdp_session_s const **return_sdp,
char const **return_sdp_str,
isize_t *return_len)
{
sdp_session_t const *sdp;
char const *sdp_str;
SU_DEBUG_9(("soa_get_local_sdp(%s::%p, [%p], [%p], [%p]) calledn",
ss ? ss->ss_actions->soa_name : "", (void *)ss,
(void *)return_sdp, (void *)return_sdp_str, (void *)return_len));
if (ss == NULL)
return (void)su_seterrno(EFAULT), -1;
sdp = ss->ss_local->ssd_sdp;
sdp_str = ss->ss_local->ssd_str;
if (sdp == NULL)
return 0;
if (return_sdp)
*return_sdp = sdp;
if (return_sdp_str)
*return_sdp_str = sdp_str;
if (return_len)
*return_len = strlen(sdp_str);
return 1;
}
/** Set session descriptions. */
int soa_description_set(soa_session_t *ss,
struct soa_description *ssd,
sdp_session_t *sdp,
char const *sdp_str,
isize_t str_len)
{
int retval = -1;
sdp_printer_t *printer = NULL;
sdp_session_t *sdp_new;
char *sdp_str_new;
char *sdp_str0_new;
void *tbf1, *tbf2, *tbf3, *tbf4;
/* Store description in three forms: unparsed, parsed and reprinted */
sdp_new = sdp_session_dup(ss->ss_home, sdp);
printer = sdp_print(ss->ss_home, sdp, NULL, 0, 0);
sdp_str_new = (char *)sdp_message(printer);
if (sdp_str)
sdp_str0_new = su_strndup(ss->ss_home, sdp_str, str_len);
else
sdp_str0_new = sdp_str_new;
if (sdp_new && printer && sdp_str_new && sdp_str0_new) {
tbf1 = ssd->ssd_sdp, tbf2 = ssd->ssd_printer;
tbf3 = (void *)ssd->ssd_str, tbf4 = (void *)ssd->ssd_unparsed;
ssd->ssd_sdp = sdp_new;
ssd->ssd_printer = printer;
ssd->ssd_str = sdp_str_new;
ssd->ssd_unparsed = sdp_str0_new;
retval = 1;
}
else {
tbf1 = sdp_new, tbf2 = printer, tbf3 = sdp_str_new, tbf4 = sdp_str0_new;
}
su_free(ss->ss_home, tbf1);
sdp_printer_free(tbf2);
if (tbf3 != tbf4)
su_free(ss->ss_home, tbf4);
return retval;
}
关键的流程在offer_answer_step方法这里:
代码语言:javascript复制/* Step A: Create local SDP session (based on user-supplied SDP) */
/* Step B: upgrade local SDP (add m= lines to it) */
/* Step C: reject media */
/* Step D: Set media mode bits */
问题就出在第四步,设置本地的sdp的媒体mode,也就是soa_sdp_mode_set方法,详细看了这个方法的代码,一下子就知道问题出在什么地方了;
修改前:
代码语言:javascript复制 if (um->m_mode) { /* when its inactive, keep it inactive */
send_mode = (sdp_mode_t)(um->m_mode & sdp_sendonly);
if (rm)
send_mode = (rm->m_mode & sdp_recvonly) ? sdp_sendonly : 0 ;
} else send_mode = um->m_mode;
修改后:只是调整了下代码顺序,一个是以本端的设置为主,修改前是以对端的mode为主!
代码语言:javascript复制 if (um->m_mode) { /* when its inactive, keep it inactive */
if (rm)
send_mode = (rm->m_mode & sdp_recvonly) ? sdp_sendonly : 0;
send_mode = (sdp_mode_t)(um->m_mode & sdp_sendonly);
} else send_mode = um->m_mode;
最后验证,freeswitch虽然收到的是recvonly,但往外转的时候还是sndrecv,原来freeswitch也用的是一样的libsofia协议栈,同样修改代码后,就符合预期了!
代码语言:javascript复制m=video 49280 RTP/AVP 96 97
c=IN IP4 192.168.16.83
a=rtpmap:96 H264/90000
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:97 VP8/90000
a=recvonly
a=mid:1
a=rtcp-fb:96 nack pli
a=rtcp-fb:97 nack pli
问题虽小,也好定位,但验证和修改过程,却不是那么容易,记录下来,后面碰到类似问题修改就容易了!