Index: pjproject/trunk/pjsip-apps/src/pjsua/main.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/pjsua/main.c	(revision 475)
+++ pjproject/trunk/pjsip-apps/src/pjsua/main.c	(revision 476)
@@ -18,1002 +18,8 @@
  */
 #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));
-}
-
-
-
 
 /*****************************************************************************
@@ -1023,28 +29,33 @@
 {
 
+    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);
 
 
@@ -1062,5 +73,5 @@
 
     /* Start UI console main loop: */
-    ui_console_main();
+    pjsua_console_app_main();
 
 
@@ -1070,5 +81,5 @@
 
     /* Close logging: */
-    app_logging_shutdown();
+    pjsua_console_app_logging_shutdown();
 
 
Index: pjproject/trunk/pjsip/build/pjsua_lib.dsp
===================================================================
--- pjproject/trunk/pjsip/build/pjsua_lib.dsp	(revision 475)
+++ pjproject/trunk/pjsip/build/pjsua_lib.dsp	(revision 476)
@@ -92,4 +92,8 @@
 # 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
@@ -97,4 +101,8 @@
 
 SOURCE="..\src\pjsua-lib\pjsua_im.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjsua-lib\pjsua_imp.h"
 # End Source File
 # Begin Source File
@@ -118,4 +126,8 @@
 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
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 475)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 476)
@@ -49,5 +49,5 @@
  */
 #ifndef PJSUA_MAX_BUDDIES
-#   define PJSUA_MAX_BUDDIES	    32
+#   define PJSUA_MAX_BUDDIES	    256
 #endif
 
@@ -62,24 +62,10 @@
 
 /**
- * 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
 
 
@@ -141,4 +127,39 @@
 
 /**
+ * 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
  */
@@ -146,17 +167,12 @@
 {
     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.			*/
 
@@ -168,5 +184,189 @@
 
 
+/**
+ * @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;
 
 
@@ -179,77 +379,33 @@
     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.	*/
@@ -274,24 +430,33 @@
  * Initialize pjsua settings with default parameters.
  */
-void pjsua_default(void);
-
-
-/**
- * Display error message for the specified error code.
- */
-void pjsua_perror(const char *sender, const char *title, 
-		  pj_status_t status);
-
-
-/**
- * Initialize pjsua application. Application can call this before parsing
- * application settings.
+PJ_DECL(void) pjsua_default_config(pjsua_config *cfg);
+
+
+/**
+ * Test configuration.
+ */
+PJ_DECL(pj_status_t) pjsua_test_config(const pjsua_config *cfg,
+				       char *errmsg,
+				       int len);
+
+
+/**
+ * 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);
 
 
@@ -303,5 +468,5 @@
  * process, if registration is configured.
  */
-pj_status_t pjsua_start(void);
+PJ_DECL(pj_status_t) pjsua_start(void);
 
 
@@ -309,17 +474,5 @@
  * 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);
 
 
@@ -329,20 +482,9 @@
 
 /**
- * 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);
 
 
@@ -350,10 +492,10 @@
  * 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);
 
 
@@ -361,5 +503,5 @@
  * Put call on-hold.
  */
-void pjsua_call_set_hold(int call_index);
+PJ_DECL(void) pjsua_call_set_hold(int call_index);
 
 
@@ -367,5 +509,5 @@
  * Send re-INVITE (to release hold).
  */
-void pjsua_call_reinvite(int call_index);
+PJ_DECL(void) pjsua_call_reinvite(int call_index);
 
 
@@ -373,5 +515,5 @@
  * Transfer call.
  */
-void pjsua_call_xfer(int call_index, const char *dest);
+PJ_DECL(void) pjsua_call_xfer(int call_index, const char *dest);
 
 
@@ -379,5 +521,5 @@
  * 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);
 
 
@@ -385,10 +527,10 @@
  * 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);
 
 
@@ -398,15 +540,8 @@
 
 /**
- * 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);
 
 
@@ -418,22 +553,12 @@
 
 /**
- * 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);
 
 
@@ -448,20 +573,10 @@
 
 
-/**
- * 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);
 
 
@@ -469,55 +584,7 @@
  * Send typing indication outside dialog.
  */
-pj_status_t pjsua_im_typing(int acc_index, const char *dst_uri, 
-			    pj_bool_t is_typing);
-
-
-/**
- * Private: check if we can accept the message.
- *	    If not, then p_accept header will be filled with a valid
- *	    Accept header.
- */
-pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata,
-				pjsip_accept_hdr **p_accept_hdr);
-
-/**
- * Private: process pager message.
- *	    This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing().
- */
-void pjsua_im_process_pager(int call_id, const pj_str_t *from,
-			    const pj_str_t *to, pjsip_rx_data *rdata);
-
-
-/*****************************************************************************
- * User Interface API.
- *
- * The UI API specifies functions that will be called by pjsua upon
- * occurence of various events.
- */
-
-/**
- * Notify UI when invite state has changed.
- */
-void pjsua_ui_on_call_state(int call_index, pjsip_event *e);
-
-/**
- * Notify UI when registration status has changed.
- */
-void pjsua_ui_on_reg_state(int acc_index);
-
-/**
- * Notify UI on incoming pager (i.e. MESSAGE request).
- * Argument call_index will be -1 if MESSAGE request is not related to an 
- * existing call.
- */
-void pjsua_ui_on_pager(int call_index, const pj_str_t *from,
-		       const pj_str_t *to, const pj_str_t *txt);
-
-
-/**
- * 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);
+PJ_DECL(pj_status_t) pjsua_im_typing(int acc_index, const char *dst_uri, 
+				     pj_bool_t is_typing);
+
 
 
@@ -533,20 +600,24 @@
  * 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);
 
 
@@ -555,10 +626,18 @@
  * @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);
+
+
 
 
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua_console_app.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua_console_app.h	(revision 476)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua_console_app.h	(revision 476)
@@ -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__ */
Index: pjproject/trunk/pjsip/src/pjsip-ua/sip_reg.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsip-ua/sip_reg.c	(revision 475)
+++ pjproject/trunk/pjsip/src/pjsip-ua/sip_reg.c	(revision 476)
@@ -485,4 +485,5 @@
     
     /* Decrement pending transaction counter. */
