First stage in pjsua library re-arrangements towards creating an easy to use high level API

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@476 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip-apps/src/pjsua/main.c b/pjsip-apps/src/pjsua/main.c
index 97da6f3..1e060f4 100644
--- a/pjsip-apps/src/pjsua/main.c
+++ b/pjsip-apps/src/pjsua/main.c
@@ -17,1035 +17,46 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  */
 #include <pjsua-lib/pjsua.h>
-#include <stdlib.h>		/* atoi */
-#include <stdio.h>
+#include <pjsua-lib/pjsua_console_app.h>
+
 
 #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<current_call; ++i) {
-	if (pjsua.calls[i].inv != NULL) {
-	    current_call = i;
-	    return PJ_TRUE;
-	}
-    }
-
-    current_call = -1;
-    return PJ_FALSE;
-}
-
-
-/*
- * Find previous call.
- */
-static pj_bool_t find_prev_call(void)
-{
-    int i;
-
-    for (i=current_call-1; 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<pjsua.buddy_cnt; ++i) {
-	    const char *status;
-
-	    if (pjsua.buddies[i].sub == NULL || 
-		pjsua.buddies[i].status.info_cnt==0)
-	    {
-		status = "   ?   ";
-	    } 
-	    else if (pjsua.buddies[i].status.info[0].basic_open)
-		status = " Online";
-	    else
-		status = "Offline";
-
-	    printf(" [%2d] <%s>  %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<pjsua.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"
-	   "  <Enter>    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 (; i<len; ++i) {
-	    if (!pj_isdigit(buf[i])) {
-		puts("Invalid input");
-		return;
-	    }
-	}
-
-	result->nb_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; i<count; ++i) {
-	char txlist[PJSUA_MAX_CALLS*4+10];
-	int j;
-	pjmedia_conf_port_info *port_info = &info[i];	
-	
-	txlist[0] = '\0';
-	for (j=0; j<pjsua.max_calls+PJSUA_CONF_MORE_PORTS; ++j) {
-	    char s[10];
-	    if (port_info->listener[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; i<atoi(menuin); ++i) {
-		pj_status_t status;
-
-		status = pjsua_make_call(current_acc, uri, NULL);
-		if (status != PJ_SUCCESS)
-		    break;
-	    }
-	    break;
-
-	case 'i':
-	    /* Send instant messaeg */
-
-	    /* i is for call index to send message, if any */
-	    i = -1;
-    
-	    /* Make compiler happy. */
-	    uri = NULL;
-
-	    /* Input destination. */
-	    ui_input_url("Send IM to", buf, sizeof(buf), &result);
-	    if (result.nb_result != NO_NB) {
-
-		if (result.nb_result == -1) {
-		    puts("You can't send broadcast IM like that!");
-		    continue;
-
-		} else if (result.nb_result == 0) {
-    
-		    i = current_call;
-
-		} else {
-		    uri = pjsua.buddies[result.nb_result-1].uri.ptr;
-		}
-
-	    } else if (result.uri_result) {
-		uri = result.uri_result;
-	    }
-	    
-
-	    /* Send typing indication. */
-	    if (i != -1)
-		pjsua_call_typing(i, PJ_TRUE);
-	    else
-		pjsua_im_typing(current_acc, uri, PJ_TRUE);
-
-	    /* Input the IM . */
-	    if (!simple_input("Message", text, sizeof(text))) {
-		/*
-		 * Cancelled.
-		 * Send typing notification too, saying we're not typing.
-		 */
-		if (i != -1)
-		    pjsua_call_typing(i, PJ_FALSE);
-		else
-		    pjsua_im_typing(current_acc, uri, PJ_FALSE);
-		continue;
-	    }
-
-	    /* Send the IM */
-	    if (i != -1)
-		pjsua_call_send_im(i, text);
-	    else
-		pjsua_im_send(current_acc, uri, text);
-
-	    break;
-
-	case 'a':
-
-	    if (current_call == -1 || 
-		pjsua.calls[current_call].inv->role != PJSIP_ROLE_UAS ||
-		pjsua.calls[current_call].inv->state >= PJSIP_INV_STATE_CONNECTING)
-	    {
-		puts("No pending incoming call");
-		fflush(stdout);
-		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, "<uri is too long>");
-		} 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; i<pjsua.buddy_cnt; ++i)
-			pjsua.buddies[i].monitor = (menuin[0]=='s');
-		} else if (result.nb_result == 0) {
-		    puts("Sorry, can only subscribe to buddy's presence, "
-			 "not from existing call");
-		} else {
-		    pjsua.buddies[result.nb_result-1].monitor = (menuin[0]=='s');
-		}
-
-		pjsua_pres_refresh(current_acc);
-
-	    } else if (result.uri_result) {
-		puts("Sorry, can only subscribe to buddy's presence, "
-		     "not arbitrary URL (for now)");
-	    }
-
-	    break;
-
-	case 'r':
-	    switch (menuin[1]) {
-	    case 'r':
-		/*
-		 * Re-Register.
-		 */
-		pjsua_regc_update(current_acc, PJ_TRUE);
-		break;
-	    case 'u':
-		/*
-		 * Unregister
-		 */
-		pjsua_regc_update(current_acc, PJ_FALSE);
-		break;
-	    }
-	    break;
-	    
-	case 't':
-	    pjsua.acc[current_acc].online_status = 
-		!pjsua.acc[current_acc].online_status;
-	    printf("Setting %s online status to %s\n",
-		   pjsua.acc[current_acc].local_uri.ptr,
-		   (pjsua.acc[current_acc].online_status?"online":"offline"));
-	    pjsua_pres_refresh(current_acc);
-	    break;
-
-	case 'c':
-	    switch (menuin[1]) {
-	    case 'l':
-		conf_list();
-		break;
-	    case 'c':
-	    case 'd':
-		{
-		    char src_port[10], dst_port[10];
-		    pj_status_t status;
-		    const char *src_title, *dst_title;
-
-		    conf_list();
-
-		    src_title = (menuin[1]=='c'?
-				 "Connect src port #":
-				 "Disconnect src port #");
-		    dst_title = (menuin[1]=='c'?
-				 "To dst port #":
-				 "From dst port #");
-
-		    if (!simple_input(src_title, src_port, sizeof(src_port)))
-			break;
-
-		    if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
-			break;
-
-		    if (menuin[1]=='c') {
-			status = pjmedia_conf_connect_port(pjsua.mconf, 
-							   atoi(src_port), 
-							   atoi(dst_port),
-							   0);
-		    } else {
-			status = pjmedia_conf_disconnect_port(pjsua.mconf, 
-							      atoi(src_port), 
-							      atoi(dst_port));
-		    }
-		    if (status == PJ_SUCCESS) {
-			puts("Success");
-		    } else {
-			puts("ERROR!!");
-		    }
-		}
-		break;
-	    }
-	    break;
-
-	case 'd':
-	    if (menuin[1] == 'c') {
-		char settings[2000];
-		int len;
-
-		len = pjsua_dump_settings(settings, sizeof(settings));
-		if (len < 1)
-		    PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
-		else
-		    PJ_LOG(3,(THIS_FILE, 
-			      "Dumping configuration (%d bytes):\n%s\n",
-			      len, settings));
-	    } else {
-		pjsua_dump(menuin[1]=='d');
-	    }
-	    break;
-
-	case 'q':
-	    goto on_exit;
-
-	default:
-	    if (menuin[0] != '\n' && menuin[0] != '\r') {
-		printf("Invalid input %s", menuin);
-	    }
-	    keystroke_help();
-	    break;
-	}
-    }
-
-on_exit:
-    ;
-}
-
-
-/*****************************************************************************
- * This is a very simple PJSIP module, whose sole purpose is to display
- * incoming and outgoing messages to log. This module will have priority
- * higher than transport layer, which means:
- *
- *  - incoming messages will come to this module first before reaching
- *    transaction layer.
- *
- *  - outgoing messages will come to this module last, after the message
- *    has been 'printed' to contiguous buffer by transport layer and
- *    appropriate transport instance has been decided for this message.
- *
- */
-
-/* Notification on incoming messages */
-static pj_bool_t console_on_rx_msg(pjsip_rx_data *rdata)
-{
-    PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
-			 "%s\n"
-			 "--end msg--",
-			 rdata->msg_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));
-}
-
-
-
-
 /*****************************************************************************
  * main():
  */
 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 @@
 
 
     /* Start UI console main loop: */
-    ui_console_main();
+    pjsua_console_app_main();
 
 
     /* Destroy pjsua: */
@@ -1069,7 +80,7 @@
 
 
     /* 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 a49b360..d3f42ba 100644
--- a/pjsip/build/pjsua_lib.dsp
+++ b/pjsip/build/pjsua_lib.dsp
@@ -91,6 +91,10 @@
 # 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 @@
 # 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="..\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 a3c0c3a..6484d72 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -48,7 +48,7 @@
  * Max buddies in buddy list.
  */
 #ifndef PJSUA_MAX_BUDDIES
-#   define PJSUA_MAX_BUDDIES	    32
+#   define PJSUA_MAX_BUDDIES	    256
 #endif
 
 
@@ -61,27 +61,13 @@
 
 
 /**
- * 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. 
@@ -140,24 +126,54 @@
 
 
 /**
+ * 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 @@
 };
 
 
+/**
+ * @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 @@
     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 @@
 /**
  * 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,25 +467,13 @@
  * 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);
 
 
 /*****************************************************************************
@@ -328,68 +481,57 @@
  */
 
 /**
- * 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);
 
 
 /*****************************************************************************
@@ -397,17 +539,10 @@
  */
 
 /**
- * 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);
 
 
 
@@ -417,24 +552,14 @@
  */
 
 /**
- * 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 @@
 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);
+PJ_DECL(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);
-
-
-/**
- * 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 @@
 /**
  * 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 0000000..058d063
--- /dev/null
+++ b/pjsip/include/pjsua-lib/pjsua_console_app.h
@@ -0,0 +1,31 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * 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 84fb57d..6272e34 100644
--- a/pjsip/src/pjsip-ua/sip_reg.c
+++ b/pjsip/src/pjsip-ua/sip_reg.c
@@ -484,6 +484,7 @@
     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 @@
     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 4dbfb59..d6013a1 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -18,7 +18,7 @@
  */
 #include <pjsua-lib/pjsua.h>
 #include <pj/log.h>
-
+#include "pjsua_imp.h"
 
 /*
  * pjsua_call.c
@@ -56,7 +56,8 @@
 	    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 @@
 /**
  * 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 @@
     dest_uri = pj_str((char*)cstr_dest_uri);
 
     /* 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;
     }
 
-    if (call_index == pjsua.max_calls) {
+    if (call_index == pjsua.config.max_calls) {
 	PJ_LOG(3,(THIS_FILE, "Error: too many calls!"));
 	return PJ_ETOOMANY;
     }
@@ -161,8 +162,8 @@
 
     /* Create outgoing dialog: */
     status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
