📄 pjsua_call.c
字号:
if (len > 0 && len < end-p) {
p += len;
*p++ = '\n';
*p = '\0';
}
/* Dump session statistics */
if (with_media && call->session)
dump_media_session(indent, p, end-p, call->session);
pjsip_dlg_dec_lock(dlg);
return PJ_SUCCESS;
}
/*
* 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)
{
pjsua_call *call;
PJSUA_LOCK();
call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
if (!call) {
PJSUA_UNLOCK();
return;
}
/* Get call times */
switch (inv->state) {
case PJSIP_INV_STATE_EARLY:
case PJSIP_INV_STATE_CONNECTING:
if (call->res_time.sec == 0)
pj_gettimeofday(&call->res_time);
call->last_code = (pjsip_status_code)
e->body.tsx_state.tsx->status_code;
pj_strncpy(&call->last_text,
&e->body.tsx_state.tsx->status_text,
sizeof(call->last_text_buf_));
break;
case PJSIP_INV_STATE_CONFIRMED:
pj_gettimeofday(&call->conn_time);
break;
case PJSIP_INV_STATE_DISCONNECTED:
pj_gettimeofday(&call->dis_time);
if (call->res_time.sec == 0)
pj_gettimeofday(&call->res_time);
if (e->body.tsx_state.tsx->status_code > call->last_code) {
call->last_code = (pjsip_status_code)
e->body.tsx_state.tsx->status_code;
pj_strncpy(&call->last_text,
&e->body.tsx_state.tsx->status_text,
sizeof(call->last_text_buf_));
}
break;
default:
call->last_code = (pjsip_status_code)
e->body.tsx_state.tsx->status_code;
pj_strncpy(&call->last_text,
&e->body.tsx_state.tsx->status_text,
sizeof(call->last_text_buf_));
break;
}
/* If this is an outgoing INVITE that was created because of
* REFER/transfer, send NOTIFY to transferer.
*/
if (call->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) {
int st_code = -1;
pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE;
switch (call->inv->state) {
case PJSIP_INV_STATE_NULL:
case PJSIP_INV_STATE_CALLING:
/* Do nothing */
break;
case PJSIP_INV_STATE_EARLY:
case PJSIP_INV_STATE_CONNECTING:
st_code = e->body.tsx_state.tsx->status_code;
ev_state = PJSIP_EVSUB_STATE_ACTIVE;
break;
case PJSIP_INV_STATE_CONFIRMED:
/* When state is confirmed, send the final 200/OK and terminate
* subscription.
*/
st_code = e->body.tsx_state.tsx->status_code;
ev_state = PJSIP_EVSUB_STATE_TERMINATED;
break;
case PJSIP_INV_STATE_DISCONNECTED:
st_code = e->body.tsx_state.tsx->status_code;
ev_state = PJSIP_EVSUB_STATE_TERMINATED;
break;
case PJSIP_INV_STATE_INCOMING:
/* Nothing to do. Just to keep gcc from complaining about
* unused enums.
*/
break;
}
if (st_code != -1) {
pjsip_tx_data *tdata;
pj_status_t status;
status = pjsip_xfer_notify( call->xfer_sub,
ev_state, st_code,
NULL, &tdata);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status);
} else {
status = pjsip_xfer_send_request(call->xfer_sub, tdata);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status);
}
}
}
}
if (pjsua_var.ua_cfg.cb.on_call_state)
(*pjsua_var.ua_cfg.cb.on_call_state)(call->index, e);
/* call->inv may be NULL now */
/* Destroy media session when invite session is disconnected. */
if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
pj_assert(call != NULL);
if (call)
pjsua_media_channel_deinit(call->index);
/* Free call */
call->inv = NULL;
--pjsua_var.call_cnt;
/* Reset call */
reset_call(call->index);
}
PJSUA_UNLOCK();
}
/*
* 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)
{
PJ_UNUSED_ARG(inv);
PJ_UNUSED_ARG(e);
PJ_TODO(HANDLE_FORKED_DIALOG);
}
/*
* Disconnect call upon error.
*/
static void call_disconnect( pjsip_inv_session *inv,
int code )
{
pjsip_tx_data *tdata;
pj_status_t status;
status = pjsip_inv_end_session(inv, code, NULL, &tdata);
if (status == PJ_SUCCESS)
pjsip_inv_send_msg(inv, tdata);
}
/*
* 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)
{
pjsua_call *call;
const pjmedia_sdp_session *local_sdp;
const pjmedia_sdp_session *remote_sdp;
PJSUA_LOCK();
call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "SDP negotiation has failed", status);
/* Stop/destroy media, if any */
pjsua_media_channel_deinit(call->index);
/* Disconnect call if we're not in the middle of initializing an
* UAS dialog and if this is not a re-INVITE
*/
if (inv->state != PJSIP_INV_STATE_NULL &&
inv->state != PJSIP_INV_STATE_CONFIRMED)
{
//call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
}
PJSUA_UNLOCK();
return;
}
/* Get local and remote SDP */
status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE,
"Unable to retrieve currently active local SDP",
status);
//call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
PJSUA_UNLOCK();
return;
}
status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE,
"Unable to retrieve currently active remote SDP",
status);
//call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
PJSUA_UNLOCK();
return;
}
status = pjsua_media_channel_update(call->index, local_sdp, remote_sdp);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to create media session",
status);
call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
pjsua_media_channel_deinit(call->index);
PJSUA_UNLOCK();
return;
}
/* Call application callback, if any */
if (pjsua_var.ua_cfg.cb.on_call_media_state)
pjsua_var.ua_cfg.cb.on_call_media_state(call->index);
PJSUA_UNLOCK();
}
/*
* Create inactive SDP for call hold.
*/
static pj_status_t create_inactive_sdp(pjsua_call *call,
pjmedia_sdp_session **p_answer)
{
pj_status_t status;
pjmedia_sdp_conn *conn;
pjmedia_sdp_attr *attr;
pjmedia_sock_info skinfo;
pjmedia_sdp_session *sdp;
/* Get media socket info */
pjmedia_transport_get_info(call->med_tp, &skinfo);
/* Create new offer */
status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pjsua_var.pool, 1,
&skinfo, &sdp);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
return status;
}
/* Get SDP media connection line */
conn = sdp->media[0]->conn;
if (!conn)
conn = sdp->conn;
/* Modify address */
conn->addr = pj_str("0.0.0.0");
/* Remove existing directions attributes */
pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendrecv");
pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendonly");
pjmedia_sdp_media_remove_all_attr(sdp->media[0], "recvonly");
pjmedia_sdp_media_remove_all_attr(sdp->media[0], "inactive");
/* Add inactive attribute */
attr = pjmedia_sdp_attr_create(pjsua_var.pool, "inactive", NULL);
pjmedia_sdp_media_add_attr(sdp->media[0], attr);
*p_answer = sdp;
return status;
}
/*
* Called when session received new offer.
*/
static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
const pjmedia_sdp_session *offer)
{
const char *remote_state;
pjsua_call *call;
pjmedia_sdp_conn *conn;
pjmedia_sdp_session *answer;
pj_bool_t is_remote_active;
pj_status_t status;
PJSUA_LOCK();
call = (pjsua_call*) inv->dlg->mod_data[pjsua_var.mod.id];
/*
* See if remote is offering active media (i.e. not on-hold)
*/
is_remote_active = PJ_TRUE;
conn = offer->media[0]->conn;
if (!conn)
conn = offer->conn;
if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 ||
pj_strcmp2(&conn->addr, "0")==0)
{
is_remote_active = PJ_FALSE;
}
else if (pjmedia_sdp_media_find_attr2(offer->media[0], "inactive", NULL))
{
is_remote_active = PJ_FALSE;
}
remote_state = (is_remote_active ? "active" : "inactive");
/* Supply candidate answer */
if (call->media_st == PJSUA_CALL_MEDIA_LOCAL_HOLD || !is_remote_active) {
PJ_LOG(4,(THIS_FILE,
"Call %d: RX new media offer, creating inactive SDP "
"(media in offer is %s)", call->index, remote_state));
status = create_inactive_sdp( call, &answer );
} else {
PJ_LOG(4,(THIS_FILE, "Call %d: received updated media offer",
call->index));
/* Init media channel */
status = pjsua_media_channel_init(call->index, PJSIP_ROLE_UAS);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Error initializing media channel", status);
PJSUA_UNLOCK();
return;
}
status = pjsua_media_channel_create_sdp(call->index, call->inv->pool, &answer);
}
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
PJSUA_UNLOCK();
return;
}
status = pjsip_inv_set_sdp_answer(call->inv, answer);
if (status != PJ_SUCCESS) {
pjsua_perror(THIS_FILE, "Unable to set answer", status);
PJSUA_UNLOCK();
return;
}
PJSUA_UNLOCK();
}
/*
* 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)
{
PJ_UNUSED_ARG(event);
/*
* When subscription is accepted (got 200/OK to REFER), check if
* subscription suppressed.
*/
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACCEPTED) {
pjsip_rx_data *rdata;
pjsip_generic_string_hdr *refer_sub;
const pj_str_t REFER_SUB = { "Refer-Sub", 9 };
pjsua_call *call;
call = (pjsua_call*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
/* Must be receipt of response message */
pj_assert(event->type == PJSIP_EVENT_TSX_STATE &&
event->body.tsx_state.type == PJSIP_EVENT_RX_MSG);
rdata = event->body.tsx_state.src.rdata;
/* Find Refer-Sub header */
refer_sub = (pjsip_generic_string_hdr*)
pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
&REFER_SUB, NULL);
/* Check if subscription is suppressed */
if (refer_sub && pj_stricmp2(&refer_sub->hvalue, "false")==0) {
/* Since no subscription is desired, assume that call has been
* transfered successfully.
*/
if (call && pjsua_var.ua_cfg.cb.on_call_transfer_status) {
const pj_str_t ACCEPTED = { "Accepted", 8 };
pj_bool_t cont = PJ_FALSE;
(*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index,
200,
&ACCEPTED,
PJ_TRUE,
&cont);
}
/* Yes, subscription is suppressed.
* Terminate our subscription now.
*/
PJ_LOG(4,(THIS_FILE, "Xfer subscription suppressed, terminating "
"event subcription..."));
pjsip_evsub_terminate(sub, PJ_TRUE);
} else {
/* Notify application about call transfer progress.
* Initially notify with 100/Accepted status.
*/
if (call && pjsua_var.ua_cfg.cb.on_call_transfer_status) {
const pj_str_t ACCEPTED = { "Accepted", 8 };
pj_bool_t cont = PJ_FALSE;
(*pjsua_var.ua_cfg.cb.on_call_transfer_status)(call->index,
100,
&ACCEPTED,
PJ_FALSE,
&cont);
}
}
}
/*
* On incoming NOTIFY, notify application about call transfer progress.
*/
else if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_ACTIVE ||
pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED)
{
pjsua_call *call;
pjsip_msg *msg;
pjsip_msg_body *body;
pjsip_status_line status_line;
pj_bool_t is_last;
pj_bool_t cont;
pj_status_t status;
call = (pjsua_call*) pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
/* When subscription is terminated, clear the xfer_sub member of
* the inv_dat
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -