Major redesign in pjsua: call is indexed by number, multiple accounts, configurable max-calls, more auto-xxx features, fixed bugs in save_settings(), etc.

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@236 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip/build/pjsua.dsp b/pjsip/build/pjsua.dsp
index 604c993..4d808e7 100644
--- a/pjsip/build/pjsua.dsp
+++ b/pjsip/build/pjsua.dsp
@@ -98,11 +98,11 @@
 # End Source File

 # Begin Source File

 

-SOURCE=..\src\pjsua\pjsua_core.c

+SOURCE=..\src\pjsua\pjsua_call.c

 # End Source File

 # Begin Source File

 

-SOURCE=..\src\pjsua\pjsua_inv.c

+SOURCE=..\src\pjsua\pjsua_core.c

 # End Source File

 # Begin Source File

 

diff --git a/pjsip/src/pjsua/main.c b/pjsip/src/pjsua/main.c
index 7070c76..25ce6dc 100644
--- a/pjsip/src/pjsua/main.c
+++ b/pjsip/src/pjsua/main.c
@@ -23,29 +23,86 @@
 #define THIS_FILE	"main.c"
 
 /* Current dialog */
-static struct pjsua_inv_data *inv_session;
+static int current_acc;
+static int current_call = -1;
+
+
+/*
+ * Find next call.
+ */
+static pj_bool_t find_next_call(void)
+{
+    int i;
+
+    for (i=current_call+1; i<(int)pjsua.max_calls; ++i) {
+	if (pjsua.calls[i].inv != NULL) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    for (i=0; i<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_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
+void pjsua_ui_inv_on_state_changed(int call_index, pjsip_event *e)
 {
+    pjsua_call *call = &pjsua.calls[call_index];
+
     PJ_UNUSED_ARG(e);
 
-    PJ_LOG(3,(THIS_FILE, "INVITE session state changed to %s", 
-	      pjsua_inv_state_names[inv->state]));
+    PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", 
+	      call_index,
+	      pjsua_inv_state_names[call->inv->state]));
 
-    if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
-	if (inv == inv_session->inv) {
-	    inv_session = inv_session->next;
-	    if (inv_session == &pjsua.inv_list)
-		inv_session = pjsua.inv_list.next;
+    if (call->inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+	call->inv = NULL;
+	if ((int)call->index == current_call) {
+	    find_next_call();
 	}
 
     } else {
 
-	if (inv_session == &pjsua.inv_list || inv_session == NULL)
-	    inv_session = inv->mod_data[pjsua.mod.id];
+	if (call && current_call==-1)
+	    current_call = call->index;
 
     }
 }
@@ -66,10 +123,10 @@
  */
 static void print_buddy_list(void)
 {
-    unsigned i;
+    int i;
 
     puts("Buddy list:");
-    //puts("-------------------------------------------------------------------------------");
+
     if (pjsua.buddy_cnt == 0)
 	puts(" -none-");
     else {
@@ -93,37 +150,56 @@
     puts("");
 }
 
+
 /*
- * Show a bit of help.
+ * Print account status.
  */
-static void keystroke_help(void)
+static void print_acc_status(int acc_index)
 {
     char reg_status[128];
 
-    if (pjsua.regc == NULL) {
+    if (pjsua.acc[acc_index].regc == NULL) {
 	pj_ansi_strcpy(reg_status, " -not registered to server-");
-    } else if (pjsua.regc_last_err != PJ_SUCCESS) {
-	pj_strerror(pjsua.regc_last_err, reg_status, sizeof(reg_status));
-    } else if (pjsua.regc_last_code>=200 && pjsua.regc_last_code<=699) {
+
+    } else if (pjsua.acc[acc_index].reg_last_err != PJ_SUCCESS) {
+	pj_strerror(pjsua.acc[acc_index].reg_last_err, reg_status, sizeof(reg_status));
+
+    } else if (pjsua.acc[acc_index].reg_last_code>=200 && 
+	       pjsua.acc[acc_index].reg_last_code<=699) {
 
 	pjsip_regc_info info;
 
-	pjsip_regc_get_info(pjsua.regc, &info);
+	pjsip_regc_get_info(pjsua.acc[acc_index].regc, &info);
 
 	pj_snprintf(reg_status, sizeof(reg_status),
 		    "%s (%.*s;expires=%d)",
-		    pjsip_get_status_text(pjsua.regc_last_code)->ptr,
+		    pjsip_get_status_text(pjsua.acc[acc_index].reg_last_code)->ptr,
 		    (int)info.client_uri.slen,
 		    info.client_uri.ptr,
 		    info.next_reg);
 
     } else {
-	pj_sprintf(reg_status, "in progress (%d)", pjsua.regc_last_code);
+	pj_sprintf(reg_status, "in progress (%d)", 
+		   pjsua.acc[acc_index].reg_last_code);
     }
 
-    printf(">>>>\nRegistration status: %s\n", reg_status);
-    printf("Online status: %s\n", 
-	   (pjsua.online_status ? "Online" : "Invisible"));
+    printf("[%2d] Registration status: %s\n", acc_index, reg_status);
+    printf("     Online status: %s\n", 
+	   (pjsua.acc[acc_index].online_status ? "Online" : "Invisible"));
+}
+
+/*
+ * Show a bit of help.
+ */
+static void keystroke_help(void)
+{
+    int i;
+
+    printf(">>>>\n");
+
+    for (i=0; i<pjsua.acc_cnt; ++i)
+	print_acc_status(i);
+
     print_buddy_list();
     
     //puts("Commands:");
@@ -134,7 +210,7 @@
     puts("|  a  Answer call              |  s  Subscribe presence   | rr  (Re-)register |");
     puts("|  h  Hangup call              |  u  Unsubscribe presence | ru  Unregister    |");
     puts("|  ]  Select next dialog       |  t  ToGgle Online status |  d  Dump status   |");
-    puts("|  [  Select previous dialog   |                          |                   |");
+    puts("|  [  Select previous dialog   |                          | dc  Dump config   |");
     puts("|                              +--------------------------+-------------------+");
     puts("|  H  Hold call                |     Conference Command   |                   |");
     puts("|  v  re-inVite (release hold) | cl  List ports           |                   |");
@@ -262,19 +338,19 @@
 static void conf_list(void)
 {
     unsigned i, count;
-    pjmedia_conf_port_info info[16];
+    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[80];
-	unsigned j;
+	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_ports; ++j) {
+	for (j=0; j<pjsua.max_calls+PJSUA_CONF_MORE_PORTS; ++j) {
 	    char s[10];
 	    if (port_info->listener[j]) {
 		pj_sprintf(s, "#%d ", j);
@@ -309,26 +385,27 @@
 
 	case 'm':
 	    /* Make call! : */
-	    if (pj_list_size(&pjsua.inv_list))
-		printf("(You have %d calls)\n", pj_list_size(&pjsua.inv_list));
+	    printf("(You currently have %d calls)\n", pjsua.call_cnt);
 	    
 	    ui_input_url("Make call", buf, sizeof(buf), &result);
 	    if (result.nb_result != NO_NB) {
 		if (result.nb_result == -1)
 		    puts("You can't do that with make call!");
 		else
-		    pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, NULL);
+		    pjsua_make_call( current_acc, 
+				     pjsua.buddies[result.nb_result].uri.ptr, 
+				     NULL);
 	    } else if (result.uri_result)
-		pjsua_invite(result.uri_result, NULL);
+		pjsua_make_call( current_acc, result.uri_result, NULL);
 	    
 	    break;
 
 
 	case 'a':
 
-	    if (inv_session == &pjsua.inv_list || 
-		inv_session->inv->role != PJSIP_ROLE_UAS ||
-		inv_session->inv->state >= PJSIP_INV_STATE_CONNECTING) 
+	    if (current_call == -1 || 
+		pjsua.calls[current_call].inv->role != PJSIP_ROLE_UAS ||
+		pjsua.calls[current_call].inv->state >= PJSIP_INV_STATE_CONNECTING)
 	    {
 		puts("No pending incoming call");
 		fflush(stdout);
@@ -349,16 +426,18 @@
 		 * Call may have been disconnected while we're waiting for 
 		 * keyboard input.
 		 */
-		if (inv_session == &pjsua.inv_list) {
+		if (current_call == -1) {
 		    puts("Call has been disconnected");
 		    fflush(stdout);
 		    continue;
 		}
 
-		status = pjsip_inv_answer(inv_session->inv, atoi(buf), 
+		status = pjsip_inv_answer(pjsua.calls[current_call].inv, 
+					  atoi(buf), 
 					  NULL, NULL, &tdata);
 		if (status == PJ_SUCCESS)
-		    status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
+		    status = pjsip_inv_send_msg(pjsua.calls[current_call].inv,
+						tdata, NULL);
 
 		if (status != PJ_SUCCESS)
 		    pjsua_perror(THIS_FILE, "Unable to create/send response", 
@@ -370,13 +449,13 @@
 
 	case 'h':
 
-	    if (inv_session == &pjsua.inv_list) {
+	    if (current_call == -1) {
 		puts("No current call");
 		fflush(stdout);
 		continue;
 
 	    } else {
-		pjsua_inv_hangup(inv_session, PJSIP_SC_DECLINE);
+		pjsua_call_hangup(current_call, PJSIP_SC_DECLINE);
 	    }
 	    break;
 
@@ -386,22 +465,19 @@
 	     * Cycle next/prev dialog.
 	     */
 	    if (menuin[0] == ']') {
-		inv_session = inv_session->next;
-		if (inv_session == &pjsua.inv_list)
-		    inv_session = pjsua.inv_list.next;
+		find_next_call();
 
 	    } else {
-		inv_session = inv_session->prev;
-		if (inv_session == &pjsua.inv_list)
-		    inv_session = pjsua.inv_list.prev;
+		find_prev_call();
 	    }
 
-	    if (inv_session != &pjsua.inv_list) {
+	    if (current_call != -1) {
 		char url[PJSIP_MAX_URL_SIZE];
 		int len;
+		const pjsip_uri *u;
 
-		len = pjsip_uri_print(0, inv_session->inv->dlg->remote.info->uri,
-				      url, sizeof(url)-1);
+		u = pjsua.calls[current_call].inv->dlg->remote.info->uri;
+		len = pjsip_uri_print(0, u, url, sizeof(url)-1);
 		if (len < 1) {
 		    pj_ansi_strcpy(url, "<uri is too long>");
 		} else {
@@ -419,9 +495,9 @@
 	    /*
 	     * Hold call.
 	     */
-	    if (inv_session != &pjsua.inv_list) {
+	    if (current_call != -1) {
 		
-		pjsua_inv_set_hold(inv_session);
+		pjsua_call_set_hold(current_call);
 
 	    } else {
 		PJ_LOG(3,(THIS_FILE, "No current call"));
@@ -432,9 +508,9 @@
 	    /*
 	     * Send re-INVITE (to release hold, etc).
 	     */
-	    if (inv_session != &pjsua.inv_list) {
+	    if (current_call != -1) {
 		
-		pjsua_inv_reinvite(inv_session);
+		pjsua_call_reinvite(current_call);
 
 	    } else {
 		PJ_LOG(3,(THIS_FILE, "No current call"));
@@ -445,18 +521,18 @@
 	    /*
 	     * Transfer call.
 	     */
-	    if (inv_session == &pjsua.inv_list) {
+	    if (current_call == -1) {
 		
 		PJ_LOG(3,(THIS_FILE, "No current call"));
 
 	    } else {
-		struct pjsua_inv_data *cur = inv_session;
+		int call = current_call;
 
 		ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
 
 		/* Check if call is still there. */
 
-		if (cur != inv_session) {
+		if (call != current_call) {
 		    puts("Call has been disconnected");
 		    continue;
 		}
@@ -465,11 +541,11 @@
 		    if (result.nb_result == -1) 
 			puts("You can't do that with transfer call!");
 		    else
-			pjsua_inv_xfer_call( inv_session,
-					     pjsua.buddies[result.nb_result].uri.ptr);
+			pjsua_call_xfer( current_call,
+					 pjsua.buddies[result.nb_result].uri.ptr);
 
 		} else if (result.uri_result) {
-		    pjsua_inv_xfer_call( inv_session, result.uri_result);
+		    pjsua_call_xfer( current_call, result.uri_result);
 		}
 	    }
 	    break;
@@ -478,17 +554,17 @@
 	    /*
 	     * Send DTMF strings.
 	     */
-	    if (inv_session == &pjsua.inv_list) {
+	    if (current_call == -1) {
 		
 		PJ_LOG(3,(THIS_FILE, "No current call"));
 
-	    } else if (inv_session->session == NULL) {
+	    } else if (pjsua.calls[current_call].session == NULL) {
 
 		PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
 
 	    } else {
 		pj_str_t digits;
-		struct pjsua_inv_data *cur = inv_session;
+		int call = current_call;
 		pj_status_t status;
 
 		if (!simple_input("DTMF strings to send (0-9*#A-B)", buf, 
@@ -497,13 +573,13 @@
 			break;
 		}
 
-		if (cur != inv_session) {
+		if (call != current_call) {
 		    puts("Call has been disconnected");
 		    continue;
 		}
 
 		digits = pj_str(buf);
-		status = pjmedia_session_dial_dtmf(inv_session->session, 0, 
+		status = pjmedia_session_dial_dtmf(pjsua.calls[current_call].session, 0, 
 						   &digits);
 		if (status != PJ_SUCCESS) {
 		    pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
@@ -521,14 +597,14 @@
 	    ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
 	    if (result.nb_result != NO_NB) {
 		if (result.nb_result == -1) {
-		    unsigned i;
+		    int i;
 		    for (i=0; i<pjsua.buddy_cnt; ++i)
 			pjsua.buddies[i].monitor = (menuin[0]=='s');
 		} else {
 		    pjsua.buddies[result.nb_result].monitor = (menuin[0]=='s');
 		}
 
-		pjsua_pres_refresh();
+		pjsua_pres_refresh(current_acc);
 
 	    } else if (result.uri_result) {
 		puts("Sorry, can only subscribe to buddy's presence, "
@@ -543,20 +619,21 @@
 		/*
 		 * Re-Register.
 		 */
-		pjsua_regc_update(PJ_TRUE);
+		pjsua_regc_update(current_acc, PJ_TRUE);
 		break;
 	    case 'u':
 		/*
 		 * Unregister
 		 */
-		pjsua_regc_update(PJ_FALSE);
+		pjsua_regc_update(current_acc, PJ_FALSE);
 		break;
 	    }
 	    break;
 	    
 	case 't':
-	    pjsua.online_status = !pjsua.online_status;
-	    pjsua_pres_refresh();
+	    pjsua.acc[current_acc].online_status = 
+		!pjsua.acc[current_acc].online_status;
+	    pjsua_pres_refresh(current_acc);
 	    break;
 
 	case 'c':
@@ -606,7 +683,20 @@
 	    break;
 
 	case 'd':
-	    pjsua_dump();
+	    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();
+	    }
 	    break;
 
 	case 'q':
@@ -765,37 +855,31 @@
 {
 
     /* Init default settings. */
-
     pjsua_default();
 
 
     /* Initialize pjsua (to create pool etc).
      */
-
     if (pjsua_init() != PJ_SUCCESS)
 	return 1;
 
 
     /* Parse command line arguments: */
-
     if (pjsua_parse_args(argc, argv) != PJ_SUCCESS)
 	return 1;
 
 
     /* Init logging: */
-
     app_logging_init();
 
 
     /* Register message logger to print incoming and outgoing
      * messages.
      */
-
     pjsip_endpt_register_module(pjsua.endpt, &console_msg_logger);
 
 
     /* Start pjsua! */
-
     if (pjsua_start() != PJ_SUCCESS) {
 
 	pjsua_destroy();
@@ -804,27 +888,18 @@
 
 
     /* Sleep for a while, let any messages get printed to console: */
-
     pj_thread_sleep(500);
 
 
-    /* No current call initially: */
-
-    inv_session = &pjsua.inv_list;
-
-
     /* Start UI console main loop: */
-
     ui_console_main();
 
 
     /* Destroy pjsua: */
-
     pjsua_destroy();
 
 
     /* Close logging: */
-
     app_logging_shutdown();
 
 
diff --git a/pjsip/src/pjsua/pjsua.h b/pjsip/src/pjsua/pjsua.h
index 2e430ce..0987416 100644
--- a/pjsip/src/pjsua/pjsua.h
+++ b/pjsip/src/pjsua/pjsua.h
@@ -47,31 +47,63 @@
 /**
  * Max buddies in buddy list.
  */
-#define PJSUA_MAX_BUDDIES   32
+#ifndef PJSUA_MAX_BUDDIES
+#   define PJSUA_MAX_BUDDIES	    32
+#endif
+
 
 /**
  * Max simultaneous calls.
  */
-#define PJSUA_MAX_CALLS	    8
+#ifndef PJSUA_MAX_CALLS
+#   define PJSUA_MAX_CALLS	    256
+#endif
+
+
+/**
+ * Aditional ports to be allocated in the conference ports for non-call
+ * streams.
+ */
+#define PJSUA_CONF_MORE_PORTS	    2
+
+
+/**
+ * Maximum accounts.
+ */
+#ifndef PJSUA_MAX_ACC
+#   define PJSUA_MAX_ACC	    8
+#endif
+
+
+/**
+ * Maximum credentials.
+ */
+#ifndef PJSUA_MAX_CRED
+#   define PJSUA_MAX_CRED	    PJSUA_MAX_ACC
+#endif
 
 
 /** 
- * Structure to be attached to all dialog. 
+ * Structure to be attached to invite dialog. 
  * Given a dialog "dlg", application can retrieve this structure
  * by accessing dlg->mod_data[pjsua.mod.id].
  */
-struct pjsua_inv_data
+struct pjsua_call
 {
-    PJ_DECL_LIST_MEMBER(struct pjsua_inv_data);
-
+    unsigned		 index;	    /**< Index in pjsua array.		    */
     pjsip_inv_session	*inv;	    /**< The invite session.		    */
+    int			 acc_index; /**< Account index being used.	    */
     pjmedia_session	*session;   /**< The media session.		    */
     unsigned		 conf_slot; /**< Slot # in conference bridge.	    */
-    unsigned		 call_slot; /**< RTP media index in med_sock_use[]  */
     pjsip_evsub		*xfer_sub;  /**< Xfer server subscription, if this
 					 call was triggered by xfer.	    */
+    pjmedia_sock_info	 skinfo;    /**< Preallocated media sockets.	    */
+
+    void		*app_data;  /**< Application data.		    */
 };
 
+typedef struct pjsua_call pjsua_call;
+
 
 /**
  * Buddy data.
@@ -79,6 +111,7 @@
 struct pjsua_buddy
 {
     pj_str_t		 uri;	    /**< Buddy URI		        */
+    int			 acc_index; /**< Which account to use.		*/
     pj_bool_t		 monitor;   /**< Should we monitor?		*/
     pjsip_evsub		*sub;	    /**< Buddy presence subscription	*/
     pjsip_pres_status	 status;    /**< Buddy presence status.		*/
@@ -100,12 +133,41 @@
 typedef struct pjsua_srv_pres pjsua_srv_pres;
 
 
+/**
+ * Account
+ */
+struct pjsua_acc
+{
+    int		     index;	    /**< Index in accounts array.	*/
+    pj_str_t	     local_uri;	    /**< Uri in From: header.		*/
+    pj_str_t	     user_part;	    /**< User part of local URI.	*/
+    pj_str_t	     host_part;	    /**< Host part of local URI.	*/
+    pj_str_t	     contact_uri;   /**< Uri in Contact: header.	*/
+
+    pj_str_t	     reg_uri;	    /**< Registrar URI.			*/
+    pjsip_regc	    *regc;	    /**< Client registration session.   */
+    pj_int32_t	     reg_timeout;   /**< Default timeout.		*/
+    pj_timer_entry   reg_timer;	    /**< Registration timer.		*/
+    pj_status_t	     reg_last_err;  /**< Last registration error.	*/
+    int		     reg_last_code; /**< Last status last register.	*/
+
+    pj_str_t	     proxy;	    /**< Proxy URL.			*/
+    pjsip_route_hdr  route_set;	    /**< Route set.			*/
+
+    pj_bool_t	     online_status; /**< Our online status.		*/
+    pjsua_srv_pres   pres_srv_list; /**< Server subscription list.	*/
+
+    void	    *app_data;	    /**< Application data.		*/
+};
+
+
+typedef struct pjsua_acc pjsua_acc;
+
 
 /* PJSUA application variables. */
 struct pjsua
 {
     /* Control: */
-
     pj_caching_pool  cp;	    /**< Global pool factory.		*/
     pjsip_endpoint  *endpt;	    /**< Global endpoint.		*/
     pj_pool_t	    *pool;	    /**< pjsua's private pool.		*/
@@ -113,95 +175,66 @@
     
 
     /* Media:  */
-
     pjmedia_endpt   *med_endpt;	    /**< Media endpoint.		*/
-    unsigned	     max_ports;	    /**< Max ports in conf.		*/
     pjmedia_conf    *mconf;	    /**< Media conference.		*/
     pj_bool_t	     null_audio;    /**< Null audio flag.		*/
     char	    *wav_file;	    /**< WAV file name to play.		*/
     unsigned	     wav_slot;	    /**< WAV player slot in bridge	*/
+    pj_bool_t	     auto_play;	    /**< Auto play file for calls?	*/
+    pj_bool_t	     auto_loop;	    /**< Auto loop RTP stream?		*/
+    pj_bool_t	     auto_conf;	    /**< Auto put to conference?	*/
+
 
     /* User Agent behaviour: */
-
     int		     auto_answer;   /**< Automatically answer in calls.	*/
 
-
-    /* Since we support simultaneous calls, we need to have multiple
-     * RTP sockets.
-     */
-    pjmedia_sock_info med_sock_info[PJSUA_MAX_CALLS];
-    pj_bool_t	      med_sock_use[PJSUA_MAX_CALLS];
-
-    /* User info: */
-
-    pj_str_t	     local_uri;	    /**< Uri in From: header.		*/
-    pj_str_t	     contact_uri;   /**< Uri in Contact: header.	*/
-
-    /* Proxy URLs: */
-
-    pj_str_t	     proxy;
-    pj_str_t	     outbound_proxy;
-    pjsip_route_hdr  route_set;
-
-
-    /* Registration: */
-
-    pj_str_t	     registrar_uri;
-    pjsip_regc	    *regc;
-    pj_int32_t	     reg_timeout;
-    pj_timer_entry   regc_timer;
-    pj_status_t	     regc_last_err; /**< Last registration error.	*/
-    int		     regc_last_code;/**< Last status last register.	*/
+    /* Account: */
+    int		     acc_cnt;	    /**< Number of client registrations	*/
+    pjsua_acc	     acc[PJSUA_MAX_ACC];    /** Client regs array.	*/
 
 
     /* Authentication credentials: */
 
-    unsigned	     cred_count;
-    pjsip_cred_info  cred_info[4];
+    int		     cred_count;    /**< Number of credentials.		*/
+    pjsip_cred_info  cred_info[10]; /**< Array of credentials.		*/
 
 
     /* Threading (optional): */
-
     int		     thread_cnt;    /**< Thread count.			*/
     pj_thread_t	    *threads[8];    /**< Thread instances.		*/
     pj_bool_t	     quit_flag;	    /**< To signal thread to quit.	*/
 
     /* Transport (UDP): */
-
     pj_uint16_t	     sip_port;	    /**< SIP signaling port.		*/
     pj_sock_t	     sip_sock;	    /**< SIP UDP socket.		*/
     pj_sockaddr_in   sip_sock_name; /**< Public/STUN UDP socket addr.	*/
 
+    pj_str_t	     outbound_proxy;/**< Outbound proxy.		*/
 
 
     /* STUN: */
-
     pj_str_t	     stun_srv1;
     int		     stun_port1;
     pj_str_t	     stun_srv2;
     int		     stun_port2;
 
 
-    /* Logging: */
-    
+    /* Logging: */    
     int		     log_level;	    /**< Logging verbosity.		*/
     int		     app_log_level; /**< stdout log verbosity.		*/
     unsigned	     log_decor;	    /**< Log decoration.		*/
     char	    *log_filename;  /**< Log filename.			*/
 
 
-    /* List of invite sessions: */
-
-    struct pjsua_inv_data inv_list;
+    /* PJSUA Calls: */
+    int		     max_calls;	    /**< Max nb of calls.		*/
+    int		     call_cnt;	    /**< Number of calls.		*/
+    pjsua_call	     calls[PJSUA_MAX_CALLS];	/** Calls array.	*/
 
 
     /* SIMPLE and buddy status: */
-
-    pj_bool_t	    online_status;  /**< Out online status.		*/
-    pjsua_srv_pres  pres_srv_list;  /**< Server subscription list.	*/
-
-    unsigned	    buddy_cnt;
-    pjsua_buddy	    buddies[PJSUA_MAX_BUDDIES];
+    int		     buddy_cnt;
+    pjsua_buddy	     buddies[PJSUA_MAX_BUDDIES];
 };
 
 
@@ -255,83 +288,71 @@
 pj_status_t pjsua_destroy(void);
 
 
-/*****************************************************************************
- * PJSUA Invite session API (defined in pjsua_inv.c).
+/**
+ * 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);
+
+
+/*****************************************************************************
+ * PJSUA Call API (defined in pjsua_call.c).
+ */
+
+/**
+ * Init pjsua call module.
+ */
+pj_status_t pjsua_call_init(void);
 
 /**
  * Make outgoing call.
  */
-pj_status_t pjsua_invite(const char *cstr_dest_uri,
-			 struct pjsua_inv_data **p_inv_data);
+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_inv_on_incoming(pjsip_rx_data *rdata);
+pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata);
 
 
 /**
+ * Answer call.
+ */
+void pjsua_call_answer(int call_index, int code);
+
+/**
  * Hangup call.
  */
-void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code);
+void pjsua_call_hangup(int call_index, int code);
 
 
 /**
  * Put call on-hold.
  */
-void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session);
+void pjsua_call_set_hold(int call_index);
 
 
 /**
  * Send re-INVITE (to release hold).
  */
-void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session);
+void pjsua_call_reinvite(int call_index);
 
 
 /**
  * Transfer call.
  */
-void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session,
-			 const char *dest);
+void pjsua_call_xfer(int call_index, const char *dest);
 
 
 /**
- * Callback to be called by session when invite session's state has changed.
- */
-void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e);
-
-
-/**
- * Callback to be called by session when outgoing dialog has forked.
- * This function will create a forked dialog.
- */
-void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e);
-
-
-/**
- * Callback to be called when SDP offer/answer negotiation has just completed
- * in the session. This function will start/update media if negotiation
- * has succeeded.
- */
-void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status);
-
-/**
- * Callback called when invite session received new offer.
- */
-void pjsua_inv_on_rx_offer( pjsip_inv_session *inv,
-			    const pjmedia_sdp_session *offer);
-
-/**
- * Callback to receive transaction state inside invite session or dialog
- * (e.g. REFER, MESSAGE).
- */
-void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv,
-				    pjsip_transaction *tsx,
-				    pjsip_event *e);
-
-/**
  * Terminate all calls.
  */
 void pjsua_inv_shutdown(void);
@@ -346,13 +367,13 @@
  *
  * @param app_callback	Optional callback
  */
-pj_status_t pjsua_regc_init(void);
+pj_status_t pjsua_regc_init(int acc_index);
 
 /**
  * Update registration or perform unregistration. If renew argument is zero,
  * this will start unregistration process.
  */
-void pjsua_regc_update(pj_bool_t renew);
+void pjsua_regc_update(int acc_index, pj_bool_t renew);
 
 
 
@@ -369,7 +390,7 @@
 /**
  * Refresh both presence client and server subscriptions.
  */
-void pjsua_pres_refresh(void);
+void pjsua_pres_refresh(int acc_index);
 
 /**
  * Terminate all subscriptions
@@ -392,12 +413,12 @@
 /**
  * Notify UI when invite state has changed.
  */
-void pjsua_ui_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e);
+void pjsua_ui_inv_on_state_changed(int call_index, pjsip_event *e);
 
 /**
  * Notify UI when registration status has changed.
  */
-void pjsua_ui_regc_on_state_changed(int code);
+void pjsua_ui_regc_on_state_changed(int acc_index);
 
 
 /*****************************************************************************
@@ -419,6 +440,11 @@
 pj_status_t pjsua_load_settings(const char *filename);
 
 /**
+ * Dump settings.
+ */
+int pjsua_dump_settings(char *buf, pj_size_t max);
+
+/**
  * Save settings to a file.
  */
 pj_status_t pjsua_save_settings(const char *filename);
diff --git a/pjsip/src/pjsua/pjsua_inv.c b/pjsip/src/pjsua/pjsua_call.c
similarity index 71%
rename from pjsip/src/pjsua/pjsua_inv.c
rename to pjsip/src/pjsua/pjsua_call.c
index f299455..e0a9a0a 100644
--- a/pjsip/src/pjsua/pjsua_inv.c
+++ b/pjsip/src/pjsua/pjsua_call.c
@@ -32,39 +32,39 @@
 /**
  * Make outgoing call.
  */
-pj_status_t pjsua_invite(const char *cstr_dest_uri,
-			 struct pjsua_inv_data **p_inv_data)
+pj_status_t pjsua_make_call(int acc_index,
+			    const char *cstr_dest_uri,
+			    int *p_call_index)
 {
     pj_str_t dest_uri;
     pjsip_dialog *dlg;
     pjmedia_sdp_session *offer;
     pjsip_inv_session *inv;
-    struct pjsua_inv_data *inv_data;
+    int call_index = -1;
     pjsip_tx_data *tdata;
-    int med_sk_index = 0;
     pj_status_t status;
 
     /* Convert cstr_dest_uri to dest_uri */
     
     dest_uri = pj_str((char*)cstr_dest_uri);
 
-    /* Find free socket. */
-    for (med_sk_index=0; med_sk_index<PJSUA_MAX_CALLS; ++med_sk_index) {
-	if (!pjsua.med_sock_use[med_sk_index])
+    /* Find free call slot. */
+    for (call_index=0; call_index<pjsua.max_calls; ++call_index) {
+	if (pjsua.calls[call_index].inv == NULL)
 	    break;
     }
 
-    if (med_sk_index == PJSUA_MAX_CALLS) {
+    if (call_index == pjsua.max_calls) {
 	PJ_LOG(3,(THIS_FILE, "Error: too many calls!"));
 	return PJ_ETOOMANY;
     }
 
-    pjsua.med_sock_use[med_sk_index] = 1;
-
     /* Create outgoing dialog: */
 
-    status = pjsip_dlg_create_uac( pjsip_ua_instance(), &pjsua.local_uri,
-				   &pjsua.contact_uri, &dest_uri, &dest_uri,
+    status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
+				   &pjsua.acc[acc_index].local_uri,
+				   &pjsua.acc[acc_index].contact_uri, 
+				   &dest_uri, &dest_uri,
 				   &dlg);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Dialog creation failed", status);
@@ -73,8 +73,8 @@
 
     /* Get media capability from media endpoint: */
 
-    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, dlg->pool,
-				       1, &pjsua.med_sock_info[med_sk_index], 
+    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, dlg->pool, 1, 
+				       &pjsua.calls[call_index].skinfo, 
 				       &offer);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status);
@@ -92,17 +92,16 @@
 
     /* Create and associate our data in the session. */
 
-    inv_data = pj_pool_zalloc( dlg->pool, sizeof(struct pjsua_inv_data));
-    inv_data->inv = inv;
-    inv_data->call_slot = med_sk_index;
-    dlg->mod_data[pjsua.mod.id] = inv_data;
-    inv->mod_data[pjsua.mod.id] = inv_data;
+    pjsua.calls[call_index].inv = inv;
+
+    dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
+    inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
 
 
     /* Set dialog Route-Set: */
 
-    if (!pj_list_empty(&pjsua.route_set))
-	pjsip_dlg_set_route_set(dlg, &pjsua.route_set);
+    if (!pj_list_empty(&pjsua.acc[acc_index].route_set))
+	pjsip_dlg_set_route_set(dlg, &pjsua.acc[acc_index].route_set);
 
 
     /* Set credentials: */
@@ -121,19 +120,10 @@
     }
 
 
-    /* Add invite session to the list. */
-    
-    pj_list_push_back(&pjsua.inv_list, inv_data);
-
-
     /* Send initial INVITE: */
 
     status = pjsip_inv_send_msg(inv, tdata, NULL);
     if (status != PJ_SUCCESS) {
-	/*
-	 * Note:
-	 *  inv_data will be removed from the list in the callback
-	 */
 	pjsua_perror(THIS_FILE, "Unable to send initial INVITE request", 
 		     status);
 	goto on_error;
@@ -141,16 +131,20 @@
 
 
     /* Done. */
-    if (p_inv_data)
-	*p_inv_data = inv_data;
+
+    ++pjsua.call_cnt;
+
+    if (p_call_index)
+	*p_call_index = call_index;
 
     return PJ_SUCCESS;
 
 
 on_error:
-
     PJ_TODO(DESTROY_DIALOG_ON_FAIL);
-    pjsua.med_sock_use[med_sk_index] = 0;
+    if (call_index != -1) {
+	pjsua.calls[call_index].inv = NULL;
+    }
     return status;
 }
 
@@ -158,7 +152,7 @@
 /**
  * Handle incoming INVITE request.
  */
-pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata)
+pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
 {
     pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
     pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
@@ -166,9 +160,9 @@
     pjsip_tx_data *response = NULL;
     unsigned options = 0;
     pjsip_inv_session *inv;
-    struct pjsua_inv_data *inv_data;
+    int acc_index;
+    int call_index = -1;
     pjmedia_sdp_session *answer;
-    int med_sk_index;
     pj_status_t status;
 
     /* Don't want to handle anything but INVITE */
@@ -214,12 +208,12 @@
      */
 
     /* Find free call slot. */
-    for (med_sk_index=0; med_sk_index<PJSUA_MAX_CALLS; ++med_sk_index) {
-	if (!pjsua.med_sock_use[med_sk_index])
+    for (call_index=0; call_index < pjsua.max_calls; ++call_index) {
+	if (pjsua.calls[call_index].inv == NULL)
 	    break;
     }
 
-    if (med_sk_index == PJSUA_MAX_CALLS) {
+    if (call_index == PJSUA_MAX_CALLS) {
 	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 
 				      PJSIP_SC_BUSY_HERE, NULL,
 				      NULL, NULL);
@@ -227,32 +221,35 @@
     }
 
 
-    pjsua.med_sock_use[med_sk_index] = 1;
-
     /* Get media capability from media endpoint: */
 
-    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool,
-				       1, &pjsua.med_sock_info[med_sk_index], 
+    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool, 1,
+				       &pjsua.calls[call_index].skinfo, 
 				       &answer );
     if (status != PJ_SUCCESS) {
 	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
 				      NULL, NULL);
 
-	/* Free call socket. */
-	pjsua.med_sock_use[med_sk_index] = 0;
 	return PJ_TRUE;
     }
 
+    /* TODO: 
+     *
+     * Get which account is most likely to be associated with this incoming
+     * call. We need the account to find which contact URI to put for
+     * the call.
+     */
+    acc_index = 0;
+
     /* Create dialog: */
 
     status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
-				   &pjsua.contact_uri, &dlg);
+				   &pjsua.acc[acc_index].contact_uri, 
+				   &dlg);
     if (status != PJ_SUCCESS) {
 	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
 				      NULL, NULL);
 
-	/* Free call socket. */
-	pjsua.med_sock_use[med_sk_index] = 0;
 	return PJ_TRUE;
     }
 
@@ -264,9 +261,6 @@
 
 	pjsip_dlg_respond(dlg, rdata, 500, NULL);
 
-	/* Free call socket. */
-	pjsua.med_sock_use[med_sk_index] = 0;
-
 	// TODO: Need to delete dialog
 	return PJ_TRUE;
     }
@@ -274,13 +268,10 @@
 
     /* Create and attach pjsua data to the dialog: */
 
-    inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data));
-    inv_data->inv = inv;
-    inv_data->call_slot = inv_data->call_slot = med_sk_index;
-    dlg->mod_data[pjsua.mod.id] = inv_data;
-    inv->mod_data[pjsua.mod.id] = inv_data;
+    pjsua.calls[call_index].inv = inv;
 
-    pj_list_push_back(&pjsua.inv_list, inv_data);
+    dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
+    inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
 
 
     /* Must answer with some response to initial INVITE.
@@ -296,9 +287,6 @@
 
 	pjsip_dlg_respond(dlg, rdata, 500, NULL);
 
-	/* Free call socket. */
-	pjsua.med_sock_use[med_sk_index] = 0;
-
 	// TODO: Need to delete dialog
 
     } else {
@@ -328,6 +316,8 @@
 		  pjsip_get_status_text(pjsua.auto_answer)->ptr ));
     }
 
+    ++pjsua.call_cnt;
+
     /* This INVITE request has been handled. */
     return PJ_TRUE;
 }
@@ -337,22 +327,20 @@
  * This callback receives notification from invite session when the
  * session state has changed.
  */
-void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
+static void pjsua_call_on_state_changed(pjsip_inv_session *inv, 
+					pjsip_event *e)
 {
-    struct pjsua_inv_data *inv_data;
-
-    inv_data = inv->dlg->mod_data[pjsua.mod.id];
+    pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id];
 
     /* If this is an outgoing INVITE that was created because of
      * REFER/transfer, send NOTIFY to transferer.
      */
-    if (inv_data && inv_data->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) 
-    {
+    if (call && call->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE)  {
 	int st_code = -1;
 	pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE;
 	
 
-	switch (inv->state) {
+	switch (call->inv->state) {
 	case PJSIP_INV_STATE_NULL:
 	case PJSIP_INV_STATE_CALLING:
 	    /* Do nothing */
@@ -382,13 +370,13 @@
 	    pjsip_tx_data *tdata;
 	    pj_status_t status;
 
-	    status = pjsip_xfer_notify( inv_data->xfer_sub,
+	    status = pjsip_xfer_notify( call->xfer_sub,
 					ev_state, st_code,
 					NULL, &tdata);
 	    if (status != PJ_SUCCESS) {
 		pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status);
 	    } else {
-		status = pjsip_xfer_send_request(inv_data->xfer_sub, tdata);
+		status = pjsip_xfer_send_request(call->xfer_sub, tdata);
 		if (status != PJ_SUCCESS) {
 		    pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status);
 		}
@@ -397,28 +385,26 @@
     }
 
 
+    pjsua_ui_inv_on_state_changed(call->index, e);
+
+    /* call->inv may be NULL now */
+
     /* Destroy media session when invite session is disconnected. */
     if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
 
-	pj_assert(inv_data != NULL);
+	pj_assert(call != NULL);
 
-	if (inv_data && inv_data->session) {
-	    pjmedia_conf_remove_port(pjsua.mconf, inv_data->conf_slot);
-	    pjmedia_session_destroy(inv_data->session);
-	    pjsua.med_sock_use[inv_data->call_slot] = 0;
-	    inv_data->session = NULL;
+	if (call && call->session) {
+	    pjmedia_conf_remove_port(pjsua.mconf, call->conf_slot);
+	    pjmedia_session_destroy(call->session);
+	    call->session = NULL;
 
 	    PJ_LOG(3,(THIS_FILE,"Media session is destroyed"));
 	}
 
-	if (inv_data) {
-
-	    pj_list_erase(inv_data);
-
-	}
+	call->inv = NULL;
+	--pjsua.call_cnt;
     }
-
-    pjsua_ui_inv_on_state_changed(inv, e);
 }
 
 
@@ -436,14 +422,14 @@
      * clear the xfer_sub member of the inv_data.
      */
     if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
-	struct pjsua_inv_data *inv_data;
+	pjsua_call *call;
 
-	inv_data = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
-	if (!inv_data)
+	call = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+	if (!call)
 	    return;
 
 	pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
-	inv_data->xfer_sub = NULL;
+	call->xfer_sub = NULL;
 
 	PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated"));
     }
@@ -458,13 +444,16 @@
 {
     pj_status_t status;
     pjsip_tx_data *tdata;
-    struct pjsua_inv_data *inv_data;
+    pjsua_call *existing_call;
+    int new_call;
     const pj_str_t str_refer_to = { "Refer-To", 8};
     pjsip_generic_string_hdr *refer_to;
     char *uri;
     struct pjsip_evsub_user xfer_cb;
     pjsip_evsub *sub;
 
+    existing_call = inv->dlg->mod_data[pjsua.mod.id];
+
     /* Find the Refer-To header */
     refer_to = (pjsip_generic_string_hdr*)
 	pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL);
@@ -523,7 +512,7 @@
     uri[refer_to->hvalue.slen] = '\0';
 
     /* Now make the outgoing call. */
-    status = pjsua_invite(uri, &inv_data);
+    status = pjsua_make_call(existing_call->acc_index, uri, &new_call);
     if (status != PJ_SUCCESS) {
 
 	/* Notify xferer about the error */
@@ -547,10 +536,10 @@
      * Subsequent state changed in pjsua_inv_on_state_changed() will be
      * reported back to the server subscription.
      */
-    inv_data->xfer_sub = sub;
+    pjsua.calls[new_call].xfer_sub = sub;
 
     /* Put the invite_data in the subscription. */
-    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, inv_data);
+    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, &pjsua.calls[new_call]);
 }
 
 
@@ -558,10 +547,12 @@
  * This callback is called when transaction state has changed in INVITE
  * session. We use this to trap incoming REFER request.
  */
-void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv,
-				    pjsip_transaction *tsx,
-				    pjsip_event *e)
+static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
+					    pjsip_transaction *tsx,
+					    pjsip_event *e)
 {
+    pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id];
+
     if (tsx->role==PJSIP_ROLE_UAS &&
 	tsx->state==PJSIP_TSX_STATE_TRYING &&
 	pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0)
@@ -569,7 +560,7 @@
 	/*
 	 * Incoming REFER request.
 	 */
-	on_call_transfered(inv, e->body.tsx_state.src.rdata);
+	on_call_transfered(call->inv, e->body.tsx_state.src.rdata);
     }
 }
 
@@ -578,7 +569,8 @@
  * This callback is called by invite session framework when UAC session
  * has forked.
  */
-void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e)
+static void pjsua_call_on_forked( pjsip_inv_session *inv, 
+				  pjsip_event *e)
 {
     PJ_UNUSED_ARG(inv);
     PJ_UNUSED_ARG(e);
@@ -590,7 +582,7 @@
 /*
  * Create inactive SDP for call hold.
  */
-static pj_status_t create_inactive_sdp(struct pjsua_inv_data *inv_session,
+static pj_status_t create_inactive_sdp(pjsua_call *call,
 				       pjmedia_sdp_session **p_answer)
 {
     pj_status_t status;
@@ -600,8 +592,7 @@
 
     /* Create new offer */
     status = pjmedia_endpt_create_sdp(pjsua.med_endpt, pjsua.pool, 1,
-				      &pjsua.med_sock_info[inv_session->call_slot],
-				      &sdp);
+				      &call->skinfo, &sdp);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
 	return status;
@@ -633,16 +624,16 @@
 /*
  * Called when session received new offer.
  */
-void pjsua_inv_on_rx_offer( pjsip_inv_session *inv,
-			    const pjmedia_sdp_session *offer)
+static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
+				   const pjmedia_sdp_session *offer)
 {
-    struct pjsua_inv_data *inv_data;
+    pjsua_call *call;
     pjmedia_sdp_conn *conn;
     pjmedia_sdp_session *answer;
     pj_bool_t is_remote_active;
     pj_status_t status;
 
-    inv_data = inv->dlg->mod_data[pjsua.mod.id];
+    call = inv->dlg->mod_data[pjsua.mod.id];
 
     /*
      * See if remote is offering active media (i.e. not on-hold)
@@ -669,11 +660,10 @@
 
     /* Supply candidate answer */
     if (is_remote_active) {
-	status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1,
-					   &pjsua.med_sock_info[inv_data->call_slot],
-					   &answer);
+	status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1,
+					   &call->skinfo, &answer);
     } else {
-	status = create_inactive_sdp( inv_data, &answer );
+	status = create_inactive_sdp( call, &answer );
     }
 
     if (status != PJ_SUCCESS) {
@@ -681,7 +671,7 @@
 	return;
     }
 
-    status = pjsip_inv_set_sdp_answer(inv, answer);
+    status = pjsip_inv_set_sdp_answer(call->inv, answer);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to set answer", status);
 	return;
@@ -695,15 +685,18 @@
  * in the session. This function will start/update media if negotiation
  * has succeeded.
  */
-void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status)
+static void pjsua_call_on_media_update(pjsip_inv_session *inv,
+				       pj_status_t status)
 {
-    struct pjsua_inv_data *inv_data;
+    pjsua_call *call;
     const pjmedia_sdp_session *local_sdp;
     const pjmedia_sdp_session *remote_sdp;
     pjmedia_port *media_port;
     pj_str_t port_name;
     char tmp[PJSIP_MAX_URL_SIZE];
 
+    call = inv->dlg->mod_data[pjsua.mod.id];
+
     if (status != PJ_SUCCESS) {
 
 	pjsua_perror(THIS_FILE, "SDP negotiation has failed", status);
@@ -713,17 +706,15 @@
 
     /* Destroy existing media session, if any. */
 
-    inv_data = inv->dlg->mod_data[pjsua.mod.id];
-    if (inv_data && inv_data->session) {
-	pjmedia_conf_remove_port(pjsua.mconf, inv_data->conf_slot);
-	pjmedia_session_destroy(inv_data->session);
-	pjsua.med_sock_use[inv_data->call_slot] = 0;
-	inv_data->session = NULL;
+    if (call && call->session) {
+	pjmedia_conf_remove_port(pjsua.mconf, call->conf_slot);
+	pjmedia_session_destroy(call->session);
+	call->session = NULL;
     }
 
     /* Get local and remote SDP */
 
-    status = pjmedia_sdp_neg_get_active_local(inv->neg, &local_sdp);
+    status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, 
 		     "Unable to retrieve currently active local SDP", 
@@ -732,7 +723,7 @@
     }
 
 
-    status = pjmedia_sdp_neg_get_active_remote(inv->neg, &remote_sdp);
+    status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, 
 		     "Unable to retrieve currently active remote SDP", 
@@ -747,10 +738,10 @@
 	return;
     
     status = pjmedia_session_create( pjsua.med_endpt, 1, 
-				     &pjsua.med_sock_info[inv_data->call_slot],
+				     &call->skinfo,
 				     local_sdp, remote_sdp, 
-				     inv_data,
-				     &inv_data->session );
+				     call,
+				     &call->session );
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create media session", 
 		     status);
@@ -761,7 +752,7 @@
     /* Get the port interface of the first stream in the session.
      * We need the port interface to add to the conference bridge.
      */
-    pjmedia_session_get_port(inv_data->session, 0, &media_port);
+    pjmedia_session_get_port(call->session, 0, &media_port);
 
 
     /*
@@ -769,38 +760,63 @@
      */
     port_name.ptr = tmp;
     port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
-				     inv_data->inv->dlg->remote.info->uri,
+				     call->inv->dlg->remote.info->uri,
 				     tmp, sizeof(tmp));
     if (port_name.slen < 1) {
 	port_name = pj_str("call");
     }
-    status = pjmedia_conf_add_port( pjsua.mconf, inv->pool,
+    status = pjmedia_conf_add_port( pjsua.mconf, call->inv->pool,
 				    media_port, 
 				    &port_name,
-				    &inv_data->conf_slot);
+				    &call->conf_slot);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create conference slot", 
 		     status);
-	pjmedia_session_destroy(inv_data->session);
-	inv_data->session = NULL;
+	pjmedia_session_destroy(call->session);
+	call->session = NULL;
 	return;
     }
 
     /* If auto-play is configured, connect the call to the file player 
      * port 
      */
-    if (pjsua.wav_file && inv->role == PJSIP_ROLE_UAS) {
+    if (pjsua.auto_play && pjsua.wav_file && 
+	call->inv->role == PJSIP_ROLE_UAS) 
+    {
 
 	pjmedia_conf_connect_port( pjsua.mconf, pjsua.wav_slot, 
-				   inv_data->conf_slot);
+				   call->conf_slot);
+
+    } else if (pjsua.auto_loop && call->inv->role == PJSIP_ROLE_UAS) {
+
+	pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 
+				   call->conf_slot);
+
+    } else if (pjsua.auto_conf) {
+
+	int i;
+
+	pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot);
+	pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0);
+
+	for (i=0; i < pjsua.max_calls; ++i) {
+
+	    if (!pjsua.calls[i].session)
+		continue;
+
+	    pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 
+				       pjsua.calls[i].conf_slot);
+	    pjmedia_conf_connect_port( pjsua.mconf, pjsua.calls[i].conf_slot,
+				       call->conf_slot);
+	}
 
     } else {
 
 	/* Connect new call to the sound device port (port zero) in the
 	 * main conference bridge.
 	 */
-	pjmedia_conf_connect_port( pjsua.mconf, 0, inv_data->conf_slot);
-	pjmedia_conf_connect_port( pjsua.mconf, inv_data->conf_slot, 0);
+	pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot);
+	pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0);
     }
 
 