-				   &pjsua.acc[acc_index].local_uri,
-				   &pjsua.acc[acc_index].contact_uri, 
+				   &pjsua.config.acc_config[acc_index].id,
+				   &pjsua.config.acc_config[acc_index].contact,
 				   &dest_uri, &dest_uri,
 				   &dlg);
     if (status != PJ_SUCCESS) {
@@ -204,9 +205,12 @@
 
 
     /* Set credentials: */
-
-    pjsip_auth_clt_set_credentials( &dlg->auth_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 @@
     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 @@
 on_error:
     if (inv != NULL) {
 	pjsip_inv_terminate(inv, PJSIP_SC_OK, PJ_FALSE);
-    } else {
+    } else if (dlg) {
 	pjsip_dlg_terminate(dlg);
     }
 
@@ -254,6 +264,36 @@
 
 
 /**
+ * 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.
  */
 pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
@@ -265,7 +305,7 @@
     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 @@
      */
 
     /* 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 @@
     /* 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 @@
      */
     
     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 @@
 	/* 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 @@
 	    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 @@
 		  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 @@
     }
 
 
-    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 @@
 	return;
     }
 
-    if (pjsua.null_audio)
+    if (pjsua.config.null_audio)
 	return;
 
     /* Create media session info based on SDP parameters. 
@@ -983,9 +1024,10 @@
     }
 
     /* 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 @@
     /* 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 @@
 				   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 @@
     /* 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 @@
 /*
  * 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 @@
 /*
  * 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 @@
 /*
  * 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 @@
 /*
  * 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 @@
 /**
  * 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 @@
 /**
  * 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 @@
 /*
  * 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<pjsua.max_calls; ++i) {
+    for (i=0; i<pjsua.config.max_calls; ++i) {
 	pjsip_tx_data *tdata;
 	int st_code;
 	pjsua_call *call;
diff --git a/pjsip/src/pjsua-lib/pjsua_console_app.c b/pjsip/src/pjsua-lib/pjsua_console_app.c
new file mode 100644
index 0000000..c2f94e6
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_console_app.c
@@ -0,0 +1,1015 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+
+#include <pjsua-lib/pjsua.h>
+#include <pjsua-lib/pjsua_console_app.h>
+#include <stdlib.h>		/* atoi */
+#include <stdio.h>
+
+#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<current_call; ++i) {
+	if (pjsua.calls[i].inv != NULL) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    current_call = -1;
+    return PJ_FALSE;
+}
+
+
+/*
+ * Find previous call.
+ */
+static pj_bool_t find_prev_call(void)
+{
+    int i;
+
+    for (i=current_call-1; 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<pjsua.buddy_cnt; ++i) {
+	    const char *status;
+
+	    if (pjsua.buddies[i].sub == NULL || 
+		pjsua.buddies[i].status.info_cnt==0)
+	    {
+		status = "   ?   ";
+	    } 
+	    else if (pjsua.buddies[i].status.info[0].basic_open)
+		status = " Online";
+	    else
+		status = "Offline";
+
+	    printf(" [%2d] <%s>  %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"
+	   "  <Enter>    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 (; i<len; ++i) {
+	    if (!pj_isdigit(buf[i])) {
+		puts("Invalid input");
+		return;
+	    }
+	}
+
+	result->nb_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; i<count; ++i) {
+	char txlist[PJSUA_MAX_CALLS*4+10];
+	int j;
+	pjmedia_conf_port_info *port_info = &info[i];	
+	
+	txlist[0] = '\0';
+	for (j=0; j<(int)count; ++j) {
+	    char s[10];
+	    if (port_info->listener[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; i<atoi(menuin); ++i) {
+		pj_status_t status;
+
+		status = pjsua_make_call(current_acc, uri, NULL);
+		if (status != PJ_SUCCESS)
+		    break;
+	    }
+	    break;
+
+	case 'i':
+	    /* Send instant messaeg */
+
+	    /* i is for call index to send message, if any */
+	    i = -1;
+    
+	    /* Make compiler happy. */
+	    uri = NULL;
+
+	    /* Input destination. */
+	    ui_input_url("Send IM to", buf, sizeof(buf), &result);
+	    if (result.nb_result != NO_NB) {
+
+		if (result.nb_result == -1) {
+		    puts("You can't send broadcast IM like that!");
+		    continue;
+
+		} else if (result.nb_result == 0) {
+    
+		    i = current_call;
+
+		} else {
+		    uri = pjsua.buddies[result.nb_result-1].uri.ptr;
+		}
+
+	    } else if (result.uri_result) {
+		uri = result.uri_result;
+	    }
+	    
+
+	    /* Send typing indication. */
+	    if (i != -1)
+		pjsua_call_typing(i, PJ_TRUE);
+	    else
+		pjsua_im_typing(current_acc, uri, PJ_TRUE);
+
+	    /* Input the IM . */
+	    if (!simple_input("Message", text, sizeof(text))) {
+		/*
+		 * Cancelled.
+		 * Send typing notification too, saying we're not typing.
+		 */
+		if (i != -1)
+		    pjsua_call_typing(i, PJ_FALSE);
+		else
+		    pjsua_im_typing(current_acc, uri, PJ_FALSE);
+		continue;
+	    }
+
+	    /* Send the IM */
+	    if (i != -1)
+		pjsua_call_send_im(i, text);
+	    else
+		pjsua_im_send(current_acc, uri, text);
+
+	    break;
+
+	case 'a':
+
+	    if (current_call == -1 || 
+		pjsua.calls[current_call].inv->role != PJSIP_ROLE_UAS ||
+		pjsua.calls[current_call].inv->state >= PJSIP_INV_STATE_CONNECTING)
+	    {
+		puts("No pending incoming call");
+		fflush(stdout);
+		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, "<uri is too long>");
+		} 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; i<pjsua.buddy_cnt; ++i)
+			pjsua.buddies[i].monitor = (menuin[0]=='s');
+		} else if (result.nb_result == 0) {
+		    puts("Sorry, can only subscribe to buddy's presence, "
+			 "not from existing call");
+		} else {
+		    pjsua.buddies[result.nb_result-1].monitor = (menuin[0]=='s');
+		}
+
+		pjsua_pres_refresh(current_acc);
+
+	    } else if (result.uri_result) {
+		puts("Sorry, can only subscribe to buddy's presence, "
+		     "not arbitrary URL (for now)");
+	    }
+
+	    break;
+
+	case 'r':
+	    switch (menuin[1]) {
+	    case 'r':
+		/*
+		 * Re-Register.
+		 */
+		pjsua_regc_update(current_acc, PJ_TRUE);
+		break;
+	    case 'u':
+		/*
+		 * Unregister
+		 */
+		pjsua_regc_update(current_acc, PJ_FALSE);
+		break;
+	    }
+	    break;
+	    
+	case 't':
+	    pjsua.acc[current_acc].online_status = 
+		!pjsua.acc[current_acc].online_status;
+	    printf("Setting %s online status to %s\n",
+		   pjsua.config.acc_config[current_acc].id.ptr,
+		   (pjsua.acc[current_acc].online_status?"online":"offline"));
+	    pjsua_pres_refresh(current_acc);
+	    break;
+
+	case 'c':
+	    switch (menuin[1]) {
+	    case 'l':
+		conf_list();
+		break;
+	    case 'c':
+	    case 'd':
+		{
+		    char src_port[10], dst_port[10];
+		    pj_status_t status;
+		    const char *src_title, *dst_title;
+
+		    conf_list();
+
+		    src_title = (menuin[1]=='c'?
+				 "Connect src port #":
+				 "Disconnect src port #");
+		    dst_title = (menuin[1]=='c'?
+				 "To dst port #":
+				 "From dst port #");
+
+		    if (!simple_input(src_title, src_port, sizeof(src_port)))
+			break;
+
+		    if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
+			break;
+
+		    if (menuin[1]=='c') {
+			status = pjmedia_conf_connect_port(pjsua.mconf, 
+							   atoi(src_port), 
+							   atoi(dst_port),
+							   0);
+		    } else {
+			status = pjmedia_conf_disconnect_port(pjsua.mconf, 
+							      atoi(src_port), 
+							      atoi(dst_port));
+		    }
+		    if (status == PJ_SUCCESS) {
+			puts("Success");
+		    } else {
+			puts("ERROR!!");
+		    }
+		}
+		break;
+	    }
+	    break;
+
+	case 'd':
+	    if (menuin[1] == 'c') {
+		char settings[2000];
+		int len;
+
+		len = pjsua_dump_settings(&pjsua.config, settings, 
+					  sizeof(settings));
+		if (len < 1)
+		    PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
+		else
+		    PJ_LOG(3,(THIS_FILE, 
+			      "Dumping configuration (%d bytes):\n%s\n",
+			      len, settings));
+	    } else {
+		pjsua_dump(menuin[1]=='d');
+	    }
+	    break;
+
+	case 'q':
+	    goto on_exit;
+
+	default:
+	    if (menuin[0] != '\n' && menuin[0] != '\r') {
+		printf("Invalid input %s", menuin);
+	    }
+	    keystroke_help();
+	    break;
+	}
+    }
+
+on_exit:
+    ;
+}
+
+
+/*****************************************************************************
+ * This is a very simple PJSIP module, whose sole purpose is to display
+ * incoming and outgoing messages to log. This module will have priority
+ * higher than transport layer, which means:
+ *
+ *  - incoming messages will come to this module first before reaching
+ *    transaction layer.
+ *
+ *  - outgoing messages will come to this module last, after the message
+ *    has been 'printed' to contiguous buffer by transport layer and
+ *    appropriate transport instance has been decided for this message.
+ *
+ */
+
+/* Notification on incoming messages */
+static pj_bool_t console_on_rx_msg(pjsip_rx_data *rdata)
+{
+    PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n"
+			 "%s\n"
+			 "--end msg--",
+			 rdata->msg_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 5959d06..dba563c 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 <pjsua-lib/pjsua.h>
+#include "pjsua_imp.h"
 
 /*
  * pjsua_core.c
@@ -43,75 +44,147 @@
 /*
  * 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;
 
-
-    /* Default transport settings: */
-    pjsua.sip_port = 5060;
-
-
-    /* Default we start RTP at port 4000 */
-    pjsua.start_rtp_port = 4000;
-
-
-    /* Default logging settings: */
-    pjsua.log_level = 5;
-    pjsua.app_log_level = 4;
-    pjsua.log_decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | 
-		      PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE;
-
-
-    /* Default call settings. */
-    pjsua.uas_refresh = -1;
-    pjsua.uas_duration = -1;
-
-    /* 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;
+
+    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;
+
+
+    /* 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;
 
 
     /* Init accounts: */
-    pjsua.acc_cnt = 1;
     for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) {
-	pjsua.acc[i].index = i;
-	pjsua.acc[i].local_uri = pj_str(PJSUA_LOCAL_URI);
-	pjsua.acc[i].reg_timeout = 55;
-	pjsua.acc[i].online_status = PJ_TRUE;
-	pj_list_init(&pjsua.acc[i].route_set);
-	pj_list_init(&pjsua.acc[i].pres_srv_list);
+	cfg->acc_config[i].reg_timeout = 55;
     }
-
-    /* Init call array: */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.calls); ++i) {
-	pjsua.calls[i].index = i;
-	pjsua.calls[i].refresh_tm._timer_id = -1;
-	pjsua.calls[i].hangup_tm._timer_id = -1;
-	pjsua.calls[i].conf_slot = 0;
-    }
-
-    /* Default max nb of calls. */
-    pjsua.max_calls = 4;
-
-    /* Init server presence subscription list: */
-    
-
 }
 
 