+    pj_assert(regc->pending_tsx > 0);
     --regc->pending_tsx;
 
@@ -610,8 +611,11 @@
     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;
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 475)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 476)
@@ -19,5 +19,5 @@
 #include <pjsua-lib/pjsua.h>
 #include <pj/log.h>
-
+#include "pjsua_imp.h"
 
 /*
@@ -57,5 +57,6 @@
 	} 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);
 	}
@@ -127,7 +128,7 @@
  * 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;
@@ -135,5 +136,5 @@
     pjmedia_sdp_session *offer;
     pjsip_inv_session *inv = NULL;
-    int call_index = -1;
+    unsigned call_index;
     pjsip_tx_data *tdata;
     pj_status_t status;
@@ -144,10 +145,10 @@
 
     /* 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;
@@ -162,6 +163,6 @@
     /* 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);
@@ -205,7 +206,10 @@
 
     /* 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);
+    }
 
 
@@ -226,4 +230,10 @@
 	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;
     }
@@ -243,5 +253,5 @@
     if (inv != NULL) {
 	pjsip_inv_terminate(inv, PJSIP_SC_OK, PJ_FALSE);
-    } else {
+    } else if (dlg) {
 	pjsip_dlg_terminate(dlg);
     }
@@ -251,4 +261,34 @@
     }
     return status;
+}
+
+
+/**
+ * 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);
+
 }
 
@@ -266,5 +306,5 @@
     pjsip_inv_session *inv = NULL;
     int acc_index;
-    int call_index = -1;
+    unsigned call_index;
     pjmedia_sdp_session *answer;
     pj_status_t status;
@@ -313,5 +353,5 @@
 
     /* 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;
@@ -354,5 +394,5 @@
 
     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) {
@@ -388,6 +428,6 @@
     
     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) {
@@ -401,5 +441,5 @@
 	 * 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
@@ -416,5 +456,5 @@
     }
 
-    if (pjsua.auto_answer < 200) {
+    if (pjsua.config.auto_answer < 200) {
 	PJ_LOG(3,(THIS_FILE,
 		  "\nIncoming call!!\n"
@@ -433,6 +473,6 @@
 		  (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 ));
     }
 
@@ -440,17 +480,17 @@
 
     /* 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);
     }
 
@@ -551,5 +591,6 @@
 
 
-    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 */
@@ -966,5 +1007,5 @@
     }
 
-    if (pjsua.null_audio)
+    if (pjsua.config.null_audio)
 	return;
 
@@ -984,7 +1025,8 @@
 
     /* 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;
@@ -1038,5 +1080,5 @@
      * port 
      */
-    if (pjsua.auto_play && pjsua.wav_file && 
+    if (pjsua.config.auto_play && pjsua.config.wav_file.slen && 
 	call->inv->role == PJSIP_ROLE_UAS) 
     {
@@ -1046,5 +1088,5 @@
 
     }
-    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, 
@@ -1052,11 +1094,11 @@
 
     }
-    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)
@@ -1074,6 +1116,6 @@
      * 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);
@@ -1128,5 +1170,5 @@
  * Hangup call.
  */
-void pjsua_call_hangup(int call_index)
+PJ_DEF(void) pjsua_call_hangup(int call_index)
 {
     pjsua_call *call;
@@ -1178,5 +1220,5 @@
  * 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;
@@ -1219,5 +1261,5 @@
  * re-INVITE.
  */
-void pjsua_call_reinvite(int call_index)
+PJ_DEF(void) pjsua_call_reinvite(int call_index)
 {
     pjmedia_sdp_session *sdp;
@@ -1266,5 +1308,5 @@
  * 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;
@@ -1318,5 +1360,5 @@
  * 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;
@@ -1374,5 +1416,5 @@
  * 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;
@@ -1416,9 +1458,9 @@
  * Terminate all calls.
  */
-void pjsua_call_hangup_all(void)
-{
-    int i;
-
-    for (i=0; i<pjsua.max_calls; ++i) {
+PJ_DEF(void) pjsua_call_hangup_all(void)
+{
+    unsigned i;
+
+    for (i=0; i<pjsua.config.max_calls; ++i) {
 	pjsip_tx_data *tdata;
 	int st_code;
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_console_app.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_console_app.c	(revision 476)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_console_app.c	(revision 476)
@@ -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,
+};
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c	(revision 475)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c	(revision 476)
@@ -18,4 +18,5 @@
  */
 #include <pjsua-lib/pjsua.h>
+#include "pjsua_imp.h"
 
 /*
@@ -44,72 +45,144 @@
  * Init default application parameters.
  */
-void pjsua_default(void)
+PJ_DEF(void) pjsua_default_config(pjsua_config *cfg)
 {
     unsigned i;
 
-
-    /* Normally need another thread for console application, because main 
-     * thread will be blocked in fgets().
-     */
-    pjsua.thread_cnt = 1;
-
-
-    /* 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: */
+    pj_memset(cfg, 0, sizeof(pjsua_config));
+
+    cfg->thread_cnt = 1;
+    cfg->udp_port = 5060;
+    cfg->start_rtp_port = 4000;
+    cfg->max_calls = 4;
+    cfg->conf_ports = 0;
+
 #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
     pjsua.clock_rate = 44100;
 #endif
-    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);
-    }
-
-    /* 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: */
-    
-
-}
-
+	cfg->acc_config[i].reg_timeout = 55;
+    }
+}
+
+
+#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;
+}
 
 
@@ -153,50 +226,110 @@
 
 
+static int PJ_THREAD_FUNC pjsua_poll(void *arg)
+{
+    pj_status_t last_err = 0;
+
+    PJ_UNUSED_ARG(arg);
+
+    do {
+	pj_time_val timeout = { 0, 10 };
+	pj_status_t status;
+	
+	status = pjsip_endpt_handle_events (pjsua.endpt, &timeout);
+	if (status != PJ_SUCCESS && status != last_err) {
+	    last_err = status;
+	    pjsua_perror(THIS_FILE, "handle_events() returned error", status);
+	}
+    } while (!pjsua.quit_flag);
+
+    return 0;
+}
+
+
+
+#define pjsua_has_stun()    (pjsua.config.stun_port1 && \
+			     pjsua.config.stun_port2)
+
+
+/*
+ * Create and initialize SIP socket (and possibly resolve public
+ * address via STUN, depending on config).
+ */
+static pj_status_t create_sip_udp_sock(int port,
+				       pj_sock_t *p_sock,
+				       pj_sockaddr_in *p_pub_addr)
+{
+    pj_sock_t sock;
+    pj_status_t status;
+
+    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "socket() error", status);
+	return status;
+    }
+
+    status = pj_sock_bind_in(sock, 0, (pj_uint16_t)port);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "bind() error", status);
+	pj_sock_close(sock);
+	return status;
+    }
+
+    if (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;
+}
+
+
 /* 
- * Initialize sockets and optionally get the public address via STUN. 
- */
-static pj_status_t init_sockets(pj_bool_t sip,
-				pjmedia_sock_info *skinfo)
+ * 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
     };