@@ -811,7 +827,7 @@
 	int info_len = 0;
 	unsigned i;
 
-	pjmedia_session_get_info(inv_data->session, &sess_info);
+	pjmedia_session_get_info(call->session, &sess_info);
 	for (i=0; i<sess_info.stream_cnt; ++i) {
 	    int len;
 	    const char *dir;
@@ -850,13 +866,21 @@
 /*
  * Hangup call.
  */
-void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code)
+void pjsua_call_hangup(int call_index, int code)
 {
+    pjsua_call *call;
     pj_status_t status;
     pjsip_tx_data *tdata;
 
-    status = pjsip_inv_end_session(inv_session->inv, 
-				   code, NULL, &tdata);
+
+    call = &pjsua.calls[call_index];
+
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	return;
+    }
+
+    status = pjsip_inv_end_session(call->inv, code, NULL, &tdata);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, 
 		     "Failed to create end session message", 
@@ -871,7 +895,7 @@
     if (tdata == NULL)
 	return;
 
-    status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL);
+    status = pjsip_inv_send_msg(call->inv, tdata, NULL);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, 
 		     "Failed to send end session message", 
@@ -884,30 +908,37 @@
 /*
  * Put call on-Hold.
  */
-void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session)
+void pjsua_call_set_hold(int call_index)
 {
     pjmedia_sdp_session *sdp;
-    pjsip_inv_session *inv = inv_session->inv;
+    pjsua_call *call;
     pjsip_tx_data *tdata;
     pj_status_t status;
 
-    if (inv->state != PJSIP_INV_STATE_CONFIRMED) {
+    call = &pjsua.calls[call_index];
+    
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	return;
+    }
+
+    if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
 	PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed"));
 	return;
     }
 
