📄 pjsua_call.c
字号:
/* $Id: pjsua_call.c 1107 2007-03-27 10:53:57Z bennylp $ */
/*
* Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjsua-lib/pjsua.h>
#include <pjsua-lib/pjsua_internal.h>
#define THIS_FILE "pjsua_call.c"
/* This callback receives notification from invite session when the
* session state has changed.
*/
static void pjsua_call_on_state_changed(pjsip_inv_session *inv,
pjsip_event *e);
/* This callback is called by invite session framework when UAC session
* has forked.
*/
static void pjsua_call_on_forked( pjsip_inv_session *inv,
pjsip_event *e);
/*
* 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.
*/
static void pjsua_call_on_media_update(pjsip_inv_session *inv,
pj_status_t status);
/*
* Called when session received new offer.
*/
static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
const pjmedia_sdp_session *offer);
/*
* This callback is called when transaction state has changed in INVITE
* session. We use this to trap:
* - incoming REFER request.
* - incoming MESSAGE request.
*/
static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
pjsip_transaction *tsx,
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,
pjmedia_sdp_session **p_answer);
/*
* Callback called by event framework when the xfer subscription state
* has changed.
*/
static void xfer_client_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
static void xfer_server_on_evsub_state( pjsip_evsub *sub, pjsip_event *event);
/*
* Reset call descriptor.
*/
static void reset_call(pjsua_call_id id)
{
pjsua_call *call = &pjsua_var.calls[id];
call->index = id;
call->inv = NULL;
call->user_data = NULL;
call->session = NULL;
call->xfer_sub = NULL;
call->last_code = 0;
call->conf_slot = PJSUA_INVALID_ID;
call->last_text.ptr = call->last_text_buf_;
call->last_text.slen = 0;
call->conn_time.sec = 0;
call->conn_time.msec = 0;
call->res_time.sec = 0;
call->res_time.msec = 0;
}
/*
* Init call subsystem.
*/
pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg)
{
pjsip_inv_callback inv_cb;
unsigned i;
const pj_str_t str_norefersub = { "norefersub", 10 };
pj_status_t status;
/* Init calls array. */
for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.calls); ++i)
reset_call(i);
/* Copy config */
pjsua_config_dup(pjsua_var.pool, &pjsua_var.ua_cfg, cfg);
/* Initialize invite session callback. */
pj_bzero(&inv_cb, sizeof(inv_cb));
inv_cb.on_state_changed = &pjsua_call_on_state_changed;
inv_cb.on_new_session = &pjsua_call_on_forked;
inv_cb.on_media_update = &pjsua_call_on_media_update;
inv_cb.on_rx_offer = &pjsua_call_on_rx_offer;
inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed;
/* Initialize invite session module: */
status = pjsip_inv_usage_init(pjsua_var.endpt, &inv_cb);
PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Add "norefersub" in Supported header */
pjsip_endpt_add_capability(pjsua_var.endpt, NULL, PJSIP_H_SUPPORTED,
NULL, 1, &str_norefersub);
return status;
}
/*
* Start call subsystem.
*/
pj_status_t pjsua_call_subsys_start(void)
{
/* Nothing to do */
return PJ_SUCCESS;
}
/*
* Get maximum number of calls configured in pjsua.
*/
PJ_DEF(unsigned) pjsua_call_get_max_count(void)
{
return pjsua_var.ua_cfg.max_calls;
}
/*
* Get number of currently active calls.
*/
PJ_DEF(unsigned) pjsua_call_get_count(void)
{
return pjsua_var.call_cnt;
}
/*
* Enum calls.
*/
PJ_DEF(pj_status_t) pjsua_enum_calls( pjsua_call_id ids[],
unsigned *count)
{
unsigned i, c;
PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL);
PJSUA_LOCK();
for (i=0, c=0; c<*count && i<pjsua_var.ua_cfg.max_calls; ++i) {
if (!pjsua_var.calls[i].inv)
continue;
ids[c] = i;
++c;
}
*count = c;
PJSUA_UNLOCK();
return PJ_SUCCESS;
}
/*
* Make outgoing call to the specified URI using the specified account.
*/
PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id,
const pj_str_t *dest_uri,
unsigned options,
void *user_data,
const pjsua_msg_data *msg_data,
pjsua_call_id *p_call_id)
{
pjsip_dialog *dlg = NULL;
pjmedia_sdp_session *offer;
pjsip_inv_session *inv = NULL;
pjsua_acc *acc;
pjsua_call *call;
unsigned call_id;
pj_str_t contact;
pjsip_tx_data *tdata;
pj_status_t status;
/* Check that account is valid */
PJ_ASSERT_RETURN(acc_id>=0 || acc_id<PJ_ARRAY_SIZE(pjsua_var.acc),
PJ_EINVAL);
/* Options must be zero for now */
PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);
/* Check arguments */
PJ_ASSERT_RETURN(dest_uri, PJ_EINVAL);
PJSUA_LOCK();
acc = &pjsua_var.acc[acc_id];
if (!acc->valid) {
pjsua_perror(THIS_FILE, "Unable to make call because account "
"is not valid", PJ_EINVALIDOP);
PJSUA_UNLOCK();
return PJ_EINVALIDOP;
}
/* Find free call slot. */
for (call_id=0; call_id<pjsua_var.ua_cfg.max_calls; ++call_id) {
if (pjsua_var.calls[call_id].inv == NULL)
break;
}
if (call_id == pjsua_var.ua_cfg.max_calls) {
pjsua_perror(THIS_FILE, "Error making file", PJ_ETOOMANY);
PJSUA_UNLOCK();
return PJ_ETOOMANY;
}
call = &pjsua_var.calls[call_id];
/* Verify that destination URI is valid before calling
* pjsua_acc_create_uac_contact, or otherwise there
* a misleading "Invalid Contact URI" error will be printed
* when pjsua_acc_create_uac_contact() fails.
*/
if (1) {
pj_pool_t *pool;
pjsip_uri *uri;
pj_str_t dup;
pool = pjsua_pool_create("tmp-uri", 4000, 4000);
if (!pool) {
pjsua_perror(THIS_FILE, "Unable to create pool", PJ_ENOMEM);
PJSUA_UNLOCK();
return PJ_ENOMEM;
}
pj_strdup_with_null(pool, &dup, dest_uri);
uri = pjsip_parse_uri(pool, dup.ptr, dup.slen, 0);
pj_pool_release(pool);
if (uri == NULL) {
pjsua_perror(THIS_FILE, "Unable to make call",
PJSIP_EINVALIDREQURI);
PJSUA_UNLOCK();
return PJSIP_EINVALIDREQURI;
}
}
PJ_LOG(4,(THIS_FILE, "Making call with acc #%d to %.*s", acc_id,
(int)dest_uri->slen, dest_uri->ptr));
/* Mark call start time. */
pj_gettimeofday(&call->start_time);
/* Reset first response time */
call->res_time.sec = 0;
/* Create suitable Contact header */
status = pjsua_acc_create_uac_contact(pjsua_var.pool, &contact,
acc_id, dest_uri);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to generate Contact header", status);
PJSUA_UNLOCK();
return status;
}
/* Create outgoing dialog: */
status = pjsip_dlg_create_uac( pjsip_ua_instance(),
&acc->cfg.id, &contact,
dest_uri, dest_uri, &dlg);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Dialog creation failed", status);
PJSUA_UNLOCK();
return status;
}
/* Get media capability from media endpoint: */
status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, dlg->pool, 1,
&call->skinfo, &offer);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status);
goto on_error;
}
/* Create the INVITE session: */
status = pjsip_inv_create_uac( dlg, offer, 0, &inv);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Invite session creation 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);
}
/* 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, both dialog and invite
* session would have been cleared.
*/
inv = NULL;
dlg = NULL;
goto on_error;
}
/* Done. */
if (p_call_id)
*p_call_id = call_id;
PJSUA_UNLOCK();
return PJ_SUCCESS;
on_error:
if (inv != NULL) {
pjsip_inv_terminate(inv, PJSIP_SC_OK, PJ_FALSE);
} else if (dlg) {
pjsip_dlg_terminate(dlg);
}
if (call_id != -1) {
reset_call(call_id);
}
PJSUA_UNLOCK();
return status;
}
/**
* Handle incoming INVITE request.
* Called by pjsua_core.c
*/
pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
{
pj_str_t contact;
pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
pjsip_dialog *replaced_dlg = NULL;
pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
pjsip_msg *msg = rdata->msg_info.msg;
pjsip_tx_data *response = NULL;
unsigned options = 0;
pjsip_inv_session *inv = NULL;
int acc_id;
pjsua_call *call;
int call_id = -1;
pjmedia_sdp_session *answer;
pj_status_t status;
/* Don't want to handle anything but INVITE */
if (msg->line.req.method.id != PJSIP_INVITE_METHOD)
return PJ_FALSE;
/* Don't want to handle anything that's already associated with
* existing dialog or transaction.
*/
if (dlg || tsx)
return PJ_FALSE;
PJSUA_LOCK();
/* Find free call slot. */
for (call_id=0; call_id<(int)pjsua_var.ua_cfg.max_calls; ++call_id) {
if (pjsua_var.calls[call_id].inv == NULL)
break;
}
if (call_id == (int)pjsua_var.ua_cfg.max_calls) {
pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata,
PJSIP_SC_BUSY_HERE, NULL,
NULL, NULL);
PJ_LOG(2,(THIS_FILE,
"Unable to accept incoming call (too many calls)"));
PJSUA_UNLOCK();
return PJ_TRUE;
}
/* Clear call descriptor */
reset_call(call_id);
call = &pjsua_var.calls[call_id];
/* Mark call start time. */
pj_gettimeofday(&call->start_time);
/* Check INVITE request for Replaces header. If Replaces header is
* present, the function will make sure that we can handle the request.
*/
status = pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE,
&response);
if (status != PJ_SUCCESS) {
/*
* Something wrong with the Replaces header.
*/
if (response) {
pjsip_response_addr res_addr;
pjsip_get_response_addr(response->pool, rdata, &res_addr);
pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response,
NULL, NULL);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -