diff options
author | Liong Sauw Ming <ming@teluu.com> | 2011-09-21 10:20:01 +0000 |
---|---|---|
committer | Liong Sauw Ming <ming@teluu.com> | 2011-09-21 10:20:01 +0000 |
commit | 8a8b2bba54aedc161159225259b83e6d7c1d17b3 (patch) | |
tree | 45a096a1b1cb6f3d5c5d3e016ce6147969a0d9b0 /pjsip/src | |
parent | be8d37186b16150716f752883ae6857a0161db40 (diff) |
Re #1266: Asynchronous media transport creation
* Add feature that allows ICE media transport to be created asynchronously.
* Add new callback, e.g. on_call_media_transport_state(call_id, state_struct) to report media transport status.
* Handle outgoing calls while creating media transport asynchronously.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@3763 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src')
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 279 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 36 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_media.c | 399 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_vid.c | 10 |
4 files changed, 517 insertions, 207 deletions
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 3deeacca..d3c655f1 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -331,6 +331,152 @@ static int call_get_secure_level(pjsua_call *call) } */ +static pj_status_t +on_make_call_med_tp_complete(pjsua_call_id call_id, + const pjsua_med_tp_state_info *info) +{ + pjmedia_sdp_session *offer; + pjsip_inv_session *inv = NULL; + pjsua_call *call = &pjsua_var.calls[call_id]; + pjsua_acc *acc = &pjsua_var.acc[call->acc_id]; + pjsip_dialog *dlg = call->async_call.dlg; + unsigned options = call->async_call.call_var.out_call.options; + pjsip_tx_data *tdata; + pj_status_t status = (info? info->status: PJ_SUCCESS); + + PJSUA_LOCK(); + + /* Increment the dialog's lock otherwise when invite session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(dlg); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + goto on_error; + } + + /* Create offer */ + status = pjsua_media_channel_create_sdp(call->index, dlg->pool, NULL, + &offer, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing media channel", status); + goto on_error; + } + + /* Create the INVITE session: */ + options |= PJSIP_INV_SUPPORT_100REL; + if (acc->cfg.require_100rel) + options |= PJSIP_INV_REQUIRE_100REL; + if (acc->cfg.use_timer != PJSUA_SIP_TIMER_INACTIVE) { + options |= PJSIP_INV_SUPPORT_TIMER; + if (acc->cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED) + options |= PJSIP_INV_REQUIRE_TIMER; + else if (acc->cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS) + options |= PJSIP_INV_ALWAYS_USE_TIMER; + } + + status = pjsip_inv_create_uac( dlg, offer, options, &inv); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Invite session creation failed", status); + goto on_error; + } + + /* Init Session Timers */ + status = pjsip_timer_init_session(inv, &acc->cfg.timer_setting); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Session Timer init failed", status); + goto on_error; + } + + /* Create and associate our data in the session. */ + call->inv = inv; + + dlg->mod_data[pjsua_var.mod.id] = call; + inv->mod_data[pjsua_var.mod.id] = call; + + /* If account is locked to specific transport, then lock dialog + * to this transport too. + */ + if (acc->cfg.transport_id != PJSUA_INVALID_ID) { + pjsip_tpselector tp_sel; + + pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); + pjsip_dlg_set_transport(dlg, &tp_sel); + } + + /* Set dialog Route-Set: */ + if (!pj_list_empty(&acc->route_set)) + pjsip_dlg_set_route_set(dlg, &acc->route_set); + + + /* Set credentials: */ + if (acc->cred_cnt) { + pjsip_auth_clt_set_credentials( &dlg->auth_sess, + acc->cred_cnt, acc->cred); + } + + /* Set authentication preference */ + pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref); + + /* Create initial INVITE: */ + + status = pjsip_inv_invite(inv, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create initial INVITE request", + status); + goto on_error; + } + + + /* Add additional headers etc */ + + pjsua_process_msg_data( tdata, + call->async_call.call_var.out_call.msg_data); + + /* Must increment call counter now */ + ++pjsua_var.call_cnt; + + /* Send initial INVITE: */ + + status = pjsip_inv_send_msg(inv, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send initial INVITE request", + status); + + /* Upon failure to send first request, the invite + * session would have been cleared. + */ + inv = NULL; + goto on_error; + } + + /* Done. */ + + pjsip_dlg_dec_lock(dlg); + PJSUA_UNLOCK(); + + return PJ_SUCCESS; + +on_error: + if (dlg) { + /* This may destroy the dialog */ + pjsip_dlg_dec_lock(dlg); + } + + if (inv != NULL) { + pjsip_inv_terminate(inv, PJSIP_SC_OK, PJ_FALSE); + } + + if (call_id != -1) { + reset_call(call_id); + pjsua_media_channel_deinit(call_id); + } + + PJSUA_UNLOCK(); + return status; +} + /* * Make outgoing call to the specified URI using the specified account. @@ -344,13 +490,10 @@ PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id, { pj_pool_t *tmp_pool = NULL; pjsip_dialog *dlg = NULL; - pjmedia_sdp_session *offer; - pjsip_inv_session *inv = NULL; pjsua_acc *acc; pjsua_call *call; int call_id = -1; pj_str_t contact; - pjsip_tx_data *tdata; pj_status_t status; @@ -376,8 +519,6 @@ PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id, if (!pjsua_var.is_mswitch && pjsua_var.snd_port==NULL && pjsua_var.null_snd==NULL && !pjsua_var.no_snd) { - pj_status_t status; - status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev); if (status != PJ_SUCCESS) goto on_error; @@ -462,123 +603,39 @@ PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id, /* Increment the dialog's lock otherwise when invite session creation * fails the dialog will be destroyed prematurely. */ - pjsip_dlg_inc_lock(dlg); +// pjsip_dlg_inc_lock(dlg); /* Calculate call's secure level */ call->secure_level = get_secure_level(acc_id, dest_uri); + /* Attach user data */ + call->user_data = user_data; + + call->async_call.call_var.out_call.options = options; + call->async_call.call_var.out_call.msg_data = pjsua_msg_data_clone( + dlg->pool, msg_data); + call->async_call.dlg = dlg; + /* Init media channel */ status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC, call->secure_level, dlg->pool, - NULL, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing media channel", status); - goto on_error; - } - - /* Create offer */ - status = pjsua_media_channel_create_sdp(call->index, dlg->pool, NULL, - &offer, NULL); - if (status != PJ_SUCCESS) { + NULL, NULL, PJ_TRUE, + (pjsua_med_tp_state_cb) + &on_make_call_med_tp_complete); + if (status == PJ_SUCCESS) { + status = on_make_call_med_tp_complete(call->index, NULL); + if (status != PJ_SUCCESS) + goto on_error; + } else if (status != PJ_EPENDING) { pjsua_perror(THIS_FILE, "Error initializing media channel", status); goto on_error; } - /* Create the INVITE session: */ - options |= PJSIP_INV_SUPPORT_100REL; - if (acc->cfg.require_100rel) - options |= PJSIP_INV_REQUIRE_100REL; - if (acc->cfg.use_timer != PJSUA_SIP_TIMER_INACTIVE) { - options |= PJSIP_INV_SUPPORT_TIMER; - if (acc->cfg.use_timer == PJSUA_SIP_TIMER_REQUIRED) - options |= PJSIP_INV_REQUIRE_TIMER; - else if (acc->cfg.use_timer == PJSUA_SIP_TIMER_ALWAYS) - options |= PJSIP_INV_ALWAYS_USE_TIMER; - } - - status = pjsip_inv_create_uac( dlg, offer, options, &inv); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Invite session creation failed", status); - goto on_error; - } - - /* Init Session Timers */ - status = pjsip_timer_init_session(inv, &acc->cfg.timer_setting); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Session Timer init failed", status); - goto on_error; - } - - /* Create and associate our data in the session. */ - call->inv = inv; - - dlg->mod_data[pjsua_var.mod.id] = call; - inv->mod_data[pjsua_var.mod.id] = call; - - /* Attach user data */ - call->user_data = user_data; - - /* If account is locked to specific transport, then lock dialog - * to this transport too. - */ - if (acc->cfg.transport_id != PJSUA_INVALID_ID) { - pjsip_tpselector tp_sel; - - pjsua_init_tpselector(acc->cfg.transport_id, &tp_sel); - pjsip_dlg_set_transport(dlg, &tp_sel); - } - - /* Set dialog Route-Set: */ - if (!pj_list_empty(&acc->route_set)) - pjsip_dlg_set_route_set(dlg, &acc->route_set); - - - /* Set credentials: */ - if (acc->cred_cnt) { - pjsip_auth_clt_set_credentials( &dlg->auth_sess, - acc->cred_cnt, acc->cred); - } - - /* Set authentication preference */ - pjsip_auth_clt_set_prefs(&dlg->auth_sess, &acc->cfg.auth_pref); - - /* Create initial INVITE: */ - - status = pjsip_inv_invite(inv, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create initial INVITE request", - status); - goto on_error; - } - - - /* Add additional headers etc */ - - pjsua_process_msg_data( tdata, msg_data); - - /* Must increment call counter now */ - ++pjsua_var.call_cnt; - - /* Send initial INVITE: */ - - status = pjsip_inv_send_msg(inv, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send initial INVITE request", - status); - - /* Upon failure to send first request, the invite - * session would have been cleared. - */ - inv = NULL; - goto on_error; - } - /* Done. */ if (p_call_id) *p_call_id = call_id; - pjsip_dlg_dec_lock(dlg); pj_pool_release(tmp_pool); PJSUA_UNLOCK(); @@ -589,14 +646,11 @@ PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id, on_error: if (dlg) { + pjsip_dlg_inc_lock(dlg); /* This may destroy the dialog */ pjsip_dlg_dec_lock(dlg); } - if (inv != NULL) { - pjsip_inv_terminate(inv, PJSIP_SC_OK, PJ_FALSE); - } - if (call_id != -1) { reset_call(call_id); pjsua_media_channel_deinit(call_id); @@ -815,7 +869,8 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) call->secure_level, rdata->tp_info.pool, offer, - &sip_err_code); + &sip_err_code, PJ_FALSE, + NULL); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error initializing media channel", status); pjsip_endpt_respond(pjsua_var.endpt, NULL, rdata, @@ -833,7 +888,6 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) goto on_return; } - /* Verify that we can handle the request. */ options |= PJSIP_INV_SUPPORT_100REL; options |= PJSIP_INV_SUPPORT_TIMER; @@ -1015,7 +1069,6 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) ++pjsua_var.call_cnt; - /* Check if this request should replace existing call */ if (replaced_dlg) { pjsip_inv_session *replaced_inv; diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 476e4cd7..31133b2c 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -150,6 +150,42 @@ PJ_DEF(void) pjsua_msg_data_init(pjsua_msg_data *msg_data) pj_list_init(&msg_data->multipart_parts); } +PJ_DEF(pjsua_msg_data*) pjsua_msg_data_clone(pj_pool_t *pool, + const pjsua_msg_data *rhs) +{ + pjsua_msg_data *msg_data; + const pjsip_hdr *hdr; + const pjsip_multipart_part *mpart; + + PJ_ASSERT_RETURN(pool && rhs, NULL); + + msg_data = PJ_POOL_ZALLOC_T(pool, pjsua_msg_data); + PJ_ASSERT_RETURN(msg_data != NULL, NULL); + + pj_list_init(&msg_data->hdr_list); + hdr = rhs->hdr_list.next; + while (hdr != &rhs->hdr_list) { + pj_list_push_back(&msg_data->hdr_list, pjsip_hdr_clone(pool, hdr)); + hdr = hdr->next; + } + + pj_strdup(pool, &msg_data->content_type, &rhs->content_type); + pj_strdup(pool, &msg_data->msg_body, &rhs->msg_body); + + pjsip_media_type_cp(pool, &msg_data->multipart_ctype, + &rhs->multipart_ctype); + + pj_list_init(&msg_data->multipart_parts); + mpart = rhs->multipart_parts.next; + while (mpart != &rhs->multipart_parts) { + pj_list_push_back(&msg_data->multipart_parts, + pjsip_multipart_clone_part(pool, mpart)); + mpart = mpart->next; + } + + return msg_data; +} + PJ_DEF(void) pjsua_transport_config_default(pjsua_transport_config *cfg) { pj_bzero(cfg, sizeof(*cfg)); diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index ff5a0ef9..932fdc8e 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -710,6 +710,8 @@ static pj_status_t create_udp_media_transport(const pjsua_transport_config *cfg, pjmedia_transport_simulate_lost(call_med->tp, PJMEDIA_DIR_DECODING, pjsua_var.media_cfg.rx_drop_pct); + call_med->tp_ready = PJ_SUCCESS; + return PJ_SUCCESS; on_error: @@ -771,7 +773,10 @@ static void on_ice_complete(pjmedia_transport *tp, switch (op) { case PJ_ICE_STRANS_OP_INIT: - call_med->tp_ready = result; + call_med->tp_ready = result; + if (call_med->med_create_cb) + (*call_med->med_create_cb)(call_med, result, + call_med->call->secure_level, NULL); break; case PJ_ICE_STRANS_OP_NEGOTIATION: if (result != PJ_SUCCESS) { @@ -831,6 +836,17 @@ static void on_ice_complete(pjmedia_transport *tp, "ICE keep alive failure for transport %d:%d", call_med->call->index, call_med->idx)); } + if (pjsua_var.ua_cfg.cb.on_call_media_transport_state) { + pjsua_med_tp_state_info info; + + pj_bzero(&info, sizeof(info)); + info.med_idx = call_med->idx; + info.state = call_med->tp_st; + info.status = result; + info.ext_info = &op; + (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)( + call_med->call->index, &info); + } if (pjsua_var.ua_cfg.cb.on_ice_transport_error) { pjsua_call_id id = call_med->call->index; (*pjsua_var.ua_cfg.cb.on_ice_transport_error)(id, op, result, @@ -870,7 +886,8 @@ static pj_status_t parse_host_port(const pj_str_t *host_port, /* Create ICE media transports (when ice is enabled) */ static pj_status_t create_ice_media_transport( const pjsua_transport_config *cfg, - pjsua_call_media *call_med) + pjsua_call_media *call_med, + pj_bool_t async) { char stunip[PJ_INET6_ADDRSTRLEN]; pj_ice_strans_cfg ice_cfg; @@ -952,12 +969,17 @@ static pj_status_t create_ice_media_transport( } /* Wait until transport is initialized, or time out */ - PJSUA_UNLOCK(); - while (call_med->tp_ready == PJ_EPENDING) { - pjsua_handle_events(100); + if (!async) { + PJSUA_UNLOCK(); + while (call_med->tp_ready == PJ_EPENDING) { + pjsua_handle_events(100); + } + PJSUA_LOCK(); } - PJSUA_LOCK(); - if (call_med->tp_ready != PJ_SUCCESS) { + + if (async && call_med->tp_ready == PJ_EPENDING) { + return PJ_EPENDING; + } else if (call_med->tp_ready != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Error initializing ICE media transport", call_med->tp_ready); status = call_med->tp_ready; @@ -1235,59 +1257,42 @@ static pj_status_t call_media_on_event(pjmedia_event_subscription *esub, return PJ_SUCCESS; } -/* Initialize the media line */ -pj_status_t pjsua_call_media_init(pjsua_call_media *call_med, - pjmedia_type type, - const pjsua_transport_config *tcfg, - int security_level, - int *sip_err_code) +/* Set media transport state and notify the application via the callback. */ +void set_media_tp_state(pjsua_call_media *call_med, + pjsua_med_tp_st tp_st) { - pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; - pj_status_t status; + if (pjsua_var.ua_cfg.cb.on_call_media_transport_state && + call_med->tp_st != tp_st) + { + pjsua_med_tp_state_info info; - /* - * Note: this function may be called when the media already exists - * (e.g. in reinvites, updates, etc.) - */ - call_med->type = type; + pj_bzero(&info, sizeof(info)); + info.med_idx = call_med->idx; + info.state = tp_st; + info.status = call_med->tp_ready; + (*pjsua_var.ua_cfg.cb.on_call_media_transport_state)( + call_med->call->index, &info); + } - /* Create the media transport for initial call. This is blocking for now */ - if (call_med->tp == NULL) { - if (pjsua_var.media_cfg.enable_ice) { - status = create_ice_media_transport(tcfg, call_med); - } else { - status = create_udp_media_transport(tcfg, call_med); - } + call_med->tp_st = tp_st; +} - if (status != PJ_SUCCESS) { - PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport")); - return status; - } - - call_med->tp_st = PJSUA_MED_TP_IDLE; +/* Callback to resume pjsua_call_media_init() after media transport + * creation is completed. + */ +static pj_status_t call_media_init_cb(pjsua_call_media *call_med, + pj_status_t status, + int security_level, + int *sip_err_code) +{ + pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; + int err_code = 0; -#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) - /* While in initial call, set default video devices */ - if (type == PJMEDIA_TYPE_VIDEO) { - call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev; - call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev; - if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) { - pjmedia_vid_dev_info info; - pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info); - call_med->strm.v.rdr_dev = info.id; - } - if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { - pjmedia_vid_dev_info info; - pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info); - call_med->strm.v.cap_dev = info.id; - } - } -#endif + if (status != PJ_SUCCESS) + goto on_error; - } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) { - /* Media is being reenabled. */ - call_med->tp_st = PJSUA_MED_TP_INIT; - } + if (call_med->tp_st == PJSUA_MED_TP_CREATING) + set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) /* This function may be called when SRTP transport already exists @@ -1300,8 +1305,7 @@ pj_status_t pjsua_call_media_init(pjsua_call_media *call_med, /* Check if SRTP requires secure signaling */ if (acc->cfg.use_srtp != PJMEDIA_SRTP_DISABLED) { if (security_level < acc->cfg.srtp_secure_signaling) { - if (sip_err_code) - *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; + err_code = PJSIP_SC_NOT_ACCEPTABLE; status = PJSIP_ESESSIONINSECURE; goto on_error; } @@ -1322,8 +1326,7 @@ pj_status_t pjsua_call_media_init(pjsua_call_media *call_med, call_med->tp, &srtp_opt, &srtp); if (status != PJ_SUCCESS) { - if (sip_err_code) - *sip_err_code = PJSIP_SC_INTERNAL_SERVER_ERROR; + err_code = PJSIP_SC_INTERNAL_SERVER_ERROR; goto on_error; } @@ -1339,15 +1342,190 @@ pj_status_t pjsua_call_media_init(pjsua_call_media *call_med, pjmedia_event_subscription_init(&call_med->esub_rend, &call_media_on_event, call_med); pjmedia_event_subscription_init(&call_med->esub_cap, &call_media_on_event, - call_med); - - return PJ_SUCCESS; + call_med); on_error: - if (call_med->tp) { + if (status != PJ_SUCCESS && call_med->tp) { pjmedia_transport_close(call_med->tp); call_med->tp = NULL; } + + if (sip_err_code) + *sip_err_code = err_code; + + if (call_med->med_init_cb) { + pjsua_med_tp_state_info info; + + pj_bzero(&info, sizeof(info)); + info.status = status; + info.state = call_med->tp_st; + info.med_idx = call_med->idx; + info.sip_err_code = err_code; + (*call_med->med_init_cb)(call_med->call->index, &info); + } + + return status; +} + +/* Initialize the media line */ +pj_status_t pjsua_call_media_init(pjsua_call_media *call_med, + pjmedia_type type, + const pjsua_transport_config *tcfg, + int security_level, + int *sip_err_code, + pj_bool_t async, + pjsua_med_tp_state_cb cb) +{ + pjsua_acc *acc = &pjsua_var.acc[call_med->call->acc_id]; + pj_status_t status = PJ_SUCCESS; + + /* + * Note: this function may be called when the media already exists + * (e.g. in reinvites, updates, etc.) + */ + call_med->type = type; + + /* Create the media transport for initial call. */ + if (call_med->tp == NULL) { +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + /* While in initial call, set default video devices */ + if (type == PJMEDIA_TYPE_VIDEO) { + call_med->strm.v.rdr_dev = acc->cfg.vid_rend_dev; + call_med->strm.v.cap_dev = acc->cfg.vid_cap_dev; + if (call_med->strm.v.rdr_dev == PJMEDIA_VID_DEFAULT_RENDER_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(call_med->strm.v.rdr_dev, &info); + call_med->strm.v.rdr_dev = info.id; + } + if (call_med->strm.v.cap_dev == PJMEDIA_VID_DEFAULT_CAPTURE_DEV) { + pjmedia_vid_dev_info info; + pjmedia_vid_dev_get_info(call_med->strm.v.cap_dev, &info); + call_med->strm.v.cap_dev = info.id; + } + } +#endif + + set_media_tp_state(call_med, PJSUA_MED_TP_CREATING); + + if (async) { + call_med->med_create_cb = &call_media_init_cb; + call_med->med_init_cb = cb; + } + + if (pjsua_var.media_cfg.enable_ice) { + status = create_ice_media_transport(tcfg, call_med, async); + } else { + status = create_udp_media_transport(tcfg, call_med); + } + + if (status == PJ_EPENDING) { + /* We will resume call media initialization in the + * on_ice_complete() callback. + */ + return PJ_EPENDING; + } else if (status != PJ_SUCCESS) { + PJ_PERROR(1,(THIS_FILE, status, "Error creating media transport")); + return status; + } + + /* Media transport creation completed immediately, so + * we don't need to call the callback. + */ + call_med->med_init_cb = NULL; + + } else if (call_med->tp_st == PJSUA_MED_TP_DISABLED) { + /* Media is being reenabled. */ + set_media_tp_state(call_med, PJSUA_MED_TP_INIT); + } + + return call_media_init_cb(call_med, status, security_level, + sip_err_code); +} + +/* Callback to resume pjsua_media_channel_init() after media transport + * initialization is completed. + */ +static pj_status_t media_channel_init_cb(pjsua_call_id call_id, + const pjsua_med_tp_state_info *info) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + pj_status_t status = (info? info->status : PJ_SUCCESS); + unsigned mi; + + if (info) { + pj_mutex_lock(call->med_ch_mutex); + + /* Set the callback to NULL to indicate that the async operation + * has completed. + */ + call->media[info->med_idx].med_init_cb = NULL; + + /* In case of failure, save the information to be returned + * by the last media transport to finish. + */ + if (info->status != PJ_SUCCESS) + pj_memcpy(&call->med_ch_info, info, sizeof(info)); + + /* Check whether all the call's medias have finished calling their + * callbacks. + */ + for (mi=0; mi < call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; + + if (call_med->med_init_cb) { + pj_mutex_unlock(call->med_ch_mutex); + return PJ_SUCCESS; + } + + if (call_med->tp_ready != PJ_SUCCESS) + status = call_med->tp_ready; + } + + /* OK, we are called by the last media transport finished. */ + pj_mutex_unlock(call->med_ch_mutex); + } + + if (call->med_ch_mutex) { + pj_mutex_destroy(call->med_ch_mutex); + call->med_ch_mutex = NULL; + } + + if (status != PJ_SUCCESS) { + pjsua_media_channel_deinit(call_id); + goto on_error; + } + + /* Tell the media transport of a new offer/answer session */ + for (mi=0; mi < call->med_cnt; ++mi) { + pjsua_call_media *call_med = &call->media[mi]; + + /* Note: tp may be NULL if this media line is disabled */ + if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) { + pj_pool_t *tmp_pool = (call->inv? call->inv->pool_prov: + call->async_call.dlg->pool); + + status = pjmedia_transport_media_create( + call_med->tp, tmp_pool, + 0, call->async_call.rem_sdp, mi); + if (status != PJ_SUCCESS) { + call->med_ch_info.status = status; + call->med_ch_info.med_idx = mi; + call->med_ch_info.state = call_med->tp_st; + call->med_ch_info.sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; + pjsua_media_channel_deinit(call_id); + goto on_error; + } + + set_media_tp_state(call_med, PJSUA_MED_TP_INIT); + } + } + + call->med_ch_info.status = PJ_SUCCESS; + +on_error: + if (call->med_ch_cb) + (*call->med_ch_cb)(call->index, &call->med_ch_info); + return status; } @@ -1356,7 +1534,9 @@ pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, int security_level, pj_pool_t *tmp_pool, const pjmedia_sdp_session *rem_sdp, - int *sip_err_code) + int *sip_err_code, + pj_bool_t async, + pjsua_med_tp_state_cb cb) { const pj_str_t STR_AUDIO = { "audio", 5 }; const pj_str_t STR_VIDEO = { "video", 5 }; @@ -1368,9 +1548,11 @@ pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, unsigned mvidcnt = PJ_ARRAY_SIZE(mvididx); pjmedia_type media_types[PJSUA_MAX_CALL_MEDIA]; unsigned mi; + pj_bool_t pending_med_tp = PJ_FALSE; pj_status_t status; PJ_UNUSED_ARG(role); + PJ_UNUSED_ARG(tmp_pool); /* * Note: this function may be called when the media already exists @@ -1380,6 +1562,15 @@ pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, if (pjsua_get_state() != PJSUA_STATE_RUNNING) return PJ_EBUSY; + if (async) { + pj_pool_t *tmppool = (call->inv? call->inv->pool_prov: + call->async_call.dlg->pool); + + status = pj_mutex_create_simple(tmppool, NULL, &call->med_ch_mutex); + if (status != PJ_SUCCESS) + return status; + } + PJ_LOG(4,(THIS_FILE, "Call %d: initializing media..", call_id)); pj_log_push_indent(); @@ -1451,6 +1642,15 @@ pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, goto on_error; } + if (async) { + call->med_ch_cb = cb; + if (rem_sdp) { + /* TODO: change rem_sdp to non-const parameter. */ + call->async_call.rem_sdp = + pjmedia_sdp_session_clone(call->inv->pool_prov, rem_sdp); + } + } + /* Initialize each media line */ for (mi=0; mi < call->med_cnt; ++mi) { pjsua_call_media *call_med = &call->media[mi]; @@ -1482,9 +1682,28 @@ pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, if (enabled) { status = pjsua_call_media_init(call_med, media_type, &acc->cfg.rtp_cfg, - security_level, sip_err_code); - if (status != PJ_SUCCESS) { - pjsua_media_channel_deinit(call_id); + security_level, sip_err_code, + async, + (async? (pjsua_med_tp_state_cb) + &media_channel_init_cb: NULL)); + if (status == PJ_EPENDING) { + pending_med_tp = PJ_TRUE; + } else if (status != PJ_SUCCESS) { + if (pending_med_tp) { + /* Save failure information. */ + call_med->tp_ready = status; + pj_bzero(&call->med_ch_info, sizeof(call->med_ch_info)); + call->med_ch_info.status = status; + call->med_ch_info.state = call_med->tp_st; + call->med_ch_info.med_idx = call_med->idx; + if (sip_err_code) + call->med_ch_info.sip_err_code = *sip_err_code; + + /* We will return failure in the callback later. */ + return PJ_EPENDING; + } + + pjsua_media_channel_deinit(call_id); goto on_error; } } else { @@ -1498,7 +1717,7 @@ pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, //call_med->tp = NULL; pj_assert(call_med->tp_st == PJSUA_MED_TP_INIT || call_med->tp_st == PJSUA_MED_TP_RUNNING); - call_med->tp_st = PJSUA_MED_TP_DISABLED; + set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED); } /* Put media type just for info */ @@ -1511,29 +1730,30 @@ pj_status_t pjsua_media_channel_init(pjsua_call_id call_id, PJ_LOG(4,(THIS_FILE, "Media index %d selected for audio call %d", call->audio_idx, call->index)); - /* Tell the media transport of a new offer/answer session */ - for (mi=0; mi < call->med_cnt; ++mi) { - pjsua_call_media *call_med = &call->media[mi]; + if (pending_med_tp) { + /* We have a pending media transport initialization. */ + pj_log_pop_indent(); + return PJ_EPENDING; + } - /* Note: tp may be NULL if this media line is disabled */ - if (call_med->tp && call_med->tp_st == PJSUA_MED_TP_IDLE) { - status = pjmedia_transport_media_create(call_med->tp, - tmp_pool, 0, - rem_sdp, mi); - if (status != PJ_SUCCESS) { - if (sip_err_code) *sip_err_code = PJSIP_SC_NOT_ACCEPTABLE; - pjsua_media_channel_deinit(call_id); - goto on_error; - } + /* Media transport initialization completed immediately, so + * we don't need to call the callback. + */ + call->med_ch_cb = NULL; - call_med->tp_st = PJSUA_MED_TP_INIT; - } - } + status = media_channel_init_cb(call_id, NULL); + if (status != PJ_SUCCESS && sip_err_code) + *sip_err_code = call->med_ch_info.sip_err_code; pj_log_pop_indent(); - return PJ_SUCCESS; + return status; on_error: + if (call->med_ch_mutex) { + pj_mutex_destroy(call->med_ch_mutex); + call->med_ch_mutex = NULL; + } + pj_log_pop_indent(); return status; } @@ -1562,7 +1782,8 @@ pj_status_t pjsua_media_channel_create_sdp(pjsua_call_id call_id, if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) { status = pjsua_media_channel_init(call_id, PJSIP_ROLE_UAS, call->secure_level, pool, - rem_sdp, sip_err_code); + rem_sdp, sip_err_code, + PJ_FALSE, NULL); if (status != PJ_SUCCESS) return status; } @@ -1885,9 +2106,9 @@ pj_status_t pjsua_media_channel_deinit(pjsua_call_id call_id) for (mi=0; mi<call->med_cnt; ++mi) { pjsua_call_media *call_med = &call->media[mi]; - if (call_med->tp_st != PJSUA_MED_TP_IDLE) { + if (call_med->tp_st > PJSUA_MED_TP_IDLE) { pjmedia_transport_media_stop(call_med->tp); - call_med->tp_st = PJSUA_MED_TP_IDLE; + set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); } //if (call_med->tp_orig && call_med->tp && @@ -1971,7 +2192,7 @@ static pj_status_t audio_channel_update(pjsua_call_media *call_med, if (status != PJ_SUCCESS) goto on_return; - call_med->tp_st = PJSUA_MED_TP_RUNNING; + set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING); /* Get remote SRTP usage policy */ pjmedia_transport_info_init(&tp_info); @@ -2263,7 +2484,7 @@ pj_status_t pjsua_media_channel_update(pjsua_call_id call_id, if (local_sdp->media[mi]->desc.port==0 && call_med->tp) { pjmedia_transport_close(call_med->tp); call_med->tp = call_med->tp_orig = NULL; - call_med->tp_st = PJSUA_MED_TP_IDLE; + set_media_tp_state(call_med, PJSUA_MED_TP_IDLE); } if (status != PJ_SUCCESS) { diff --git a/pjsip/src/pjsua-lib/pjsua_vid.c b/pjsip/src/pjsua-lib/pjsua_vid.c index 55f892ec..1d0697ed 100644 --- a/pjsip/src/pjsua-lib/pjsua_vid.c +++ b/pjsip/src/pjsua-lib/pjsua_vid.c @@ -675,7 +675,7 @@ pj_status_t video_channel_update(pjsua_call_media *call_med, if (status != PJ_SUCCESS) goto on_error; - call_med->tp_st = PJSUA_MED_TP_RUNNING; + set_media_tp_state(call_med, PJSUA_MED_TP_RUNNING); /* Get remote SRTP usage policy */ pjmedia_transport_info_init(&tp_info); @@ -1472,7 +1472,7 @@ static pj_status_t call_add_video(pjsua_call *call, status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, &acc_cfg->rtp_cfg, call->secure_level, - NULL); + NULL, PJ_FALSE, NULL); if (status != PJ_SUCCESS) goto on_error; @@ -1485,7 +1485,7 @@ static pj_status_t call_add_video(pjsua_call *call, if (status != PJ_SUCCESS) goto on_error; - call_med->tp_st = PJSUA_MED_TP_INIT; + set_media_tp_state(call_med, PJSUA_MED_TP_INIT); /* Get transport address info */ pjmedia_transport_info_init(&tpinfo); @@ -1592,7 +1592,7 @@ static pj_status_t call_modify_video(pjsua_call *call, status = pjsua_call_media_init(call_med, PJMEDIA_TYPE_VIDEO, &acc_cfg->rtp_cfg, call->secure_level, - NULL); + NULL, PJ_FALSE, NULL); if (status != PJ_SUCCESS) goto on_error; @@ -1662,7 +1662,7 @@ on_error: /* Mark media transport to disabled */ // Don't close this here, as SDP negotiation has not been // done and stream may be still active. - call_med->tp_st = PJSUA_MED_TP_DISABLED; + set_media_tp_state(call_med, PJSUA_MED_TP_DISABLED); /* Deactivate the stream */ pjmedia_sdp_media_deactivate(pool, sdp->media[med_idx]); |