-    status = create_inactive_sdp(inv_session, &sdp);
+    status = create_inactive_sdp(call, &sdp);
     if (status != PJ_SUCCESS)
 	return;
 
     /* Send re-INVITE with new offer */
-    status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata);
+    status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
 	return;
     }
 
-    status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL);
+    status = pjsip_inv_send_msg( call->inv, tdata, NULL);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
 	return;
@@ -918,36 +949,43 @@
 /*
  * re-INVITE.
  */
-void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session)
+void pjsua_call_reinvite(int call_index)
 {
     pjmedia_sdp_session *sdp;
     pjsip_tx_data *tdata;
-    pjsip_inv_session *inv = inv_session->inv;
+    pjsua_call *call;
     pj_status_t status;
 
+    call = &pjsua.calls[call_index];
 
-    if (inv->state != PJSIP_INV_STATE_CONFIRMED) {
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	return;
+    }
+
+
+    if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
 	PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
 	return;
     }
 
     /* Create SDP */
-    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1,
-				       &pjsua.med_sock_info[inv_session->call_slot],
-				       &sdp);
+    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1,
+				       &call->skinfo, &sdp);
     if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", status);
+	pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", 
+		     status);
 	return;
     }
 
     /* Send re-INVITE with new offer */
-    status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata);
+    status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
 	return;
     }
 
-    status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL);
+    status = pjsip_inv_send_msg( call->inv, tdata, NULL);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
 	return;
@@ -958,20 +996,27 @@
 /*
  * Transfer call.
  */
-void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session,
-			 const char *dest)
+void pjsua_call_xfer(int call_index, const char *dest)
 {
     pjsip_evsub *sub;
     pjsip_tx_data *tdata;
+    pjsua_call *call;
     pj_str_t tmp;
     pj_status_t status;
- 
+
     
+    call = &pjsua.calls[call_index];
+
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	return;
+    }
+   
     /* Create xfer client subscription.
      * We're not interested in knowing the transfer result, so we
      * put NULL as the callback.
      */
-    status = pjsip_xfer_create_uac(inv_session->inv->dlg, NULL, &sub);
+    status = pjsip_xfer_create_uac(call->inv->dlg, NULL, &sub);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create xfer", status);
 	return;
@@ -1005,21 +1050,41 @@
  */
 void pjsua_inv_shutdown()
 {
-    struct pjsua_inv_data *inv_data, *next;
+    int i;
 
-    inv_data = pjsua.inv_list.next;
-    while (inv_data != &pjsua.inv_list) {
+    for (i=0; i<pjsua.max_calls; ++i) {
 	pjsip_tx_data *tdata;
+	pjsua_call *call;
 
-	next = inv_data->next;
+	if (pjsua.calls[i].inv == NULL)
+	    continue;
 
-	if (pjsip_inv_end_session(inv_data->inv, 410, NULL, &tdata)==0) {
+	call = &pjsua.calls[i];
+
+	if (pjsip_inv_end_session(call->inv, 410, NULL, &tdata)==0) {
 	    if (tdata)
-		pjsip_inv_send_msg(inv_data->inv, tdata, NULL);
+		pjsip_inv_send_msg(call->inv, tdata, NULL);
 	}
-
-	inv_data = next;
     }
 }
 
 
+pj_status_t pjsua_call_init(void)
+{
+    /* Initialize invite session callback. */
+    pjsip_inv_callback inv_cb;
+    pj_status_t status;
+
+    pj_memset(&inv_cb, 0, sizeof(inv_cb));
+    inv_cb.on_state_changed = &pjsua_call_on_state_changed;
+    inv_cb.on_new_session = &pjsua_call_on_forked;
+    inv_cb.on_media_update = &pjsua_call_on_media_update;
+    inv_cb.on_rx_offer = &pjsua_call_on_rx_offer;
+    inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed;
+
+
+    /* Initialize invite session module: */
+    status = pjsip_inv_usage_init(pjsua.endpt, &pjsua.mod, &inv_cb);
+    
+    return status;
+}
diff --git a/pjsip/src/pjsua/pjsua_core.c b/pjsip/src/pjsua/pjsua_core.c
index f5c3546..974c6d5 100644
--- a/pjsip/src/pjsua/pjsua_core.c
+++ b/pjsip/src/pjsua/pjsua_core.c
@@ -45,6 +45,8 @@
  */
 void pjsua_default(void)
 {
+    unsigned i;
+
 
     /* Normally need another thread for console application, because main 
      * thread will be blocked in fgets().
@@ -53,44 +55,38 @@
 
 
     /* Default transport settings: */
-
     pjsua.sip_port = 5060;
 
 
     /* Default logging settings: */
-
     pjsua.log_level = 5;
     pjsua.app_log_level = 4;
     pjsua.log_decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | 
 		      PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE;
 
-    /* Default: do not use STUN: */
 
+    /* Default: do not use STUN: */
     pjsua.stun_port1 = pjsua.stun_port2 = 0;
 
-    /* Default URIs: */
+    /* 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;
+	pj_list_init(&pjsua.acc[i].route_set);
+	pj_list_init(&pjsua.acc[i].pres_srv_list);
+    }
 
-    pjsua.local_uri = pj_str(PJSUA_LOCAL_URI);
+    /* Init call array: */
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua.calls); ++i)
+	pjsua.calls[i].index = i;
 
-    /* Default registration timeout: */
-
-    pjsua.reg_timeout = 55;
-
-    /* Default maximum conference ports: */
-
-    pjsua.max_ports = 8;
-
-    /* Init route set list: */
-
-    pj_list_init(&pjsua.route_set);
-
-    /* Init invite session list: */
-
-    pj_list_init(&pjsua.inv_list);
+    /* Default max nb of calls. */
+    pjsua.max_calls = 4;
 
     /* Init server presence subscription list: */
     
-    pj_list_init(&pjsua.pres_srv_list);
 
 }
 
@@ -110,8 +106,7 @@
 
     if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) {
 
-	return pjsua_inv_on_incoming(rdata);
-
+	return pjsua_call_on_incoming(rdata);
     }
 
     return PJ_FALSE;
@@ -145,7 +140,7 @@
     enum { 
 	RTP_START_PORT = 4000,
 	RTP_RANDOM_START = 2,
-	RTP_RETRY = 20 
+	RTP_RETRY = 100
     };
     enum {
 	SIP_SOCK,
@@ -240,37 +235,49 @@
 	    for (i=0; i<3; ++i)
 		pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
 
-	    if (sip)
-		mapped_addr[SIP_SOCK].sin_port = pj_htons((pj_uint16_t)pjsua.sip_port);
-	    mapped_addr[RTP_SOCK].sin_port = pj_htons((pj_uint16_t)rtp_port);
-	    mapped_addr[RTCP_SOCK].sin_port = pj_htons((pj_uint16_t)(rtp_port+1));
+	    if (sip) {
+		mapped_addr[SIP_SOCK].sin_port = 
+		    pj_htons((pj_uint16_t)pjsua.sip_port);
+	    }
+	    mapped_addr[RTP_SOCK].sin_port=pj_htons((pj_uint16_t)rtp_port);
+	    mapped_addr[RTCP_SOCK].sin_port=pj_htons((pj_uint16_t)(rtp_port+1));
 	    break;
+
 	} else {
-	    status = pj_stun_get_mapped_addr( &pjsua.cp.factory, 3, sock,
-					      &pjsua.stun_srv1, pjsua.stun_port1,
-					      &pjsua.stun_srv2, pjsua.stun_port2,
-					      mapped_addr);
+	    status=pj_stun_get_mapped_addr(&pjsua.cp.factory, 3, sock,
+					   &pjsua.stun_srv1, pjsua.stun_port1,
+					   &pjsua.stun_srv2, pjsua.stun_port2,
+					   mapped_addr);
 	    if (status != PJ_SUCCESS) {
 		pjsua_perror(THIS_FILE, "STUN error", status);
 		goto on_error;
 	    }
 
-	    if (pj_ntohs(mapped_addr[2].sin_port) == pj_ntohs(mapped_addr[1].sin_port)+1)
+	    if (pj_ntohs(mapped_addr[2].sin_port) == 
+		pj_ntohs(mapped_addr[1].sin_port)+1)
+	    {
 		break;
+	    }
 
-	    pj_sock_close(sock[RTP_SOCK]); sock[RTP_SOCK] = PJ_INVALID_SOCKET;
-	    pj_sock_close(sock[RTCP_SOCK]); sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
+	    pj_sock_close(sock[RTP_SOCK]); 
+	    sock[RTP_SOCK] = PJ_INVALID_SOCKET;
+
+	    pj_sock_close(sock[RTCP_SOCK]); 
+	    sock[RTCP_SOCK] = PJ_INVALID_SOCKET;
 	}
     }
 
     if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) {
-	PJ_LOG(1,(THIS_FILE, "Unable to find appropriate RTP/RTCP ports combination"));
+	PJ_LOG(1,(THIS_FILE, 
+		  "Unable to find appropriate RTP/RTCP ports combination"));
 	goto on_error;
     }
 
     if (sip) {
 	pjsua.sip_sock = sock[SIP_SOCK];
-	pj_memcpy(&pjsua.sip_sock_name, &mapped_addr[SIP_SOCK], sizeof(pj_sockaddr_in));
+	pj_memcpy(&pjsua.sip_sock_name, 
+		  &mapped_addr[SIP_SOCK], 
+		  sizeof(pj_sockaddr_in));
     } else {
 	pj_sock_close(sock[0]);
     }
@@ -391,30 +398,13 @@
 
     /* Initialize invite session module: */
 
-    {
-	
-	/* Initialize invite session callback. */
-	pjsip_inv_callback inv_cb;
-
-	pj_memset(&inv_cb, 0, sizeof(inv_cb));
-	inv_cb.on_state_changed = &pjsua_inv_on_state_changed;
-	inv_cb.on_new_session = &pjsua_inv_on_new_session;
-	inv_cb.on_media_update = &pjsua_inv_on_media_update;
-	inv_cb.on_rx_offer = &pjsua_inv_on_rx_offer;
-	inv_cb.on_tsx_state_changed = &pjsua_inv_on_tsx_state_changed;
-
-
-	/* Initialize invite session module: */
-	status = pjsip_inv_usage_init(pjsua.endpt, &pjsua.mod, &inv_cb);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Invite usage initialization error", 
-			 status);
-	    goto on_error;
-	}
-
+    status = pjsua_call_init();
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Invite usage initialization error", 
+		     status);
+	goto on_error;
     }
 
-
     /* Done */
 
     return PJ_SUCCESS;
@@ -520,7 +510,8 @@
 
     /* Init conference bridge. */
 