+#define strncpy_with_null(dst,src,len)	\
+do { \
+    strncpy(dst, src, len); \
+    dst[len-1] = '\0'; \
+} while (0)
+
+
+
+PJ_DEF(pj_status_t) pjsua_test_config( const pjsua_config *cfg,
+				       char *errmsg,
+				       int len)
+{
+    unsigned i;
+
+    /* If UDP port is zero, then sip_host and sip_port must be specified */
+    if (cfg->udp_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;
+	}
+    }
+
+    if (cfg->max_calls < 1) {
+	strncpy_with_null(errmsg, 
+			  "max_calls needs to be at least 1",
+			  len);
+	return -1;
+    }
+
+    /* 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;
+	}
+    }
+
+    /* Verify accounts */
+    for (i=0; i<cfg->acc_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; j<acc_cfg->cred_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;
+}
+
 
 /*
  * Handler for receiving incoming requests.
@@ -152,293 +225,6 @@
 }
 
 
-/* 
- * Initialize sockets and optionally get the public address via STUN. 
- */
-static pj_status_t init_sockets(pj_bool_t sip,
-				pjmedia_sock_info *skinfo)
-{
-    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;
-
-    if (rtp_port == 0)
-	rtp_port = (pj_uint16_t)pjsua.start_rtp_port;
-
-    for (i=0; i<3; ++i)
-	sock[i] = PJ_INVALID_SOCKET;
-
-    /* Create and bind SIP UDP socket. */
-    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[SIP_SOCK]);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "socket() error", status);
-	goto on_error;
-    }
-
-    if (sip) {
-	status = pj_sock_bind_in(sock[SIP_SOCK], 0, pjsua.sip_port);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "bind() error", status);
-	    goto on_error;
-	}
-    } else {
-	status = pj_sock_bind_in(sock[SIP_SOCK], 0, 0);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "bind() error", status);
-	    goto on_error;
-	}
-    }
-
-
-    /* Loop retry to bind RTP and RTCP sockets. */
-    for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
-
-	/* Create and bind RTP socket. */
-	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[RTP_SOCK]);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "socket() error", status);
-	    goto on_error;
-	}
-
-	status = pj_sock_bind_in(sock[RTP_SOCK], 0, rtp_port);
-	if (status != PJ_SUCCESS) {
-	    pj_sock_close(sock[RTP_SOCK]); 
-	    sock[RTP_SOCK] = PJ_INVALID_SOCKET;
-	    continue;
-	}
-
-	/* Create and bind RTCP socket. */
-	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[RTCP_SOCK]);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "socket() error", status);
-	    goto on_error;
-	}
-
-	status = pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1));
-	if (status != PJ_SUCCESS) {
-	    pj_sock_close(sock[RTP_SOCK]); 
-	    sock[RTP_SOCK] = PJ_INVALID_SOCKET;
-
-	    pj_sock_close(sock[RTCP_SOCK]); 
-	    sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
-	    continue;
-	}
-
-	/*
-	 * If we're configured to use STUN, then find out the mapped address,
-	 * and make sure that the mapped RTCP port is adjacent with the RTP.
-	 */
-	if (pjsua.stun_port1 == 0) {
-	    const pj_str_t *hostname;
-	    pj_sockaddr_in addr;
-
-	    /* Get local IP address. */
-	    hostname = pj_gethostname();
-
-	    pj_memset( &addr, 0, sizeof(addr));
-	    addr.sin_family = PJ_AF_INET;
-	    status = pj_sockaddr_in_set_str_addr( &addr, hostname);
-	    if (status != PJ_SUCCESS) {
-		pjsua_perror(THIS_FILE, "Unresolvable local hostname", 
-			     status);
-		goto on_error;
-	    }
-
-	    for (i=0; i<3; ++i)
-		pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
-
-	    if (sip) {
-		mapped_addr[SIP_SOCK].sin_port = 
-		    pj_htons((pj_uint16_t)pjsua.sip_port);
-	    }
-	    mapped_addr[RTP_SOCK].sin_port=pj_htons((pj_uint16_t)rtp_port);
-	    mapped_addr[RTCP_SOCK].sin_port=pj_htons((pj_uint16_t)(rtp_port+1));
-	    break;
-
-	} else {
-	    status=pj_stun_get_mapped_addr(&pjsua.cp.factory, 3, sock,
-					   &pjsua.stun_srv1, pjsua.stun_port1,
-					   &pjsua.stun_srv2, pjsua.stun_port2,
-					   mapped_addr);
-	    if (status != PJ_SUCCESS) {
-		pjsua_perror(THIS_FILE, "STUN error", status);
-		goto on_error;
-	    }
-
-	    if (pj_ntohs(mapped_addr[2].sin_port) == 
-		pj_ntohs(mapped_addr[1].sin_port)+1)
-	    {
-		break;
-	    }
-
-	    pj_sock_close(sock[RTP_SOCK]); 
-	    sock[RTP_SOCK] = PJ_INVALID_SOCKET;
-
-	    pj_sock_close(sock[RTCP_SOCK]); 
-	    sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
-	}
-    }
-
-    if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) {
-	PJ_LOG(1,(THIS_FILE, 
-		  "Unable to find appropriate RTP/RTCP ports combination"));
-	goto on_error;
-    }
-
-    if (sip) {
-	pjsua.sip_sock = sock[SIP_SOCK];
-	pj_memcpy(&pjsua.sip_sock_name, 
-		  &mapped_addr[SIP_SOCK], 
-		  sizeof(pj_sockaddr_in));
-    } else {
-	pj_sock_close(sock[0]);
-    }
-
-    skinfo->rtp_sock = sock[RTP_SOCK];
-    pj_memcpy(&skinfo->rtp_addr_name, 
-	      &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in));
-
-    skinfo->rtcp_sock = sock[RTCP_SOCK];
-    pj_memcpy(&skinfo->rtcp_addr_name, 
-	      &mapped_addr[RTCP_SOCK], 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)));
-    PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
-	      pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr), 
-	      pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
-
-    rtp_port += 2;
-    return PJ_SUCCESS;
-
-on_error:
-    for (i=0; i<3; ++i) {
-	if (sip && i==0)
-	    continue;
-	if (sock[i] != PJ_INVALID_SOCKET)
-	    pj_sock_close(sock[i]);
-    }
-    return status;
-}
-
-
-
-/* 
- * Initialize stack. 
- */
-static pj_status_t init_stack(void)
-{
-    pj_status_t status;
-
-    /* Create global endpoint: */
-
-    {
-	const pj_str_t *hostname;
-	const char *endpt_name;
-
-	/* Endpoint MUST be assigned a globally unique name.
-	 * The name will be used as the hostname in Warning header.
-	 */
-
-	/* For this implementation, we'll use hostname for simplicity */
-	hostname = pj_gethostname();
-	endpt_name = hostname->ptr;
-
-	/* Create the endpoint: */
-
-	status = pjsip_endpt_create(&pjsua.cp.factory, endpt_name, 
-				    &pjsua.endpt);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create SIP endpoint", status);
-	    return status;
-	}
-    }
-
-
-    /* Initialize transaction layer: */
-
-    status = pjsip_tsx_layer_init_module(pjsua.endpt);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Transaction layer initialization error", 
-		     status);
-	goto on_error;
-    }
-
-    /* Initialize UA layer module: */
-
-    status = pjsip_ua_init_module( pjsua.endpt, NULL );
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "UA layer initialization error", status);
-	goto on_error;
-    }
-
-    /* Initialize and register pjsua's application module: */
-
-    {
-	pjsip_module my_mod = 
-	{
-	NULL, NULL,		    /* prev, next.			*/
-	{ "mod-pjsua", 9 },	    /* Name.				*/
-	-1,			    /* Id				*/
-	PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority			*/
-	NULL,			    /* load()				*/
-	NULL,			    /* start()				*/
-	NULL,			    /* stop()				*/
-	NULL,			    /* unload()				*/
-	&mod_pjsua_on_rx_request,   /* on_rx_request()			*/
-	&mod_pjsua_on_rx_response,  /* on_rx_response()			*/
-	NULL,			    /* on_tx_request.			*/
-	NULL,			    /* on_tx_response()			*/
-	NULL,			    /* on_tsx_state()			*/
-	};
-
-	pjsua.mod = my_mod;
-
-	status = pjsip_endpt_register_module(pjsua.endpt, &pjsua.mod);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to register pjsua module", 
-			 status);
-	    goto on_error;
-	}
-    }
-
-    /* Initialize invite session module: */
-
-    status = pjsua_call_init();
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Invite usage initialization error", 
-		     status);
-	goto on_error;
-    }
-
-    /* Done */
-
-    return PJ_SUCCESS;
-
-
-on_error:
-    pjsip_endpt_destroy(pjsua.endpt);
-    pjsua.endpt = NULL;
-    return status;
-}
-
-
 static int PJ_THREAD_FUNC pjsua_poll(void *arg)
 {
     pj_status_t last_err = 0;
@@ -459,20 +245,226 @@
     return 0;
 }
 
