diff options
Diffstat (limited to 'pjsip/src/pjsua-lib/pjsua_media.c')
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_media.c | 399 |
1 files changed, 310 insertions, 89 deletions
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) { |