From c01fdece34cb0eac0c1fdbafb5c1cc242ec01933 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Fri, 26 May 2006 12:17:46 +0000 Subject: First stage in pjsua library re-arrangements towards creating an easy to use high level API git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@476 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip-apps/src/pjsua/main.c | 1023 +---------------------- pjsip/build/pjsua_lib.dsp | 12 + pjsip/include/pjsua-lib/pjsua.h | 485 ++++++----- pjsip/include/pjsua-lib/pjsua_console_app.h | 31 + pjsip/src/pjsip-ua/sip_reg.c | 10 +- pjsip/src/pjsua-lib/pjsua_call.c | 136 ++-- pjsip/src/pjsua-lib/pjsua_console_app.c | 1015 +++++++++++++++++++++++ pjsip/src/pjsua-lib/pjsua_core.c | 1170 ++++++++++++++++----------- pjsip/src/pjsua-lib/pjsua_im.c | 30 +- pjsip/src/pjsua-lib/pjsua_imp.h | 95 +++ pjsip/src/pjsua-lib/pjsua_pres.c | 36 +- pjsip/src/pjsua-lib/pjsua_reg.c | 32 +- pjsip/src/pjsua-lib/pjsua_settings.c | 373 +++++---- 13 files changed, 2476 insertions(+), 1972 deletions(-) create mode 100644 pjsip/include/pjsua-lib/pjsua_console_app.h create mode 100644 pjsip/src/pjsua-lib/pjsua_console_app.c create mode 100644 pjsip/src/pjsua-lib/pjsua_imp.h diff --git a/pjsip-apps/src/pjsua/main.c b/pjsip-apps/src/pjsua/main.c index 97da6f34..1e060f4e 100644 --- a/pjsip-apps/src/pjsua/main.c +++ b/pjsip-apps/src/pjsua/main.c @@ -17,1004 +17,10 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include -#include /* atoi */ -#include - -#define THIS_FILE "main.c" - -/* Current dialog */ -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_on_call_state(int call_index, pjsip_event *e) -{ - pjsua_call *call = &pjsua.calls[call_index]; - - PJ_UNUSED_ARG(e); - - if (call->inv->state == PJSIP_INV_STATE_DISCONNECTED) { - - PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", - call_index, - call->inv->cause, - pjsip_get_status_text(call->inv->cause)->ptr)); - - call->inv = NULL; - if ((int)call->index == current_call) { - find_next_call(); - } - - } else { - - PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", - call_index, - pjsua_inv_state_names[call->inv->state])); - - if (call && current_call==-1) - current_call = call->index; - - } -} - -/** - * Notify UI when registration status has changed. - */ -void pjsua_ui_on_reg_state(int acc_index) -{ - PJ_UNUSED_ARG(acc_index); - - // Log already written. -} - - -/** - * Incoming IM message (i.e. MESSAGE request)! - */ -void pjsua_ui_on_pager(int call_index, const pj_str_t *from, - const pj_str_t *to, const pj_str_t *text) -{ - /* Note: call index may be -1 */ - PJ_UNUSED_ARG(call_index); - PJ_UNUSED_ARG(to); - - PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s", - (int)from->slen, from->ptr, - (int)text->slen, text->ptr)); -} - - -/** - * Typing indication - */ -void pjsua_ui_on_typing(int call_index, const pj_str_t *from, - const pj_str_t *to, pj_bool_t is_typing) -{ - PJ_UNUSED_ARG(call_index); - PJ_UNUSED_ARG(to); - - PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s", - (int)from->slen, from->ptr, - (is_typing?"is typing..":"has stopped typing"))); -} - - -/* - * Print buddy list. - */ -static void print_buddy_list(void) -{ - int i; - - puts("Buddy list:"); - - if (pjsua.buddy_cnt == 0) - puts(" -none-"); - else { - for (i=0; i %s\n", - i+1, status, pjsua.buddies[i].uri.ptr); - } - } - puts(""); -} - - -/* - * Print account status. - */ -static void print_acc_status(int acc_index) -{ - char reg_status[128]; - - if (pjsua.acc[acc_index].regc == NULL) { - pj_ansi_strcpy(reg_status, " -not registered to server-"); - - } else if (pjsua.acc[acc_index].reg_last_err != PJ_SUCCESS) { - pj_strerror(pjsua.acc[acc_index].reg_last_err, reg_status, sizeof(reg_status)); - - } else if (pjsua.acc[acc_index].reg_last_code>=200 && - pjsua.acc[acc_index].reg_last_code<=699) { - - pjsip_regc_info info; - const pj_str_t *status_str; - - pjsip_regc_get_info(pjsua.acc[acc_index].regc, &info); - - status_str = pjsip_get_status_text(pjsua.acc[acc_index].reg_last_code); - pj_ansi_snprintf(reg_status, sizeof(reg_status), - "%s (%.*s;expires=%d)", - status_str->ptr, - (int)info.client_uri.slen, - info.client_uri.ptr, - info.next_reg); - - } else { - pj_ansi_sprintf(reg_status, "in progress (%d)", - pjsua.acc[acc_index].reg_last_code); - } - - 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; inb_result = NO_NB; - result->uri_result = NULL; - - print_buddy_list(); - - printf("Choices:\n" - " 0 For current dialog.\n" - " -1 All %d buddies in buddy list\n" - " [1 -%2d] Select from buddy list\n" - " URL An URL\n" - " Empty input (or 'q') to cancel\n" - , pjsua.buddy_cnt, pjsua.buddy_cnt); - printf("%s: ", title); - - fflush(stdout); - fgets(buf, len, stdin); - len = strlen(buf); - - /* Left trim */ - while (pj_isspace(*buf)) { - ++buf; - --len; - } - - /* Remove trailing newlines */ - while (len && (buf[len-1] == '\r' || buf[len-1] == '\n')) - buf[--len] = '\0'; - - if (len == 0 || buf[0]=='q') - return; - - if (pj_isdigit(*buf) || *buf=='-') { - - int i; - - if (*buf=='-') - i = 1; - else - i = 0; - - for (; inb_result = atoi(buf); - - if (result->nb_result >= 0 && result->nb_result <= (int)pjsua.buddy_cnt) { - return; - } - if (result->nb_result == -1) - return; - - puts("Invalid input"); - result->nb_result = NO_NB; - return; - - } else { - pj_status_t status; - - if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Invalid URL", status); - return; - } - - result->uri_result = buf; - } -} - -static void conf_list(void) -{ - unsigned i, count; - pjmedia_conf_port_info info[PJSUA_MAX_CALLS]; - - printf("Conference ports:\n"); - - count = PJ_ARRAY_SIZE(info); - pjmedia_conf_get_ports_info(pjsua.mconf, &count, info); - for (i=0; ilistener[j]) { - pj_ansi_sprintf(s, "#%d ", j); - pj_ansi_strcat(txlist, s); - } - } - printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n", - port_info->slot, - port_info->clock_rate/1000, - port_info->samples_per_frame * 1000 / port_info->clock_rate, - (int)port_info->name.slen, - port_info->name.ptr, - txlist); - - } - puts(""); -} - - -static void ui_console_main(void) -{ - char menuin[10]; - char buf[128]; - char text[128]; - int i, count; - char *uri; - struct input_result result; - - - /* If user specifies URI to call, then call the URI */ - if (pjsua.uri_to_call.slen) { - pjsua_make_call( current_acc, pjsua.uri_to_call.ptr, NULL); - } - - keystroke_help(); - - for (;;) { - - printf(">>> "); - fflush(stdout); - - fgets(menuin, sizeof(menuin), stdin); - - switch (menuin[0]) { - - case 'm': - /* Make call! : */ - printf("(You currently have %d calls)\n", pjsua.call_cnt); - - uri = NULL; - ui_input_url("Make call", buf, sizeof(buf), &result); - if (result.nb_result != NO_NB) { - - if (result.nb_result == -1 || result.nb_result == 0) { - puts("You can't do that with make call!"); - continue; - } else { - uri = pjsua.buddies[result.nb_result-1].uri.ptr; - } - - } else if (result.uri_result) { - uri = result.uri_result; - } - - pjsua_make_call( current_acc, uri, NULL); - break; - - case 'M': - /* Make multiple calls! : */ - printf("(You currently have %d calls)\n", pjsua.call_cnt); - - if (!simple_input("Number of calls", menuin, sizeof(menuin))) - continue; - - count = atoi(menuin); - if (count < 1) - continue; - - ui_input_url("Make call", buf, sizeof(buf), &result); - if (result.nb_result != NO_NB) { - if (result.nb_result == -1 || result.nb_result == 0) { - puts("You can't do that with make call!"); - continue; - } - uri = pjsua.buddies[result.nb_result-1].uri.ptr; - } else { - uri = result.uri_result; - } - - for (i=0; irole != PJSIP_ROLE_UAS || - pjsua.calls[current_call].inv->state >= PJSIP_INV_STATE_CONNECTING) - { - puts("No pending incoming call"); - fflush(stdout); - continue; - - } else { - pj_status_t status; - pjsip_tx_data *tdata; - - if (!simple_input("Answer with code (100-699)", buf, sizeof(buf))) - continue; - - if (atoi(buf) < 100) - continue; - - /* - * Must check again! - * Call may have been disconnected while we're waiting for - * keyboard input. - */ - if (current_call == -1) { - puts("Call has been disconnected"); - fflush(stdout); - continue; - } - - status = pjsip_inv_answer(pjsua.calls[current_call].inv, - atoi(buf), - NULL, NULL, &tdata); - if (status == PJ_SUCCESS) - status = pjsip_inv_send_msg(pjsua.calls[current_call].inv, - tdata); - - if (status != PJ_SUCCESS) - pjsua_perror(THIS_FILE, "Unable to create/send response", - status); - } - - break; - - - case 'h': - - if (current_call == -1) { - puts("No current call"); - fflush(stdout); - continue; - - } else if (menuin[1] == 'a') { - - /* Hangup all calls */ - pjsua_call_hangup_all(); - - } else { - - /* Hangup current calls */ - pjsua_call_hangup(current_call); - } - break; - - case ']': - case '[': - /* - * Cycle next/prev dialog. - */ - if (menuin[0] == ']') { - find_next_call(); - - } else { - find_prev_call(); - } - - if (current_call != -1) { - char url[PJSIP_MAX_URL_SIZE]; - int len; - const pjsip_uri *u; - - u = pjsua.calls[current_call].inv->dlg->remote.info->uri; - len = pjsip_uri_print(0, u, url, sizeof(url)-1); - if (len < 1) { - pj_ansi_strcpy(url, ""); - } else { - url[len] = '\0'; - } - - PJ_LOG(3,(THIS_FILE,"Current dialog: %s", url)); - - } else { - PJ_LOG(3,(THIS_FILE,"No current dialog")); - } - break; - - case 'H': - /* - * Hold call. - */ - if (current_call != -1) { - - pjsua_call_set_hold(current_call); - - } else { - PJ_LOG(3,(THIS_FILE, "No current call")); - } - break; - - case 'v': - /* - * Send re-INVITE (to release hold, etc). - */ - if (current_call != -1) { - - pjsua_call_reinvite(current_call); - - } else { - PJ_LOG(3,(THIS_FILE, "No current call")); - } - break; - - case 'x': - /* - * Transfer call. - */ - if (current_call == -1) { - - PJ_LOG(3,(THIS_FILE, "No current call")); - - } else { - int call = current_call; - - ui_input_url("Transfer to URL", buf, sizeof(buf), &result); - - /* Check if call is still there. */ - - if (call != current_call) { - puts("Call has been disconnected"); - continue; - } - - if (result.nb_result != NO_NB) { - if (result.nb_result == -1 || result.nb_result == 0) - puts("You can't do that with transfer call!"); - else - pjsua_call_xfer( current_call, - pjsua.buddies[result.nb_result-1].uri.ptr); - - } else if (result.uri_result) { - pjsua_call_xfer( current_call, result.uri_result); - } - } - break; - - case '#': - /* - * Send DTMF strings. - */ - if (current_call == -1) { - - PJ_LOG(3,(THIS_FILE, "No current call")); - - } else if (pjsua.calls[current_call].session == NULL) { - - PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); - - } else { - pj_str_t digits; - int call = current_call; - pj_status_t status; - - if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, - sizeof(buf))) - { - break; - } - - if (call != current_call) { - puts("Call has been disconnected"); - continue; - } - - digits = pj_str(buf); - 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); - } else { - puts("DTMF digits enqueued for transmission"); - } - } - break; - - case 's': - case 'u': - /* - * Subscribe/unsubscribe presence. - */ - ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result); - if (result.nb_result != NO_NB) { - if (result.nb_result == -1) { - int i; - for (i=0; imsg_info.len, - pjsip_rx_data_get_info(rdata), - rdata->pkt_info.src_name, - rdata->pkt_info.src_port, - rdata->msg_info.msg_buf)); - - /* Always return false, otherwise messages will not get processed! */ - return PJ_FALSE; -} - -/* Notification on outgoing messages */ -static pj_status_t console_on_tx_msg(pjsip_tx_data *tdata) -{ - - /* Important note: - * tp_info field is only valid after outgoing messages has passed - * transport layer. So don't try to access tp_info when the module - * has lower priority than transport layer. - */ - - PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n" - "%s\n" - "--end msg--", - (tdata->buf.cur - tdata->buf.start), - pjsip_tx_data_get_info(tdata), - tdata->tp_info.dst_name, - tdata->tp_info.dst_port, - tdata->buf.start)); - - /* Always return success, otherwise message will not get sent! */ - return PJ_SUCCESS; -} - -/* The module instance. */ -static pjsip_module console_msg_logger = -{ - NULL, NULL, /* prev, next. */ - { "mod-pjsua-log", 13 }, /* Name. */ - -1, /* Id */ - PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ - NULL, /* load() */ - NULL, /* start() */ - NULL, /* stop() */ - NULL, /* unload() */ - &console_on_rx_msg, /* on_rx_request() */ - &console_on_rx_msg, /* on_rx_response() */ - &console_on_tx_msg, /* on_tx_request. */ - &console_on_tx_msg, /* on_tx_response() */ - NULL, /* on_tsx_state() */ - -}; - - - -/***************************************************************************** - * Console application custom logging: - */ - - -static FILE *log_file; - - -static void app_log_writer(int level, const char *buffer, int len) -{ - /* Write to both stdout and file. */ - - if (level <= pjsua.app_log_level) - pj_log_write(level, buffer, len); - - if (log_file) { - fwrite(buffer, len, 1, log_file); - fflush(log_file); - } -} - - -pj_status_t app_logging_init(void) -{ - /* Redirect log function to ours */ - - pj_log_set_log_func( &app_log_writer ); - - /* If output log file is desired, create the file: */ - - if (pjsua.log_filename) { - log_file = fopen(pjsua.log_filename, "wt"); - if (log_file == NULL) { - PJ_LOG(1,(THIS_FILE, "Unable to open log file %s", - pjsua.log_filename)); - return -1; - } - } - - return PJ_SUCCESS; -} - - -void app_logging_shutdown(void) -{ - /* Close logging file, if any: */ - - if (log_file) { - fclose(log_file); - log_file = NULL; - } -} - -/***************************************************************************** - * Error display: - */ - -/* - * Display error message for the specified error code. - */ -void pjsua_perror(const char *sender, const char *title, - pj_status_t status) -{ - char errmsg[PJ_ERR_MSG_SIZE]; - - pj_strerror(status, errmsg, sizeof(errmsg)); - - PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status)); -} - +#include +#define THIS_FILE "main.c" /***************************************************************************** * main(): @@ -1022,30 +28,35 @@ void pjsua_perror(const char *sender, const char *title, int main(int argc, char *argv[]) { + pjsua_config cfg; + /* Init default settings. */ - pjsua_default(); + pjsua_default_config(&cfg); - /* Initialize pjsua (to create pool etc). - */ - if (pjsua_init() != PJ_SUCCESS) - return 1; + /* Create PJLIB and memory pool */ + pjsua_create(); /* Parse command line arguments: */ - if (pjsua_parse_args(argc, argv) != PJ_SUCCESS) + if (pjsua_parse_args(argc, argv, &cfg) != PJ_SUCCESS) return 1; /* Init logging: */ - if (app_logging_init() != PJ_SUCCESS) + if (pjsua_console_app_logging_init(&cfg) != PJ_SUCCESS) return 1; + /* Init pjsua */ + if (pjsua_init(&cfg, &console_callback) != PJ_SUCCESS) + return 1; + /* Register message logger to print incoming and outgoing * messages. */ - pjsip_endpt_register_module(pjsua.endpt, &console_msg_logger); + pjsip_endpt_register_module(pjsua.endpt, + &pjsua_console_app_msg_logger); /* Start pjsua! */ @@ -1061,7 +72,7 @@ int main(int argc, char *argv[]) /* Start UI console main loop: */ - ui_console_main(); + pjsua_console_app_main(); /* Destroy pjsua: */ @@ -1069,7 +80,7 @@ int main(int argc, char *argv[]) /* Close logging: */ - app_logging_shutdown(); + pjsua_console_app_logging_shutdown(); /* Exit... */ diff --git a/pjsip/build/pjsua_lib.dsp b/pjsip/build/pjsua_lib.dsp index a49b3606..d3f42ba7 100644 --- a/pjsip/build/pjsua_lib.dsp +++ b/pjsip/build/pjsua_lib.dsp @@ -91,6 +91,10 @@ SOURCE="..\src\pjsua-lib\pjsua_call.c" # End Source File # Begin Source File +SOURCE="..\src\pjsua-lib\pjsua_console_app.c" +# End Source File +# Begin Source File + SOURCE="..\src\pjsua-lib\pjsua_core.c" # End Source File # Begin Source File @@ -99,6 +103,10 @@ SOURCE="..\src\pjsua-lib\pjsua_im.c" # End Source File # Begin Source File +SOURCE="..\src\pjsua-lib\pjsua_imp.h" +# End Source File +# Begin Source File + SOURCE="..\src\pjsua-lib\pjsua_pres.c" # End Source File # Begin Source File @@ -117,6 +125,10 @@ SOURCE="..\src\pjsua-lib\pjsua_settings.c" SOURCE="..\include\pjsua-lib\pjsua.h" # End Source File +# Begin Source File + +SOURCE="..\include\pjsua-lib\pjsua_console_app.h" +# End Source File # End Group # End Target # End Project diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index a3c0c3a4..6484d729 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -48,7 +48,7 @@ PJ_BEGIN_DECL * Max buddies in buddy list. */ #ifndef PJSUA_MAX_BUDDIES -# define PJSUA_MAX_BUDDIES 32 +# define PJSUA_MAX_BUDDIES 256 #endif @@ -60,28 +60,14 @@ PJ_BEGIN_DECL #endif -/** - * Aditional ports to be allocated in the conference ports for non-call - * streams. - */ -#define PJSUA_CONF_MORE_PORTS 3 - - /** * Maximum accounts. */ #ifndef PJSUA_MAX_ACC -# define PJSUA_MAX_ACC 8 +# define PJSUA_MAX_ACC 32 #endif -/** - * Maximum credentials. - */ -#ifndef PJSUA_MAX_CRED -# define PJSUA_MAX_CRED PJSUA_MAX_ACC -#endif - /** * Structure to be attached to invite dialog. @@ -139,25 +125,55 @@ struct pjsua_srv_pres typedef struct pjsua_srv_pres pjsua_srv_pres; +/** + * Account configuration. + */ +struct pjsua_acc_config +{ + /** SIP URL for account ID (mandatory) */ + pj_str_t id; + + /** Registrar URI (mandatory) */ + pj_str_t reg_uri; + + /** Optional contact URI */ + pj_str_t contact; + + /** Service proxy (default: none) */ + pj_str_t proxy; + + /** Default timeout (mandatory) */ + pj_int32_t reg_timeout; + + /** Number of credentials. */ + unsigned cred_count; + + /** Array of credentials. */ + pjsip_cred_info cred_info[4]; + +}; + + +/** + * @see pjsua_acc_config + */ +typedef struct pjsua_acc_config pjsua_acc_config; + + /** * 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. */ @@ -167,9 +183,193 @@ struct pjsua_acc }; +/** + * @see pjsua_acc + */ typedef struct pjsua_acc pjsua_acc; +/** + * PJSUA settings. + */ +struct pjsua_config +{ + /** SIP UDP signaling port. Set to zero to disable UDP signaling, + * which in this case application must manually add a transport + * to SIP endpoint. + * (default: 5060) + */ + unsigned udp_port; + + /** Optional hostname or IP address to publish as the host part of + * Contact header. This must be specified if UDP transport is + * disabled. + * (default: NULL) + */ + pj_str_t sip_host; + + /** Optional port number to publish in the port part of Contact header. + * This must be specified if UDP transport is disabled. + * (default: 0) + */ + unsigned sip_port; + + /** Start of RTP port. Set to zero to prevent pjsua from creating + * media transports, which in this case application must manually + * create media transport for each calls. + * (default: 4000) + */ + unsigned start_rtp_port; + + /** Maximum calls to support (default: 4) */ + unsigned max_calls; + + /** Maximum slots in the conference bridge (default: 0/calculated + * as max_calls*2 + */ + unsigned conf_ports; + + /** Number of worker threads (default: 1) */ + unsigned thread_cnt; + + /** First STUN server IP address. When STUN is configured, then the + * two STUN server settings must be fully set. + * (default: none) + */ + pj_str_t stun_srv1; + + /** First STUN port number */ + unsigned stun_port1; + + /** Second STUN server IP address */ + pj_str_t stun_srv2; + + /** Second STUN server port number */ + unsigned stun_port2; + + /** Internal clock rate (to be applied to sound devices and conference + * bridge, default is 0/follows the codec, or 44100 for MacOS). + */ + unsigned clock_rate; + + /** Do not use sound device (default: 0). */ + pj_bool_t null_audio; + + /** WAV file to load for auto_play (default: NULL) */ + pj_str_t wav_file; + + /** Auto play WAV file for calls? (default: no) */ + pj_bool_t auto_play; + + /** Auto loopback calls? (default: no) */ + pj_bool_t auto_loop; + + /** Automatically put calls to conference? (default: no) */ + pj_bool_t auto_conf; + + /** Speex codec complexity? (default: 10) */ + unsigned complexity; + + /** Speex codec quality? (default: 10) */ + unsigned quality; + + /** Codec ptime? (default: 0 (follows the codec)) */ + unsigned ptime; + + /** Number of additional codecs/"--add-codec" with pjsua (default: 0) */ + unsigned codec_cnt; + + /** Additional codecs/"--add-codec" options */ + pj_str_t codec_arg[32]; + + /** SIP status code to be automatically sent to incoming calls + * (default: 100). + */ + unsigned auto_answer; + + /** Periodic time to refresh call with re-INVITE (default: 0) + */ + unsigned uas_refresh; + + /** Maximum incoming call duration (default: 3600) */ + unsigned uas_duration; + + /** Outbound proxy (default: none) */ + pj_str_t outbound_proxy; + + /** URI to call. */ + pj_str_t uri_to_call; + + /** Number of SIP accounts */ + unsigned acc_cnt; + + /** SIP accounts configuration */ + pjsua_acc_config acc_config[32]; + + /** Logging verbosity (default: 5). */ + unsigned log_level; + + /** Logging to be displayed to stdout (default: 4) */ + unsigned app_log_level; + + /** Log decoration */ + unsigned log_decor; + + /** Optional log filename (default: NULL) */ + pj_str_t log_filename; + + /** Number of buddies in address book (default: 0) */ + unsigned buddy_cnt; + + /** Buddies URI */ + pj_str_t buddy_uri[256]; +}; + + +/** + * @see pjsua_config + */ +typedef struct pjsua_config pjsua_config; + + + +/** + * Application callbacks. + */ +struct pjsua_callback +{ + /** + * Notify UI when invite state has changed. + */ + void (*on_call_state)(int call_index, pjsip_event *e); + + /** + * Notify UI when registration status has changed. + */ + void (*on_reg_state)(int acc_index); + + /** + * Notify UI on incoming pager (i.e. MESSAGE request). + * Argument call_index will be -1 if MESSAGE request is not related to an + * existing call. + */ + void (*on_pager)(int call_index, const pj_str_t *from, + const pj_str_t *to, const pj_str_t *txt); + + /** + * Notify UI about typing indication. + */ + void (*on_typing)(int call_index, const pj_str_t *from, + const pj_str_t *to, pj_bool_t is_typing); + +}; + +/** + * @see pjsua_callback + */ +typedef struct pjsua_callback pjsua_callback; + + /* PJSUA application variables. */ struct pjsua { @@ -178,79 +378,35 @@ struct pjsua pjsip_endpoint *endpt; /**< Global endpoint. */ pj_pool_t *pool; /**< pjsua's private pool. */ pjsip_module mod; /**< pjsua's PJSIP module. */ + + /* Config: */ + pjsua_config config; /**< PJSUA configs */ + + /* Application callback: */ + pjsua_callback cb; /**< Application callback. */ /* Media: */ - int start_rtp_port;/**< Start of RTP port to try. */ pjmedia_endpt *med_endpt; /**< Media endpoint. */ - unsigned clock_rate; /**< Internal clock rate. */ pjmedia_conf *mconf; /**< Media conference. */ - pj_bool_t null_audio; /**< Null audio flag. */ - pj_bool_t no_mic; /**< Disable microphone. */ - char *wav_file; /**< WAV file name to play. */ unsigned wav_slot; /**< WAV player slot in bridge */ pjmedia_port *file_port; /**< WAV player port. */ - pjmedia_port *null_port; /**< NULL port. */ - 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? */ - int complexity; /**< Codec complexity. */ - int quality; /**< Codec quality. */ - int ptime; /**< Codec ptime in msec. */ - - /* Codec arguments: */ - int codec_cnt; /**< Number of --add-codec args. */ - pj_str_t codec_arg[32]; /**< Array of --add-codec args. */ - pj_status_t (*codec_deinit[32])(void); /**< Array of funcs. */ - - /* User Agent behaviour: */ - int auto_answer; /**< Automatically answer in calls. */ - int uas_refresh; /**< Time to re-INVITE. */ - int uas_duration; /**< Max call duration. */ /* Account: */ - pj_bool_t has_acc; /**< Any --id cmdline? */ - int acc_cnt; /**< Number of client registrations */ pjsua_acc acc[PJSUA_MAX_ACC]; /** Client regs array. */ - /* Authentication credentials: */ - - 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: */ - int log_level; /**< Logging verbosity. */ - int app_log_level; /**< stdout log verbosity. */ - unsigned log_decor; /**< Log decoration. */ - char *log_filename; /**< Log filename. */ - /* PJSUA Calls: */ - pj_str_t uri_to_call; /**< URI to call. */ - int max_calls; /**< Max nb of calls. */ int call_cnt; /**< Number of calls. */ pjsua_call calls[PJSUA_MAX_CALLS]; /** Calls array. */ @@ -273,26 +429,35 @@ extern struct pjsua pjsua; /** * Initialize pjsua settings with default parameters. */ -void pjsua_default(void); +PJ_DECL(void) pjsua_default_config(pjsua_config *cfg); /** - * Display error message for the specified error code. + * Test configuration. */ -void pjsua_perror(const char *sender, const char *title, - pj_status_t status); +PJ_DECL(pj_status_t) pjsua_test_config(const pjsua_config *cfg, + char *errmsg, + int len); /** - * Initialize pjsua application. Application can call this before parsing - * application settings. + * Create pjsua application. + * This initializes pjlib/pjlib-util, and creates memory pool factory to + * be used by application. + */ +PJ_DECL(pj_status_t) pjsua_create(void); + + +/** + * Initialize pjsua application with the specified settings. * * This will initialize all libraries, create endpoint instance, and register - * pjsip modules. Transport will NOT be created however. + * pjsip modules. * * Application may register module after calling this function. */ -pj_status_t pjsua_init(void); +PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, + const pjsua_callback *cb); /** @@ -302,112 +467,82 @@ pj_status_t pjsua_init(void); * This will start the transport, worker threads (if any), and registration * process, if registration is configured. */ -pj_status_t pjsua_start(void); +PJ_DECL(pj_status_t) pjsua_start(void); /** * Destroy pjsua. */ -pj_status_t pjsua_destroy(void); - - -/** - * Find account for incoming request. - */ -int pjsua_find_account_for_incoming(pjsip_rx_data *rdata); - - -/** - * Find account for outgoing request. - */ -int pjsua_find_account_for_outgoing(const pj_str_t *url); +PJ_DECL(pj_status_t) pjsua_destroy(void); /***************************************************************************** * PJSUA Call API (defined in pjsua_call.c). */ -/** - * Init pjsua call module. - */ -pj_status_t pjsua_call_init(void); - /** * Make outgoing call. */ -pj_status_t pjsua_make_call(int acc_index, - const char *cstr_dest_uri, - int *p_call_index); - - -/** - * Handle incoming invite request. - */ -pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata); +PJ_DECL(pj_status_t) pjsua_make_call(int acc_index, + const char *cstr_dest_uri, + int *p_call_index); /** * Answer call. */ -void pjsua_call_answer(int call_index, int code); +PJ_DECL(void) pjsua_call_answer(int call_index, int code); /** * Hangup call. */ -void pjsua_call_hangup(int call_index); +PJ_DECL(void) pjsua_call_hangup(int call_index); /** * Put call on-hold. */ -void pjsua_call_set_hold(int call_index); +PJ_DECL(void) pjsua_call_set_hold(int call_index); /** * Send re-INVITE (to release hold). */ -void pjsua_call_reinvite(int call_index); +PJ_DECL(void) pjsua_call_reinvite(int call_index); /** * Transfer call. */ -void pjsua_call_xfer(int call_index, const char *dest); +PJ_DECL(void) pjsua_call_xfer(int call_index, const char *dest); /** * Send instant messaging inside INVITE session. */ -void pjsua_call_send_im(int call_index, const char *text); +PJ_DECL(void) pjsua_call_send_im(int call_index, const char *text); /** * Send IM typing indication inside INVITE session. */ -void pjsua_call_typing(int call_index, pj_bool_t is_typing); +PJ_DECL(void) pjsua_call_typing(int call_index, pj_bool_t is_typing); /** * Terminate all calls. */ -void pjsua_call_hangup_all(void); +PJ_DECL(void) pjsua_call_hangup_all(void); /***************************************************************************** * PJSUA Client Registration API (defined in pjsua_reg.c). */ -/** - * Initialize client registration session. - * - * @param app_callback Optional callback - */ -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(int acc_index, pj_bool_t renew); +PJ_DECL(void) pjsua_regc_update(int acc_index, pj_bool_t renew); @@ -416,25 +551,15 @@ void pjsua_regc_update(int acc_index, pj_bool_t renew); * PJSUA Presence (pjsua_pres.c) */ -/** - * Init presence. - */ -pj_status_t pjsua_pres_init(); - /** * Refresh both presence client and server subscriptions. */ -void pjsua_pres_refresh(int acc_index); - -/** - * Terminate all subscriptions - */ -void pjsua_pres_shutdown(void); +PJ_DECL(void) pjsua_pres_refresh(int acc_index); /** * Dump presence subscriptions. */ -void pjsua_pres_dump(pj_bool_t detail); +PJ_DECL(void) pjsua_pres_dump(pj_bool_t detail); /***************************************************************************** @@ -447,79 +572,21 @@ void pjsua_pres_dump(pj_bool_t detail); extern const pjsip_method pjsip_message_method; -/** - * Init IM module handler to handle incoming MESSAGE outside dialog. - */ -pj_status_t pjsua_im_init(); - - -/** - * Create Accept header for MESSAGE. - */ -pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool); /** * Send IM outside dialog. */ -pj_status_t pjsua_im_send(int acc_index, const char *dst_uri, - const char *text); +PJ_DECL(pj_status_t) pjsua_im_send(int acc_index, const char *dst_uri, + const char *text); /** * Send typing indication outside dialog. */ -pj_status_t pjsua_im_typing(int acc_index, const char *dst_uri, - pj_bool_t is_typing); - - -/** - * Private: check if we can accept the message. - * If not, then p_accept header will be filled with a valid - * Accept header. - */ -pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata, - pjsip_accept_hdr **p_accept_hdr); - -/** - * Private: process pager message. - * This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing(). - */ -void pjsua_im_process_pager(int call_id, const pj_str_t *from, - const pj_str_t *to, pjsip_rx_data *rdata); - - -/***************************************************************************** - * User Interface API. - * - * The UI API specifies functions that will be called by pjsua upon - * occurence of various events. - */ - -/** - * Notify UI when invite state has changed. - */ -void pjsua_ui_on_call_state(int call_index, pjsip_event *e); - -/** - * Notify UI when registration status has changed. - */ -void pjsua_ui_on_reg_state(int acc_index); - -/** - * Notify UI on incoming pager (i.e. MESSAGE request). - * Argument call_index will be -1 if MESSAGE request is not related to an - * existing call. - */ -void pjsua_ui_on_pager(int call_index, const pj_str_t *from, - const pj_str_t *to, const pj_str_t *txt); +PJ_DECL(pj_status_t) pjsua_im_typing(int acc_index, const char *dst_uri, + pj_bool_t is_typing); -/** - * Notify UI about typing indication. - */ -void pjsua_ui_on_typing(int call_index, const pj_str_t *from, - const pj_str_t *to, pj_bool_t is_typing); - /***************************************************************************** * Utilities. @@ -532,34 +599,46 @@ extern const char *pjsua_inv_state_names[]; /** * Parse arguments (pjsua_opt.c). */ -pj_status_t pjsua_parse_args(int argc, char *argv[]); +PJ_DECL(pj_status_t) pjsua_parse_args(int argc, char *argv[], + pjsua_config *cfg); /** * Load settings from a file. */ -pj_status_t pjsua_load_settings(const char *filename); +PJ_DECL(pj_status_t) pjsua_load_settings(const char *filename, + pjsua_config *cfg); /** * Dump settings. */ -int pjsua_dump_settings(char *buf, pj_size_t max); +PJ_DECL(int) pjsua_dump_settings(const pjsua_config *cfg, + char *buf, pj_size_t max); /** * Save settings to a file. */ -pj_status_t pjsua_save_settings(const char *filename); +PJ_DECL(pj_status_t) pjsua_save_settings(const char *filename, + const pjsua_config *cfg); /* * Verify that valid SIP url is given. * @return PJ_SUCCESS if valid. */ -pj_status_t pjsua_verify_sip_url(const char *c_url); +PJ_DECL(pj_status_t) pjsua_verify_sip_url(const char *c_url); /* * Dump application states. */ -void pjsua_dump(pj_bool_t detail); +PJ_DECL(void) pjsua_dump(pj_bool_t detail); + +/** + * Display error message for the specified error code. + */ +PJ_DECL(void) pjsua_perror(const char *sender, const char *title, + pj_status_t status); + + PJ_END_DECL diff --git a/pjsip/include/pjsua-lib/pjsua_console_app.h b/pjsip/include/pjsua-lib/pjsua_console_app.h new file mode 100644 index 00000000..058d0637 --- /dev/null +++ b/pjsip/include/pjsua-lib/pjsua_console_app.h @@ -0,0 +1,31 @@ +/* $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 + */ +#ifndef __PJSUA_CONSOLE_APP_H__ +#define __PJSUA_CONSOLE_APP_H__ + + +pj_status_t pjsua_console_app_logging_init(const pjsua_config *cfg); +void pjsua_console_app_logging_shutdown(void); + +void pjsua_console_app_main(void); + +extern pjsip_module pjsua_console_app_msg_logger; +extern pjsua_callback console_callback; + +#endif /* __PJSUA_CONSOLE_APP_H__ */ diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c index 84fb57dd..6272e34c 100644 --- a/pjsip/src/pjsip-ua/sip_reg.c +++ b/pjsip/src/pjsip-ua/sip_reg.c @@ -484,6 +484,7 @@ static void tsx_callback(void *token, pjsip_event *event) pjsip_transaction *tsx = event->body.tsx_state.tsx; /* Decrement pending transaction counter. */ + pj_assert(regc->pending_tsx > 0); --regc->pending_tsx; /* If registration data has been deleted by user then remove registration @@ -609,10 +610,13 @@ PJ_DEF(pj_status_t) pjsip_regc_send(pjsip_regc *regc, pjsip_tx_data *tdata) cseq_hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); cseq_hdr->cseq = cseq; - /* Send. */ + /* Increment pending transaction first, since transaction callback + * may be called even before send_request() returns! + */ + ++regc->pending_tsx; status = pjsip_endpt_send_request(regc->endpt, tdata, -1, regc, &tsx_callback); - if (status==PJ_SUCCESS) - ++regc->pending_tsx; + if (status!=PJ_SUCCESS) + --regc->pending_tsx; return status; } diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 4dbfb596..d6013a13 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -18,7 +18,7 @@ */ #include #include - +#include "pjsua_imp.h" /* * pjsua_call.c @@ -56,7 +56,8 @@ static void call_on_timer(pj_timer_heap_t *ht, pj_timer_entry *e) pjsua_call_hangup(call->index); } else { PJ_LOG(3,(THIS_FILE, "Refreshing call %d", call->index)); - schedule_call_timer(call,e,REFRESH_CALL_TIMER,pjsua.uas_refresh); + schedule_call_timer(call,e,REFRESH_CALL_TIMER, + pjsua.config.uas_refresh); pjsua_call_reinvite(call->index); } @@ -126,15 +127,15 @@ static pj_status_t call_destroy_media(int call_index) /** * Make outgoing call. */ -pj_status_t pjsua_make_call(int acc_index, - const char *cstr_dest_uri, - int *p_call_index) +PJ_DEF(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 = NULL; pjmedia_sdp_session *offer; pjsip_inv_session *inv = NULL; - int call_index = -1; + unsigned call_index; pjsip_tx_data *tdata; pj_status_t status; @@ -143,12 +144,12 @@ pj_status_t pjsua_make_call(int acc_index, dest_uri = pj_str((char*)cstr_dest_uri); /* Find free call slot. */ - for (call_index=0; call_indexauth_sess, pjsua.cred_count, - pjsua.cred_info); + if (pjsua.config.acc_config[acc_index].cred_count) { + pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index]; + pjsip_auth_clt_set_credentials( &dlg->auth_sess, + acc_cfg->cred_count, + acc_cfg->cred_info); + } /* Create initial INVITE: */ @@ -225,6 +229,12 @@ pj_status_t pjsua_make_call(int acc_index, if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to send initial INVITE request", status); + + /* Upon failure to send first request, both dialog and invite + * session would have been cleared. + */ + inv = NULL; + dlg = NULL; goto on_error; } @@ -242,7 +252,7 @@ pj_status_t pjsua_make_call(int acc_index, on_error: if (inv != NULL) { pjsip_inv_terminate(inv, PJSIP_SC_OK, PJ_FALSE); - } else { + } else if (dlg) { pjsip_dlg_terminate(dlg); } @@ -253,6 +263,36 @@ on_error: } +/** + * Answer call. + */ +PJ_DEF(void) pjsua_call_answer(int call_index, int code) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_ASSERT_ON_FAIL(call_index >= 0 && + call_index < (int)pjsua.config.max_calls, + return); + + if (pjsua.calls[call_index].inv == NULL) { + PJ_LOG(3,(THIS_FILE, "Call %d already disconnected")); + return; + } + + status = pjsip_inv_answer(pjsua.calls[call_index].inv, + code, NULL, NULL, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_inv_send_msg(pjsua.calls[call_index].inv, + tdata); + + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Unable to create/send response", + status); + +} + + /** * Handle incoming INVITE request. */ @@ -265,7 +305,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) unsigned options = 0; pjsip_inv_session *inv = NULL; int acc_index; - int call_index = -1; + unsigned call_index; pjmedia_sdp_session *answer; pj_status_t status; @@ -312,7 +352,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) */ /* Find free call slot. */ - for (call_index=0; call_index < pjsua.max_calls; ++call_index) { + for (call_index=0; call_index < pjsua.config.max_calls; ++call_index) { if (pjsua.calls[call_index].inv == NULL) break; } @@ -353,7 +393,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) /* Create dialog: */ status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, - &pjsua.acc[acc_index].contact_uri, + &pjsua.config.acc_config[acc_index].contact, &dlg); if (status != PJ_SUCCESS) { pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, @@ -387,8 +427,8 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) */ status = pjsip_inv_initial_answer(inv, rdata, - (pjsua.auto_answer ? pjsua.auto_answer - : 100), + (pjsua.config.auto_answer ? + pjsua.config.auto_answer : 100), NULL, NULL, &response); if (status != PJ_SUCCESS) { @@ -400,7 +440,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) /* If failed to send 2xx response, there's a good chance that it is * because SDP negotiation has failed. */ - if (pjsua.auto_answer/100 == 2) + if (pjsua.config.auto_answer/100 == 2) st_code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE; else st_code = 500; @@ -415,7 +455,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) pjsua_perror(THIS_FILE, "Unable to send 100 response", status); } - if (pjsua.auto_answer < 200) { + if (pjsua.config.auto_answer < 200) { PJ_LOG(3,(THIS_FILE, "\nIncoming call!!\n" "From: %.*s\n" @@ -432,26 +472,26 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) 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.config.auto_answer, + pjsip_get_status_text(pjsua.config.auto_answer)->ptr )); } ++pjsua.call_cnt; /* Schedule timer to refresh. */ - if (pjsua.uas_refresh > 0) { + if (pjsua.config.uas_refresh > 0) { schedule_call_timer( &pjsua.calls[call_index], &pjsua.calls[call_index].refresh_tm, REFRESH_CALL_TIMER, - pjsua.uas_refresh); + pjsua.config.uas_refresh); } /* Schedule timer to hangup call. */ - if (pjsua.uas_duration > 0) { + if (pjsua.config.uas_duration > 0) { schedule_call_timer( &pjsua.calls[call_index], &pjsua.calls[call_index].hangup_tm, HANGUP_CALL_TIMER, - pjsua.uas_duration); + pjsua.config.uas_duration); } /* This INVITE request has been handled. */ @@ -550,7 +590,8 @@ static void pjsua_call_on_state_changed(pjsip_inv_session *inv, } - pjsua_ui_on_call_state(call->index, e); + if (pjsua.cb.on_call_state) + (*pjsua.cb.on_call_state)(call->index, e); /* call->inv may be NULL now */ @@ -965,7 +1006,7 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, return; } - if (pjsua.null_audio) + if (pjsua.config.null_audio) return; /* Create media session info based on SDP parameters. @@ -983,9 +1024,10 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, } /* Override ptime, if this option is specified. */ - if (pjsua.ptime) { + if (pjsua.config.ptime) { sess_info.stream_info[0].param->setting.frm_per_pkt = (pj_uint8_t) - (pjsua.ptime / sess_info.stream_info[0].param->info.frm_ptime); + (pjsua.config.ptime / + sess_info.stream_info[0].param->info.frm_ptime); if (sess_info.stream_info[0].param->setting.frm_per_pkt==0) sess_info.stream_info[0].param->setting.frm_per_pkt = 1; } @@ -1037,7 +1079,7 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, /* If auto-play is configured, connect the call to the file player * port */ - if (pjsua.auto_play && pjsua.wav_file && + if (pjsua.config.auto_play && pjsua.config.wav_file.slen && call->inv->role == PJSIP_ROLE_UAS) { @@ -1045,19 +1087,19 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, call->conf_slot, 0); } - if (pjsua.auto_loop && call->inv->role == PJSIP_ROLE_UAS) { + if (pjsua.config.auto_loop && call->inv->role == PJSIP_ROLE_UAS) { pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, call->conf_slot, 0); } - if (pjsua.auto_conf) { - int i; + if (pjsua.config.auto_conf) { + unsigned i; pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot, 0); pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0, 0); - for (i=0; i < pjsua.max_calls; ++i) { + for (i=0; i < pjsua.config.max_calls; ++i) { if (!pjsua.calls[i].session) continue; @@ -1073,8 +1115,8 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, /* Normal operation: if no auto_xx is given, connect new call to * the sound device port (port zero) in the main conference bridge. */ - if (pjsua.auto_play == 0 && pjsua.auto_loop == 0 && - pjsua.auto_conf == 0) + if (pjsua.config.auto_play == 0 && pjsua.config.auto_loop == 0 && + pjsua.config.auto_conf == 0) { pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot, 0); pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0, 0); @@ -1127,7 +1169,7 @@ static void pjsua_call_on_media_update(pjsip_inv_session *inv, /* * Hangup call. */ -void pjsua_call_hangup(int call_index) +PJ_DEF(void) pjsua_call_hangup(int call_index) { pjsua_call *call; int code; @@ -1177,7 +1219,7 @@ void pjsua_call_hangup(int call_index) /* * Put call on-Hold. */ -void pjsua_call_set_hold(int call_index) +PJ_DEF(void) pjsua_call_set_hold(int call_index) { pjmedia_sdp_session *sdp; pjsua_call *call; @@ -1218,7 +1260,7 @@ void pjsua_call_set_hold(int call_index) /* * re-INVITE. */ -void pjsua_call_reinvite(int call_index) +PJ_DEF(void) pjsua_call_reinvite(int call_index) { pjmedia_sdp_session *sdp; pjsip_tx_data *tdata; @@ -1265,7 +1307,7 @@ void pjsua_call_reinvite(int call_index) /* * Transfer call. */ -void pjsua_call_xfer(int call_index, const char *dest) +PJ_DEF(void) pjsua_call_xfer(int call_index, const char *dest) { pjsip_evsub *sub; pjsip_tx_data *tdata; @@ -1317,7 +1359,7 @@ void pjsua_call_xfer(int call_index, const char *dest) /** * Send instant messaging inside INVITE session. */ -void pjsua_call_send_im(int call_index, const char *str) +PJ_DECL(void) pjsua_call_send_im(int call_index, const char *str) { pjsua_call *call; const pj_str_t mime_text = pj_str("text"); @@ -1373,7 +1415,7 @@ on_return: /** * Send IM typing indication inside INVITE session. */ -void pjsua_call_typing(int call_index, pj_bool_t is_typing) +PJ_DECL(void) pjsua_call_typing(int call_index, pj_bool_t is_typing) { pjsua_call *call; pjsip_tx_data *tdata; @@ -1415,11 +1457,11 @@ on_return: /* * Terminate all calls. */ -void pjsua_call_hangup_all(void) +PJ_DEF(void) pjsua_call_hangup_all(void) { - int i; + unsigned i; - 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 +#include +#include /* atoi */ +#include + +#define THIS_FILE "main.c" + +/* Current dialog */ +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.config.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.config.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. + */ +static void console_on_call_state(int call_index, pjsip_event *e) +{ + pjsua_call *call = &pjsua.calls[call_index]; + + PJ_UNUSED_ARG(e); + + if (call->inv->state == PJSIP_INV_STATE_DISCONNECTED) { + + PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", + call_index, + call->inv->cause, + pjsip_get_status_text(call->inv->cause)->ptr)); + + call->inv = NULL; + if ((int)call->index == current_call) { + find_next_call(); + } + + } else { + + PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", + call_index, + pjsua_inv_state_names[call->inv->state])); + + if (call && current_call==-1) + current_call = call->index; + + } +} + +/** + * Notify UI when registration status has changed. + */ +static void console_on_reg_state(int acc_index) +{ + PJ_UNUSED_ARG(acc_index); + + // Log already written. +} + + +/** + * Incoming IM message (i.e. MESSAGE request)! + */ +static void console_on_pager(int call_index, const pj_str_t *from, + const pj_str_t *to, const pj_str_t *text) +{ + /* Note: call index may be -1 */ + PJ_UNUSED_ARG(call_index); + PJ_UNUSED_ARG(to); + + PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s", + (int)from->slen, from->ptr, + (int)text->slen, text->ptr)); +} + + +/** + * Typing indication + */ +static void console_on_typing(int call_index, const pj_str_t *from, + const pj_str_t *to, pj_bool_t is_typing) +{ + PJ_UNUSED_ARG(call_index); + PJ_UNUSED_ARG(to); + + PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s", + (int)from->slen, from->ptr, + (is_typing?"is typing..":"has stopped typing"))); +} + + +/* + * Print buddy list. + */ +static void print_buddy_list(void) +{ + int i; + + puts("Buddy list:"); + + if (pjsua.buddy_cnt == 0) + puts(" -none-"); + else { + for (i=0; i %s\n", + i+1, status, pjsua.buddies[i].uri.ptr); + } + } + puts(""); +} + + +/* + * Print account status. + */ +static void print_acc_status(int acc_index) +{ + char reg_status[128]; + + if (pjsua.acc[acc_index].regc == NULL) { + pj_ansi_strcpy(reg_status, " -not registered to server-"); + + } else if (pjsua.acc[acc_index].reg_last_err != PJ_SUCCESS) { + pj_strerror(pjsua.acc[acc_index].reg_last_err, reg_status, sizeof(reg_status)); + + } else if (pjsua.acc[acc_index].reg_last_code>=200 && + pjsua.acc[acc_index].reg_last_code<=699) { + + pjsip_regc_info info; + const pj_str_t *status_str; + + pjsip_regc_get_info(pjsua.acc[acc_index].regc, &info); + + status_str = pjsip_get_status_text(pjsua.acc[acc_index].reg_last_code); + pj_ansi_snprintf(reg_status, sizeof(reg_status), + "%s (%.*s;expires=%d)", + status_str->ptr, + (int)info.client_uri.slen, + info.client_uri.ptr, + info.next_reg); + + } else { + pj_ansi_sprintf(reg_status, "in progress (%d)", + pjsua.acc[acc_index].reg_last_code); + } + + 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; i<(int)pjsua.config.acc_cnt; ++i) + print_acc_status(i); + + print_buddy_list(); + + //puts("Commands:"); + puts("+=============================================================================+"); + puts("| Call Commands: | IM & Presence: | Misc: |"); + puts("| | | |"); + puts("| m Make new call | i Send IM | o Send OPTIONS |"); + puts("| M Make multiple calls | s Subscribe presence | rr (Re-)register |"); + puts("| a Answer call | u Unsubscribe presence | ru Unregister |"); + puts("| h Hangup call (ha=all) | t ToGgle Online status | |"); + puts("| H Hold call | | |"); + puts("| v re-inVite (release hold) +--------------------------+-------------------+"); + puts("| ] Select next dialog | Conference Command | |"); + puts("| [ Select previous dialog | cl List ports | d Dump status |"); + puts("| x Xfer call | cc Connect port | dd Dump detailed |"); + puts("| # Send DTMF string | cd Disconnect port | dc Dump config |"); + puts("+------------------------------+--------------------------+-------------------+"); + puts("| q QUIT |"); + puts("+=============================================================================+"); +} + + +/* + * Input simple string + */ +static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len) +{ + char *p; + + printf("%s (empty to cancel): ", title); fflush(stdout); + fgets(buf, len, stdin); + + /* Remove trailing newlines. */ + for (p=buf; ; ++p) { + if (*p=='\r' || *p=='\n') *p='\0'; + else if (!*p) break; + } + + if (!*buf) + return PJ_FALSE; + + return PJ_TRUE; +} + + +#define NO_NB -2 +struct input_result +{ + int nb_result; + char *uri_result; +}; + + +/* + * Input URL. + */ +static void ui_input_url(const char *title, char *buf, int len, + struct input_result *result) +{ + result->nb_result = NO_NB; + result->uri_result = NULL; + + print_buddy_list(); + + printf("Choices:\n" + " 0 For current dialog.\n" + " -1 All %d buddies in buddy list\n" + " [1 -%2d] Select from buddy list\n" + " URL An URL\n" + " Empty input (or 'q') to cancel\n" + , pjsua.buddy_cnt, pjsua.buddy_cnt); + printf("%s: ", title); + + fflush(stdout); + fgets(buf, len, stdin); + len = strlen(buf); + + /* Left trim */ + while (pj_isspace(*buf)) { + ++buf; + --len; + } + + /* Remove trailing newlines */ + while (len && (buf[len-1] == '\r' || buf[len-1] == '\n')) + buf[--len] = '\0'; + + if (len == 0 || buf[0]=='q') + return; + + if (pj_isdigit(*buf) || *buf=='-') { + + int i; + + if (*buf=='-') + i = 1; + else + i = 0; + + for (; inb_result = atoi(buf); + + if (result->nb_result >= 0 && result->nb_result <= (int)pjsua.buddy_cnt) { + return; + } + if (result->nb_result == -1) + return; + + puts("Invalid input"); + result->nb_result = NO_NB; + return; + + } else { + pj_status_t status; + + if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Invalid URL", status); + return; + } + + result->uri_result = buf; + } +} + +static void conf_list(void) +{ + unsigned i, count; + pjmedia_conf_port_info info[PJSUA_MAX_CALLS]; + + printf("Conference ports:\n"); + + count = PJ_ARRAY_SIZE(info); + pjmedia_conf_get_ports_info(pjsua.mconf, &count, info); + for (i=0; ilistener[j]) { + pj_ansi_sprintf(s, "#%d ", j); + pj_ansi_strcat(txlist, s); + } + } + printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n", + port_info->slot, + port_info->clock_rate/1000, + port_info->samples_per_frame * 1000 / port_info->clock_rate, + (int)port_info->name.slen, + port_info->name.ptr, + txlist); + + } + puts(""); +} + + +void pjsua_console_app_main(void) +{ + char menuin[10]; + char buf[128]; + char text[128]; + int i, count; + char *uri; + struct input_result result; + + + /* If user specifies URI to call, then call the URI */ + if (pjsua.config.uri_to_call.slen) { + pjsua_make_call( current_acc, pjsua.config.uri_to_call.ptr, NULL); + } + + keystroke_help(); + + for (;;) { + + printf(">>> "); + fflush(stdout); + + fgets(menuin, sizeof(menuin), stdin); + + switch (menuin[0]) { + + case 'm': + /* Make call! : */ + printf("(You currently have %d calls)\n", pjsua.call_cnt); + + uri = NULL; + ui_input_url("Make call", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + + if (result.nb_result == -1 || result.nb_result == 0) { + puts("You can't do that with make call!"); + continue; + } else { + uri = pjsua.buddies[result.nb_result-1].uri.ptr; + } + + } else if (result.uri_result) { + uri = result.uri_result; + } + + pjsua_make_call( current_acc, uri, NULL); + break; + + case 'M': + /* Make multiple calls! : */ + printf("(You currently have %d calls)\n", pjsua.call_cnt); + + if (!simple_input("Number of calls", menuin, sizeof(menuin))) + continue; + + count = atoi(menuin); + if (count < 1) + continue; + + ui_input_url("Make call", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + if (result.nb_result == -1 || result.nb_result == 0) { + puts("You can't do that with make call!"); + continue; + } + uri = pjsua.buddies[result.nb_result-1].uri.ptr; + } else { + uri = result.uri_result; + } + + for (i=0; irole != PJSIP_ROLE_UAS || + pjsua.calls[current_call].inv->state >= PJSIP_INV_STATE_CONNECTING) + { + puts("No pending incoming call"); + fflush(stdout); + continue; + + } else { + if (!simple_input("Answer with code (100-699)", buf, sizeof(buf))) + continue; + + if (atoi(buf) < 100) + continue; + + /* + * Must check again! + * Call may have been disconnected while we're waiting for + * keyboard input. + */ + if (current_call == -1) { + puts("Call has been disconnected"); + fflush(stdout); + continue; + } + + pjsua_call_answer(current_call, atoi(buf)); + } + + break; + + + case 'h': + + if (current_call == -1) { + puts("No current call"); + fflush(stdout); + continue; + + } else if (menuin[1] == 'a') { + + /* Hangup all calls */ + pjsua_call_hangup_all(); + + } else { + + /* Hangup current calls */ + pjsua_call_hangup(current_call); + } + break; + + case ']': + case '[': + /* + * Cycle next/prev dialog. + */ + if (menuin[0] == ']') { + find_next_call(); + + } else { + find_prev_call(); + } + + if (current_call != -1) { + char url[PJSIP_MAX_URL_SIZE]; + int len; + const pjsip_uri *u; + + u = pjsua.calls[current_call].inv->dlg->remote.info->uri; + len = pjsip_uri_print(0, u, url, sizeof(url)-1); + if (len < 1) { + pj_ansi_strcpy(url, ""); + } else { + url[len] = '\0'; + } + + PJ_LOG(3,(THIS_FILE,"Current dialog: %s", url)); + + } else { + PJ_LOG(3,(THIS_FILE,"No current dialog")); + } + break; + + case 'H': + /* + * Hold call. + */ + if (current_call != -1) { + + pjsua_call_set_hold(current_call); + + } else { + PJ_LOG(3,(THIS_FILE, "No current call")); + } + break; + + case 'v': + /* + * Send re-INVITE (to release hold, etc). + */ + if (current_call != -1) { + + pjsua_call_reinvite(current_call); + + } else { + PJ_LOG(3,(THIS_FILE, "No current call")); + } + break; + + case 'x': + /* + * Transfer call. + */ + if (current_call == -1) { + + PJ_LOG(3,(THIS_FILE, "No current call")); + + } else { + int call = current_call; + + ui_input_url("Transfer to URL", buf, sizeof(buf), &result); + + /* Check if call is still there. */ + + if (call != current_call) { + puts("Call has been disconnected"); + continue; + } + + if (result.nb_result != NO_NB) { + if (result.nb_result == -1 || result.nb_result == 0) + puts("You can't do that with transfer call!"); + else + pjsua_call_xfer( current_call, + pjsua.buddies[result.nb_result-1].uri.ptr); + + } else if (result.uri_result) { + pjsua_call_xfer( current_call, result.uri_result); + } + } + break; + + case '#': + /* + * Send DTMF strings. + */ + if (current_call == -1) { + + PJ_LOG(3,(THIS_FILE, "No current call")); + + } else if (pjsua.calls[current_call].session == NULL) { + + PJ_LOG(3,(THIS_FILE, "Media is not established yet!")); + + } else { + pj_str_t digits; + int call = current_call; + pj_status_t status; + + if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, + sizeof(buf))) + { + break; + } + + if (call != current_call) { + puts("Call has been disconnected"); + continue; + } + + digits = pj_str(buf); + 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); + } else { + puts("DTMF digits enqueued for transmission"); + } + } + break; + + case 's': + case 'u': + /* + * Subscribe/unsubscribe presence. + */ + ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + if (result.nb_result == -1) { + int i; + for (i=0; imsg_info.len, + pjsip_rx_data_get_info(rdata), + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + rdata->msg_info.msg_buf)); + + /* Always return false, otherwise messages will not get processed! */ + return PJ_FALSE; +} + +/* Notification on outgoing messages */ +static pj_status_t console_on_tx_msg(pjsip_tx_data *tdata) +{ + + /* Important note: + * tp_info field is only valid after outgoing messages has passed + * transport layer. So don't try to access tp_info when the module + * has lower priority than transport layer. + */ + + PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n" + "%s\n" + "--end msg--", + (tdata->buf.cur - tdata->buf.start), + pjsip_tx_data_get_info(tdata), + tdata->tp_info.dst_name, + tdata->tp_info.dst_port, + tdata->buf.start)); + + /* Always return success, otherwise message will not get sent! */ + return PJ_SUCCESS; +} + +/* The module instance. */ +pjsip_module pjsua_console_app_msg_logger = +{ + NULL, NULL, /* prev, next. */ + { "mod-pjsua-log", 13 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &console_on_rx_msg, /* on_rx_request() */ + &console_on_rx_msg, /* on_rx_response() */ + &console_on_tx_msg, /* on_tx_request. */ + &console_on_tx_msg, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + + +/***************************************************************************** + * Console application custom logging: + */ + + +static FILE *log_file; + + +static void app_log_writer(int level, const char *buffer, int len) +{ + /* Write to both stdout and file. */ + + if (level <= (int)pjsua.config.app_log_level) + pj_log_write(level, buffer, len); + + if (log_file) { + fwrite(buffer, len, 1, log_file); + fflush(log_file); + } +} + + +pj_status_t pjsua_console_app_logging_init(const pjsua_config *cfg) +{ + /* Redirect log function to ours */ + + pj_log_set_log_func( &app_log_writer ); + + /* If output log file is desired, create the file: */ + + if (cfg->log_filename.slen) { + log_file = fopen(cfg->log_filename.ptr, "wt"); + if (log_file == NULL) { + PJ_LOG(1,(THIS_FILE, "Unable to open log file %s", + cfg->log_filename.ptr)); + return -1; + } + } + + return PJ_SUCCESS; +} + + +void pjsua_console_app_logging_shutdown(void) +{ + /* Close logging file, if any: */ + + if (log_file) { + fclose(log_file); + log_file = NULL; + } +} + +/***************************************************************************** + * Error display: + */ + +/* + * Display error message for the specified error code. + */ +void pjsua_perror(const char *sender, const char *title, + pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + + PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status)); +} + + + +pjsua_callback console_callback = +{ + &console_on_call_state, + &console_on_reg_state, + &console_on_pager, + &console_on_typing, +}; diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 5959d065..dba563ce 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include +#include "pjsua_imp.h" /* * pjsua_core.c @@ -43,74 +44,146 @@ struct pjsua pjsua; /* * Init default application parameters. */ -void pjsua_default(void) +PJ_DEF(void) pjsua_default_config(pjsua_config *cfg) { unsigned i; + pj_memset(cfg, 0, sizeof(pjsua_config)); - /* Normally need another thread for console application, because main - * thread will be blocked in fgets(). - */ - pjsua.thread_cnt = 1; + cfg->thread_cnt = 1; + cfg->udp_port = 5060; + cfg->start_rtp_port = 4000; + cfg->max_calls = 4; + cfg->conf_ports = 0; +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + pjsua.clock_rate = 44100; +#endif - /* Default transport settings: */ - pjsua.sip_port = 5060; + cfg->complexity = 10; + cfg->quality = 10; + + cfg->auto_answer = 100; + cfg->uas_duration = 3600; + /* Default logging settings: */ + cfg->log_level = 5; + cfg->app_log_level = 4; + cfg->log_decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | + PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE; - /* Default we start RTP at port 4000 */ - pjsua.start_rtp_port = 4000; + /* Also init logging settings in pjsua.config, because log + * may be written before pjsua_init() is called. + */ + pjsua.config.log_level = 5; + pjsua.config.app_log_level = 4; - /* 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; + + /* Init accounts: */ + for (i=0; iacc_config[i].reg_timeout = 55; + } +} - /* Default call settings. */ - pjsua.uas_refresh = -1; - pjsua.uas_duration = -1; +#define strncpy_with_null(dst,src,len) \ +do { \ + strncpy(dst, src, len); \ + dst[len-1] = '\0'; \ +} while (0) - /* Default: do not use STUN: */ - pjsua.stun_port1 = pjsua.stun_port2 = 0; - /* Default for media: */ -#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 - pjsua.clock_rate = 44100; -#endif - pjsua.complexity = -1; - pjsua.quality = 4; +PJ_DEF(pj_status_t) pjsua_test_config( const pjsua_config *cfg, + char *errmsg, + int len) +{ + unsigned i; - /* Init accounts: */ - pjsua.acc_cnt = 1; - for (i=0; iudp_port == 0) { + if (cfg->sip_host.slen==0 || cfg->sip_port==0) { + strncpy_with_null(errmsg, + "sip_host and sip_port must be specified", + len); + return -1; + } } - /* Init call array: */ - for (i=0; imax_calls < 1) { + strncpy_with_null(errmsg, + "max_calls needs to be at least 1", + len); + return -1; } - /* Default max nb of calls. */ - pjsua.max_calls = 4; + /* STUN */ + if (cfg->stun_srv1.slen || cfg->stun_port1 || cfg->stun_port2 || + cfg->stun_srv2.slen) + { + if (cfg->stun_port1 == 0) { + strncpy_with_null(errmsg, "stun_port1 required", len); + return -1; + } + if (cfg->stun_srv1.slen == 0) { + strncpy_with_null(errmsg, "stun_srv1 required", len); + return -1; + } + if (cfg->stun_port2 == 0) { + strncpy_with_null(errmsg, "stun_port2 required", len); + return -1; + } + if (cfg->stun_srv2.slen == 0) { + strncpy_with_null(errmsg, "stun_srv2 required", len); + return -1; + } + } - /* Init server presence subscription list: */ - + /* Verify accounts */ + for (i=0; iacc_cnt; ++i) { + const pjsua_acc_config *acc_cfg = &cfg->acc_config[i]; + unsigned j; -} + if (acc_cfg->id.slen == 0) { + strncpy_with_null(errmsg, "missing account ID", len); + return -1; + } + + if (acc_cfg->id.slen == 0) { + strncpy_with_null(errmsg, "missing registrar URI", len); + return -1; + } + + if (acc_cfg->reg_timeout == 0) { + strncpy_with_null(errmsg, "missing registration timeout", len); + return -1; + } + + + for (j=0; jcred_count; ++j) { + if (acc_cfg->cred_info[j].scheme.slen == 0) { + strncpy_with_null(errmsg, "missing auth scheme in account", + len); + return -1; + } + + if (acc_cfg->cred_info[j].realm.slen == 0) { + strncpy_with_null(errmsg, "missing realm in account", len); + return -1; + } + + if (acc_cfg->cred_info[j].username.slen == 0) { + strncpy_with_null(errmsg, "missing username in account", len); + return -1; + } + + } + } + + return PJ_SUCCESS; +} /* @@ -152,85 +225,146 @@ static pj_bool_t mod_pjsua_on_rx_response(pjsip_rx_data *rdata) } -/* - * Initialize sockets and optionally get the public address via STUN. - */ -static pj_status_t init_sockets(pj_bool_t sip, - pjmedia_sock_info *skinfo) +static int PJ_THREAD_FUNC pjsua_poll(void *arg) { - enum { - RTP_RETRY = 100 - }; - enum { - SIP_SOCK, - RTP_SOCK, - RTCP_SOCK, - }; - int i; - static pj_uint16_t rtp_port; - pj_sock_t sock[3]; - pj_sockaddr_in mapped_addr[3]; - pj_status_t status = PJ_SUCCESS; + pj_status_t last_err = 0; - if (rtp_port == 0) - rtp_port = (pj_uint16_t)pjsua.start_rtp_port; + PJ_UNUSED_ARG(arg); - for (i=0; i<3; ++i) - sock[i] = PJ_INVALID_SOCKET; + do { + pj_time_val timeout = { 0, 10 }; + pj_status_t status; + + status = pjsip_endpt_handle_events (pjsua.endpt, &timeout); + if (status != PJ_SUCCESS && status != last_err) { + last_err = status; + pjsua_perror(THIS_FILE, "handle_events() returned error", status); + } + } while (!pjsua.quit_flag); + + return 0; +} + + + +#define pjsua_has_stun() (pjsua.config.stun_port1 && \ + pjsua.config.stun_port2) - /* Create and bind SIP UDP socket. */ - status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[SIP_SOCK]); + +/* + * Create and initialize SIP socket (and possibly resolve public + * address via STUN, depending on config). + */ +static pj_status_t create_sip_udp_sock(int port, + pj_sock_t *p_sock, + pj_sockaddr_in *p_pub_addr) +{ + pj_sock_t sock; + pj_status_t status; + + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "socket() error", status); - goto on_error; + return status; + } + + status = pj_sock_bind_in(sock, 0, (pj_uint16_t)port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "bind() error", status); + pj_sock_close(sock); + return status; } - if (sip) { - status = pj_sock_bind_in(sock[SIP_SOCK], 0, pjsua.sip_port); + if (pjsua_has_stun()) { + status = pj_stun_get_mapped_addr(&pjsua.cp.factory, 1, &sock, + &pjsua.config.stun_srv1, + pjsua.config.stun_port1, + &pjsua.config.stun_srv2, + pjsua.config.stun_port2, + p_pub_addr); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "bind() error", status); - goto on_error; + pjsua_perror(THIS_FILE, "STUN resolve error", status); + pj_sock_close(sock); + return status; } + } else { - status = pj_sock_bind_in(sock[SIP_SOCK], 0, 0); + + const pj_str_t *hostname = pj_gethostname(); + struct pj_hostent he; + + status = pj_gethostbyname(hostname, &he); if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "bind() error", status); - goto on_error; + pjsua_perror(THIS_FILE, "Unable to resolve local host", status); + pj_sock_close(sock); + return status; } + + pj_memset(p_pub_addr, 0, sizeof(pj_sockaddr_in)); + p_pub_addr->sin_family = PJ_AF_INET; + p_pub_addr->sin_port = pj_htons((pj_uint16_t)port); + p_pub_addr->sin_addr = *(pj_in_addr*)he.h_addr; } + *p_sock = sock; + return PJ_SUCCESS; +} + + +/* + * Create RTP and RTCP socket pair, and possibly resolve their public + * address via STUN. + */ +static pj_status_t create_rtp_rtcp_sock(pjmedia_sock_info *skinfo) +{ + enum { + RTP_RETRY = 100 + }; + int i; + static pj_uint16_t rtp_port; + pj_sockaddr_in mapped_addr[2]; + pj_status_t status = PJ_SUCCESS; + pj_sock_t sock[2]; + + if (rtp_port == 0) + rtp_port = (pj_uint16_t)pjsua.config.start_rtp_port; + + for (i=0; i<2; ++i) + sock[i] = PJ_INVALID_SOCKET; + /* Loop retry to bind RTP and RTCP sockets. */ for (i=0; irtp_sock = sock[RTP_SOCK]; + skinfo->rtp_sock = sock[0]; pj_memcpy(&skinfo->rtp_addr_name, - &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in)); + &mapped_addr[0], sizeof(pj_sockaddr_in)); - skinfo->rtcp_sock = sock[RTCP_SOCK]; + skinfo->rtcp_sock = sock[1]; pj_memcpy(&skinfo->rtcp_addr_name, - &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in)); + &mapped_addr[1], sizeof(pj_sockaddr_in)); - if (sip) { - PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d", - pj_inet_ntoa(pjsua.sip_sock_name.sin_addr), - pj_ntohs(pjsua.sip_sock_name.sin_port))); - } PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d", pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr), pj_ntohs(skinfo->rtp_addr_name.sin_port))); @@ -328,9 +448,7 @@ static pj_status_t init_sockets(pj_bool_t sip, return PJ_SUCCESS; on_error: - for (i=0; i<3; ++i) { - if (sip && i==0) - continue; + for (i=0; i<2; ++i) { if (sock[i] != PJ_INVALID_SOCKET) pj_sock_close(sock[i]); } @@ -339,37 +457,363 @@ on_error: -/* - * Initialize stack. +/** + * Create pjsua application. + * This initializes pjlib/pjlib-util, and creates memory pool factory to + * be used by application. */ -static pj_status_t init_stack(void) +PJ_DEF(pj_status_t) pjsua_create(void) { pj_status_t status; + /* Init PJLIB: */ + + status = pj_init(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pj_init() error", status); + return status; + } + + /* Init PJLIB-UTIL: */ + + status = pjlib_util_init(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pjlib_util_init() error", status); + return status; + } + + /* Init memory pool: */ + + /* Init caching pool. */ + pj_caching_pool_init(&pjsua.cp, &pj_pool_factory_default_policy, 0); + + /* Create memory pool for application. */ + pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL); + + /* Must create endpoint to initialize SIP parser. */ /* Create global endpoint: */ - { - const pj_str_t *hostname; - const char *endpt_name; + status = pjsip_endpt_create(&pjsua.cp.factory, + pj_gethostname()->ptr, + &pjsua.endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create SIP endpoint", status); + return status; + } - /* Endpoint MUST be assigned a globally unique name. - * The name will be used as the hostname in Warning header. - */ + /* Must create media endpoint too */ + status = pjmedia_endpt_create(&pjsua.cp.factory, + pjsip_endpt_get_ioqueue(pjsua.endpt), 0, + &pjsua.med_endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Media stack initialization has returned error", + status); + return status; + } + + + return PJ_SUCCESS; +} + + + +/* + * Init media. + */ +static pj_status_t init_media(void) +{ + int i; + unsigned options; + unsigned clock_rate; + unsigned samples_per_frame; + pj_str_t codec_id; + pj_status_t status; - /* For this implementation, we'll use hostname for simplicity */ - hostname = pj_gethostname(); - endpt_name = hostname->ptr; + /* Register all codecs */ +#if PJMEDIA_HAS_SPEEX_CODEC + /* Register speex. */ + status = pjmedia_codec_speex_init(pjsua.med_endpt, + PJMEDIA_SPEEX_NO_UWB, + pjsua.config.quality, + pjsua.config.complexity ); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing Speex codec", + status); + return status; + } + + /* Set "speex/16000/1" to have highest priority */ + codec_id = pj_str("speex/16000/1"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua.med_endpt), + &codec_id, + PJMEDIA_CODEC_PRIO_HIGHEST); + +#endif /* PJMEDIA_HAS_SPEEX_CODEC */ + +#if PJMEDIA_HAS_GSM_CODEC + /* Register GSM */ + status = pjmedia_codec_gsm_init(pjsua.med_endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing GSM codec", + status); + return status; + } +#endif /* PJMEDIA_HAS_GSM_CODEC */ + +#if PJMEDIA_HAS_G711_CODEC + /* Register PCMA and PCMU */ + status = pjmedia_codec_g711_init(pjsua.med_endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing G711 codec", + status); + return status; + } +#endif /* PJMEDIA_HAS_G711_CODEC */ + +#if PJMEDIA_HAS_L16_CODEC + /* Register L16 family codecs, but disable all */ + status = pjmedia_codec_l16_init(pjsua.med_endpt, 0); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error initializing L16 codecs", + status); + return status; + } + + /* Disable ALL L16 codecs */ + codec_id = pj_str("L16"); + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua.med_endpt), + &codec_id, + PJMEDIA_CODEC_PRIO_DISABLED); + +#endif /* PJMEDIA_HAS_L16_CODEC */ + + + /* Enable those codecs that user put with "--add-codec", and move + * the priority to top + */ + for (i=0; i<(int)pjsua.config.codec_cnt; ++i) { + pjmedia_codec_mgr_set_codec_priority( + pjmedia_endpt_get_codec_mgr(pjsua.med_endpt), + &pjsua.config.codec_arg[i], + PJMEDIA_CODEC_PRIO_HIGHEST); + } + + + /* Init options for conference bridge. */ + options = 0; + + /* Calculate maximum number of ports, if it's not specified */ + if (pjsua.config.conf_ports == 0) { + pjsua.config.conf_ports = 3 * pjsua.config.max_calls; + } + + /* Init conference bridge. */ + clock_rate = pjsua.config.clock_rate ? pjsua.config.clock_rate : 16000; + samples_per_frame = clock_rate * 10 / 1000; + status = pjmedia_conf_create(pjsua.pool, + pjsua.config.conf_ports, + clock_rate, + 1, /* mono */ + samples_per_frame, + 16, + options, + &pjsua.mconf); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Media stack initialization has returned error", + status); + return status; + } + + /* Create WAV file player if required: */ + + if (pjsua.config.wav_file.slen) { + pj_str_t port_name; + + /* Create the file player port. */ + status = pjmedia_wav_player_port_create( pjsua.pool, + pjsua.config.wav_file.ptr, + 0, 0, -1, NULL, + &pjsua.file_port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Error playing media file", + status); + return status; + } + + /* Add port to conference bridge: */ + port_name = pjsua.config.wav_file; + status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, + pjsua.file_port, + &port_name, + &pjsua.wav_slot); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to add file player to conference bridge", + status); + return status; + } + } + + + return PJ_SUCCESS; +} + + +/* + * Copy configuration. + */ +static void copy_config(pj_pool_t *pool, pjsua_config *dst, + const pjsua_config *src) +{ + unsigned i; + + /* Plain memcpy */ + pj_memcpy(dst, src, sizeof(pjsua_config)); + + /* Duplicate strings */ + pj_strdup_with_null(pool, &dst->sip_host, &src->sip_host); + pj_strdup_with_null(pool, &dst->stun_srv1, &src->stun_srv1); + pj_strdup_with_null(pool, &dst->stun_srv2, &src->stun_srv2); + pj_strdup_with_null(pool, &dst->wav_file, &src->wav_file); + + for (i=0; icodec_cnt; ++i) { + pj_strdup_with_null(pool, &dst->codec_arg[i], &src->codec_arg[i]); + } + + pj_strdup_with_null(pool, &dst->outbound_proxy, &src->outbound_proxy); + pj_strdup_with_null(pool, &dst->uri_to_call, &src->uri_to_call); + + for (i=0; iacc_cnt; ++i) { + pjsua_acc_config *dst_acc = &dst->acc_config[i]; + const pjsua_acc_config *src_acc = &src->acc_config[i]; + unsigned j; + + pj_strdup_with_null(pool, &dst_acc->id, &src_acc->id); + pj_strdup_with_null(pool, &dst_acc->reg_uri, &src_acc->reg_uri); + pj_strdup_with_null(pool, &dst_acc->contact, &src_acc->contact); + pj_strdup_with_null(pool, &dst_acc->proxy, &src_acc->proxy); + + for (j=0; jcred_count; ++j) { + pj_strdup_with_null(pool, &dst_acc->cred_info[j].realm, + &src_acc->cred_info[j].realm); + pj_strdup_with_null(pool, &dst_acc->cred_info[j].scheme, + &src_acc->cred_info[j].scheme); + pj_strdup_with_null(pool, &dst_acc->cred_info[j].username, + &src_acc->cred_info[j].username); + pj_strdup_with_null(pool, &dst_acc->cred_info[j].data, + &src_acc->cred_info[j].data); + } + } + + pj_strdup_with_null(pool, &dst->log_filename, &src->log_filename); + + for (i=0; ibuddy_cnt; ++i) { + pj_strdup_with_null(pool, &dst->buddy_uri[i], &src->buddy_uri[i]); + } +} + + +/* + * Initialize pjsua application. + * This will initialize all libraries, create endpoint instance, and register + * pjsip modules. + */ +PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg, + const pjsua_callback *cb) +{ + char errmsg[80]; + unsigned i; + pj_status_t status; + + + /* Init accounts: */ + for (i=0; isip_host.slen == 0 || cfg->sip_port == 0) { + PJ_LOG(1,(THIS_FILE, + "Error: sip_host and sip_port must be specified")); + return PJ_EINVAL; + } + + pjsua.sip_sock = PJ_INVALID_SOCKET; + } + + + /* Init media endpoint */ + status = init_media(); + if (status != PJ_SUCCESS) + return status; - /* Create the endpoint: */ - status = pjsip_endpt_create(&pjsua.cp.factory, endpt_name, - &pjsua.endpt); + /* Init RTP sockets, only when UDP transport is enabled */ + for (i=0; pjsua.config.start_rtp_port && imsg_info.to->uri; - /* Just return account #0 if To URI is not SIP: */ + /* Just return last account if To URI is not SIP: */ if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) { - return 0; + return pjsua.config.acc_cnt; } 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) { + for (acc_index=0; acc_index < pjsua.config.acc_cnt; ++acc_index) { pjsua_acc *acc = &pjsua.acc[acc_index]; @@ -584,7 +936,7 @@ int pjsua_find_account_for_incoming(pjsip_rx_data *rdata) } /* No matching, try match domain part only. */ - for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) { + for (acc_index=0; acc_index < pjsua.config.acc_cnt; ++acc_index) { pjsua_acc *acc = &pjsua.acc[acc_index]; @@ -594,8 +946,8 @@ int pjsua_find_account_for_incoming(pjsip_rx_data *rdata) } } - /* Still no match, just return account #0 */ - return 0; + /* Still no match, just return last account */ + return pjsua.config.acc_cnt; } @@ -611,209 +963,34 @@ int pjsua_find_account_for_outgoing(const pj_str_t *url) } -/* - * Init media. - */ -static pj_status_t init_media(void) -{ - int i; - unsigned options; - unsigned clock_rate; - unsigned samples_per_frame; - pj_str_t codec_id; - pj_status_t status; - - /* Register all codecs */ -#if PJMEDIA_HAS_SPEEX_CODEC - /* Register speex. */ - status = pjmedia_codec_speex_init(pjsua.med_endpt, - PJMEDIA_SPEEX_NO_UWB, - pjsua.quality, pjsua.complexity ); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing Speex codec", - status); - return status; - } - - /* Set "speex/16000/1" to have highest priority */ - codec_id = pj_str("speex/16000/1"); - pjmedia_codec_mgr_set_codec_priority( - pjmedia_endpt_get_codec_mgr(pjsua.med_endpt), - &codec_id, - PJMEDIA_CODEC_PRIO_HIGHEST); - -#endif /* PJMEDIA_HAS_SPEEX_CODEC */ - -#if PJMEDIA_HAS_GSM_CODEC - /* Register GSM */ - status = pjmedia_codec_gsm_init(pjsua.med_endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing GSM codec", - status); - return status; - } -#endif /* PJMEDIA_HAS_GSM_CODEC */ - -#if PJMEDIA_HAS_G711_CODEC - /* Register PCMA and PCMU */ - status = pjmedia_codec_g711_init(pjsua.med_endpt); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing G711 codec", - status); - return status; - } -#endif /* PJMEDIA_HAS_G711_CODEC */ - -#if PJMEDIA_HAS_L16_CODEC - /* Register L16 family codecs, but disable all */ - status = pjmedia_codec_l16_init(pjsua.med_endpt, 0); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Error initializing L16 codecs", - status); - return status; - } - - /* Disable ALL L16 codecs */ - codec_id = pj_str("L16"); - pjmedia_codec_mgr_set_codec_priority( - pjmedia_endpt_get_codec_mgr(pjsua.med_endpt), - &codec_id, - PJMEDIA_CODEC_PRIO_DISABLED); - -#endif /* PJMEDIA_HAS_L16_CODEC */ - - - /* Enable those codecs that user put with "--add-codec", and move - * the priority to top - */ - for (i=0; iinfo.name, NULL ); - - /* Create WAV file player if required: */ - - if (pjsua.wav_file) { - pj_str_t port_name; - - /* Create the file player port. */ - status = pjmedia_wav_player_port_create( pjsua.pool, pjsua.wav_file, - 0, 0, -1, NULL, - &pjsua.file_port); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Error playing media file", - status); - return status; - } - - /* Add port to conference bridge: */ - status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, - pjsua.file_port, - pj_cstr(&port_name, pjsua.wav_file), - &pjsua.wav_slot); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Unable to add file player to conference bridge", - status); - return status; - } - } - - - return PJ_SUCCESS; -} - /* * Start pjsua stack. * This will start the registration process, if registration is configured. */ -pj_status_t pjsua_start(void) +PJ_DEF(pj_status_t) pjsua_start(void) { int i; /* Must be signed */ - pjsip_transport *udp_transport; pj_status_t status = PJ_SUCCESS; - /* - * Init media subsystem (codecs, conference bridge, et all). - */ - status = init_media(); - if (status != PJ_SUCCESS) - return status; - - /* Init sockets (STUN etc): */ - for (i=0; i<(int)pjsua.max_calls; ++i) { - status = init_sockets(i==0, &pjsua.calls[i].skinfo); - if (status == PJ_SUCCESS) - status = pjmedia_transport_udp_attach(pjsua.med_endpt, NULL, - &pjsua.calls[i].skinfo, - &pjsua.calls[i].med_tp); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "init_sockets() has returned error", - status); - --i; - if (i >= 0) - pj_sock_close(pjsua.sip_sock); - while (i >= 0) { - pjmedia_transport_udp_close(pjsua.calls[i].med_tp); - } - return status; - } - } /* Add UDP transport: */ + if (pjsua.sip_sock > 0) { - { /* Init the published name for the transport. * Depending whether STUN is used, this may be the STUN mapped * address, or socket's bound address. */ pjsip_host_port addr_name; - addr_name.host.ptr = pj_inet_ntoa(pjsua.sip_sock_name.sin_addr); - addr_name.host.slen = pj_ansi_strlen(addr_name.host.ptr); - addr_name.port = pj_ntohs(pjsua.sip_sock_name.sin_port); + addr_name.host = pjsua.config.sip_host; + addr_name.port = pjsua.config.sip_port; /* Create UDP transport from previously created UDP socket: */ status = pjsip_udp_transport_attach( pjsua.endpt, pjsua.sip_sock, &addr_name, 1, - &udp_transport); + NULL); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to start UDP transport", status); @@ -821,16 +998,37 @@ pj_status_t pjsua_start(void) } } - /* Initialize Contact URI, if one is not specified: */ - for (i=0; i", + pjsua.config.sip_host.ptr, + pjsua.config.sip_port); + + pj_strdup_with_null( pjsua.pool, &acc_cfg->id, &tmp); + acc_cfg->contact = acc_cfg->id; + } + + + /* Initialize accounts: */ + for (i=0; i<(int)pjsua.config.acc_cnt; ++i) { + pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[i]; + pjsua_acc *acc = &pjsua.acc[i]; pjsip_uri *uri; pjsip_sip_uri *sip_uri; /* Need to parse local_uri to get the elements: */ - uri = pjsip_parse_uri(pjsua.pool, pjsua.acc[i].local_uri.ptr, - pjsua.acc[i].local_uri.slen, 0); + uri = pjsip_parse_uri(pjsua.pool, acc_cfg->id.ptr, + acc_cfg->id.slen, 0); if (uri == NULL) { pjsua_perror(THIS_FILE, "Invalid local URI", PJSIP_EINVALIDURI); @@ -852,15 +1050,20 @@ pj_status_t pjsua_start(void) sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri); - pjsua.acc[i].user_part = sip_uri->user; - pjsua.acc[i].host_part = sip_uri->host; + acc->user_part = sip_uri->user; + acc->host_part = sip_uri->host; - if (pjsua.acc[i].contact_uri.slen == 0 && - pjsua.acc[i].local_uri.slen) - { + /* Build Contact header */ + + if (acc_cfg->contact.slen == 0) { char contact[128]; + const char *addr; + int port; int len; + addr = pjsua.config.sip_host.ptr; + port = pjsua.config.sip_port; + /* The local Contact is the username@ip-addr, where * - username is taken from the local URI, * - ip-addr in UDP transport's address name (which may have been @@ -873,21 +1076,17 @@ pj_status_t pjsua_start(void) /* With the user part. */ len = pj_ansi_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); + addr, port); } else { /* Without user part */ len = pj_ansi_snprintf(contact, sizeof(contact), - "", - (int)udp_transport->local_name.host.slen, - udp_transport->local_name.host.ptr, - udp_transport->local_name.port); + "", + addr, port); } if (len < 1 || len >= sizeof(contact)) { @@ -897,38 +1096,39 @@ pj_status_t pjsua_start(void) /* Duplicate Contact uri. */ - pj_strdup2(pjsua.pool, &pjsua.acc[i].contact_uri, contact); + pj_strdup2(pjsua.pool, &acc_cfg->contact, contact); } - } - - /* If outbound_proxy is specified, put it in the route_set: */ - if (pjsua.outbound_proxy.slen) { - pjsip_route_hdr *route; - const pj_str_t hname = { "Route", 5 }; - int parsed_len; + /* Build route-set for this account */ + if (pjsua.config.outbound_proxy.slen) { + pj_str_t hname = { "Route", 5}; + pjsip_route_hdr *r; + pj_str_t tmp; - route = pjsip_parse_hdr( pjsua.pool, &hname, - pjsua.outbound_proxy.ptr, - pjsua.outbound_proxy.slen, - &parsed_len); - if (route == NULL) { - pjsua_perror(THIS_FILE, "Invalid outbound proxy URL", - PJSIP_EINVALIDURI); - return PJSIP_EINVALIDURI; + pj_strdup_with_null(pjsua.pool, &tmp, &pjsua.config.outbound_proxy); + r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL); + pj_list_push_back(&acc->route_set, r); } - for (i=0; iproxy.slen) { + pj_str_t hname = { "Route", 5}; + pjsip_route_hdr *r; + pj_str_t tmp; + + pj_strdup_with_null(pjsua.pool, &tmp, &acc_cfg->proxy); + r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL); + pj_list_push_back(&acc->route_set, r); } } + + /* Create worker thread(s), if required: */ - for (i=0; imsg_info.msg->body->data; text.slen = rdata->msg_info.msg->body->len; - pjsua_ui_on_pager(call_index, from, to, &text); + if (pjsua.cb.on_pager) + (*pjsua.cb.on_pager)(call_index, from, to, &text); } else { @@ -169,7 +170,8 @@ void pjsua_im_process_pager(int call_index, const pj_str_t *from, return; } - pjsua_ui_on_typing(call_index, from, to, is_typing); + if (pjsua.cb.on_typing) + (*pjsua.cb.on_typing)(call_index, from, to, is_typing); } } @@ -269,8 +271,8 @@ static void im_callback(void *token, pjsip_event *e) /** * Send IM outside dialog. */ -pj_status_t pjsua_im_send(int acc_index, const char *dst_uri, - const char *str) +PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const char *dst_uri, + const char *str) { pjsip_tx_data *tdata; const pj_str_t STR_CONTACT = { "Contact", 7 }; @@ -281,9 +283,10 @@ pj_status_t pjsua_im_send(int acc_index, const char *dst_uri, pj_status_t status; /* Create request. */ - status = pjsip_endpt_create_request( pjsua.endpt, &pjsip_message_method, - &dst, &pjsua.acc[acc_index].local_uri, - &dst, NULL, NULL, -1, NULL, &tdata); + status = pjsip_endpt_create_request(pjsua.endpt, &pjsip_message_method, + &dst, + &pjsua.config.acc_config[acc_index].id, + &dst, NULL, NULL, -1, NULL, &tdata); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create request", status); return status; @@ -295,9 +298,9 @@ pj_status_t pjsua_im_send(int acc_index, const char *dst_uri, /* Add contact. */ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*) - pjsip_generic_string_hdr_create(tdata->pool, - &STR_CONTACT, - &pjsua.acc[acc_index].contact_uri)); + pjsip_generic_string_hdr_create(tdata->pool, + &STR_CONTACT, + &pjsua.config.acc_config[acc_index].contact)); /* Duplicate text. * We need to keep the text because we will display it when we fail to @@ -330,8 +333,8 @@ pj_status_t pjsua_im_send(int acc_index, const char *dst_uri, /** * Send typing indication outside dialog. */ -pj_status_t pjsua_im_typing(int acc_index, const char *dst_uri, - pj_bool_t is_typing) +PJ_DEF(pj_status_t) pjsua_im_typing(int acc_index, const char *dst_uri, + pj_bool_t is_typing) { const pj_str_t dst = pj_str((char*)dst_uri); pjsip_tx_data *tdata; @@ -339,7 +342,8 @@ pj_status_t pjsua_im_typing(int acc_index, const char *dst_uri, /* Create request. */ status = pjsip_endpt_create_request( pjsua.endpt, &pjsip_message_method, - &dst, &pjsua.acc[acc_index].local_uri, + &dst, + &pjsua.config.acc_config[acc_index].id, &dst, NULL, NULL, -1, NULL, &tdata); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create request", status); diff --git a/pjsip/src/pjsua-lib/pjsua_imp.h b/pjsip/src/pjsua-lib/pjsua_imp.h new file mode 100644 index 00000000..b406415f --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_imp.h @@ -0,0 +1,95 @@ +/* $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 + */ +#ifndef __PJSUA_IMP_H__ +#define __PJSUA_IMP_H__ + + + +/** + * Find account for incoming request. + */ +int pjsua_find_account_for_incoming(pjsip_rx_data *rdata); + + +/** + * Find account for outgoing request. + */ +int pjsua_find_account_for_outgoing(const pj_str_t *url); + + +/** + * Init pjsua call module. + */ +pj_status_t pjsua_call_init(void); + + +/** + * Handle incoming invite request. + */ +pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata); + + +/** + * Initialize client registration session. + * + * @param app_callback Optional callback + */ +pj_status_t pjsua_regc_init(int acc_index); + + +/** + * Init presence. + */ +pj_status_t pjsua_pres_init(); + + +/** + * Terminate all subscriptions + */ +void pjsua_pres_shutdown(void); + +/** + * Init IM module handler to handle incoming MESSAGE outside dialog. + */ +pj_status_t pjsua_im_init(); + +/** + * Create Accept header for MESSAGE. + */ +pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool); + +/** + * Private: check if we can accept the message. + * If not, then p_accept header will be filled with a valid + * Accept header. + */ +pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata, + pjsip_accept_hdr **p_accept_hdr); + +/** + * Private: process pager message. + * This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing(). + */ +void pjsua_im_process_pager(int call_id, const pj_str_t *from, + const pj_str_t *to, pjsip_rx_data *rdata); + + + +#endif /* __PJSUA_IMP_H__ */ + diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c index b77871f9..be31e2e1 100644 --- a/pjsip/src/pjsua-lib/pjsua_pres.c +++ b/pjsip/src/pjsua-lib/pjsua_pres.c @@ -17,6 +17,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include +#include "pjsua_imp.h" /* * pjsua_pres.c @@ -80,6 +81,7 @@ static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event) static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) { int acc_index; + pjsua_acc_config *acc_config; pjsip_method *req_method = &rdata->msg_info.msg->line.req.method; pjsua_srv_pres *uapres; pjsip_evsub *sub; @@ -96,11 +98,12 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) /* Find which account for the incoming request. */ acc_index = pjsua_find_account_for_incoming(rdata); + acc_config = &pjsua.config.acc_config[acc_index]; /* Create UAS dialog: */ - status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, - &pjsua.acc[acc_index].contact_uri, - &dlg); + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, + &acc_config->contact, + &dlg); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Unable to create UAS dialog for subscription", @@ -306,15 +309,17 @@ static pjsip_evsub_user pres_callback = static void subscribe_buddy_presence(unsigned index) { int acc_index; + pjsua_acc_config *acc_config; pjsip_dialog *dlg; pjsip_tx_data *tdata; pj_status_t status; acc_index = pjsua.buddies[index].acc_index; + acc_config = &pjsua.config.acc_config[acc_index]; status = pjsip_dlg_create_uac( pjsip_ua_instance(), - &pjsua.acc[acc_index].local_uri, - &pjsua.acc[acc_index].contact_uri, + &acc_config->id, + &acc_config->contact, &pjsua.buddies[index].uri, NULL, &dlg); if (status != PJ_SUCCESS) { @@ -323,8 +328,11 @@ static void subscribe_buddy_presence(unsigned index) return; } - pjsip_auth_clt_set_credentials( &dlg->auth_sess, pjsua.cred_count, - pjsua.cred_info); + if (acc_config->cred_count) { + pjsip_auth_clt_set_credentials( &dlg->auth_sess, + acc_config->cred_count, + acc_config->cred_info); + } status = pjsip_pres_create_uac( dlg, &pres_callback, &pjsua.buddies[index].sub); @@ -426,7 +434,7 @@ pj_status_t pjsua_pres_init() /* * Refresh presence */ -void pjsua_pres_refresh(int acc_index) +PJ_DEF(void) pjsua_pres_refresh(int acc_index) { refresh_client_subscription(); refresh_server_subscription(acc_index); @@ -441,7 +449,7 @@ void pjsua_pres_shutdown(void) int acc_index; int i; - for (acc_index=0; acc_indexregc); acc->regc = NULL; PJ_LOG(3,(THIS_FILE, "%s: unregistration success", - acc->local_uri.ptr)); + pjsua.config.acc_config[acc->index].id.ptr)); } else { PJ_LOG(3, (THIS_FILE, "%s: registration success, status=%d (%s), " "will re-register in %d seconds", - acc->local_uri.ptr, + pjsua.config.acc_config[acc->index].id.ptr, param->code, pjsip_get_status_text(param->code)->ptr, param->expiration)); @@ -77,14 +77,15 @@ static void regc_cb(struct pjsip_regc_cbparam *param) acc->reg_last_err = param->status; acc->reg_last_code = param->code; - pjsua_ui_on_reg_state(acc->index); + if (pjsua.cb.on_reg_state) + (*pjsua.cb.on_reg_state)(acc->index); } /* * Update registration. If renew is false, then unregistration will be performed. */ -void pjsua_regc_update(int acc_index, pj_bool_t renew) +PJ_DECL(void) pjsua_regc_update(int acc_index, pj_bool_t renew) { pj_status_t status = 0; pjsip_tx_data *tdata = 0; @@ -129,9 +130,12 @@ void pjsua_regc_update(int acc_index, pj_bool_t renew) */ pj_status_t pjsua_regc_init(int acc_index) { + pjsua_acc_config *acc_config; pj_status_t status; - if (pjsua.acc[acc_index].reg_uri.slen == 0) { + acc_config = &pjsua.config.acc_config[acc_index]; + + if (acc_config->reg_uri.slen == 0) { PJ_LOG(3,(THIS_FILE, "Registrar URI is not specified")); return PJ_SUCCESS; } @@ -151,11 +155,11 @@ pj_status_t pjsua_regc_init(int acc_index) 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); + &acc_config->reg_uri, + &acc_config->id, + &acc_config->id, + 1, &acc_config->contact, + acc_config->reg_timeout); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "Client registration initialization error", @@ -163,9 +167,11 @@ pj_status_t pjsua_regc_init(int acc_index) return status; } - pjsip_regc_set_credentials( pjsua.acc[acc_index].regc, - pjsua.cred_count, - pjsua.cred_info ); + if (acc_config->cred_count) { + pjsip_regc_set_credentials( pjsua.acc[acc_index].regc, + acc_config->cred_count, + acc_config->cred_info ); + } pjsip_regc_set_route_set( pjsua.acc[acc_index].regc, &pjsua.acc[acc_index].route_set ); diff --git a/pjsip/src/pjsua-lib/pjsua_settings.c b/pjsip/src/pjsua-lib/pjsua_settings.c index cb98effe..e1bdd34f 100644 --- a/pjsip/src/pjsua-lib/pjsua_settings.c +++ b/pjsip/src/pjsua-lib/pjsua_settings.c @@ -59,22 +59,17 @@ static void usage(void) puts (" --app-log-level=N Set log max level for stdout display (default=4)"); puts (""); 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 (""); - 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 (" --id=url Set the URL of local ID (used in From header)"); + puts (" --contact=url Optionally override the Contact information"); + puts (" --proxy=url Optional URL of proxy server to visit"); puts (" --realm=string Set realm"); puts (" --username=string Set authentication username"); puts (" --password=string Set authentication password"); - puts (" --next-cred Add more credential"); + puts (" --reg-timeout=SEC Optional registration interval (default 55)"); + puts (""); + puts ("SIP Account Control:"); + puts (" --next-account Add more account"); puts (""); puts ("Transport Options:"); puts (" --local-port=port Set TCP/UDP port"); @@ -86,7 +81,6 @@ static void usage(void) puts (" --add-codec=name Manually add codec (default is to enable all)"); puts (" --clock-rate=N Override sound device clock rate"); puts (" --null-audio Use NULL audio device"); - puts (" --no-mic Disable microphone device"); 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"); @@ -113,7 +107,7 @@ static void usage(void) /* * Verify that valid SIP url is given. */ -pj_status_t pjsua_verify_sip_url(const char *c_url) +PJ_DEF(pj_status_t) pjsua_verify_sip_url(const char *c_url) { pjsip_uri *p; pj_pool_t *pool; @@ -214,12 +208,13 @@ static int my_atoi(const char *cs) /* Parse arguments. */ -pj_status_t pjsua_parse_args(int argc, char *argv[]) +PJ_DEF(pj_status_t) pjsua_parse_args(int argc, char *argv[], + pjsua_config *cfg) { int c; int option_index; enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL, - OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO, OPT_NO_MIC, + OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO, OPT_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_ID, OPT_CONTACT, OPT_REALM, OPT_USERNAME, OPT_PASSWORD, @@ -229,7 +224,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC, OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, - OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS, OPT_UAS_REFRESH, + OPT_NEXT_ACCOUNT, OPT_MAX_CALLS, OPT_UAS_REFRESH, OPT_UAS_DURATION, }; struct pj_getopt_option long_options[] = { @@ -241,7 +236,6 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) { "version", 0, 0, OPT_VERSION}, { "clock-rate", 1, 0, OPT_CLOCK_RATE}, { "null-audio", 0, 0, OPT_NULL_AUDIO}, - { "no-mic", 0, 0, OPT_NO_MIC}, { "local-port", 1, 0, OPT_LOCAL_PORT}, { "proxy", 1, 0, OPT_PROXY}, { "outbound", 1, 0, OPT_OUTBOUND_PROXY}, @@ -269,19 +263,21 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) { "quality", 1, 0, OPT_QUALITY}, { "ptime", 1, 0, OPT_PTIME}, { "next-account",0,0, OPT_NEXT_ACCOUNT}, - { "next-cred", 0, 0, OPT_NEXT_CRED}, { "max-calls", 1, 0, OPT_MAX_CALLS}, { "uas-refresh",1, 0, OPT_UAS_REFRESH}, { "uas-duration",1,0, OPT_UAS_DURATION}, { NULL, 0, 0, 0} }; pj_status_t status; - pjsua_acc *cur_acc; - pjsip_cred_info *cur_cred; + pjsua_acc_config *cur_acc; + char errmsg[80]; char *config_file = NULL; + unsigned i; /* Run pj_getopt once to see if user specifies config file to read. */ - while ((c=pj_getopt_long(argc, argv, "", long_options, &option_index)) != -1) { + while ((c=pj_getopt_long(argc, argv, "", long_options, + &option_index)) != -1) + { switch (c) { case OPT_CONFIG_FILE: config_file = pj_optarg; @@ -297,16 +293,15 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) return status; } - - cur_acc = &pjsua.acc[0]; - cur_cred = &pjsua.cred_info[0]; + cfg->acc_cnt = 0; + cur_acc = &cfg->acc_config[0]; /* Reinitialize and re-run pj_getopt again, possibly with new arguments * read from config file. */ pj_optind = 0; - while((c=pj_getopt_long(argc, argv, "", long_options, &option_index))!=-1) { + while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) { char *p; pj_str_t tmp; long lval; @@ -314,7 +309,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) switch (c) { case OPT_LOG_FILE: - pjsua.log_filename = pj_optarg; + cfg->log_filename = pj_str(pj_optarg); break; case OPT_LOG_LEVEL: @@ -325,12 +320,13 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) "for --log-level")); return PJ_EINVAL; } + cfg->log_level = c; pj_log_set_level( c ); break; case OPT_APP_LOG_LEVEL: - pjsua.app_log_level = pj_strtoul(pj_cstr(&tmp, pj_optarg)); - if (pjsua.app_log_level < 0 || pjsua.app_log_level > 6) { + cfg->app_log_level = pj_strtoul(pj_cstr(&tmp, pj_optarg)); + if (cfg->app_log_level < 0 || cfg->app_log_level > 6) { PJ_LOG(1,(THIS_FILE, "Error: expecting integer value 0-6 " "for --app-log-level")); @@ -347,11 +343,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) return PJ_EINVAL; case OPT_NULL_AUDIO: - pjsua.null_audio = 1; - break; - - case OPT_NO_MIC: - pjsua.no_mic = 1; + cfg->null_audio = 1; break; case OPT_CLOCK_RATE: @@ -361,7 +353,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) "8000-48000 for clock rate")); return PJ_EINVAL; } - pjsua.clock_rate = (int)lval; + cfg->clock_rate = lval; break; case OPT_LOCAL_PORT: /* local-port */ @@ -372,7 +364,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) "--local-port")); return PJ_EINVAL; } - pjsua.sip_port = (pj_uint16_t)lval; + cfg->udp_port = (pj_uint16_t)lval; break; case OPT_PROXY: /* proxy */ @@ -392,7 +384,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) "in outbound proxy argument", pj_optarg)); return PJ_EINVAL; } - pjsua.outbound_proxy = pj_str(pj_optarg); + cfg->outbound_proxy = pj_str(pj_optarg); break; case OPT_REGISTRAR: /* registrar */ @@ -422,8 +414,7 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) "in local id argument", pj_optarg)); return PJ_EINVAL; } - cur_acc->local_uri = pj_str(pj_optarg); - pjsua.has_acc = 1; + cur_acc->id = pj_str(pj_optarg); break; case OPT_CONTACT: /* contact */ @@ -433,50 +424,42 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) "in contact argument", pj_optarg)); return PJ_EINVAL; } - cur_acc->contact_uri = pj_str(pj_optarg); + cur_acc->contact = pj_str(pj_optarg); break; case OPT_NEXT_ACCOUNT: /* Add more account. */ - pjsua.acc_cnt++; - cur_acc = &pjsua.acc[pjsua.acc_cnt - 1]; + cfg->acc_cnt++; + cur_acc = &cfg->acc_config[cfg->acc_cnt - 1]; break; case OPT_USERNAME: /* Default authentication user */ - if (pjsua.cred_count==0) pjsua.cred_count=1; - cur_cred->username = pj_str(pj_optarg); + cur_acc->cred_info[0].username = pj_str(pj_optarg); break; case OPT_REALM: /* Default authentication realm. */ - if (pjsua.cred_count==0) pjsua.cred_count=1; - cur_cred->realm = pj_str(pj_optarg); + cur_acc->cred_info[0].realm = pj_str(pj_optarg); break; case OPT_PASSWORD: /* authentication password */ - if (pjsua.cred_count==0) pjsua.cred_count=1; - cur_cred->data_type = 0; - cur_cred->data = pj_str(pj_optarg); - break; - - case OPT_NEXT_CRED: /* Next credential */ - pjsua.cred_count++; - cur_cred = &pjsua.cred_info[pjsua.cred_count - 1]; + cur_acc->cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; + cur_acc->cred_info[0].data = pj_str(pj_optarg); break; case OPT_USE_STUN1: /* STUN server 1 */ p = pj_ansi_strchr(pj_optarg, ':'); if (p) { *p = '\0'; - pjsua.stun_srv1 = pj_str(pj_optarg); - pjsua.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1)); - if (pjsua.stun_port1 < 1 || pjsua.stun_port1 > 65535) { + cfg->stun_srv1 = pj_str(pj_optarg); + cfg->stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1)); + if (cfg->stun_port1 < 1 || cfg->stun_port1 > 65535) { PJ_LOG(1,(THIS_FILE, "Error: expecting port number with " "option --use-stun1")); return PJ_EINVAL; } } else { - pjsua.stun_port1 = 3478; - pjsua.stun_srv1 = pj_str(pj_optarg); + cfg->stun_port1 = 3478; + cfg->stun_srv1 = pj_str(pj_optarg); } break; @@ -484,17 +467,17 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) p = pj_ansi_strchr(pj_optarg, ':'); if (p) { *p = '\0'; - pjsua.stun_srv2 = pj_str(pj_optarg); - pjsua.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1)); - if (pjsua.stun_port2 < 1 || pjsua.stun_port2 > 65535) { + cfg->stun_srv2 = pj_str(pj_optarg); + cfg->stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1)); + if (cfg->stun_port2 < 1 || cfg->stun_port2 > 65535) { PJ_LOG(1,(THIS_FILE, "Error: expecting port number with " "option --use-stun2")); return PJ_EINVAL; } } else { - pjsua.stun_port2 = 3478; - pjsua.stun_srv2 = pj_str(pj_optarg); + cfg->stun_port2 = 3478; + cfg->stun_srv2 = pj_str(pj_optarg); } break; @@ -505,33 +488,33 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) "--add-buddy option", pj_optarg)); return -1; } - if (pjsua.buddy_cnt == PJSUA_MAX_BUDDIES) { + if (cfg->buddy_cnt == PJSUA_MAX_BUDDIES) { PJ_LOG(1,(THIS_FILE, "Error: too many buddies in buddy list.")); return -1; } - pjsua.buddies[pjsua.buddy_cnt++].uri = pj_str(pj_optarg); + cfg->buddy_uri[cfg->buddy_cnt++] = pj_str(pj_optarg); break; case OPT_AUTO_PLAY: - pjsua.auto_play = 1; + cfg->auto_play = 1; break; case OPT_AUTO_LOOP: - pjsua.auto_loop = 1; + cfg->auto_loop = 1; break; case OPT_AUTO_CONF: - pjsua.auto_conf = 1; + cfg->auto_conf = 1; break; case OPT_PLAY_FILE: - pjsua.wav_file = pj_optarg; + cfg->wav_file = pj_str(pj_optarg); break; case OPT_RTP_PORT: - pjsua.start_rtp_port = my_atoi(pj_optarg); - if (pjsua.start_rtp_port < 1 || pjsua.start_rtp_port > 65535) { + cfg->start_rtp_port = my_atoi(pj_optarg); + if (cfg->start_rtp_port < 1 || cfg->start_rtp_port > 65535) { PJ_LOG(1,(THIS_FILE, "Error: rtp-port argument value " "(expecting 1-65535")); @@ -540,12 +523,12 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) break; case OPT_ADD_CODEC: - pjsua.codec_arg[pjsua.codec_cnt++] = pj_str(pj_optarg); + cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg); break; case OPT_COMPLEXITY: - pjsua.complexity = my_atoi(pj_optarg); - if (pjsua.complexity < 0 || pjsua.complexity > 10) { + cfg->complexity = my_atoi(pj_optarg); + if (cfg->complexity < 0 || cfg->complexity > 10) { PJ_LOG(1,(THIS_FILE, "Error: invalid --complexity (expecting 0-10")); return -1; @@ -553,8 +536,8 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) break; case OPT_QUALITY: - pjsua.quality = my_atoi(pj_optarg); - if (pjsua.quality < 0 || pjsua.quality > 10) { + cfg->quality = my_atoi(pj_optarg); + if (cfg->quality < 0 || cfg->quality > 10) { PJ_LOG(1,(THIS_FILE, "Error: invalid --quality (expecting 0-10")); return -1; @@ -562,8 +545,8 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) break; case OPT_PTIME: - pjsua.ptime = my_atoi(pj_optarg); - if (pjsua.ptime < 10 || pjsua.ptime > 1000) { + cfg->ptime = my_atoi(pj_optarg); + if (cfg->ptime < 10 || cfg->ptime > 1000) { PJ_LOG(1,(THIS_FILE, "Error: invalid --ptime option")); return -1; @@ -571,8 +554,8 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) break; case OPT_AUTO_ANSWER: - pjsua.auto_answer = my_atoi(pj_optarg); - if (pjsua.auto_answer < 100 || pjsua.auto_answer > 699) { + cfg->auto_answer = my_atoi(pj_optarg); + if (cfg->auto_answer < 100 || cfg->auto_answer > 699) { PJ_LOG(1,(THIS_FILE, "Error: invalid code in --auto-answer " "(expecting 100-699")); @@ -581,16 +564,16 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) break; case OPT_MAX_CALLS: - pjsua.max_calls = my_atoi(pj_optarg); - if (pjsua.max_calls < 1 || pjsua.max_calls > 255) { + cfg->max_calls = my_atoi(pj_optarg); + if (cfg->max_calls < 1 || cfg->max_calls > 255) { PJ_LOG(1,(THIS_FILE,"Too many calls for max-calls (1-255)")); return -1; } break; case OPT_UAS_REFRESH: - pjsua.uas_refresh = my_atoi(pj_optarg); - if (pjsua.uas_refresh < 1) { + cfg->uas_refresh = my_atoi(pj_optarg); + if (cfg->uas_refresh < 1) { PJ_LOG(1,(THIS_FILE, "Invalid value for --uas-refresh (must be >0)")); return -1; @@ -598,8 +581,8 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) break; case OPT_UAS_DURATION: - pjsua.uas_duration = my_atoi(pj_optarg); - if (pjsua.uas_duration < 1) { + cfg->uas_duration = my_atoi(pj_optarg); + if (cfg->uas_duration < 1) { PJ_LOG(1,(THIS_FILE, "Invalid value for --uas-duration " "(must be >0)")); @@ -610,22 +593,21 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) } if (pj_optind != argc) { - int i; if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) { PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind])); return -1; } - pjsua.uri_to_call = pj_str(argv[pj_optind]); + cfg->uri_to_call = pj_str(argv[pj_optind]); pj_optind++; /* Add URI to call to buddy list if it's not already there */ - for (i=0; ibuddy_cnt; ++i) { + if (pj_stricmp(&cfg->buddy_uri[i], &cfg->uri_to_call)==0) break; } - if (i == pjsua.buddy_cnt && pjsua.buddy_cnt < PJSUA_MAX_BUDDIES) { - pjsua.buddies[pjsua.buddy_cnt++].uri = pjsua.uri_to_call; + if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) { + cfg->buddy_uri[cfg->buddy_cnt++] = cfg->uri_to_call; } } @@ -634,6 +616,23 @@ pj_status_t pjsua_parse_args(int argc, char *argv[]) return PJ_EINVAL; } + if (cfg->acc_config[0].id.slen && cfg->acc_cnt==0) + cfg->acc_cnt = 1; + + for (i=0; iacc_cnt; ++i) { + if (cfg->acc_config[i].cred_info[0].username.slen || + cfg->acc_config[i].cred_info[0].realm.slen) + { + cfg->acc_config[i].cred_count = 1; + cfg->acc_config[i].cred_info[0].scheme = pj_str("digest"); + } + } + + if (pjsua_test_config(cfg, errmsg, sizeof(errmsg)) != PJ_SUCCESS) { + PJ_LOG(1,(THIS_FILE, "Error: %s", errmsg)); + return -1; + } + return PJ_SUCCESS; } @@ -831,7 +830,7 @@ static void dump_media_session(pjmedia_session *session) /* * Dump application states. */ -void pjsua_dump(pj_bool_t detail) +PJ_DEF(void) pjsua_dump(pj_bool_t detail) { char buf[128]; unsigned old_decor; @@ -858,9 +857,9 @@ void pjsua_dump(pj_bool_t detail) PJ_LOG(3,(THIS_FILE, " - no sessions -")); } else { - int i; + unsigned i; - for (i=0; ilocal_uri.slen) { + if (acc_cfg->id.slen) { pj_ansi_sprintf(line, "--id %.*s\n", - (int)acc->local_uri.slen, - acc->local_uri.ptr); + (int)acc_cfg->id.slen, + acc_cfg->id.ptr); pj_strcat2(result, line); } /* Registrar server */ - if (acc->reg_uri.slen) { + if (acc_cfg->reg_uri.slen) { pj_ansi_sprintf(line, "--registrar %.*s\n", - (int)acc->reg_uri.slen, - acc->reg_uri.ptr); + (int)acc_cfg->reg_uri.slen, + acc_cfg->reg_uri.ptr); pj_strcat2(result, line); pj_ansi_sprintf(line, "--reg-timeout %u\n", - acc->reg_timeout); + acc_cfg->reg_timeout); pj_strcat2(result, line); } /* Proxy */ - if (acc->proxy.slen) { + if (acc_cfg->proxy.slen) { pj_ansi_sprintf(line, "--proxy %.*s\n", - (int)acc->proxy.slen, - acc->proxy.ptr); + (int)acc_cfg->proxy.slen, + acc_cfg->proxy.ptr); + pj_strcat2(result, line); + } + + if (acc_cfg->cred_info[0].realm.slen) { + pj_ansi_sprintf(line, "--realm %.*s\n", + (int)acc_cfg->cred_info[0].realm.slen, + acc_cfg->cred_info[0].realm.ptr); pj_strcat2(result, line); } + + if (acc_cfg->cred_info[0].username.slen) { + pj_ansi_sprintf(line, "--username %.*s\n", + (int)acc_cfg->cred_info[0].username.slen, + acc_cfg->cred_info[0].username.ptr); + pj_strcat2(result, line); + } + + if (acc_cfg->cred_info[0].data.slen) { + pj_ansi_sprintf(line, "--password %.*s\n", + (int)acc_cfg->cred_info[0].data.slen, + acc_cfg->cred_info[0].data.ptr); + pj_strcat2(result, line); + } + } @@ -975,10 +997,11 @@ static void save_account_settings(int acc_index, pj_str_t *result) /* * Dump settings. */ -int pjsua_dump_settings(char *buf, pj_size_t max) +PJ_DEF(int) pjsua_dump_settings(const pjsua_config *config, + char *buf, pj_size_t max) { - int acc_index; - int i; + unsigned acc_index; + unsigned i; pj_str_t cfg; char line[128]; @@ -991,89 +1014,60 @@ int pjsua_dump_settings(char *buf, pj_size_t max) /* Logging. */ pj_strcat2(&cfg, "#\n# Logging options:\n#\n"); pj_ansi_sprintf(line, "--log-level %d\n", - pjsua.log_level); + config->log_level); pj_strcat2(&cfg, line); pj_ansi_sprintf(line, "--app-log-level %d\n", - pjsua.app_log_level); + config->app_log_level); pj_strcat2(&cfg, line); - if (pjsua.log_filename) { + if (config->log_filename.slen) { pj_ansi_sprintf(line, "--log-file %s\n", - pjsua.log_filename); + config->log_filename.ptr); pj_strcat2(&cfg, line); } /* Save account settings. */ - if (pjsua.has_acc) { - 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; iacc_cnt; ++acc_index) { + + save_account_settings(acc_index, &cfg); - if (i < pjsua.cred_count-1) - pj_strcat2(&cfg, "--next-cred\n"); + if (acc_index < config->acc_cnt-1) + pj_strcat2(&cfg, "--next-account\n"); } pj_strcat2(&cfg, "#\n# Network settings:\n#\n"); /* Outbound proxy */ - if (pjsua.outbound_proxy.slen) { + if (config->outbound_proxy.slen) { pj_ansi_sprintf(line, "--outbound %.*s\n", - (int)pjsua.outbound_proxy.slen, - pjsua.outbound_proxy.ptr); + (int)config->outbound_proxy.slen, + config->outbound_proxy.ptr); pj_strcat2(&cfg, line); } /* Transport. */ - pj_ansi_sprintf(line, "--local-port %d\n", pjsua.sip_port); + pj_ansi_sprintf(line, "--local-port %d\n", config->udp_port); pj_strcat2(&cfg, line); /* STUN */ - if (pjsua.stun_port1) { + if (config->stun_port1) { pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n", - (int)pjsua.stun_srv1.slen, - pjsua.stun_srv1.ptr, - pjsua.stun_port1); + (int)config->stun_srv1.slen, + config->stun_srv1.ptr, + config->stun_port1); pj_strcat2(&cfg, line); } - if (pjsua.stun_port2) { + if (config->stun_port2) { pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n", - (int)pjsua.stun_srv2.slen, - pjsua.stun_srv2.ptr, - pjsua.stun_port2); + (int)config->stun_srv2.slen, + config->stun_srv2.ptr, + config->stun_port2); pj_strcat2(&cfg, line); } @@ -1082,89 +1076,89 @@ int pjsua_dump_settings(char *buf, pj_size_t max) /* Media */ - if (pjsua.null_audio) + if (config->null_audio) pj_strcat2(&cfg, "--null-audio\n"); - if (pjsua.auto_play) + if (config->auto_play) pj_strcat2(&cfg, "--auto-play\n"); - if (pjsua.auto_loop) + if (config->auto_loop) pj_strcat2(&cfg, "--auto-loop\n"); - if (pjsua.auto_conf) + if (config->auto_conf) pj_strcat2(&cfg, "--auto-conf\n"); - if (pjsua.wav_file) { + if (config->wav_file.slen) { pj_ansi_sprintf(line, "--play-file %s\n", - pjsua.wav_file); + config->wav_file.ptr); pj_strcat2(&cfg, line); } /* Media clock rate. */ - if (pjsua.clock_rate) { + if (config->clock_rate) { pj_ansi_sprintf(line, "--clock-rate %d\n", - pjsua.clock_rate); + config->clock_rate); pj_strcat2(&cfg, line); } /* Encoding quality and complexity */ pj_ansi_sprintf(line, "--quality %d\n", - pjsua.quality); + config->quality); pj_strcat2(&cfg, line); pj_ansi_sprintf(line, "--complexity %d\n", - pjsua.complexity); + config->complexity); pj_strcat2(&cfg, line); /* ptime */ - if (pjsua.ptime) { + if (config->ptime) { pj_ansi_sprintf(line, "--ptime %d\n", - pjsua.ptime); + config->ptime); pj_strcat2(&cfg, line); } /* Start RTP port. */ pj_ansi_sprintf(line, "--rtp-port %d\n", - pjsua.start_rtp_port); + config->start_rtp_port); pj_strcat2(&cfg, line); /* Add codec. */ - for (i=0; icodec_cnt; ++i) { pj_ansi_sprintf(line, "--add-codec %s\n", - pjsua.codec_arg[i].ptr); + config->codec_arg[i].ptr); pj_strcat2(&cfg, line); } pj_strcat2(&cfg, "#\n# User agent:\n#\n"); /* Auto-answer. */ - if (pjsua.auto_answer != 0) { + if (config->auto_answer != 0) { pj_ansi_sprintf(line, "--auto-answer %d\n", - pjsua.auto_answer); + config->auto_answer); pj_strcat2(&cfg, line); } /* Max calls. */ pj_ansi_sprintf(line, "--max-calls %d\n", - pjsua.max_calls); + config->max_calls); pj_strcat2(&cfg, line); /* Uas-refresh. */ - if (pjsua.uas_refresh > 0) { + if (config->uas_refresh > 0) { pj_ansi_sprintf(line, "--uas-refresh %d\n", - pjsua.uas_refresh); + config->uas_refresh); pj_strcat2(&cfg, line); } /* Uas-duration. */ - if (pjsua.uas_duration > 0) { + if (config->uas_duration > 0) { pj_ansi_sprintf(line, "--uas-duration %d\n", - pjsua.uas_duration); + config->uas_duration); pj_strcat2(&cfg, line); } pj_strcat2(&cfg, "#\n# Buddies:\n#\n"); /* Add buddies. */ - for (i=0; ibuddy_cnt; ++i) { pj_ansi_sprintf(line, "--add-buddy %.*s\n", - (int)pjsua.buddies[i].uri.slen, - pjsua.buddies[i].uri.ptr); + (int)config->buddy_uri[i].slen, + config->buddy_uri[i].ptr); pj_strcat2(&cfg, line); } @@ -1176,7 +1170,8 @@ int pjsua_dump_settings(char *buf, pj_size_t max) /* * Save settings. */ -pj_status_t pjsua_save_settings(const char *filename) +PJ_DEF(pj_status_t) pjsua_save_settings(const char *filename, + const pjsua_config *config) { pj_str_t cfg; pj_pool_t *pool; @@ -1195,7 +1190,7 @@ pj_status_t pjsua_save_settings(const char *filename) } - cfg.slen = pjsua_dump_settings(cfg.ptr, 3800); + cfg.slen = pjsua_dump_settings(config, cfg.ptr, 3800); if (cfg.slen < 1) { pj_pool_release(pool); return PJ_ENOMEM; -- cgit v1.2.3