-    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_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.start_rtp_port;
-
-    for (i=0; i<3; ++i)
+	rtp_port = (pj_uint16_t)pjsua.config.start_rtp_port;
+
+    for (i=0; i<2; ++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;
-	}
-    }
 
 
@@ -205,31 +338,32 @@
 
 	/* Create and bind RTP socket. */
-	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[RTP_SOCK]);
+	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]);
 	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);
+	    return status;
+	}
+
+	status = pj_sock_bind_in(sock[0], 0, rtp_port);
 	if (status != PJ_SUCCESS) {
-	    pj_sock_close(sock[RTP_SOCK]); 
-	    sock[RTP_SOCK] = PJ_INVALID_SOCKET;
+	    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[RTCP_SOCK]);
+	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[1]);
 	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));
+	    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[RTP_SOCK]); 
-	    sock[RTP_SOCK] = PJ_INVALID_SOCKET;
-
-	    pj_sock_close(sock[RTCP_SOCK]); 
-	    sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
+	    pj_sock_close(sock[0]); 
+	    sock[0] = PJ_INVALID_SOCKET;
+
+	    pj_sock_close(sock[1]); 
+	    sock[1] = PJ_INVALID_SOCKET;
 	    continue;
 	}
@@ -239,5 +373,30 @@
 	 * and make sure that the mapped RTCP port is adjacent with the RTP.
 	 */
-	if (pjsua.stun_port1 == 0) {
+	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;
@@ -255,40 +414,14 @@
 	    }
 
-	    for (i=0; i<3; ++i)
+	    for (i=0; i<2; ++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));
+	    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;
-
-	} 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) {
+	}
+    }
+
+    if (sock[0] == PJ_INVALID_SOCKET) {
 	PJ_LOG(1,(THIS_FILE, 
 		  "Unable to find appropriate RTP/RTCP ports combination"));
@@ -296,26 +429,13 @@
     }
 
-    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];
+
+    skinfo->rtp_sock = sock[0];
     pj_memcpy(&skinfo->rtp_addr_name, 
-	      &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in));
-
-    skinfo->rtcp_sock = sock[RTCP_SOCK];
+	      &mapped_addr[0], sizeof(pj_sockaddr_in));
+
+    skinfo->rtcp_sock = sock[1];
     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)));
-    }
+	      &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), 
@@ -329,7 +449,5 @@
 
 on_error:
-    for (i=0; i<3; ++i) {
-	if (sip && i==0)
-	    continue;
+    for (i=0; i<2; ++i) {
 	if (sock[i] != PJ_INVALID_SOCKET)
 	    pj_sock_close(sock[i]);
@@ -340,35 +458,361 @@
 
 
-/* 
- * Initialize stack. 
- */
-static pj_status_t init_stack(void)
+/**
+ * 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: */
+
+    status = pj_init();
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "pj_init() error", status);
+	return status;
+    }
+
+    /* Init PJLIB-UTIL: */
+
+    status = pjlib_util_init();
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "pjlib_util_init() error", status);
+	return status;
+    }
+
+    /* Init memory pool: */
+
+    /* Init caching pool. */
+    pj_caching_pool_init(&pjsua.cp, &pj_pool_factory_default_policy, 0);
+
+    /* Create memory pool for application. */
+    pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL);
+
+    /* Must create endpoint to initialize SIP parser. */
     /* Create global endpoint: */
 