-    status = pjmedia_conf_create(pjsua.pool, pjsua.max_ports, 
+    status = pjmedia_conf_create(pjsua.pool, 
+				 pjsua.max_calls+PJSUA_CONF_MORE_PORTS, 
 				 8000, 160, 16, &pjsua.mconf);
     if (status != PJ_SUCCESS) {
 	pj_caching_pool_destroy(&pjsua.cp);
@@ -547,6 +538,67 @@
 }
 
 
+/*
+ * Find account for incoming request.
+ */
+int pjsua_find_account_for_incoming(pjsip_rx_data *rdata)
+{
+    pjsip_uri *uri;
+    pjsip_sip_uri *sip_uri;
+    int acc_index;
+
+    uri = rdata->msg_info.to->uri;
+
+    /* Just return account #0 if To URI is not SIP: */
+    if (!PJSIP_URI_SCHEME_IS_SIP(uri) && 
+	!PJSIP_URI_SCHEME_IS_SIPS(uri)) 
+    {
+	return 0;
+    }
+
+
+    sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+
+    /* Find account which has matching username and domain. */
+    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
+
+	pjsua_acc *acc = &pjsua.acc[acc_index];
+
+	if (pj_stricmp(&acc->user_part, &sip_uri->user)==0 &&
+	    pj_stricmp(&acc->host_part, &sip_uri->host)==0) 
+	{
+	    /* Match ! */
+	    return acc_index;
+	}
+    }
+
+    /* No matching, try match domain part only. */
+    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
+
+	pjsua_acc *acc = &pjsua.acc[acc_index];
+
+	if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) {
+	    /* Match ! */
+	    return acc_index;
+	}
+    }
+
+    /* Still no match, just return account #0 */
+    return 0;
+}
+
+
+/*
+ * Find account for outgoing request.
+ */
+int pjsua_find_account_for_outgoing(const pj_str_t *url)
+{
+    PJ_UNUSED_ARG(url);
+
+    /* Just use account #0 */
+    return 0;
+}
+
 
 /*
  * Start pjsua stack.
@@ -588,11 +640,18 @@
 
 
     /* Init sockets (STUN etc): */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.med_sock_info); ++i) {
-	status = init_sockets(i==0, &pjsua.med_sock_info[i]);
+    for (i=0; i<(int)pjsua.max_calls; ++i) {
+	status = init_sockets(i==0, &pjsua.calls[i].skinfo);
 	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, "init_sockets() has returned error", 
 			 status);
+	    --i;
+	    if (i >= 0)
+		pj_sock_close(pjsua.sip_sock);
+	    while (i >= 0) {
+		pj_sock_close(pjsua.calls[i].skinfo.rtp_sock);
+		pj_sock_close(pjsua.calls[i].skinfo.rtcp_sock);
+	    }
 	    return status;
 	}
     }
@@ -623,34 +682,26 @@
     }
 
     /* Initialize Contact URI, if one is not specified: */
-
-    if (pjsua.contact_uri.slen == 0 && pjsua.local_uri.slen) {
+    for (i=0; i<pjsua.acc_cnt; ++i) {
 
 	pjsip_uri *uri;
 	pjsip_sip_uri *sip_uri;
-	char contact[128];
-	int len;
 
-	/* The local Contact is the username@ip-addr, where
-	 *  - username is taken from the local URI,
-	 *  - ip-addr in UDP transport's address name (which may have been
-	 *    resolved from STUN.
-	 */
-	
 	/* Need to parse local_uri to get the elements: */
 
-	uri = pjsip_parse_uri(pjsua.pool, pjsua.local_uri.ptr, 
-			      pjsua.local_uri.slen, 0);
+	uri = pjsip_parse_uri(pjsua.pool, pjsua.acc[i].local_uri.ptr,
+			      pjsua.acc[i].local_uri.slen, 0);
 	if (uri == NULL) {
 	    pjsua_perror(THIS_FILE, "Invalid local URI", 
 			 PJSIP_EINVALIDURI);
 	    return PJSIP_EINVALIDURI;
 	}
 
-
 	/* Local URI MUST be a SIP or SIPS: */
 
-	if (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri)) {
+	if (!PJSIP_URI_SCHEME_IS_SIP(uri) && 
+	    !PJSIP_URI_SCHEME_IS_SIPS(uri)) 
+	{
 	    pjsua_perror(THIS_FILE, "Invalid local URI", 
 			 PJSIP_EINVALIDSCHEME);
 	    return PJSIP_EINVALIDSCHEME;
@@ -661,39 +712,54 @@
 
 	sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri);
 
-	
-	/* Build temporary contact string. */
+	pjsua.acc[i].user_part = sip_uri->user;
+	pjsua.acc[i].host_part = sip_uri->host;
 
-	if (sip_uri->user.slen) {
+	if (pjsua.acc[i].contact_uri.slen == 0 && 
+	    pjsua.acc[i].local_uri.slen) 
+	{
+	    char contact[128];
+	    int len;
 
-	    /* With the user part. */
-	    len = pj_snprintf(contact, sizeof(contact),
-			      "<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);
-	} else {
+	    /* The local Contact is the username@ip-addr, where
+	     *  - username is taken from the local URI,
+	     *  - ip-addr in UDP transport's address name (which may have been
+	     *    resolved from STUN.
+	     */
+	    
+	    /* Build temporary contact string. */
 
-	    /* Without user part */
+	    if (sip_uri->user.slen) {
 
-	    len = pj_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);
+		/* With the user part. */
+		len = pj_snprintf(contact, sizeof(contact),
+				  "<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);
+	    } else {
+
+		/* Without user part */
+
+		len = pj_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);
+	    }
+
+	    if (len < 1 || len >= sizeof(contact)) {
+		pjsua_perror(THIS_FILE, "Invalid Contact", PJSIP_EURITOOLONG);
+		return PJSIP_EURITOOLONG;
+	    }
+
+	    /* Duplicate Contact uri. */
+
+	    pj_strdup2(pjsua.pool, &pjsua.acc[i].contact_uri, contact);
+
 	}
-
-	if (len < 1 || len >= sizeof(contact)) {
-	    pjsua_perror(THIS_FILE, "Invalid Contact", PJSIP_EURITOOLONG);
-	    return PJSIP_EURITOOLONG;
-	}
-
-	/* Duplicate Contact uri. */
-
-	pj_strdup2(pjsua.pool, &pjsua.contact_uri, contact);
-
     }
 
     /* If outbound_proxy is specified, put it in the route_set: */
@@ -714,7 +780,9 @@
 	    return PJSIP_EINVALIDURI;
 	}
 
-	pj_list_push_back(&pjsua.route_set, route);
+	for (i=0; i<pjsua.acc_cnt; ++i) {
+	    pj_list_push_front(&pjsua.acc[i].route_set, route);
+	}
     }
 
 
@@ -736,14 +804,22 @@
     /* Start registration: */
 
     /* Create client registration session: */
+    for (i=0; i<pjsua.acc_cnt; ++i) {
+	status = pjsua_regc_init(i);
+	if (status != PJ_SUCCESS)
+	    return status;
 
-    status = pjsua_regc_init();
-    if (status != PJ_SUCCESS)
-	return status;
+	/* Perform registration, if required. */
+	if (pjsua.acc[i].regc) {
+	    pjsua_regc_update(i, 1);
+	}
+    }
 
-    /* Perform registration, if required. */
-    if (pjsua.regc) {
-	pjsua_regc_update(1);
+
+    /* Find account for outgoing preence subscription */
+    for (i=0; i<pjsua.buddy_cnt; ++i) {
+	pjsua.buddies[i].acc_index = 
+	    pjsua_find_account_for_outgoing(&pjsua.buddies[i].uri);
     }
 
 
@@ -777,6 +853,19 @@
     /* Signal threads to quit: */
     pjsua.quit_flag = 1;
 
+    /* Terminate all calls. */
+    pjsua_inv_shutdown();
+
+    /* Terminate all presence subscriptions. */
+    pjsua_pres_shutdown();
+
+    /* Unregister, if required: */
+    for (i=0; i<pjsua.acc_cnt; ++i) {
+	if (pjsua.acc[i].regc) {
+	    pjsua_regc_update(i, 0);
+	}
+    }
+
     /* Wait worker threads to quit: */
     for (i=0; i<pjsua.thread_cnt; ++i) {
 	
@@ -788,17 +877,6 @@
     }
 
 
-    /* Terminate all calls. */
-    pjsua_inv_shutdown();
-
-    /* Terminate all presence subscriptions. */
-    pjsua_pres_shutdown();
-
-    /* Unregister, if required: */
-    if (pjsua.regc) {
-	pjsua_regc_update(0);
-    }
-
     /* Wait for some time to allow unregistration to complete: */
     PJ_LOG(4,(THIS_FILE, "Shutting down..."));
     busy_sleep(1000);
diff --git a/pjsip/src/pjsua/pjsua_opt.c b/pjsip/src/pjsua/pjsua_opt.c
index 5281926..5ec3798 100644
--- a/pjsip/src/pjsua/pjsua_opt.c
+++ b/pjsip/src/pjsua/pjsua_opt.c
@@ -43,8 +43,6 @@
     puts("Usage:");
     puts("  pjsua [options]");
     puts("");
-    puts("  [sip-url]   Default URL to invite.");
-    puts("");
     puts("General options:");
     puts("  --help              Display this help screen");
     puts("  --version           Display version info");
@@ -55,36 +53,43 @@
     puts("  --log-level=N       Set log max level to N (0(none) to 6(trace))");
     puts("  --app-log-level=N   Set log max level for stdout display to N");
     puts("");
+    puts("SIP Account options:");
+    puts("  --id=url            Set the URL of local ID (used in From header)");
+    puts("  --contact=url       Override the Contact information");
+    puts("  --proxy=url         Set the URL of proxy server");
+    puts("");
+    puts("SIP Account Registration Options:");
+    puts("  --registrar=url     Set the URL of registrar server");
+    puts("  --reg-timeout=secs  Set registration interval to secs (default 3600)");
+    puts("");
+    puts("SIP Account Control:");
+    puts("  --next-account      Add more account");
+    puts("");
     puts("Authentication options:");
     puts("  --realm=string      Set realm");
     puts("  --username=string   Set authentication username");
     puts("  --password=string   Set authentication password");
-    puts("");
-    puts("SIP options:");
-    puts("  --id=url            Set the URL of local ID (used in From header)");
-    puts("  --contact=url       Override the Contact information");
-    puts("  --proxy=url         Set the URL of proxy server");
-    //puts("  --outbound=url      Set the URL of outbound proxy server");
-    puts("");
-    puts("Registration Options:");
-    puts("  --registrar=url     Set the URL of registrar server");
-    puts("  --reg-timeout=secs  Set registration interval to secs (default 3600)");
+    puts("  --next-cred         Add more credential");
     puts("");
     puts("Transport Options:");
-    puts("  --local-port=port   Set TCP/UDP port");
+    puts("  --local-port=port        Set TCP/UDP port");
+    puts("  --outbound=url           Set the URL of outbound proxy server");
     puts("  --use-stun1=host[:port]");
-    puts("  --use-stun2=host[:port]  Use STUN and set host name and port of STUN servers");
+    puts("  --use-stun2=host[:port]  Resolve local IP with the specified STUN servers");
     puts("");
     puts("Media Options:");
     puts("  --null-audio        Use NULL audio device");
-    //puts("  --wav-file=file     Play WAV file in conference bridge");
+    puts("  --play-file=file    Play WAV file in conference bridge");
+    puts("  --auto-play         Automatically play the file (to incoming calls only)");
+    puts("  --auto-loop         Automatically loop incoming RTP to outgoing RTP");
+    puts("  --auto-conf         Automatically put incoming calls to conference");
     puts("");
     puts("Buddy List (can be more than one):");
     puts("  --add-buddy url     Add the specified URL to the buddy list.");
     puts("");
     puts("User Agent options:");
     puts("  --auto-answer=code  Automatically answer incoming calls with code (e.g. 200)");
-    puts("  --auto-play=file    Automatically play WAVE file to incoming calls");
+    puts("  --max-calls=N       Maximum number of concurrent calls (default:4, max:255)");
     puts("");
     fflush(stdout);
 }
@@ -199,7 +204,11 @@
 	   OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
 	   OPT_USE_STUN1, OPT_USE_STUN2, 
 	   OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
-	   OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY};
+	   OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
+	   OPT_AUTO_CONF,
+	   OPT_PLAY_FILE,
+	   OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
+    };
     struct option long_options[] = {
 	{ "config-file",1, 0, OPT_CONFIG_FILE},
 	{ "log-file",	1, 0, OPT_LOG_FILE},
@@ -225,10 +234,18 @@
 	{ "no-presence", 0, 0, OPT_NO_PRESENCE},
 	{ "auto-answer",1, 0, OPT_AUTO_ANSWER},
 	{ "auto-hangup",1, 0, OPT_AUTO_HANGUP},
-	{ "auto-play",  1, 0, OPT_AUTO_PLAY},
+	{ "auto-play",  0, 0, OPT_AUTO_PLAY},
+	{ "auto-loop",  0, 0, OPT_AUTO_LOOP},
+	{ "auto-conf",  0, 0, OPT_AUTO_CONF},
+	{ "play-file",  1, 0, OPT_PLAY_FILE},
+	{ "next-account",0,0, OPT_NEXT_ACCOUNT},
+	{ "next-cred",	0, 0, OPT_NEXT_CRED},
+	{ "max-calls",	1, 0, OPT_MAX_CALLS},
 	{ NULL, 0, 0, 0}
     };
     pj_status_t status;
+    pjsua_acc *cur_acc;
+    pjsip_cred_info *cur_cred;
     char *config_file = NULL;
 
     /* Run getopt once to see if user specifies config file to read. */
@@ -249,6 +266,10 @@
     }
 
 
+    cur_acc = &pjsua.acc[0];
+    cur_cred = &pjsua.cred_info[0];
+
+
     /* Reinitialize and re-run getopt again, possibly with new arguments
      * read from config file.
      */