+
+
+#define pjsua_has_stun()    (pjsua.config.stun_port1 && \
+			     pjsua.config.stun_port2)
+
+
 /*
- * Initialize pjsua application.
- * This will initialize all libraries, create endpoint instance, and register
- * pjsip modules.
+ * Create and initialize SIP socket (and possibly resolve public
+ * address via STUN, depending on config).
  */
-pj_status_t pjsua_init(void)
+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;
 
-    /* Init PJLIB logging: */
+    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "socket() error", status);
+	return status;
+    }
 
-    pj_log_set_level(pjsua.log_level);
-    pj_log_set_decor(pjsua.log_decor);
+    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 (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, "STUN resolve error", status);
+	    pj_sock_close(sock);
+	    return status;
+	}
+
+    } else {
+
+	const pj_str_t *hostname = pj_gethostname();
+	struct pj_hostent he;
+
+	status = pj_gethostbyname(hostname, &he);
+	if (status != PJ_SUCCESS) {
+	    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; i<RTP_RETRY; ++i, rtp_port += 2) {
+
+	/* Create and bind RTP socket. */
+	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "socket() error", status);
+	    return status;
+	}
+
+	status = pj_sock_bind_in(sock[0], 0, rtp_port);
+	if (status != PJ_SUCCESS) {
+	    pj_sock_close(sock[0]); 
+	    sock[0] = PJ_INVALID_SOCKET;
+	    continue;
+	}
+
+	/* Create and bind RTCP socket. */
+	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[1]);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "socket() error", status);
+	    pj_sock_close(sock[0]);
+	    return status;
+	}
+
+	status = pj_sock_bind_in(sock[1], 0, (pj_uint16_t)(rtp_port+1));
+	if (status != PJ_SUCCESS) {
+	    pj_sock_close(sock[0]); 
+	    sock[0] = PJ_INVALID_SOCKET;
+
+	    pj_sock_close(sock[1]); 
+	    sock[1] = PJ_INVALID_SOCKET;
+	    continue;
+	}
+
+	/*
+	 * If we're configured to use STUN, then find out the mapped address,
+	 * and make sure that the mapped RTCP port is adjacent with the RTP.
+	 */
+	if (pjsua_has_stun()) {
+	    status=pj_stun_get_mapped_addr(&pjsua.cp.factory, 2, sock,
+					   &pjsua.config.stun_srv1, 
+					   pjsua.config.stun_port1,
+					   &pjsua.config.stun_srv2, 
+					   pjsua.config.stun_port2,
+					   mapped_addr);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "STUN resolve error", status);
+		goto on_error;
+	    }
+
+	    if (pj_ntohs(mapped_addr[1].sin_port) == 
+		pj_ntohs(mapped_addr[0].sin_port)+1)
+	    {
+		/* Success! */
+		break;
+	    }
+
+	    pj_sock_close(sock[0]); 
+	    sock[0] = PJ_INVALID_SOCKET;
+
+	    pj_sock_close(sock[1]); 
+	    sock[1] = PJ_INVALID_SOCKET;
+
+	} else {
+	    const pj_str_t *hostname;
+	    pj_sockaddr_in addr;
+
+	    /* Get local IP address. */
+	    hostname = pj_gethostname();
+
+	    pj_memset( &addr, 0, sizeof(addr));
+	    addr.sin_family = PJ_AF_INET;
+	    status = pj_sockaddr_in_set_str_addr( &addr, hostname);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Unresolvable local hostname", 
+			     status);
+		goto on_error;
+	    }
+
+	    for (i=0; i<2; ++i)
+		pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
+
+	    mapped_addr[0].sin_port=pj_htons((pj_uint16_t)rtp_port);
+	    mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(rtp_port+1));
+	    break;
+	}
+    }
+
+    if (sock[0] == PJ_INVALID_SOCKET) {
+	PJ_LOG(1,(THIS_FILE, 
+		  "Unable to find appropriate RTP/RTCP ports combination"));
+	goto on_error;
+    }
+
+
+    skinfo->rtp_sock = sock[0];
+    pj_memcpy(&skinfo->rtp_addr_name, 
+	      &mapped_addr[0], sizeof(pj_sockaddr_in));
+
+    skinfo->rtcp_sock = sock[1];
+    pj_memcpy(&skinfo->rtcp_addr_name, 
+	      &mapped_addr[1], sizeof(pj_sockaddr_in));
+
+    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)));
+    PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
+	      pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr), 
+	      pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
+
+    rtp_port += 2;
+    return PJ_SUCCESS;
+
+on_error:
+    for (i=0; i<2; ++i) {
+	if (sock[i] != PJ_INVALID_SOCKET)
+	    pj_sock_close(sock[i]);
+    }
+    return status;
+}
+
+
+
+/**
+ * Create pjsua application.
+ * This initializes pjlib/pjlib-util, and creates memory pool factory to
+ * be used by application.
+ */
+PJ_DEF(pj_status_t) pjsua_create(void)
+{
+    pj_status_t status;
 
     /* Init PJLIB: */
 
@@ -498,118 +490,33 @@
     /* 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: */
 
-    /* Init PJSIP : */
-
-    status = init_stack();
+    status = pjsip_endpt_create(&pjsua.cp.factory, 
+				pj_gethostname()->ptr, 
+				&pjsua.endpt);
     if (status != PJ_SUCCESS) {
-	pj_caching_pool_destroy(&pjsua.cp);
-	pjsua_perror(THIS_FILE, "Stack initialization has returned error", 
-		     status);
+	pjsua_perror(THIS_FILE, "Unable to create SIP endpoint", status);
 	return status;
     }
 
-
-    /* Init core SIMPLE module : */
-
-    pjsip_evsub_init_module(pjsua.endpt);
-
-    /* Init presence module: */
-
-    pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance());
-
-    /* Init xfer/REFER module */
-
-    pjsip_xfer_init_module( pjsua.endpt );
-
-    /* Init pjsua presence handler: */
-
-    pjsua_pres_init();
-
-    /* Init out-of-dialog MESSAGE request handler. */
-
-    pjsua_im_init();
-
-
-    /* Init media endpoint: */
-
+    /* 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) {
-	pj_caching_pool_destroy(&pjsua.cp);
 	pjsua_perror(THIS_FILE, 
 		     "Media stack initialization has returned error", 
 		     status);
 	return status;
     }
 
-    /* Done. */
+
     return PJ_SUCCESS;
 }
 
 
