summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsua-lib/pjsua_media.c
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2007-03-23 16:34:20 +0000
committerBenny Prijono <bennylp@teluu.com>2007-03-23 16:34:20 +0000
commite3fd604ea862f68ad3ece248ca7d853899cbf48f (patch)
tree88fb4659ab449d79b25dc8e0dfe3b64f145b9dd3 /pjsip/src/pjsua-lib/pjsua_media.c
parent05e7998ba4cbd7fb0b02b7f82c5b328cf203fbc9 (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/src/pjsua-lib/pjsua_media.c')
-rw-r--r--pjsip/src/pjsua-lib/pjsua_media.c460
1 files changed, 427 insertions, 33 deletions
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.
*/