-    {
-	const pj_str_t *hostname;
-	const char *endpt_name;
-
-	/* 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);
+    status = pjsip_endpt_create(&pjsua.cp.factory, 
+				pj_gethostname()->ptr, 
+				&pjsua.endpt);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create SIP endpoint", status);
+	return status;
+    }
+
+    /* Must create media endpoint too */
+    status = pjmedia_endpt_create(&pjsua.cp.factory, 
+				  pjsip_endpt_get_ioqueue(pjsua.endpt), 0,
+				  &pjsua.med_endpt);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, 
+		     "Media stack initialization has returned error", 
+		     status);
+	return status;
+    }
+
+
+    return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Init media.
+ */
+static pj_status_t init_media(void)
+{
+    int i;
+    unsigned options;
+    unsigned clock_rate;
+    unsigned samples_per_frame;
+    pj_str_t codec_id;
+    pj_status_t status;
+
+    /* Register all codecs */
+#if PJMEDIA_HAS_SPEEX_CODEC
+    /* Register speex. */
+    status = pjmedia_codec_speex_init(pjsua.med_endpt, 
+				      PJMEDIA_SPEEX_NO_UWB,
+				      pjsua.config.quality, 
+				      pjsua.config.complexity );
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error initializing Speex codec",
+		     status);
+	return status;
+    }
+
+    /* Set "speex/16000/1" to have highest priority */
+    codec_id = pj_str("speex/16000/1");
+    pjmedia_codec_mgr_set_codec_priority( 
+	pjmedia_endpt_get_codec_mgr(pjsua.med_endpt),
+	&codec_id, 
+	PJMEDIA_CODEC_PRIO_HIGHEST);
+
+#endif /* PJMEDIA_HAS_SPEEX_CODEC */
+
+#if PJMEDIA_HAS_GSM_CODEC
+    /* Register GSM */
+    status = pjmedia_codec_gsm_init(pjsua.med_endpt);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error initializing GSM codec",
+		     status);
+	return status;
+    }
+#endif /* PJMEDIA_HAS_GSM_CODEC */
+
+#if PJMEDIA_HAS_G711_CODEC
+    /* Register PCMA and PCMU */
+    status = pjmedia_codec_g711_init(pjsua.med_endpt);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error initializing G711 codec",
+		     status);
+	return status;
+    }
+#endif	/* PJMEDIA_HAS_G711_CODEC */
+
+#if PJMEDIA_HAS_L16_CODEC
+    /* Register L16 family codecs, but disable all */
+    status = pjmedia_codec_l16_init(pjsua.med_endpt, 0);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
+		     status);
+	return status;
+    }
+
+    /* Disable ALL L16 codecs */
+    codec_id = pj_str("L16");
+    pjmedia_codec_mgr_set_codec_priority( 
+	pjmedia_endpt_get_codec_mgr(pjsua.med_endpt),
+	&codec_id, 
+	PJMEDIA_CODEC_PRIO_DISABLED);
+
+#endif	/* PJMEDIA_HAS_L16_CODEC */
+
+
+    /* Enable those codecs that user put with "--add-codec", and move
+     * the priority to top
+     */
+    for (i=0; i<(int)pjsua.config.codec_cnt; ++i) {
+	pjmedia_codec_mgr_set_codec_priority( 
+	    pjmedia_endpt_get_codec_mgr(pjsua.med_endpt),
+	    &pjsua.config.codec_arg[i], 
+	    PJMEDIA_CODEC_PRIO_HIGHEST);
+    }
+
+
+    /* Init options for conference bridge. */
+    options = 0;
+
+    /* Calculate maximum number of ports, if it's not specified */
+    if (pjsua.config.conf_ports == 0) {
+	pjsua.config.conf_ports = 3 * pjsua.config.max_calls;
+    }
+
+    /* Init conference bridge. */
+    clock_rate = pjsua.config.clock_rate ? pjsua.config.clock_rate : 16000;
+    samples_per_frame = clock_rate * 10 / 1000;
+    status = pjmedia_conf_create(pjsua.pool, 
+				 pjsua.config.conf_ports, 
+				 clock_rate, 
+				 1, /* mono */
+				 samples_per_frame, 
+				 16, 
+				 options,
+				 &pjsua.mconf);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, 
+		     "Media stack initialization has returned error", 
+		     status);
+	return status;
+    }
+
+    /* Create WAV file player if required: */
+
+    if (pjsua.config.wav_file.slen) {
+	pj_str_t port_name;
+
+	/* Create the file player port. */
+	status = pjmedia_wav_player_port_create(  pjsua.pool, 
+						  pjsua.config.wav_file.ptr,
+						  0, 0, -1, NULL, 
+						  &pjsua.file_port);
 	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create SIP endpoint", status);
+	    pjsua_perror(THIS_FILE, 
+			 "Error playing media file", 
+			 status);
 	    return status;
 	}
-    }
-
+
+	/* Add port to conference bridge: */
+	port_name = pjsua.config.wav_file;
+	status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, 
+				       pjsua.file_port, 
+				       &port_name,
+				       &pjsua.wav_slot);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, 
+			 "Unable to add file player to conference bridge", 
+			 status);
+	    return status;
+	}
+    }
+
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Copy configuration.
+ */
+static void copy_config(pj_pool_t *pool, pjsua_config *dst, 
+			const pjsua_config *src)
+{
+    unsigned i;
+
+    /* Plain memcpy */
+    pj_memcpy(dst, src, sizeof(pjsua_config));
+
+    /* Duplicate strings */
+    pj_strdup_with_null(pool, &dst->sip_host, &src->sip_host);
+    pj_strdup_with_null(pool, &dst->stun_srv1, &src->stun_srv1);
+    pj_strdup_with_null(pool, &dst->stun_srv2, &src->stun_srv2);
+    pj_strdup_with_null(pool, &dst->wav_file, &src->wav_file);
+    
+    for (i=0; 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 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) {
+	    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);
+    }
+
+    /* Init PJSIP : */
 
     /* Initialize transaction layer: */
@@ -428,87 +872,4 @@
     }
 
-    /* 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;
-
-    PJ_UNUSED_ARG(arg);
-
-    do {
-	pj_time_val timeout = { 0, 10 };
-	pj_status_t status;
-	
-	status = pjsip_endpt_handle_events (pjsua.endpt, &timeout);
-	if (status != PJ_SUCCESS && status != last_err) {
-	    last_err = status;
-	    pjsua_perror(THIS_FILE, "handle_events() returned error", status);
-	}
-    } while (!pjsua.quit_flag);
-
-    return 0;
-}
-
-/*
- * Initialize pjsua application.
- * This will initialize all libraries, create endpoint instance, and register
- * pjsip modules.
- */
-pj_status_t pjsua_init(void)
-{
-    pj_status_t status;
-
-    /* Init PJLIB logging: */
-
-    pj_log_set_level(pjsua.log_level);
-    pj_log_set_decor(pjsua.log_decor);
-
-
-    /* Init PJLIB: */
-
-    status = pj_init();
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "pj_init() error", status);
-	return status;
-    }
-
-    /* Init PJLIB-UTIL: */
-
-    status = pjlib_util_init();
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "pjlib_util_init() error", status);
-	return status;
-    }
-
-    /* Init memory pool: */
-
-    /* Init caching pool. */
-    pj_caching_pool_init(&pjsua.cp, &pj_pool_factory_default_policy, 0);
-
-    /* Create memory pool for application. */
-    pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL);
-
-
-    /* Init PJSIP : */
-
-    status = init_stack();
-    if (status != PJ_SUCCESS) {
-	pj_caching_pool_destroy(&pjsua.cp);
-	pjsua_perror(THIS_FILE, "Stack initialization has returned error", 
-		     status);
-	return status;
-    }
-
-
     /* Init core SIMPLE module : */
 
