一个sofia-sip问题的解决过程记录

2023-05-02 15:34:22 浏览数 (2)

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

问题虽小,也好定位,但验证和修改过程,却不是那么容易,记录下来,后面碰到类似问题修改就容易了!

0 人点赞