diff options
author | Benny Prijono <bennylp@teluu.com> | 2007-03-23 16:34:20 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2007-03-23 16:34:20 +0000 |
commit | e3fd604ea862f68ad3ece248ca7d853899cbf48f (patch) | |
tree | 88fb4659ab449d79b25dc8e0dfe3b64f145b9dd3 /pjsip | |
parent | 05e7998ba4cbd7fb0b02b7f82c5b328cf203fbc9 (diff) |
ICE (work in progress): integration with PJSUA
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1098 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip')
-rw-r--r-- | pjsip/build/pjsua_lib.dsp | 4 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_endpoint.h | 9 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua.h | 130 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua_internal.h | 24 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_endpoint.c | 8 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 251 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 95 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_media.c | 460 |
8 files changed, 601 insertions, 380 deletions
diff --git a/pjsip/build/pjsua_lib.dsp b/pjsip/build/pjsua_lib.dsp index b15a7f43..da7c163f 100644 --- a/pjsip/build/pjsua_lib.dsp +++ b/pjsip/build/pjsua_lib.dsp @@ -41,7 +41,7 @@ RSC=rc.exe # PROP Intermediate_Dir ".\output\pjsua-lib-i386-win32-vc6-release"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
-# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /c
+# ADD CPP /nologo /MD /W4 /GX /Zi /O2 /I "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /I "../../pjnath/include" /D "NDEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
@@ -65,7 +65,7 @@ LIB32=link.exe -lib # PROP Intermediate_Dir ".\output\pjsua-lib-i386-win32-vc6-debug"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjmedia/include" /I "../../pjlib-util/include" /I "../../pjlib/include" /I "../../pjnath/include" /D "_DEBUG" /D PJ_WIN32=1 /D PJ_M_I386=1 /D "WIN32" /D "_MBCS" /D "_LIB" /FR /FD /GZ /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
diff --git a/pjsip/include/pjsip/sip_endpoint.h b/pjsip/include/pjsip/sip_endpoint.h index 08c67931..f285ef8f 100644 --- a/pjsip/include/pjsip/sip_endpoint.h +++ b/pjsip/include/pjsip/sip_endpoint.h @@ -156,6 +156,15 @@ PJ_DECL(pj_status_t) pjsip_endpt_schedule_timer( pjsip_endpoint *endpt, PJ_DECL(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt, pj_timer_entry *entry ); +/** + * Get the timer heap instance of the SIP endpoint. + * + * @param endpt The endpoint. + * + * @return The timer heap instance. + */ +PJ_DECL(pj_timer_heap_t*) pjsip_endpt_get_timer_heap(pjsip_endpoint *endpt); + /** * Register new module to the endpoint. diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 55cf6264..fcd0549a 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -902,6 +902,14 @@ typedef struct pjsua_config */ pj_str_t outbound_proxy[4]; + /** + * Specify STUN server. This server will be first resolved with DNS SRV + * to get the actual server address. If DNS SRV resolution failed, or + * when nameserver is not configured, the server will be resolved using + * DNS A resolution (i.e. gethostbyname()). + */ + pj_str_t stun_srv; + /** * Number of credentials in the credential array. */ @@ -1001,6 +1009,7 @@ PJ_INLINE(void) pjsua_config_dup(pj_pool_t *pool, } pj_strdup_with_null(pool, &dst->user_agent, &src->user_agent); + pj_strdup_with_null(pool, &dst->stun_srv, &src->stun_srv); } @@ -1334,60 +1343,6 @@ typedef int pjsua_transport_id; /** - * This structure describes STUN configuration for SIP and media transport, - * and is embedded inside pjsua_transport_config structure. - */ -typedef struct pjsua_stun_config -{ - /** - * The first STUN server IP address or hostname. - */ - pj_str_t stun_srv1; - - /** - * Port number of the first STUN server. - * If zero, default STUN port will be used. - */ - unsigned stun_port1; - - /** - * Optional second STUN server IP address or hostname, for which the - * result of the mapping request will be compared to. If the value - * is empty, only one STUN server will be used. - */ - pj_str_t stun_srv2; - - /** - * Port number of the second STUN server. - * If zero, default STUN port will be used. - */ - unsigned stun_port2; - -} pjsua_stun_config; - - - -/** - * Call this function to initialize STUN config with default values. - * STUN config is normally embedded inside pjsua_transport_config, so - * normally there is no need to call this function and rather just - * call pjsua_transport_config_default() instead. - * - * @param cfg The STUN config to be initialized. - * - * \par Python: - * The corresponding Python function creates and initialize the config: - * \code - stun_cfg = py_pjsua.stun_config_default() - * \endcode - */ -PJ_INLINE(void) pjsua_stun_config_default(pjsua_stun_config *cfg) -{ - pj_bzero(cfg, sizeof(*cfg)); -} - - -/** * Transport configuration for creating transports for both SIP * and media. Before setting some values to this structure, application * MUST call #pjsua_transport_config_default() to initialize its @@ -1435,16 +1390,6 @@ typedef struct pjsua_transport_config pj_str_t bound_addr; /** - * Flag to indicate whether STUN should be used. - */ - pj_bool_t use_stun; - - /** - * STUN configuration, must be specified when STUN is used. - */ - pjsua_stun_config stun_config; - - /** * This specifies TLS settings for TLS transport. It is only be used * when this transport config is being used to create a SIP TLS * transport. @@ -1468,46 +1413,11 @@ typedef struct pjsua_transport_config PJ_INLINE(void) pjsua_transport_config_default(pjsua_transport_config *cfg) { pj_bzero(cfg, sizeof(*cfg)); - pjsua_stun_config_default(&cfg->stun_config); pjsip_tls_setting_default(&cfg->tls_setting); } /** - * This is a utility function to normalize STUN config. It's only - * used internally by the library. - * - * @param cfg The STUN config to be initialized. - * - * \par Python: - * \code - py_pjsua.normalize_stun_config(cfg) - * \code - */ -PJ_INLINE(void) pjsua_normalize_stun_config( pjsua_stun_config *cfg ) -{ - if (cfg->stun_srv1.slen) { - - if (cfg->stun_port1 == 0) - cfg->stun_port1 = 3478; - - if (cfg->stun_srv2.slen == 0) { - cfg->stun_srv2 = cfg->stun_srv1; - cfg->stun_port2 = cfg->stun_port1; - } else { - if (cfg->stun_port2 == 0) - cfg->stun_port2 = 3478; - } - - } else { - cfg->stun_port1 = 0; - cfg->stun_srv2.slen = 0; - cfg->stun_port2 = 0; - } -} - - -/** * Duplicate transport config. * * @param pool The pool. @@ -1522,19 +1432,8 @@ PJ_INLINE(void) pjsua_transport_config_dup(pj_pool_t *pool, pjsua_transport_config *dst, const pjsua_transport_config *src) { + PJ_UNUSED_ARG(pool); pj_memcpy(dst, src, sizeof(*src)); - - if (src->stun_config.stun_srv1.slen) { - pj_strdup_with_null(pool, &dst->stun_config.stun_srv1, - &src->stun_config.stun_srv1); - } - - if (src->stun_config.stun_srv2.slen) { - pj_strdup_with_null(pool, &dst->stun_config.stun_srv2, - &src->stun_config.stun_srv2); - } - - pjsua_normalize_stun_config(&dst->stun_config); } @@ -3530,6 +3429,15 @@ struct pjsua_media_config */ int jb_max; + /** + * Enable ICE + */ + pj_bool_t enable_ice; + + /** + * Enable ICE media relay. + */ + pj_bool_t enable_relay; }; diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index 4f6a49fd..5365ff12 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -186,6 +186,12 @@ struct pjsua_data pj_bool_t thread_quit_flag; /**< Thread quit flag. */ pj_thread_t *thread[4]; /**< Array of threads. */ + /* STUN and resolver */ + pj_stun_config stun_cfg; /**< Global STUN settings. */ + pj_sockaddr stun_srv; /**< Resolved STUN server address */ + pj_status_t stun_status; /**< STUN server status. */ + pj_dns_resolver *resolver; /**< DNS resolver. */ + /* Account: */ unsigned acc_cnt; /**< Number of accounts. */ pjsua_acc_id default_acc; /**< Default account ID */ @@ -270,12 +276,30 @@ PJ_INLINE(pjsua_im_data*) pjsua_im_data_dup(pj_pool_t *pool, #endif +/** + * Resolve STUN server. + */ +pj_status_t pjsua_resolve_stun_server(pj_bool_t wait); /** * Handle incoming invite request. */ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata); +/* + * Media channel. + */ +pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, + pjsip_role_e role); +pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, + pj_pool_t *pool, + pjmedia_sdp_session **p_sdp); +pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, + pjmedia_sdp_session *local_sdp, + pjmedia_sdp_session *remote_sdp); +pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id); + + /** * Init presence. */ diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index b587ac1a..1a509aab 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -758,6 +758,14 @@ PJ_DEF(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt, } /* + * Get the timer heap instance of the SIP endpoint. + */ +PJ_DEF(pj_timer_heap_t*) pjsip_endpt_get_timer_heap(pjsip_endpoint *endpt) +{ + return endpt->timer_heap; +} + +/* * This is the callback that is called by the transport manager when it * receives a message from the network. */ diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 68eba778..2441f8ed 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -60,8 +60,6 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_event *e); -/* Destroy the call's media */ -static pj_status_t call_destroy_media(int call_id); /* Create inactive SDP for call hold. */ static pj_status_t create_inactive_sdp(pjsua_call *call, @@ -302,10 +300,15 @@ PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id, return status; } - /* Get media capability from media endpoint: */ + /* Init media channel */ + status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + goto on_error; + } - status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, dlg->pool, 1, - &call->skinfo, &offer); + /* Create SDP offer */ + status = pjsua_media_channel_create_sdp(call->index, dlg->pool, &offer); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status); goto on_error; @@ -403,6 +406,7 @@ on_error: if (call_id != -1) { reset_call(call_id); + pjsua_media_channel_deinit(call_id); } PJSUA_UNLOCK(); @@ -526,13 +530,22 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) } + /* Init media channel */ + status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, + NULL, NULL); + PJSUA_UNLOCK(); + return PJ_TRUE; + } + + /* Get media capability from media endpoint: */ - status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, - rdata->tp_info.pool, 1, - &call->skinfo, &answer ); + status = pjsua_media_channel_create_sdp(call->index, rdata->tp_info.pool, &answer); if (status != PJ_SUCCESS) { pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, NULL, NULL); + pjsua_media_channel_deinit(call->index); PJSUA_UNLOCK(); return PJ_TRUE; } @@ -561,6 +574,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) NULL, NULL); } + pjsua_media_channel_deinit(call->index); PJSUA_UNLOCK(); return PJ_TRUE; } @@ -580,6 +594,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsua_perror(THIS_FILE, "Unable to generate Contact header", status); pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, NULL, NULL); + pjsua_media_channel_deinit(call->index); PJSUA_UNLOCK(); return PJ_TRUE; } @@ -590,6 +605,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) if (status != PJ_SUCCESS) { pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL, NULL, NULL); + pjsua_media_channel_deinit(call->index); PJSUA_UNLOCK(); return PJ_TRUE; } @@ -618,6 +634,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) /* Can't terminate dialog because transaction is in progress. pjsip_dlg_terminate(dlg); */ + pjsua_media_channel_deinit(call->index); PJSUA_UNLOCK(); return PJ_TRUE; } @@ -649,13 +666,15 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL); pjsip_inv_terminate(inv, 500, PJ_FALSE); + pjsua_media_channel_deinit(call->index); PJSUA_UNLOCK(); return PJ_TRUE; } else { status = pjsip_inv_send_msg(inv, response); - if (status != PJ_SUCCESS) + if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to send 100 response", status); + } } ++pjsua_var.call_cnt; @@ -1191,8 +1210,7 @@ PJ_DEF(pj_status_t) pjsua_call_reinvite( pjsua_call_id call_id, /* Create SDP */ PJ_UNUSED_ARG(unhold); PJ_TODO(create_active_inactive_sdp_based_on_unhold_arg); - status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, call->inv->pool, - 1, &call->skinfo, &sdp); + status = pjsua_media_channel_create_sdp(call->index, call->inv->pool, &sdp); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", status); @@ -1885,34 +1903,6 @@ PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id, /* - * Destroy the call's media - */ -static pj_status_t call_destroy_media(int call_id) -{ - pjsua_call *call = &pjsua_var.calls[call_id]; - - if (call->conf_slot != PJSUA_INVALID_ID) { - pjmedia_conf_remove_port(pjsua_var.mconf, call->conf_slot); - call->conf_slot = PJSUA_INVALID_ID; - } - - if (call->session) { - /* Destroy session (this will also close RTP/RTCP sockets). */ - pjmedia_session_destroy(call->session); - call->session = NULL; - - PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed", - call_id)); - - } - - call->media_st = PJSUA_CALL_MEDIA_NONE; - - return PJ_SUCCESS; -} - - -/* * This callback receives notification from invite session when the * session state has changed. */ @@ -2034,7 +2024,7 @@ static void pjsua_call_on_state_changed(pjsip_inv_session *inv, pj_assert(call != NULL); if (call) - call_destroy_media(call->index); + pjsua_media_channel_deinit(call->index); /* Free call */ call->inv = NULL; @@ -2077,22 +2067,6 @@ static void call_disconnect( pjsip_inv_session *inv, } /* - * DTMF callback from the stream. - */ -static void dtmf_callback(pjmedia_stream *strm, void *user_data, - int digit) -{ - PJ_UNUSED_ARG(strm); - - if (pjsua_var.ua_cfg.cb.on_dtmf_digit) { - pjsua_call_id call_id; - - call_id = (pjsua_call_id)user_data; - pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit); - } -} - -/* * Callback to be called when SDP offer/answer negotiation has just completed * in the session. This function will start/update media if negotiation * has succeeded. @@ -2100,14 +2074,9 @@ static void dtmf_callback(pjmedia_stream *strm, void *user_data, static void pjsua_call_on_media_update(pjsip_inv_session *inv, pj_status_t status) { - int prev_media_st = 0; pjsua_call *call; - pjmedia_session_info sess_info; - const pjmedia_sdp_session *local_sdp; - const pjmedia_sdp_session *remote_sdp; - pjmedia_port *media_port; - pj_str_t port_name; - char tmp[PJSIP_MAX_URL_SIZE]; + pjmedia_sdp_session *local_sdp; + pjmedia_sdp_session *remote_sdp; PJSUA_LOCK(); @@ -2118,7 +2087,7 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, pjsua_perror(THIS_FILE, "SDP negotiation has failed", status); /* Stop/destroy media, if any */ - call_destroy_media(call->index); + pjsua_media_channel_deinit(call->index); /* Disconnect call if we're not in the middle of initializing an * UAS dialog and if this is not a re-INVITE @@ -2133,15 +2102,8 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, return; } - /* Destroy existing media session, if any. */ - - if (call) { - prev_media_st = call->media_st; - call_destroy_media(call->index); - } /* Get local and remote SDP */ - status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, @@ -2163,155 +2125,16 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, return; } - /* Create media session info based on SDP parameters. - * We only support one stream per session at the moment - */ - status = pjmedia_session_info_from_sdp( call->inv->dlg->pool, - pjsua_var.med_endpt, - 1,&sess_info, - local_sdp, remote_sdp); + status = pjsua_media_channel_update(call->index, local_sdp, remote_sdp); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create media session", status); call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + pjsua_media_channel_deinit(call->index); PJSUA_UNLOCK(); return; } - /* Check if media is put on-hold */ - if (sess_info.stream_cnt == 0 || - sess_info.stream_info[0].dir == PJMEDIA_DIR_NONE) - { - - /* Determine who puts the call on-hold */ - if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) { - if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) { - /* It was local who offer hold */ - call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD; - } else { - call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD; - } - } - - call->media_dir = PJMEDIA_DIR_NONE; - - } else { - - /* Override ptime, if this option is specified. */ - if (pjsua_var.media_cfg.ptime != 0) { - sess_info.stream_info[0].param->setting.frm_per_pkt = (pj_uint8_t) - (pjsua_var.media_cfg.ptime / sess_info.stream_info[0].param->info.frm_ptime); - if (sess_info.stream_info[0].param->setting.frm_per_pkt == 0) - sess_info.stream_info[0].param->setting.frm_per_pkt = 1; - } - - /* Disable VAD, if this option is specified. */ - if (pjsua_var.media_cfg.no_vad) { - sess_info.stream_info[0].param->setting.vad = 0; - } - - - /* Optionally, application may modify other stream settings here - * (such as jitter buffer parameters, codec ptime, etc.) - */ - sess_info.stream_info[0].jb_init = pjsua_var.media_cfg.jb_init; - sess_info.stream_info[0].jb_min_pre = pjsua_var.media_cfg.jb_min_pre; - sess_info.stream_info[0].jb_max_pre = pjsua_var.media_cfg.jb_max_pre; - sess_info.stream_info[0].jb_max = pjsua_var.media_cfg.jb_max; - - /* Create session based on session info. */ - status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info, - &call->med_tp, - call, &call->session ); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create media session", - status); - //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); - PJSUA_UNLOCK(); - return; - } - - /* If DTMF callback is installed by application, install our - * callback to the session. - */ - if (pjsua_var.ua_cfg.cb.on_dtmf_digit) { - pjmedia_session_set_dtmf_callback(call->session, 0, - &dtmf_callback, - (void*)(call->index)); - } - - /* Get the port interface of the first stream in the session. - * We need the port interface to add to the conference bridge. - */ - pjmedia_session_get_port(call->session, 0, &media_port); - - - /* - * Add the call to conference bridge. - */ - port_name.ptr = tmp; - port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, - call->inv->dlg->remote.info->uri, - tmp, sizeof(tmp)); - if (port_name.slen < 1) { - port_name = pj_str("call"); - } - status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool, - media_port, - &port_name, - (unsigned*)&call->conf_slot); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create conference slot", - status); - call_destroy_media(call->index); - //call_disconnect(inv, PJSIP_SC_INTERNAL_SERVER_ERROR); - PJSUA_UNLOCK(); - return; - } - - /* Call's media state is active */ - call->media_st = PJSUA_CALL_MEDIA_ACTIVE; - call->media_dir = sess_info.stream_info[0].dir; - } - - /* 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)); - } /* Call application callback, if any */ if (pjsua_var.ua_cfg.cb.on_call_media_state) @@ -2413,9 +2236,7 @@ static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, } else { PJ_LOG(4,(THIS_FILE, "Call %d: received updated media offer", call->index)); - status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, - call->inv->pool, 1, - &call->skinfo, &answer); + status = pjsua_media_channel_create_sdp(call->index, call->inv->pool, &answer); } if (status != PJ_SUCCESS) { diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 46b8e0e5..116a51f5 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -42,11 +42,15 @@ static void init_data() { unsigned i; + pj_bzero(&pjsua_var, sizeof(pjsua_var)); + for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) pjsua_var.acc[i].index = i; for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata); ++i) pjsua_var.tpdata[i].index = i; + + pjsua_var.stun_status = PJ_EUNKNOWN; } @@ -466,15 +470,16 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, */ if (ua_cfg->nameserver_count) { #if PJSIP_HAS_RESOLVER - pj_dns_resolver *resv; unsigned i; /* Create DNS resolver */ - status = pjsip_endpt_create_resolver(pjsua_var.endpt, &resv); + status = pjsip_endpt_create_resolver(pjsua_var.endpt, + &pjsua_var.resolver); PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); /* Configure nameserver for the DNS resolver */ - status = pj_dns_resolver_set_ns(resv, ua_cfg->nameserver_count, + status = pj_dns_resolver_set_ns(pjsua_var.resolver, + ua_cfg->nameserver_count, ua_cfg->nameserver, NULL); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error setting nameserver", status); @@ -482,7 +487,7 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, } /* Set this DNS resolver to be used by the SIP resolver */ - status = pjsip_endpt_set_resolver(pjsua_var.endpt, resv); + status = pjsip_endpt_set_resolver(pjsua_var.endpt, pjsua_var.resolver); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error setting DNS resolver", status); return status; @@ -550,6 +555,13 @@ PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg, goto on_error; + /* Start resolving STUN server */ + status = pjsua_resolve_stun_server(PJ_FALSE); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + pjsua_perror(THIS_FILE, "Error resolving STUN server", status); + return status; + } + /* Initialize PJSUA media subsystem */ status = pjsua_media_subsys_init(media_cfg); if (status != PJ_SUCCESS) @@ -638,6 +650,49 @@ static void busy_sleep(unsigned msec) } /* + * Resolve STUN server. + */ +pj_status_t pjsua_resolve_stun_server(pj_bool_t wait) +{ + if (pjsua_var.stun_status == PJ_EUNKNOWN) { + /* Initialize STUN configuration */ + pj_stun_config_init(&pjsua_var.stun_cfg, &pjsua_var.cp.factory, 0, + pjsip_endpt_get_ioqueue(pjsua_var.endpt), + pjsip_endpt_get_timer_heap(pjsua_var.endpt)); + + /* Start STUN server resolution */ + /* For now just do DNS A resolution */ + + if (pjsua_var.ua_cfg.stun_srv.slen == 0) { + pjsua_var.stun_status = PJ_SUCCESS; + } else { + pj_hostent he; + + pjsua_var.stun_status = + pj_gethostbyname(&pjsua_var.ua_cfg.stun_srv, &he); + + if (pjsua_var.stun_status == PJ_SUCCESS) { + pj_sockaddr_in_init(&pjsua_var.stun_srv.ipv4, NULL, 0); + pjsua_var.stun_srv.ipv4.sin_addr = *(pj_in_addr*)he.h_addr; + pjsua_var.stun_srv.ipv4.sin_port = pj_htons((pj_uint16_t)3478); + } + } + return pjsua_var.stun_status; + + } else if (pjsua_var.stun_status == PJ_EPENDING) { + /* STUN server resolution has been started, wait for the + * result. + */ + pj_assert(!"Should not happen"); + return PJ_EBUG; + + } else { + /* STUN server has been resolved, return the status */ + return pjsua_var.stun_status; + } +} + +/* * Destroy pjsua. */ PJ_DEF(pj_status_t) pjsua_destroy(void) @@ -819,15 +874,21 @@ PJ_DEF(pj_pool_factory*) pjsua_get_pool_factory(void) */ static pj_status_t create_sip_udp_sock(pj_in_addr bound_addr, int port, - pj_bool_t use_stun, - const pjsua_stun_config *stun_param, pj_sock_t *p_sock, pj_sockaddr_in *p_pub_addr) { - pjsua_stun_config stun; + char ip_addr[32]; + pj_str_t stun_srv; pj_sock_t sock; pj_status_t status; + /* Make sure STUN server resolution has completed */ + status = pjsua_resolve_stun_server(PJ_TRUE); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error resolving STUN server", status); + return status; + } + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "socket() error", status); @@ -856,27 +917,24 @@ static pj_status_t create_sip_udp_sock(pj_in_addr bound_addr, port = pj_ntohs(bound_addr.sin_port); } - /* Copy and normalize STUN param */ - if (use_stun) { - pj_memcpy(&stun, stun_param, sizeof(*stun_param)); - pjsua_normalize_stun_config(&stun); + if (pjsua_var.stun_srv.addr.sa_family != 0) { + pj_ansi_strcpy(ip_addr,pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr)); + stun_srv = pj_str(ip_addr); } else { - pj_bzero(&stun, sizeof(pjsua_stun_config)); + stun_srv.slen = 0; } /* Get the published address, either by STUN or by resolving * the name of local host. */ - if (stun.stun_srv1.slen) { + if (stun_srv.slen) { /* * STUN is specified, resolve the address with STUN. */ status = pjstun_get_mapped_addr(&pjsua_var.cp.factory, 1, &sock, - &stun.stun_srv1, - stun.stun_port1, - &stun.stun_srv2, - stun.stun_port2, - p_pub_addr); + &stun_srv, 3478, + &stun_srv, 3478, + p_pub_addr); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error resolving with STUN", status); pj_sock_close(sock); @@ -986,7 +1044,6 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, * (only when public address is not specified). */ status = create_sip_udp_sock(bound_addr.sin_addr, cfg->port, - cfg->use_stun, &cfg->stun_config, &sock, &pub_addr); if (status != PJ_SUCCESS) goto on_return; diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index 668d3b92..6c81583d 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -215,6 +215,14 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, pj_status_t status = PJ_SUCCESS; pj_sock_t sock[2]; + /* Make sure STUN server resolution has completed */ + status = pjsua_resolve_stun_server(PJ_TRUE); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error resolving STUN server", status); + return status; + } + + if (next_rtp_port == 0) next_rtp_port = (pj_uint16_t)cfg->port; @@ -272,12 +280,17 @@ static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg, * If we're configured to use STUN, then find out the mapped address, * and make sure that the mapped RTCP port is adjacent with the RTP. */ - if (cfg->stun_config.stun_srv1.slen) { + if (pjsua_var.stun_srv.addr.sa_family != 0) { + char ip_addr[32]; + pj_str_t stun_srv; + + pj_ansi_strcpy(ip_addr, + pj_inet_ntoa(pjsua_var.stun_srv.ipv4.sin_addr)); + stun_srv = pj_str(ip_addr); + status=pjstun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock, - &cfg->stun_config.stun_srv1, - cfg->stun_config.stun_port1, - &cfg->stun_config.stun_srv2, - cfg->stun_config.stun_port2, + &stun_srv, 3478, + &stun_srv, 3478, mapped_addr); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "STUN resolve error", status); @@ -488,39 +501,16 @@ pj_status_t pjsua_media_subsys_destroy(void) } -/* - * Create UDP media transports for all the calls. This function creates - * one UDP media transport for each call. - */ -PJ_DEF(pj_status_t) -pjsua_media_transports_create(const pjsua_transport_config *app_cfg) +/* Create normal UDP media transports */ +static pj_status_t create_udp_media_transports(pjsua_transport_config *cfg) { - pjsua_transport_config cfg; unsigned i; pj_status_t status; - - /* Make sure pjsua_init() has been called */ - PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP); - - PJSUA_LOCK(); - - /* Delete existing media transports */ - for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { - if (pjsua_var.calls[i].med_tp != NULL) { - pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp); - pjsua_var.calls[i].med_tp = NULL; - } - } - - /* Copy config */ - pj_memcpy(&cfg, app_cfg, sizeof(*app_cfg)); - pjsua_normalize_stun_config(&cfg.stun_config); - /* Create each media transport */ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { - status = create_rtp_rtcp_sock(&cfg, &pjsua_var.calls[i].skinfo); + status = create_rtp_rtcp_sock(cfg, &pjsua_var.calls[i].skinfo); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket", status); @@ -545,24 +535,428 @@ pjsua_media_transports_create(const pjsua_transport_config *app_cfg) } - PJSUA_UNLOCK(); + return PJ_SUCCESS; + +on_error: + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + if (pjsua_var.calls[i].med_tp != NULL) { + pjmedia_transport_close(pjsua_var.calls[i].med_tp); + pjsua_var.calls[i].med_tp = NULL; + } + } + + return status; +} + + +/* Create ICE media transports (when ice is enabled) */ +static pj_status_t create_ice_media_transports(pjsua_transport_config *cfg) +{ + unsigned i; + pj_status_t status; + + /* Create each media transport */ + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + pj_ice_st *ice_st; + + status = pjmedia_ice_create(pjsua_var.med_endpt, NULL, 1, + &pjsua_var.stun_cfg, + &pjsua_var.calls[i].med_tp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create ICE media transport", + status); + goto on_error; + } + + ice_st = pjmedia_ice_get_ice_st(pjsua_var.calls[i].med_tp); + + /* Add host candidates for RTP */ + status = pj_ice_st_add_all_host_interfaces(ice_st, 1, 0, + PJ_FALSE, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error adding ICE host candidates", + status); + goto on_error; + } + + /* Configure STUN server */ + if (pjsua_var.stun_srv.addr.sa_family != 0) { + + status = pj_ice_st_set_stun_addr(ice_st, + pjsua_var.media_cfg.enable_relay, + &pjsua_var.stun_srv.ipv4); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error setting ICE's STUN server", + status); + goto on_error; + } + + /* Add STUN server reflexive candidate for RTP */ + status = pj_ice_st_add_stun_interface(ice_st, 1, 0, + PJ_FALSE, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error adding ICE address", + status); + goto on_error; + } + } + } + + /* Wait until all ICE transports are ready */ + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + pj_ice_st *ice_st; + + ice_st = pjmedia_ice_get_ice_st(pjsua_var.calls[i].med_tp); + + /* Wait until interface status is PJ_SUCCESS */ + for (;;) { + status = pj_ice_st_get_interfaces_status(ice_st); + if (status == PJ_EPENDING) + pjsua_handle_events(100); + else + break; + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Error adding STUN address to ICE media transport", + status); + goto on_error; + } + + /* Get transport info */ + pjmedia_transport_get_info(pjsua_var.calls[i].med_tp, + &pjsua_var.calls[i].skinfo); + + } return PJ_SUCCESS; on_error: for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { if (pjsua_var.calls[i].med_tp != NULL) { - pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp); + pjmedia_transport_close(pjsua_var.calls[i].med_tp); pjsua_var.calls[i].med_tp = NULL; } } + return status; +} + + +/* + * Create UDP media transports for all the calls. This function creates + * one UDP media transport for each call. + */ +PJ_DEF(pj_status_t) +pjsua_media_transports_create(const pjsua_transport_config *app_cfg) +{ + pjsua_transport_config cfg; + unsigned i; + pj_status_t status; + + + /* Make sure pjsua_init() has been called */ + PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP); + + PJSUA_LOCK(); + + /* Delete existing media transports */ + for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) { + if (pjsua_var.calls[i].med_tp != NULL) { + pjmedia_transport_close(pjsua_var.calls[i].med_tp); + pjsua_var.calls[i].med_tp = NULL; + } + } + + /* Copy config */ + pjsua_transport_config_dup(pjsua_var.pool, &cfg, app_cfg); + + if (pjsua_var.media_cfg.enable_ice) { + status = create_ice_media_transports(&cfg); + } else { + status = create_udp_media_transports(&cfg); + } + + PJSUA_UNLOCK(); return status; } +pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, + pjsip_role_e role) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + + if (pjsua_var.media_cfg.enable_ice) { + pj_ice_role ice_role; + pj_status_t status; + + ice_role = (role==PJSIP_ROLE_UAC ? PJ_ICE_ROLE_CONTROLLING : + PJ_ICE_ROLE_CONTROLLED); + + /* Restart ICE */ + pjmedia_ice_stop_ice(call->med_tp); + + status = pjmedia_ice_init_ice(call->med_tp, ice_role, NULL, NULL); + if (status != PJ_SUCCESS) + return status; + } + + return PJ_SUCCESS; +} + +pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, + pj_pool_t *pool, + pjmedia_sdp_session **p_sdp) +{ + pjmedia_sdp_session *sdp; + pjsua_call *call = &pjsua_var.calls[call_id]; + pj_status_t status; + + status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pool, 1, + &call->skinfo, &sdp); + if (status != PJ_SUCCESS) + goto on_error; + + if (pjsua_var.media_cfg.enable_ice) { + status = pjmedia_ice_modify_sdp(call->med_tp, pool, sdp); + if (status != PJ_SUCCESS) + goto on_error; + } + + *p_sdp = sdp; + return PJ_SUCCESS; + +on_error: + pjsua_media_channel_deinit(call_id); + return status; + +} + + +static void stop_media_session(pjsua_call_id call_id) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + + if (call->conf_slot != PJSUA_INVALID_ID) { + pjmedia_conf_remove_port(pjsua_var.mconf, call->conf_slot); + call->conf_slot = PJSUA_INVALID_ID; + } + + if (call->session) { + pjmedia_session_destroy(call->session); + call->session = NULL; + + PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed", + call_id)); + + } + + call->media_st = PJSUA_CALL_MEDIA_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 (pjsua_var.media_cfg.enable_ice) { + pjmedia_ice_stop_ice(call->med_tp); + } + + return PJ_SUCCESS; +} + + +/* + * DTMF callback from the stream. + */ +static void dtmf_callback(pjmedia_stream *strm, void *user_data, + int digit) +{ + PJ_UNUSED_ARG(strm); + + if (pjsua_var.ua_cfg.cb.on_dtmf_digit) { + pjsua_call_id call_id; + + call_id = (pjsua_call_id)user_data; + pjsua_var.ua_cfg.cb.on_dtmf_digit(call_id, digit); + } +} + + +pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, + pjmedia_sdp_session *local_sdp, + pjmedia_sdp_session *remote_sdp) +{ + int prev_media_st = 0; + pjsua_call *call = &pjsua_var.calls[call_id]; + pjmedia_session_info sess_info; + pjmedia_port *media_port; + pj_str_t port_name; + char tmp[PJSIP_MAX_URL_SIZE]; + pj_status_t status; + + /* Destroy existing media session, if any. */ + prev_media_st = call->media_st; + stop_media_session(call->index); + + /* Create media session info based on SDP parameters. + * We only support one stream per session at the moment + */ + status = pjmedia_session_info_from_sdp( call->inv->dlg->pool, + pjsua_var.med_endpt, + 1,&sess_info, + local_sdp, remote_sdp); + if (status != PJ_SUCCESS) + return status; + + + /* Check if media is put on-hold */ + if (sess_info.stream_cnt == 0 || + sess_info.stream_info[0].dir == PJMEDIA_DIR_NONE) + { + + /* Determine who puts the call on-hold */ + if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) { + if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) { + /* It was local who offer hold */ + call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD; + } else { + call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD; + } + } + + call->media_dir = PJMEDIA_DIR_NONE; + + /* Shutdown transport */ + /* No need because we need keepalive? */ + + } else { + + /* Start ICE */ + if (pjsua_var.media_cfg.enable_ice) { + status = pjmedia_ice_start_ice(call->med_tp, call->inv->pool, + remote_sdp); + if (status != PJ_SUCCESS) + return status; + } + + /* Override ptime, if this option is specified. */ + if (pjsua_var.media_cfg.ptime != 0) { + sess_info.stream_info[0].param->setting.frm_per_pkt = (pj_uint8_t) + (pjsua_var.media_cfg.ptime / sess_info.stream_info[0].param->info.frm_ptime); + if (sess_info.stream_info[0].param->setting.frm_per_pkt == 0) + sess_info.stream_info[0].param->setting.frm_per_pkt = 1; + } + + /* Disable VAD, if this option is specified. */ + if (pjsua_var.media_cfg.no_vad) { + sess_info.stream_info[0].param->setting.vad = 0; + } + + + /* Optionally, application may modify other stream settings here + * (such as jitter buffer parameters, codec ptime, etc.) + */ + sess_info.stream_info[0].jb_init = pjsua_var.media_cfg.jb_init; + sess_info.stream_info[0].jb_min_pre = pjsua_var.media_cfg.jb_min_pre; + sess_info.stream_info[0].jb_max_pre = pjsua_var.media_cfg.jb_max_pre; + sess_info.stream_info[0].jb_max = pjsua_var.media_cfg.jb_max; + + /* Create session based on session info. */ + status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info, + &call->med_tp, + call, &call->session ); + if (status != PJ_SUCCESS) { + return status; + } + + /* If DTMF callback is installed by application, install our + * callback to the session. + */ + if (pjsua_var.ua_cfg.cb.on_dtmf_digit) { + pjmedia_session_set_dtmf_callback(call->session, 0, + &dtmf_callback, + (void*)(call->index)); + } + + /* Get the port interface of the first stream in the session. + * We need the port interface to add to the conference bridge. + */ + pjmedia_session_get_port(call->session, 0, &media_port); + + + /* + * Add the call to conference bridge. + */ + port_name.ptr = tmp; + port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + call->inv->dlg->remote.info->uri, + tmp, sizeof(tmp)); + if (port_name.slen < 1) { + port_name = pj_str("call"); + } + status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool, + media_port, + &port_name, + (unsigned*)&call->conf_slot); + if (status != PJ_SUCCESS) { + return status; + } + + /* Call's media state is active */ + call->media_st = PJSUA_CALL_MEDIA_ACTIVE; + call->media_dir = sess_info.stream_info[0].dir; + } + + /* 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; +} + + /* * Get maxinum number of conference ports. */ |