1、问题:
广播终端作为被叫方,接听时,只看对方的视频,本端的视频是recvonly,webrtc生成sdp时是recvonly,但是janus转到freeswitch后,freeswitch收到的sdp的video部分却成了sendrecv;
janus生成的sdp video部分:a=recvonly
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
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的过程出问题了,发送代码:
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
#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 是需要有协商的过程,然后打印日志,大概找到了问题的地方:
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]) called\n", 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方法这里:
/* 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方法,详细看了这个方法的代码,一下子就知道问题出在什么地方了;
修改前:
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为主!
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协议栈,同样修改代码后,就符合预期了!
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
问题虽小,也好定位,但验证和修改过程,却不是那么容易,记录下来,后面碰到类似问题修改就容易了!
-------------------广告线---------------
项目、合作,欢迎勾搭,邮箱:promall@qq.com
本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com