@@ -531,20 +892,11 @@
     pjsua_im_init();
 
-
-    /* Init media endpoint: */
-
-    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;
+
+on_error:
+    pj_caching_pool_destroy(&pjsua.cp);
+    return status;
 }
 
@@ -557,13 +909,13 @@
     pjsip_uri *uri;
     pjsip_sip_uri *sip_uri;
-    int acc_index;
+    unsigned acc_index;
 
     uri = rdata->msg_info.to->uri;
 
-    /* Just return account #0 if To URI is not SIP: */
+    /* Just return last account if To URI is not SIP: */
     if (!PJSIP_URI_SCHEME_IS_SIP(uri) && 
 	!PJSIP_URI_SCHEME_IS_SIPS(uri)) 
     {
-	return 0;
+	return pjsua.config.acc_cnt;
     }
 
@@ -572,5 +924,5 @@
 
     /* Find account which has matching username and domain. */
-    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
+    for (acc_index=0; acc_index < pjsua.config.acc_cnt; ++acc_index) {
 
 	pjsua_acc *acc = &pjsua.acc[acc_index];
@@ -585,5 +937,5 @@
 
     /* No matching, try match domain part only. */
-    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
+    for (acc_index=0; acc_index < pjsua.config.acc_cnt; ++acc_index) {
 
 	pjsua_acc *acc = &pjsua.acc[acc_index];
@@ -595,6 +947,6 @@
     }
 
-    /* Still no match, just return account #0 */
-    return 0;
+    /* Still no match, just return last account */
+    return pjsua.config.acc_cnt;
 }
 
@@ -611,151 +963,4 @@
 }
 
-
-/*
- * Init media.
- */
-static pj_status_t init_media(void)
-{
-    int i;
-    unsigned options;
-    unsigned clock_rate;
-    unsigned samples_per_frame;
-    pj_str_t codec_id;
-    pj_status_t status;
-
-    /* Register all codecs */
-#if PJMEDIA_HAS_SPEEX_CODEC
-    /* Register speex. */
-    status = pjmedia_codec_speex_init(pjsua.med_endpt, 
-				      PJMEDIA_SPEEX_NO_UWB,
-				      pjsua.quality, pjsua.complexity );
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error initializing Speex codec",
-		     status);
-	return status;
-    }
-
-    /* Set "speex/16000/1" to have highest priority */
-    codec_id = pj_str("speex/16000/1");
-    pjmedia_codec_mgr_set_codec_priority( 
-	pjmedia_endpt_get_codec_mgr(pjsua.med_endpt),
-	&codec_id, 
-	PJMEDIA_CODEC_PRIO_HIGHEST);
-
-#endif /* PJMEDIA_HAS_SPEEX_CODEC */
-
-#if PJMEDIA_HAS_GSM_CODEC
-    /* Register GSM */
-    status = pjmedia_codec_gsm_init(pjsua.med_endpt);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error initializing GSM codec",
-		     status);
-	return status;
-    }
-#endif /* PJMEDIA_HAS_GSM_CODEC */
-
-#if PJMEDIA_HAS_G711_CODEC
-    /* Register PCMA and PCMU */
-    status = pjmedia_codec_g711_init(pjsua.med_endpt);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error initializing G711 codec",
-		     status);
-	return status;
-    }
-#endif	/* PJMEDIA_HAS_G711_CODEC */
-
-#if PJMEDIA_HAS_L16_CODEC
-    /* Register L16 family codecs, but disable all */
-    status = pjmedia_codec_l16_init(pjsua.med_endpt, 0);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error initializing L16 codecs",
-		     status);
-	return status;
-    }
-
-    /* Disable ALL L16 codecs */
-    codec_id = pj_str("L16");
-    pjmedia_codec_mgr_set_codec_priority( 
-	pjmedia_endpt_get_codec_mgr(pjsua.med_endpt),
-	&codec_id, 
-	PJMEDIA_CODEC_PRIO_DISABLED);
-
-#endif	/* PJMEDIA_HAS_L16_CODEC */
-
-
-    /* Enable those codecs that user put with "--add-codec", and move
-     * the priority to top
-     */
-    for (i=0; i<pjsua.codec_cnt; ++i) {
-	pjmedia_codec_mgr_set_codec_priority( 
-	    pjmedia_endpt_get_codec_mgr(pjsua.med_endpt),
-	    &pjsua.codec_arg[i], 
-	    PJMEDIA_CODEC_PRIO_HIGHEST);
-    }
-
-
-    /* Init options for conference bridge. */
-    options = 0;
-    if (pjsua.no_mic)
-	options |= PJMEDIA_CONF_NO_MIC;
-
-    /* Init conference bridge. */
-    clock_rate = pjsua.clock_rate ? pjsua.clock_rate : 16000;
-    samples_per_frame = clock_rate * 10 / 1000;
-    status = pjmedia_conf_create(pjsua.pool, 
-				 pjsua.max_calls+PJSUA_CONF_MORE_PORTS, 
-				 clock_rate, 
-				 1, /* mono */
-				 samples_per_frame, 
-				 16, 
-				 options,
-				 &pjsua.mconf);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, 
-		     "Media stack initialization has returned error", 
-		     status);
-	return status;
-    }
-
-    /* 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) {
-	pj_str_t port_name;
-
-	/* Create the file player port. */
-	status = pjmedia_wav_player_port_create(  pjsua.pool, pjsua.wav_file,
-						  0, 0, -1, NULL, 
-						  &pjsua.file_port);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, 
-			 "Error playing media file", 
-			 status);
-	    return status;
-	}
-
-	/* Add port to conference bridge: */
-	status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, 
-				       pjsua.file_port, 
-				       pj_cstr(&port_name, pjsua.wav_file),
-				       &pjsua.wav_slot);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, 
-			 "Unable to add file player to conference bridge", 
-			 status);
-	    return status;
-	}
-    }
-
-
-    return PJ_SUCCESS;
-}
 
 
@@ -764,40 +969,13 @@
  * This will start the registration process, if registration is configured.
  */