-/*
- * Find account for incoming request.
- */
-int pjsua_find_account_for_incoming(pjsip_rx_data *rdata)
-{
-    pjsip_uri *uri;
-    pjsip_sip_uri *sip_uri;
-    int acc_index;
-
-    uri = rdata->msg_info.to->uri;
-
-    /* Just return account #0 if To URI is not SIP: */
-    if (!PJSIP_URI_SCHEME_IS_SIP(uri) && 
-	!PJSIP_URI_SCHEME_IS_SIPS(uri)) 
-    {
-	return 0;
-    }
-
-
-    sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
-
-    /* Find account which has matching username and domain. */
-    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
-
-	pjsua_acc *acc = &pjsua.acc[acc_index];
-
-	if (pj_stricmp(&acc->user_part, &sip_uri->user)==0 &&
-	    pj_stricmp(&acc->host_part, &sip_uri->host)==0) 
-	{
-	    /* Match ! */
-	    return acc_index;
-	}
-    }
-
-    /* No matching, try match domain part only. */
-    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
-
-	pjsua_acc *acc = &pjsua.acc[acc_index];
-
-	if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) {
-	    /* Match ! */
-	    return acc_index;
-	}
-    }
-
-    /* Still no match, just return account #0 */
-    return 0;
-}
-
-
-/*
- * Find account for outgoing request.
- */
-int pjsua_find_account_for_outgoing(const pj_str_t *url)
-{
-    PJ_UNUSED_ARG(url);
-
-    /* Just use account #0 */
-    return 0;
-}
-
 
 /*
  * Init media.
@@ -628,7 +535,8 @@
     /* Register speex. */
     status = pjmedia_codec_speex_init(pjsua.med_endpt, 
 				      PJMEDIA_SPEEX_NO_UWB,
-				      pjsua.quality, pjsua.complexity );
+				      pjsua.config.quality, 
+				      pjsua.config.complexity );
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Error initializing Speex codec",
 		     status);
@@ -686,24 +594,27 @@
     /* Enable those codecs that user put with "--add-codec", and move
      * the priority to top
      */
-    for (i=0; i<pjsua.codec_cnt; ++i) {
+    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.codec_arg[i], 
+	    &pjsua.config.codec_arg[i], 
 	    PJMEDIA_CODEC_PRIO_HIGHEST);
     }
 
 
     /* Init options for conference bridge. */
     options = 0;
-    if (pjsua.no_mic)
-	options |= PJMEDIA_CONF_NO_MIC;
+
+    /* 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.clock_rate ? pjsua.clock_rate : 16000;
+    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.max_calls+PJSUA_CONF_MORE_PORTS, 
+				 pjsua.config.conf_ports, 
 				 clock_rate, 
 				 1, /* mono */
 				 samples_per_frame, 
@@ -717,21 +628,14 @@
 	return status;
     }
 
-    /* Add NULL port to the bridge. */
-    status = pjmedia_null_port_create( pjsua.pool, clock_rate, 
-				       1, /* mono */
-				       samples_per_frame, 16,
-				       &pjsua.null_port);
-    pjmedia_conf_add_port( pjsua.mconf, pjsua.pool, pjsua.null_port, 
-			   &pjsua.null_port->info.name, NULL );
-
     /* Create WAV file player if required: */
 
-    if (pjsua.wav_file) {
+    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.wav_file,
+	status = pjmedia_wav_player_port_create(  pjsua.pool, 
+						  pjsua.config.wav_file.ptr,
 						  0, 0, -1, NULL, 
 						  &pjsua.file_port);
 	if (status != PJ_SUCCESS) {
@@ -742,9 +646,10 @@
 	}
 
 	/* Add port to conference bridge: */
+	port_name = pjsua.config.wav_file;
 	status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, 
 				       pjsua.file_port, 
-				       pj_cstr(&port_name, pjsua.wav_file),
+				       &port_name,
 				       &pjsua.wav_slot);
 	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, 
@@ -760,60 +665,332 @@
 
 
 /*
- * Start pjsua stack.
- * This will start the registration process, if registration is configured.
+ * Copy configuration.
  */
-pj_status_t pjsua_start(void)
+static void copy_config(pj_pool_t *pool, pjsua_config *dst, 
+			const pjsua_config *src)
 {
-    int i;  /* Must be signed */
-    pjsip_transport *udp_transport;
-    pj_status_t status = PJ_SUCCESS;
+    unsigned i;
 
-    /*
-     * Init media subsystem (codecs, conference bridge, et all).
-     */
+    /* 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; i<src->codec_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; i<src->acc_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; j<src_acc->cred_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; i<src->buddy_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; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) {
+	pjsua.acc[i].index = i;
+	pjsua.acc[i].online_status = PJ_TRUE;
+	pj_list_init(&pjsua.acc[i].route_set);
+	pj_list_init(&pjsua.acc[i].pres_srv_list);
+    }
+
+    /* Init call array: */
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua.calls); ++i) {
+	pjsua.calls[i].index = i;
+	pjsua.calls[i].refresh_tm._timer_id = -1;
+	pjsua.calls[i].hangup_tm._timer_id = -1;
+	pjsua.calls[i].conf_slot = 0;
+    }
+
+    /* Copy configuration */
+    copy_config(pjsua.pool, &pjsua.config, cfg);
+
+    /* Copy callback */
+    pj_memcpy(&pjsua.cb, cb, sizeof(pjsua_callback));
+
+    /* Test configuration */
+    if (pjsua_test_config(&pjsua.config, errmsg, sizeof(errmsg))) {
+	PJ_LOG(1,(THIS_FILE, "Error in configuration: %s", errmsg));
+	return -1;
+    }
+
+
+    /* Init PJLIB logging: */
+
+    pj_log_set_level(pjsua.config.log_level);
+    pj_log_set_decor(pjsua.config.log_decor);
+
+
+    /* Create SIP UDP socket */
+    if (pjsua.config.udp_port) {
+
+	status = create_sip_udp_sock( pjsua.config.udp_port,
+				      &pjsua.sip_sock,
+				      &pjsua.sip_sock_name);
+	if (status != PJ_SUCCESS)
+	    return status;
+    
+	pj_strdup2_with_null(pjsua.pool, &pjsua.config.sip_host,
+			     pj_inet_ntoa(pjsua.sip_sock_name.sin_addr));
+	pjsua.config.sip_port = pj_ntohs(pjsua.sip_sock_name.sin_port);
+
+    } else {
+
+	/* Check that SIP host and port is configured */
+	if (cfg->sip_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;
 
-    /* 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);
+
+    /* Init RTP sockets, only when UDP transport is enabled */
+    for (i=0; pjsua.config.start_rtp_port && i<pjsua.config.max_calls; ++i) {
+	status = create_rtp_rtcp_sock(&pjsua.calls[i].skinfo);
 	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);
+	    unsigned j;
+	    for (j=0; j<i; ++j) {
+		pjmedia_transport_udp_close(pjsua.calls[j].med_tp);
 	    }
 	    return status;
 	}
+	status = pjmedia_transport_udp_attach(pjsua.med_endpt, NULL,
+					      &pjsua.calls[i].skinfo,
+					      &pjsua.calls[i].med_tp);
     }
 
