Call Setting中的Line Switch功能作用

ALS功能允许一个移动设备通过单一IMSI关联两个独立线路,用户可以根据需求在这两条线路间进行呼入和呼出操作,并且每条线路都有独立的计费、电话号码及订阅配置。除了‘呼叫等待’服务会同时覆盖两条线路外,其他基本服务和附加服务均可独立配置。
该功能同CPHS规范中的ALS功能。具体内容为:ALS provides the MS with the capability of associating two alternate lines with one IMSI. A user will be able to make and receive calls on either line as desired and will be billed separately for calls on each line. Each line will be associated with a separate directory number (MSISDN) and separate subscription profile. Where ALS is available each directory number shall be treated separately from the point of view of basic and supplementary services, so that independent service sets may be assigned for each number. The exception is “Call Waiting”, which shall be treated as applying to both directory numbers if either or both numbers carry a subscription to this service. In such cases, the handset shall generate a call waiting indication if a call arrives whilst either line is engaged on another call. The user should be able to store and recover customised (alpha)numeric descriptions of the two lines. Where there is no user customised description, the handset shall display a default description of “Line 1” and “Line 2” for the two lines.The handset shall provide an indication of the line on which incoming and outgoing service is being requested by audible and/or visual differentiation. For outgoing calls, the handset shall enable the user to select the desired line via a single command interface.
pj_status_t pjsua_media_channel_create_sdp_t38(pjsua_call_id call_id, pj_pool_t *pool, const pjmedia_sdp_session *rem_sdp, pjmedia_sdp_session **p_sdp, int *sip_status_code, pj_bool_t createFwRule) { enum { MAX_MEDIA = 1 }; pjmedia_sdp_session *sdp; pjmedia_transport_info tpinfo; pjsua_call *call = &pjsua_var.calls[call_id]; pj_status_t status; int i; # ifdef SUPPORT_METABOLIC_MEDIA_PORT pj_uint16_t medport; pj_bool_t portAvaible = PJ_FALSE; # endif #if defined(PJSIP_HAS_FIREWALL_FILTER) && PJSIP_HAS_FIREWALL_FILTER!=0 char localIp[CMSIP_STR_40]; pj_uint16_t localPort; pj_uint16_t localRtcpPort; pj_str_t ip; PJ_FIREWALL_RULE* pFwRule = NULL; #endif pj_str_t *pBoundIp = &pjsua_var.BoundIp; int family = AF_INET; /* Return error if media transport has not been created yet * (e.g. application is starting) */ if (call->med_tp == NULL) { return PJ_EBUSY; } /* Media index must have been determined before */ pj_assert(call->audio_idx != -1); /* Create media if it's not created. This could happen when call is * currently on-hold */ if (call->med_tp_st == PJSUA_MED_TP_IDLE) { pjsip_role_e role; role = (rem_sdp ? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC); status = pjsua_media_channel_init(call_id, role, call->secure_level, pool, rem_sdp, sip_status_code); if (status != PJ_SUCCESS) { return status; } } /* Get media socket info */ pjmedia_transport_info_init(&tpinfo); # ifdef SUPPORT_METABOLIC_MEDIA_PORT #ifdef INCLUDE_VOIP_SUPPORT_IPV6 if (pjsua_var.BoundIp6.slen <= 0 || !strncmp(pjsua_var.BoundIp6.ptr, "::", 2)) { CMSIP_PRINT("-------use IPv4--------"); family = pj_AF_INET(); pBoundIp = &pjsua_var.BoundIp; } else { CMSIP_PRINT("-------use IPv6--------"); family = pj_AF_INET6(); pBoundIp = &pjsua_var.BoundIp6; } #endif for (i = 0; i < PJMEDIA_PORT_TEST_COUNT; ++i) { medport = pjmedia_endpt_get_mediaPort(pjsua_var.med_endpt); pjmedia_endpt_set_mediaPort(pjsua_var.med_endpt); /*test RTP media port*/ status = pjmedia_endpt_test_media_port(*pBoundIp, medport, &portAvaible, family); if (PJ_FALSE==portAvaible && EADDRINUSE == errno) { CMSIP_PRINT("-------address is already used-------"); errno = 0; continue; } else if (PJ_SUCCESS != status) { CMSIP_PRINT("----------test media port error-----------"); return status; } /*test RTCP media port*/ status = pjmedia_endpt_test_media_port(*pBoundIp, (medport+1), &portAvaible, family); if (PJ_FALSE==portAvaible && EADDRINUSE == errno) { CMSIP_PRINT("-------address is already used-------"); errno = 0; continue; } else if (PJ_SUCCESS != status) { CMSIP_PRINT("----------test media port error-----------"); return status; } else { break; } } if (i >= PJMEDIA_PORT_TEST_COUNT) { return status; } pjmedia_transport_udp_set_mediaPort(call->med_tp, medport); # endif pjmedia_transport_get_info(call->med_tp, &tpinfo); #if defined(PJSIP_HAS_FIREWALL_FILTER) && PJSIP_HAS_FIREWALL_FILTER!=0 if (createFwRule) { for (i = 0; i < PJ_ARRAY_SIZE(call->fwRule); ++i) { CMSIP_PRINT("--------ready to delete iptables rule--------"); pFwRule = &call->fwRule[i]; if (pFwRule->destination.slen > 0 && pFwRule->dport > 0) { pj_firewall_set_rule_accept(PJ_FIREWALLCFG_DEL, PJ_TRANSPORT_UDP, &pFwRule->destination, NULL, pFwRule->dport, NULL, NULL, -1, pj_firewall_get_fwtype(pFwRule->family) #ifdef INCLUDE_VOIP_SUPPORT_IPV6 , pFwRule->family #endif /* INCLUDE_VOIP_SUPPORT_IPV6 */ , pjsua_var.tcMark); } } pjmedia_transport_udp_local_info(call->med_orig, localIp, &localPort, &localRtcpPort, &family); ip = pj_str(localIp); CMSIP_PRINT("-----add iptables rule(dstIp(%s), localPort(%d), localRtcpPort(%d))", localIp, localPort, localRtcpPort); /*����DMZ������ʱ������Է���rtp�ȵ����ʹ�ñ�������DUT VOIP��rtp���ݰ�֮��ֱ�� ��DMZ��iptables����ת��������DUT VOIP�޷����յ����ݰ�������������Ҫ�ڷ���INVITE֮ ǰ�Ϳ�����Ӧ�˿ڡ�by yuchuwei*/ /*now, only support UDP*/ #ifdef INCLUDE_VOIP_SUPPORT_IPV6 if (AF_INET == family) { #endif /* INCLUDE_VOIP_SUPPORT_IPV6 */ status = pj_firewall_set_rule_accept(PJ_FIREWALLCFG_ADD, PJ_TRANSPORT_UDP, &ip, NULL, localPort, NULL, NULL, -1, pjsua_var.fwType #ifdef INCLUDE_VOIP_SUPPORT_IPV6 , AF_INET #endif /* INCLUDE_VOIP_SUPPORT_IPV6 */ , pjsua_var.tcMark); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error create Netfilter rule", status); return status; } #ifdef INCLUDE_VOIP_SUPPORT_IPV6 } #endif /* INCLUDE_VOIP_SUPPORT_IPV6 */ CMSIP_PRINT("=============add iptables rule end===================================="); pFwRule = &call->fwRule[0]; memset(pFwRule, 0, sizeof(PJ_FIREWALL_RULE)); pFwRule->protocol = PJ_TRANSPORT_UDP; strcpy(pFwRule->dstBuf, localIp); pFwRule->destination = pj_str(pFwRule->dstBuf); pFwRule->dport = localPort; pFwRule->sport = -1; pFwRule->family = family; status = pj_firewall_set_rule_accept(PJ_FIREWALLCFG_ADD, PJ_TRANSPORT_UDP, &ip, NULL, localRtcpPort, NULL, NULL, -1, pj_firewall_get_fwtype(family) #ifdef INCLUDE_VOIP_SUPPORT_IPV6 , family #endif /* INCLUDE_VOIP_SUPPORT_IPV6 */ , pjsua_var.tcMark); if (status != PJ_SUCCESS) { #ifdef INCLUDE_VOIP_SUPPORT_IPV6 if (AF_INET == family) { #endif /* INCLUDE_VOIP_SUPPORT_IPV6 */ pj_firewall_set_rule_accept(PJ_FIREWALLCFG_DEL, PJ_TRANSPORT_UDP, &ip, NULL, localPort, NULL, NULL, -1, pjsua_var.fwType #ifdef INCLUDE_VOIP_SUPPORT_IPV6 , pj_AF_INET() #endif /* INCLUDE_VOIP_SUPPORT_IPV6 */ , pjsua_var.tcMark); #ifdef INCLUDE_VOIP_SUPPORT_IPV6 } #endif /* INCLUDE_VOIP_SUPPORT_IPV6 */ pjsua_perror(THIS_FILE, "Error create Netfilter rule", status); return status; } CMSIP_PRINT("=============add iptables rule end===================================="); pFwRule = &call->fwRule[1]; memset(pFwRule, 0, sizeof(PJ_FIREWALL_RULE)); pFwRule->protocol = PJ_TRANSPORT_UDP; strcpy(pFwRule->dstBuf, localIp); pFwRule->destination = pj_str(pFwRule->dstBuf); pFwRule->dport = localRtcpPort; pFwRule->sport = -1; pFwRule->family = family; } #endif /* Create SDP */ status = pjmedia_endpt_create_sdp_t38(pjsua_var.med_endpt, pool, MAX_MEDIA, &tpinfo.sock_info, &sdp); if (status != PJ_SUCCESS) { if (sip_status_code) { *sip_status_code = 500; } return status; } /* If we're answering and the selected media is not the first media * in SDP, then fill in the unselected media with with zero port. * Otherwise we'll crash in transport_encode_sdp() because the media * lines are not aligned between offer and answer. */ if (rem_sdp && call->audio_idx != 0) { unsigned i; for (i=0; i<rem_sdp->media_count; ++i) { const pjmedia_sdp_media *rem_m = rem_sdp->media[i]; pjmedia_sdp_media *m; const pjmedia_sdp_attr *a; if ((int)i == call->audio_idx) continue; m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); pj_strdup(pool, &m->desc.media, &rem_m->desc.media); pj_strdup(pool, &m->desc.transport, &rem_m->desc.transport); m->desc.port = 0; /* Add one format, copy from the offer. And copy the corresponding * rtpmap and fmtp attributes too. */ m->desc.fmt_count = 1; pj_strdup(pool, &m->desc.fmt[0], &rem_m->desc.fmt[0]); if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, "rtpmap", &m->desc.fmt[0])) != NULL) { m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a); } if ((a=pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, "fmtp", &m->desc.fmt[0])) != NULL) { m->attr[m->attr_count++] = pjmedia_sdp_attr_clone(pool, a); } if (i==sdp->media_count) sdp->media[sdp->media_count++] = m; else { pj_array_insert(sdp->media, sizeof(sdp->media[0]), sdp->media_count, i, &m); ++sdp->media_count; } } } #if SUPPORT_STUN /* Add NAT info in the SDP */ if (pjsua_var.ua_cfg.nat_type_in_sdp) { pjmedia_sdp_attr *a; pj_str_t value; char nat_info[80]; value.ptr = nat_info; if (pjsua_var.ua_cfg.nat_type_in_sdp == 1) { value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info), "%d", pjsua_var.nat_type); } else { const char *type_name = pj_stun_get_nat_name(pjsua_var.nat_type); value.slen = pj_ansi_snprintf(nat_info, sizeof(nat_info), "%d %s", pjsua_var.nat_type, type_name); } a = pjmedia_sdp_attr_create(pool, "X-nat", &value); pjmedia_sdp_attr_add(&sdp->attr_count, sdp->attr, a); } #endif /* SUPPORT_STUN */ /* Give the SDP to media transport */ /* pjsip will crash if the result of negotiation is T38 and this media is the peer's second media */ #if 0 status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp, call->audio_idx); #else if (rem_sdp) { status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp, call->audio_idx); } else { status = pjmedia_transport_encode_sdp(call->med_tp, pool, sdp, rem_sdp, 0); } #endif if (status != PJ_SUCCESS) { if (sip_status_code) *sip_status_code = PJSIP_SC_NOT_ACCEPTABLE; return status; } /* Update currently advertised RTP source address */ pj_memcpy(&call->med_rtp_addr, &tpinfo.sock_info.rtp_addr_name, sizeof(pj_sockaddr)); *p_sdp = sdp; return PJ_SUCCESS; } static void stop_media_session(pjsua_call_id call_id) { CMSIP_PRINT("--Now, stop media session-------"); /*ycw-pjsip:here*/ pjsua_call *call = &pjsua_var.calls[call_id]; /*ycw-pjsip-delete conference*/ #if 0 if (call->conf_slot != PJSUA_INVALID_ID) { if (pjsua_var.mconf) { pjsua_conf_remove_port(call->conf_slot); } call->conf_slot = PJSUA_INVALID_ID; } #endif /*ycw-pjsip*/ PJSUA_LOCK(); if (call->session) { /*ycw-pjsip.20111104*/ #if 0 pjmedia_rtcp_stat stat; if ((call->media_dir & PJMEDIA_DIR_ENCODING) #if 0 && (pjmedia_session_get_stream_stat(call->session, 0, &stat) == PJ_SUCCESS) #endif ) { /* Save RTP timestamp & sequence, so when media session is * restarted, those values will be restored as the initial * RTP timestamp & sequence of the new media session. So in * the same call session, RTP timestamp and sequence are * guaranteed to be contigue. */ #if 0 call->rtp_tx_seq_ts_set = 1 | (1 << 1); call->rtp_tx_seq = stat.rtp_tx_last_seq; call->rtp_tx_ts = stat.rtp_tx_last_ts; #endif } #endif if (pjsua_var.ua_cfg.cb.on_stream_destroyed) { pjsua_var.ua_cfg.cb.on_stream_destroyed(call_id, call->session, 0); } pjmedia_session_destroy(call->session, call_id, call->cmHangup #if defined(PJ_MEDIA_TRANSIT_BY_PJSIP) && 0==PJ_MEDIA_TRANSIT_BY_PJSIP #if defined(INCLUDE_USB_VOICEMAIL) , call->isUsbVm #endif #endif # if defined(PJSIP_HAS_FIREWALL_FILTER) && PJSIP_HAS_FIREWALL_FILTER!=0 #ifdef INCLUDE_VOIP_SUPPORT_IPV6 ,pj_firewall_get_fwtype(call->fwRule[0].family) #else ,pjsua_var.fwType #endif , pjsua_var.tcMark #endif ); call->session = NULL; PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed", call_id)); } call->media_st = PJSUA_CALL_MEDIA_NONE; /*ycw-pjsip*/ PJSUA_UNLOCK(); CMSIP_PRINT("---stop media session. media state is none"); } pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id) { pjsua_call *call = &pjsua_var.calls[call_id]; stop_media_session(call_id); # if defined(PJ_MEDIA_TRANSIT_BY_PJSIP) && 0==PJ_MEDIA_TRANSIT_BY_PJSIP if ( # if defined(INCLUDE_USB_VOICEMAIL) call->isUsbVm == PJ_TRUE # else 0 # endif || # if defined(INCLUDE_PSTN_GATEWAY) call->isPstn # else 0 # endif ) { # if defined(INCLUDE_USB_VOICEMAIL) || defined(INCLUDE_PSTN_GATEWAY) pjsua_media_transport_destroy_for_single_call(call_id); # endif } # endif if (call->med_tp_st != PJSUA_MED_TP_IDLE) { pjmedia_transport_media_stop(call->med_tp); call->med_tp_st = PJSUA_MED_TP_IDLE; } if (call->med_orig && call->med_tp && call->med_tp != call->med_orig) { pjmedia_transport_close(call->med_tp); call->med_tp = call->med_orig; } /*ycw-pjsip-delete sound device*/ #if 0 check_snd_dev_idle(); #endif # if (defined(PJ_MEDIA_TRANSIT_BY_PJSIP) && 0==PJ_MEDIA_TRANSIT_BY_PJSIP) && \ (defined(INCLUDE_PSTN_GATEWAY) || defined(INCLUDE_USB_VOICEMAIL)) call->medTpReady = PJ_FALSE; # endif return PJ_SUCCESS; } # if 0 /* * DTMF callback from the stream. */ static void dtmf_callback(pjmedia_stream *strm, void *user_data, int digit) { PJ_UNUSED_ARG(strm); /* For discussions about call mutex protection related to this * callback, please see ticket #460: * http://trac.pjsip.org/repos/ticket/460#comment:4 */ if (pjsua_var.ua_cfg.cb.on_dtmf_digit) { pjsua_call_id call_id; call_id = (pjsua_call_id)(long)user_data; pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit); } } # endif pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, const pjmedia_sdp_session *local_sdp, const pjmedia_sdp_session *remote_sdp) { pjsua_call *call = &pjsua_var.calls[call_id]; pjmedia_session_info sess_info; pjmedia_stream_info *si = NULL; pj_status_t status; unsigned i; #if defined(PJSIP_HAS_FIREWALL_FILTER) && PJSIP_HAS_FIREWALL_FILTER!=0 PJ_FIREWALL_RULE* pFwRule = NULL; #endif char localIp[CMSIP_STR_40] = {0}; pj_uint16_t rtpPort = 0; pj_uint16_t rtcpPort = 0; pj_str_t tmpStr; int family = AF_INET; if (!pjsua_var.med_endpt) { /* We're being shutdown */ return PJ_EBUSY; } /*ycw-pjsip*/ /*we must compare the new stream with the old stram to decide to destroy the old stream or not*/ /* Destroy existing media session, if any. */ #if 0 prev_media_st = call->media_st; stop_media_session(call->index); #endif memset(&sess_info, 0, sizeof(sess_info)); /* Create media session info based on SDP parameters. */ status = pjmedia_session_info_from_sdp( call->inv->pool_prov, pjsua_var.med_endpt, PJMEDIA_MAX_SDP_MEDIA, &sess_info, local_sdp, remote_sdp); if (status != PJ_SUCCESS) { return status; } for (i = 0; i < sess_info.stream_cnt; ++i) { if (sess_info.stream_info[i].dir == PJMEDIA_DIR_NONE) { continue; } if (sess_info.stream_info[i].ulptime== 0) { sess_info.stream_info[i].ulptime = 20; } } /* Update audio index from the negotiated SDP */ call->audio_idx = find_audio_index(local_sdp, PJ_TRUE); CMSIP_PRINT("---call audio index(%d)---", call->audio_idx); /* Find which session is audio */ PJ_ASSERT_RETURN(call->audio_idx != -1, PJ_EBUG); PJ_ASSERT_RETURN(call->audio_idx < (int)sess_info.stream_cnt, PJ_EBUG); si = &sess_info.stream_info[call->audio_idx]; /* Reset session info with only one media stream */ sess_info.stream_cnt = 1; CMSIP_PRINT("-----stream cnt(%d)---", sess_info.stream_cnt); /*ycw-pjsip-t38. Ŀǰֻ֧��һ����*/ if (si != &sess_info.stream_info[0]) { pj_memcpy(&sess_info.stream_info[0], si, sizeof(pjmedia_stream_info)); si = &sess_info.stream_info[0]; } pjmedia_transport_udp_local_info(call->med_orig, localIp, &rtpPort, &rtcpPort, &family); CMSIP_PRINT("Get local rtp network address:%s:%d", localIp, rtpPort); CMSIP_PRINT("Get local rtcp network address:%s:%d", localIp, rtcpPort); CMSIP_PRINT("Get local family:%d(AF_INET:%d,AF_INET6:%d)", family, AF_INET, AF_INET6); tmpStr = pj_str(localIp); pj_sockaddr_init(family, &si->local_addr, &tmpStr, rtpPort); pj_sockaddr_init(family, &si->local_rtcp, &tmpStr, rtcpPort); /*ycw-pjsip-t38*/ /*for the new re-INVITE, we accept all. But, we must check the new stream equal the old stream or not. If they are same, we need not re-create the stream*/ if (call->session && call->session->stream_cnt == sess_info.stream_cnt && pjsua_media_stream_info_equal(&call->session->stream_info[0], si) /*now, just support one stream */) { CMSIP_PRINT("----the existed session has the same transport info, do not re-create--"); goto ON_SUCCESS; } CMSIP_PRINT("destroy old stream, create new stream."); /*ycw-pjsip*/ stop_media_session(call->index); /* Check if no media is active */ if (sess_info.stream_cnt == 0 || si->dir == PJMEDIA_DIR_NONE) { /* Call media state */ call->media_st = PJSUA_CALL_MEDIA_NONE; /* Call media direction */ call->media_dir = PJMEDIA_DIR_NONE; /* Don't stop transport because we need to transmit keep-alives, and * also to prevent restarting ICE negotiation. See * http://trac.pjsip.org/repos/ticket/1094 */ #if 0 /* Shutdown transport's session */ pjmedia_transport_media_stop(call->med_tp); call->med_tp_st = PJSUA_MED_TP_IDLE; /* No need because we need keepalive? */ /* Close upper entry of transport stack */ if (call->med_orig && (call->med_tp != call->med_orig)) { pjmedia_transport_close(call->med_tp); call->med_tp = call->med_orig; } #endif } else { pjmedia_transport_info tp_info; /* Start/restart media transport */ CMSIP_PRINT("----start media transport-------------"); status = pjmedia_transport_media_start(call->med_tp, call->inv->pool_prov, local_sdp, remote_sdp, call->audio_idx); if (status != PJ_SUCCESS) return status; CMSIP_PRINT("-----media transport state is running---------"); call->med_tp_st = PJSUA_MED_TP_RUNNING; /* Get remote SRTP usage policy */ pjmedia_transport_info_init(&tp_info); pjmedia_transport_get_info(call->med_tp, &tp_info); if (tp_info.specific_info_cnt > 0) { unsigned i; for (i = 0; i < tp_info.specific_info_cnt; ++i) { if (tp_info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_SRTP) { pjmedia_srtp_info *srtp_info = (pjmedia_srtp_info*) tp_info.spc_info[i].buffer; call->rem_srtp_use = srtp_info->peer_use; break; } } } /*ycw-pjsip*/ #if 0 /* Override ptime, if this option is specified. */ if (pjsua_var.media_cfg.ptime != 0) { si->param->setting.frm_per_pkt = (pj_uint8_t) (pjsua_var.media_cfg.ptime / si->param->info.frm_ptime); if (si->param->setting.frm_per_pkt == 0) si->param->setting.frm_per_pkt = 1; } #endif /* Disable VAD, if this option is specified. */ if (pjsua_var.media_cfg.no_vad) { si->param->setting.vad = 0; } /* Optionally, application may modify other stream settings here * (such as jitter buffer parameters, codec ptime, etc.) */ #if 0 si->jb_init = pjsua_var.media_cfg.jb_init; si->jb_min_pre = pjsua_var.media_cfg.jb_min_pre; si->jb_max_pre = pjsua_var.media_cfg.jb_max_pre; si->jb_max = pjsua_var.media_cfg.jb_max; #endif /* ycw-pjsip: vbd support */ if (call->request_channel_mode == pj_channel_passthrough) { #if 0 si->jb_min_pre = si->jb_max_pre = -2; #endif si->vad = si->cng = 0; } /* Set SSRC */ #if 0 si->ssrc = call->ssrc; #endif /* Set RTP timestamp & sequence, normally these value are intialized * automatically when stream session created, but for some cases (e.g: * call reinvite, call update) timestamp and sequence need to be kept * contigue. */ #if 0 si->rtp_ts = call->rtp_tx_ts; si->rtp_seq = call->rtp_tx_seq; si->rtp_seq_ts_set = call->rtp_tx_seq_ts_set; #endif #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 /* Enable/disable stream keep-alive and NAT hole punch. */ si->use_ka = (call->acc_id >= 0) ? pjsua_var.acc[call->acc_id].cfg.use_stream_ka : PJ_TRUE; #endif /* Create session based on session info. */ status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info, &call->med_orig, call, call_id, # if (!(defined(PJ_MEDIA_TRANSIT_BY_PJSIP) && 0==PJ_MEDIA_TRANSIT_BY_PJSIP) || \ defined(INCLUDE_PSTN_GATEWAY)) &call->dsp_med_tp, # endif # if defined(PJ_MEDIA_TRANSIT_BY_PJSIP) && 0==PJ_MEDIA_TRANSIT_BY_PJSIP # if defined(INCLUDE_USB_VOICEMAIL) call->isUsbVm, # endif # if defined(INCLUDE_PSTN_GATEWAY) call->isPstn, # endif # endif # if defined(PJSIP_HAS_FIREWALL_FILTER) && PJSIP_HAS_FIREWALL_FILTER!=0 #ifdef INCLUDE_VOIP_SUPPORT_IPV6 pj_firewall_get_fwtype(call->fwRule[0].family), #else pjsua_var.fwType, #endif pjsua_var.tcMark, # endif &call->session ); if (status != PJ_SUCCESS) { return status; } #if defined(PJSIP_HAS_FIREWALL_FILTER) && PJSIP_HAS_FIREWALL_FILTER!=0 for (i = 0; i < PJ_ARRAY_SIZE(call->fwRule); ++i) { pFwRule = &call->fwRule[i]; if (pFwRule->destination.slen > 0 && pFwRule->dport > 0) { status = pj_firewall_set_rule_accept(PJ_FIREWALLCFG_DEL, pFwRule->protocol, &(pFwRule->destination), NULL, pFwRule->dport, NULL, NULL, -1, pj_firewall_get_fwtype(pFwRule->family) #ifdef INCLUDE_VOIP_SUPPORT_IPV6 , pFwRule->family #endif /* INCLUDE_VOIP_SUPPORT_IPV6 */ , pjsua_var.tcMark); if (status != PJ_SUCCESS) { printf("==%s,%d==firewall rule set error!!!!====", __FUNCTION__, __LINE__); } } memset(pFwRule, 0, sizeof(PJ_FIREWALL_RULE)); } #endif CMSIP_PRINT("----session create successfully--------------------------------"); /* If DTMF callback is installed by application, install our * callback to the session. */ if (pjsua_var.ua_cfg.cb.on_dtmf_digit) { #if 0 pjmedia_session_set_dtmf_callback(call->session, 0, &dtmf_callback, (void*)(long)(call->index)); #endif } /* Call media direction */ call->media_dir = si->dir; /* Call media state */ if (call->local_hold) { call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD; } else if (call->media_dir == PJMEDIA_DIR_DECODING) { call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD; } else { call->media_st = PJSUA_CALL_MEDIA_ACTIVE; } } ON_SUCCESS: /* Print info. */ { char info[80]; int info_len = 0; unsigned i; for (i = 0; i < sess_info.stream_cnt; ++i) { int len; const char *dir; pjmedia_stream_info *strm_info = &sess_info.stream_info[i]; switch (strm_info->dir) { case PJMEDIA_DIR_NONE: dir = "inactive"; break; case PJMEDIA_DIR_ENCODING: dir = "sendonly"; break; case PJMEDIA_DIR_DECODING: dir = "recvonly"; break; case PJMEDIA_DIR_ENCODING_DECODING: dir = "sendrecv"; break; default: dir = "unknown"; break; } len = pj_ansi_sprintf( info+info_len, ", stream #%d: %.*s (%s)", i, (int)strm_info->fmt.encoding_name.slen, strm_info->fmt.encoding_name.ptr, dir); if (len > 0) { info_len += len; } } PJ_LOG(4,(THIS_FILE,"Media updates%s", info)); } return PJ_SUCCESS; } 在这段代码插入我的一个需求,也就是通知内核态接下来rtp的端口你觉得适合吗
10-21
你提出的问题非常关键: > **“在这段代码中插入一个‘通知内核态接下来 RTP 的端口’的需求,是否合适?”** 我们来从架构、流程和实现角度全面分析,并给出**最佳实践建议**。 --- ## ✅ 你的需求本质 你想在 PJSIP 媒体通道建立过程中: > 🔔 **提前将即将使用的 RTP/RTCP 端口告知内核(如通过 netfilter/iptables、eBPF、socket filter 或防火墙模块),以便允许这些 UDP 流量通过。** 这常见于以下场景: - NAT 穿透 - 防火墙策略动态配置 - DMZ 映射或 UPnP - 内核级 QoS 标记(TC / TOS) - 安全隔离系统(如 VoIP 网关) --- ## 🧩 当前代码位置分析:`pjsua_media_channel_create_sdp_t38()` 你现在要修改的函数是: ```c pj_status_t pjsua_media_channel_create_sdp_t38(...) ``` 它的职责是: 1. 创建用于 T.38 传真会话的 SDP 2. 分配 RTP/RTCP 端口(含端口探测) 3. 构建 SDP 并交给 INVITE/RE-INVITE 消息发送 📌 **在这个函数里已经可以获取到“即将使用的 RTP 端口”了!** 比如你在 `SUPPORT_METABOLIC_MEDIA_PORT` 分支中做了: ```c medport = pjmedia_endpt_get_mediaPort(...); ... pjmedia_transport_udp_set_mediaPort(call->med_tp, medport); ``` ✅ 所以此时你知道: - 本地绑定 IP:`pBoundIp` - RTP 端口:`medport` - RTCP 端口:`medport + 1` - 协议族:IPv4 / IPv6 👉 **理论上完全可以在此处插入“通知内核”的逻辑。** --- ## ⚠️ 但是:这是不是最合适的地方? ### ❌ 不推荐的理由(为什么不建议在这里做) | 问题 | 说明 | |------|------| | **太早了!SDP 还没协商成功** | 虽然你分配了端口,但对方可能拒绝这个 offer,或者 ICE 协商失败,最终不会使用该端口 | | **端口可能会变** | 在后续 `pjmedia_transport_media_start()` 中,transport 层(如 ICE)可能重新 bind 新端口 | | **违反职责分离原则** | SDP 创建函数应只负责生成媒体描述,不应承担“系统资源管理”任务 | | **可重入性风险** | 多个 call 同时执行此函数?锁保护不足可能导致竞争 | --- ## ✅ 推荐做法:应该在哪里通知内核? 你应该选择一个 **更靠后、更确定、且 transport 已稳定** 的时机。 ### ✅ 最佳插入点:`pjsua_media_channel_update()` 函数末尾,在 `pjmedia_session_create()` 成功之后 #### 👉 理由: - 此时已完成: - SDP 协商(offer/answer) - transport 初始化(包括 ICE、SRTP、TURN 等) - 实际 socket 已创建并绑定 - `si->local_addr` 包含真实使用的 RTP 地址和端口 - session 已创建,不会再轻易变更 #### 💡 插入示例代码: ```c // 在 pjmedia_session_create(...) 成功后插入 if (status == PJ_SUCCESS) { // 获取真实的本地 RTP 地址 pj_sockaddr *rtp_addr = &si->local_addr; pj_uint16_t rtp_port = pj_sockaddr_get_port(rtp_addr); pj_uint16_t rtcp_port = rtp_port + 1; pj_str_t *bound_ip = &tmpStr; // 来自 localIp 字符串 // 调用你的内核通信接口(伪代码) notify_kernel_rtp_ports(bound_ip, rtp_port, rtcp_port, family, call_id); CMSIP_PRINT("Notified kernel: RTP=%s:%d, RTCP=%d", bound_ip->ptr, rtp_port, rtcp_port); } ``` --- ## 🔧 如何实现“通知内核”? 取决于你的系统架构,有多种方式: ### 方式 1:Netlink Socket(推荐 Linux 用户态↔内核通信) ```c int notify_kernel_rtp_ports(const pj_str_t *ip, uint16_t rtp, uint16_t rtcp, int af, int call_id) { struct sockaddr_nl dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; struct msghdr msg; int sock_fd; sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_YOUR_TYPE); if (sock_fd < 0) return -1; memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; /* kernel */ dest_addr.nl_groups = 0; bind(sock_fd, (struct sockaddr*)&dest_addr, sizeof(dest_addr)); nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(sizeof(struct rtp_rule_msg))); nlh->nlmsg_len = NLMSG_SPACE(sizeof(struct rtp_rule_msg)); nlh->nlmsg_pid = getpid(); nlh->nlmsg_flags = 0; struct rtp_rule_msg *data = (struct rtp_rule_msg*)NLMSG_DATA(nlh); data->call_id = call_id; data->af = af; strncpy(data->ip, ip->ptr, sizeof(data->ip)-1); data->rtp_port = rtp; data->rtcp_port = rtcp; iov.iov_base = (void*)nlh; iov.iov_len = nlh->nlmsg_len; memset(&msg, 0, sizeof(msg)); msg.msg_name = (void*)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(sock_fd, &msg, 0); close(sock_fd); free(nlh); return 0; } ``` --- ### 方式 2:ioctl + 字符设备驱动 ```c int fd = open("/dev/rtp_filter", O_RDWR); if (fd >= 0) { struct rtp_open_rule rule = { .ip = "192.168.1.100", .rtp_port = 10000, .rtcp_port = 10001, .proto = IPPROTO_UDP, .action = RULE_ALLOW }; ioctl(fd, RTP_IOC_OPEN_PORTS, &rule); close(fd); } ``` --- ### 方式 3:写 proc 文件或 sysfs 节点(简单但不灵活) ```bash echo "allow udp 192.168.1.100 10000 10001" > /proc/net/rtp_fw ``` 用户态程序写入,内核模块监听变化。 --- ## ✅ 结论:是否适合在当前函数插入? | 维度 | 回答 | |------|------| | **技术上可行吗?** | ✅ 可行,此时已有端口号 | | **语义上合理吗?** | ❌ 不合理,太早,端口尚未最终确认 | | **会不会出错?** | ⚠️ 有可能通知了错误端口(如后面被 ICE 改写) | | **有没有更好的地方?** | ✅ 有!推荐在 `pjsua_media_channel_update()` 创建 session 后 | --- ## 🛠️ 推荐改造方案总结 ### ✅ 修改步骤: 1. **不要在 `pjsua_media_channel_create_sdp_t38()` 中通知内核** 2. **而在 `pjsua_media_channel_update()` 中,`pjmedia_session_create()` 成功后添加调用:** ```c if (status == PJ_SUCCESS) { notify_kernel_about_rtp_ports(call_id, si->local_addr); } ``` 3. **确保在 `stop_media_session()` 或 `pjsua_media_channel_deinit()` 中释放规则:** ```c notify_kernel_close_rtp_ports(call_id); // 删除 iptables/netfilter 规则 ``` 4. **考虑线程安全与并发控制(多个 call 同时存在)** --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值