-pj_status_t pjsua_start(void)
+PJ_DEF(pj_status_t) pjsua_start(void)
 {
     int i;  /* Must be signed */
-    pjsip_transport *udp_transport;
     pj_status_t status = PJ_SUCCESS;
 
-    /*
-     * Init media subsystem (codecs, conference bridge, et all).
-     */
-    status = init_media();
-    if (status != PJ_SUCCESS)
-	return status;
-
-    /* Init sockets (STUN etc): */
-    for (i=0; i<(int)pjsua.max_calls; ++i) {
-	status = init_sockets(i==0, &pjsua.calls[i].skinfo);
-	if (status == PJ_SUCCESS)
-	    status = pjmedia_transport_udp_attach(pjsua.med_endpt, NULL,
-						  &pjsua.calls[i].skinfo,
-						  &pjsua.calls[i].med_tp);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "init_sockets() has returned error", 
-			 status);
-	    --i;
-	    if (i >= 0)
-		pj_sock_close(pjsua.sip_sock);
-	    while (i >= 0) {
-		pjmedia_transport_udp_close(pjsua.calls[i].med_tp);
-	    }
-	    return status;
-	}
-    }
 
     /* Add UDP transport: */
-
-    {
+    if (pjsua.sip_sock > 0) {
+
 	/* Init the published name for the transport.
          * Depending whether STUN is used, this may be the STUN mapped
@@ -806,7 +984,6 @@
 	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: */
@@ -814,5 +991,5 @@
 	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", 
@@ -822,7 +999,28 @@
     }
 
-    /* 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;
@@ -830,6 +1028,6 @@
 	/* 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", 
@@ -853,12 +1051,17 @@
 	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;
-
-	if (pjsua.acc[i].contact_uri.slen == 0 && 
-	    pjsua.acc[i].local_uri.slen) 
-	{
+	acc->user_part = sip_uri->user;
+	acc->host_part = sip_uri->host;
+
+	/* Build Contact header */
+
+	if (acc_cfg->contact.slen == 0)  {
 	    char contact[128];
+	    const char *addr;
+	    int port;
 	    int len;
+
+	    addr = pjsua.config.sip_host.ptr;
+	    port = pjsua.config.sip_port;
 
 	    /* The local Contact is the username@ip-addr, where
@@ -874,10 +1077,8 @@
 		/* 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 {
 
@@ -885,8 +1086,6 @@
 
 		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);
 	    }
 
@@ -898,36 +1097,37 @@
 	    /* Duplicate Contact uri. */
 
-	    pj_strdup2(pjsua.pool, &pjsua.acc[i].contact_uri, contact);
-
-	}
-    }
-
-    /* If outbound_proxy is specified, put it in the route_set: */
-
-    if (pjsua.outbound_proxy.slen) {
-
-	pjsip_route_hdr *route;
-	const pj_str_t hname = { "Route", 5 };
-	int parsed_len;
-
-	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);
-	}
-    }
+	    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);
+	}
+    }
+
+
 
 
     /* 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]);
@@ -945,5 +1145,5 @@
 
     /* 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)
@@ -956,4 +1156,10 @@
     }
 
+
+    /* 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 */
@@ -987,5 +1193,5 @@
  * Destroy pjsua.
  */
-pj_status_t pjsua_destroy(void)
+PJ_DEF(pj_status_t) pjsua_destroy(void)
 {
     int i;  /* Must be signed */
@@ -1001,5 +1207,5 @@
 
     /* 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);
@@ -1008,5 +1214,5 @@
 
     /* 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]) {
@@ -1030,8 +1236,4 @@
 	pjmedia_port_destroy(pjsua.file_port);
 
-    /* Destroy null port. */
-    if (pjsua.null_port)
-	pjmedia_port_destroy(pjsua.null_port);
-
 
     /* Shutdown all codecs: */
@@ -1054,5 +1256,5 @@
 
     /* 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);
     }
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_im.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_im.c	(revision 475)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_im.c	(revision 476)
@@ -153,5 +153,6 @@
 	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 {
@@ -170,5 +171,6 @@
 	}
 
-	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);
     }
 
@@ -270,6 +272,6 @@
  * 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;
@@ -282,7 +284,8 @@
 
     /* 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);
@@ -296,7 +299,7 @@
     /* 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.
@@ -331,6 +334,6 @@
  * 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);
@@ -340,5 +343,6 @@
     /* 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) {
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_imp.h
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_imp.h	(revision 476)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_imp.h	(revision 476)
@@ -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__ */
+
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_pres.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_pres.c	(revision 475)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_pres.c	(revision 476)
@@ -18,4 +18,5 @@
  */
 #include <pjsua-lib/pjsua.h>
+#include "pjsua_imp.h"
 
 /*
@@ -81,4 +82,5 @@
 {
     int acc_index;
+    pjsua_acc_config *acc_config;
     pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
     pjsua_srv_pres *uapres;
@@ -97,9 +99,10 @@
     /* 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, 
@@ -307,4 +310,5 @@
 {
     int acc_index;
+    pjsua_acc_config *acc_config;
     pjsip_dialog *dlg;
     pjsip_tx_data *tdata;
@@ -312,8 +316,9 @@
 
     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);
@@ -324,6 +329,9 @@
     }
 
-    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, 
@@ -427,5 +435,5 @@
  * Refresh presence
  */
-void pjsua_pres_refresh(int acc_index)
+PJ_DEF(void) pjsua_pres_refresh(int acc_index)
 {
     refresh_client_subscription();
@@ -442,5 +450,5 @@
     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;
     }
@@ -450,5 +458,5 @@
     }
 