-    /* Add UDP transport: */
+    /* Init PJSIP : */
+
+    /* Initialize transaction layer: */
+
+    status = pjsip_tsx_layer_init_module(pjsua.endpt);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Transaction layer initialization error", 
+		     status);
+	goto on_error;
+    }
+
+    /* Initialize UA layer module: */
+
+    status = pjsip_ua_init_module( pjsua.endpt, NULL );
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "UA layer initialization error", status);
+	goto on_error;
+    }
+
+    /* Initialize and register pjsua's application module: */
 
     {
+	pjsip_module my_mod = 
+	{
+	NULL, NULL,		    /* prev, next.			*/
+	{ "mod-pjsua", 9 },	    /* Name.				*/
+	-1,			    /* Id				*/
+	PJSIP_MOD_PRIORITY_APPLICATION,	/* Priority			*/
+	NULL,			    /* load()				*/
+	NULL,			    /* start()				*/
+	NULL,			    /* stop()				*/
+	NULL,			    /* unload()				*/
+	&mod_pjsua_on_rx_request,   /* on_rx_request()			*/
+	&mod_pjsua_on_rx_response,  /* on_rx_response()			*/
+	NULL,			    /* on_tx_request.			*/
+	NULL,			    /* on_tx_response()			*/
+	NULL,			    /* on_tsx_state()			*/
+	};
+
+	pjsua.mod = my_mod;
+
+	status = pjsip_endpt_register_module(pjsua.endpt, &pjsua.mod);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to register pjsua module", 
+			 status);
+	    goto on_error;
+	}
+    }
+
+    /* Initialize invite session module: */
+
+    status = pjsua_call_init();
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Invite usage initialization error", 
+		     status);
+	goto on_error;
+    }
+
+    /* Init core SIMPLE module : */
+
+    pjsip_evsub_init_module(pjsua.endpt);
+
+    /* Init presence module: */
+
+    pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance());
+
+    /* Init xfer/REFER module */
+
+    pjsip_xfer_init_module( pjsua.endpt );
+
+    /* Init pjsua presence handler: */
+
+    pjsua_pres_init();
+
+    /* Init out-of-dialog MESSAGE request handler. */
+
+    pjsua_im_init();
+
+
+    /* Done. */
+    return PJ_SUCCESS;
+
+on_error:
+    pj_caching_pool_destroy(&pjsua.cp);
+    return status;
+}
+
+
+/*
+ * Find account for incoming request.
+ */
+int pjsua_find_account_for_incoming(pjsip_rx_data *rdata)
+{
+    pjsip_uri *uri;
+    pjsip_sip_uri *sip_uri;
+    unsigned acc_index;
+
+    uri = rdata->msg_info.to->uri;
+
+    /* Just return last account if To URI is not SIP: */
+    if (!PJSIP_URI_SCHEME_IS_SIP(uri) && 
+	!PJSIP_URI_SCHEME_IS_SIPS(uri)) 
+    {
+	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.config.acc_cnt; ++acc_index) {
+
+	pjsua_acc *acc = &pjsua.acc[acc_index];
+
+	if (pj_stricmp(&acc->user_part, &sip_uri->user)==0 &&
+	    pj_stricmp(&acc->host_part, &sip_uri->host)==0) 
+	{
+	    /* Match ! */
+	    return acc_index;
+	}
+    }
+
+    /* No matching, try match domain part only. */
+    for (acc_index=0; acc_index < pjsua.config.acc_cnt; ++acc_index) {
+
+	pjsua_acc *acc = &pjsua.acc[acc_index];
+
+	if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) {
+	    /* Match ! */
+	    return acc_index;
+	}
+    }
+
+    /* Still no match, just return last account */
+    return pjsua.config.acc_cnt;
+}
+
+
+/*
+ * Find account for outgoing request.
+ */
+int pjsua_find_account_for_outgoing(const pj_str_t *url)
+{
+    PJ_UNUSED_ARG(url);
+
+    /* Just use account #0 */
+    return 0;
+}
+
+
+
+/*
+ * Start pjsua stack.
+ * This will start the registration process, if registration is configured.
+ */
+PJ_DEF(pj_status_t) pjsua_start(void)
+{
+    int i;  /* Must be signed */
+    pj_status_t status = PJ_SUCCESS;
+
+
+    /* 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 @@
 	}
     }
 
-    /* Initialize Contact URI, if one is not specified: */
-    for (i=0; i<pjsua.acc_cnt; ++i) {
+    /* The last account is default account to be used when nothing match 
+     * any configured accounts.
+     */
+    {
+	char buf[80];
+	pj_str_t tmp;
+	pjsua_acc_config *acc_cfg = 
+	    &pjsua.config.acc_config[pjsua.config.acc_cnt];
 
+	tmp.ptr = buf;
+	tmp.slen = pj_ansi_sprintf(tmp.ptr, "<sip:%s:%d>", 
+				   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 @@
 
 	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 @@
 
 		/* With the user part. */
 		len = pj_ansi_snprintf(contact, sizeof(contact),
-				  "<sip:%.*s@%.*s:%d>",
+				  "<sip:%.*s@%s:%d>",
 				  (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),
-				  "<sip:%.*s:%d>",
-				  (int)udp_transport->local_name.host.slen,
-				  udp_transport->local_name.host.ptr,
-				  udp_transport->local_name.port);
+				  "<sip:%s:%d>",
+				  addr, port);
 	    }
 
 	    if (len < 1 || len >= sizeof(contact)) {
@@ -897,38 +1096,39 @@
 
 	    /* Duplicate Contact uri. */
 
-	    pj_strdup2(pjsua.pool, &pjsua.acc[i].contact_uri, contact);
+	    pj_strdup2(pjsua.pool, &acc_cfg->contact, contact);
 
 	}
+
+
+	/* Build route-set for this account */
+	if (pjsua.config.outbound_proxy.slen) {
+	    pj_str_t hname = { "Route", 5};
+	    pjsip_route_hdr *r;
+	    pj_str_t tmp;
+
+	    pj_strdup_with_null(pjsua.pool, &tmp, &pjsua.config.outbound_proxy);
+	    r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL);
+	    pj_list_push_back(&acc->route_set, r);
+	}
+
+	if (acc_cfg->proxy.slen) {
+	    pj_str_t hname = { "Route", 5};
+	    pjsip_route_hdr *r;
+	    pj_str_t tmp;
+
+	    pj_strdup_with_null(pjsua.pool, &tmp, &acc_cfg->proxy);
+	    r = pjsip_parse_hdr(pjsua.pool, &hname, tmp.ptr, tmp.slen, NULL);
+	    pj_list_push_back(&acc->route_set, r);
+	}
     }
 
-    /* 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;
-
-	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;
-	}
-
-	for (i=0; i<pjsua.acc_cnt; ++i) {
-	    pj_list_push_front(&pjsua.acc[i].route_set, route);
-	}
-    }
 
 
     /* Create worker thread(s), if required: */
 