@@ -307,7 +328,7 @@
 		printf("Error: invalid SIP URL '%s' in proxy argument\n", optarg);
 		return PJ_EINVAL;
 	    }
-	    pjsua.proxy = pj_str(optarg);
+	    cur_acc->proxy = pj_str(optarg);
 	    break;
 
 	case OPT_OUTBOUND_PROXY:   /* outbound proxy */
@@ -323,12 +344,12 @@
 		printf("Error: invalid SIP URL '%s' in registrar argument\n", optarg);
 		return PJ_EINVAL;
 	    }
-	    pjsua.registrar_uri = pj_str(optarg);
+	    cur_acc->reg_uri = pj_str(optarg);
 	    break;
 
 	case OPT_REG_TIMEOUT:   /* reg-timeout */
-	    pjsua.reg_timeout = pj_strtoul(pj_cstr(&tmp,optarg));
-	    if (pjsua.reg_timeout < 1 || pjsua.reg_timeout > 3600) {
+	    cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,optarg));
+	    if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
 		printf("Error: invalid value for --reg-timeout (expecting 1-3600)\n");
 		return PJ_EINVAL;
 	    }
@@ -339,7 +360,7 @@
 		printf("Error: invalid SIP URL '%s' in local id argument\n", optarg);
 		return PJ_EINVAL;
 	    }
-	    pjsua.local_uri = pj_str(optarg);
+	    cur_acc->local_uri = pj_str(optarg);
 	    break;
 
 	case OPT_CONTACT:   /* contact */
@@ -347,23 +368,33 @@
 		printf("Error: invalid SIP URL '%s' in contact argument\n", optarg);
 		return PJ_EINVAL;
 	    }
-	    pjsua.contact_uri = pj_str(optarg);
+	    cur_acc->contact_uri = pj_str(optarg);
+	    break;
+
+	case OPT_NEXT_ACCOUNT: /* Add more account. */
+	    pjsua.acc_cnt++;
+	    cur_acc = &pjsua.acc[pjsua.acc_cnt - 1];
 	    break;
 
 	case OPT_USERNAME:   /* Default authentication user */
-	    if (!pjsua.cred_count) pjsua.cred_count = 1;
-	    pjsua.cred_info[0].username = pj_str(optarg);
+	    if (pjsua.cred_count==0) pjsua.cred_count=1;
+	    cur_cred->username = pj_str(optarg);
 	    break;
 
 	case OPT_REALM:	    /* Default authentication realm. */
-	    if (!pjsua.cred_count) pjsua.cred_count = 1;
-	    pjsua.cred_info[0].realm = pj_str(optarg);
+	    if (pjsua.cred_count==0) pjsua.cred_count=1;
+	    cur_cred->realm = pj_str(optarg);
 	    break;
 
 	case OPT_PASSWORD:   /* authentication password */
-	    if (!pjsua.cred_count) pjsua.cred_count = 1;
-	    pjsua.cred_info[0].data_type = 0;
-	    pjsua.cred_info[0].data = pj_str(optarg);
+	    if (pjsua.cred_count==0) pjsua.cred_count=1;
+	    cur_cred->data_type = 0;
+	    cur_cred->data = pj_str(optarg);
+	    break;
+
+	case OPT_NEXT_CRED: /* Next credential */
+	    pjsua.cred_count++;
+	    cur_cred = &pjsua.cred_info[pjsua.cred_count - 1];
 	    break;
 
 	case OPT_USE_STUN1:   /* STUN server 1 */
@@ -411,6 +442,18 @@
 	    break;
 
 	case OPT_AUTO_PLAY:
+	    pjsua.auto_play = 1;
+	    break;
+
+	case OPT_AUTO_LOOP:
+	    pjsua.auto_loop = 1;
+	    break;
+
+	case OPT_AUTO_CONF:
+	    pjsua.auto_conf = 1;
+	    break;
+
+	case OPT_PLAY_FILE:
 	    pjsua.wav_file = optarg;
 	    break;
 
@@ -421,6 +464,14 @@
 		return -1;
 	    }
 	    break;
+
+	case OPT_MAX_CALLS:
+	    pjsua.max_calls = atoi(optarg);
+	    if (pjsua.max_calls < 1 || pjsua.max_calls > 255) {
+		puts("Too many calls for max-calls (1-255)");
+		return -1;
+	    }
+	    break;
 	}
     }
 
@@ -429,21 +480,17 @@
 	return PJ_EINVAL;
     }
 
-    if (pjsua.reg_timeout == 0)
-	pjsua.reg_timeout = 3600;
-
-
     return PJ_SUCCESS;
 }
 
 
 
