From 1c2e7acc0844421cd4da9fa2032e36eb98d673f3 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sun, 26 Feb 2006 21:23:45 +0000 Subject: Major redesign in pjsua: call is indexed by number, multiple accounts, configurable max-calls, more auto-xxx features, fixed bugs in save_settings(), etc. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@236 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/build/pjsua.dsp | 4 +- pjsip/src/pjsua/main.c | 251 ++++++---- pjsip/src/pjsua/pjsua.h | 222 +++++---- pjsip/src/pjsua/pjsua_call.c | 1090 ++++++++++++++++++++++++++++++++++++++++++ pjsip/src/pjsua/pjsua_core.c | 322 ++++++++----- pjsip/src/pjsua/pjsua_inv.c | 1025 --------------------------------------- pjsip/src/pjsua/pjsua_opt.c | 316 +++++++++--- pjsip/src/pjsua/pjsua_pres.c | 81 ++-- pjsip/src/pjsua/pjsua_reg.c | 75 +-- 9 files changed, 1919 insertions(+), 1467 deletions(-) create mode 100644 pjsip/src/pjsua/pjsua_call.c delete mode 100644 pjsip/src/pjsua/pjsua_inv.c diff --git a/pjsip/build/pjsua.dsp b/pjsip/build/pjsua.dsp index 604c9939..4d808e7c 100644 --- a/pjsip/build/pjsua.dsp +++ b/pjsip/build/pjsua.dsp @@ -98,11 +98,11 @@ SOURCE=..\src\pjsua\main.c # End Source File # Begin Source File -SOURCE=..\src\pjsua\pjsua_core.c +SOURCE=..\src\pjsua\pjsua_call.c # End Source File # Begin Source File -SOURCE=..\src\pjsua\pjsua_inv.c +SOURCE=..\src\pjsua\pjsua_core.c # End Source File # Begin Source File diff --git a/pjsip/src/pjsua/main.c b/pjsip/src/pjsua/main.c index 7070c761..25ce6dcf 100644 --- a/pjsip/src/pjsua/main.c +++ b/pjsip/src/pjsua/main.c @@ -23,29 +23,86 @@ #define THIS_FILE "main.c" /* Current dialog */ -static struct pjsua_inv_data *inv_session; +static int current_acc; +static int current_call = -1; + + +/* + * Find next call. + */ +static pj_bool_t find_next_call(void) +{ + int i; + + for (i=current_call+1; i<(int)pjsua.max_calls; ++i) { + if (pjsua.calls[i].inv != NULL) { + current_call = i; + return PJ_TRUE; + } + } + + for (i=0; i=0; --i) { + if (pjsua.calls[i].inv != NULL) { + current_call = i; + return PJ_TRUE; + } + } + + for (i=pjsua.max_calls-1; i>current_call; --i) { + if (pjsua.calls[i].inv != NULL) { + current_call = i; + return PJ_TRUE; + } + } + + current_call = -1; + return PJ_FALSE; +} + + /* * Notify UI when invite state has changed. */ -void pjsua_ui_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) +void pjsua_ui_inv_on_state_changed(int call_index, pjsip_event *e) { + pjsua_call *call = &pjsua.calls[call_index]; + PJ_UNUSED_ARG(e); - PJ_LOG(3,(THIS_FILE, "INVITE session state changed to %s", - pjsua_inv_state_names[inv->state])); + PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", + call_index, + pjsua_inv_state_names[call->inv->state])); - if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { - if (inv == inv_session->inv) { - inv_session = inv_session->next; - if (inv_session == &pjsua.inv_list) - inv_session = pjsua.inv_list.next; + if (call->inv->state == PJSIP_INV_STATE_DISCONNECTED) { + call->inv = NULL; + if ((int)call->index == current_call) { + find_next_call(); } } else { - if (inv_session == &pjsua.inv_list || inv_session == NULL) - inv_session = inv->mod_data[pjsua.mod.id]; + if (call && current_call==-1) + current_call = call->index; } } @@ -66,10 +123,10 @@ void pjsua_ui_regc_on_state_changed(int code) */ static void print_buddy_list(void) { - unsigned i; + int i; puts("Buddy list:"); - //puts("-------------------------------------------------------------------------------"); + if (pjsua.buddy_cnt == 0) puts(" -none-"); else { @@ -93,37 +150,56 @@ static void print_buddy_list(void) puts(""); } + /* - * Show a bit of help. + * Print account status. */ -static void keystroke_help(void) +static void print_acc_status(int acc_index) { char reg_status[128]; - if (pjsua.regc == NULL) { + if (pjsua.acc[acc_index].regc == NULL) { pj_ansi_strcpy(reg_status, " -not registered to server-"); - } else if (pjsua.regc_last_err != PJ_SUCCESS) { - pj_strerror(pjsua.regc_last_err, reg_status, sizeof(reg_status)); - } else if (pjsua.regc_last_code>=200 && pjsua.regc_last_code<=699) { + + } 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) { pjsip_regc_info info; - pjsip_regc_get_info(pjsua.regc, &info); + pjsip_regc_get_info(pjsua.acc[acc_index].regc, &info); pj_snprintf(reg_status, sizeof(reg_status), "%s (%.*s;expires=%d)", - pjsip_get_status_text(pjsua.regc_last_code)->ptr, + pjsip_get_status_text(pjsua.acc[acc_index].reg_last_code)->ptr, (int)info.client_uri.slen, info.client_uri.ptr, info.next_reg); } else { - pj_sprintf(reg_status, "in progress (%d)", pjsua.regc_last_code); + pj_sprintf(reg_status, "in progress (%d)", + pjsua.acc[acc_index].reg_last_code); } - printf(">>>>\nRegistration status: %s\n", reg_status); - printf("Online status: %s\n", - (pjsua.online_status ? "Online" : "Invisible")); + printf("[%2d] Registration status: %s\n", acc_index, reg_status); + printf(" Online status: %s\n", + (pjsua.acc[acc_index].online_status ? "Online" : "Invisible")); +} + +/* + * Show a bit of help. + */ +static void keystroke_help(void) +{ + int i; + + printf(">>>>\n"); + + for (i=0; ilistener[j]) { pj_sprintf(s, "#%d ", j); @@ -309,26 +385,27 @@ static void ui_console_main(void) case 'm': /* Make call! : */ - if (pj_list_size(&pjsua.inv_list)) - printf("(You have %d calls)\n", pj_list_size(&pjsua.inv_list)); + printf("(You currently have %d calls)\n", pjsua.call_cnt); ui_input_url("Make call", buf, sizeof(buf), &result); if (result.nb_result != NO_NB) { if (result.nb_result == -1) puts("You can't do that with make call!"); else - pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, NULL); + pjsua_make_call( current_acc, + pjsua.buddies[result.nb_result].uri.ptr, + NULL); } else if (result.uri_result) - pjsua_invite(result.uri_result, NULL); + pjsua_make_call( current_acc, result.uri_result, NULL); break; case 'a': - if (inv_session == &pjsua.inv_list || - inv_session->inv->role != PJSIP_ROLE_UAS || - inv_session->inv->state >= PJSIP_INV_STATE_CONNECTING) + if (current_call == -1 || + pjsua.calls[current_call].inv->role != PJSIP_ROLE_UAS || + pjsua.calls[current_call].inv->state >= PJSIP_INV_STATE_CONNECTING) { puts("No pending incoming call"); fflush(stdout); @@ -349,16 +426,18 @@ static void ui_console_main(void) * Call may have been disconnected while we're waiting for * keyboard input. */ - if (inv_session == &pjsua.inv_list) { + if (current_call == -1) { puts("Call has been disconnected"); fflush(stdout); continue; } - status = pjsip_inv_answer(inv_session->inv, atoi(buf), + status = pjsip_inv_answer(pjsua.calls[current_call].inv, + atoi(buf), NULL, NULL, &tdata); if (status == PJ_SUCCESS) - status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL); + status = pjsip_inv_send_msg(pjsua.calls[current_call].inv, + tdata, NULL); if (status != PJ_SUCCESS) pjsua_perror(THIS_FILE, "Unable to create/send response", @@ -370,13 +449,13 @@ static void ui_console_main(void) case 'h': - if (inv_session == &pjsua.inv_list) { + if (current_call == -1) { puts("No current call"); fflush(stdout); continue; } else { - pjsua_inv_hangup(inv_session, PJSIP_SC_DECLINE); + pjsua_call_hangup(current_call, PJSIP_SC_DECLINE); } break; @@ -386,22 +465,19 @@ static void ui_console_main(void) * Cycle next/prev dialog. */ if (menuin[0] == ']') { - inv_session = inv_session->next; - if (inv_session == &pjsua.inv_list) - inv_session = pjsua.inv_list.next; + find_next_call(); } else { - inv_session = inv_session->prev; - if (inv_session == &pjsua.inv_list) - inv_session = pjsua.inv_list.prev; + find_prev_call(); } - if (inv_session != &pjsua.inv_list) { + if (current_call != -1) { char url[PJSIP_MAX_URL_SIZE]; int len; + const pjsip_uri *u; - len = pjsip_uri_print(0, inv_session->inv->dlg->remote.info->uri, - url, sizeof(url)-1); + 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 { @@ -419,9 +495,9 @@ static void ui_console_main(void) /* * Hold call. */ - if (inv_session != &pjsua.inv_list) { + if (current_call != -1) { - pjsua_inv_set_hold(inv_session); + pjsua_call_set_hold(current_call); } else { PJ_LOG(3,(THIS_FILE, "No current call")); @@ -432,9 +508,9 @@ static void ui_console_main(void) /* * Send re-INVITE (to release hold, etc). */ - if (inv_session != &pjsua.inv_list) { + if (current_call != -1) { - pjsua_inv_reinvite(inv_session); + pjsua_call_reinvite(current_call); } else { PJ_LOG(3,(THIS_FILE, "No current call")); @@ -445,18 +521,18 @@ static void ui_console_main(void) /* * Transfer call. */ - if (inv_session == &pjsua.inv_list) { + if (current_call == -1) { PJ_LOG(3,(THIS_FILE, "No current call")); } else { - struct pjsua_inv_data *cur = inv_session; + int call = current_call; ui_input_url("Transfer to URL", buf, sizeof(buf), &result); /* Check if call is still there. */ - if (cur != inv_session) { + if (call != current_call) { puts("Call has been disconnected"); continue; } @@ -465,11 +541,11 @@ static void ui_console_main(void) if (result.nb_result == -1) puts("You can't do that with transfer call!"); else - pjsua_inv_xfer_call( inv_session, - pjsua.buddies[result.nb_result].uri.ptr); + pjsua_call_xfer( current_call, + pjsua.buddies[result.nb_result].uri.ptr); } else if (result.uri_result) { - pjsua_inv_xfer_call( inv_session, result.uri_result); + pjsua_call_xfer( current_call, result.uri_result); } } break; @@ -478,17 +554,17 @@ static void ui_console_main(void) /* * Send DTMF strings. */ - if (inv_session == &pjsua.inv_list) { + if (current_call == -1) { PJ_LOG(3,(THIS_FILE, "No current call")); - } else if (inv_session->session == NULL) { + } else if (pjsua.calls[current_call].session == NULL) { PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); } else { pj_str_t digits; - struct pjsua_inv_data *cur = inv_session; + int call = current_call; pj_status_t status; if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, @@ -497,13 +573,13 @@ static void ui_console_main(void) break; } - if (cur != inv_session) { + if (call != current_call) { puts("Call has been disconnected"); continue; } digits = pj_str(buf); - status = pjmedia_session_dial_dtmf(inv_session->session, 0, + status = pjmedia_session_dial_dtmf(pjsua.calls[current_call].session, 0, &digits); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to send DTMF", status); @@ -521,14 +597,14 @@ static void ui_console_main(void) ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result); if (result.nb_result != NO_NB) { if (result.nb_result == -1) { - unsigned i; + int i; for (i=0; imod_data[pjsua.mod.id]. */ -struct pjsua_inv_data +struct pjsua_call { - PJ_DECL_LIST_MEMBER(struct pjsua_inv_data); - + unsigned index; /**< Index in pjsua array. */ pjsip_inv_session *inv; /**< The invite session. */ + int acc_index; /**< Account index being used. */ pjmedia_session *session; /**< The media session. */ unsigned conf_slot; /**< Slot # in conference bridge. */ - unsigned call_slot; /**< RTP media index in med_sock_use[] */ pjsip_evsub *xfer_sub; /**< Xfer server subscription, if this call was triggered by xfer. */ + pjmedia_sock_info skinfo; /**< Preallocated media sockets. */ + + void *app_data; /**< Application data. */ }; +typedef struct pjsua_call pjsua_call; + /** * Buddy data. @@ -79,6 +111,7 @@ struct pjsua_inv_data struct pjsua_buddy { pj_str_t uri; /**< Buddy URI */ + 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. */ @@ -100,12 +133,41 @@ struct pjsua_srv_pres typedef struct pjsua_srv_pres pjsua_srv_pres; +/** + * Account + */ +struct pjsua_acc +{ + int index; /**< Index in accounts array. */ + pj_str_t local_uri; /**< Uri in From: header. */ + pj_str_t user_part; /**< User part of local URI. */ + pj_str_t host_part; /**< Host part of local URI. */ + pj_str_t contact_uri; /**< Uri in Contact: header. */ + + pj_str_t reg_uri; /**< Registrar URI. */ + pjsip_regc *regc; /**< Client registration session. */ + pj_int32_t reg_timeout; /**< Default timeout. */ + pj_timer_entry reg_timer; /**< Registration timer. */ + pj_status_t reg_last_err; /**< Last registration error. */ + int reg_last_code; /**< Last status last register. */ + + pj_str_t proxy; /**< Proxy URL. */ + 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. */ +}; + + +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. */ @@ -113,95 +175,66 @@ struct pjsua /* Media: */ - pjmedia_endpt *med_endpt; /**< Media endpoint. */ - unsigned max_ports; /**< Max ports in conf. */ pjmedia_conf *mconf; /**< Media conference. */ pj_bool_t null_audio; /**< Null audio flag. */ char *wav_file; /**< WAV file name to play. */ unsigned wav_slot; /**< WAV player slot in bridge */ + pj_bool_t auto_play; /**< Auto play file for calls? */ + pj_bool_t auto_loop; /**< Auto loop RTP stream? */ + pj_bool_t auto_conf; /**< Auto put to conference? */ - /* User Agent behaviour: */ + /* User Agent behaviour: */ int auto_answer; /**< Automatically answer in calls. */ - - /* Since we support simultaneous calls, we need to have multiple - * RTP sockets. - */ - pjmedia_sock_info med_sock_info[PJSUA_MAX_CALLS]; - pj_bool_t med_sock_use[PJSUA_MAX_CALLS]; - - /* User info: */ - - pj_str_t local_uri; /**< Uri in From: header. */ - pj_str_t contact_uri; /**< Uri in Contact: header. */ - - /* Proxy URLs: */ - - pj_str_t proxy; - pj_str_t outbound_proxy; - pjsip_route_hdr route_set; - - - /* Registration: */ - - pj_str_t registrar_uri; - pjsip_regc *regc; - pj_int32_t reg_timeout; - pj_timer_entry regc_timer; - pj_status_t regc_last_err; /**< Last registration error. */ - int regc_last_code;/**< Last status last register. */ + /* Account: */ + int acc_cnt; /**< Number of client registrations */ + pjsua_acc acc[PJSUA_MAX_ACC]; /** Client regs array. */ /* Authentication credentials: */ - unsigned cred_count; - pjsip_cred_info cred_info[4]; + int cred_count; /**< Number of credentials. */ + pjsip_cred_info cred_info[10]; /**< Array of credentials. */ /* Threading (optional): */ - int thread_cnt; /**< Thread count. */ pj_thread_t *threads[8]; /**< Thread instances. */ pj_bool_t quit_flag; /**< To signal thread to quit. */ /* Transport (UDP): */ - pj_uint16_t sip_port; /**< SIP signaling port. */ pj_sock_t sip_sock; /**< SIP UDP socket. */ pj_sockaddr_in sip_sock_name; /**< Public/STUN UDP socket addr. */ + pj_str_t outbound_proxy;/**< Outbound proxy. */ /* STUN: */ - pj_str_t stun_srv1; int stun_port1; pj_str_t stun_srv2; int stun_port2; - /* Logging: */ - + /* Logging: */ int log_level; /**< Logging verbosity. */ int app_log_level; /**< stdout log verbosity. */ unsigned log_decor; /**< Log decoration. */ char *log_filename; /**< Log filename. */ - /* List of invite sessions: */ - - struct pjsua_inv_data inv_list; + /* PJSUA Calls: */ + int max_calls; /**< Max nb of calls. */ + int call_cnt; /**< Number of calls. */ + pjsua_call calls[PJSUA_MAX_CALLS]; /** Calls array. */ /* SIMPLE and buddy status: */ - - pj_bool_t online_status; /**< Out online status. */ - pjsua_srv_pres pres_srv_list; /**< Server subscription list. */ - - unsigned buddy_cnt; - pjsua_buddy buddies[PJSUA_MAX_BUDDIES]; + int buddy_cnt; + pjsua_buddy buddies[PJSUA_MAX_BUDDIES]; }; @@ -255,81 +288,69 @@ pj_status_t pjsua_start(void); pj_status_t pjsua_destroy(void); -/***************************************************************************** - * PJSUA Invite session API (defined in pjsua_inv.c). - */ - /** - * Make outgoing call. + * Find account for incoming request. */ -pj_status_t pjsua_invite(const char *cstr_dest_uri, - struct pjsua_inv_data **p_inv_data); +int pjsua_find_account_for_incoming(pjsip_rx_data *rdata); /** - * Handle incoming invite request. + * Find account for outgoing request. */ -pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata); +int pjsua_find_account_for_outgoing(const pj_str_t *url); -/** - * Hangup call. +/***************************************************************************** + * PJSUA Call API (defined in pjsua_call.c). */ -void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code); - /** - * Put call on-hold. + * Init pjsua call module. */ -void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session); - +pj_status_t pjsua_call_init(void); /** - * Send re-INVITE (to release hold). + * Make outgoing call. */ -void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session); +pj_status_t pjsua_make_call(int acc_index, + const char *cstr_dest_uri, + int *p_call_index); /** - * Transfer call. + * Handle incoming invite request. */ -void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session, - const char *dest); +pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata); /** - * Callback to be called by session when invite session's state has changed. + * Answer call. */ -void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e); - +void pjsua_call_answer(int call_index, int code); /** - * Callback to be called by session when outgoing dialog has forked. - * This function will create a forked dialog. + * Hangup call. */ -void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e); +void pjsua_call_hangup(int call_index, int code); /** - * 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. + * Put call on-hold. */ -void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status); +void pjsua_call_set_hold(int call_index); + /** - * Callback called when invite session received new offer. + * Send re-INVITE (to release hold). */ -void pjsua_inv_on_rx_offer( pjsip_inv_session *inv, - const pjmedia_sdp_session *offer); +void pjsua_call_reinvite(int call_index); + /** - * Callback to receive transaction state inside invite session or dialog - * (e.g. REFER, MESSAGE). + * Transfer call. */ -void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv, - pjsip_transaction *tsx, - pjsip_event *e); +void pjsua_call_xfer(int call_index, const char *dest); + /** * Terminate all calls. @@ -346,13 +367,13 @@ void pjsua_inv_shutdown(void); * * @param app_callback Optional callback */ -pj_status_t pjsua_regc_init(void); +pj_status_t pjsua_regc_init(int acc_index); /** * Update registration or perform unregistration. If renew argument is zero, * this will start unregistration process. */ -void pjsua_regc_update(pj_bool_t renew); +void pjsua_regc_update(int acc_index, pj_bool_t renew); @@ -369,7 +390,7 @@ pj_status_t pjsua_pres_init(); /** * Refresh both presence client and server subscriptions. */ -void pjsua_pres_refresh(void); +void pjsua_pres_refresh(int acc_index); /** * Terminate all subscriptions @@ -392,12 +413,12 @@ void pjsua_pres_dump(void); /** * Notify UI when invite state has changed. */ -void pjsua_ui_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e); +void pjsua_ui_inv_on_state_changed(int call_index, pjsip_event *e); /** * Notify UI when registration status has changed. */ -void pjsua_ui_regc_on_state_changed(int code); +void pjsua_ui_regc_on_state_changed(int acc_index); /***************************************************************************** @@ -418,6 +439,11 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]); */ pj_status_t pjsua_load_settings(const char *filename); +/** + * Dump settings. + */ +int pjsua_dump_settings(char *buf, pj_size_t max); + /** * Save settings to a file. */ diff --git a/pjsip/src/pjsua/pjsua_call.c b/pjsip/src/pjsua/pjsua_call.c new file mode 100644 index 00000000..e0a9a0af --- /dev/null +++ b/pjsip/src/pjsua/pjsua_call.c @@ -0,0 +1,1090 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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.h" +#include + + +/* + * pjsua_inv.c + * + * Invite session specific functionalities. + */ + +#define THIS_FILE "pjsua_inv.c" + + +/** + * Make outgoing call. + */ +pj_status_t pjsua_make_call(int acc_index, + const char *cstr_dest_uri, + int *p_call_index) +{ + pj_str_t dest_uri; + pjsip_dialog *dlg; + pjmedia_sdp_session *offer; + pjsip_inv_session *inv; + int call_index = -1; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Convert cstr_dest_uri to dest_uri */ + + dest_uri = pj_str((char*)cstr_dest_uri); + + /* Find free call slot. */ + for (call_index=0; call_indexpool, 1, + &pjsua.calls[call_index].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. */ + + pjsua.calls[call_index].inv = inv; + + dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + + + /* Set dialog Route-Set: */ + + if (!pj_list_empty(&pjsua.acc[acc_index].route_set)) + pjsip_dlg_set_route_set(dlg, &pjsua.acc[acc_index].route_set); + + + /* Set credentials: */ + + pjsip_auth_clt_set_credentials( &dlg->auth_sess, pjsua.cred_count, + pjsua.cred_info); + + + /* 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; + } + + + /* Send initial INVITE: */ + + status = pjsip_inv_send_msg(inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send initial INVITE request", + status); + goto on_error; + } + + + /* Done. */ + + ++pjsua.call_cnt; + + if (p_call_index) + *p_call_index = call_index; + + return PJ_SUCCESS; + + +on_error: + PJ_TODO(DESTROY_DIALOG_ON_FAIL); + if (call_index != -1) { + pjsua.calls[call_index].inv = NULL; + } + return status; +} + + +/** + * Handle incoming INVITE request. + */ +pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) +{ + pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); + 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; + int acc_index; + int call_index = -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; + + + /* Verify that we can handle the request. */ + status = pjsip_inv_verify_request(rdata, &options, NULL, NULL, + pjsua.endpt, &response); + if (status != PJ_SUCCESS) { + + /* + * No we can't handle the incoming INVITE request. + */ + + if (response) { + pjsip_response_addr res_addr; + + pjsip_get_response_addr(response->pool, rdata, &res_addr); + pjsip_endpt_send_response(pjsua.endpt, &res_addr, response, + NULL, NULL); + + } else { + + /* Respond with 500 (Internal Server Error) */ + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + NULL, NULL); + } + + return PJ_TRUE; + } + + + /* + * Yes we can handle the incoming INVITE request. + */ + + /* Find free call slot. */ + for (call_index=0; call_index < pjsua.max_calls; ++call_index) { + if (pjsua.calls[call_index].inv == NULL) + break; + } + + if (call_index == PJSUA_MAX_CALLS) { + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, + PJSIP_SC_BUSY_HERE, NULL, + NULL, NULL); + return PJ_TRUE; + } + + + /* Get media capability from media endpoint: */ + + status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool, 1, + &pjsua.calls[call_index].skinfo, + &answer ); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + NULL, NULL); + + return PJ_TRUE; + } + + /* TODO: + * + * Get which account is most likely to be associated with this incoming + * call. We need the account to find which contact URI to put for + * the call. + */ + acc_index = 0; + + /* Create dialog: */ + + status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, + &pjsua.acc[acc_index].contact_uri, + &dlg); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + NULL, NULL); + + return PJ_TRUE; + } + + + /* Create invite session: */ + + status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv); + if (status != PJ_SUCCESS) { + + pjsip_dlg_respond(dlg, rdata, 500, NULL); + + // TODO: Need to delete dialog + return PJ_TRUE; + } + + + /* Create and attach pjsua data to the dialog: */ + + pjsua.calls[call_index].inv = inv; + + dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + + + /* Must answer with some response to initial INVITE. + * If auto-answer flag is set, send 200 straight away, otherwise send 100. + */ + + status = pjsip_inv_initial_answer(inv, rdata, + (pjsua.auto_answer ? 200 : 100), + NULL, NULL, &response); + if (status != PJ_SUCCESS) { + + pjsua_perror(THIS_FILE, "Unable to create 100 response", status); + + pjsip_dlg_respond(dlg, rdata, 500, NULL); + + // TODO: Need to delete dialog + + } else { + status = pjsip_inv_send_msg(inv, response, NULL); + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Unable to send 100 response", status); + } + + if (pjsua.auto_answer < 200) { + PJ_LOG(3,(THIS_FILE, + "\nIncoming call!!\n" + "From: %.*s\n" + "To: %.*s\n" + "(press 'a' to answer, 'h' to decline)", + (int)dlg->remote.info_str.slen, + dlg->remote.info_str.ptr, + (int)dlg->local.info_str.slen, + dlg->local.info_str.ptr)); + } else { + PJ_LOG(3,(THIS_FILE, + "Call From:%.*s To:%.*s was answered with %d (%s)", + (int)dlg->remote.info_str.slen, + dlg->remote.info_str.ptr, + (int)dlg->local.info_str.slen, + dlg->local.info_str.ptr, + pjsua.auto_answer, + pjsip_get_status_text(pjsua.auto_answer)->ptr )); + } + + ++pjsua.call_cnt; + + /* This INVITE request has been handled. */ + return PJ_TRUE; +} + + +/* + * 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 = inv->dlg->mod_data[pjsua.mod.id]; + + /* If this is an outgoing INVITE that was created because of + * REFER/transfer, send NOTIFY to transferer. + */ + if (call && 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; + } + + 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); + } + } + } + } + + + pjsua_ui_inv_on_state_changed(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 && call->session) { + pjmedia_conf_remove_port(pjsua.mconf, call->conf_slot); + pjmedia_session_destroy(call->session); + call->session = NULL; + + PJ_LOG(3,(THIS_FILE,"Media session is destroyed")); + } + + call->inv = NULL; + --pjsua.call_cnt; + } +} + + +/* + * Callback called by event framework when the xfer subscription state + * has changed. + */ +static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + + PJ_UNUSED_ARG(event); + + /* + * We're only interested when subscription is terminated, to + * clear the xfer_sub member of the inv_data. + */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsua_call *call; + + call = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); + if (!call) + return; + + pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); + call->xfer_sub = NULL; + + PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated")); + } +} + + +/* + * Follow transfer (REFER) request. + */ +static void on_call_transfered( pjsip_inv_session *inv, + pjsip_rx_data *rdata ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + pjsua_call *existing_call; + int new_call; + const pj_str_t str_refer_to = { "Refer-To", 8}; + pjsip_generic_string_hdr *refer_to; + char *uri; + struct pjsip_evsub_user xfer_cb; + pjsip_evsub *sub; + + existing_call = inv->dlg->mod_data[pjsua.mod.id]; + + /* Find the Refer-To header */ + refer_to = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL); + + if (refer_to == NULL) { + /* Invalid Request. + * No Refer-To header! + */ + PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!")); + pjsip_dlg_respond( inv->dlg, rdata, 400, 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, + (int)refer_to->hvalue.slen, + refer_to->hvalue.ptr)); + + /* Init callback */ + pj_memset(&xfer_cb, 0, sizeof(xfer_cb)); + xfer_cb.on_evsub_state = &xfer_on_evsub_state; + + /* Create transferee event subscription */ + status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create xfer uas", status); + pjsip_dlg_respond( inv->dlg, rdata, 500, NULL); + return; + } + + /* Accept the REFER request, send 200 (OK). */ + pjsip_xfer_accept(sub, rdata, 200, NULL); + + /* Create initial NOTIFY request */ + status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, + 100, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status); + return; + } + + /* Send initial NOTIFY request */ + status = pjsip_xfer_send_request( sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status); + return; + } + + /* We're cheating here. + * We need to get a null terminated string from a pj_str_t. + * So grab the pointer from the hvalue and NULL terminate it, knowing + * that the NULL position will be occupied by a newline. + */ + uri = refer_to->hvalue.ptr; + uri[refer_to->hvalue.slen] = '\0'; + + /* Now make the outgoing call. */ + status = pjsua_make_call(existing_call->acc_index, uri, &new_call); + if (status != PJ_SUCCESS) { + + /* Notify xferer about the error */ + status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + 500, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", + status); + return; + } + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", + status); + return; + } + return; + } + + /* Put the server subscription in inv_data. + * Subsequent state changed in pjsua_inv_on_state_changed() will be + * reported back to the server subscription. + */ + pjsua.calls[new_call].xfer_sub = sub; + + /* Put the invite_data in the subscription. */ + pjsip_evsub_set_mod_data(sub, pjsua.mod.id, &pjsua.calls[new_call]); +} + + +/* + * This callback is called when transaction state has changed in INVITE + * session. We use this to trap incoming REFER request. + */ +static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_event *e) +{ + pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id]; + + if (tsx->role==PJSIP_ROLE_UAS && + tsx->state==PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0) + { + /* + * Incoming REFER request. + */ + on_call_transfered(call->inv, e->body.tsx_state.src.rdata); + } +} + + +/* + * 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); +} + + +/* + * 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_sdp_session *sdp; + + /* Create new offer */ + status = pjmedia_endpt_create_sdp(pjsua.med_endpt, pjsua.pool, 1, + &call->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.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) +{ + pjsua_call *call; + pjmedia_sdp_conn *conn; + pjmedia_sdp_session *answer; + pj_bool_t is_remote_active; + pj_status_t status; + + call = inv->dlg->mod_data[pjsua.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; + } + + PJ_LOG(4,(THIS_FILE, "Received SDP offer, remote media is %s", + (is_remote_active ? "active" : "inactive"))); + + /* Supply candidate answer */ + if (is_remote_active) { + status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1, + &call->skinfo, &answer); + } else { + status = create_inactive_sdp( call, &answer ); + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + return; + } + + status = pjsip_inv_set_sdp_answer(call->inv, answer); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to set answer", status); + return; + } + +} + + +/* + * 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; + pjmedia_port *media_port; + pj_str_t port_name; + char tmp[PJSIP_MAX_URL_SIZE]; + + call = inv->dlg->mod_data[pjsua.mod.id]; + + if (status != PJ_SUCCESS) { + + pjsua_perror(THIS_FILE, "SDP negotiation has failed", status); + return; + + } + + /* Destroy existing media session, if any. */ + + if (call && call->session) { + pjmedia_conf_remove_port(pjsua.mconf, call->conf_slot); + pjmedia_session_destroy(call->session); + call->session = NULL; + } + + /* 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); + 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); + return; + } + + /* Create new media session. + * The media session is active immediately. + */ + if (pjsua.null_audio) + return; + + status = pjmedia_session_create( pjsua.med_endpt, 1, + &call->skinfo, + local_sdp, remote_sdp, + call, + &call->session ); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create media session", + status); + return; + } + + + /* Get the port interface of the first stream in the session. + * We need the port interface to add to the conference bridge. + */ + pjmedia_session_get_port(call->session, 0, &media_port); + + + /* + * Add the call to conference bridge. + */ + port_name.ptr = tmp; + port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + call->inv->dlg->remote.info->uri, + tmp, sizeof(tmp)); + if (port_name.slen < 1) { + port_name = pj_str("call"); + } + status = pjmedia_conf_add_port( pjsua.mconf, call->inv->pool, + media_port, + &port_name, + &call->conf_slot); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create conference slot", + status); + pjmedia_session_destroy(call->session); + call->session = NULL; + return; + } + + /* If auto-play is configured, connect the call to the file player + * port + */ + if (pjsua.auto_play && pjsua.wav_file && + call->inv->role == PJSIP_ROLE_UAS) + { + + pjmedia_conf_connect_port( pjsua.mconf, pjsua.wav_slot, + call->conf_slot); + + } else if (pjsua.auto_loop && call->inv->role == PJSIP_ROLE_UAS) { + + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, + call->conf_slot); + + } else if (pjsua.auto_conf) { + + int i; + + pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot); + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0); + + for (i=0; i < pjsua.max_calls; ++i) { + + if (!pjsua.calls[i].session) + continue; + + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, + pjsua.calls[i].conf_slot); + pjmedia_conf_connect_port( pjsua.mconf, pjsua.calls[i].conf_slot, + call->conf_slot); + } + + } else { + + /* Connect new call to the sound device port (port zero) in the + * main conference bridge. + */ + pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot); + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0); + } + + + /* Done. */ + { + struct pjmedia_session_info sess_info; + char info[80]; + int info_len = 0; + unsigned i; + + pjmedia_session_get_info(call->session, &sess_info); + for (i=0; idir) { + case PJMEDIA_DIR_NONE: + dir = "inactive"; + break; + case PJMEDIA_DIR_ENCODING: + dir = "sendonly"; + break; + case PJMEDIA_DIR_DECODING: + dir = "recvonly"; + break; + case PJMEDIA_DIR_ENCODING_DECODING: + dir = "sendrecv"; + break; + default: + dir = "unknown"; + break; + } + len = pj_ansi_sprintf( info+info_len, + ", stream #%d: %.*s (%s)", i, + (int)strm_info->fmt.encoding_name.slen, + (int)strm_info->fmt.encoding_name.ptr, + dir); + if (len > 0) + info_len += len; + } + PJ_LOG(3,(THIS_FILE,"Media started%s", info)); + } +} + + +/* + * Hangup call. + */ +void pjsua_call_hangup(int call_index, int code) +{ + pjsua_call *call; + pj_status_t status; + pjsip_tx_data *tdata; + + + call = &pjsua.calls[call_index]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + return; + } + + status = pjsip_inv_end_session(call->inv, code, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Failed to create end session message", + status); + return; + } + + /* pjsip_inv_end_session may return PJ_SUCCESS with NULL + * as p_tdata when INVITE transaction has not been answered + * with any provisional responses. + */ + if (tdata == NULL) + return; + + status = pjsip_inv_send_msg(call->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Failed to send end session message", + status); + return; + } +} + + +/* + * Put call on-Hold. + */ +void pjsua_call_set_hold(int call_index) +{ + pjmedia_sdp_session *sdp; + pjsua_call *call; + pjsip_tx_data *tdata; + pj_status_t status; + + call = &pjsua.calls[call_index]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + return; + } + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed")); + return; + } + + status = create_inactive_sdp(call, &sdp); + if (status != PJ_SUCCESS) + return; + + /* Send re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + return; + } + + status = pjsip_inv_send_msg( call->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + return; + } +} + + +/* + * re-INVITE. + */ +void pjsua_call_reinvite(int call_index) +{ + pjmedia_sdp_session *sdp; + pjsip_tx_data *tdata; + pjsua_call *call; + pj_status_t status; + + call = &pjsua.calls[call_index]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + return; + } + + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); + return; + } + + /* Create SDP */ + status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1, + &call->skinfo, &sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", + status); + return; + } + + /* Send re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + return; + } + + status = pjsip_inv_send_msg( call->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + return; + } +} + + +/* + * Transfer call. + */ +void pjsua_call_xfer(int call_index, const char *dest) +{ + pjsip_evsub *sub; + pjsip_tx_data *tdata; + pjsua_call *call; + pj_str_t tmp; + pj_status_t status; + + + call = &pjsua.calls[call_index]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + return; + } + + /* Create xfer client subscription. + * We're not interested in knowing the transfer result, so we + * put NULL as the callback. + */ + status = pjsip_xfer_create_uac(call->inv->dlg, NULL, &sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create xfer", status); + return; + } + + /* + * Create REFER request. + */ + status = pjsip_xfer_initiate(sub, pj_cstr(&tmp, dest), &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create REFER request", status); + return; + } + + /* Send. */ + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send REFER request", status); + return; + } + + /* For simplicity (that's what this program is intended to be!), + * leave the original invite session as it is. More advanced application + * may want to hold the INVITE, or terminate the invite, or whatever. + */ +} + + +/* + * Terminate all calls. + */ +void pjsua_inv_shutdown() +{ + int i; + + for (i=0; iinv, 410, NULL, &tdata)==0) { + if (tdata) + pjsip_inv_send_msg(call->inv, tdata, NULL); + } + } +} + + +pj_status_t pjsua_call_init(void) +{ + /* Initialize invite session callback. */ + pjsip_inv_callback inv_cb; + pj_status_t status; + + pj_memset(&inv_cb, 0, 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.endpt, &pjsua.mod, &inv_cb); + + return status; +} diff --git a/pjsip/src/pjsua/pjsua_core.c b/pjsip/src/pjsua/pjsua_core.c index f5c35468..974c6d53 100644 --- a/pjsip/src/pjsua/pjsua_core.c +++ b/pjsip/src/pjsua/pjsua_core.c @@ -45,6 +45,8 @@ struct pjsua pjsua; */ void pjsua_default(void) { + unsigned i; + /* Normally need another thread for console application, because main * thread will be blocked in fgets(). @@ -53,44 +55,38 @@ void pjsua_default(void) /* Default transport settings: */ - pjsua.sip_port = 5060; /* Default logging settings: */ - pjsua.log_level = 5; pjsua.app_log_level = 4; pjsua.log_decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE; - /* Default: do not use STUN: */ + /* Default: do not use STUN: */ pjsua.stun_port1 = pjsua.stun_port2 = 0; - /* Default URIs: */ - - pjsua.local_uri = pj_str(PJSUA_LOCAL_URI); - - /* Default registration timeout: */ - - pjsua.reg_timeout = 55; - - /* Default maximum conference ports: */ - - pjsua.max_ports = 8; - - /* Init route set list: */ - - pj_list_init(&pjsua.route_set); + /* Init accounts: */ + pjsua.acc_cnt = 1; + for (i=0; imsg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) { - return pjsua_inv_on_incoming(rdata); - + return pjsua_call_on_incoming(rdata); } return PJ_FALSE; @@ -145,7 +140,7 @@ static pj_status_t init_sockets(pj_bool_t sip, enum { RTP_START_PORT = 4000, RTP_RANDOM_START = 2, - RTP_RETRY = 20 + RTP_RETRY = 100 }; enum { SIP_SOCK, @@ -240,37 +235,49 @@ static pj_status_t init_sockets(pj_bool_t sip, for (i=0; i<3; ++i) pj_memcpy(&mapped_addr[i], &addr, sizeof(addr)); - if (sip) - mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)pjsua.sip_port); - mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port); - mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1)); + if (sip) { + mapped_addr[SIP_SOCK].sin_port = + pj_htons((pj_uint16_t)pjsua.sip_port); + } + mapped_addr[RTP_SOCK].sin_port=pj_htons((pj_uint16_t)rtp_port); + mapped_addr[RTCP_SOCK].sin_port=pj_htons((pj_uint16_t)(rtp_port+1)); break; + } else { - status = pj_stun_get_mapped_addr( &pjsua.cp.factory, 3, sock, - &pjsua.stun_srv1, pjsua.stun_port1, - &pjsua.stun_srv2, pjsua.stun_port2, - mapped_addr); + status=pj_stun_get_mapped_addr(&pjsua.cp.factory, 3, sock, + &pjsua.stun_srv1, pjsua.stun_port1, + &pjsua.stun_srv2, pjsua.stun_port2, + mapped_addr); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "STUN error", status); goto on_error; } - if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1) + if (pj_ntohs(mapped_addr[2].sin_port) == + pj_ntohs(mapped_addr[1].sin_port)+1) + { break; + } - pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET; - pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET; + pj_sock_close(sock[RTP_SOCK]); + sock[RTP_SOCK] = PJ_INVALID_SOCKET; + + pj_sock_close(sock[RTCP_SOCK]); + sock[RTCP_SOCK] = PJ_INVALID_SOCKET; } } if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) { - PJ_LOG(1,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination")); + PJ_LOG(1,(THIS_FILE, + "Unable to find appropriate RTP/RTCP ports combination")); goto on_error; } if (sip) { pjsua.sip_sock = sock[SIP_SOCK]; - pj_memcpy(&pjsua.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in)); + pj_memcpy(&pjsua.sip_sock_name, + &mapped_addr[SIP_SOCK], + sizeof(pj_sockaddr_in)); } else { pj_sock_close(sock[0]); } @@ -391,30 +398,13 @@ static pj_status_t init_stack(void) /* Initialize invite session module: */ - { - - /* Initialize invite session callback. */ - pjsip_inv_callback inv_cb; - - pj_memset(&inv_cb, 0, sizeof(inv_cb)); - inv_cb.on_state_changed = &pjsua_inv_on_state_changed; - inv_cb.on_new_session = &pjsua_inv_on_new_session; - inv_cb.on_media_update = &pjsua_inv_on_media_update; - inv_cb.on_rx_offer = &pjsua_inv_on_rx_offer; - inv_cb.on_tsx_state_changed = &pjsua_inv_on_tsx_state_changed; - - - /* Initialize invite session module: */ - status = pjsip_inv_usage_init(pjsua.endpt, &pjsua.mod, &inv_cb); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Invite usage initialization error", - status); - goto on_error; - } - + status = pjsua_call_init(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Invite usage initialization error", + status); + goto on_error; } - /* Done */ return PJ_SUCCESS; @@ -520,7 +510,8 @@ pj_status_t pjsua_init(void) /* Init conference bridge. */ - status = pjmedia_conf_create(pjsua.pool, pjsua.max_ports, + status = pjmedia_conf_create(pjsua.pool, + pjsua.max_calls+PJSUA_CONF_MORE_PORTS, 8000, 160, 16, &pjsua.mconf); if (status != PJ_SUCCESS) { pj_caching_pool_destroy(&pjsua.cp); @@ -547,6 +538,67 @@ pj_status_t pjsua_init(void) } +/* + * Find account for incoming request. + */ +int pjsua_find_account_for_incoming(pjsip_rx_data *rdata) +{ + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + int acc_index; + + uri = rdata->msg_info.to->uri; + + /* Just return account #0 if To URI is not SIP: */ + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri)) + { + return 0; + } + + + sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); + + /* Find account which has matching username and domain. */ + for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) { + + pjsua_acc *acc = &pjsua.acc[acc_index]; + + if (pj_stricmp(&acc->user_part, &sip_uri->user)==0 && + pj_stricmp(&acc->host_part, &sip_uri->host)==0) + { + /* Match ! */ + return acc_index; + } + } + + /* No matching, try match domain part only. */ + for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) { + + pjsua_acc *acc = &pjsua.acc[acc_index]; + + if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) { + /* Match ! */ + return acc_index; + } + } + + /* Still no match, just return account #0 */ + return 0; +} + + +/* + * Find account for outgoing request. + */ +int pjsua_find_account_for_outgoing(const pj_str_t *url) +{ + PJ_UNUSED_ARG(url); + + /* Just use account #0 */ + return 0; +} + /* * Start pjsua stack. @@ -588,11 +640,18 @@ pj_status_t pjsua_start(void) /* Init sockets (STUN etc): */ - for (i=0; i= 0) + pj_sock_close(pjsua.sip_sock); + while (i >= 0) { + pj_sock_close(pjsua.calls[i].skinfo.rtp_sock); + pj_sock_close(pjsua.calls[i].skinfo.rtcp_sock); + } return status; } } @@ -623,34 +682,26 @@ pj_status_t pjsua_start(void) } /* Initialize Contact URI, if one is not specified: */ - - if (pjsua.contact_uri.slen == 0 && pjsua.local_uri.slen) { + for (i=0; iuser.slen) { - - /* With the user part. */ - len = pj_snprintf(contact, sizeof(contact), - "", - (int)sip_uri->user.slen, - sip_uri->user.ptr, - (int)udp_transport->local_name.host.slen, - udp_transport->local_name.host.ptr, - udp_transport->local_name.port); - } else { - - /* Without user part */ + pjsua.acc[i].user_part = sip_uri->user; + pjsua.acc[i].host_part = sip_uri->host; - len = pj_snprintf(contact, sizeof(contact), - "", - (int)udp_transport->local_name.host.slen, - udp_transport->local_name.host.ptr, - udp_transport->local_name.port); - } + if (pjsua.acc[i].contact_uri.slen == 0 && + pjsua.acc[i].local_uri.slen) + { + char contact[128]; + int len; + + /* 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_snprintf(contact, sizeof(contact), + "", + (int)sip_uri->user.slen, + sip_uri->user.ptr, + (int)udp_transport->local_name.host.slen, + udp_transport->local_name.host.ptr, + udp_transport->local_name.port); + } else { + + /* Without user part */ + + len = pj_snprintf(contact, sizeof(contact), + "", + (int)udp_transport->local_name.host.slen, + udp_transport->local_name.host.ptr, + udp_transport->local_name.port); + } - if (len < 1 || len >= sizeof(contact)) { - pjsua_perror(THIS_FILE, "Invalid Contact", PJSIP_EURITOOLONG); - return PJSIP_EURITOOLONG; - } + if (len < 1 || len >= sizeof(contact)) { + pjsua_perror(THIS_FILE, "Invalid Contact", PJSIP_EURITOOLONG); + return PJSIP_EURITOOLONG; + } - /* Duplicate Contact uri. */ + /* Duplicate Contact uri. */ - pj_strdup2(pjsua.pool, &pjsua.contact_uri, contact); + pj_strdup2(pjsua.pool, &pjsua.acc[i].contact_uri, contact); + } } /* If outbound_proxy is specified, put it in the route_set: */ @@ -714,7 +780,9 @@ pj_status_t pjsua_start(void) return PJSIP_EINVALIDURI; } - pj_list_push_back(&pjsua.route_set, route); + for (i=0; i - * - * 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.h" -#include - - -/* - * pjsua_inv.c - * - * Invite session specific functionalities. - */ - -#define THIS_FILE "pjsua_inv.c" - - -/** - * Make outgoing call. - */ -pj_status_t pjsua_invite(const char *cstr_dest_uri, - struct pjsua_inv_data **p_inv_data) -{ - pj_str_t dest_uri; - pjsip_dialog *dlg; - pjmedia_sdp_session *offer; - pjsip_inv_session *inv; - struct pjsua_inv_data *inv_data; - pjsip_tx_data *tdata; - int med_sk_index = 0; - pj_status_t status; - - /* Convert cstr_dest_uri to dest_uri */ - - dest_uri = pj_str((char*)cstr_dest_uri); - - /* Find free socket. */ - for (med_sk_index=0; med_sk_indexpool, - 1, &pjsua.med_sock_info[med_sk_index], - &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. */ - - inv_data = pj_pool_zalloc( dlg->pool, sizeof(struct pjsua_inv_data)); - inv_data->inv = inv; - inv_data->call_slot = med_sk_index; - dlg->mod_data[pjsua.mod.id] = inv_data; - inv->mod_data[pjsua.mod.id] = inv_data; - - - /* Set dialog Route-Set: */ - - if (!pj_list_empty(&pjsua.route_set)) - pjsip_dlg_set_route_set(dlg, &pjsua.route_set); - - - /* Set credentials: */ - - pjsip_auth_clt_set_credentials( &dlg->auth_sess, pjsua.cred_count, - pjsua.cred_info); - - - /* 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 invite session to the list. */ - - pj_list_push_back(&pjsua.inv_list, inv_data); - - - /* Send initial INVITE: */ - - status = pjsip_inv_send_msg(inv, tdata, NULL); - if (status != PJ_SUCCESS) { - /* - * Note: - * inv_data will be removed from the list in the callback - */ - pjsua_perror(THIS_FILE, "Unable to send initial INVITE request", - status); - goto on_error; - } - - - /* Done. */ - if (p_inv_data) - *p_inv_data = inv_data; - - return PJ_SUCCESS; - - -on_error: - - PJ_TODO(DESTROY_DIALOG_ON_FAIL); - pjsua.med_sock_use[med_sk_index] = 0; - return status; -} - - -/** - * Handle incoming INVITE request. - */ -pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata) -{ - pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); - 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; - struct pjsua_inv_data *inv_data; - pjmedia_sdp_session *answer; - int med_sk_index; - 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; - - - /* Verify that we can handle the request. */ - status = pjsip_inv_verify_request(rdata, &options, NULL, NULL, - pjsua.endpt, &response); - if (status != PJ_SUCCESS) { - - /* - * No we can't handle the incoming INVITE request. - */ - - if (response) { - pjsip_response_addr res_addr; - - pjsip_get_response_addr(response->pool, rdata, &res_addr); - pjsip_endpt_send_response(pjsua.endpt, &res_addr, response, - NULL, NULL); - - } else { - - /* Respond with 500 (Internal Server Error) */ - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, - NULL, NULL); - } - - return PJ_TRUE; - } - - - /* - * Yes we can handle the incoming INVITE request. - */ - - /* Find free call slot. */ - for (med_sk_index=0; med_sk_indextp_info.pool, - 1, &pjsua.med_sock_info[med_sk_index], - &answer ); - if (status != PJ_SUCCESS) { - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, - NULL, NULL); - - /* Free call socket. */ - pjsua.med_sock_use[med_sk_index] = 0; - return PJ_TRUE; - } - - /* Create dialog: */ - - status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, - &pjsua.contact_uri, &dlg); - if (status != PJ_SUCCESS) { - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, - NULL, NULL); - - /* Free call socket. */ - pjsua.med_sock_use[med_sk_index] = 0; - return PJ_TRUE; - } - - - /* Create invite session: */ - - status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv); - if (status != PJ_SUCCESS) { - - pjsip_dlg_respond(dlg, rdata, 500, NULL); - - /* Free call socket. */ - pjsua.med_sock_use[med_sk_index] = 0; - - // TODO: Need to delete dialog - return PJ_TRUE; - } - - - /* Create and attach pjsua data to the dialog: */ - - inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data)); - inv_data->inv = inv; - inv_data->call_slot = inv_data->call_slot = med_sk_index; - dlg->mod_data[pjsua.mod.id] = inv_data; - inv->mod_data[pjsua.mod.id] = inv_data; - - pj_list_push_back(&pjsua.inv_list, inv_data); - - - /* Must answer with some response to initial INVITE. - * If auto-answer flag is set, send 200 straight away, otherwise send 100. - */ - - status = pjsip_inv_initial_answer(inv, rdata, - (pjsua.auto_answer ? 200 : 100), - NULL, NULL, &response); - if (status != PJ_SUCCESS) { - - pjsua_perror(THIS_FILE, "Unable to create 100 response", status); - - pjsip_dlg_respond(dlg, rdata, 500, NULL); - - /* Free call socket. */ - pjsua.med_sock_use[med_sk_index] = 0; - - // TODO: Need to delete dialog - - } else { - status = pjsip_inv_send_msg(inv, response, NULL); - if (status != PJ_SUCCESS) - pjsua_perror(THIS_FILE, "Unable to send 100 response", status); - } - - if (pjsua.auto_answer < 200) { - PJ_LOG(3,(THIS_FILE, - "\nIncoming call!!\n" - "From: %.*s\n" - "To: %.*s\n" - "(press 'a' to answer, 'h' to decline)", - (int)dlg->remote.info_str.slen, - dlg->remote.info_str.ptr, - (int)dlg->local.info_str.slen, - dlg->local.info_str.ptr)); - } else { - PJ_LOG(3,(THIS_FILE, - "Call From:%.*s To:%.*s was answered with %d (%s)", - (int)dlg->remote.info_str.slen, - dlg->remote.info_str.ptr, - (int)dlg->local.info_str.slen, - dlg->local.info_str.ptr, - pjsua.auto_answer, - pjsip_get_status_text(pjsua.auto_answer)->ptr )); - } - - /* This INVITE request has been handled. */ - return PJ_TRUE; -} - - -/* - * This callback receives notification from invite session when the - * session state has changed. - */ -void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) -{ - struct pjsua_inv_data *inv_data; - - inv_data = inv->dlg->mod_data[pjsua.mod.id]; - - /* If this is an outgoing INVITE that was created because of - * REFER/transfer, send NOTIFY to transferer. - */ - if (inv_data && inv_data->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) - { - int st_code = -1; - pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE; - - - switch (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; - } - - if (st_code != -1) { - pjsip_tx_data *tdata; - pj_status_t status; - - status = pjsip_xfer_notify( inv_data->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(inv_data->xfer_sub, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status); - } - } - } - } - - - /* Destroy media session when invite session is disconnected. */ - if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { - - pj_assert(inv_data != NULL); - - if (inv_data && inv_data->session) { - pjmedia_conf_remove_port(pjsua.mconf, inv_data->conf_slot); - pjmedia_session_destroy(inv_data->session); - pjsua.med_sock_use[inv_data->call_slot] = 0; - inv_data->session = NULL; - - PJ_LOG(3,(THIS_FILE,"Media session is destroyed")); - } - - if (inv_data) { - - pj_list_erase(inv_data); - - } - } - - pjsua_ui_inv_on_state_changed(inv, e); -} - - -/* - * Callback called by event framework when the xfer subscription state - * has changed. - */ -static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) -{ - - PJ_UNUSED_ARG(event); - - /* - * We're only interested when subscription is terminated, to - * clear the xfer_sub member of the inv_data. - */ - if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { - struct pjsua_inv_data *inv_data; - - inv_data = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); - if (!inv_data) - return; - - pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); - inv_data->xfer_sub = NULL; - - PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated")); - } -} - - -/* - * Follow transfer (REFER) request. - */ -static void on_call_transfered( pjsip_inv_session *inv, - pjsip_rx_data *rdata ) -{ - pj_status_t status; - pjsip_tx_data *tdata; - struct pjsua_inv_data *inv_data; - const pj_str_t str_refer_to = { "Refer-To", 8}; - pjsip_generic_string_hdr *refer_to; - char *uri; - struct pjsip_evsub_user xfer_cb; - pjsip_evsub *sub; - - /* Find the Refer-To header */ - refer_to = (pjsip_generic_string_hdr*) - pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL); - - if (refer_to == NULL) { - /* Invalid Request. - * No Refer-To header! - */ - PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!")); - pjsip_dlg_respond( inv->dlg, rdata, 400, 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, - (int)refer_to->hvalue.slen, - refer_to->hvalue.ptr)); - - /* Init callback */ - pj_memset(&xfer_cb, 0, sizeof(xfer_cb)); - xfer_cb.on_evsub_state = &xfer_on_evsub_state; - - /* Create transferee event subscription */ - status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create xfer uas", status); - pjsip_dlg_respond( inv->dlg, rdata, 500, NULL); - return; - } - - /* Accept the REFER request, send 200 (OK). */ - pjsip_xfer_accept(sub, rdata, 200, NULL); - - /* Create initial NOTIFY request */ - status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, - 100, NULL, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status); - return; - } - - /* Send initial NOTIFY request */ - status = pjsip_xfer_send_request( sub, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status); - return; - } - - /* We're cheating here. - * We need to get a null terminated string from a pj_str_t. - * So grab the pointer from the hvalue and NULL terminate it, knowing - * that the NULL position will be occupied by a newline. - */ - uri = refer_to->hvalue.ptr; - uri[refer_to->hvalue.slen] = '\0'; - - /* Now make the outgoing call. */ - status = pjsua_invite(uri, &inv_data); - if (status != PJ_SUCCESS) { - - /* Notify xferer about the error */ - status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, - 500, NULL, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", - status); - return; - } - status = pjsip_xfer_send_request(sub, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", - status); - return; - } - return; - } - - /* Put the server subscription in inv_data. - * Subsequent state changed in pjsua_inv_on_state_changed() will be - * reported back to the server subscription. - */ - inv_data->xfer_sub = sub; - - /* Put the invite_data in the subscription. */ - pjsip_evsub_set_mod_data(sub, pjsua.mod.id, inv_data); -} - - -/* - * This callback is called when transaction state has changed in INVITE - * session. We use this to trap incoming REFER request. - */ -void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv, - pjsip_transaction *tsx, - pjsip_event *e) -{ - if (tsx->role==PJSIP_ROLE_UAS && - tsx->state==PJSIP_TSX_STATE_TRYING && - pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0) - { - /* - * Incoming REFER request. - */ - on_call_transfered(inv, e->body.tsx_state.src.rdata); - } -} - - -/* - * This callback is called by invite session framework when UAC session - * has forked. - */ -void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e) -{ - PJ_UNUSED_ARG(inv); - PJ_UNUSED_ARG(e); - - PJ_TODO(HANDLE_FORKED_DIALOG); -} - - -/* - * Create inactive SDP for call hold. - */ -static pj_status_t create_inactive_sdp(struct pjsua_inv_data *inv_session, - pjmedia_sdp_session **p_answer) -{ - pj_status_t status; - pjmedia_sdp_conn *conn; - pjmedia_sdp_attr *attr; - pjmedia_sdp_session *sdp; - - /* Create new offer */ - status = pjmedia_endpt_create_sdp(pjsua.med_endpt, pjsua.pool, 1, - &pjsua.med_sock_info[inv_session->call_slot], - &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.pool, "inactive", NULL); - pjmedia_sdp_media_add_attr(sdp->media[0], attr); - - *p_answer = sdp; - - return status; -} - -/* - * Called when session received new offer. - */ -void pjsua_inv_on_rx_offer( pjsip_inv_session *inv, - const pjmedia_sdp_session *offer) -{ - struct pjsua_inv_data *inv_data; - pjmedia_sdp_conn *conn; - pjmedia_sdp_session *answer; - pj_bool_t is_remote_active; - pj_status_t status; - - inv_data = inv->dlg->mod_data[pjsua.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; - } - - PJ_LOG(4,(THIS_FILE, "Received SDP offer, remote media is %s", - (is_remote_active ? "active" : "inactive"))); - - /* Supply candidate answer */ - if (is_remote_active) { - status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1, - &pjsua.med_sock_info[inv_data->call_slot], - &answer); - } else { - status = create_inactive_sdp( inv_data, &answer ); - } - - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create local SDP", status); - return; - } - - status = pjsip_inv_set_sdp_answer(inv, answer); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to set answer", status); - return; - } - -} - - -/* - * 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. - */ -void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status) -{ - struct pjsua_inv_data *inv_data; - const pjmedia_sdp_session *local_sdp; - const pjmedia_sdp_session *remote_sdp; - pjmedia_port *media_port; - pj_str_t port_name; - char tmp[PJSIP_MAX_URL_SIZE]; - - if (status != PJ_SUCCESS) { - - pjsua_perror(THIS_FILE, "SDP negotiation has failed", status); - return; - - } - - /* Destroy existing media session, if any. */ - - inv_data = inv->dlg->mod_data[pjsua.mod.id]; - if (inv_data && inv_data->session) { - pjmedia_conf_remove_port(pjsua.mconf, inv_data->conf_slot); - pjmedia_session_destroy(inv_data->session); - pjsua.med_sock_use[inv_data->call_slot] = 0; - inv_data->session = NULL; - } - - /* Get local and remote SDP */ - - status = pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Unable to retrieve currently active local SDP", - status); - return; - } - - - status = pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Unable to retrieve currently active remote SDP", - status); - return; - } - - /* Create new media session. - * The media session is active immediately. - */ - if (pjsua.null_audio) - return; - - status = pjmedia_session_create( pjsua.med_endpt, 1, - &pjsua.med_sock_info[inv_data->call_slot], - local_sdp, remote_sdp, - inv_data, - &inv_data->session ); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create media session", - status); - return; - } - - - /* Get the port interface of the first stream in the session. - * We need the port interface to add to the conference bridge. - */ - pjmedia_session_get_port(inv_data->session, 0, &media_port); - - - /* - * Add the call to conference bridge. - */ - port_name.ptr = tmp; - port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, - inv_data->inv->dlg->remote.info->uri, - tmp, sizeof(tmp)); - if (port_name.slen < 1) { - port_name = pj_str("call"); - } - status = pjmedia_conf_add_port( pjsua.mconf, inv->pool, - media_port, - &port_name, - &inv_data->conf_slot); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create conference slot", - status); - pjmedia_session_destroy(inv_data->session); - inv_data->session = NULL; - return; - } - - /* If auto-play is configured, connect the call to the file player - * port - */ - if (pjsua.wav_file && inv->role == PJSIP_ROLE_UAS) { - - pjmedia_conf_connect_port( pjsua.mconf, pjsua.wav_slot, - inv_data->conf_slot); - - } else { - - /* Connect new call to the sound device port (port zero) in the - * main conference bridge. - */ - pjmedia_conf_connect_port( pjsua.mconf, 0, inv_data->conf_slot); - pjmedia_conf_connect_port( pjsua.mconf, inv_data->conf_slot, 0); - } - - - /* Done. */ - { - struct pjmedia_session_info sess_info; - char info[80]; - int info_len = 0; - unsigned i; - - pjmedia_session_get_info(inv_data->session, &sess_info); - for (i=0; idir) { - case PJMEDIA_DIR_NONE: - dir = "inactive"; - break; - case PJMEDIA_DIR_ENCODING: - dir = "sendonly"; - break; - case PJMEDIA_DIR_DECODING: - dir = "recvonly"; - break; - case PJMEDIA_DIR_ENCODING_DECODING: - dir = "sendrecv"; - break; - default: - dir = "unknown"; - break; - } - len = pj_ansi_sprintf( info+info_len, - ", stream #%d: %.*s (%s)", i, - (int)strm_info->fmt.encoding_name.slen, - (int)strm_info->fmt.encoding_name.ptr, - dir); - if (len > 0) - info_len += len; - } - PJ_LOG(3,(THIS_FILE,"Media started%s", info)); - } -} - - -/* - * Hangup call. - */ -void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code) -{ - pj_status_t status; - pjsip_tx_data *tdata; - - status = pjsip_inv_end_session(inv_session->inv, - code, NULL, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Failed to create end session message", - status); - return; - } - - /* pjsip_inv_end_session may return PJ_SUCCESS with NULL - * as p_tdata when INVITE transaction has not been answered - * with any provisional responses. - */ - if (tdata == NULL) - return; - - status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Failed to send end session message", - status); - return; - } -} - - -/* - * Put call on-Hold. - */ -void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session) -{ - pjmedia_sdp_session *sdp; - pjsip_inv_session *inv = inv_session->inv; - pjsip_tx_data *tdata; - pj_status_t status; - - if (inv->state != PJSIP_INV_STATE_CONFIRMED) { - PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed")); - return; - } - - status = create_inactive_sdp(inv_session, &sdp); - if (status != PJ_SUCCESS) - return; - - /* Send re-INVITE with new offer */ - status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); - return; - } - - status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); - return; - } -} - - -/* - * re-INVITE. - */ -void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session) -{ - pjmedia_sdp_session *sdp; - pjsip_tx_data *tdata; - pjsip_inv_session *inv = inv_session->inv; - pj_status_t status; - - - if (inv->state != PJSIP_INV_STATE_CONFIRMED) { - PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); - return; - } - - /* Create SDP */ - status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1, - &pjsua.med_sock_info[inv_session->call_slot], - &sdp); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", status); - return; - } - - /* Send re-INVITE with new offer */ - status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); - return; - } - - status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); - return; - } -} - - -/* - * Transfer call. - */ -void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session, - const char *dest) -{ - pjsip_evsub *sub; - pjsip_tx_data *tdata; - pj_str_t tmp; - pj_status_t status; - - - /* Create xfer client subscription. - * We're not interested in knowing the transfer result, so we - * put NULL as the callback. - */ - status = pjsip_xfer_create_uac(inv_session->inv->dlg, NULL, &sub); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create xfer", status); - return; - } - - /* - * Create REFER request. - */ - status = pjsip_xfer_initiate(sub, pj_cstr(&tmp, dest), &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create REFER request", status); - return; - } - - /* Send. */ - status = pjsip_xfer_send_request(sub, tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to send REFER request", status); - return; - } - - /* For simplicity (that's what this program is intended to be!), - * leave the original invite session as it is. More advanced application - * may want to hold the INVITE, or terminate the invite, or whatever. - */ -} - - -/* - * Terminate all calls. - */ -void pjsua_inv_shutdown() -{ - struct pjsua_inv_data *inv_data, *next; - - inv_data = pjsua.inv_list.next; - while (inv_data != &pjsua.inv_list) { - pjsip_tx_data *tdata; - - next = inv_data->next; - - if (pjsip_inv_end_session(inv_data->inv, 410, NULL, &tdata)==0) { - if (tdata) - pjsip_inv_send_msg(inv_data->inv, tdata, NULL); - } - - inv_data = next; - } -} - - diff --git a/pjsip/src/pjsua/pjsua_opt.c b/pjsip/src/pjsua/pjsua_opt.c index 52819265..5ec37980 100644 --- a/pjsip/src/pjsua/pjsua_opt.c +++ b/pjsip/src/pjsua/pjsua_opt.c @@ -43,8 +43,6 @@ static void usage(void) puts("Usage:"); puts(" pjsua [options]"); puts(""); - puts(" [sip-url] Default URL to invite."); - puts(""); puts("General options:"); puts(" --help Display this help screen"); puts(" --version Display version info"); @@ -55,36 +53,43 @@ static void usage(void) puts(" --log-level=N Set log max level to N (0(none) to 6(trace))"); puts(" --app-log-level=N Set log max level for stdout display to N"); puts(""); - puts("Authentication options:"); - puts(" --realm=string Set realm"); - puts(" --username=string Set authentication username"); - puts(" --password=string Set authentication password"); - puts(""); - puts("SIP options:"); + puts("SIP Account options:"); puts(" --id=url Set the URL of local ID (used in From header)"); puts(" --contact=url Override the Contact information"); puts(" --proxy=url Set the URL of proxy server"); - //puts(" --outbound=url Set the URL of outbound proxy server"); puts(""); - puts("Registration Options:"); + puts("SIP Account Registration Options:"); puts(" --registrar=url Set the URL of registrar server"); puts(" --reg-timeout=secs Set registration interval to secs (default 3600)"); puts(""); + puts("SIP Account Control:"); + puts(" --next-account Add more account"); + puts(""); + puts("Authentication options:"); + puts(" --realm=string Set realm"); + puts(" --username=string Set authentication username"); + puts(" --password=string Set authentication password"); + puts(" --next-cred Add more credential"); + puts(""); puts("Transport Options:"); - puts(" --local-port=port Set TCP/UDP port"); + puts(" --local-port=port Set TCP/UDP port"); + puts(" --outbound=url Set the URL of outbound proxy server"); puts(" --use-stun1=host[:port]"); - puts(" --use-stun2=host[:port] Use STUN and set host name and port of STUN servers"); + puts(" --use-stun2=host[:port] Resolve local IP with the specified STUN servers"); puts(""); puts("Media Options:"); puts(" --null-audio Use NULL audio device"); - //puts(" --wav-file=file Play WAV file in conference bridge"); + puts(" --play-file=file Play WAV file in conference bridge"); + puts(" --auto-play Automatically play the file (to incoming calls only)"); + puts(" --auto-loop Automatically loop incoming RTP to outgoing RTP"); + puts(" --auto-conf Automatically put incoming calls to conference"); puts(""); puts("Buddy List (can be more than one):"); puts(" --add-buddy url Add the specified URL to the buddy list."); puts(""); puts("User Agent options:"); puts(" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)"); - puts(" --auto-play=file Automatically play WAVE file to incoming calls"); + puts(" --max-calls=N Maximum number of concurrent calls (default:4, max:255)"); puts(""); fflush(stdout); } @@ -199,7 +204,11 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) OPT_REALM, OPT_USERNAME, OPT_PASSWORD, OPT_USE_STUN1, OPT_USE_STUN2, OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE, - OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY}; + OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP, + OPT_AUTO_CONF, + OPT_PLAY_FILE, + OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS, + }; struct option long_options[] = { { "config-file",1, 0, OPT_CONFIG_FILE}, { "log-file", 1, 0, OPT_LOG_FILE}, @@ -225,10 +234,18 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) { "no-presence", 0, 0, OPT_NO_PRESENCE}, { "auto-answer",1, 0, OPT_AUTO_ANSWER}, { "auto-hangup",1, 0, OPT_AUTO_HANGUP}, - { "auto-play", 1, 0, OPT_AUTO_PLAY}, + { "auto-play", 0, 0, OPT_AUTO_PLAY}, + { "auto-loop", 0, 0, OPT_AUTO_LOOP}, + { "auto-conf", 0, 0, OPT_AUTO_CONF}, + { "play-file", 1, 0, OPT_PLAY_FILE}, + { "next-account",0,0, OPT_NEXT_ACCOUNT}, + { "next-cred", 0, 0, OPT_NEXT_CRED}, + { "max-calls", 1, 0, OPT_MAX_CALLS}, { NULL, 0, 0, 0} }; pj_status_t status; + pjsua_acc *cur_acc; + pjsip_cred_info *cur_cred; char *config_file = NULL; /* Run getopt once to see if user specifies config file to read. */ @@ -249,6 +266,10 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) } + cur_acc = &pjsua.acc[0]; + cur_cred = &pjsua.cred_info[0]; + + /* Reinitialize and re-run getopt again, possibly with new arguments * read from config file. */ @@ -307,7 +328,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) printf("Error: invalid SIP URL '%s' in proxy argument\n", optarg); return PJ_EINVAL; } - pjsua.proxy = pj_str(optarg); + cur_acc->proxy = pj_str(optarg); break; case OPT_OUTBOUND_PROXY: /* outbound proxy */ @@ -323,12 +344,12 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) printf("Error: invalid SIP URL '%s' in registrar argument\n", optarg); return PJ_EINVAL; } - pjsua.registrar_uri = pj_str(optarg); + cur_acc->reg_uri = pj_str(optarg); break; case OPT_REG_TIMEOUT: /* reg-timeout */ - pjsua.reg_timeout = pj_strtoul(pj_cstr(&tmp,optarg)); - if (pjsua.reg_timeout < 1 || pjsua.reg_timeout > 3600) { + cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,optarg)); + if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) { printf("Error: invalid value for --reg-timeout (expecting 1-3600)\n"); return PJ_EINVAL; } @@ -339,7 +360,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) printf("Error: invalid SIP URL '%s' in local id argument\n", optarg); return PJ_EINVAL; } - pjsua.local_uri = pj_str(optarg); + cur_acc->local_uri = pj_str(optarg); break; case OPT_CONTACT: /* contact */ @@ -347,23 +368,33 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) printf("Error: invalid SIP URL '%s' in contact argument\n", optarg); return PJ_EINVAL; } - pjsua.contact_uri = pj_str(optarg); + cur_acc->contact_uri = pj_str(optarg); + break; + + case OPT_NEXT_ACCOUNT: /* Add more account. */ + pjsua.acc_cnt++; + cur_acc = &pjsua.acc[pjsua.acc_cnt - 1]; break; case OPT_USERNAME: /* Default authentication user */ - if (!pjsua.cred_count) pjsua.cred_count = 1; - pjsua.cred_info[0].username = pj_str(optarg); + if (pjsua.cred_count==0) pjsua.cred_count=1; + cur_cred->username = pj_str(optarg); break; case OPT_REALM: /* Default authentication realm. */ - if (!pjsua.cred_count) pjsua.cred_count = 1; - pjsua.cred_info[0].realm = pj_str(optarg); + if (pjsua.cred_count==0) pjsua.cred_count=1; + cur_cred->realm = pj_str(optarg); break; case OPT_PASSWORD: /* authentication password */ - if (!pjsua.cred_count) pjsua.cred_count = 1; - pjsua.cred_info[0].data_type = 0; - pjsua.cred_info[0].data = pj_str(optarg); + if (pjsua.cred_count==0) pjsua.cred_count=1; + cur_cred->data_type = 0; + cur_cred->data = pj_str(optarg); + break; + + case OPT_NEXT_CRED: /* Next credential */ + pjsua.cred_count++; + cur_cred = &pjsua.cred_info[pjsua.cred_count - 1]; break; case OPT_USE_STUN1: /* STUN server 1 */ @@ -411,6 +442,18 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) break; case OPT_AUTO_PLAY: + pjsua.auto_play = 1; + break; + + case OPT_AUTO_LOOP: + pjsua.auto_loop = 1; + break; + + case OPT_AUTO_CONF: + pjsua.auto_conf = 1; + break; + + case OPT_PLAY_FILE: pjsua.wav_file = optarg; break; @@ -421,6 +464,14 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) return -1; } break; + + case OPT_MAX_CALLS: + pjsua.max_calls = atoi(optarg); + if (pjsua.max_calls < 1 || pjsua.max_calls > 255) { + puts("Too many calls for max-calls (1-255)"); + return -1; + } + break; } } @@ -429,21 +480,17 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) return PJ_EINVAL; } - if (pjsua.reg_timeout == 0) - pjsua.reg_timeout = 3600; - - return PJ_SUCCESS; } -static void print_invite_session(const char *title, - struct pjsua_inv_data *inv_data, - char *buf, pj_size_t size) +static void print_call(const char *title, + int call_index, + char *buf, pj_size_t size) { int len; - pjsip_inv_session *inv = inv_data->inv; + pjsip_inv_session *inv = pjsua.calls[call_index].inv; pjsip_dialog *dlg = inv->dlg; char userinfo[128]; @@ -515,7 +562,6 @@ static void dump_media_session(pjmedia_session *session) */ void pjsua_dump(void) { - struct pjsua_inv_data *inv_data; char buf[128]; unsigned old_decor; @@ -533,23 +579,23 @@ void pjsua_dump(void) /* Dump all invite sessions: */ PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:")); - if (pj_list_empty(&pjsua.inv_list)) { + if (pjsua.call_cnt == 0) { PJ_LOG(3,(THIS_FILE, " - no sessions -")); } else { + int i; - inv_data = pjsua.inv_list.next; + for (i=0; isession) - dump_media_session(inv_data->session); - - inv_data = inv_data->next; + if (pjsua.calls[i].session) + dump_media_session(pjsua.calls[i].session); } } @@ -575,40 +621,96 @@ pj_status_t pjsua_load_settings(const char *filename) /* - * Save settings. + * Save account settings */ -pj_status_t pjsua_save_settings(const char *filename) +static void save_account_settings(int acc_index, pj_str_t *result) { - unsigned i; - pj_str_t cfg; char line[128]; - pj_pool_t *pool; - FILE *fhnd; + pjsua_acc *acc = &pjsua.acc[acc_index]; - /* Create pool for temporary buffer. */ - pool = pj_pool_create(&pjsua.cp.factory, "settings", 4000, 0, NULL); - if (!pool) - return PJ_ENOMEM; + + pj_ansi_sprintf(line, "#\n# Account %d:\n#\n", acc_index); + pj_strcat2(result, line); - cfg.ptr = pj_pool_alloc(pool, 3800); - if (!cfg.ptr) { - pj_pool_release(pool); - return PJ_EBUG; + /* Identity */ + if (acc->local_uri.slen) { + pj_ansi_sprintf(line, "--id %.*s\n", + (int)acc->local_uri.slen, + acc->local_uri.ptr); + pj_strcat2(result, line); + } + + /* Registrar server */ + if (acc->reg_uri.slen) { + pj_ansi_sprintf(line, "--registrar %.*s\n", + (int)acc->reg_uri.slen, + acc->reg_uri.ptr); + pj_strcat2(result, line); + + pj_ansi_sprintf(line, "--reg-timeout %u\n", + acc->reg_timeout); + pj_strcat2(result, line); } + + + /* Proxy */ + if (acc->proxy.slen) { + pj_ansi_sprintf(line, "--proxy %.*s\n", + (int)acc->proxy.slen, + acc->proxy.ptr); + pj_strcat2(result, line); + } +} + + + +/* + * Dump settings. + */ +int pjsua_dump_settings(char *buf, pj_size_t max) +{ + int acc_index; + int i; + pj_str_t cfg; + char line[128]; + + cfg.ptr = buf; cfg.slen = 0; - /* Identity */ - if (pjsua.local_uri.slen) { - pj_ansi_sprintf(line, "--id %.*s\n", - (int)pjsua.local_uri.slen, - pjsua.local_uri.ptr); + /* Logging. */ + pj_strcat2(&cfg, "#\n# Logging options:\n#\n"); + pj_ansi_sprintf(line, "--log-level %d\n", + pjsua.log_level); + pj_strcat2(&cfg, line); + + pj_ansi_sprintf(line, "--app-log-level %d\n", + pjsua.app_log_level); + pj_strcat2(&cfg, line); + + if (pjsua.log_filename) { + pj_ansi_sprintf(line, "--log-file %s\n", + pjsua.log_filename); pj_strcat2(&cfg, line); } + + /* Save account settings. */ + for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) { + + save_account_settings(acc_index, &cfg); + + if (acc_index < pjsua.acc_cnt-1) + pj_strcat2(&cfg, "--next-account\n"); + } + /* Credentials. */ for (i=0; imsg_info.msg->line.req.method; pjsua_srv_pres *uapres; pjsip_evsub *sub; @@ -93,9 +94,13 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) /* Incoming SUBSCRIBE: */ + /* Find which account for the incoming request. */ + acc_index = pjsua_find_account_for_incoming(rdata); + /* Create UAS dialog: */ status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, - &pjsua.contact_uri, &dlg); + &pjsua.acc[acc_index].contact_uri, + &dlg); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create UAS dialog for subscription", @@ -130,7 +135,7 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) pjsip_evsub_set_mod_data(sub, pjsua.mod.id, uapres); /* Add server subscription to the list: */ - pj_list_push_back(&pjsua.pres_srv_list, uapres); + pj_list_push_back(&pjsua.acc[acc_index].pres_srv_list, uapres); /* Create and send 200 (OK) to the SUBSCRIBE request: */ @@ -146,7 +151,7 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) /* Set our online status: */ pj_memset(&pres_status, 0, sizeof(pres_status)); pres_status.info_cnt = 1; - pres_status.info[0].basic_open = pjsua.online_status; + pres_status.info[0].basic_open = pjsua.acc[acc_index].online_status; //Both pjsua.local_uri and pjsua.contact_uri are enclosed in "<" and ">" //causing XML parsing to fail. //pres_status.info[0].contact = pjsua.local_uri; @@ -174,20 +179,20 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) /* Refresh subscription (e.g. when our online status has changed) */ -static void refresh_server_subscription() +static void refresh_server_subscription(int acc_index) { pjsua_srv_pres *uapres; - uapres = pjsua.pres_srv_list.next; + uapres = pjsua.acc[acc_index].pres_srv_list.next; - while (uapres != &pjsua.pres_srv_list) { + while (uapres != &pjsua.acc[acc_index].pres_srv_list) { pjsip_pres_status pres_status; pjsip_tx_data *tdata; pjsip_pres_get_status(uapres->sub, &pres_status); - if (pres_status.info[0].basic_open != pjsua.online_status) { - pres_status.info[0].basic_open = pjsua.online_status; + if (pres_status.info[0].basic_open != pjsua.acc[acc_index].online_status) { + pres_status.info[0].basic_open = pjsua.acc[acc_index].online_status; pjsip_pres_set_status(uapres->sub, &pres_status); if (pjsua.quit_flag) { @@ -298,13 +303,16 @@ static pjsip_evsub_user pres_callback = /* It does what it says.. */ static void subscribe_buddy_presence(unsigned index) { + int acc_index; pjsip_dialog *dlg; pjsip_tx_data *tdata; pj_status_t status; + acc_index = pjsua.buddies[index].acc_index; + status = pjsip_dlg_create_uac( pjsip_ua_instance(), - &pjsua.local_uri, - &pjsua.contact_uri, + &pjsua.acc[acc_index].local_uri, + &pjsua.acc[acc_index].contact_uri, &pjsua.buddies[index].uri, NULL, &dlg); if (status != PJ_SUCCESS) { @@ -325,7 +333,7 @@ static void subscribe_buddy_presence(unsigned index) pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id, &pjsua.buddies[index]); - status = pjsip_pres_initiate(pjsua.buddies[index].sub, 60, &tdata); + status = pjsip_pres_initiate(pjsua.buddies[index].sub, -1, &tdata); if (status != PJ_SUCCESS) { pjsua.buddies[index].sub = NULL; pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE", @@ -381,7 +389,7 @@ static void unsubscribe_buddy_presence(unsigned index) /* It does what it says.. */ static void refresh_client_subscription(void) { - unsigned i; + int i; for (i=0; isub), - uapres->remote)); + PJ_LOG(3,(THIS_FILE, " %.*s", + (int)pjsua.acc[acc_index].local_uri.slen, + pjsua.acc[acc_index].local_uri.ptr)); + + if (pj_list_empty(&pjsua.acc[acc_index].pres_srv_list)) { + PJ_LOG(3,(THIS_FILE, " - none - ")); + } else { + struct pjsua_srv_pres *uapres; + + uapres = pjsua.acc[acc_index].pres_srv_list.next; + while (uapres != &pjsua.acc[acc_index].pres_srv_list) { + + PJ_LOG(3,(THIS_FILE, " %10s %s", + pjsip_evsub_get_state_name(uapres->sub), + uapres->remote)); - uapres = uapres->next; + uapres = uapres->next; + } } } diff --git a/pjsip/src/pjsua/pjsua_reg.c b/pjsip/src/pjsua/pjsua_reg.c index f11ff3fd..006bc732 100644 --- a/pjsip/src/pjsua/pjsua_reg.c +++ b/pjsip/src/pjsua/pjsua_reg.c @@ -34,66 +34,81 @@ */ static void regc_cb(struct pjsip_regc_cbparam *param) { + + pjsua_acc *acc = param->token; + /* * Print registration status. */ if (param->status!=PJ_SUCCESS) { pjsua_perror(THIS_FILE, "SIP registration error", param->status); - pjsua.regc = NULL; + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; } else if (param->code < 0 || param->code >= 300) { PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)", param->code, pjsip_get_status_text(param->code)->ptr)); - pjsua.regc = NULL; + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { - PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), " - "will re-register in %d seconds", - param->code, - pjsip_get_status_text(param->code)->ptr, - param->expiration)); + + if (param->expiration < 1) { + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + PJ_LOG(3,(THIS_FILE, "%s: unregistration success", + acc->local_uri.ptr)); + } else { + PJ_LOG(3, (THIS_FILE, + "%s: registration success, status=%d (%s), " + "will re-register in %d seconds", + acc->local_uri.ptr, + param->code, + pjsip_get_status_text(param->code)->ptr, + param->expiration)); + } } else { PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); } - pjsua.regc_last_err = param->status; - pjsua.regc_last_code = param->code; + acc->reg_last_err = param->status; + acc->reg_last_code = param->code; - pjsua_ui_regc_on_state_changed(pjsua.regc_last_code); + pjsua_ui_regc_on_state_changed(acc->index); } /* * Update registration. If renew is false, then unregistration will be performed. */ -void pjsua_regc_update(pj_bool_t renew) +void pjsua_regc_update(int acc_index, pj_bool_t renew) { pj_status_t status; pjsip_tx_data *tdata; if (renew) { - if (pjsua.regc == NULL) { - status = pjsua_regc_init(); + if (pjsua.acc[acc_index].regc == NULL) { + status = pjsua_regc_init(acc_index); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create registration", status); return; } } - status = pjsip_regc_register(pjsua.regc, 1, &tdata); + status = pjsip_regc_register(pjsua.acc[acc_index].regc, 1, &tdata); } else { - if (pjsua.regc == NULL) { + if (pjsua.acc[acc_index].regc == NULL) { PJ_LOG(3,(THIS_FILE, "Currently not registered")); return; } - status = pjsip_regc_unregister(pjsua.regc, &tdata); + status = pjsip_regc_unregister(pjsua.acc[acc_index].regc, &tdata); } if (status == PJ_SUCCESS) - status = pjsip_regc_send( pjsua.regc, tdata ); + status = pjsip_regc_send( pjsua.acc[acc_index].regc, tdata ); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create/send REGISTER", @@ -107,14 +122,17 @@ void pjsua_regc_update(pj_bool_t renew) /* * Initialize client registration. */ -pj_status_t pjsua_regc_init(void) +pj_status_t pjsua_regc_init(int acc_index) { pj_status_t status; /* initialize SIP registration if registrar is configured */ - if (pjsua.registrar_uri.slen) { + if (pjsua.acc[acc_index].reg_uri.slen) { - status = pjsip_regc_create( pjsua.endpt, NULL, ®c_cb, &pjsua.regc); + status = pjsip_regc_create( pjsua.endpt, + &pjsua.acc[acc_index], + ®c_cb, + &pjsua.acc[acc_index].regc); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create client registration", @@ -123,11 +141,12 @@ pj_status_t pjsua_regc_init(void) } - status = pjsip_regc_init( pjsua.regc, &pjsua.registrar_uri, - &pjsua.local_uri, - &pjsua.local_uri, - 1, &pjsua.contact_uri, - pjsua.reg_timeout); + status = pjsip_regc_init( pjsua.acc[acc_index].regc, + &pjsua.acc[acc_index].reg_uri, + &pjsua.acc[acc_index].local_uri, + &pjsua.acc[acc_index].local_uri, + 1, &pjsua.acc[acc_index].contact_uri, + pjsua.acc[acc_index].reg_timeout); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Client registration initialization error", @@ -135,10 +154,12 @@ pj_status_t pjsua_regc_init(void) return status; } - pjsip_regc_set_credentials( pjsua.regc, pjsua.cred_count, + pjsip_regc_set_credentials( pjsua.acc[acc_index].regc, + pjsua.cred_count, pjsua.cred_info ); - pjsip_regc_set_route_set( pjsua.regc, &pjsua.route_set ); + pjsip_regc_set_route_set( pjsua.acc[acc_index].regc, + &pjsua.acc[acc_index].route_set ); } return PJ_SUCCESS; -- cgit v1.2.3