-    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);
     }
@@ -472,5 +480,5 @@
 	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)) {
@@ -507,9 +515,9 @@
     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)) {
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_reg.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_reg.c	(revision 475)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_reg.c	(revision 476)
@@ -60,10 +60,10 @@
 	    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,
@@ -78,5 +78,6 @@
     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);
 }
 
@@ -85,5 +86,5 @@
  * 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;
@@ -130,7 +131,10 @@
 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;
@@ -152,9 +156,9 @@
 
     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, 
@@ -164,7 +168,9 @@
     }
 
-    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, 
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_settings.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_settings.c	(revision 475)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_settings.c	(revision 476)
@@ -60,20 +60,15 @@
     puts  ("");
     puts  ("SIP Account options:");
+    puts  ("  --registrar=url     Set the URL of registrar server");
     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  ("  --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  ("  --reg-timeout=SEC   Optional registration interval (default 55)");
     puts  ("");
     puts  ("SIP Account Control:");
     puts  ("  --next-account      Add more account");
-    puts  ("");
-    puts  ("Authentication options:");
-    puts  ("  --realm=string      Set realm");
-    puts  ("  --username=string   Set authentication username");
-    puts  ("  --password=string   Set authentication password");
-    puts  ("  --next-cred         Add more credential");
     puts  ("");
     puts  ("Transport Options:");
@@ -87,5 +82,4 @@
     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)");
@@ -114,5 +108,5 @@
  * 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;
@@ -215,10 +209,11 @@
 
 /* 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, 
@@ -230,5 +225,5 @@
 	   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,
     };
@@ -242,5 +237,4 @@
 	{ "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},
@@ -270,5 +264,4 @@
 	{ "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},
@@ -277,10 +270,13 @@
     };
     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:
@@ -298,7 +294,6 @@
     }
 
-
-    cur_acc = &pjsua.acc[0];
-    cur_cred = &pjsua.cred_info[0];
+    cfg->acc_cnt = 0;
+    cur_acc = &cfg->acc_config[0];
 
 
@@ -307,5 +302,5 @@
      */
     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;
@@ -315,5 +310,5 @@
 
 	case OPT_LOG_FILE:
-	    pjsua.log_filename = pj_optarg;
+	    cfg->log_filename = pj_str(pj_optarg);
 	    break;
 
@@ -326,10 +321,11 @@
 		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 "
@@ -348,9 +344,5 @@
 
 	case OPT_NULL_AUDIO:
-	    pjsua.null_audio = 1;
-	    break;
-
-	case OPT_NO_MIC:
-	    pjsua.no_mic = 1;
+	    cfg->null_audio = 1;
 	    break;
 
@@ -362,5 +354,5 @@
 		return PJ_EINVAL;
 	    }
-	    pjsua.clock_rate = (int)lval; 
+	    cfg->clock_rate = lval; 
 	    break;
 
@@ -373,5 +365,5 @@
 		return PJ_EINVAL;
 	    }
-	    pjsua.sip_port = (pj_uint16_t)lval;
+	    cfg->udp_port = (pj_uint16_t)lval;
 	    break;
 
@@ -393,5 +385,5 @@
 		return PJ_EINVAL;
 	    }
-	    pjsua.outbound_proxy = pj_str(pj_optarg);
+	    cfg->outbound_proxy = pj_str(pj_optarg);
 	    break;
 
@@ -423,6 +415,5 @@
 		return PJ_EINVAL;
 	    }
-	    cur_acc->local_uri = pj_str(pj_optarg);
-	    pjsua.has_acc = 1;
+	    cur_acc->id = pj_str(pj_optarg);
 	    break;
 
@@ -434,31 +425,23 @@
 		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;
 
@@ -467,7 +450,7 @@
 	    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 "
@@ -476,6 +459,6 @@
 		}
 	    } else {
-		pjsua.stun_port1 = 3478;
-		pjsua.stun_srv1 = pj_str(pj_optarg);
+		cfg->stun_port1 = 3478;
+		cfg->stun_srv1 = pj_str(pj_optarg);
 	    }
 	    break;
@@ -485,7 +468,7 @@
 	    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 "
@@ -494,6 +477,6 @@
 		}
 	    } else {
-		pjsua.stun_port2 = 3478;
-		pjsua.stun_srv2 = pj_str(pj_optarg);
+		cfg->stun_port2 = 3478;
+		cfg->stun_srv2 = pj_str(pj_optarg);
 	    }
 	    break;
@@ -506,31 +489,31 @@
 		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 "
@@ -541,10 +524,10 @@
 
 	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"));
@@ -554,6 +537,6 @@
 
 	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"));
@@ -563,6 +546,6 @@
 
 	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"));
@@ -572,6 +555,6 @@
 
 	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 "
@@ -582,6 +565,6 @@
 
 	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;
@@ -590,6 +573,6 @@
 
 	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)"));
@@ -599,6 +582,6 @@
 
 	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 "
@@ -611,5 +594,4 @@
 
     if (pj_optind != argc) {
-	int i;
 
 	if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
@@ -617,14 +599,14 @@
 	    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;
 	}
     }
@@ -633,4 +615,21 @@
 	PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
 	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;
     }
 
@@ -832,5 +831,5 @@
  * Dump application states.
  */
