您的位置:首页 > Web前端 > JavaScript

PJSIP学习笔记——PJSUA层发起呼叫的主要流程

2013-09-03 16:05 309 查看
在上一篇学习笔记从simple_pjsua.c示例程序了解PJSUA-LIB的基本使用流程中,使用了PJSUA层的
pjsua_call_make_call来发起一个呼叫,那么这个发起呼叫的流程是怎样的呢?先来看看这个函数:

[cpp]
view plaincopy

/*
* 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,
const pjsua_call_setting *opt,
void *user_data,
const pjsua_msg_data *msg_data,
pjsua_call_id *p_call_id)
{
pj_pool_t *tmp_pool = NULL;
pjsip_dialog *dlg = NULL;
pjsua_acc *acc;
pjsua_call *call;
int call_id = -1;
pj_str_t contact;
pj_status_t status;

/* Check that account is valid */
PJ_ASSERT_RETURN(acc_id>=0 || acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
PJ_EINVAL);

/* Check arguments */
PJ_ASSERT_RETURN(dest_uri, PJ_EINVAL);

PJ_LOG(4,(THIS_FILE, "Making call with acc #%d to %.*s", acc_id,
(int)dest_uri->slen, dest_uri->ptr));

pj_log_push_indent();

PJSUA_LOCK();

// 创建声音设备
/* Create sound port if none is instantiated, to check if sound device
* can be used. But only do this with the conference bridge, as with
* audio switchboard (i.e. APS-Direct), we can only open the sound
* device once the correct format has been known
*/
if (!pjsua_var.is_mswitch && pjsua_var.snd_port==NULL &&
pjsua_var.null_snd==NULL && !pjsua_var.no_snd)
{
status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
if (status != PJ_SUCCESS)
goto on_error;
}

// 检查SIP帐号
acc = &pjsua_var.acc[acc_id];
if (!acc->valid) {
pjsua_perror(THIS_FILE, "Unable to make call because account "
"is not valid", PJ_EINVALIDOP);
status = PJ_EINVALIDOP;
goto on_error;
}

// 创建呼叫标识
/* Find free call slot. */
call_id = alloc_call_id();

if (call_id == PJSUA_INVALID_ID) {
pjsua_perror(THIS_FILE, "Error making call", PJ_ETOOMANY);
status = PJ_ETOOMANY;
goto on_error;
}

// 复位呼叫参数
/* Clear call descriptor */
reset_call(call_id);

call = &pjsua_var.calls[call_id];

/* Associate session with account */
call->acc_id = acc_id;
call->call_hold_type = acc->cfg.call_hold_type;

// 设置呼叫参数
/* 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_error;
}

/* Create temporary pool */
tmp_pool = pjsua_pool_create("tmpcall10", 512, 256);

/* 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) {
pjsip_uri *uri;
pj_str_t dup;

// 分析被叫SIP号码
pj_strdup_with_null(tmp_pool, &dup, dest_uri);
uri = pjsip_parse_uri(tmp_pool, dup.ptr, dup.slen, 0);

if (uri == NULL) {
pjsua_perror(THIS_FILE, "Unable to make call",
PJSIP_EINVALIDREQURI);
status = PJSIP_EINVALIDREQURI;
goto on_error;
}
}

/* Mark call start time. */
pj_gettimeofday(&call->start_time);

/* Reset first response time */
call->res_time.sec = 0;

// 创建Contact头域
/* Create suitable Contact header unless a Contact header has been
* set in the account.
*/
if (acc->contact.slen) {
contact = acc->contact;
} else {
status = pjsua_acc_create_uac_contact(tmp_pool, &contact,
acc_id, dest_uri);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to generate Contact header",
status);
goto on_error;
}
}

// 创建SIP对话(Dialog)
/* 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);
goto on_error;
}

/* Increment the dialog's lock otherwise when invite session creation
* fails the dialog will be destroyed prematurely.
*/
pjsip_dlg_inc_lock(dlg);

// 设置Via头域
if (acc->cfg.allow_via_rewrite && acc->via_addr.host.slen > 0)
pjsip_dlg_set_via_sent_by(dlg, &acc->via_addr, acc->via_tp);

// 设置安全级别,安全级别有何作用?
/* Calculate call's secure level */
call->secure_level = get_secure_level(acc_id, dest_uri);

// 设置用户数据,用户数据是什么?
/* Attach user data */
call->user_data = user_data;

// 复制消息数据,消息数据有何作用?
/* Store variables required for the callback after the async
* media transport creation is completed.
*/
if (msg_data) {
call->async_call.call_var.out_call.msg_data = pjsua_msg_data_clone(
dlg->pool, msg_data);
}
// 保存对话信息
call->async_call.dlg = dlg;

/* Temporarily increment dialog session. Without this, dialog will be
* prematurely destroyed if dec_lock() is called on the dialog before
* the invite session is created.
*/
pjsip_dlg_inc_session(dlg, &pjsua_var.mod);

// 初始化媒体通道
/* Init media channel */
status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAC,
call->secure_level, dlg->pool,
NULL, NULL, PJ_TRUE,
&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);
pjsip_dlg_dec_session(dlg, &pjsua_var.mod);
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();

pj_log_pop_indent();

return PJ_SUCCESS;

on_error:
if (dlg) {
/* This may destroy the dialog */
pjsip_dlg_dec_lock(dlg);
}

if (call_id != -1) {
pjsua_media_channel_deinit(call_id);
reset_call(call_id);
}

pjsua_check_snd_dev_idle();

if (tmp_pool)
pj_pool_release(tmp_pool);
PJSUA_UNLOCK();

pj_log_pop_indent();
return status;
}

我们先来看看如何分配一个呼叫标识:

[cpp]
view plaincopy

/* Allocate one call id */
static pjsua_call_id alloc_call_id(void)
{
pjsua_call_id cid;

#if 1
/* New algorithm: round-robin */
if (pjsua_var.next_call_id >= (int)pjsua_var.ua_cfg.max_calls ||
pjsua_var.next_call_id < 0)
{
pjsua_var.next_call_id = 0;
}

// 从next_call_id到max_calls之间找一个空闲的calls数组元素
for (cid=pjsua_var.next_call_id;
cid<(int)pjsua_var.ua_cfg.max_calls;
++cid)
{
if (pjsua_var.calls[cid].inv == NULL &&
pjsua_var.calls[cid].async_call.dlg == NULL)
{
++pjsua_var.next_call_id;
return cid;
}
}

// 从0到next_call_id之间找一个空闲的calls数组元素
for (cid=0; cid < pjsua_var.next_call_id; ++cid) {
if (pjsua_var.calls[cid].inv == NULL &&
pjsua_var.calls[cid].async_call.dlg == NULL)
{
++pjsua_var.next_call_id;
return cid;
}
}

#else
/* Old algorithm */
for (cid=0; cid<(int)pjsua_var.ua_cfg.max_calls; ++cid) {
if (pjsua_var.calls[cid].inv == NULL)
return cid;
}
#endif

return PJSUA_INVALID_ID;
}

从上面的函数来看,这里的分配呼叫标识只是在calls数据中寻找一个空闲的单元(用于存放呼叫数据),这个呼叫标识并不是SIP协议里面的CALL ID的概念。
reset_call函数就是将呼叫参数设置为0值:

[cpp]
view plaincopy

*
* Reset call descriptor.
*/
static void reset_call(pjsua_call_id id)
{
pjsua_call *call = &pjsua_var.calls[id];
unsigned i;

pj_bzero(call, sizeof(*call));
call->index = id;
call->last_text.ptr = call->last_text_buf_;
for (i=0; i<PJ_ARRAY_SIZE(call->media); ++i) {
pjsua_call_media *call_med = &call->media[i];
call_med->ssrc = pj_rand();
call_med->strm.a.conf_slot = PJSUA_INVALID_ID;
call_med->strm.v.cap_win_id = PJSUA_INVALID_ID;
call_med->strm.v.rdr_win_id = PJSUA_INVALID_ID;
call_med->call = call;
call_med->idx = i;
call_med->tp_auto_del = PJ_TRUE;
}
pjsua_call_setting_default(&call->opt);
pj_timer_entry_init(&call->reinv_timer, PJ_FALSE,
(void*)(pj_size_t)id, &reinv_timer_cb);
}

设置呼叫参数:

[cpp]
view plaincopy

static pj_status_t apply_call_setting(pjsua_call *call,
const pjsua_call_setting *opt,
const pjmedia_sdp_session *rem_sdp)
{
pj_assert(call);

if (!opt)
return PJ_SUCCESS;

#if !PJMEDIA_HAS_VIDEO
pj_assert(opt->vid_cnt == 0);
#endif

call->opt = *opt;

// 如果呼叫已建立,则设置本端的对话角色
// 如果有远端SDP,则本端为UAS(User Agent Server),否则为UAC
/* If call is established, reinit media channel */
if (call->inv && call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
pjsip_role_e role = rem_sdp? PJSIP_ROLE_UAS : PJSIP_ROLE_UAC;
pj_status_t status;

// 初始化媒体通道
status = pjsua_media_channel_init(call->index, role,
call->secure_level,
call->inv->pool_prov,
rem_sdp, NULL,
PJ_FALSE, NULL);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Error re-initializing media channel",
status);
return status;
}
}

return PJ_SUCCESS;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: