From 4d82c2e0f6790422e6e319ad80983e1e002fca85 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sun, 28 May 2006 14:58:12 +0000 Subject: More changes in pjsua API to make it more complete high level API git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@482 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/src/pjsua-lib/pjsua_call.c | 204 ++++++++- pjsip/src/pjsua-lib/pjsua_console_app.c | 276 ++++++----- pjsip/src/pjsua-lib/pjsua_core.c | 783 +++++++++++++++++++++++--------- pjsip/src/pjsua-lib/pjsua_im.c | 19 +- pjsip/src/pjsua-lib/pjsua_imp.h | 162 +++++++ pjsip/src/pjsua-lib/pjsua_pres.c | 169 +++++-- pjsip/src/pjsua-lib/pjsua_reg.c | 49 +- pjsip/src/pjsua-lib/pjsua_settings.c | 14 + 8 files changed, 1299 insertions(+), 377 deletions(-) (limited to 'pjsip/src/pjsua-lib') diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index d6013a13..70d9a088 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -124,14 +124,131 @@ static pj_status_t call_destroy_media(int call_index) } +/** + * Get maximum number of calls configured in pjsua. + */ +PJ_DEF(unsigned) pjsua_get_max_calls(void) +{ + return pjsua.config.max_calls; +} + + +/** + * Get current number of active calls. + */ +PJ_DEF(unsigned) pjsua_get_call_count(void) +{ + return pjsua.call_cnt; +} + + +/** + * Check if the specified call is active. + */ +PJ_DEF(pj_bool_t) pjsua_call_is_active(unsigned call_index) +{ + PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, + PJ_EINVAL); + return pjsua.calls[call_index].inv != NULL && + pjsua.calls[call_index].inv->state != PJSIP_INV_STATE_DISCONNECTED; +} + +/** + * Check if call has a media session. + */ +PJ_DEF(pj_bool_t) pjsua_call_has_media(unsigned call_index) +{ + PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, PJ_EINVAL); + return pjsua.calls[call_index].session != NULL; +} + + +/** + * Get call info. + */ +PJ_DEF(pj_status_t) pjsua_get_call_info( unsigned call_index, + pjsua_call_info *info) +{ + pjsua_call *call; + + PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, + PJ_EINVAL); + + pj_memset(info, 0, sizeof(pjsua_call_info)); + + call = &pjsua.calls[call_index]; + info->active = pjsua_call_is_active(call_index); + + if (call->inv == NULL) + return PJ_SUCCESS; + + info->index = call_index; + info->role = call->inv->role; + info->local_info = call->inv->dlg->local.info_str; + info->remote_info = call->inv->dlg->remote.info_str; + info->state = call->inv->state; + info->state_text = pj_str((char*)pjsip_inv_state_name(info->state)); + + if (info->state >= PJSIP_INV_STATE_DISCONNECTED) { + + info->total_duration = call->dis_time; + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + + if (call->conn_time.sec) { + info->connect_duration = call->dis_time; + PJ_TIME_VAL_SUB(info->total_duration, call->conn_time); + } + + } else if (info->state == PJSIP_INV_STATE_CONFIRMED) { + + pj_gettimeofday(&info->total_duration); + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + + pj_gettimeofday(&info->connect_duration); + PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time); + + } else { + pj_gettimeofday(&info->total_duration); + PJ_TIME_VAL_SUB(info->total_duration, call->start_time); + } + + info->cause = call->inv->cause; + info->cause_text = *pjsip_get_status_text(info->cause); + + info->has_media = (call->session != NULL); + info->conf_slot = call->conf_slot; + + return PJ_SUCCESS; +} + + +/** + * Duplicate call info. + */ +PJ_DEF(void) pjsua_dup_call_info( pj_pool_t *pool, + pjsua_call_info *dst_info, + const pjsua_call_info *src_info) +{ + PJ_ASSERT_ON_FAIL(pool && dst_info && src_info, return); + + pj_memcpy(dst_info, src_info, sizeof(pjsua_call_info)); + + pj_strdup(pool, &dst_info->local_info, &src_info->local_info); + pj_strdup(pool, &dst_info->remote_info, &src_info->remote_info); + + /* state_text and cause_text belong to pjsip, so don't need to be + * duplicated because they'll always be available. + */ +} + + /** * Make outgoing call. */ -PJ_DEF(pj_status_t) pjsua_make_call(int acc_index, - const char *cstr_dest_uri, +PJ_DEF(pj_status_t) pjsua_make_call(unsigned acc_index, + const pj_str_t *dest_uri, int *p_call_index) { - pj_str_t dest_uri; pjsip_dialog *dlg = NULL; pjmedia_sdp_session *offer; pjsip_inv_session *inv = NULL; @@ -139,9 +256,10 @@ PJ_DEF(pj_status_t) pjsua_make_call(int acc_index, pjsip_tx_data *tdata; pj_status_t status; - /* Convert cstr_dest_uri to dest_uri */ - - dest_uri = pj_str((char*)cstr_dest_uri); + + PJ_ASSERT_RETURN(acc_index==0 || acc_index < pjsua.config.acc_cnt, + PJ_EINVAL); + /* Find free call slot. */ for (call_index=0; call_indexdlg->mod_data[pjsua.mod.id]; @@ -673,6 +793,20 @@ static void on_call_transfered( pjsip_inv_session *inv, return; } + /* Notify callback */ + code = PJSIP_SC_OK; + if (pjsua.cb.on_call_transfered) + (*pjsua.cb.on_call_transfered)(existing_call->index, + &refer_to->hvalue, &code); + + if (code < 200) + code = 200; + if (code >= 300) { + /* Application rejects call transfer request */ + pjsip_dlg_respond( inv->dlg, rdata, code, NULL, NULL, NULL); + return; + } + PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s", (int)inv->dlg->remote.info_str.slen, inv->dlg->remote.info_str.ptr, @@ -692,7 +826,7 @@ static void on_call_transfered( pjsip_inv_session *inv, } /* Accept the REFER request, send 200 (OK). */ - pjsip_xfer_accept(sub, rdata, 200, NULL); + pjsip_xfer_accept(sub, rdata, code, NULL); /* Create initial NOTIFY request */ status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, @@ -718,7 +852,8 @@ static void on_call_transfered( pjsip_inv_session *inv, uri[refer_to->hvalue.slen] = '\0'; /* Now make the outgoing call. */ - status = pjsua_make_call(existing_call->acc_index, uri, &new_call); + tmp = pj_str(uri); + status = pjsua_make_call(existing_call->acc_index, &tmp, &new_call); if (status != PJ_SUCCESS) { /* Notify xferer about the error */ @@ -1083,7 +1218,7 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, call->inv->role == PJSIP_ROLE_UAS) { - pjmedia_conf_connect_port( pjsua.mconf, pjsua.wav_slot, + pjmedia_conf_connect_port( pjsua.mconf, pjsua.player[0].slot, call->conf_slot, 0); } @@ -1307,12 +1442,11 @@ PJ_DEF(void) pjsua_call_reinvite(int call_index) /* * Transfer call. */ -PJ_DEF(void) pjsua_call_xfer(int call_index, const char *dest) +PJ_DEF(void) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest) { pjsip_evsub *sub; pjsip_tx_data *tdata; pjsua_call *call; - pj_str_t tmp; pj_status_t status; @@ -1336,7 +1470,7 @@ PJ_DEF(void) pjsua_call_xfer(int call_index, const char *dest) /* * Create REFER request. */ - status = pjsip_xfer_initiate(sub, pj_cstr(&tmp, dest), &tdata); + status = pjsip_xfer_initiate(sub, dest, &tdata); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create REFER request", status); return; @@ -1356,15 +1490,33 @@ PJ_DEF(void) pjsua_call_xfer(int call_index, const char *dest) } +/** + * Dial DTMF. + */ +PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( unsigned call_index, + const pj_str_t *digits) +{ + pjsua_call *call = &pjsua.calls[call_index]; + + PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, PJ_EINVAL); + + if (!call->session) { + PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); + return -1; + } + + return pjmedia_session_dial_dtmf( call->session, 0, digits); +} + + /** * Send instant messaging inside INVITE session. */ -PJ_DECL(void) pjsua_call_send_im(int call_index, const char *str) +PJ_DECL(void) pjsua_call_send_im(int call_index, const pj_str_t *str) { pjsua_call *call; const pj_str_t mime_text = pj_str("text"); const pj_str_t mime_plain = pj_str("plain"); - pj_str_t text; pjsip_tx_data *tdata; pj_status_t status; @@ -1392,8 +1544,7 @@ PJ_DECL(void) pjsua_call_send_im(int call_index, const char *str) /* Create "text/plain" message body. */ tdata->msg->body = pjsip_msg_body_create( tdata->pool, &mime_text, - &mime_plain, - pj_cstr(&text, str)); + &mime_plain, str); if (tdata->msg->body == NULL) { pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM); pjsip_tx_data_dec_ref(tdata); @@ -1504,3 +1655,22 @@ pj_status_t pjsua_call_init(void) return status; } + +/** + * Replace media transport. + */ +PJ_DEF(pj_status_t) pjsua_set_call_media_transport( unsigned call_index, + const pjmedia_sock_info *i, + pjmedia_transport *tp) +{ + pjsua_call *call = &pjsua.calls[call_index]; + + if (i) + pj_memcpy(&call->skinfo, i, sizeof(pjmedia_sock_info)); + + if (call->med_tp) + (*call->med_tp->op->destroy)(call->med_tp); + + call->med_tp = tp; + return PJ_SUCCESS; +} diff --git a/pjsip/src/pjsua-lib/pjsua_console_app.c b/pjsip/src/pjsua-lib/pjsua_console_app.c index c2f94e62..62069b9a 100644 --- a/pjsip/src/pjsua-lib/pjsua_console_app.c +++ b/pjsip/src/pjsua-lib/pjsua_console_app.c @@ -34,17 +34,18 @@ static int current_call = -1; */ static pj_bool_t find_next_call(void) { - int i; + int i, max; - for (i=current_call+1; i<(int)pjsua.config.max_calls; ++i) { - if (pjsua.calls[i].inv != NULL) { + max = pjsua_get_max_calls(); + for (i=current_call+1; i=0; --i) { - if (pjsua.calls[i].inv != NULL) { + if (pjsua_call_is_active(i)) { current_call = i; return PJ_TRUE; } } - for (i=pjsua.config.max_calls-1; i>current_call; --i) { - if (pjsua.calls[i].inv != NULL) { + for (i=max-1; i>current_call; --i) { + if (pjsua_call_is_active(i)) { current_call = i; return PJ_TRUE; } @@ -87,19 +89,20 @@ static pj_bool_t find_prev_call(void) */ static void console_on_call_state(int call_index, pjsip_event *e) { - pjsua_call *call = &pjsua.calls[call_index]; + pjsua_call_info call_info; PJ_UNUSED_ARG(e); - if (call->inv->state == PJSIP_INV_STATE_DISCONNECTED) { + pjsua_get_call_info(call_index, &call_info); + + if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) { PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", call_index, - call->inv->cause, - pjsip_get_status_text(call->inv->cause)->ptr)); + call_info.cause, + call_info.cause_text.ptr)); - call->inv = NULL; - if ((int)call->index == current_call) { + if ((int)call_index == current_call) { find_next_call(); } @@ -107,10 +110,10 @@ static void console_on_call_state(int call_index, pjsip_event *e) PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", call_index, - pjsua_inv_state_names[call->inv->state])); + call_info.state_text.ptr)); - if (call && current_call==-1) - current_call = call->index; + if (current_call==-1) + current_call = call_index; } } @@ -126,6 +129,22 @@ static void console_on_reg_state(int acc_index) } +/** + * Notify UI on buddy state changed. + */ +static void console_on_buddy_state(int buddy_index) +{ + pjsua_buddy_info info; + pjsua_buddy_get_info(buddy_index, &info); + + PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s", + (int)info.uri.slen, + info.uri.ptr, + (int)info.status_text.slen, + info.status_text.ptr)); +} + + /** * Incoming IM message (i.e. MESSAGE request)! */ @@ -162,28 +181,28 @@ static void console_on_typing(int call_index, const pj_str_t *from, */ static void print_buddy_list(void) { - int i; + int i, count; puts("Buddy list:"); - if (pjsua.buddy_cnt == 0) + count = pjsua_get_buddy_count(); + + if (count == 0) puts(" -none-"); else { - for (i=0; i %s\n", - i+1, status, pjsua.buddies[i].uri.ptr); + if (pjsua_buddy_get_info(i, &info) != PJ_SUCCESS) + continue; + + if (!info.is_valid) + continue; + + printf(" [%2d] <%7s> %.*s\n", + i+1, info.status_text.ptr, + (int)info.uri.slen, + info.uri.ptr); } } puts(""); @@ -195,38 +214,28 @@ static void print_buddy_list(void) */ static void print_acc_status(int acc_index) { - char reg_status[128]; - - if (pjsua.acc[acc_index].regc == NULL) { - pj_ansi_strcpy(reg_status, " -not registered to server-"); - - } else if (pjsua.acc[acc_index].reg_last_err != PJ_SUCCESS) { - pj_strerror(pjsua.acc[acc_index].reg_last_err, reg_status, sizeof(reg_status)); - - } else if (pjsua.acc[acc_index].reg_last_code>=200 && - pjsua.acc[acc_index].reg_last_code<=699) { + char buf[80]; + pjsua_acc_info info; - pjsip_regc_info info; - const pj_str_t *status_str; + pjsua_acc_get_info(acc_index, &info); - pjsip_regc_get_info(pjsua.acc[acc_index].regc, &info); - - status_str = pjsip_get_status_text(pjsua.acc[acc_index].reg_last_code); - pj_ansi_snprintf(reg_status, sizeof(reg_status), - "%s (%.*s;expires=%d)", - status_str->ptr, - (int)info.client_uri.slen, - info.client_uri.ptr, - info.next_reg); + if (!info.has_registration) { + pj_ansi_strcpy(buf, " -not registered to server-"); } else { - pj_ansi_sprintf(reg_status, "in progress (%d)", - pjsua.acc[acc_index].reg_last_code); + pj_ansi_snprintf(buf, sizeof(buf), + "%.*s (%.*s;expires=%d)", + (int)info.status_text.slen, + info.status_text.ptr, + (int)info.acc_id.slen, + info.acc_id.ptr, + info.expires); + } - printf("[%2d] Registration status: %s\n", acc_index, reg_status); + printf("[%2d] Registration status: %s\n", acc_index, buf); printf(" Online status: %s\n", - (pjsua.acc[acc_index].online_status ? "Online" : "Invisible")); + (info.online_status ? "Online" : "Invisible")); } /* @@ -238,7 +247,7 @@ static void keystroke_help(void) printf(">>>>\n"); - for (i=0; i<(int)pjsua.config.acc_cnt; ++i) + for (i=0; i<(int)pjsua_get_acc_count(); ++i) print_acc_status(i); print_buddy_list(); @@ -311,7 +320,7 @@ static void ui_input_url(const char *title, char *buf, int len, " [1 -%2d] Select from buddy list\n" " URL An URL\n" " Empty input (or 'q') to cancel\n" - , pjsua.buddy_cnt, pjsua.buddy_cnt); + , pjsua_get_buddy_count(), pjsua_get_buddy_count()); printf("%s: ", title); fflush(stdout); @@ -349,7 +358,9 @@ static void ui_input_url(const char *title, char *buf, int len, result->nb_result = atoi(buf); - if (result->nb_result >= 0 && result->nb_result <= (int)pjsua.buddy_cnt) { + if (result->nb_result >= 0 && + result->nb_result <= (int)pjsua_get_buddy_count()) + { return; } if (result->nb_result == -1) @@ -379,7 +390,7 @@ static void conf_list(void) printf("Conference ports:\n"); count = PJ_ARRAY_SIZE(info); - pjmedia_conf_get_ports_info(pjsua.mconf, &count, info); + pjsua_conf_enum_ports(&count, info); for (i=0; iuri_to_call.slen) { + pjsua_make_call( current_acc, &pjsua_get_config()->uri_to_call, + NULL); } keystroke_help(); @@ -434,7 +449,7 @@ void pjsua_console_app_main(void) case 'm': /* Make call! : */ - printf("(You currently have %d calls)\n", pjsua.call_cnt); + printf("(You currently have %d calls)\n", pjsua_get_call_count()); uri = NULL; ui_input_url("Make call", buf, sizeof(buf), &result); @@ -444,19 +459,22 @@ void pjsua_console_app_main(void) puts("You can't do that with make call!"); continue; } else { - uri = pjsua.buddies[result.nb_result-1].uri.ptr; + pjsua_buddy_info binfo; + pjsua_buddy_get_info(result.nb_result-1, &binfo); + uri = binfo.uri.ptr; } } else if (result.uri_result) { uri = result.uri_result; } - pjsua_make_call( current_acc, uri, NULL); + tmp = pj_str(uri); + pjsua_make_call( current_acc, &tmp, NULL); break; case 'M': /* Make multiple calls! : */ - printf("(You currently have %d calls)\n", pjsua.call_cnt); + printf("(You currently have %d calls)\n", pjsua_get_call_count()); if (!simple_input("Number of calls", menuin, sizeof(menuin))) continue; @@ -467,19 +485,22 @@ void pjsua_console_app_main(void) ui_input_url("Make call", buf, sizeof(buf), &result); if (result.nb_result != NO_NB) { + pjsua_buddy_info binfo; if (result.nb_result == -1 || result.nb_result == 0) { puts("You can't do that with make call!"); continue; } - uri = pjsua.buddies[result.nb_result-1].uri.ptr; + pjsua_buddy_get_info(result.nb_result-1, &binfo); + uri = binfo.uri.ptr; } else { uri = result.uri_result; } for (i=0; irole != PJSIP_ROLE_UAS || - pjsua.calls[current_call].inv->state >= PJSIP_INV_STATE_CONNECTING) + call_info.active==0 || + call_info.role != PJSIP_ROLE_UAS || + call_info.state >= PJSIP_INV_STATE_CONNECTING) { puts("No pending incoming call"); fflush(stdout); @@ -608,19 +649,11 @@ void pjsua_console_app_main(void) } if (current_call != -1) { - char url[PJSIP_MAX_URL_SIZE]; - int len; - const pjsip_uri *u; - - u = pjsua.calls[current_call].inv->dlg->remote.info->uri; - len = pjsip_uri_print(0, u, url, sizeof(url)-1); - if (len < 1) { - pj_ansi_strcpy(url, ""); - } else { - url[len] = '\0'; - } - - PJ_LOG(3,(THIS_FILE,"Current dialog: %s", url)); + + pjsua_get_call_info(current_call, &call_info); + PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s", + (int)call_info.remote_info.slen, + call_info.remote_info.ptr)); } else { PJ_LOG(3,(THIS_FILE,"No current dialog")); @@ -676,12 +709,17 @@ void pjsua_console_app_main(void) if (result.nb_result != NO_NB) { if (result.nb_result == -1 || result.nb_result == 0) puts("You can't do that with transfer call!"); - else + else { + pjsua_buddy_info binfo; + pjsua_buddy_get_info(result.nb_result-1, &binfo); pjsua_call_xfer( current_call, - pjsua.buddies[result.nb_result-1].uri.ptr); + &binfo.uri); + } } else if (result.uri_result) { - pjsua_call_xfer( current_call, result.uri_result); + pj_str_t tmp; + tmp = pj_str(result.uri_result); + pjsua_call_xfer( current_call, &tmp); } } break; @@ -694,7 +732,7 @@ void pjsua_console_app_main(void) PJ_LOG(3,(THIS_FILE, "No current call")); - } else if (pjsua.calls[current_call].session == NULL) { + } else if (!pjsua_call_has_media(current_call)) { PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); @@ -715,8 +753,7 @@ void pjsua_console_app_main(void) } digits = pj_str(buf); - status = pjmedia_session_dial_dtmf(pjsua.calls[current_call].session, 0, - &digits); + status = pjsua_call_dial_dtmf(current_call, &digits); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to send DTMF", status); } else { @@ -733,14 +770,15 @@ void pjsua_console_app_main(void) ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result); if (result.nb_result != NO_NB) { if (result.nb_result == -1) { - int i; - for (i=0; iapp_log_level) pj_log_write(level, buffer, len); if (log_file) { @@ -1009,7 +1045,9 @@ void pjsua_perror(const char *sender, const char *title, pjsua_callback console_callback = { &console_on_call_state, + NULL, /* default accept transfer */ &console_on_reg_state, + &console_on_buddy_state, &console_on_pager, &console_on_typing, }; diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index dba563ce..3ec5947f 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -84,6 +84,7 @@ PJ_DEF(void) pjsua_default_config(pjsua_config *cfg) for (i=0; iacc_config[i].reg_timeout = 55; } + } @@ -525,8 +526,6 @@ static pj_status_t init_media(void) { int i; unsigned options; - unsigned clock_rate; - unsigned samples_per_frame; pj_str_t codec_id; pj_status_t status; @@ -603,7 +602,7 @@ static pj_status_t init_media(void) /* Init options for conference bridge. */ - options = 0; + options = PJMEDIA_CONF_NO_DEVICE; /* Calculate maximum number of ports, if it's not specified */ if (pjsua.config.conf_ports == 0) { @@ -611,13 +610,13 @@ static pj_status_t init_media(void) } /* Init conference bridge. */ - clock_rate = pjsua.config.clock_rate ? pjsua.config.clock_rate : 16000; - samples_per_frame = clock_rate * 10 / 1000; + pjsua.clock_rate = pjsua.config.clock_rate ? pjsua.config.clock_rate : 16000; + pjsua.samples_per_frame = pjsua.clock_rate * 10 / 1000; status = pjmedia_conf_create(pjsua.pool, pjsua.config.conf_ports, - clock_rate, + pjsua.clock_rate, 1, /* mono */ - samples_per_frame, + pjsua.samples_per_frame, 16, options, &pjsua.mconf); @@ -628,32 +627,58 @@ static pj_status_t init_media(void) return status; } - /* Create WAV file player if required: */ + if (pjsua.config.null_audio == PJ_FALSE) { + pjmedia_port *conf_port; - if (pjsua.config.wav_file.slen) { - pj_str_t port_name; + /* Create sound device port */ + status = pjmedia_snd_port_create(pjsua.pool, + pjsua.config.snd_capture_id, + pjsua.config.snd_player_id, + pjsua.clock_rate, 1 /* mono */, + pjsua.samples_per_frame, 16, + 0, &pjsua.snd_port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create sound device", status); + return status; + } - /* Create the file player port. */ - status = pjmedia_wav_player_port_create( pjsua.pool, - pjsua.config.wav_file.ptr, - 0, 0, -1, NULL, - &pjsua.file_port); + /* Get the port interface of the conference bridge */ + conf_port = pjmedia_conf_get_master_port(pjsua.mconf); + + /* Connect conference port interface to sound port */ + pjmedia_snd_port_connect( pjsua.snd_port, conf_port); + + } else { + pjmedia_port *null_port, *conf_port; + + /* Create NULL port */ + status = pjmedia_null_port_create(pjsua.pool, pjsua.clock_rate, + 1, pjsua.samples_per_frame, 16, + &null_port); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Error playing media file", - status); + pjsua_perror(THIS_FILE, "Unable to create NULL port", status); return status; } - /* Add port to conference bridge: */ - port_name = pjsua.config.wav_file; - status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, - pjsua.file_port, - &port_name, - &pjsua.wav_slot); + /* Get the port interface of the conference bridge */ + conf_port = pjmedia_conf_get_master_port(pjsua.mconf); + + /* Create master port to control conference bridge's clock */ + status = pjmedia_master_port_create(pjsua.pool, null_port, conf_port, + 0, &pjsua.master_port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create master port", status); + return status; + } + } + + /* Create WAV file player if required: */ + + if (pjsua.config.wav_file.slen) { + + status = pjsua_player_create(&pjsua.config.wav_file, NULL); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Unable to add file player to conference bridge", + pjsua_perror(THIS_FILE, "Unable to create file player", status); return status; } @@ -664,6 +689,33 @@ static pj_status_t init_media(void) } +/* + * Copy account configuration. + */ +static void copy_acc_config(pj_pool_t *pool, + pjsua_acc_config *dst_acc, + const pjsua_acc_config *src_acc) +{ + unsigned j; + + pj_strdup_with_null(pool, &dst_acc->id, &src_acc->id); + pj_strdup_with_null(pool, &dst_acc->reg_uri, &src_acc->reg_uri); + pj_strdup_with_null(pool, &dst_acc->contact, &src_acc->contact); + pj_strdup_with_null(pool, &dst_acc->proxy, &src_acc->proxy); + + for (j=0; jcred_count; ++j) { + pj_strdup_with_null(pool, &dst_acc->cred_info[j].realm, + &src_acc->cred_info[j].realm); + pj_strdup_with_null(pool, &dst_acc->cred_info[j].scheme, + &src_acc->cred_info[j].scheme); + pj_strdup_with_null(pool, &dst_acc->cred_info[j].username, + &src_acc->cred_info[j].username); + pj_strdup_with_null(pool, &dst_acc->cred_info[j].data, + &src_acc->cred_info[j].data); + } +} + + /* * Copy configuration. */ @@ -691,23 +743,7 @@ static void copy_config(pj_pool_t *pool, pjsua_config *dst, for (i=0; iacc_cnt; ++i) { pjsua_acc_config *dst_acc = &dst->acc_config[i]; const pjsua_acc_config *src_acc = &src->acc_config[i]; - unsigned j; - - pj_strdup_with_null(pool, &dst_acc->id, &src_acc->id); - pj_strdup_with_null(pool, &dst_acc->reg_uri, &src_acc->reg_uri); - pj_strdup_with_null(pool, &dst_acc->contact, &src_acc->contact); - pj_strdup_with_null(pool, &dst_acc->proxy, &src_acc->proxy); - - for (j=0; jcred_count; ++j) { - pj_strdup_with_null(pool, &dst_acc->cred_info[j].realm, - &src_acc->cred_info[j].realm); - pj_strdup_with_null(pool, &dst_acc->cred_info[j].scheme, - &src_acc->cred_info[j].scheme); - pj_strdup_with_null(pool, &dst_acc->cred_info[j].username, - &src_acc->cred_info[j].username); - pj_strdup_with_null(pool, &dst_acc->cred_info[j].data, - &src_acc->cred_info[j].data); - } + copy_acc_config(pool, dst_acc, src_acc); } pj_strdup_with_null(pool, &dst->log_filename, &src->log_filename); @@ -747,6 +783,11 @@ PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, pjsua.calls[i].conf_slot = 0; } + /* Init buddies array */ + for (i=0; isip_host.slen == 0 || cfg->sip_port == 0) { PJ_LOG(1,(THIS_FILE, "Error: sip_host and sip_port must be specified")); - return PJ_EINVAL; + status = PJ_EINVAL; + goto on_error; } pjsua.sip_sock = PJ_INVALID_SOCKET; @@ -795,7 +838,7 @@ PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, /* Init media endpoint */ status = init_media(); if (status != PJ_SUCCESS) - return status; + goto on_error; /* Init RTP sockets, only when UDP transport is enabled */ @@ -804,9 +847,10 @@ PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, if (status != PJ_SUCCESS) { unsigned j; for (j=0; jop->destroy(pjsua.calls[i].med_tp); } - return status; + goto on_error; } status = pjmedia_transport_udp_attach(pjsua.med_endpt, NULL, &pjsua.calls[i].skinfo, @@ -896,7 +940,7 @@ PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, return PJ_SUCCESS; on_error: - pj_caching_pool_destroy(&pjsua.cp); + pjsua_destroy(); return status; } @@ -963,6 +1007,144 @@ int pjsua_find_account_for_outgoing(const pj_str_t *url) } +/* + * Init account + */ +static pj_status_t init_acc(unsigned acc_index) +{ + pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index]; + pjsua_acc *acc = &pjsua.acc[acc_index]; + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + + /* Need to parse local_uri to get the elements: */ + + uri = pjsip_parse_uri(pjsua.pool, acc_cfg->id.ptr, + acc_cfg->id.slen, 0); + if (uri == NULL) { + pjsua_perror(THIS_FILE, "Invalid local URI", + PJSIP_EINVALIDURI); + return PJSIP_EINVALIDURI; + } + + /* Local URI MUST be a SIP or SIPS: */ + + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri)) + { + pjsua_perror(THIS_FILE, "Invalid local URI", + PJSIP_EINVALIDSCHEME); + return PJSIP_EINVALIDSCHEME; + } + + + /* Get the SIP URI object: */ + + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri); + + acc->user_part = sip_uri->user; + acc->host_part = sip_uri->host; + + /* Build Contact header */ + + if (acc_cfg->contact.slen == 0) { + char contact[128]; + const char *addr; + int port; + int len; + + addr = pjsua.config.sip_host.ptr; + port = pjsua.config.sip_port; + + /* The local Contact is the username@ip-addr, where + * - username is taken from the local URI, + * - ip-addr in UDP transport's address name (which may have been + * resolved from STUN. + */ + + /* Build temporary contact string. */ + + if (sip_uri->user.slen) { + + /* With the user part. */ + len = pj_ansi_snprintf(contact, sizeof(contact), + "", + (int)sip_uri->user.slen, + sip_uri->user.ptr, + addr, port); + } else { + + /* Without user part */ + + len = pj_ansi_snprintf(contact, sizeof(contact), + "", + addr, port); + } + + if (len < 1 || len >= sizeof(contact)) { + pjsua_perror(THIS_FILE, "Invalid Contact", PJSIP_EURITOOLONG); + return PJSIP_EURITOOLONG; + } + + /* Duplicate Contact uri. */ + + pj_strdup2(pjsua.pool, &acc_cfg->contact, contact); + + } + + + /* Build route-set for this account */ + if (pjsua.config.outbound_proxy.slen) { + pj_str_t hname = { "Route", 5}; + pjsip_route_hdr *r; + pj_str_t tmp; + + pj_strdup_with_null(pjsua.pool, &tmp, &pjsua.config.outbound_proxy); + r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL); + pj_list_push_back(&acc->route_set, r); + } + + if (acc_cfg->proxy.slen) { + pj_str_t hname = { "Route", 5}; + pjsip_route_hdr *r; + pj_str_t tmp; + + pj_strdup_with_null(pjsua.pool, &tmp, &acc_cfg->proxy); + r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL); + pj_list_push_back(&acc->route_set, r); + } + + return PJ_SUCCESS; +} + +/* + * Add a new account. + */ +PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg, + int *acc_index) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(pjsua.config.acc_cnt", pjsua.config.sip_host.ptr, pjsua.config.sip_port); + pj_strdup_with_null( pjsua.pool, &id, &tmp); - pj_strdup_with_null( pjsua.pool, &acc_cfg->id, &tmp); - acc_cfg->contact = acc_cfg->id; + for (i=pjsua.config.acc_cnt; iid = id; + acc_cfg->contact = id; + } } /* Initialize accounts: */ for (i=0; i<(int)pjsua.config.acc_cnt; ++i) { - - pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[i]; - pjsua_acc *acc = &pjsua.acc[i]; - pjsip_uri *uri; - pjsip_sip_uri *sip_uri; - - /* Need to parse local_uri to get the elements: */ - - uri = pjsip_parse_uri(pjsua.pool, acc_cfg->id.ptr, - acc_cfg->id.slen, 0); - if (uri == NULL) { - pjsua_perror(THIS_FILE, "Invalid local URI", - PJSIP_EINVALIDURI); - return PJSIP_EINVALIDURI; - } - - /* Local URI MUST be a SIP or SIPS: */ - - if (!PJSIP_URI_SCHEME_IS_SIP(uri) && - !PJSIP_URI_SCHEME_IS_SIPS(uri)) - { - pjsua_perror(THIS_FILE, "Invalid local URI", - PJSIP_EINVALIDSCHEME); - return PJSIP_EINVALIDSCHEME; - } - - - /* Get the SIP URI object: */ - - sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri); - - acc->user_part = sip_uri->user; - acc->host_part = sip_uri->host; - - /* Build Contact header */ - - if (acc_cfg->contact.slen == 0) { - char contact[128]; - const char *addr; - int port; - int len; - - addr = pjsua.config.sip_host.ptr; - port = pjsua.config.sip_port; - - /* The local Contact is the username@ip-addr, where - * - username is taken from the local URI, - * - ip-addr in UDP transport's address name (which may have been - * resolved from STUN. - */ - - /* Build temporary contact string. */ - - if (sip_uri->user.slen) { - - /* With the user part. */ - len = pj_ansi_snprintf(contact, sizeof(contact), - "", - (int)sip_uri->user.slen, - sip_uri->user.ptr, - addr, port); - } else { - - /* Without user part */ - - len = pj_ansi_snprintf(contact, sizeof(contact), - "", - addr, port); - } - - if (len < 1 || len >= sizeof(contact)) { - pjsua_perror(THIS_FILE, "Invalid Contact", PJSIP_EURITOOLONG); - return PJSIP_EURITOOLONG; - } - - /* Duplicate Contact uri. */ - - pj_strdup2(pjsua.pool, &acc_cfg->contact, contact); - - } - - - /* Build route-set for this account */ - if (pjsua.config.outbound_proxy.slen) { - pj_str_t hname = { "Route", 5}; - pjsip_route_hdr *r; - pj_str_t tmp; - - pj_strdup_with_null(pjsua.pool, &tmp, &pjsua.config.outbound_proxy); - r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL); - pj_list_push_back(&acc->route_set, r); - } - - if (acc_cfg->proxy.slen) { - pj_str_t hname = { "Route", 5}; - pjsip_route_hdr *r; - pj_str_t tmp; - - pj_strdup_with_null(pjsua.pool, &tmp, &acc_cfg->proxy); - r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL); - pj_list_push_back(&acc->route_set, r); + status = init_acc(i); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing account", status); + goto on_error; } } - /* Create worker thread(s), if required: */ for (i=0; i<(int)pjsua.config.thread_cnt; ++i) { @@ -1137,7 +1227,7 @@ PJ_DEF(pj_status_t) pjsua_start(void) pj_thread_join(pjsua.threads[i]); pj_thread_destroy(pjsua.threads[i]); } - return status; + goto on_error; } } @@ -1147,30 +1237,31 @@ PJ_DEF(pj_status_t) pjsua_start(void) for (i=0; i<(int)pjsua.config.acc_cnt; ++i) { status = pjsua_regc_init(i); if (status != PJ_SUCCESS) - return status; + goto on_error; /* Perform registration, if required. */ if (pjsua.acc[i].regc) { - pjsua_regc_update(i, 1); + pjsua_acc_set_registration(i, PJ_TRUE); } } - /* Init buddies */ - for (i=0; i<(int)pjsua.config.buddy_cnt; ++i) { - pjsua.buddies[i].uri = pjsua.config.buddy_uri[i]; + /* Re-init buddies */ + count = pjsua.config.buddy_cnt; + pjsua.config.buddy_cnt = 0; + for (i=0; i<(int)count; ++i) { + pj_str_t uri = pjsua.config.buddy_uri[i]; + pjsua_buddy_add(&uri, NULL); } - pjsua.buddy_cnt = pjsua.config.buddy_cnt; - /* Find account for outgoing preence subscription */ - for (i=0; i= PJ_ARRAY_SIZE(pjsua.player)) + return PJ_ETOOMANY; + + pj_memcpy(path, filename->ptr, filename->slen); + path[filename->slen] = '\0'; + status = pjmedia_wav_player_port_create(pjsua.pool, path, + pjsua.samples_per_frame * + 1000 / pjsua.clock_rate, + 0, 0, NULL, + &port); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, + port, filename, &slot); + if (status != PJ_SUCCESS) { + pjmedia_port_destroy(port); + return status; + } + + pjsua.player[pjsua.player_cnt].port = port; + pjsua.player[pjsua.player_cnt].slot = slot; + + if (*id) + *id = pjsua.player_cnt; + + ++pjsua.player_cnt; + + return PJ_SUCCESS; +} + + +/** + * Get conference port associated with player. + */ +PJ_DEF(unsigned) pjsua_player_get_conf_port(pjsua_player_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL); + return pjsua.player[id].slot; +} + + +/** + * Re-wind playback. + */ +PJ_DEF(pj_status_t) pjsua_player_set_pos(pjsua_player_id id, + pj_uint32_t samples) +{ + PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua.player[id].port != NULL, PJ_EINVALIDOP); + + return pjmedia_wav_player_port_set_pos(pjsua.player[id].port, samples); +} + + +/** + * Get conference port associated with player. + */ +PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL); + + if (pjsua.player[id].port) { + pjmedia_port_destroy(pjsua.player[id].port); + pjsua.player[id].port = NULL; + pjsua.player[id].slot = 0xFFFF; + pjsua.player_cnt--; + } + + return PJ_SUCCESS; +} + + +/** + * Create a file recorder. + */ +PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename, + pjsua_recorder_id *id) +{ + unsigned slot; + char path[128]; + pjmedia_port *port; + pj_status_t status; + + if (pjsua.recorder_cnt >= PJ_ARRAY_SIZE(pjsua.recorder)) + return PJ_ETOOMANY; + + pj_memcpy(path, filename->ptr, filename->slen); + path[filename->slen] = '\0'; + status = pjmedia_wav_writer_port_create(pjsua.pool, path, + pjsua.clock_rate, 1, + pjsua.samples_per_frame, + 16, 0, 0, NULL, + &port); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, + port, filename, &slot); + if (status != PJ_SUCCESS) { + pjmedia_port_destroy(port); + return status; + } + + pjsua.recorder[pjsua.recorder_cnt].port = port; + pjsua.recorder[pjsua.recorder_cnt].slot = slot; + + if (*id) + *id = pjsua.recorder_cnt; + + ++pjsua.recorder_cnt; + + return PJ_SUCCESS; +} + + +/** + * Get conference port associated with recorder. + */ +PJ_DEF(unsigned) pjsua_recorder_get_conf_port(pjsua_recorder_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.recorder), PJ_EINVAL); + return pjsua.recorder[id].slot; +} + + +/** + * Destroy recorder (will complete recording). + */ +PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id) +{ + PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.recorder), PJ_EINVAL); + + if (pjsua.recorder[id].port) { + pjmedia_port_destroy(pjsua.recorder[id].port); + pjsua.recorder[id].port = NULL; + pjsua.recorder[id].slot = 0xFFFF; + pjsua.recorder_cnt--; + } + + return PJ_SUCCESS; +} + +/** + * Enum sound devices. + */ +PJ_DEF(pj_status_t) pjsua_enum_snd_devices( unsigned *count, + pjmedia_snd_dev_info info[]) +{ + int i, dev_count; + + dev_count = pjmedia_snd_get_dev_count(); + if (dev_count > (int)*count) + dev_count = *count; + + for (i=0; iop->destroy)(pjsua.calls[i].med_tp); + pjsua.calls[i].med_tp = NULL; + } } /* Destroy media endpoint. */ + if (pjsua.med_endpt) { - pjmedia_endpt_destroy(pjsua.med_endpt); + /* Shutdown all codecs: */ +# if PJMEDIA_HAS_SPEEX_CODEC + pjmedia_codec_speex_deinit(); +# endif /* PJMEDIA_HAS_SPEEX_CODEC */ - /* Destroy endpoint. */ +# if PJMEDIA_HAS_GSM_CODEC + pjmedia_codec_gsm_deinit(); +# endif /* PJMEDIA_HAS_GSM_CODEC */ - pjsip_endpt_destroy(pjsua.endpt); - pjsua.endpt = NULL; +# if PJMEDIA_HAS_G711_CODEC + pjmedia_codec_g711_deinit(); +# endif /* PJMEDIA_HAS_G711_CODEC */ - /* Destroy caching pool. */ +# if PJMEDIA_HAS_L16_CODEC + pjmedia_codec_l16_deinit(); +# endif /* PJMEDIA_HAS_L16_CODEC */ + + + pjmedia_endpt_destroy(pjsua.med_endpt); + pjsua.med_endpt = NULL; + } + /* Destroy endpoint. */ + if (pjsua.endpt) { + pjsip_endpt_destroy(pjsua.endpt); + pjsua.endpt = NULL; + } + + /* Destroy caching pool. */ pj_caching_pool_destroy(&pjsua.cp); @@ -1278,3 +1634,22 @@ PJ_DEF(pj_status_t) pjsua_destroy(void) return PJ_SUCCESS; } + +/** + * Get SIP endpoint instance. + * Only valid after pjsua_init(). + */ +PJ_DEF(pjsip_endpoint*) pjsua_get_pjsip_endpt(void) +{ + return pjsua.endpt; +} + +/** + * Get media endpoint instance. + * Only valid after pjsua_init(). + */ +PJ_DEF(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void) +{ + return pjsua.med_endpt; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_im.c b/pjsip/src/pjsua-lib/pjsua_im.c index a5d96df4..efd8cbf6 100644 --- a/pjsip/src/pjsua-lib/pjsua_im.c +++ b/pjsip/src/pjsua-lib/pjsua_im.c @@ -18,6 +18,7 @@ */ #include #include +#include "pjsua_imp.h" /* * pjsua_im.c @@ -271,22 +272,21 @@ static void im_callback(void *token, pjsip_event *e) /** * Send IM outside dialog. */ -PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const char *dst_uri, - const char *str) +PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const pj_str_t *dst_uri, + const pj_str_t *str) { pjsip_tx_data *tdata; const pj_str_t STR_CONTACT = { "Contact", 7 }; const pj_str_t mime_text = pj_str("text"); const pj_str_t mime_plain = pj_str("plain"); pj_str_t *text; - const pj_str_t dst = pj_str((char*)dst_uri); pj_status_t status; /* Create request. */ status = pjsip_endpt_create_request(pjsua.endpt, &pjsip_message_method, - &dst, + dst_uri, &pjsua.config.acc_config[acc_index].id, - &dst, NULL, NULL, -1, NULL, &tdata); + dst_uri, NULL, NULL, -1, NULL, &tdata); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create request", status); return status; @@ -307,7 +307,7 @@ PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const char *dst_uri, * send the message. */ text = pj_pool_alloc(tdata->pool, sizeof(pj_str_t)); - pj_strdup2_with_null(tdata->pool, text, str); + pj_strdup_with_null(tdata->pool, text, str); /* Add message body */ tdata->msg->body = pjsip_msg_body_create( tdata->pool, &mime_text, @@ -333,18 +333,17 @@ PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const char *dst_uri, /** * Send typing indication outside dialog. */ -PJ_DEF(pj_status_t) pjsua_im_typing(int acc_index, const char *dst_uri, +PJ_DEF(pj_status_t) pjsua_im_typing(int acc_index, const pj_str_t *dst_uri, pj_bool_t is_typing) { - const pj_str_t dst = pj_str((char*)dst_uri); pjsip_tx_data *tdata; pj_status_t status; /* Create request. */ status = pjsip_endpt_create_request( pjsua.endpt, &pjsip_message_method, - &dst, + dst_uri, &pjsua.config.acc_config[acc_index].id, - &dst, NULL, NULL, -1, NULL, &tdata); + dst_uri, NULL, NULL, -1, NULL, &tdata); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create request", status); return status; diff --git a/pjsip/src/pjsua-lib/pjsua_imp.h b/pjsip/src/pjsua-lib/pjsua_imp.h index b406415f..208c999d 100644 --- a/pjsip/src/pjsua-lib/pjsua_imp.h +++ b/pjsip/src/pjsua-lib/pjsua_imp.h @@ -21,6 +21,168 @@ + + +/** + * Structure to be attached to invite dialog. + * Given a dialog "dlg", application can retrieve this structure + * by accessing dlg->mod_data[pjsua.mod.id]. + */ +struct pjsua_call +{ + unsigned index; /**< Index in pjsua array. */ + pjsip_inv_session *inv; /**< The invite session. */ + pj_time_val start_time;/**< First INVITE sent/received. */ + pj_time_val res_time; /**< First response sent/received. */ + pj_time_val conn_time; /**< Connected/confirmed time. */ + pj_time_val dis_time; /**< Disconnect time. */ + int acc_index; /**< Account index being used. */ + pjmedia_session *session; /**< The media session. */ + unsigned conf_slot; /**< Slot # in conference bridge. */ + pjsip_evsub *xfer_sub; /**< Xfer server subscription, if this + call was triggered by xfer. */ + pjmedia_sock_info skinfo; /**< Preallocated media sockets. */ + pjmedia_transport *med_tp; /**< Media transport. */ + void *app_data; /**< Application data. */ + pj_timer_entry refresh_tm;/**< Timer to send re-INVITE. */ + pj_timer_entry hangup_tm; /**< Timer to hangup call. */ +}; + +typedef struct pjsua_call pjsua_call; + + +/** + * Buddy data. + */ +struct pjsua_buddy +{ + unsigned index; /**< Buddy index. */ + pj_str_t name; /**< Buddy name. */ + pj_str_t display; /**< Buddy display name. */ + pj_str_t host; /**< Buddy host. */ + unsigned port; /**< Buddy port. */ + int acc_index; /**< Which account to use. */ + pj_bool_t monitor; /**< Should we monitor? */ + pjsip_evsub *sub; /**< Buddy presence subscription */ + pjsip_pres_status status; /**< Buddy presence status. */ +}; + +typedef struct pjsua_buddy pjsua_buddy; + + +/** + * Server presence subscription list head. + */ +struct pjsua_srv_pres +{ + PJ_DECL_LIST_MEMBER(struct pjsua_srv_pres); + pjsip_evsub *sub; + char *remote; +}; + +typedef struct pjsua_srv_pres pjsua_srv_pres; + + + +/** + * Account + */ +struct pjsua_acc +{ + int index; /**< Index in accounts array. */ + pj_str_t user_part; /**< User part of local URI. */ + pj_str_t host_part; /**< Host part of local URI. */ + + pjsip_regc *regc; /**< Client registration session. */ + pj_timer_entry reg_timer; /**< Registration timer. */ + pj_status_t reg_last_err; /**< Last registration error. */ + int reg_last_code; /**< Last status last register. */ + + pjsip_route_hdr route_set; /**< Route set. */ + + pj_bool_t online_status; /**< Our online status. */ + pjsua_srv_pres pres_srv_list; /**< Server subscription list. */ + + void *app_data; /**< Application data. */ +}; + + +/** + * @see pjsua_acc + */ +typedef struct pjsua_acc pjsua_acc; + + +/* PJSUA application variables. */ +struct pjsua +{ + /* Control: */ + pj_caching_pool cp; /**< Global pool factory. */ + pjsip_endpoint *endpt; /**< Global endpoint. */ + pj_pool_t *pool; /**< pjsua's private pool. */ + pjsip_module mod; /**< pjsua's PJSIP module. */ + + + /* Config: */ + pjsua_config config; /**< PJSUA configs */ + + /* Application callback + : */ + pjsua_callback cb; /**< Application callback. */ + + /* Media: */ + pjmedia_endpt *med_endpt; /**< Media endpoint. */ + unsigned clock_rate; /**< Conference bridge's clock rate.*/ + unsigned samples_per_frame; /**< Bridge's frame size. */ + pjmedia_conf *mconf; /**< Media conference. */ + + pjmedia_snd_port *snd_port; /**< Sound device port. */ + pjmedia_master_port *master_port; /**< Master port, when no snd dev */ + + unsigned player_cnt; /**< Number of file player. */ + + /** Array of file players */ + struct { + unsigned slot; /**< WAV player slot in bridge */ + pjmedia_port *port; /**< WAV player port. */ + } player[32]; + + unsigned recorder_cnt; /**< Number of file recorders. */ + + /** Array of file recorders */ + struct { + unsigned slot; /**< Slot # in conf bridge. */ + pjmedia_port *port; /**< The recorder media port. */ + } recorder[32]; + + /* Account: */ + pjsua_acc acc[PJSUA_MAX_ACC]; /** Client regs array. */ + + + /* Threading (optional): */ + pj_thread_t *threads[8]; /**< Thread instances. */ + pj_bool_t quit_flag; /**< To signal thread to quit. */ + + /* Transport (UDP): */ + pj_sock_t sip_sock; /**< SIP UDP socket. */ + pj_sockaddr_in sip_sock_name; /**< Public/STUN UDP socket addr. */ + + + /* PJSUA Calls: */ + unsigned call_cnt; /**< Number of calls. */ + pjsua_call calls[PJSUA_MAX_CALLS]; /** Calls array. */ + + + /* SIMPLE and buddy status: */ + pjsua_buddy buddies[PJSUA_MAX_BUDDIES]; +}; + + +/** PJSUA instance. */ +extern struct pjsua pjsua; + + + /** * Find account for incoming request. */ diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c index be31e2e1..fe6eedce 100644 --- a/pjsip/src/pjsua-lib/pjsua_pres.c +++ b/pjsip/src/pjsua-lib/pjsua_pres.c @@ -235,8 +235,9 @@ static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); if (buddy) { PJ_LOG(3,(THIS_FILE, - "Presence subscription to %s is %s", - buddy->uri.ptr, + "Presence subscription to %.*s is %s", + (int)pjsua.config.buddy_uri[buddy->index].slen, + pjsua.config.buddy_uri[buddy->index].ptr, pjsip_evsub_get_state_name(sub))); if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { @@ -244,6 +245,10 @@ static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) buddy->status.info_cnt = 0; pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); } + + /* Call callback */ + if (pjsua.cb.on_buddy_state) + (*pjsua.cb.on_buddy_state)(buddy->index); } } @@ -261,15 +266,6 @@ static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub, if (buddy) { /* Update our info. */ pjsip_pres_get_status(sub, &buddy->status); - - if (buddy->status.info_cnt) { - PJ_LOG(3,(THIS_FILE, "%s is %s", - buddy->uri.ptr, - (buddy->status.info[0].basic_open?"online":"offline"))); - } else { - PJ_LOG(3,(THIS_FILE, "No presence info for %s", - buddy->uri.ptr)); - } } /* The default is to send 200 response to NOTIFY. @@ -320,7 +316,7 @@ static void subscribe_buddy_presence(unsigned index) status = pjsip_dlg_create_uac( pjsip_ua_instance(), &acc_config->id, &acc_config->contact, - &pjsua.buddies[index].uri, + &pjsua.config.buddy_uri[index], NULL, &dlg); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create dialog", @@ -400,9 +396,9 @@ static void unsubscribe_buddy_presence(unsigned index) /* It does what it says.. */ static void refresh_client_subscription(void) { - int i; + unsigned i; - for (i=0; iindex = buddy->index; + info->is_valid = pjsua.config.buddy_uri[index].slen; + if (!info->is_valid) + return PJ_SUCCESS; + + info->name = buddy->name; + info->display_name = buddy->display; + info->host = buddy->host; + info->port = buddy->port; + info->uri = pjsua.config.buddy_uri[index]; + + if (buddy->sub == NULL || buddy->status.info_cnt==0) { + info->status = PJSUA_BUDDY_STATUS_UNKNOWN; + info->status_text = pj_str("?"); + } else if (pjsua.buddies[index].status.info[0].basic_open) { + info->status = PJSUA_BUDDY_STATUS_ONLINE; + info->status_text = pj_str("Online"); + } else { + info->status = PJSUA_BUDDY_STATUS_OFFLINE; + info->status_text = pj_str("Offline"); + } + + return PJ_SUCCESS; +} + + +/** + * Add new buddy. + */ +PJ_DEF(pj_status_t) pjsua_buddy_add( const pj_str_t *uri, + int *buddy_index) +{ + pjsip_name_addr *url; + pjsip_sip_uri *sip_uri; + int index; + pj_str_t tmp; + + PJ_ASSERT_RETURN(pjsua.config.buddy_cnt <= PJ_ARRAY_SIZE(pjsua.config.buddy_uri), + PJ_ETOOMANY); + + index = pjsua.config.buddy_cnt; + + /* Get name and display name for buddy */ + pj_strdup_with_null(pjsua.pool, &tmp, uri); + url = (pjsip_name_addr*)pjsip_parse_uri(pjsua.pool, tmp.ptr, tmp.slen, + PJSIP_PARSE_URI_AS_NAMEADDR); + + if (url == NULL) + return PJSIP_EINVALIDURI; + + /* Save URI */ + pjsua.config.buddy_uri[index] = tmp; + + sip_uri = (pjsip_sip_uri*) url->uri; + pjsua.buddies[index].name = sip_uri->user; + pjsua.buddies[index].display = url->display; + pjsua.buddies[index].host = sip_uri->host; + pjsua.buddies[index].port = sip_uri->port; + if (pjsua.buddies[index].port == 0) + pjsua.buddies[index].port = 5060; + + /* Find account for outgoing preence subscription */ + pjsua.buddies[index].acc_index = + pjsua_find_account_for_outgoing(&pjsua.config.buddy_uri[index]); + + if (buddy_index) + *buddy_index = index; + + pjsua.config.buddy_cnt++; + + return PJ_SUCCESS; +} + + + +PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( unsigned index, + pj_bool_t monitor) +{ + pjsua_buddy *buddy; + + PJ_ASSERT_RETURN(index < pjsua.config.buddy_cnt, PJ_EINVAL); + + buddy = &pjsua.buddies[index]; + buddy->monitor = monitor; + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjsua_acc_set_online_status( unsigned acc_index, + pj_bool_t is_online) +{ + PJ_ASSERT_RETURN(acc_index < pjsua.config.acc_cnt, PJ_EINVAL); + pjsua.acc[acc_index].online_status = is_online; + return PJ_SUCCESS; +} + + /* * Refresh presence */ @@ -446,14 +561,14 @@ PJ_DEF(void) pjsua_pres_refresh(int acc_index) */ void pjsua_pres_shutdown(void) { - int acc_index; - int i; + unsigned acc_index; + unsigned i; for (acc_index=0; acc_index<(int)pjsua.config.acc_cnt; ++acc_index) { pjsua.acc[acc_index].online_status = 0; } - for (i=0; i +#include "pjsua_imp.h" /* @@ -82,10 +83,56 @@ static void regc_cb(struct pjsip_regc_cbparam *param) } +/** + * Get number of accounts. + */ +PJ_DEF(unsigned) pjsua_get_acc_count(void) +{ + return pjsua.config.acc_cnt; +} + + +/** + * Get account info. + */ +PJ_DEF(pj_status_t) pjsua_acc_get_info( unsigned acc_index, + pjsua_acc_info *info) +{ + pjsua_acc *acc = &pjsua.acc[acc_index]; + pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index]; + + PJ_ASSERT_RETURN(acc_index < pjsua.config.acc_cnt, PJ_EINVAL); + + pj_memset(info, 0, sizeof(pjsua_acc_info)); + + info->index = acc_index; + info->acc_id = acc_cfg->id; + info->has_registration = (acc->regc != NULL); + info->online_status = acc->online_status; + + if (acc->reg_last_err) { + info->status = acc->reg_last_err; + pj_strerror(acc->reg_last_err, info->buf, sizeof(info->buf)); + info->status_text = pj_str(info->buf); + } else { + info->status = acc->reg_last_code; + info->status_text = *pjsip_get_status_text(acc->reg_last_code); + } + + if (acc->regc) { + pjsip_regc_info regc_info; + pjsip_regc_get_info(acc->regc, ®c_info); + info->expires = regc_info.next_reg; + } + + return PJ_SUCCESS; +} + + /* * Update registration. If renew is false, then unregistration will be performed. */ -PJ_DECL(void) pjsua_regc_update(int acc_index, pj_bool_t renew) +PJ_DECL(void) pjsua_acc_set_registration(unsigned acc_index, pj_bool_t renew) { pj_status_t status = 0; pjsip_tx_data *tdata = 0; diff --git a/pjsip/src/pjsua-lib/pjsua_settings.c b/pjsip/src/pjsua-lib/pjsua_settings.c index e1bdd34f..b0cd8558 100644 --- a/pjsip/src/pjsua-lib/pjsua_settings.c +++ b/pjsip/src/pjsua-lib/pjsua_settings.c @@ -18,6 +18,8 @@ */ #include #include +#include "pjsua_imp.h" + /* * pjsua_settings.c @@ -1007,6 +1009,9 @@ PJ_DEF(int) pjsua_dump_settings(const pjsua_config *config, PJ_UNUSED_ARG(max); + if (config == NULL) + config = &pjsua.config; + cfg.ptr = buf; cfg.slen = 0; @@ -1210,3 +1215,12 @@ PJ_DEF(pj_status_t) pjsua_save_settings(const char *filename, pj_pool_release(pool); return PJ_SUCCESS; } + +/** + * Get pjsua running config. + */ +PJ_DEF(const pjsua_config*) pjsua_get_config(void) +{ + return &pjsua.config; +} + -- cgit v1.2.3