-void pjsua_dump(pj_bool_t detail)
+PJ_DEF(void) pjsua_dump(pj_bool_t detail)
 {
     char buf[128];
@@ -859,7 +858,7 @@
 
     } else {
-	int i;
-
-	for (i=0; i<pjsua.max_calls; ++i) {
+	unsigned i;
+
+	for (i=0; i<pjsua.config.max_calls; ++i) {
 
 	    pjsua_call *call = &pjsua.calls[i];
@@ -918,5 +917,6 @@
  * 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;
@@ -924,5 +924,5 @@
 
     argv[3] = (char*)filename;
-    return pjsua_parse_args(argc, argv);
+    return pjsua_parse_args(argc, argv, cfg);
 }
 
@@ -934,5 +934,5 @@
 {
     char line[128];
-    pjsua_acc *acc = &pjsua.acc[acc_index];
+    pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index];
 
     
@@ -942,20 +942,20 @@
 
     /* 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);
     }
@@ -963,10 +963,32 @@
 
     /* 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);
+    }
+
 }
 
@@ -976,8 +998,9 @@
  * Dump settings.
  */
-int pjsua_dump_settings(char *buf, pj_size_t max)
-{
-    int acc_index;
-    int i;
+PJ_DEF(int) pjsua_dump_settings(const pjsua_config *config,
+				char *buf, pj_size_t max)
+{
+    unsigned acc_index;
+    unsigned i;
     pj_str_t cfg;
     char line[128];
@@ -992,14 +1015,14 @@
     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);
     }
@@ -1007,97 +1030,68 @@
 
     /* Save account settings. */
-    if (pjsua.has_acc) {
-	for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
-	    
-	    save_account_settings(acc_index, &cfg);
-
-	    if (acc_index < pjsua.acc_cnt-1)
-		pj_strcat2(&cfg, "--next-account\n");
-	}
-    }
-
-    /* Credentials. */
-    for (i=0; i<pjsua.cred_count; ++i) {
-
-	pj_ansi_sprintf(line, "#\n# Credential %d:\n#\n", i);
+    for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
+	
+	save_account_settings(acc_index, &cfg);
+
+	if (acc_index < config->acc_cnt-1)
+	    pj_strcat2(&cfg, "--next-account\n");
+    }
+
+
+    pj_strcat2(&cfg, "#\n# Network settings:\n#\n");
+
+    /* Outbound proxy */
+    if (config->outbound_proxy.slen) {
+	pj_ansi_sprintf(line, "--outbound %.*s\n",
+			      (int)config->outbound_proxy.slen,
+			      config->outbound_proxy.ptr);
 	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);
+    }
+
+
+    /* Transport. */
+    pj_ansi_sprintf(line, "--local-port %d\n", config->udp_port);
+    pj_strcat2(&cfg, line);
+
+
+    /* STUN */
+    if (config->stun_port1) {
+	pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
+			(int)config->stun_srv1.slen, 
+			config->stun_srv1.ptr, 
+			config->stun_port1);
 	pj_strcat2(&cfg, line);
-
-	pj_ansi_sprintf(line, "--password %.*s\n",
-			      (int)pjsua.cred_info[i].data.slen,
-			      pjsua.cred_info[i].data.ptr);
+    }
+
+    if (config->stun_port2) {
+	pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
+			(int)config->stun_srv2.slen, 
+			config->stun_srv2.ptr, 
+			config->stun_port2);
 	pj_strcat2(&cfg, line);
-
-	if (i < pjsua.cred_count-1)
-	    pj_strcat2(&cfg, "--next-cred\n");
-    }
-
-
-    pj_strcat2(&cfg, "#\n# Network settings:\n#\n");
-
-    /* Outbound proxy */
-    if (pjsua.outbound_proxy.slen) {
-	pj_ansi_sprintf(line, "--outbound %.*s\n",
-			      (int)pjsua.outbound_proxy.slen,
-			      pjsua.outbound_proxy.ptr);
+    }
+
+
+    pj_strcat2(&cfg, "#\n# Media settings:\n#\n");
+
+
+    /* Media */
+    if (config->null_audio)
+	pj_strcat2(&cfg, "--null-audio\n");
+    if (config->auto_play)
+	pj_strcat2(&cfg, "--auto-play\n");
+    if (config->auto_loop)
+	pj_strcat2(&cfg, "--auto-loop\n");
+    if (config->auto_conf)
+	pj_strcat2(&cfg, "--auto-conf\n");
+    if (config->wav_file.slen) {
+	pj_ansi_sprintf(line, "--play-file %s\n",
+			config->wav_file.ptr);
 	pj_strcat2(&cfg, line);
     }
-
-
-    /* Transport. */
-    pj_ansi_sprintf(line, "--local-port %d\n", pjsua.sip_port);
-    pj_strcat2(&cfg, line);
-
-
-    /* STUN */
-    if (pjsua.stun_port1) {
-	pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
-			(int)pjsua.stun_srv1.slen, 
-			pjsua.stun_srv1.ptr, 
-			pjsua.stun_port1);
-	pj_strcat2(&cfg, line);
-    }
-
-    if (pjsua.stun_port2) {
-	pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
-			(int)pjsua.stun_srv2.slen, 
-			pjsua.stun_srv2.ptr, 
-			pjsua.stun_port2);
-	pj_strcat2(&cfg, line);
-    }
-
-
-    pj_strcat2(&cfg, "#\n# Media settings:\n#\n");
-
-
-    /* Media */
-    if (pjsua.null_audio)
-	pj_strcat2(&cfg, "--null-audio\n");
-    if (pjsua.auto_play)
-	pj_strcat2(&cfg, "--auto-play\n");
-    if (pjsua.auto_loop)
-	pj_strcat2(&cfg, "--auto-loop\n");
-    if (pjsua.auto_conf)
-	pj_strcat2(&cfg, "--auto-conf\n");
-    if (pjsua.wav_file) {
-	pj_ansi_sprintf(line, "--play-file %s\n",
-			pjsua.wav_file);
-	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);
     }
@@ -1106,14 +1100,14 @@
     /* 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);
     }
@@ -1121,11 +1115,11 @@
     /* 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);
     }
@@ -1134,7 +1128,7 @@
 
     /* 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);
     }
@@ -1142,18 +1136,18 @@
     /* 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);
     }
@@ -1162,8 +1156,8 @@
 
     /* 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);
     }
@@ -1177,5 +1171,6 @@
  * 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;
@@ -1196,5 +1191,5 @@
 
 
-    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);
