pjsua_call_make_call来发起一个呼叫,那么这个发起呼叫的流程是怎样的呢?先来看看这个函数:
/* * 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; }
/* 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值:
* * 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); }
设置呼叫参数:
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; }