summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsua-lib
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src/pjsua-lib')
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c348
1 files changed, 253 insertions, 95 deletions
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index ab7fd1f7..4d879bad 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -806,6 +806,79 @@ static void update_remote_nat_type(pjsua_call *call,
}
+static pj_status_t process_incoming_call_replace(pjsua_call *call,
+ pjsip_dialog *replaced_dlg)
+{
+ pjsip_inv_session *replaced_inv;
+ struct pjsua_call *replaced_call;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ /* Get the invite session in the dialog */
+ replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg);
+
+ /* Get the replaced call instance */
+ replaced_call = (pjsua_call*) replaced_dlg->mod_data[pjsua_var.mod.id];
+
+ /* Notify application */
+ if (pjsua_var.ua_cfg.cb.on_call_replaced)
+ pjsua_var.ua_cfg.cb.on_call_replaced(replaced_call->index,
+ call->index);
+
+ PJ_LOG(4,(THIS_FILE, "Answering replacement call %d with 200/OK",
+ call->index));
+
+ /* Answer the new call with 200 response */
+ status = pjsip_inv_answer(call->inv, 200, NULL, NULL, &tdata);
+ if (status == PJ_SUCCESS)
+ status = pjsip_inv_send_msg(call->inv, tdata);
+
+ if (status != PJ_SUCCESS)
+ pjsua_perror(THIS_FILE, "Error answering session", status);
+
+ /* Note that inv may be invalid if 200/OK has caused error in
+ * starting the media.
+ */
+
+ PJ_LOG(4,(THIS_FILE, "Disconnecting replaced call %d",
+ replaced_call->index));
+
+ /* Disconnect replaced invite session */
+ status = pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, NULL,
+ &tdata);
+ if (status == PJ_SUCCESS && tdata)
+ status = pjsip_inv_send_msg(replaced_inv, tdata);
+
+ if (status != PJ_SUCCESS)
+ pjsua_perror(THIS_FILE, "Error terminating session", status);
+
+ return status;
+}
+
+
+static void process_pending_call_answer(pjsua_call *call)
+{
+ struct call_answer *answer, *next;
+
+ answer = call->async_call.call_var.inc_call.answers.next;
+ while (answer != &call->async_call.call_var.inc_call.answers) {
+ next = answer->next;
+ pjsua_call_answer2(call->index, answer->opt, answer->code,
+ answer->reason, answer->msg_data);
+
+ /* Call might have been disconnected if application is answering
+ * with 200/OK and the media failed to start.
+ * See pjsua_call_answer() below.
+ */
+ if (!call->inv || !call->inv->pool_prov)
+ break;
+
+ pj_list_erase(answer);
+ answer = next;
+ }
+}
+
+
/* Incoming call callback when media transport creation is completed. */
static pj_status_t
on_incoming_call_med_tp_complete(pjsua_call_id call_id,
@@ -889,27 +962,17 @@ on_return:
*/
call->med_ch_cb = NULL;
- if (status == PJ_SUCCESS &&
- !pj_list_empty(&call->async_call.call_var.inc_call.answers))
- {
- struct call_answer *answer, *next;
-
- answer = call->async_call.call_var.inc_call.answers.next;
- while (answer != &call->async_call.call_var.inc_call.answers) {
- next = answer->next;
- pjsua_call_answer(call_id, answer->code, answer->reason,
- answer->msg_data);
-
- /* Call might have been disconnected if application is answering
- * with 200/OK and the media failed to start.
- * See pjsua_call_answer() below.
- */
- if (!call->inv || !call->inv->pool_prov)
- break;
-
- pj_list_erase(answer);
- answer = next;
- }
+ /* Finish any pending process */
+ if (status == PJ_SUCCESS) {
+ if (call->async_call.call_var.inc_call.replaced_dlg) {
+ /* Process pending call replace */
+ pjsip_dialog *replaced_dlg =
+ call->async_call.call_var.inc_call.replaced_dlg;
+ process_incoming_call_replace(call, replaced_dlg);
+ } else {
+ /* Process pending call answers */
+ process_pending_call_answer(call);
+ }
}
PJSUA_UNLOCK();
@@ -1252,33 +1315,38 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
call->async_call.dlg = dlg;
pj_list_init(&call->async_call.call_var.inc_call.answers);
- /* Init media channel */
- status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS,
- call->secure_level,
- rdata->tp_info.pool,
- offer,
- &sip_err_code, PJ_TRUE,
- &on_incoming_call_med_tp_complete);
- if (status == PJ_SUCCESS) {
- status = on_incoming_call_med_tp_complete(call_id, NULL);
- if (status != PJ_SUCCESS) {
- sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
- /* Since the call invite's state is still PJSIP_INV_STATE_NULL,
- * the invite session was not ended in
- * on_incoming_call_med_tp_complete(), so we need to send
- * a response message and terminate the invite here.
- */
- pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL);
- pjsip_inv_terminate(call->inv, sip_err_code, PJ_FALSE);
- call->inv = NULL;
+ /* Init media channel, only when there is offer or call replace request.
+ * For incoming call without SDP offer, media channel init will be done
+ * in pjsua_call_answer(), see ticket #1526.
+ */
+ if (offer || replaced_dlg) {
+ status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS,
+ call->secure_level,
+ rdata->tp_info.pool,
+ offer,
+ &sip_err_code, PJ_TRUE,
+ &on_incoming_call_med_tp_complete);
+ if (status == PJ_SUCCESS) {
+ status = on_incoming_call_med_tp_complete(call_id, NULL);
+ if (status != PJ_SUCCESS) {
+ sip_err_code = PJSIP_SC_NOT_ACCEPTABLE;
+ /* Since the call invite's state is still PJSIP_INV_STATE_NULL,
+ * the invite session was not ended in
+ * on_incoming_call_med_tp_complete(), so we need to send
+ * a response message and terminate the invite here.
+ */
+ pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL);
+ pjsip_inv_terminate(call->inv, sip_err_code, PJ_FALSE);
+ call->inv = NULL;
+ goto on_return;
+ }
+ } else if (status != PJ_EPENDING) {
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL);
+ pjsip_inv_terminate(call->inv, sip_err_code, PJ_FALSE);
+ call->inv = NULL;
goto on_return;
- }
- } else if (status != PJ_EPENDING) {
- pjsua_perror(THIS_FILE, "Error initializing media channel", status);
- pjsip_dlg_respond(dlg, rdata, sip_err_code, NULL, NULL, NULL);
- pjsip_inv_terminate(call->inv, sip_err_code, PJ_FALSE);
- call->inv = NULL;
- goto on_return;
+ }
}
/* Create answer */
@@ -1364,51 +1432,17 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
/* Check if this request should replace existing call */
if (replaced_dlg) {
- pjsip_inv_session *replaced_inv;
- struct pjsua_call *replaced_call;
- pjsip_tx_data *tdata;
-
- /* Get the invite session in the dialog */
- replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg);
-
- /* Get the replaced call instance */
- replaced_call = (pjsua_call*) replaced_dlg->mod_data[pjsua_var.mod.id];
-
- /* Notify application */
- if (pjsua_var.ua_cfg.cb.on_call_replaced)
- pjsua_var.ua_cfg.cb.on_call_replaced(replaced_call->index,
- call_id);
-
- PJ_LOG(4,(THIS_FILE, "Answering replacement call %d with 200/OK",
- call_id));
-
- /* Answer the new call with 200 response */
- status = pjsip_inv_answer(inv, 200, NULL, NULL, &tdata);
- if (status == PJ_SUCCESS)
- status = pjsip_inv_send_msg(inv, tdata);
-
- if (status != PJ_SUCCESS)
- pjsua_perror(THIS_FILE, "Error answering session", status);
-
- /* Note that inv may be invalid if 200/OK has caused error in
- * starting the media.
+ /* Process call replace. If the media channel init has been completed,
+ * just process now, otherwise, just queue the replaced dialog so
+ * it will be processed once the media channel async init is finished
+ * successfully.
*/
-
- PJ_LOG(4,(THIS_FILE, "Disconnecting replaced call %d",
- replaced_call->index));
-
- /* Disconnect replaced invite session */
- status = pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, NULL,
- &tdata);
- if (status == PJ_SUCCESS && tdata)
- status = pjsip_inv_send_msg(replaced_inv, tdata);
-
- if (status != PJ_SUCCESS)
- pjsua_perror(THIS_FILE, "Error terminating session", status);
-
-
+ if (call->med_ch_cb == NULL) {
+ process_incoming_call_replace(call, replaced_dlg);
+ } else {
+ call->async_call.call_var.inc_call.replaced_dlg = replaced_dlg;
+ }
} else {
-
/* Notify application if on_incoming_call() is overriden,
* otherwise hangup the call with 480
*/
@@ -1821,6 +1855,81 @@ pjsua_call_get_med_transport_info(pjsua_call_id call_id,
}
+/* Media channel init callback for pjsua_call_answer(). */
+static pj_status_t
+on_answer_call_med_tp_complete(pjsua_call_id call_id,
+ const pjsua_med_tp_state_info *info)
+{
+ pjsua_call *call = &pjsua_var.calls[call_id];
+ pjmedia_sdp_session *sdp;
+ int sip_err_code = (info? info->sip_err_code: 0);
+ pj_status_t status = (info? info->status: PJ_SUCCESS);
+
+ PJSUA_LOCK();
+
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ goto on_return;
+ }
+
+ /* pjsua_media_channel_deinit() has been called. */
+ if (call->async_call.med_ch_deinit) {
+ pjsua_media_channel_deinit(call->index);
+ call->med_ch_cb = NULL;
+ PJSUA_UNLOCK();
+ return PJ_SUCCESS;
+ }
+
+ status = pjsua_media_channel_create_sdp(call_id,
+ call->async_call.dlg->pool,
+ NULL, &sdp, &sip_err_code);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error creating SDP answer", status);
+ goto on_return;
+ }
+
+ status = pjsip_inv_set_local_sdp(call->inv, sdp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Error setting local SDP", status);
+ sip_err_code = PJSIP_SC_NOT_ACCEPTABLE_HERE;
+ goto on_return;
+ }
+
+on_return:
+ if (status != PJ_SUCCESS) {
+ /* If the callback is called from pjsua_call_on_incoming(), the
+ * invite's state is PJSIP_INV_STATE_NULL, so the invite session
+ * will be terminated later, otherwise we end the session here.
+ */
+ if (call->inv->state > PJSIP_INV_STATE_NULL) {
+ pjsip_tx_data *tdata;
+ pj_status_t status_;
+
+ status_ = pjsip_inv_end_session(call->inv, sip_err_code, NULL,
+ &tdata);
+ if (status_ == PJ_SUCCESS && tdata)
+ status_ = pjsip_inv_send_msg(call->inv, tdata);
+ }
+
+ pjsua_media_channel_deinit(call->index);
+ }
+
+ /* Set the callback to NULL to indicate that the async operation
+ * has completed.
+ */
+ call->med_ch_cb = NULL;
+
+ /* Finish any pending process */
+ if (status == PJ_SUCCESS) {
+ /* Process pending call answers */
+ process_pending_call_answer(call);
+ }
+
+ PJSUA_UNLOCK();
+ return status;
+}
+
+
/*
* Send response to incoming INVITE request.
*/
@@ -1857,14 +1966,57 @@ PJ_DEF(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id,
if (status != PJ_SUCCESS)
goto on_return;
- /* Apply call setting */
- status = apply_call_setting(call, opt, NULL);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Failed to apply call setting", status);
- goto on_return;
+ /* Apply call setting, only if status code is 1xx or 2xx. */
+ if (opt && code < 300) {
+ /* Check if it has not been set previously or it is different to
+ * the previous one.
+ */
+ if (!call->opt_inited) {
+ call->opt_inited = PJ_TRUE;
+ apply_call_setting(call, opt, NULL);
+ } else if (pj_memcmp(opt, &call->opt, sizeof(*opt)) != 0) {
+ /* Warn application about call setting inconsistency */
+ PJ_LOG(2,(THIS_FILE, "The call setting changes is ignored."));
+ }
}
PJSUA_LOCK();
+
+ /* Ticket #1526: When the incoming call contains no SDP offer, the media
+ * channel may have not been initialized at this stage. The media channel
+ * will be initialized here (along with SDP local offer generation) when
+ * the following conditions are met:
+ * - no pending media channel init
+ * - local SDP has not been generated
+ * - call setting has just been set, or SDP offer needs to be sent, i.e:
+ * answer code 183 or 2xx is issued
+ */
+ if (!call->med_ch_cb &&
+ (call->opt_inited || (code==183 && code/100==2)) &&
+ (!call->inv->neg ||
+ pjmedia_sdp_neg_get_state(call->inv->neg) ==
+ PJMEDIA_SDP_NEG_STATE_NULL))
+ {
+ /* Mark call setting as initialized as it is just about to be used
+ * for initializing the media channel.
+ */
+ call->opt_inited = PJ_TRUE;
+
+ status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC,
+ call->secure_level,
+ dlg->pool,
+ NULL, NULL, PJ_TRUE,
+ &on_answer_call_med_tp_complete);
+ if (status == PJ_SUCCESS) {
+ status = on_answer_call_med_tp_complete(call->index, NULL);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ } else if (status != PJ_EPENDING) {
+ pjsua_perror(THIS_FILE, "Error initializing media channel", status);
+ goto on_return;
+ }
+ }
+
/* If media transport creation is not yet completed, we will answer
* the call in the media transport creation callback instead.
*/
@@ -1876,6 +2028,11 @@ PJ_DEF(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id,
answer = PJ_POOL_ZALLOC_T(call->inv->pool_prov, struct call_answer);
answer->code = code;
+ if (opt) {
+ answer->opt = PJ_POOL_ZALLOC_T(call->inv->pool_prov,
+ pjsua_call_setting);
+ *answer->opt = *opt;
+ }
if (reason) {
pj_strdup(call->inv->pool_prov, answer->reason, reason);
}
@@ -1891,6 +2048,7 @@ PJ_DEF(pj_status_t) pjsua_call_answer2(pjsua_call_id call_id,
pj_log_pop_indent();
return status;
}
+
PJSUA_UNLOCK();
if (call->res_time.sec == 0)