-static void print_invite_session(const char *title,
-				 struct pjsua_inv_data *inv_data, 
-				 char *buf, pj_size_t size)
+static void print_call(const char *title,
+		       int call_index, 
+		       char *buf, pj_size_t size)
 {
     int len;
-    pjsip_inv_session *inv = inv_data->inv;
+    pjsip_inv_session *inv = pjsua.calls[call_index].inv;
     pjsip_dialog *dlg = inv->dlg;
     char userinfo[128];
 
@@ -515,7 +562,6 @@
  */
 void pjsua_dump(void)
 {
-    struct pjsua_inv_data *inv_data;
     char buf[128];
     unsigned old_decor;
 
@@ -533,23 +579,23 @@
     /* Dump all invite sessions: */
     PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
 
-    if (pj_list_empty(&pjsua.inv_list)) {
+    if (pjsua.call_cnt == 0) {
 
 	PJ_LOG(3,(THIS_FILE, "  - no sessions -"));
 
     } else {
+	int i;
 
-	inv_data = pjsua.inv_list.next;
+	for (i=0; i<pjsua.max_calls; ++i) {
 
-	while (inv_data != &pjsua.inv_list) {
+	    if (pjsua.calls[i].inv == NULL)
+		continue;
 
-	    print_invite_session("  ", inv_data, buf, sizeof(buf));
+	    print_call("  ", i, buf, sizeof(buf));
 	    PJ_LOG(3,(THIS_FILE, "%s", buf));
 
-	    if (inv_data->session)
-		dump_media_session(inv_data->session);
-
-	    inv_data = inv_data->next;
+	    if (pjsua.calls[i].session)
+		dump_media_session(pjsua.calls[i].session);
 	}
     }
 
@@ -575,40 +621,96 @@
 
 
 /*
- * Save settings.
+ * Save account settings
  */
-pj_status_t pjsua_save_settings(const char *filename)
+static void save_account_settings(int acc_index, pj_str_t *result)
 {
-    unsigned i;
-    pj_str_t cfg;
     char line[128];
-    pj_pool_t *pool;
-    FILE *fhnd;
+    pjsua_acc *acc = &pjsua.acc[acc_index];
 
-    /* Create pool for temporary buffer. */
-    pool = pj_pool_create(&pjsua.cp.factory, "settings", 4000, 0, NULL);
-    if (!pool)
-	return PJ_ENOMEM;
-
-
-    cfg.ptr = pj_pool_alloc(pool, 3800);
-    if (!cfg.ptr) {
-	pj_pool_release(pool);
-	return PJ_EBUG;
-    }
-    cfg.slen = 0;
+    
+    pj_ansi_sprintf(line, "#\n# Account %d:\n#\n", acc_index);
+    pj_strcat2(result, line);
 
 
     /* Identity */
-    if (pjsua.local_uri.slen) {
+    if (acc->local_uri.slen) {
 	pj_ansi_sprintf(line, "--id %.*s\n", 
-			(int)pjsua.local_uri.slen, 
-			pjsua.local_uri.ptr);
+			(int)acc->local_uri.slen, 
+			acc->local_uri.ptr);
+	pj_strcat2(result, line);
+    }
+
+    /* Registrar server */
+    if (acc->reg_uri.slen) {
+	pj_ansi_sprintf(line, "--registrar %.*s\n",
+			      (int)acc->reg_uri.slen,
+			      acc->reg_uri.ptr);
+	pj_strcat2(result, line);
+
+	pj_ansi_sprintf(line, "--reg-timeout %u\n",
+			      acc->reg_timeout);
+	pj_strcat2(result, line);
+    }
+
+
+    /* Proxy */
+    if (acc->proxy.slen) {
+	pj_ansi_sprintf(line, "--proxy %.*s\n",
+			      (int)acc->proxy.slen,
+			      acc->proxy.ptr);
+	pj_strcat2(result, line);
+    }
+}
+
+
+
+/*
+ * Dump settings.
+ */
+int pjsua_dump_settings(char *buf, pj_size_t max)
+{
+    int acc_index;
+    int i;
+    pj_str_t cfg;
+    char line[128];
+
+    cfg.ptr = buf;
+    cfg.slen = 0;
+
+
+    /* Logging. */
+    pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
+    pj_ansi_sprintf(line, "--log-level %d\n",
+		    pjsua.log_level);
+    pj_strcat2(&cfg, line);
+
+    pj_ansi_sprintf(line, "--app-log-level %d\n",
+		    pjsua.app_log_level);
+    pj_strcat2(&cfg, line);
+
+    if (pjsua.log_filename) {
+	pj_ansi_sprintf(line, "--log-file %s\n",
+			pjsua.log_filename);
 	pj_strcat2(&cfg, line);
     }
 
+
+    /* Save account settings. */
+    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
+	
+	save_account_settings(acc_index, &cfg);
+
+	if (acc_index < pjsua.acc_cnt-1)
+	    pj_strcat2(&cfg, "--next-account\n");
+    }
+
     /* Credentials. */
     for (i=0; i<pjsua.cred_count; ++i) {
+
+	pj_ansi_sprintf(line, "#\n# Credential %d:\n#\n", i);
+	pj_strcat2(&cfg, line);
+
 	if (pjsua.cred_info[i].realm.slen) {
 	    pj_ansi_sprintf(line, "--realm %.*s\n",
 				  (int)pjsua.cred_info[i].realm.slen,
@@ -625,16 +727,13 @@
 			      (int)pjsua.cred_info[i].data.slen,
 			      pjsua.cred_info[i].data.ptr);
 	pj_strcat2(&cfg, line);
+
+	if (i < pjsua.cred_count-1)
+	    pj_strcat2(&cfg, "--next-cred\n");
     }
 
-    /* Registrar server */
-    if (pjsua.registrar_uri.slen) {
-	pj_ansi_sprintf(line, "--registrar %.*s\n",
-			      (int)pjsua.registrar_uri.slen,
-			      pjsua.registrar_uri.ptr);
-	pj_strcat2(&cfg, line);
-    }
 
+    pj_strcat2(&cfg, "#\n# Network settings:\n#\n");
 
     /* Outbound proxy */
     if (pjsua.outbound_proxy.slen) {
@@ -644,10 +743,6 @@
 	pj_strcat2(&cfg, line);
     }
 
-    /* Media */
-    if (pjsua.null_audio)
-	pj_strcat2(&cfg, "--null-audio\n");
-
 
     /* Transport. */
     pj_ansi_sprintf(line, "--local-port %d\n", pjsua.sip_port);
@@ -672,6 +767,42 @@
     }
 
 
+    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);
+    }
+
+
+    pj_strcat2(&cfg, "#\n# User agent:\n#\n");
+
+    /* Auto-answer. */
+    if (pjsua.auto_answer != 0) {
+	pj_ansi_sprintf(line, "--auto-answer %d\n",
+			pjsua.auto_answer);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Max calls. */
+    pj_ansi_sprintf(line, "--max-calls %d\n",
+		    pjsua.max_calls);
+    pj_strcat2(&cfg, line);
+
+
+    pj_strcat2(&cfg, "#\n# Buddies:\n#\n");
+
     /* Add buddies. */
     for (i=0; i<pjsua.buddy_cnt; ++i) {
 	pj_ansi_sprintf(line, "--add-buddy %.*s\n",
@@ -681,6 +812,39 @@
     }
 
 
+    *(cfg.ptr + cfg.slen) = '\0';
+    return cfg.slen;
+}
+
+/*
+ * Save settings.
+ */
+pj_status_t pjsua_save_settings(const char *filename)
+{
+    pj_str_t cfg;
+    pj_pool_t *pool;
+    FILE *fhnd;
+
+    /* Create pool for temporary buffer. */
+    pool = pj_pool_create(&pjsua.cp.factory, "settings", 4000, 0, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+
+    cfg.ptr = pj_pool_alloc(pool, 3800);
+    if (!cfg.ptr) {
+	pj_pool_release(pool);
+	return PJ_EBUG;
+    }
+
+
+    cfg.slen = pjsua_dump_settings(cfg.ptr, 3800);
+    if (cfg.slen < 1) {
+	pj_pool_release(pool);
+	return PJ_ENOMEM;
+    }
+
+
     /* Write to file. */
     fhnd = fopen(filename, "wt");
     if (!fhnd) {
diff --git a/pjsip/src/pjsua/pjsua_pres.c b/pjsip/src/pjsua/pjsua_pres.c
index cba02b5..b203a2c 100644
--- a/pjsip/src/pjsua/pjsua_pres.c
+++ b/pjsip/src/pjsua/pjsua_pres.c
@@ -79,6 +79,7 @@
  */
 static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
 {
+    int acc_index;
     pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
     pjsua_srv_pres *uapres;
     pjsip_evsub *sub;
@@ -93,9 +94,13 @@
 
     /* Incoming SUBSCRIBE: */
 
+    /* Find which account for the incoming request. */
+    acc_index = pjsua_find_account_for_incoming(rdata);
+
     /* Create UAS dialog: */
     status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, 
-				   &pjsua.contact_uri, &dlg);
+				   &pjsua.acc[acc_index].contact_uri, 
+				   &dlg);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, 
 		     "Unable to create UAS dialog for subscription", 
@@ -130,7 +135,7 @@
     pjsip_evsub_set_mod_data(sub, pjsua.mod.id, uapres);
 
     /* Add server subscription to the list: */
-    pj_list_push_back(&pjsua.pres_srv_list, uapres);
+    pj_list_push_back(&pjsua.acc[acc_index].pres_srv_list, uapres);
 
 
     /* Create and send 200 (OK) to the SUBSCRIBE request: */
@@ -146,7 +151,7 @@
     /* Set our online status: */
     pj_memset(&pres_status, 0, sizeof(pres_status));
     pres_status.info_cnt = 1;
-    pres_status.info[0].basic_open = pjsua.online_status;
+    pres_status.info[0].basic_open = pjsua.acc[acc_index].online_status;
     //Both pjsua.local_uri and pjsua.contact_uri are enclosed in "<" and ">"
     //causing XML parsing to fail.
     //pres_status.info[0].contact = pjsua.local_uri;
@@ -174,20 +179,20 @@
 
 
 /* Refresh subscription (e.g. when our online status has changed) */
-static void refresh_server_subscription()
+static void refresh_server_subscription(int acc_index)
 {
     pjsua_srv_pres *uapres;
 
-    uapres = pjsua.pres_srv_list.next;
+    uapres = pjsua.acc[acc_index].pres_srv_list.next;
 
-    while (uapres != &pjsua.pres_srv_list) {
+    while (uapres != &pjsua.acc[acc_index].pres_srv_list) {
 	
 	pjsip_pres_status pres_status;
 	pjsip_tx_data *tdata;
 
 	pjsip_pres_get_status(uapres->sub, &pres_status);
-	if (pres_status.info[0].basic_open != pjsua.online_status) {
-	    pres_status.info[0].basic_open = pjsua.online_status;
+	if (pres_status.info[0].basic_open != pjsua.acc[acc_index].online_status) {
+	    pres_status.info[0].basic_open = pjsua.acc[acc_index].online_status;
 	    pjsip_pres_set_status(uapres->sub, &pres_status);
 
 	    if (pjsua.quit_flag) {
@@ -298,13 +303,16 @@
 /* It does what it says.. */
 static void subscribe_buddy_presence(unsigned index)
 {
+    int acc_index;
     pjsip_dialog *dlg;
     pjsip_tx_data *tdata;
     pj_status_t status;
 
+    acc_index = pjsua.buddies[index].acc_index;
+
     status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
-				   &pjsua.local_uri,
-				   &pjsua.contact_uri,
+				   &pjsua.acc[acc_index].local_uri,
+				   &pjsua.acc[acc_index].contact_uri,
 				   &pjsua.buddies[index].uri,
 				   NULL, &dlg);
     if (status != PJ_SUCCESS) {
@@ -325,7 +333,7 @@
     pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id,
 			     &pjsua.buddies[index]);
 
-    status = pjsip_pres_initiate(pjsua.buddies[index].sub, 60, &tdata);
+    status = pjsip_pres_initiate(pjsua.buddies[index].sub, -1, &tdata);
     if (status != PJ_SUCCESS) {
 	pjsua.buddies[index].sub = NULL;
 	pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE", 
@@ -381,7 +389,7 @@
 /* It does what it says.. */
 static void refresh_client_subscription(void)
 {
-    unsigned i;
+    int i;
 
     for (i=0; i<pjsua.buddy_cnt; ++i) {
 
@@ -415,10 +423,10 @@
 /*
  * Refresh presence
  */
-void pjsua_pres_refresh(void)
+void pjsua_pres_refresh(int acc_index)
 {
     refresh_client_subscription();
-    refresh_server_subscription();
+    refresh_server_subscription(acc_index);
 }
 
 
@@ -427,13 +435,20 @@
  */
 void pjsua_pres_shutdown(void)
 {
-    unsigned i;
+    int acc_index;
+    int i;
 
-    pjsua.online_status = 0;
+    for (acc_index=0; acc_index<pjsua.acc_cnt; ++acc_index) {
+	pjsua.acc[acc_index].online_status = 0;
+    }
+
     for (i=0; i<pjsua.buddy_cnt; ++i) {
 	pjsua.buddies[i].monitor = 0;
     }
-    pjsua_pres_refresh();
+
+    for (acc_index=0; acc_index<pjsua.acc_cnt; ++acc_index) {
+	pjsua_pres_refresh(acc_index);
+    }
 }
 
 /*
@@ -441,22 +456,30 @@
  */
 void pjsua_pres_dump(void)
 {
-    unsigned i;
+    int acc_index;
+    int i;
 
     PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
-    if (pj_list_empty(&pjsua.pres_srv_list)) {
-	PJ_LOG(3,(THIS_FILE, "  - none - "));
-    } else {
-	struct pjsua_srv_pres *uapres;
+    for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) {
 
-	uapres = pjsua.pres_srv_list.next;
-	while (uapres != &pjsua.pres_srv_list) {
-	
-	    PJ_LOG(3,(THIS_FILE, "  %10s %s",
-		      pjsip_evsub_get_state_name(uapres->sub),
-		      uapres->remote));
+	PJ_LOG(3,(THIS_FILE, "  %.*s",
+		  (int)pjsua.acc[acc_index].local_uri.slen,
+		  pjsua.acc[acc_index].local_uri.ptr));
 
-	    uapres = uapres->next;
+	if (pj_list_empty(&pjsua.acc[acc_index].pres_srv_list)) {
+	    PJ_LOG(3,(THIS_FILE, "  - none - "));
+	} else {
+	    struct pjsua_srv_pres *uapres;
+
+	    uapres = pjsua.acc[acc_index].pres_srv_list.next;
+	    while (uapres != &pjsua.acc[acc_index].pres_srv_list) {
+	    
+		PJ_LOG(3,(THIS_FILE, "    %10s %s",
+			  pjsip_evsub_get_state_name(uapres->sub),
+			  uapres->remote));
+
+		uapres = uapres->next;
+	    }
 	}
     }
 
diff --git a/pjsip/src/pjsua/pjsua_reg.c b/pjsip/src/pjsua/pjsua_reg.c
index f11ff3f..006bc73 100644
--- a/pjsip/src/pjsua/pjsua_reg.c
+++ b/pjsip/src/pjsua/pjsua_reg.c
@@ -34,66 +34,81 @@
  */
 static void regc_cb(struct pjsip_regc_cbparam *param)
 {
+
+    pjsua_acc *acc = param->token;
+
     /*
      * Print registration status.
      */
     if (param->status!=PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "SIP registration error", 
 		     param->status);
-	pjsua.regc = NULL;
+	pjsip_regc_destroy(acc->regc);
+	acc->regc = NULL;
 	
     } else if (param->code < 0 || param->code >= 300) {
 	PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)", 
 		   param->code, 
 		   pjsip_get_status_text(param->code)->ptr));
-	pjsua.regc = NULL;
+	pjsip_regc_destroy(acc->regc);
+	acc->regc = NULL;
 
     } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
-	PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), "
-			      "will re-register in %d seconds", 
-			      param->code,
-			      pjsip_get_status_text(param->code)->ptr,
-			      param->expiration));
+
+	if (param->expiration < 1) {
+	    pjsip_regc_destroy(acc->regc);
+	    acc->regc = NULL;
+	    PJ_LOG(3,(THIS_FILE, "%s: unregistration success",
+		      acc->local_uri.ptr));
+	} else {
+	    PJ_LOG(3, (THIS_FILE, 
+		       "%s: registration success, status=%d (%s), "
+		       "will re-register in %d seconds", 
+		       acc->local_uri.ptr,
+		       param->code,
+		       pjsip_get_status_text(param->code)->ptr,
+		       param->expiration));
+	}
 
     } else {
 	PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code));
     }
 
-    pjsua.regc_last_err = param->status;
-    pjsua.regc_last_code = param->code;
+    acc->reg_last_err = param->status;
+    acc->reg_last_code = param->code;
 
-    pjsua_ui_regc_on_state_changed(pjsua.regc_last_code);
+    pjsua_ui_regc_on_state_changed(acc->index);
 }
 
 
 /*
  * Update registration. If renew is false, then unregistration will be performed.
  */
-void pjsua_regc_update(pj_bool_t renew)
+void pjsua_regc_update(int acc_index, pj_bool_t renew)
 {
     pj_status_t status;
     pjsip_tx_data *tdata;
 
     if (renew) {
-	if (pjsua.regc == NULL) {
-	    status = pjsua_regc_init();
+	if (pjsua.acc[acc_index].regc == NULL) {
+	    status = pjsua_regc_init(acc_index);
 	    if (status != PJ_SUCCESS) {
 		pjsua_perror(THIS_FILE, "Unable to create registration", 
 			     status);
 		return;
 	    }
 	}
-	status = pjsip_regc_register(pjsua.regc, 1, &tdata);
+	status = pjsip_regc_register(pjsua.acc[acc_index].regc, 1, &tdata);
     } else {
-	if (pjsua.regc == NULL) {
+	if (pjsua.acc[acc_index].regc == NULL) {
 	    PJ_LOG(3,(THIS_FILE, "Currently not registered"));
 	    return;
 	}
-	status = pjsip_regc_unregister(pjsua.regc, &tdata);
+	status = pjsip_regc_unregister(pjsua.acc[acc_index].regc, &tdata);
     }
 
     if (status == PJ_SUCCESS)
-	status = pjsip_regc_send( pjsua.regc, tdata );
+	status = pjsip_regc_send( pjsua.acc[acc_index].regc, tdata );
 
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create/send REGISTER", 
@@ -107,14 +122,17 @@
 /*
  * Initialize client registration.
  */
-pj_status_t pjsua_regc_init(void)
+pj_status_t pjsua_regc_init(int acc_index)
 {
     pj_status_t status;
 
     /* initialize SIP registration if registrar is configured */
-    if (pjsua.registrar_uri.slen) {
+    if (pjsua.acc[acc_index].reg_uri.slen) {
 
-	status = pjsip_regc_create( pjsua.endpt, NULL, &regc_cb, &pjsua.regc);
+	status = pjsip_regc_create( pjsua.endpt, 
+				    &pjsua.acc[acc_index], 
+				    &regc_cb, 
+				    &pjsua.acc[acc_index].regc);
 
 	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, "Unable to create client registration", 
@@ -123,11 +141,12 @@
 	}
 
 
-	status = pjsip_regc_init( pjsua.regc, &pjsua.registrar_uri, 
-				  &pjsua.local_uri, 
-				  &pjsua.local_uri,
-				  1, &pjsua.contact_uri, 
-				  pjsua.reg_timeout);
+	status = pjsip_regc_init( pjsua.acc[acc_index].regc, 
+				  &pjsua.acc[acc_index].reg_uri, 
+				  &pjsua.acc[acc_index].local_uri, 
+				  &pjsua.acc[acc_index].local_uri,
+				  1, &pjsua.acc[acc_index].contact_uri, 
+				  pjsua.acc[acc_index].reg_timeout);
 	if (status != PJ_SUCCESS) {
 	    pjsua_perror(THIS_FILE, 
 			 "Client registration initialization error", 
@@ -135,10 +154,12 @@
 	    return status;
 	}
 
-	pjsip_regc_set_credentials( pjsua.regc, pjsua.cred_count, 
+	pjsip_regc_set_credentials( pjsua.acc[acc_index].regc, 
+				    pjsua.cred_count, 
 				    pjsua.cred_info );
 
-	pjsip_regc_set_route_set( pjsua.regc, &pjsua.route_set );
+	pjsip_regc_set_route_set( pjsua.acc[acc_index].regc, 
+				  &pjsua.acc[acc_index].route_set );
     }
 
     return PJ_SUCCESS;