-    for (i=0; i<pjsua.thread_cnt; ++i) {
+    for (i=0; i<(int)pjsua.config.thread_cnt; ++i) {
 	status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_poll,
 				   NULL, 0, 0, &pjsua.threads[i]);
 	if (status != PJ_SUCCESS) {
@@ -944,7 +1144,7 @@
     /* Start registration: */
 
     /* Create client registration session: */
-    for (i=0; i<pjsua.acc_cnt; ++i) {
+    for (i=0; i<(int)pjsua.config.acc_cnt; ++i) {
 	status = pjsua_regc_init(i);
 	if (status != PJ_SUCCESS)
 	    return status;
@@ -956,6 +1156,12 @@
     }
 
 
+    /* Init buddies */
+    for (i=0; i<(int)pjsua.config.buddy_cnt; ++i) {
+	pjsua.buddies[i].uri = pjsua.config.buddy_uri[i];
+    }
+    pjsua.buddy_cnt = pjsua.config.buddy_cnt;
+
     /* Find account for outgoing preence subscription */
     for (i=0; i<pjsua.buddy_cnt; ++i) {
 	pjsua.buddies[i].acc_index = 
@@ -986,7 +1192,7 @@
 /*
  * Destroy pjsua.
  */
-pj_status_t pjsua_destroy(void)
+PJ_DEF(pj_status_t) pjsua_destroy(void)
 {
     int i;  /* Must be signed */
 
@@ -1000,14 +1206,14 @@
     pjsua_pres_shutdown();
 
     /* Unregister, if required: */
-    for (i=0; i<pjsua.acc_cnt; ++i) {
+    for (i=0; i<(int)pjsua.config.acc_cnt; ++i) {
 	if (pjsua.acc[i].regc) {
 	    pjsua_regc_update(i, 0);
 	}
     }
 
     /* Wait worker threads to quit: */
-    for (i=0; i<pjsua.thread_cnt; ++i) {
+    for (i=0; i<(int)pjsua.config.thread_cnt; ++i) {
 	
 	if (pjsua.threads[i]) {
 	    pj_thread_join(pjsua.threads[i]);
@@ -1029,10 +1235,6 @@
     if (pjsua.file_port)
 	pjmedia_port_destroy(pjsua.file_port);
 
-    /* Destroy null port. */
-    if (pjsua.null_port)
-	pjmedia_port_destroy(pjsua.null_port);
-
 
     /* Shutdown all codecs: */
 #if PJMEDIA_HAS_SPEEX_CODEC
@@ -1053,7 +1255,7 @@
 
 
     /* Close transports */
-    for (i=0; i<pjsua.call_cnt; ++i) {
+    for (i=0; pjsua.config.start_rtp_port && i<(int)pjsua.config.max_calls; ++i) {
 	pjmedia_transport_udp_close(pjsua.calls[i].med_tp);
     }
 
diff --git a/pjsip/src/pjsua-lib/pjsua_im.c b/pjsip/src/pjsua-lib/pjsua_im.c
index cd0bd89..a5d96df 100644
--- a/pjsip/src/pjsua-lib/pjsua_im.c
+++ b/pjsip/src/pjsua-lib/pjsua_im.c
@@ -152,7 +152,8 @@
 	text.ptr = rdata->msg_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 @@
 	    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 @@
 /**
  * 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 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 @@
 
     /* 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 @@
 /**
  * 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 @@
 
     /* 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 0000000..b406415
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_imp.h
@@ -0,0 +1,95 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * 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 b77871f..be31e2e 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 <pjsua-lib/pjsua.h>
+#include "pjsua_imp.h"
 
 /*
  * pjsua_pres.c
@@ -80,6 +81,7 @@
 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 @@
 
     /* 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 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 @@
 	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 @@
 /*
  * 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 @@
     int acc_index;
     int i;
 
-    for (acc_index=0; acc_index<pjsua.acc_cnt; ++acc_index) {
+    for (acc_index=0; acc_index<(int)pjsua.config.acc_cnt; ++acc_index) {
 	pjsua.acc[acc_index].online_status = 0;
     }
 
@@ -449,7 +457,7 @@
 	pjsua.buddies[i].monitor = 0;
     }
 
-    for (acc_index=0; acc_index<pjsua.acc_cnt; ++acc_index) {
+    for (acc_index=0; acc_index<(int)pjsua.config.acc_cnt; ++acc_index) {
 	pjsua_pres_refresh(acc_index);
     }
 }
@@ -471,7 +479,7 @@
 	
 	int count = 0;
 
-	for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
+	for (acc_index=0; acc_index < (int)pjsua.config.acc_cnt; ++acc_index) {
 
 	    if (!pj_list_empty(&pjsua.acc[acc_index].pres_srv_list)) {
 		struct pjsua_srv_pres *uapres;
@@ -506,11 +514,11 @@
      */
     PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
 
-    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
+    for (acc_index=0; acc_index < (int)pjsua.config.acc_cnt; ++acc_index) {
 
 	PJ_LOG(3,(THIS_FILE, "  %.*s",
-		  (int)pjsua.acc[acc_index].local_uri.slen,
-		  pjsua.acc[acc_index].local_uri.ptr));
+		  (int)pjsua.config.acc_config[acc_index].id.slen,
+		  pjsua.config.acc_config[acc_index].id.ptr));
 
 	if (pj_list_empty(&pjsua.acc[acc_index].pres_srv_list)) {
 
diff --git a/pjsip/src/pjsua-lib/pjsua_reg.c b/pjsip/src/pjsua-lib/pjsua_reg.c
index 83485f1..64ebe97 100644
--- a/pjsip/src/pjsua-lib/pjsua_reg.c
+++ b/pjsip/src/pjsua-lib/pjsua_reg.c
@@ -59,12 +59,12 @@
 	    pjsip_regc_destroy(acc->regc);
 	    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 @@
     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 @@
  */
 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 @@
 
 
     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 @@
 	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 cb98eff..e1bdd34 100644
--- a/pjsip/src/pjsua-lib/pjsua_settings.c
+++ b/pjsip/src/pjsua-lib/pjsua_settings.c
@@ -59,22 +59,17 @@
     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 @@
     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 @@
 /*
  * 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 @@
 
 
 /* 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 @@
 	   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 @@
 	{ "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 @@
 	{ "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 @@
 	    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 @@
 	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 @@
 			  "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 @@
 	    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 @@
 				     "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 @@
 			  "--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 @@
 			  "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 @@
 			  "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 @@
 			  "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 @@
 	    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 @@
 			  "--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 @@
 	    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 @@
 	    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 @@
 	    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 @@
 	    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 @@
 	    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 @@
 	    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 @@
     }
 
     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; i<pjsua.buddy_cnt; ++i) {
-	    if (pj_stricmp(&pjsua.buddies[i].uri, &pjsua.uri_to_call)==0)
+	for (i=0; i<cfg->buddy_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 @@
 	return PJ_EINVAL;
     }
 
+    if (cfg->acc_config[0].id.slen && cfg->acc_cnt==0)
+	cfg->acc_cnt = 1;
+
+    for (i=0; i<cfg->acc_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 @@
 /*
  * 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 @@
 	PJ_LOG(3,(THIS_FILE, "  - no sessions -"));
 
     } else {
-	int i;
+	unsigned i;
 
-	for (i=0; i<pjsua.max_calls; ++i) {
+	for (i=0; i<pjsua.config.max_calls; ++i) {
 
 	    pjsua_call *call = &pjsua.calls[i];
 	    pj_time_val duration, res_delay, con_delay;
@@ -917,13 +916,14 @@
 /*
  * Load settings.
  */
-pj_status_t pjsua_load_settings(const char *filename)
+PJ_DECL(pj_status_t) pjsua_load_settings(const char *filename,
+					 pjsua_config *cfg)
 {
     int argc = 3;
     char *argv[4] = { "pjsua", "--config-file", NULL, NULL};
 
     argv[3] = (char*)filename;
-    return pjsua_parse_args(argc, argv);
+    return pjsua_parse_args(argc, argv, cfg);
 }
 
 
@@ -933,7 +933,7 @@
 static void save_account_settings(int acc_index, pj_str_t *result)
 {
     char line[128];
-    pjsua_acc *acc = &pjsua.acc[acc_index];
+    pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index];
 
     
     pj_ansi_sprintf(line, "#\n# Account %d:\n#\n", acc_index);
@@ -941,33 +941,55 @@
 
 
     /* Identity */
-    if (acc->local_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 @@
 /*
  * 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 @@
     /* 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);
+    for (acc_index=0; acc_index < config->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; i<pjsua.cred_count; ++i) {
-
-	pj_ansi_sprintf(line, "#\n# Credential %d:\n#\n", i);
-	pj_strcat2(&cfg, line);
-
-	if (pjsua.cred_info[i].realm.slen) {
-	    pj_ansi_sprintf(line, "--realm %.*s\n",
-				  (int)pjsua.cred_info[i].realm.slen,
-				  pjsua.cred_info[i].realm.ptr);
-	    pj_strcat2(&cfg, line);
-	}
-
-	pj_ansi_sprintf(line, "--username %.*s\n",
-			      (int)pjsua.cred_info[i].username.slen,
-			      pjsua.cred_info[i].username.ptr);
-	pj_strcat2(&cfg, line);
-
-	pj_ansi_sprintf(line, "--password %.*s\n",
-			      (int)pjsua.cred_info[i].data.slen,
-			      pjsua.cred_info[i].data.ptr);
-	pj_strcat2(&cfg, line);
-
-	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 @@
 
 
     /* 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; i<pjsua.codec_cnt; ++i) {
+    for (i=0; i<config->codec_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; i<pjsua.buddy_cnt; ++i) {
+    for (i=0; i<config->buddy_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 @@
 /*
  * 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 @@
     }
 
 
-    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;