Index: /pjproject/trunk/pjlib/src/pj/config.c
===================================================================
--- /pjproject/trunk/pjlib/src/pj/config.c	(revision 502)
+++ /pjproject/trunk/pjlib/src/pj/config.c	(revision 503)
@@ -22,5 +22,5 @@
 
 static const char *id = "config.c";
-const char *PJ_VERSION = "0.5.5.6";
+const char *PJ_VERSION = "0.5.6.0";
 
 PJ_DEF(void) pj_dump_config(void)
Index: /pjproject/trunk/pjsip-apps/build/activex-pjsua.dsp
===================================================================
--- /pjproject/trunk/pjsip-apps/build/activex-pjsua.dsp	(revision 502)
+++ /pjproject/trunk/pjsip-apps/build/activex-pjsua.dsp	(revision 503)
@@ -324,5 +324,5 @@
 # Begin Source File
 
-SOURCE="..\src\activex-pjsua\pjsua.h"
+SOURCE="..\src\activex-pjsua\pjsua-structs.h"
 # End Source File
 # Begin Source File
Index: /pjproject/trunk/pjsip-apps/build/pjsua.dsp
===================================================================
--- /pjproject/trunk/pjsip-apps/build/pjsua.dsp	(revision 502)
+++ /pjproject/trunk/pjsip-apps/build/pjsua.dsp	(revision 503)
@@ -93,4 +93,8 @@
 SOURCE=..\src\pjsua\main.c
 # End Source File
+# Begin Source File
+
+SOURCE=..\src\pjsua\pjsua.c
+# End Source File
 # End Group
 # Begin Group "Header Files"
Index: /pjproject/trunk/pjsip-apps/src/pjsua/main.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/main.c	(revision 502)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/main.c	(revision 503)
@@ -18,60 +18,22 @@
  */
 #include <pjsua-lib/pjsua.h>
-#include <pjsua-lib/pjsua_console_app.h>
-
 
 #define THIS_FILE	"main.c"
 
-/*****************************************************************************
- * main():
+
+/*
+ * These are defined in pjsua.c.
  */
+pj_status_t app_init(int argc, char *argv[]);
+pj_status_t app_main(void);
+pj_status_t app_destroy(void);
+
 int main(int argc, char *argv[])
 {
-    pjsua_config cfg;
-    pj_str_t uri_to_call = { NULL, 0 };
-
-    /* Init default settings. */
-    pjsua_default_config(&cfg);
-
-
-    /* Create PJLIB and memory pool */
-    pjsua_create();
-
-
-    /* Parse command line arguments: */
-    if (pjsua_parse_args(argc, argv, &cfg, &uri_to_call) != PJ_SUCCESS)
+    if (app_init(argc, argv) != PJ_SUCCESS)
 	return 1;
 
-
-    /* Init pjsua */
-    if (pjsua_init(&cfg, &console_callback) != PJ_SUCCESS)
-	return 1;
-
-
-    /* Start pjsua! */
-    if (pjsua_start() != PJ_SUCCESS) {
-	pjsua_destroy();
-	return 1;
-    }
-
-
-    /* Sleep for a while, let any messages get printed to console: */
-    pj_thread_sleep(500);
-
-
-    /* Start UI console main loop: */
-    pjsua_console_app_main(&uri_to_call);
-
-
-    /* Destroy pjsua: */
-    pjsua_destroy();
-
-    /* This is for internal testing, to make sure that pjsua_destroy()
-     * can be called multiple times. 
-     */
-    pjsua_destroy();
-
-
-    /* Exit... */
+    app_main();
+    app_destroy();
 
     return 0;
Index: /pjproject/trunk/pjsip-apps/src/pjsua/pjsua.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/pjsua/pjsua.c	(revision 503)
+++ /pjproject/trunk/pjsip-apps/src/pjsua/pjsua.c	(revision 503)
@@ -0,0 +1,2143 @@
+/* $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>
+
+
+#define THIS_FILE	"pjsua.c"
+
+
+
+/* Pjsua application data */
+static struct app_config
+{
+    pjsua_config	    cfg;
+    pjsua_logging_config    log_cfg;
+    pjsua_media_config	    media_cfg;
+    pjsua_transport_config  udp_cfg;
+    pjsua_transport_config  rtp_cfg;
+
+    unsigned		    acc_cnt;
+    pjsua_acc_config	    acc_cfg[PJSUA_MAX_ACC];
+
+    unsigned		    buddy_cnt;
+    pjsua_buddy_config	    buddy_cfg[PJSUA_MAX_BUDDIES];
+
+    pj_pool_t		   *pool;
+    /* Compatibility with older pjsua */
+
+    unsigned		    codec_cnt;
+    pj_str_t		    codec_arg[32];
+    unsigned		    clock_rate;
+    pj_bool_t		    null_audio;
+    pj_str_t		    wav_file;
+    pjsua_player_id	    wav_id;
+    pjsua_conf_port_id	    wav_port;
+    pj_bool_t		    auto_play;
+    pj_bool_t		    auto_loop;
+    unsigned		    ptime;
+    unsigned		    quality;
+    unsigned		    complexity;
+    unsigned		    auto_answer;
+    unsigned		    duration;
+} app_config;
+
+
+static pjsua_acc_id	current_acc;
+static pjsua_call_id	current_call;
+static pj_str_t		uri_arg;
+
+/*****************************************************************************
+ * Configuration manipulation
+ */
+
+/* Show usage */
+static void usage(void)
+{
+    puts  ("Usage:");
+    puts  ("  pjsua [options]");
+    puts  ("");
+    puts  ("General options:");
+    puts  ("  --help              Display this help screen");
+    puts  ("  --version           Display version info");
+    puts  ("");
+    puts  ("Logging options:");
+    puts  ("  --config-file=file  Read the config/arguments from file.");
+    puts  ("  --log-file=fname    Log to filename (default stderr)");
+    puts  ("  --log-level=N       Set log max level to N (0(none) to 6(trace)) (default=5)");
+    puts  ("  --app-log-level=N   Set log max level for stdout display (default=4)");
+    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       Optionally override the Contact information");
+    puts  ("  --proxy=url         Optional URL of proxy server to visit");
+    puts  ("                      May be specified multiple times");
+    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  ("Transport Options:");
+    puts  ("  --local-port=port   Set TCP/UDP port");
+    puts  ("  --outbound=url      Set the URL of global outbound proxy server");
+    puts  ("                      May be specified multiple times");
+    puts  ("  --use-stun1=host[:port]");
+    puts  ("  --use-stun2=host[:port]  Resolve local IP with the specified STUN servers");
+    puts  ("");
+    puts  ("Media Options:");
+    puts  ("  --add-codec=name    Manually add codec (default is to enable all)");
+    puts  ("  --clock-rate=N      Override sound device clock rate");
+    puts  ("  --null-audio        Use NULL audio device");
+    puts  ("  --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  ("  --rtp-port=N        Base port to try for RTP (default=4000)");
+    puts  ("  --complexity=N      Specify encoding complexity (0-10, default=none(-1))");
+    puts  ("  --quality=N         Specify encoding quality (0-10, default=4)");
+    puts  ("  --ptime=MSEC        Override codec ptime to MSEC (default=specific)");
+    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  ("  --max-calls=N       Maximum number of concurrent calls (default:4, max:255)");
+    puts  ("  --duration=SEC      Set maximum call duration (default:no limit)");
+    puts  ("");
+    fflush(stdout);
+}
+
+
+/* Set default config. */
+static void default_config(struct app_config *cfg)
+{
+    pjsua_config_default(&cfg->cfg);
+    pjsua_logging_config_default(&cfg->log_cfg);
+    pjsua_media_config_default(&cfg->media_cfg);
+    pjsua_transport_config_default(&cfg->udp_cfg);
+    cfg->udp_cfg.port = 5060;
+    pjsua_transport_config_default(&cfg->rtp_cfg);
+    cfg->rtp_cfg.port = 4000;
+    cfg->duration = (unsigned)-1;
+    cfg->wav_id = PJSUA_INVALID_ID;
+    cfg->wav_port = PJSUA_INVALID_ID;
+}
+
+
+/*
+ * Read command arguments from config file.
+ */
+static int read_config_file(pj_pool_t *pool, const char *filename, 
+			    int *app_argc, char ***app_argv)
+{
+    int i;
+    FILE *fhnd;
+    char line[200];
+    int argc = 0;
+    char **argv;
+    enum { MAX_ARGS = 64 };
+
+    /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
+    argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
+    argv[argc++] = *app_argv[0];
+
+    /* Open config file. */
+    fhnd = fopen(filename, "rt");
+    if (!fhnd) {
+	PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
+	fflush(stdout);
+	return -1;
+    }
+
+    /* Scan tokens in the file. */
+    while (argc < MAX_ARGS && !feof(fhnd)) {
+	char *token, *p = line;
+
+	if (fgets(line, sizeof(line), fhnd) == NULL) break;
+
+	for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS; 
+	     token = strtok(NULL, " \t\r\n"))
+	{
+	    int token_len;
+	    
+	    if (!token) break;
+	    if (*token == '#') break;
+
+	    token_len = strlen(token);
+	    if (!token_len)
+		continue;
+	    argv[argc] = pj_pool_alloc(pool, token_len+1);
+	    pj_memcpy(argv[argc], token, token_len+1);
+	    ++argc;
+	}
+    }
+
+    /* Copy arguments from command line */
+    for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
+	argv[argc++] = (*app_argv)[i];
+
+    if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
+	PJ_LOG(1,(THIS_FILE, 
+		  "Too many arguments specified in cmd line/config file"));
+	fflush(stdout);
+	fclose(fhnd);
+	return -1;
+    }
+
+    fclose(fhnd);
+
+    /* Assign the new command line back to the original command line. */
+    *app_argc = argc;
+    *app_argv = argv;
+    return 0;
+
+}
+
+static int my_atoi(const char *cs)
+{
+    pj_str_t s;
+    return pj_strtoul(pj_cstr(&s, cs));
+}
+
+
+/* Parse arguments. */
+static pj_status_t parse_args(int argc, char *argv[],
+			      struct app_config *cfg,
+			      pj_str_t *uri_to_call)
+{
+    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_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR,
+	   OPT_REG_TIMEOUT, OPT_ID, OPT_CONTACT, 
+	   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_LOOP,
+	   OPT_AUTO_CONF, OPT_CLOCK_RATE,
+	   OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC,
+	   OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME,
+	   OPT_NEXT_ACCOUNT, OPT_MAX_CALLS, 
+	   OPT_DURATION,
+    };
+    struct pj_getopt_option long_options[] = {
+	{ "config-file",1, 0, OPT_CONFIG_FILE},
+	{ "log-file",	1, 0, OPT_LOG_FILE},
+	{ "log-level",	1, 0, OPT_LOG_LEVEL},
+	{ "app-log-level",1,0,OPT_APP_LOG_LEVEL},
+	{ "help",	0, 0, OPT_HELP},
+	{ "version",	0, 0, OPT_VERSION},
+	{ "clock-rate",	1, 0, OPT_CLOCK_RATE},
+	{ "null-audio", 0, 0, OPT_NULL_AUDIO},
+	{ "local-port", 1, 0, OPT_LOCAL_PORT},
+	{ "proxy",	1, 0, OPT_PROXY},
+	{ "outbound",	1, 0, OPT_OUTBOUND_PROXY},
+	{ "registrar",	1, 0, OPT_REGISTRAR},
+	{ "reg-timeout",1, 0, OPT_REG_TIMEOUT},
+	{ "id",		1, 0, OPT_ID},
+	{ "contact",	1, 0, OPT_CONTACT},
+	{ "realm",	1, 0, OPT_REALM},
+	{ "username",	1, 0, OPT_USERNAME},
+	{ "password",	1, 0, OPT_PASSWORD},
+	{ "use-stun1",  1, 0, OPT_USE_STUN1},
+	{ "use-stun2",  1, 0, OPT_USE_STUN2},
+	{ "add-buddy",  1, 0, OPT_ADD_BUDDY},
+	{ "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
+	{ "no-presence", 0, 0, OPT_NO_PRESENCE},
+	{ "auto-answer",1, 0, OPT_AUTO_ANSWER},
+	{ "auto-hangup",1, 0, OPT_AUTO_HANGUP},
+	{ "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},
+	{ "rtp-port",	1, 0, OPT_RTP_PORT},
+	{ "add-codec",  1, 0, OPT_ADD_CODEC},
+	{ "complexity",	1, 0, OPT_COMPLEXITY},
+	{ "quality",	1, 0, OPT_QUALITY},
+	{ "ptime",      1, 0, OPT_PTIME},
+	{ "next-account",0,0, OPT_NEXT_ACCOUNT},
+	{ "max-calls",	1, 0, OPT_MAX_CALLS},
+	{ "duration",1,0, OPT_DURATION},
+	{ NULL, 0, 0, 0}
+    };
+    pj_status_t status;
+    pjsua_acc_config *cur_acc;
+    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) 
+    {
+	switch (c) {
+	case OPT_CONFIG_FILE:
+	    config_file = pj_optarg;
+	    break;
+	}
+	if (config_file)
+	    break;
+    }
+
+    if (config_file) {
+	status = read_config_file(app_config.pool, config_file, &argc, &argv);
+	if (status != 0)
+	    return status;
+    }
+
+    cfg->acc_cnt = 0;
+    cur_acc = &cfg->acc_cfg[0];
+
+
+    /* Reinitialize and re-run pj_getopt again, possibly with new arguments
+     * read from config file.
+     */
+    pj_optind = 0;
+    while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
+	char *p;
+	pj_str_t tmp;
+	long lval;
+
+	switch (c) {
+
+	case OPT_LOG_FILE:
+	    cfg->log_cfg.log_filename = pj_str(pj_optarg);
+	    break;
+
+	case OPT_LOG_LEVEL:
+	    c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
+	    if (c < 0 || c > 6) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: expecting integer value 0-6 "
+			  "for --log-level"));
+		return PJ_EINVAL;
+	    }
+	    cfg->log_cfg.level = c;
+	    pj_log_set_level( c );
+	    break;
+
+	case OPT_APP_LOG_LEVEL:
+	    cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
+	    if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: expecting integer value 0-6 "
+			  "for --app-log-level"));
+		return PJ_EINVAL;
+	    }
+	    break;
+
+	case OPT_HELP:
+	    usage();
+	    return PJ_EINVAL;
+
+	case OPT_VERSION:   /* version */
+	    pj_dump_config();
+	    return PJ_EINVAL;
+
+	case OPT_NULL_AUDIO:
+	    cfg->null_audio = PJ_TRUE;
+	    break;
+
+	case OPT_CLOCK_RATE:
+	    lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
+	    if (lval < 8000 || lval > 48000) {
+		PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
+				     "8000-48000 for clock rate"));
+		return PJ_EINVAL;
+	    }
+	    cfg->media_cfg.clock_rate = lval; 
+	    break;
+
+	case OPT_LOCAL_PORT:   /* local-port */
+	    lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
+	    if (lval < 1 || lval > 65535) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: expecting integer value for "
+			  "--local-port"));
+		return PJ_EINVAL;
+	    }
+	    cfg->udp_cfg.port = (pj_uint16_t)lval;
+	    break;
+
+	case OPT_PROXY:   /* proxy */
+	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: invalid SIP URL '%s' "
+			  "in proxy argument", pj_optarg));
+		return PJ_EINVAL;
+	    }
+	    cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
+	    break;
+
+	case OPT_OUTBOUND_PROXY:   /* outbound proxy */
+	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: invalid SIP URL '%s' "
+			  "in outbound proxy argument", pj_optarg));
+		return PJ_EINVAL;
+	    }
+	    cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
+	    break;
+
+	case OPT_REGISTRAR:   /* registrar */
+	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: invalid SIP URL '%s' in "
+			  "registrar argument", pj_optarg));
+		return PJ_EINVAL;
+	    }
+	    cur_acc->reg_uri = pj_str(pj_optarg);
+	    break;
+
+	case OPT_REG_TIMEOUT:   /* reg-timeout */
+	    cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
+	    if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: invalid value for --reg-timeout "
+			  "(expecting 1-3600)"));
+		return PJ_EINVAL;
+	    }
+	    break;
+
+	case OPT_ID:   /* id */
+	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: invalid SIP URL '%s' "
+			  "in local id argument", pj_optarg));
+		return PJ_EINVAL;
+	    }
+	    cur_acc->id = pj_str(pj_optarg);
+	    break;
+
+	case OPT_CONTACT:   /* contact */
+	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: invalid SIP URL '%s' "
+			  "in contact argument", pj_optarg));
+		return PJ_EINVAL;
+	    }
+	    cur_acc->contact = pj_str(pj_optarg);
+	    break;
+
+	case OPT_NEXT_ACCOUNT: /* Add more account. */
+	    cfg->acc_cnt++;
+	    cur_acc = &cfg->acc_cfg[cfg->acc_cnt - 1];
+	    break;
+
+	case OPT_USERNAME:   /* Default authentication user */
+	    cur_acc->cred_count = 1;
+	    cur_acc->cred_info[0].username = pj_str(pj_optarg);
+	    break;
+
+	case OPT_REALM:	    /* Default authentication realm. */
+	    cur_acc->cred_count = 1;
+	    cur_acc->cred_info[0].realm = pj_str(pj_optarg);
+	    break;
+
+	case OPT_PASSWORD:   /* authentication password */
+	    cur_acc->cred_count = 1;
+	    cur_acc->cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+	    cur_acc->cred_info[0].data = pj_str(pj_optarg);
+	    break;
+
+	case OPT_USE_STUN1:   /* STUN server 1 */
+	    p = pj_ansi_strchr(pj_optarg, ':');
+	    if (p) {
+		*p = '\0';
+		cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
+		cfg->udp_cfg.stun_config.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1));
+		if (cfg->udp_cfg.stun_config.stun_port1 < 1 || cfg->udp_cfg.stun_config.stun_port1 > 65535) {
+		    PJ_LOG(1,(THIS_FILE, 
+			      "Error: expecting port number with "
+			      "option --use-stun1"));
+		    return PJ_EINVAL;
+		}
+	    } else {
+		cfg->udp_cfg.stun_config.stun_port1 = 3478;
+		cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
+	    }
+	    cfg->udp_cfg.use_stun = PJ_TRUE;
+	    break;
+
+	case OPT_USE_STUN2:   /* STUN server 2 */
+	    p = pj_ansi_strchr(pj_optarg, ':');
+	    if (p) {
+		*p = '\0';
+		cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
+		cfg->udp_cfg.stun_config.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1));
+		if (cfg->udp_cfg.stun_config.stun_port2 < 1 || cfg->udp_cfg.stun_config.stun_port2 > 65535) {
+		    PJ_LOG(1,(THIS_FILE, 
+			      "Error: expecting port number with "
+			      "option --use-stun2"));
+		    return PJ_EINVAL;
+		}
+	    } else {
+		cfg->udp_cfg.stun_config.stun_port2 = 3478;
+		cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
+	    }
+	    break;
+
+	case OPT_ADD_BUDDY: /* Add to buddy list. */
+	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: invalid URL '%s' in "
+			  "--add-buddy option", pj_optarg));
+		return -1;
+	    }
+	    if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
+		PJ_LOG(1,(THIS_FILE, 
+			  "Error: too many buddies in buddy list."));
+		return -1;
+	    }
+	    cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
+	    cfg->buddy_cnt++;
+	    break;
+
+	case OPT_AUTO_PLAY:
+	    cfg->auto_play = 1;
+	    break;
+
+	case OPT_AUTO_LOOP:
+	    cfg->auto_loop = 1;
+	    break;
+
+	case OPT_PLAY_FILE:
+	    cfg->wav_file = pj_str(pj_optarg);
+	    break;
+
+	case OPT_RTP_PORT:
+	    cfg->rtp_cfg.port = my_atoi(pj_optarg);
+	    if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
+		PJ_LOG(1,(THIS_FILE,
+			  "Error: rtp-port argument value "
+			  "(expecting 1-65535"));
+		return -1;
+	    }
+	    break;
+
+	case OPT_ADD_CODEC:
+	    cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
+	    break;
+
+	case OPT_COMPLEXITY:
+	    cfg->complexity = my_atoi(pj_optarg);
+	    if (cfg->complexity < 0 || cfg->complexity > 10) {
+		PJ_LOG(1,(THIS_FILE,
+			  "Error: invalid --complexity (expecting 0-10"));
+		return -1;
+	    }
+	    break;
+
+	case OPT_QUALITY:
+	    cfg->quality = my_atoi(pj_optarg);
+	    if (cfg->quality < 0 || cfg->quality > 10) {
+		PJ_LOG(1,(THIS_FILE,
+			  "Error: invalid --quality (expecting 0-10"));
+		return -1;
+	    }
+	    break;
+
+	case OPT_PTIME:
+	    cfg->ptime = my_atoi(pj_optarg);
+	    if (cfg->ptime < 10 || cfg->ptime > 1000) {
+		PJ_LOG(1,(THIS_FILE,
+			  "Error: invalid --ptime option"));
+		return -1;
+	    }
+	    break;
+
+	case OPT_AUTO_ANSWER:
+	    cfg->auto_answer = my_atoi(pj_optarg);
+	    if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
+		PJ_LOG(1,(THIS_FILE,
+			  "Error: invalid code in --auto-answer "
+			  "(expecting 100-699"));
+		return -1;
+	    }
+	    break;
+
+	case OPT_MAX_CALLS:
+	    cfg->cfg.max_calls = my_atoi(pj_optarg);
+	    if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > 255) {
+		PJ_LOG(1,(THIS_FILE,"Too many calls for max-calls (1-255)"));
+		return -1;
+	    }
+	    break;
+
+	case OPT_DURATION:
+	    cfg->duration = my_atoi(pj_optarg);
+	    break;
+	}
+    }
+
+    if (pj_optind != argc) {
+	pj_str_t uri_arg;
+
+	if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
+	    PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
+	    return -1;
+	}
+	uri_arg = pj_str(argv[pj_optind]);
+	if (uri_to_call)
+	    *uri_to_call = uri_arg;
+	pj_optind++;
+
+	/* Add URI to call to buddy list if it's not already there */
+	for (i=0; i<cfg->buddy_cnt; ++i) {
+	    if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
+		break;
+	}
+	if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
+	    cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
+	}
+
+    } else {
+	if (uri_to_call)
+	    uri_to_call->slen = 0;
+    }
+
+    if (pj_optind != argc) {
+	PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
+	return PJ_EINVAL;
+    }
+
+    if (cfg->acc_cfg[0].id.slen && cfg->acc_cnt==0)
+	cfg->acc_cnt = 1;
+
+    for (i=0; i<cfg->acc_cnt; ++i) {
+	if (cfg->acc_cfg[i].cred_info[0].username.slen ||
+	    cfg->acc_cfg[i].cred_info[0].realm.slen)
+	{
+	    cfg->acc_cfg[i].cred_count = 1;
+	    cfg->acc_cfg[i].cred_info[0].scheme = pj_str("digest");
+	}
+    }
+
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Save account settings
+ */
+static void write_account_settings(int acc_index, pj_str_t *result)
+{
+    unsigned i;
+    char line[128];
+    pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
+
+    
+    pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
+    pj_strcat2(result, line);
+
+
+    /* Identity */
+    if (acc_cfg->id.slen) {
+	pj_ansi_sprintf(line, "--id %.*s\n", 
+			(int)acc_cfg->id.slen, 
+			acc_cfg->id.ptr);
+	pj_strcat2(result, line);
+    }
+
+    /* Registrar server */
+    if (acc_cfg->reg_uri.slen) {
+	pj_ansi_sprintf(line, "--registrar %.*s\n",
+			      (int)acc_cfg->reg_uri.slen,
+			      acc_cfg->reg_uri.ptr);
+	pj_strcat2(result, line);
+
+	pj_ansi_sprintf(line, "--reg-timeout %u\n",
+			      acc_cfg->reg_timeout);
+	pj_strcat2(result, line);
+    }
+
+    /* Contact */
+    if (acc_cfg->contact.slen) {
+	pj_ansi_sprintf(line, "--contact %.*s\n", 
+			(int)acc_cfg->contact.slen, 
+			acc_cfg->contact.ptr);
+	pj_strcat2(result, line);
+    }
+
+    /* Proxy */
+    for (i=0; i<acc_cfg->proxy_cnt; ++i) {
+	pj_ansi_sprintf(line, "--proxy %.*s\n",
+			      (int)acc_cfg->proxy[i].slen,
+			      acc_cfg->proxy[i].ptr);
+	pj_strcat2(result, line);
+    }
+
+    /* Credentials */
+    for (i=0; i<acc_cfg->cred_count; ++i) {
+	if (acc_cfg->cred_info[i].realm.slen) {
+	    pj_ansi_sprintf(line, "--realm %.*s\n",
+				  (int)acc_cfg->cred_info[i].realm.slen,
+				  acc_cfg->cred_info[i].realm.ptr);
+	    pj_strcat2(result, line);
+	}
+
+	if (acc_cfg->cred_info[i].username.slen) {
+	    pj_ansi_sprintf(line, "--username %.*s\n",
+				  (int)acc_cfg->cred_info[i].username.slen,
+				  acc_cfg->cred_info[i].username.ptr);
+	    pj_strcat2(result, line);
+	}
+
+	if (acc_cfg->cred_info[i].data.slen) {
+	    pj_ansi_sprintf(line, "--password %.*s\n",
+				  (int)acc_cfg->cred_info[i].data.slen,
+				  acc_cfg->cred_info[i].data.ptr);
+	    pj_strcat2(result, line);
+	}
+    }
+
+}
+
+
+/*
+ * Write settings.
+ */
+static int write_settings(const struct app_config *config,
+			  char *buf, pj_size_t max)
+{
+    unsigned acc_index;
+    unsigned i;
+    pj_str_t cfg;
+    char line[128];
+
+    PJ_UNUSED_ARG(max);
+
+    cfg.ptr = buf;
+    cfg.slen = 0;
+
+    /* Logging. */
+    pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
+    pj_ansi_sprintf(line, "--log-level %d\n",
+		    config->log_cfg.level);
+    pj_strcat2(&cfg, line);
+
+    pj_ansi_sprintf(line, "--app-log-level %d\n",
+		    config->log_cfg.console_level);
+    pj_strcat2(&cfg, line);
+
+    if (config->log_cfg.log_filename.slen) {
+	pj_ansi_sprintf(line, "--log-file %.*s\n",
+			(int)config->log_cfg.log_filename.slen,
+			config->log_cfg.log_filename.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    /* Save account settings. */
+    for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
+	
+	write_account_settings(acc_index, &cfg);
+
+	if (acc_index < config->acc_cnt-1)
+	    pj_strcat2(&cfg, "--next-account\n");
+    }
+
+
+    pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
+
+    /* Outbound proxy */
+    for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
+	pj_ansi_sprintf(line, "--outbound %.*s\n",
+			      (int)config->cfg.outbound_proxy[i].slen,
+			      config->cfg.outbound_proxy[i].ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    /* UDP Transport. */
+    pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
+    pj_strcat2(&cfg, line);
+
+
+    /* STUN */
+    if (config->udp_cfg.stun_config.stun_port1) {
+	pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
+			(int)config->udp_cfg.stun_config.stun_srv1.slen, 
+			config->udp_cfg.stun_config.stun_srv1.ptr, 
+			config->udp_cfg.stun_config.stun_port1);
+	pj_strcat2(&cfg, line);
+    }
+
+    if (config->udp_cfg.stun_config.stun_port2) {
+	pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
+			(int)config->udp_cfg.stun_config.stun_srv2.slen, 
+			config->udp_cfg.stun_config.stun_srv2.ptr, 
+			config->udp_cfg.stun_config.stun_port2);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    pj_strcat2(&cfg, "\n#\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->wav_file.slen) {
+	pj_ansi_sprintf(line, "--play-file %s\n",
+			config->wav_file.ptr);
+	pj_strcat2(&cfg, line);
+    }
+    /* Media clock rate. */
+    if (config->clock_rate) {
+	pj_ansi_sprintf(line, "--clock-rate %d\n",
+			config->clock_rate);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    /* Encoding quality and complexity */
+    if (config->quality > 0) {
+	pj_ansi_sprintf(line, "--quality %d\n",
+			config->quality);
+	pj_strcat2(&cfg, line);
+    }
+    if (config->complexity > 0) {
+	pj_ansi_sprintf(line, "--complexity %d\n",
+			config->complexity);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* ptime */
+    if (config->ptime) {
+	pj_ansi_sprintf(line, "--ptime %d\n",
+			config->ptime);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Start RTP port. */
+    pj_ansi_sprintf(line, "--rtp-port %d\n",
+		    config->rtp_cfg.port);
+    pj_strcat2(&cfg, line);
+
+    /* Add codec. */
+    for (i=0; i<config->codec_cnt; ++i) {
+	pj_ansi_sprintf(line, "--add-codec %s\n",
+		    config->codec_arg[i].ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+    pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
+
+    /* Auto-answer. */
+    if (config->auto_answer != 0) {
+	pj_ansi_sprintf(line, "--auto-answer %d\n",
+			config->auto_answer);
+	pj_strcat2(&cfg, line);
+    }
+
+    /* Max calls. */
+    pj_ansi_sprintf(line, "--max-calls %d\n",
+		    config->cfg.max_calls);
+    pj_strcat2(&cfg, line);
+
+    /* Uas-duration. */
+    if (config->duration != (unsigned)-1) {
+	pj_ansi_sprintf(line, "--duration %d\n",
+			config->duration);
+	pj_strcat2(&cfg, line);
+    }
+
+    pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
+
+    /* Add buddies. */
+    for (i=0; i<config->buddy_cnt; ++i) {
+	pj_ansi_sprintf(line, "--add-buddy %.*s\n",
+			      (int)config->buddy_cfg[i].uri.slen,
+			      config->buddy_cfg[i].uri.ptr);
+	pj_strcat2(&cfg, line);
+    }
+
+
+    *(cfg.ptr + cfg.slen) = '\0';
+    return cfg.slen;
+}
+
+
+/*
+ * Dump application states.
+ */
+static void app_dump(pj_bool_t detail)
+{
+    unsigned old_decor;
+    char buf[1024];
+
+    PJ_LOG(3,(THIS_FILE, "Start dumping application states:"));
+
+    old_decor = pj_log_get_decor();
+    pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
+
+    if (detail)
+	pj_dump_config();
+
+    pjsip_endpt_dump(pjsua_get_pjsip_endpt(), detail);
+    pjmedia_endpt_dump(pjsua_get_pjmedia_endpt());
+    pjsip_tsx_layer_dump(detail);
+    pjsip_ua_dump(detail);
+
+
+    /* Dump all invite sessions: */
+    PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
+
+    if (pjsua_call_get_count() == 0) {
+
+	PJ_LOG(3,(THIS_FILE, "  - no sessions -"));
+
+    } else {
+	unsigned i;
+
+	for (i=0; i<app_config.cfg.max_calls; ++i) {
+	    if (pjsua_call_is_active(i)) {
+		pjsua_call_dump(i, detail, buf, sizeof(buf), "  ");
+		PJ_LOG(3,(THIS_FILE, "%s", buf));
+	    }
+	}
+    }
+
+    /* Dump presence status */
+    pjsua_pres_dump(detail);
+
+    pj_log_set_decor(old_decor);
+    PJ_LOG(3,(THIS_FILE, "Dump complete"));
+}
+
+
+/*****************************************************************************
+ * Console application
+ */
+
+/*
+ * Find next call when current call is disconnected or when user
+ * press ']'
+ */
+static pj_bool_t find_next_call(void)
+{
+    int i, max;
+
+    max = pjsua_call_get_max_count();
+    for (i=current_call+1; i<max; ++i) {
+	if (pjsua_call_is_active(i)) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    for (i=0; i<current_call; ++i) {
+	if (pjsua_call_is_active(i)) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    current_call = PJSUA_INVALID_ID;
+    return PJ_FALSE;
+}
+
+
+/*
+ * Find previous call when user press '['
+ */
+static pj_bool_t find_prev_call(void)
+{
+    int i, max;
+
+    max = pjsua_call_get_max_count();
+    for (i=current_call-1; i>=0; --i) {
+	if (pjsua_call_is_active(i)) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    for (i=max-1; i>current_call; --i) {
+	if (pjsua_call_is_active(i)) {
+	    current_call = i;
+	    return PJ_TRUE;
+	}
+    }
+
+    current_call = PJSUA_INVALID_ID;
+    return PJ_FALSE;
+}
+
+
+
+/*
+ * Handler when invite state has changed.
+ */
+static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
+{
+    pjsua_call_info call_info;
+
+    PJ_UNUSED_ARG(e);
+
+    pjsua_call_get_info(call_id, &call_info);
+
+    if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
+
+	PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", 
+		  call_id,
+		  call_info.last_status,
+		  call_info.last_status_text.ptr));
+
+	if (call_id == current_call) {
+	    find_next_call();
+	}
+
+    } else {
+
+	PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", 
+		  call_id,
+		  call_info.state_text.ptr));
+
+	if (current_call==PJSUA_INVALID_ID)
+	    current_call = call_id;
+
+    }
+}
+
+
+/**
+ * Handler when there is incoming call.
+ */
+static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
+			     pjsip_rx_data *rdata)
+{
+    pjsua_call_info call_info;
+
+    PJ_UNUSED_ARG(acc_id);
+    PJ_UNUSED_ARG(rdata);
+
+    pjsua_call_get_info(call_id, &call_info);
+
+    if (app_config.auto_answer > 0) {
+	pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL);
+    }
+
+    if (app_config.auto_answer < 200) {
+	PJ_LOG(3,(THIS_FILE,
+		  "Incoming call for account %d!\n"
+		  "From: %s\n"
+		  "To: %s\n"
+		  "Press a to answer or h to reject call",
+		  acc_id,
+		  call_info.remote_info.ptr,
+		  call_info.local_info.ptr));
+    }
+}
+
+
+/*
+ * Callback on media state changed event.
+ * The action may connect the call to sound device, to file, or
+ * to loop the call.
+ */
+static void on_call_media_state(pjsua_call_id call_id)
+{
+    pjsua_call_info call_info;
+
+    pjsua_call_get_info(call_id, &call_info);
+
+    if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
+	pj_bool_t connect_sound = PJ_TRUE;
+
+	/* Loopback sound, if desired */
+	if (app_config.auto_loop) {
+	    pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
+	    connect_sound = PJ_FALSE;
+	}
+
+	/* Stream a file, if desired */
+	if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) {
+	    pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
+	    connect_sound = PJ_FALSE;
+	}
+
+	/* Otherwise connect to sound device */
+	if (connect_sound) {
+	    pjsua_conf_connect(call_info.conf_slot, 0);
+	    pjsua_conf_connect(0, call_info.conf_slot);
+	}
+
+	PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
+
+    } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) {
+	PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
+		  call_id));
+    } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) {
+	PJ_LOG(3,(THIS_FILE, 
+		  "Media for call %d is suspended (hold) by remote",
+		  call_id));
+    } else {
+	PJ_LOG(3,(THIS_FILE, 
+		  "Media for call %d is inactive",
+		  call_id));
+    }
+}
+
+
+/*
+ * Handler registration status has changed.
+ */
+static void on_reg_state(pjsua_acc_id acc_id)
+{
+    PJ_UNUSED_ARG(acc_id);
+
+    // Log already written.
+}
+
+
+/*
+ * Handler on buddy state changed.
+ */
+static void on_buddy_state(pjsua_buddy_id buddy_id)
+{
+    pjsua_buddy_info info;
+    pjsua_buddy_get_info(buddy_id, &info);
+
+    PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
+	      (int)info.uri.slen,
+	      info.uri.ptr,
+	      (int)info.status_text.slen,
+	      info.status_text.ptr));
+}
+
+
+/**
+ * Incoming IM message (i.e. MESSAGE request)!
+ */
+static void on_pager(pjsua_call_id call_id, const pj_str_t *from, 
+		     const pj_str_t *to, const pj_str_t *contact,
+		     const pj_str_t *mime_type, const pj_str_t *text)
+{
+    /* Note: call index may be -1 */
+    PJ_UNUSED_ARG(call_id);
+    PJ_UNUSED_ARG(to);
+    PJ_UNUSED_ARG(contact);
+    PJ_UNUSED_ARG(mime_type);
+
+    PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s",
+	      (int)from->slen, from->ptr,
+	      (int)text->slen, text->ptr));
+}
+
+
+/**
+ * Received typing indication
+ */
+static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
+		      const pj_str_t *to, const pj_str_t *contact,
+		      pj_bool_t is_typing)
+{
+    PJ_UNUSED_ARG(call_id);
+    PJ_UNUSED_ARG(to);
+    PJ_UNUSED_ARG(contact);
+
+    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)
+{
+    pjsua_buddy_id ids[64];
+    int i;
+    unsigned count = PJ_ARRAY_SIZE(ids);
+
+    puts("Buddy list:");
+
+    pjsua_enum_buddies(ids, &count);
+
+    if (count == 0)
+	puts(" -none-");
+    else {
+	for (i=0; i<(int)count; ++i) {
+	    pjsua_buddy_info info;
+
+	    if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
+		continue;
+
+	    printf(" [%2d] <%7s>  %.*s\n", 
+		    ids[i]+1, info.status_text.ptr, 
+		    (int)info.uri.slen,
+		    info.uri.ptr);
+	}
+    }
+    puts("");
+}
+
+
+/*
+ * Print account status.
+ */
+static void print_acc_status(int acc_id)
+{
+    char buf[80];
+    pjsua_acc_info info;
+
+    pjsua_acc_get_info(acc_id, &info);
+
+    if (!info.has_registration) {
+	pj_ansi_snprintf(buf, sizeof(buf), "%.*s", 
+			 (int)info.status_text.slen,
+			 info.status_text.ptr);
+
+    } else {
+	pj_ansi_snprintf(buf, sizeof(buf),
+			 "%d/%.*s (expires=%d)",
+			 info.status,
+			 (int)info.status_text.slen,
+			 info.status_text.ptr,
+			 info.expires);
+
+    }
+
+    printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
+	   acc_id,  (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
+    printf("       Online status: %s\n", 
+	   (info.online_status ? "Online" : "Invisible"));
+}
+
+
+/*
+ * Show a bit of help.
+ */
+static void keystroke_help(void)
+{
+    pjsua_acc_id acc_ids[16];
+    unsigned count = PJ_ARRAY_SIZE(acc_ids);
+    int i;
+
+    printf(">>>>\n");
+
+    pjsua_enum_accs(acc_ids, &count);
+
+    printf("Account list:\n");
+    for (i=0; i<(int)count; ++i)
+	print_acc_status(acc_ids[i]);
+
+    print_buddy_list();
+    
+    //puts("Commands:");
+    puts("+=============================================================================+");
+    puts("|       Call Commands:         |   Buddy, IM & Presence:  |     Account:      |");
+    puts("|                              |                          |                   |");
+    puts("|  m  Make new call            | +b  Add new buddy       .| +a  Add new accnt |");
+    puts("|  M  Make multiple calls      | -b  Delete buddy         | -a  Delete accnt. |");
+    puts("|  a  Answer call              | !b  Modify buddy         | !a  Modify accnt. |");
+    puts("|  h  Hangup call  (ha=all)    |  i  Send IM              | rr  (Re-)register |");
+    puts("|  H  Hold call                |  s  Subscribe presence   | ru  Unregister    |");
+    puts("|  v  re-inVite (release hold) |  u  Unsubscribe presence |  >  Cycle next ac.|");
+    puts("|  ]  Select next dialog       |  t  ToGgle Online status |  <  Cycle prev ac.|");
+    puts("|  [  Select previous dialog   +--------------------------+-------------------+");
+    puts("|  x  Xfer call                |      Media Commands:     |  Status & Config: |");
+    puts("|  #  Send DTMF string         |                          |                   |");
+    puts("|                              | cl  List ports           |  d  Dump status   |");
+    puts("|                              | cc  Connect port         | dd  Dump detailed |");
+    puts("|                              | cd  Disconnect port      | dc  Dump config   |");
+    puts("|                              |                          |  f  Save 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_get_buddy_count(), pjsua_get_buddy_count());
+    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 = my_atoi(buf);
+
+	if (result->nb_result >= 0 && 
+	    result->nb_result <= (int)pjsua_get_buddy_count()) 
+	{
+	    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;
+    }
+}
+
+/*
+ * List the ports in conference bridge
+ */
+static void conf_list(void)
+{
+    unsigned i, count;
+    pjsua_conf_port_id id[PJSUA_MAX_CALLS];
+
+    printf("Conference ports:\n");
+
+    count = PJ_ARRAY_SIZE(id);
+    pjsua_enum_conf_ports(id, &count);
+
+    for (i=0; i<count; ++i) {
+	char txlist[PJSUA_MAX_CALLS*4+10];
+	unsigned j;
+	pjsua_conf_port_info info;
+
+	pjsua_conf_get_port_info(id[i], &info);
+
+	txlist[0] = '\0';
+	for (j=0; j<info.listener_cnt; ++j) {
+	    char s[10];
+	    pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
+	    pj_ansi_strcat(txlist, s);
+	}
+	printf("Port #%02d[%2dKHz/%dms] %20.*s  transmitting to: %s\n", 
+	       info.slot_id, 
+	       info.clock_rate/1000,
+	       info.samples_per_frame * 1000 / info.clock_rate,
+	       (int)info.name.slen, 
+	       info.name.ptr,
+	       txlist);
+
+    }
+    puts("");
+}
+
+
+/*
+ * Main "user interface" loop.
+ */
+void console_app_main(const pj_str_t *uri_to_call)
+{
+    char menuin[10];
+    char buf[128];
+    char text[128];
+    int i, count;
+    char *uri;
+    pj_str_t tmp;
+    struct input_result result;
+    pjsua_call_info call_info;
+    pjsua_acc_info acc_info;
+
+
+    /* If user specifies URI to call, then call the URI */
+    if (uri_to_call->slen) {
+	pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, 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_get_count());
+	    
+	    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 {
+		    pjsua_buddy_info binfo;
+		    pjsua_buddy_get_info(result.nb_result-1, &binfo);
+		    uri = binfo.uri.ptr;
+		}
+
+	    } else if (result.uri_result) {
+		uri = result.uri_result;
+	    }
+	    
+	    tmp = pj_str(uri);
+	    pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL);
+	    break;
+
+	case 'M':
+	    /* Make multiple calls! : */
+	    printf("(You currently have %d calls)\n", 
+		   pjsua_call_get_count());
+	    
+	    if (!simple_input("Number of calls", menuin, sizeof(menuin)))
+		continue;
+
+	    count = my_atoi(menuin);
+	    if (count < 1)
+		continue;
+
+	    ui_input_url("Make call", buf, sizeof(buf), &result);
+	    if (result.nb_result != NO_NB) {
+		pjsua_buddy_info binfo;
+		if (result.nb_result == -1 || result.nb_result == 0) {
+		    puts("You can't do that with make call!");
+		    continue;
+		}
+		pjsua_buddy_get_info(result.nb_result-1, &binfo);
+		uri = binfo.uri.ptr;
+	    } else {
+		uri =  result.uri_result;
+	    }
+
+	    for (i=0; i<my_atoi(menuin); ++i) {
+		pj_status_t status;
+	    
+		tmp = pj_str(uri);
+		status = pjsua_call_make_call(current_acc, &tmp, 0, NULL,
+					      NULL, 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 {
+		    pjsua_buddy_info binfo;
+		    pjsua_buddy_get_info(result.nb_result-1, &binfo);
+		    uri = binfo.uri.ptr;
+		}
+
+	    } else if (result.uri_result) {
+		uri = result.uri_result;
+	    }
+	    
+
+	    /* Send typing indication. */
+	    if (i != -1)
+		pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
+	    else {
+		pj_str_t tmp_uri = pj_str(uri);
+		pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
+	    }
+
+	    /* 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_send_typing_ind(i, PJ_FALSE, NULL);
+		else {
+		    pj_str_t tmp_uri = pj_str(uri);
+		    pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
+		}
+		continue;
+	    }
+
+	    tmp = pj_str(text);
+
+	    /* Send the IM */
+	    if (i != -1)
+		pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
+	    else {
+		pj_str_t tmp_uri = pj_str(uri);
+		pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
+	    }
+
+	    break;
+
+	case 'a':
+
+	    if (current_call != -1) {
+		pjsua_call_get_info(current_call, &call_info);
+	    } else {
+		/* Make compiler happy */
+		call_info.role = PJSIP_ROLE_UAC;
+		call_info.state = PJSIP_INV_STATE_DISCONNECTED;
+	    }
+
+	    if (current_call == -1 || 
+		call_info.role != PJSIP_ROLE_UAS ||
+		call_info.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 (my_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, my_atoi(buf), NULL, NULL);
+	    }
+
+	    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, 0, NULL, NULL);
+	    }
+	    break;
+
+	case ']':
+	case '[':
+	    /*
+	     * Cycle next/prev dialog.
+	     */
+	    if (menuin[0] == ']') {
+		find_next_call();
+
+	    } else {
+		find_prev_call();
+	    }
+
+	    if (current_call != -1) {
+		
+		pjsua_call_get_info(current_call, &call_info);
+		PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s", 
+			  (int)call_info.remote_info.slen, 
+			  call_info.remote_info.ptr));
+
+	    } else {
+		PJ_LOG(3,(THIS_FILE,"No current dialog"));
+	    }
+	    break;
+
+
+	case '>':
+	case '<':
+	    if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
+		break;
+
+	    i = my_atoi(buf);
+	    if (pjsua_acc_is_valid(i)) {
+		current_acc = i;
+		PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
+	    } else {
+		PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
+	    }
+	    break;
+
+
+	case '+':
+	    if (menuin[1] == 'b') {
+	    
+		pjsua_buddy_config buddy_cfg;
+		pjsua_buddy_id buddy_id;
+		pj_status_t status;
+
+		if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
+		    break;
+
+		if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) {
+		    printf("Invalid SIP URI '%s'\n", buf);
+		    break;
+		}
+
+		pj_memset(&buddy_cfg, 0, sizeof(pjsua_buddy_config));
+
+		buddy_cfg.uri = pj_str(buf);
+		buddy_cfg.subscribe = PJ_TRUE;
+
+		status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
+		if (status == PJ_SUCCESS) {
+		    printf("New buddy '%s' added at index %d\n",
+			   buf, buddy_id+1);
+		}
+
+	    } else if (menuin[1] == 'a') {
+
+		printf("Sorry, this command is not supported yet\n");
+
+	    } else {
+		printf("Invalid input %s\n", menuin);
+	    }
+	    break;
+
+	case '-':
+	    if (menuin[1] == 'b') {
+		if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
+		    break;
+
+		i = my_atoi(buf) - 1;
+
+		if (!pjsua_buddy_is_valid(i)) {
+		    printf("Invalid buddy id %d\n", i);
+		} else {
+		    pjsua_buddy_del(i);
+		    printf("Buddy %d deleted\n", i);
+		}
+
+	    } else if (menuin[1] == 'a') {
+
+		if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
+		    break;
+
+		i = my_atoi(buf);
+
+		if (!pjsua_acc_is_valid(i)) {
+		    printf("Invalid account id %d\n", i);
+		} else {
+		    pjsua_acc_del(i);
+		    printf("Account %d deleted\n", i);
+		}
+
+	    } else {
+		printf("Invalid input %s\n", menuin);
+	    }
+	    break;
+
+	case 'H':
+	    /*
+	     * Hold call.
+	     */
+	    if (current_call != -1) {
+		
+		pjsua_call_set_hold(current_call, NULL);
+
+	    } 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, PJ_TRUE, NULL);
+
+	    } 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_buddy_info binfo;
+			pjsua_buddy_get_info(result.nb_result-1, &binfo);
+			pjsua_call_xfer( current_call, &binfo.uri, NULL);
+		    }
+
+		} else if (result.uri_result) {
+		    pj_str_t tmp;
+		    tmp = pj_str(result.uri_result);
+		    pjsua_call_xfer( current_call, &tmp, NULL);
+		}
+	    }
+	    break;
+
+	case '#':
+	    /*
+	     * Send DTMF strings.
+	     */
+	    if (current_call == -1) {
+		
+		PJ_LOG(3,(THIS_FILE, "No current call"));
+
+	    } else if (!pjsua_call_has_media(current_call)) {
+
+		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 = pjsua_call_dial_dtmf(current_call, &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, count;
+		    count = pjsua_get_buddy_count();
+		    for (i=0; i<count; ++i)
+			pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
+		} else if (result.nb_result == 0) {
+		    puts("Sorry, can only subscribe to buddy's presence, "
+			 "not from existing call");
+		} else {
+		    pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
+		}
+
+	    } 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_acc_set_registration(current_acc, PJ_TRUE);
+		break;
+	    case 'u':
+		/*
+		 * Unregister
+		 */
+		pjsua_acc_set_registration(current_acc, PJ_FALSE);
+		break;
+	    }
+	    break;
+	    
+	case 't':
+	    pjsua_acc_get_info(current_acc, &acc_info);
+	    acc_info.online_status = !acc_info.online_status;
+	    pjsua_acc_set_online_status(current_acc, acc_info.online_status);
+	    printf("Setting %s online status to %s\n",
+		   acc_info.acc_uri.ptr,
+		   (acc_info.online_status?"online":"offline"));
+	    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 = pjsua_conf_connect(my_atoi(src_port), 
+						    my_atoi(dst_port));
+		    } else {
+			status = pjsua_conf_disconnect(my_atoi(src_port), 
+						       my_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 = write_settings(&app_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 {
+		app_dump(menuin[1]=='d');
+	    }
+	    break;
+
+
+	case 'f':
+	    if (simple_input("Enter output filename", buf, sizeof(buf))) {
+		char settings[2000];
+		int len;
+
+		len = write_settings(&app_config, settings, sizeof(settings));
+		if (len < 1)
+		    PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
+		else {
+		    pj_oshandle_t fd;
+		    pj_status_t status;
+
+		    status = pj_file_open(app_config.pool, buf, 
+					  PJ_O_WRONLY, &fd);
+		    if (status != PJ_SUCCESS) {
+			pjsua_perror(THIS_FILE, "Unable to open file", status);
+		    } else {
+			pj_ssize_t size = len;
+			pj_file_write(fd, settings, &size);
+			pj_file_close(fd);
+
+			printf("Settings successfully written to '%s'\n", buf);
+		    }
+		}
+		
+	    }
+	    break;
+
+
+	case 'q':
+	    goto on_exit;
+
+
+	default:
+	    if (menuin[0] != '\n' && menuin[0] != '\r') {
+		printf("Invalid input %s", menuin);
+	    }
+	    keystroke_help();
+	    break;
+	}
+    }
+
+on_exit:
+    ;
+}
+
+
+/*****************************************************************************
+ * Public API
+ */
+
+pj_status_t app_init(int argc, char *argv[])
+{
+    pjsua_transport_id transport_id;
+    unsigned i;
+    pj_status_t status;
+
+    /* Create pjsua */
+    status = pjsua_create();
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Create pool for application */
+    app_config.pool = pjsua_pool_create("pjsua", 4000, 4000);
+
+    /* Initialize default config */
+    default_config(&app_config);
+    
+    /* Parse the arguments */
+    status = parse_args(argc, argv, &app_config, &uri_arg);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Copy udp_cfg STUN config to rtp_cfg */
+    app_config.rtp_cfg.use_stun = app_config.udp_cfg.use_stun;
+    app_config.rtp_cfg.stun_config = app_config.udp_cfg.stun_config;
+
+
+    /* Initialize application callbacks */
+    app_config.cfg.cb.on_call_state = &on_call_state;
+    app_config.cfg.cb.on_call_media_state = &on_call_media_state;
+    app_config.cfg.cb.on_incoming_call = &on_incoming_call;
+    app_config.cfg.cb.on_reg_state = &on_reg_state;
+    app_config.cfg.cb.on_buddy_state = &on_buddy_state;
+    app_config.cfg.cb.on_pager = &on_pager;
+    app_config.cfg.cb.on_typing = &on_typing;
+
+    /* Initialize pjsua */
+    status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
+			&app_config.media_cfg);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Optionally registers WAV file */
+    if (app_config.wav_file.slen) {
+	status = pjsua_player_create(&app_config.wav_file, 0, NULL,
+				     &app_config.wav_id);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+
+    /* Add UDP transport */
+    status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
+				    &app_config.udp_cfg, 
+				    &transport_id);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Add local account */
+    pjsua_acc_add_local(transport_id, PJ_TRUE, &current_acc);
+    pjsua_acc_set_online_status(current_acc, PJ_TRUE);
+
+
+    /* Add accounts */
+    for (i=0; i<app_config.acc_cnt; ++i) {
+	status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, &current_acc);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+	pjsua_acc_set_online_status(current_acc, PJ_TRUE);
+    }
+
+    /* Add buddies */
+    for (i=0; i<app_config.buddy_cnt; ++i) {
+	status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+
+    /* Optionally set codec orders */
+    for (i=0; i<app_config.codec_cnt; ++i) {
+	pjsua_codec_set_priority(&app_config.codec_arg[i],
+				 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
+    }
+
+    /* Add RTP transports */
+    status = pjsua_media_transports_create(&app_config.rtp_cfg);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    return PJ_SUCCESS;
+
+on_error:
+    pjsua_destroy();
+    return status;
+}
+
+
+pj_status_t app_main(void)
+{
+    pj_status_t status;
+
+    /* Start pjsua */
+    status = pjsua_start();
+    if (status != PJ_SUCCESS) {
+	pjsua_destroy();
+	return status;
+    }
+
+    console_app_main(&uri_arg);
+
+    return PJ_SUCCESS;
+}
+
+pj_status_t app_destroy(void)
+{
+    if (app_config.pool) {
+	pj_pool_release(app_config.pool);
+	app_config.pool = NULL;
+    }
+
+    return pjsua_destroy();
+}
Index: /pjproject/trunk/pjsip/build/pjsua_lib.dsp
===================================================================
--- /pjproject/trunk/pjsip/build/pjsua_lib.dsp	(revision 502)
+++ /pjproject/trunk/pjsip/build/pjsua_lib.dsp	(revision 503)
@@ -88,9 +88,9 @@
 # Begin Source File
 
-SOURCE="..\src\pjsua-lib\pjsua_call.c"
+SOURCE="..\src\pjsua-lib\pjsua_acc.c"
 # End Source File
 # Begin Source File
 
-SOURCE="..\src\pjsua-lib\pjsua_console_app.c"
+SOURCE="..\src\pjsua-lib\pjsua_call.c"
 # End Source File
 # Begin Source File
@@ -104,17 +104,9 @@
 # Begin Source File
 
-SOURCE="..\src\pjsua-lib\pjsua_imp.h"
+SOURCE="..\src\pjsua-lib\pjsua_media.c"
 # End Source File
 # Begin Source File
 
 SOURCE="..\src\pjsua-lib\pjsua_pres.c"
-# End Source File
-# Begin Source File
-
-SOURCE="..\src\pjsua-lib\pjsua_reg.c"
-# End Source File
-# Begin Source File
-
-SOURCE="..\src\pjsua-lib\pjsua_settings.c"
 # End Source File
 # End Group
@@ -128,5 +120,5 @@
 # Begin Source File
 
-SOURCE="..\include\pjsua-lib\pjsua_console_app.h"
+SOURCE="..\include\pjsua-lib\pjsua_internal.h"
 # End Source File
 # End Group
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 502)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 503)
@@ -57,214 +57,640 @@
  */
 #ifndef PJSUA_MAX_CALLS
-#   define PJSUA_MAX_CALLS	    256
+#   define PJSUA_MAX_CALLS	    32
 #endif
 
 
 /**
+ * Max ports in the conference bridge.
+ */
+#ifndef PJSUA_MAX_CONF_PORTS
+#   define PJSUA_MAX_CONF_PORTS	    254
+#endif
+
+
+/**
  * Maximum accounts.
  */
 #ifndef PJSUA_MAX_ACC
-#   define PJSUA_MAX_ACC	    32
+#   define PJSUA_MAX_ACC	    8
 #endif
 
 
+/**
+ * Maximum proxies in account.
+ */
+#ifndef PJSUA_ACC_MAX_PROXIES
+#   define PJSUA_ACC_MAX_PROXIES    8
+#endif
+
+/**
+ * Default registration interval.
+ */
+#ifndef PJSUA_REG_INTERVAL
+#   define PJSUA_REG_INTERVAL	    55
+#endif
+
+
+/** Account identification */
 typedef int pjsua_acc_id;
+
+/** Call identification */
+typedef int pjsua_call_id;
+
+/** SIP transport identification */
+typedef int pjsua_transport_id;
+
+/** Buddy identification */
 typedef int pjsua_buddy_id;
+
+/** File player identification */
 typedef int pjsua_player_id;
+
+/** File recorder identification */
 typedef int pjsua_recorder_id;
+
+/** Conference port identification */
 typedef int pjsua_conf_port_id;
 
 
+/** Constant to identify invalid ID for all sorts of IDs. */
+#define PJSUA_INVALID_ID	    (-1)
+
+
+
 /**
  * Account configuration.
  */
-struct pjsua_acc_config
-{
-    /** SIP URL for account ID (mandatory) */
+typedef struct pjsua_acc_config
+{
+    /** 
+     * The full SIP URL for the account. The value can take name address or 
+     * URL format, and will look something like "sip:account@serviceprovider".
+     *
+     * This field is mandatory.
+     */
     pj_str_t	    id;
 
-    /** Registrar URI (mandatory) */
+    /** 
+     * This is the URL to be put in the request URI for the registration,
+     * and will look something like "sip:serviceprovider".
+     *
+     * This field should be specified if registration is desired. If the
+     * value is empty, no account registration will be performed.
+     */
     pj_str_t	    reg_uri;
 
-    /** Optional contact URI */
+    /** 
+     * Optional URI to be put as Contact for this account. It is recommended
+     * that this field is left empty, so that the value will be calculated
+     * automatically based on the transport address.
+     */
     pj_str_t	    contact;
 
-    /** Service proxy (default: none) */
-    pj_str_t	    proxy;
-
-    /** Default timeout (mandatory) */
-    pj_int32_t	    reg_timeout;
-
-    /** Number of credentials. */
+    /**
+     * Number of proxies in the proxy array below.
+     */
+    unsigned	    proxy_cnt;
+
+    /** 
+     * Optional URI of the proxies to be visited for all outgoing requests 
+     * that are using this account (REGISTER, INVITE, etc). Application need 
+     * to specify these proxies if the service provider requires that requests
+     * destined towards its network should go through certain proxies first
+     * (for example, border controllers).
+     *
+     * These proxies will be put in the route set for this account, with 
+     * maintaining the orders (the first proxy in the array will be visited
+     * first).
+     */
+    pj_str_t	    proxy[PJSUA_ACC_MAX_PROXIES];
+
+    /** 
+     * Optional interval for registration, in seconds. If the value is zero, 
+     * default interval will be used (PJSUA_REG_INTERVAL, 55 seconds).
+     */
+    unsigned	    reg_timeout;
+
+    /** 
+     * Number of credentials in the credential array.
+     */
     unsigned	    cred_count;
 
-    /** Array of credentials. */
-    pjsip_cred_info cred_info[4];
-
-};
-
-
-/**
- * @see pjsua_acc_config
- */
-typedef struct pjsua_acc_config pjsua_acc_config;
-
-
-/**
- * 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;
-
-    /**
-     * Enable incoming and outgoing message logging (default: 1).
+    /** 
+     * Array of credentials. If registration is desired, normally there should
+     * be at least one credential specified, to successfully authenticate
+     * against the service provider. More credentials can be specified, for
+     * example when the requests are expected to be challenged by the
+     * proxies in the route set.
+     */
+    pjsip_cred_info cred_info[PJSUA_ACC_MAX_PROXIES];
+
+} pjsua_acc_config;
+
+
+/**
+ * Call this function to initialize account config with default values.
+ *
+ * @param cfg	    The account config to be initialized.
+ */
+PJ_INLINE(void) pjsua_acc_config_default(pjsua_acc_config *cfg)
+{
+    pj_memset(cfg, 0, sizeof(*cfg));
+
+    cfg->reg_timeout = PJSUA_REG_INTERVAL;
+}
+
+
+
+/**
+ * Account info. Application can query account info by calling 
+ * #pjsua_acc_get_info().
+ */
+typedef struct pjsua_acc_info
+{
+    /** 
+     * The account ID. 
+     */
+    pjsua_acc_id	id;
+
+    /**
+     * Flag to indicate whether this is the default account.
+     */
+    pj_bool_t		is_default;
+
+    /** 
+     * Account URI 
+     */
+    pj_str_t		acc_uri;
+
+    /** 
+     * Flag to tell whether this account has registration setting
+     * (reg_uri is not empty).
+     */
+    pj_bool_t		has_registration;
+
+    /**
+     * An up to date expiration interval for account registration session.
+     */
+    int			expires;
+
+    /**
+     * Last registration status code. If status code is zero, the account
+     * is currently not registered. Any other value indicates the SIP
+     * status code of the registration.
+     */
+    pjsip_status_code	status;
+
+    /**
+     * String describing the registration status.
+     */
+    pj_str_t		status_text;
+
+    /**
+     * Presence online status for this account.
+     */
+    pj_bool_t		online_status;
+
+    /**
+     * Buffer that is used internally to store the status text.
+     */
+    char		buf_[PJ_ERR_MSG_SIZE];
+
+} pjsua_acc_info;
+
+
+
+/**
+ * STUN configuration.
+ */
+typedef struct pjsua_stun_config
+{
+    /**
+     * The first STUN server IP address or hostname.
+     */
+    pj_str_t	stun_srv1;
+
+    /**
+     * Port number of the first STUN server.
+     * If zero, default STUN port will be used.
+     */
+    unsigned	stun_port1;
+    
+    /**
+     * Optional second STUN server IP address or hostname, for which the
+     * result of the mapping request will be compared to. If the value
+     * is empty, only one STUN server will be used.
+     */
+    pj_str_t	stun_srv2;
+
+    /**
+     * Port number of the second STUN server.
+     * If zero, default STUN port will be used.
+     */
+    unsigned	stun_port2;
+
+} pjsua_stun_config;
+
+
+
+/**
+ * Call this function to initialize STUN config with default values.
+ *
+ * @param cfg	    The STUN config to be initialized.
+ */
+PJ_INLINE(void) pjsua_stun_config_default(pjsua_stun_config *cfg)
+{
+    pj_memset(cfg, 0, sizeof(*cfg));
+}
+
+
+/**
+ * Transport configuration for creating UDP transports for both SIP
+ * and media.
+ */
+typedef struct pjsua_transport_config
+{
+    /**
+     * UDP port number to bind locally. This setting MUST be specified
+     * even when default port is desired. If the value is zero, the
+     * transport will be bound to any available port, and application
+     * can query the port by querying the transport info.
+     */
+    unsigned		port;
+
+    /**
+     * Optional address where the socket should be bound.
+     */
+    pj_in_addr		ip_addr;
+
+    /**
+     * Flag to indicate whether STUN should be used.
+     */
+    pj_bool_t		use_stun;
+
+    /**
+     * STUN configuration, must be specified when STUN is used.
+     */
+    pjsua_stun_config	stun_config;
+
+} pjsua_transport_config;
+
+
+/**
+ * Call this function to initialize UDP config with default values.
+ *
+ * @param cfg	    The UDP config to be initialized.
+ */
+PJ_INLINE(void) pjsua_transport_config_default(pjsua_transport_config *cfg)
+{
+    pj_memset(cfg, 0, sizeof(*cfg));
+}
+
+
+/**
+ * Normalize STUN config.
+ */
+PJ_INLINE(void) pjsua_normalize_stun_config( pjsua_stun_config *cfg )
+{
+    if (cfg->stun_srv1.slen) {
+
+	if (cfg->stun_port1 == 0)
+	    cfg->stun_port1 = 3478;
+
+	if (cfg->stun_srv2.slen == 0) {
+	    cfg->stun_srv2 = cfg->stun_srv1;
+	    cfg->stun_port2 = cfg->stun_port1;
+	} else {
+	    if (cfg->stun_port2 == 0)
+		cfg->stun_port2 = 3478;
+	}
+
+    } else {
+	cfg->stun_port1 = 0;
+	cfg->stun_srv2.slen = 0;
+	cfg->stun_port2 = 0;
+    }
+}
+
+
+/**
+ * Duplicate transport config.
+ */
+PJ_INLINE(void) pjsua_transport_config_dup(pj_pool_t *pool,
+					   pjsua_transport_config *dst,
+					   const pjsua_transport_config *src)
+{
+    pj_memcpy(dst, src, sizeof(*src));
+
+    if (src->stun_config.stun_srv1.slen) {
+	pj_strdup_with_null(pool, &dst->stun_config.stun_srv1,
+			    &src->stun_config.stun_srv1);
+    }
+
+    if (src->stun_config.stun_srv2.slen) {
+	pj_strdup_with_null(pool, &dst->stun_config.stun_srv2,
+			    &src->stun_config.stun_srv2);
+    }
+
+    pjsua_normalize_stun_config(&dst->stun_config);
+}
+
+
+
+/**
+ * Transport info.
+ */
+typedef struct pjsua_transport_info
+{
+    /**
+     * PJSUA transport identification.
+     */
+    pjsua_transport_id	    id;
+
+    /**
+     * Transport type.
+     */
+    pjsip_transport_type_e  type;
+
+    /**
+     * Transport type name.
+     */
+    pj_str_t		    type_name;
+
+    /**
+     * Transport string info/description.
+     */
+    pj_str_t		    info;
+
+    /**
+     * Transport flag (see ##pjsip_transport_flags_e).
+     */
+    unsigned		    flag;
+
+    /**
+     * Local address length.
+     */
+    unsigned		    addr_len;
+
+    /**
+     * Local/bound address.
+     */
+    pj_sockaddr		    local_addr;
+
+    /**
+     * Published address (or transport address name).
+     */
+    pjsip_host_port	    local_name;
+
+    /**
+     * Current number of objects currently referencing this transport.
+     */
+    unsigned		    usage_count;
+
+
+} pjsua_transport_info;
+
+
+/**
+ * Media configuration.
+ */
+typedef struct pjsua_media_config
+{
+    /**
+     * Clock rate to be applied to the conference bridge.
+     * If value is zero, default clock rate will be used (16KHz).
+     */
+    unsigned		clock_rate;
+
+    /**
+     * Specify maximum number of media ports to be created in the
+     * conference bridge. Since all media terminate in the bridge
+     * (calls, file player, file recorder, etc), the value must be
+     * large enough to support all of them. However, the larger
+     * the value, the more computations are performed.
+     */
+    unsigned		max_media_ports;
+
+    /**
+     * Specify whether the media manager should manage its own
+     * ioqueue for the RTP/RTCP sockets. If yes, ioqueue will be created
+     * and at least one worker thread will be created too. If no,
+     * the RTP/RTCP sockets will share the same ioqueue as SIP sockets,
+     * and no worker thread is needed.
+     *
+     * Normally application would say yes here, unless it wants to
+     * run everything from a single thread.
+     */
+    pj_bool_t		has_ioqueue;
+
+    /**
+     * Specify the number of worker threads to handle incoming RTP
+     * packets. A value of one is recommended for most applications.
+     */
+    unsigned		thread_cnt;
+
+
+} pjsua_media_config;
+
+
+/**
+ * Use this function to initialize media config.
+ *
+ * @param cfg	The media config to be initialized.
+ */
+PJ_INLINE(void) pjsua_media_config_default(pjsua_media_config *cfg)
+{
+    pj_memset(cfg, 0, sizeof(*cfg));
+
+    cfg->clock_rate = 16000;
+    cfg->max_media_ports = 32;
+    cfg->has_ioqueue = PJ_TRUE;
+    cfg->thread_cnt = 1;
+}
+
+
+
+/**
+ * Logging configuration.
+ */
+typedef struct pjsua_logging_config
+{
+    /**
+     * Log incoming and outgoing SIP message? Yes!
      */
     pj_bool_t	msg_logging;
 
-    /** 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 (value >=0, default: 1) */
-    unsigned	thread_cnt;
-
-    /** Separate ioqueue for media? (default: yes) */
-    pj_bool_t	media_has_ioqueue;
-
-    /** Number of worker thread for media (value >=0, default: 1) */
-    unsigned	media_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;
-
-    /** Sound player device ID (default: 0) */
-    unsigned	snd_player_id;
-
-    /** Sound capture device ID (default: 0) */
-    unsigned	snd_capture_id;
-
-    /** 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;
-
-    /** 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) */
+    /**
+     * Input verbosity level. Value 5 is reasonable.
+     */
+    unsigned	level;
+
+    /**
+     * Verbosity level for console. Value 4 is reasonable.
+     */
+    unsigned	console_level;
+
+    /**
+     * Log decoration.
+     */
+    unsigned	decor;
+
+    /**
+     * Optional log filename.
+     */
     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;
-
+    /**
+     * Optional callback function to be called to write log to 
+     * application specific device. This function will be called for
+     * log messages on input verbosity level.
+     */
+    void       (*cb)(int level, const char *data, pj_size_t len);
+
+
+} pjsua_logging_config;
+
+
+/**
+ * Use this function to initialize logging config.
+ *
+ * @param cfg	The logging config to be initialized.
+ */
+PJ_INLINE(void) pjsua_logging_config_default(pjsua_logging_config *cfg)
+{
+    pj_memset(cfg, 0, sizeof(*cfg));
+
+    cfg->msg_logging = PJ_TRUE;
+    cfg->level = 5;
+    cfg->console_level = 4;
+    cfg->decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | 
+		 PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE;
+}
+
+/**
+ * Use this function to duplicate logging config.
+ *
+ * @param pool	    Pool to use.
+ * @param dst	    Destination config.
+ * @param src	    Source config.
+ */
+PJ_INLINE(void) pjsua_logging_config_dup(pj_pool_t *pool,
+					 pjsua_logging_config *dst,
+					 const pjsua_logging_config *src)
+{
+    pj_memcpy(dst, src, sizeof(*src));
+    pj_strdup_with_null(pool, &dst->log_filename, &src->log_filename);
+}
+
+
+/**
+ * Buddy configuration.
+ */
+typedef struct pjsua_buddy_config
+{
+    /**
+     * Buddy URL or name address.
+     */
+    pj_str_t	uri;
+
+    /**
+     * Specify whether presence subscription should start immediately.
+     */
+    pj_bool_t	subscribe;
+
+} pjsua_buddy_config;
+
+
+/**
+ * Buddy's online status.
+ */
+typedef enum pjsua_buddy_status
+{
+    /**
+     * Online status is unknown (possibly because no presence subscription
+     * has been established).
+     */
+    PJSUA_BUDDY_STATUS_UNKNOWN,
+
+    /**
+     * Buddy is known to be offline.
+     */
+    PJSUA_BUDDY_STATUS_ONLINE,
+
+    /**
+     * Buddy is offline.
+     */
+    PJSUA_BUDDY_STATUS_OFFLINE,
+
+} pjsua_buddy_status;
+
+
+
+/**
+ * Buddy info.
+ */
+typedef struct pjsua_buddy_info
+{
+    /**
+     * The buddy ID.
+     */
+    pjsua_buddy_id	id;
+
+    /**
+     * The full URI of the buddy, as specified in the configuration.
+     */
+    pj_str_t		uri;
+
+    /**
+     * Buddy's Contact, only available when presence subscription has
+     * been established to the buddy.
+     */
+    pj_str_t		contact;
+
+    /**
+     * Buddy's online status.
+     */
+    pjsua_buddy_status	status;
+
+    /**
+     * Text to describe buddy's online status.
+     */
+    pj_str_t		status_text;
+
+    /**
+     * Flag to indicate that we should monitor the presence information for
+     * this buddy (normally yes, unless explicitly disabled).
+     */
+    pj_bool_t		monitor_pres;
+
+    /**
+     * Internal buffer.
+     */
+    char		buf_[256];
+
+} pjsua_buddy_info;
+
+
+/**
+ * Codec config.
+ */
+typedef struct pjsua_codec_info
+{
+    /**
+     * Codec unique identification.
+     */
+    pj_str_t		codec_id;
+
+    /**
+     * Codec priority (integer 0-255).
+     */
+    pj_uint8_t		priority;
+
+    /**
+     * Internal buffer.
+     */
+    char		buf_[32];
+
+} pjsua_codec_info;
 
 
@@ -272,5 +698,5 @@
  * Application callbacks.
  */
-struct pjsua_callback
+typedef struct pjsua_callback
 {
     /**
@@ -279,11 +705,18 @@
      * detail call states.
      */
-    void (*on_call_state)(int call_index, pjsip_event *e);
+    void (*on_call_state)(pjsua_call_id call_id, pjsip_event *e);
 
     /**
      * Notify application on incoming call.
      */
-    void (*on_incoming_call)(pjsua_acc_id acc_id, int call_index,
+    void (*on_incoming_call)(pjsua_acc_id acc_id, pjsua_call_id call_id,
 			     pjsip_rx_data *rdata);
+
+    /**
+     * Notify application when media state in the call has changed.
+     * Normal application would need to implement this callback, e.g.
+     * to connect the call's media to sound device.
+     */
+    void (*on_call_media_state)(pjsua_call_id call_id);
 
     /**
@@ -294,5 +727,5 @@
      * transfer.
      */
-    void (*on_call_transfered)(int call_index,
+    void (*on_call_transfered)(pjsua_call_id call_id,
 			       const pj_str_t *dst,
 			       pjsip_status_code *code);
@@ -313,22 +746,157 @@
     /**
      * Notify application on incoming pager (i.e. MESSAGE request).
-     * Argument call_index will be -1 if MESSAGE request is not related to an 
+     * Argument call_id 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);
+    void (*on_pager)(pjsua_call_id call_id, const pj_str_t *from,
+		     const pj_str_t *to, const pj_str_t *contact,
+		     const pj_str_t *mime_type, const pj_str_t *body);
+
+    /**
+     * Notify application about the delivery status of outgoing pager
+     * request.
+     *
+     * @param call_id	    Containts the ID of the call where the IM was
+     *			    sent, or PJSUA_INVALID_ID if the IM was sent
+     *			    outside call context.
+     * @param to	    Destination URI.
+     * @param body	    Message body.
+     * @param user_data	    Arbitrary data that was specified when sending
+     *			    IM message.
+     * @param status	    Delivery status.
+     * @param reason	    Delivery status reason.
+     */
+    void (*on_pager_status)(pjsua_call_id call_id,
+			    const pj_str_t *to,
+			    const pj_str_t *body,
+			    void *user_data,
+			    pjsip_status_code status,
+			    const pj_str_t *reason);
 
     /**
      * Notify application 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;
+    void (*on_typing)(pjsua_call_id call_id, const pj_str_t *from,
+		      const pj_str_t *to, const pj_str_t *contact,
+		      pj_bool_t is_typing);
+
+} pjsua_callback;
+
+
+
+
+/**
+ * PJSUA settings.
+ */
+typedef struct pjsua_config
+{
+
+    /** 
+     * Maximum calls to support (default: 4) 
+     */
+    unsigned	    max_calls;
+
+    /** 
+     * Number of worker threads. Normally application will want to have at
+     * least one worker thread, unless when it wants to poll the library
+     * periodically, which in this case the worker thread can be set to
+     * zero.
+     */
+    unsigned	    thread_cnt;
+
+    /**
+     * Number of outbound proxies in the array.
+     */
+    unsigned	    outbound_proxy_cnt;
+
+    /** 
+     * Specify the URL of outbound proxies to visit for all outgoing requests.
+     * The outbound proxies will be used for all accounts, and it will
+     * be used to build the route set for outgoing requests. The final
+     * route set for outgoing requests will consists of the outbound proxies
+     * and the proxy configured in the account.
+     */
+    pj_str_t	    outbound_proxy[4];
+
+    /** 
+     * Number of credentials in the credential array.
+     */
+    unsigned	    cred_count;
+
+    /** 
+     * Array of credentials. These credentials will be used by all accounts,
+     * and can be used to authenticate against outbound proxies.
+     */
+    pjsip_cred_info cred_info[PJSUA_ACC_MAX_PROXIES];
+
+    /**
+     * Application callback.
+     */
+    pjsua_callback  cb;
+
+} pjsua_config;
+
+
+/**
+ * Use this function to initialize pjsua config.
+ *
+ * @param cfg	pjsua config to be initialized.
+ */
+PJ_INLINE(void) pjsua_config_default(pjsua_config *cfg)
+{
+    pj_memset(cfg, 0, sizeof(*cfg));
+
+    cfg->max_calls = 4;
+    cfg->thread_cnt = 1;
+}
+
+
+/**
+ * Duplicate credential.
+ */
+PJ_INLINE(void) pjsip_cred_dup( pj_pool_t *pool,
+				pjsip_cred_info *dst,
+				const pjsip_cred_info *src)
+{
+    pj_strdup_with_null(pool, &dst->realm, &src->realm);
+    pj_strdup_with_null(pool, &dst->scheme, &src->scheme);
+    pj_strdup_with_null(pool, &dst->username, &src->username);
+    pj_strdup_with_null(pool, &dst->data, &src->data);
+
+}
+
+
+/**
+ * Duplicate pjsua_config.
+ */
+PJ_INLINE(void) pjsua_config_dup(pj_pool_t *pool,
+				 pjsua_config *dst,
+				 const pjsua_config *src)
+{
+    unsigned i;
+
+    pj_memcpy(dst, src, sizeof(*src));
+
+    for (i=0; i<src->outbound_proxy_cnt; ++i) {
+	pj_strdup_with_null(pool, &dst->outbound_proxy[i],
+			    &src->outbound_proxy[i]);
+    }
+
+    for (i=0; i<src->cred_count; ++i) {
+	pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]);
+    }
+}
+
+
+/**
+ * Call media status.
+ */
+typedef enum pjsua_call_media_status
+{
+    PJSUA_CALL_MEDIA_NONE,
+    PJSUA_CALL_MEDIA_ACTIVE,
+    PJSUA_CALL_MEDIA_LOCAL_HOLD,
+    PJSUA_CALL_MEDIA_REMOTE_HOLD,
+} pjsua_call_media_status;
 
 
@@ -336,112 +904,173 @@
  * Call info.
  */
-struct pjsua_call_info
-{
-    unsigned		index;
-    pj_bool_t		active;
+typedef struct pjsua_call_info
+{
+    /** Call identification. */
+    pjsua_call_id	id;
+
+    /** Initial call role (UAC == caller) */
     pjsip_role_e	role;
+
+    /** Local URI */
     pj_str_t		local_info;
+
+    /** Local Contact */
+    pj_str_t		local_contact;
+
+    /** Remote URI */
     pj_str_t		remote_info;
+
+    /** Remote contact */
+    pj_str_t		remote_contact;
+
+    /** Dialog Call-ID string. */
+    pj_str_t		call_id;
+
+    /** Call state */
     pjsip_inv_state	state;
+
+    /** Text describing the state */
     pj_str_t		state_text;
+
+    /** Last status code heard, which can be used as cause code */
     pjsip_status_code	last_status;
+
+    /** The reason phrase describing the status. */
     pj_str_t		last_status_text;
+
+    /** Call media status. */
+    pjsua_call_media_status media_status;
+
+    /** Media direction */
+    pjmedia_dir		media_dir;
+
+    /** The conference port number for the call */
+    pjsua_conf_port_id	conf_slot;
+
+    /** Up-to-date call connected duration (zero when call is not 
+     *  established)
+     */
     pj_time_val		connect_duration;
+
+    /** Total call duration, including set-up time */
     pj_time_val		total_duration;
-    pj_bool_t		has_media;
-    pjsua_conf_port_id	conf_slot;
-};
-
-typedef struct pjsua_call_info pjsua_call_info;
-
-
-enum pjsua_buddy_status
-{
-    PJSUA_BUDDY_STATUS_UNKNOWN,
-    PJSUA_BUDDY_STATUS_ONLINE,
-    PJSUA_BUDDY_STATUS_OFFLINE,
-};
-
-typedef enum pjsua_buddy_status pjsua_buddy_status;
-
-
-/**
- * Buddy info.
- */
-struct pjsua_buddy_info
-{
-    pjsua_buddy_id	index;
-    pj_bool_t		is_valid;
+
+    /** Internal */
+    struct {
+	char	local_info[128];
+	char	local_contact[128];
+	char	remote_info[128];
+	char	remote_contact[128];
+	char	call_id[128];
+	char	last_status_text[128];
+    } buf_;
+
+} pjsua_call_info;
+
+
+
+
+/**
+ * Conference port info.
+ */
+typedef struct pjsua_conf_port_info
+{
+    /** Conference port number. */
+    pjsua_conf_port_id	slot_id;
+
+    /** Port name. */
     pj_str_t		name;
-    pj_str_t		display_name;
-    pj_str_t		host;
-    unsigned		port;
-    pj_str_t		uri;
-    pjsua_buddy_status	status;
-    pj_str_t		status_text;
-    pj_bool_t		monitor;
-};
-
-typedef struct pjsua_buddy_info pjsua_buddy_info;
-
-
-/**
- * Account info.
- */
-struct pjsua_acc_info
-{
-    pjsua_acc_id	index;
-    pj_str_t		acc_id;
-    pj_bool_t		has_registration;
-    int			expires;
-    pjsip_status_code	status;
-    pj_str_t		status_text;
-    pj_bool_t		online_status;
-    char		buf[PJ_ERR_MSG_SIZE];
-};
-
-typedef struct pjsua_acc_info pjsua_acc_info;
-
-
-/**
- * Conference port info.
- */
-struct pjsua_conf_port_info
-{
-    pjsua_conf_port_id	slot_id;
-    pj_str_t		name;
+
+    /** Clock rate. */
     unsigned		clock_rate;
+
+    /** Number of channels. */
     unsigned		channel_count;
+
+    /** Samples per frame */
     unsigned		samples_per_frame;
+
+    /** Bits per sample */
     unsigned		bits_per_sample;
+
+    /** Number of listeners in the array. */
     unsigned		listener_cnt;
-    pjsua_conf_port_id	listeners[256];
-};
-
-
-typedef struct pjsua_conf_port_info pjsua_conf_port_info;
+
+    /** Array of listeners (in other words, ports where this port is 
+     *  transmitting to.
+     */
+    pjsua_conf_port_id	listeners[PJSUA_MAX_CONF_PORTS];
+
+} pjsua_conf_port_info;
+
+
+/**
+ * This structure holds information about custom media transport to
+ * be registered to pjsua.
+ */
+typedef struct pjsua_media_transport
+{
+    /**
+     * Media socket information containing the address information
+     * of the RTP and RTCP socket.
+     */
+    pjmedia_sock_info	 skinfo;
+
+    /**
+     * The media transport instance.
+     */
+    pjmedia_transport	*transport;
+
+} pjsua_media_transport;
+
+
+/**
+ * This structure describes additional information to be sent with
+ * outgoing SIP message.
+ */
+typedef struct pjsua_msg_data
+{
+    /**
+     * Additional message headers as linked list.
+     */
+    pjsip_hdr	hdr_list;
+
+    /**
+     * MIME type of optional message body. 
+     */
+    pj_str_t	content_type;
+
+    /**
+     * Optional message body.
+     */
+    pj_str_t	msg_body;
+
+} pjsua_msg_data;
+
+
+/**
+ * Initialize message data.
+ *
+ * @param msg_data  Message data to be initialized.
+ */
+PJ_INLINE(void) pjsua_msg_data_init(pjsua_msg_data *msg_data)
+{
+    pj_memset(msg_data, 0, sizeof(*msg_data));
+    pj_list_init(&msg_data->hdr_list);
+}
 
 
 /*****************************************************************************
- * PJSUA API (defined in pjsua_core.c).
- */
-
-/**
- * Initialize pjsua settings with default parameters.
- */
-PJ_DECL(void) pjsua_default_config(pjsua_config *cfg);
-
-
-/**
- * Validate configuration.
- */
-PJ_DECL(pj_status_t) pjsua_test_config(const pjsua_config *cfg,
-				       char *errmsg,
-				       int len);
-
-
-/**
- * Instantiate pjsua application. This initializes pjlib/pjlib-util, and 
- * creates memory pool factory to be used by application.
+ * PJSUA Core API
+ */
+
+
+/**
+ * Instantiate pjsua application. Application must call this function before
+ * calling any other functions, to make sure that the underlying libraries
+ * are properly initialized. Once this function has returned success,
+ * application must call pjsua_destroy() before quitting.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_create(void);
@@ -449,31 +1078,48 @@
 
 /**
- * Initialize pjsua application with the specified settings.
- *
- * This will initialize all libraries, create endpoint instance, and register
- * pjsip modules. 
- *
- * Application may register module after calling this function.
- */
-PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg,
-				const pjsua_callback *cb);
-
-
-/**
- * Start pjsua stack. Application calls this after pjsua settings has been
- * configured.
- *
- * This will start the transport, worker threads (if any), and registration 
- * process, if registration is configured.
+ * Initialize pjsua with the specified settings. All the settings are 
+ * optional, and the default values will be used when the config is not
+ * specified.
+ *
+ * @param ua_cfg	User agent configuration.
+ * @param log_cfg	Optional logging configuration.
+ * @param media_cfg	Optional media configuration.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *ua_cfg,
+				const pjsua_logging_config *log_cfg,
+				const pjsua_media_config *media_cfg);
+
+
+/**
+ * Application is recommended to call this function after all initialization
+ * is done, so that the library can do additional checking set up
+ * additional 
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_start(void);
 
-/**
- * Destroy pjsua.
+
+/**
+ * Destroy pjsua. This function must be called once PJSUA is created. To
+ * make it easier for application, application may call this function
+ * several times with no danger.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_destroy(void);
 
-/**
- * Poll pjsua.
+
+/**
+ * Poll pjsua for events, and if necessary block the caller thread for
+ * the specified maximum interval (in miliseconds).
+ *
+ * @param msec_timeout	Maximum time to wait, in miliseconds.
+ *
+ * @return  The number of events that have been handled during the
+ *	    poll. Negative value indicates error, and application
+ *	    can retrieve the error as (err = -return_value).
  */
 PJ_DECL(int) pjsua_handle_events(unsigned msec_timeout);
@@ -481,119 +1127,418 @@
 
 /**
- * Get SIP endpoint instance.
- * Only valid after pjsua_init().
+ * Create memory pool.
+ *
+ * @param name		Optional pool name.
+ * @param size		Initial size of the pool.
+ * @param increment	Increment size.
+ *
+ * @return		The pool, or NULL when there's no memory.
+ */
+PJ_DECL(pj_pool_t*) pjsua_pool_create(const char *name, pj_size_t init_size,
+				      pj_size_t increment);
+
+
+/**
+ * Application can call this function at any time (after pjsua_create(), of
+ * course) to change logging settings.
+ *
+ * @param c		Logging configuration.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_reconfigure_logging(const pjsua_logging_config *c);
+
+
+/**
+ * Internal function to get SIP endpoint instance of pjsua, which is
+ * needed for example to register module, create transports, etc.
+ * Probably is only valid after #pjsua_init() is called.
+ * 
+ * @return		SIP endpoint instance.
  */
 PJ_DECL(pjsip_endpoint*) pjsua_get_pjsip_endpt(void);
 
 /**
- * Get media endpoint instance.
- * Only valid after pjsua_init().
+ * Internal function to get media endpoint instance.
+ * Only valid after #pjsua_init() is called.
+ *
+ * @return		Media endpoint instance.
  */
 PJ_DECL(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void);
 
-/**
- * Replace media transport.
- */
-PJ_DECL(pj_status_t) pjsua_set_call_media_transport(unsigned call_index,
-						    const pjmedia_sock_info *i,
-						    pjmedia_transport *tp);
-
 
 /*****************************************************************************
- * PJSUA Call API (defined in pjsua_call.c).
+ * PJSUA SIP Transport API.
+ */
+
+/**
+ * Create SIP transport.
+ *
+ * @param type		Transport type.
+ * @param cfg		Transport configuration.
+ * @param p_id		Optional pointer to receive transport ID.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_transport_create(pjsip_transport_type_e type,
+					    const pjsua_transport_config *cfg,
+					    pjsua_transport_id *p_id);
+
+/**
+ * Register transport that has been created by application.
+ *
+ * @param tp		Transport instance.
+ * @param p_id		Optional pointer to receive transport ID.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_transport_register(pjsip_transport *tp,
+					      pjsua_transport_id *p_id);
+
+
+/**
+ * Enumerate all transports currently created in the system.
+ *
+ * @param id		Array to receive transport ids.
+ * @param count		In input, specifies the maximum number of elements.
+ *			On return, it contains the actual number of elements.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_enum_transports( pjsua_transport_id id[],
+					    unsigned *count );
+
+
+/**
+ * Get information about transports.
+ *
+ * @param id		Transport ID.
+ * @param info		Pointer to receive transport info.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_transport_get_info(pjsua_transport_id id,
+					      pjsua_transport_info *info);
+
+
+/**
+ * Disable a transport or re-enable it. By default transport is always 
+ * enabled after it is created. Disabling a transport does not necessarily
+ * close the socket, it will only discard incoming messages and prevent
+ * the transport from being used to send outgoing messages.
+ *
+ * @param id		Transport ID.
+ * @param enabled	Non-zero to enable, zero to disable.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_transport_set_enable(pjsua_transport_id id,
+						pj_bool_t enabled);
+
+
+/**
+ * Close the transport. If transport is forcefully closed, it will be
+ * immediately closed, and any pending transactions that are using the
+ * transport may not terminate properly. Otherwise, the system will wait
+ * until all transactions are closed while preventing new users from
+ * using the transport, and will close the transport when it is safe to
+ * do so.
+ *
+ * @param id		Transport ID.
+ * @param force		Non-zero to immediately close the transport. This
+ *			is not recommended!
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_transport_close( pjsua_transport_id id,
+					    pj_bool_t force );
+
+
+/*****************************************************************************
+ * PJSUA Media Transport.
+ */
+
+/**
+ * Create UDP media transports for all the calls. This function creates
+ * one UDP media transport for each call.
+ *
+ * @param cfg		Media transport configuration. The "port" field in the
+ *			configuration is used as the start port to bind the
+ *			sockets.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) 
+pjsua_media_transports_create(const pjsua_transport_config *cfg);
+
+
+/**
+ * Register custom media transports to be used by calls. There must
+ * enough media transports for all calls.
+ *
+ * @param tp		The media transport array.
+ * @param count		Number of elements in the array. This number MUST
+ *			match the number of maximum calls configured when
+ *			pjsua is created.
+ * @param auto_delete	Flag to indicate whether the transports should be
+ *			destroyed when pjsua is shutdown.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) 
+pjsua_media_transports_attach( pjsua_media_transport tp[],
+			       unsigned count,
+			       pj_bool_t auto_delete);
+
+
+
+/*****************************************************************************
+ * PJSUA Call API.
  */
 
 /**
  * Get maximum number of calls configured in pjsua.
+ *
+ * @return		Maximum number of calls configured.
  */
 PJ_DECL(unsigned) pjsua_call_get_max_count(void);
 
 /**
- * Get current number of active calls.
+ * Get number of currently active calls.
+ *
+ * @return		Number of currently active calls.
  */
 PJ_DECL(unsigned) pjsua_call_get_count(void);
+
+/**
+ * Enumerate all active calls.
+ *
+ * @param ids		Array of account IDs to be initialized.
+ * @param count		In input, specifies the maximum number of elements.
+ *			On return, it contains the actual number of elements.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_enum_calls(pjsua_call_id ids[],
+				      unsigned *count);
+
+
+/**
+ * Make outgoing call to the specified URI using the specified account.
+ *
+ * @param acc_id	The account to be used.
+ * @param target	URI to be put in the request URI.
+ * @param dst_uri	URI to be put in the To header (normally is the same
+ *			as the target URI).
+ * @param options	Options (must be zero at the moment).
+ * @param user_data	Arbitrary user data to be attached to the call, and
+ *			can be retrieved later.
+ * @param msg_data	Optional headers etc to be added to outgoing INVITE
+ *			request, or NULL if no custom header is desired.
+ * @param p_call_id	Pointer to receive call identification.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_make_call(pjsua_acc_id acc_id,
+					  const pj_str_t *dst_uri,
+					  unsigned options,
+					  void *user_data,
+					  const pjsua_msg_data *msg_data,
+					  pjsua_call_id *p_call_id);
+
 
 /**
  * Check if the specified call has active INVITE session and the INVITE
  * session has not been disconnected.
- */
-PJ_DECL(pj_bool_t) pjsua_call_is_active(unsigned call_index);
-
-
-/**
- * Check if call has a media session.
- */
-PJ_DECL(pj_bool_t) pjsua_call_has_media(unsigned call_index);
-
-
-/**
- * Get call info.
- */
-PJ_DECL(pj_status_t) pjsua_call_get_info(unsigned call_index,
+ *
+ * @param call_id	Call identification.
+ *
+ * @return		Non-zero if call is active.
+ */
+PJ_DECL(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id);
+
+
+/**
+ * Check if call has an active media session.
+ *
+ * @param call_id	Call identification.
+ *
+ * @return		Non-zero if yes.
+ */
+PJ_DECL(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id);
+
+
+/**
+ * Get the conference port identification associated with the call.
+ *
+ * @param call_id	Call identification.
+ *
+ * @return		Conference port ID, or PJSUA_INVALID_ID when the 
+ *			media has not been established or is not active.
+ */
+PJ_DECL(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id);
+
+/**
+ * Obtain detail information about the specified call.
+ *
+ * @param call_id	Call identification.
+ * @param info		Call info to be initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_get_info(pjsua_call_id call_id,
 					 pjsua_call_info *info);
 
 
 /**
- * Duplicate call info.
- */
-PJ_DECL(void) pjsua_call_info_dup(pj_pool_t *pool,
-				  pjsua_call_info *dst_info,
-				  const pjsua_call_info *src_info);
-
-
-/**
- * Make outgoing call.
- */
-PJ_DECL(pj_status_t) pjsua_call_make_call(unsigned acc_id,
-					  const pj_str_t *dst_uri,
-					  int *p_call_index);
-
-
-/**
- * Answer call.
- */
-PJ_DECL(pj_status_t) pjsua_call_answer(int call_index, int code);
-
-/**
- * Hangup call.
- */
-PJ_DECL(void) pjsua_call_hangup(int call_index);
-
-
-/**
- * Put call on-hold.
- */
-PJ_DECL(pj_status_t) pjsua_call_set_hold(int call_index);
+ * Attach application specific data to the call.
+ *
+ * @param call_id	Call identification.
+ * @param user_data	Arbitrary data to be attached to the call.
+ *
+ * @return		The user data.
+ */
+PJ_DECL(pj_status_t) pjsua_call_set_user_data(pjsua_call_id call_id,
+					      void *user_data);
+
+
+/**
+ * Get user data attached to the call.
+ *
+ * @param call_id	Call identification.
+ *
+ * @return		The user data.
+ */
+PJ_DECL(void*) pjsua_call_get_user_data(pjsua_call_id call_id);
+
+
+/**
+ * Send response to incoming INVITE request.
+ *
+ * @param call_id	Incoming call identification.
+ * @param code		Status code, (100-699).
+ * @param reason	Optional reason phrase. If NULL, default text
+ *			will be used.
+ * @param msg_data	Optional list of headers etc to be added to outgoing
+ *			response message.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_answer(pjsua_call_id call_id, 
+				       unsigned code,
+				       const pj_str_t *reason,
+				       const pjsua_msg_data *msg_data);
+
+/**
+ * Hangup call by using method that is appropriate according to the
+ * call state.
+ *
+ * @param call_id	Call identification.
+ * @param code		Optional status code to be sent when we're rejecting
+ *			incoming call. If the value is zero, "603/Decline"
+ *			will be sent.
+ * @param reason	Optional reason phrase to be sent when we're rejecting
+ *			incoming call.  If NULL, default text will be used.
+ * @param msg_data	Optional list of headers etc to be added to outgoing
+ *			request/response message.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id,
+				       unsigned code,
+				       const pj_str_t *reason,
+				       const pjsua_msg_data *msg_data);
+
+
+/**
+ * Put the specified call on hold.
+ *
+ * @param call_id	Call identification.
+ * @param msg_data	Optional message components to be sent with
+ *			the request.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_set_hold(pjsua_call_id call_id,
+					 const pjsua_msg_data *msg_data);
 
 
 /**
  * Send re-INVITE (to release hold).
- */
-PJ_DECL(pj_status_t) pjsua_call_reinvite(int call_index);
-
-
-/**
- * Transfer call.
- */
-PJ_DECL(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest);
-
-/**
- * Dial DTMF.
- */
-PJ_DECL(pj_status_t) pjsua_call_dial_dtmf(unsigned call_index, 
+ *
+ * @param call_id	Call identification.
+ * @param unhold	If this argument is non-zero and the call is locally
+ *			held, this will release the local hold.
+ * @param msg_data	Optional message components to be sent with
+ *			the request.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_reinvite(pjsua_call_id call_id,
+					 pj_bool_t unhold,
+					 const pjsua_msg_data *msg_data);
+
+
+/**
+ * Initiate call transfer to the specified address.
+ *
+ * @param call_id	Call identification.
+ * @param dest		Address of new target to be contacted.
+ * @param msg_data	Optional message components to be sent with
+ *			the request.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_xfer(pjsua_call_id call_id, 
+				     const pj_str_t *dest,
+				     const pjsua_msg_data *msg_data);
+
+/**
+ * Send DTMF digits to remote using RFC 2833 payload formats.
+ *
+ * @param call_id	Call identification.
+ * @param digits	DTMF digits to be sent.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_dial_dtmf(pjsua_call_id call_id, 
 					  const pj_str_t *digits);
 
-
 /**
  * Send instant messaging inside INVITE session.
- */
-PJ_DECL(pj_status_t) pjsua_call_send_im(int call_index, const pj_str_t *text);
+ *
+ * @param call_id	Call identification.
+ * @param mime_type	Optional MIME type. If NULL, then "text/plain" is 
+ *			assumed.
+ * @param content	The message content.
+ * @param msg_data	Optional list of headers etc to be included in outgoing
+ *			request. The body descriptor in the msg_data is 
+ *			ignored.
+ * @param user_data	Optional user data, which will be given back when
+ *			the IM callback is called.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_send_im( pjsua_call_id call_id, 
+					 const pj_str_t *mime_type,
+					 const pj_str_t *content,
+					 const pjsua_msg_data *msg_data,
+					 void *user_data);
 
 
 /**
  * Send IM typing indication inside INVITE session.
- */
-PJ_DECL(pj_status_t) pjsua_call_send_typing_ind(int call_index, 
-						pj_bool_t is_typing);
+ *
+ * @param call_id	Call identification.
+ * @param is_typing	Non-zero to indicate to remote that local person is
+ *			currently typing an IM.
+ * @param msg_data	Optional list of headers etc to be included in outgoing
+ *			request.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_call_send_typing_ind(pjsua_call_id call_id, 
+						pj_bool_t is_typing,
+						const pjsua_msg_data*msg_data);
 
 /**
@@ -605,22 +1550,138 @@
 /**
  * Dump call and media statistics to string.
- */
-PJ_DECL(void) pjsua_call_dump(int call_index, int with_media, 
-			      char *buffer, unsigned maxlen,
-			      const char *indent);
+ *
+ * @param call_id	Call identification.
+ * @param with_media	Non-zero to include media information too.
+ * @param buffer	Buffer where the statistics are to be written to.
+ * @param maxlen	Maximum length of buffer.
+ * @param indent	Spaces for left indentation.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjsua_call_dump(pjsua_call_id call_id, 
+				     pj_bool_t with_media, 
+				     char *buffer, 
+				     unsigned maxlen,
+				     const char *indent);
 
 
 /*****************************************************************************
- * PJSUA Account and Client Registration API (defined in pjsua_reg.c).
- */
-
-
-/**
- * Get number of accounts.
- */
-PJ_DECL(unsigned) pjsua_get_acc_count(void);
-
-/**
- * Get account info.
+ * PJSUA Account and Client Registration API.
+ */
+
+
+/**
+ * Get number of current accounts.
+ *
+ * @return		Current number of accounts.
+ */
+PJ_DECL(unsigned) pjsua_acc_get_count(void);
+
+
+/**
+ * Check if the specified account ID is valid.
+ *
+ * @param acc_id	Account ID to check.
+ *
+ * @return		Non-zero if account ID is valid.
+ */
+PJ_DECL(pj_bool_t) pjsua_acc_is_valid(pjsua_acc_id acc_id);
+
+
+/**
+ * Add a new account to pjsua. PJSUA must have been initialized (with
+ * #pjsua_init()) before calling this function.
+ *
+ * @param cfg		Account configuration.
+ * @param is_default	If non-zero, this account will be set as the default
+ *			account. The default account will be used when sending
+ *			outgoing requests (e.g. making call) when no account is
+ *			specified, and when receiving incoming requests when the
+ *			request does not match any accounts. It is recommended
+ *			that default account is set to local/LAN account.
+ * @param p_acc_id	Pointer to receive account ID of the new account.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_acc_add(const pjsua_acc_config *cfg,
+				   pj_bool_t is_default,
+				   pjsua_acc_id *p_acc_id);
+
+
+/**
+ * Add a local account. A local account is used to identify local endpoint
+ * instead of a specific user, and for this reason, a transport ID is needed
+ * to obtain the local address information.
+ *
+ * @param tid		Transport ID to generate account address.
+ * @param is_default	If non-zero, this account will be set as the default
+ *			account. The default account will be used when sending
+ *			outgoing requests (e.g. making call) when no account is
+ *			specified, and when receiving incoming requests when the
+ *			request does not match any accounts. It is recommended
+ *			that default account is set to local/LAN account.
+ * @param p_acc_id	Pointer to receive account ID of the new account.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_acc_add_local(pjsua_transport_id tid,
+					 pj_bool_t is_default,
+					 pjsua_acc_id *p_acc_id);
+
+/**
+ * Delete account.
+ *
+ * @param acc_id	Id of the account to be deleted.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_id);
+
+
+/**
+ * Modify account information.
+ *
+ * @param acc_id	Id of the account to be modified.
+ * @param cfg		New account configuration.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_acc_modify(pjsua_acc_id acc_id,
+				      const pjsua_acc_config *cfg);
+
+
+/**
+ * Modify account's presence status to be advertised to remote/presence
+ * subscribers.
+ *
+ * @param acc_id	The account ID.
+ * @param is_online	True of false.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_acc_set_online_status(pjsua_acc_id acc_id,
+						 pj_bool_t is_online);
+
+
+/**
+ * Update registration or perform unregistration. 
+ *
+ * @param acc_id	The account ID.
+ * @param renew		If renew argument is zero, this will start 
+ *			unregistration process.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_id, 
+						pj_bool_t renew);
+
+
+/**
+ * Get account information.
+ *
+ * @param acc_id	Account identification.
+ * @param info		Pointer to receive account information.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_acc_get_info(pjsua_acc_id acc_id,
@@ -629,12 +1690,24 @@
 
 /**
- * Enum accounts id.
- */
-PJ_DECL(pj_status_t) pjsua_acc_enum_id( pjsua_acc_id ids[],
-				        unsigned *count );
+ * Enum accounts all account ids.
+ *
+ * @param ids		Array of account IDs to be initialized.
+ * @param count		In input, specifies the maximum number of elements.
+ *			On return, it contains the actual number of elements.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_enum_accs(pjsua_acc_id ids[],
+				     unsigned *count );
 
 
 /**
  * Enum accounts info.
+ *
+ * @param info		Array of account infos to be initialized.
+ * @param count		In input, specifies the maximum number of elements.
+ *			On return, it contains the actual number of elements.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[],
@@ -643,42 +1716,23 @@
 
 /**
- * Find account for outgoing request.
+ * This is an internal function to find the most appropriate account to
+ * used to reach to the specified URL.
+ *
+ * @param url		The remote URL to reach.
+ *
+ * @return		Account id.
  */
 PJ_DECL(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *url);
 
-/**
- * Find account for incoming request.
+
+/**
+ * This is an internal function to find the most appropriate account to be
+ * used to handle incoming calls.
+ *
+ * @param rdata		The incoming request message.
+ *
+ * @return		Account id.
  */
 PJ_DECL(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata);
-
-/**
- * Add a new account.
- * This function should be called after pjsua_init().
- * Application should call pjsua_acc_set_registration() to start 
- * registration for this account.
- */
-PJ_DECL(pj_status_t) pjsua_acc_add(const pjsua_acc_config *cfg,
-				   pjsua_acc_id *acc_id);
-
-/**
- * Delete account.
- */
-PJ_DECL(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_id);
-
-
-/**
- * Set account's presence status.
- */
-PJ_DECL(pj_status_t) pjsua_acc_set_online_status(pjsua_acc_id acc_id,
-						 pj_bool_t is_online);
-
-
-/**
- * Update registration or perform unregistration. If renew argument is zero,
- * this will start unregistration process.
- */
-PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_id, 
-						pj_bool_t renew);
-
 
 
@@ -689,5 +1743,7 @@
 
 /**
- * Get buddy count.
+ * Get total number of buddies.
+ *
+ * @return		Number of buddies.
  */
 PJ_DECL(unsigned) pjsua_get_buddy_count(void);
@@ -695,33 +1751,78 @@
 
 /**
- * Get buddy info.
- */
-PJ_DECL(pj_status_t) pjsua_buddy_get_info(pjsua_buddy_id buddy_index,
+ * Check if buddy ID is valid.
+ *
+ * @param buddy_id	Buddy ID to check.
+ *
+ * @return		Non-zero if buddy ID is valid.
+ */
+PJ_DECL(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id);
+
+
+/**
+ * Enum buddy IDs.
+ *
+ * @param ids		Array of ids to be initialized.
+ * @param count		On input, specifies max elements in the array.
+ *			On return, it contains actual number of elements
+ *			that have been initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_enum_buddies(pjsua_buddy_id ids[],
+					unsigned *count);
+
+/**
+ * Get detailed buddy info.
+ *
+ * @param buddy_id	The buddy identification.
+ * @param info		Pointer to receive information about buddy.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_buddy_get_info(pjsua_buddy_id buddy_id,
 					  pjsua_buddy_info *info);
 
 /**
  * Add new buddy.
- */
-PJ_DECL(pj_status_t) pjsua_buddy_add(const pj_str_t *uri,
-				     pjsua_buddy_id *buddy_index);
+ *
+ * @param cfg		Buddy configuration.
+ * @param p_buddy_id	Pointer to receive buddy ID.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_buddy_add(const pjsua_buddy_config *cfg,
+				     pjsua_buddy_id *p_buddy_id);
 
 
 /**
  * Delete buddy.
- */
-PJ_DECL(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_index);
+ *
+ * @param buddy_id	Buddy identification.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id);
 
 
 /**
  * Enable/disable buddy's presence monitoring.
- */
-PJ_DECL(pj_status_t) pjsua_buddy_subscribe_pres(pjsua_buddy_id buddy_index,
-						pj_bool_t monitor);
-
-
-/**
- * Dump presence subscriptions.
- */
-PJ_DECL(void) pjsua_pres_dump(pj_bool_t detail);
+ *
+ * @param buddy_id	Buddy identification.
+ * @param subscribe	Specify non-zero to activate presence subscription to
+ *			the specified buddy.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_buddy_subscribe_pres(pjsua_buddy_id buddy_id,
+						pj_bool_t subscribe);
+
+
+/**
+ * Dump presence subscriptions to log file.
+ *
+ * @param verbose	Yes or no.
+ */
+PJ_DECL(void) pjsua_pres_dump(pj_bool_t verbose);
 
 
@@ -738,37 +1839,88 @@
 
 /**
- * Send IM outside dialog.
- */
-PJ_DECL(pj_status_t) pjsua_im_send(int acc_id, const pj_str_t *dst_uri, 
-				   const pj_str_t *text);
+ * Send instant messaging outside dialog, using the specified account for
+ * route set and authentication.
+ *
+ * @param acc_id	Account ID to be used to send the request.
+ * @param to		Remote URI.
+ * @param mime_type	Optional MIME type. If NULL, then "text/plain" is 
+ *			assumed.
+ * @param content	The message content.
+ * @param msg_data	Optional list of headers etc to be included in outgoing
+ *			request. The body descriptor in the msg_data is 
+ *			ignored.
+ * @param user_data	Optional user data, which will be given back when
+ *			the IM callback is called.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_im_send(pjsua_acc_id acc_id, 
+				   const pj_str_t *to,
+				   const pj_str_t *mime_type,
+				   const pj_str_t *content,
+				   const pjsua_msg_data *msg_data,
+				   void *user_data);
 
 
 /**
  * Send typing indication outside dialog.
- */
-PJ_DECL(pj_status_t) pjsua_im_typing(int acc_id, const pj_str_t *dst_uri, 
-				     pj_bool_t is_typing);
+ *
+ * @param acc_id	Account ID to be used to send the request.
+ * @param to		Remote URI.
+ * @param is_typing	If non-zero, it tells remote person that local person
+ *			is currently composing an IM.
+ * @param msg_data	Optional list of headers etc to be added to outgoing
+ *			request.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_im_typing(pjsua_acc_id acc_id, 
+				     const pj_str_t *to, 
+				     pj_bool_t is_typing,
+				     const pjsua_msg_data *msg_data);
 
 
 
 /*****************************************************************************
- * Media.
+ * Conference bridge manipulation.
  */
 
 /**
  * Get maxinum number of conference ports.
- */
-PJ_DECL(unsigned) pjsua_conf_max_ports(void);
-
-
-/**
- * Enum all conference ports.
- */
-PJ_DECL(pj_status_t) pjsua_conf_enum_port_ids(pjsua_conf_port_id id[],
-					      unsigned *count);
+ *
+ * @return		Maximum number of ports in the conference bridge.
+ */
+PJ_DECL(unsigned) pjsua_conf_get_max_ports(void);
+
+
+/**
+ * Get current number of active ports in the bridge.
+ *
+ * @return		The number.
+ */
+PJ_DECL(unsigned) pjsua_conf_get_active_ports(void);
+
+
+/**
+ * Enumerate all conference ports.
+ *
+ * @param id		Array of conference port ID to be initialized.
+ * @param count		On input, specifies max elements in the array.
+ *			On return, it contains actual number of elements
+ *			that have been initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
+					   unsigned *count);
 
 
 /**
  * Get information about the specified conference port
+ *
+ * @param id		Port identification.
+ * @param info		Pointer to store the port info.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
@@ -777,26 +1929,63 @@
 
 /**
- * Connect conference port.
- */
-PJ_DECL(pj_status_t) pjsua_conf_connect(pjsua_conf_port_id src_port,
-					pjsua_conf_port_id dst_port);
-
-
-/**
- * Connect conference port connection.
- */
-PJ_DECL(pj_status_t) pjsua_conf_disconnect(pjsua_conf_port_id src_port,
-					   pjsua_conf_port_id dst_port);
-
-
-/**
- * Create a file player.
+ * Establish unidirectional media flow from souce to sink. One source
+ * may transmit to multiple destinations/sink. And if multiple
+ * sources are transmitting to the same sink, the media will be mixed
+ * together. Source and sink may refer to the same ID, effectively
+ * looping the media.
+ *
+ * If bidirectional media flow is desired, application needs to call
+ * this function twice, with the second one having the arguments
+ * reversed.
+ *
+ * @param source	Port ID of the source media/transmitter.
+ * @param sink		Port ID of the destination media/received.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_conf_connect(pjsua_conf_port_id source,
+					pjsua_conf_port_id sink);
+
+
+/**
+ * Disconnect media flow from the source to destination port.
+ *
+ * @param source	Port ID of the source media/transmitter.
+ * @param sink		Port ID of the destination media/received.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_conf_disconnect(pjsua_conf_port_id source,
+					   pjsua_conf_port_id sink);
+
+
+/*****************************************************************************
+ * File player.
+ */
+
+/**
+ * Create a file player, and automatically connect this player to
+ * the conference bridge.
+ *
+ * @param filename	The filename to be played. Currently only
+ *			WAV files are supported.
+ * @param options	Options (currently zero).
+ * @param user_data	Arbitrary user data to be associated with the player.
+ * @param p_id		Pointer to receive player ID.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_player_create(const pj_str_t *filename,
-					 pjsua_player_id *id);
-
-
-/**
- * Get conference port associated with player.
+					 unsigned options,
+					 void *user_data,
+					 pjsua_player_id *p_id);
+
+
+/**
+ * Get conference port ID associated with player.
+ *
+ * @param id		The file player ID.
+ *
+ * @return		Conference port ID associated with this player.
  */
 PJ_DECL(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id);
@@ -805,4 +1994,10 @@
 /**
  * Set playback position.
+ *
+ * @param id		The file player ID.
+ * @param samples	The playback position, in samples. Application can
+ *			specify zero to re-start the playback.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_player_set_pos(pjsua_player_id id,
@@ -811,19 +2006,52 @@
 
 /**
- * Destroy player.
+ * Close the file, remove the player from the bridge, and free
+ * resources associated with the file player.
+ *
+ * @param id		The file player ID.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_player_destroy(pjsua_player_id id);
 
 
-
-/**
- * Create a file recorder.
+/*****************************************************************************
+ * File recorder.
+ */
+
+/**
+ * Create a file recorder, and automatically connect this recorder to
+ * the conference bridge.
+ *
+ * @param filename	Output file name.
+ * @param file_format	Specify the file format (currently only WAV is
+ *			supported, so the value MUST be zero).
+ * @param encoding	Specify the encoding to be applied to the file.
+ *			Currently only 16bit raw PCM is supported, so
+ *			the value must be NULL.
+ * @param max_size	Maximum file size. Specify -1 to remove size
+ *			limitation.
+ * @param options	Optional options.
+ * @param user_data	Arbitrary user data which will be given in the 
+ *			callback once the recording complete.
+ * @param p_id		Pointer to receive the recorder instance.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_recorder_create(const pj_str_t *filename,
-					   pjsua_recorder_id *id);
+					   unsigned file_format,
+					   const pj_str_t *encoding,
+					   pj_ssize_t max_size,
+					   unsigned options,
+					   void *user_data,
+					   pjsua_recorder_id *p_id);
 
 
 /**
  * Get conference port associated with recorder.
+ *
+ * @param id		The recorder ID.
+ *
+ * @return		Conference port ID associated with this recorder.
  */
 PJ_DECL(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id);
@@ -831,23 +2059,110 @@
 
 /**
- * Destroy recorder (will complete recording).
+ * Destroy recorder (this will complete recording).
+ *
+ * @param id		The recorder ID.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id);
 
 
+/*****************************************************************************
+ * Sound devices.
+ */
+
 /**
  * Enum sound devices.
- */
-PJ_DECL(pj_status_t) pjsua_enum_snd_devices(unsigned *count,
-					    pjmedia_snd_dev_info info[]);
-
-
-/**
- * Select or change sound device.
- * This will only change the device ID in configuration (not changing
- * the current device).
- */
-PJ_DECL(pj_status_t) pjsua_set_snd_dev(int snd_capture_id,
-				       int snd_player_id);
+ *
+ * @param info		Array of info to be initialized.
+ * @param count		On input, specifies max elements in the array.
+ *			On return, it contains actual number of elements
+ *			that have been initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_enum_snd_devs(pjmedia_snd_dev_info info[],
+					 unsigned *count);
+
+
+/**
+ * Select or change sound device. Application may call this function at
+ * any time to replace current sound device.
+ *
+ * @param capture_dev   Device ID of the capture device.
+ * @param playback_dev	Device ID of the playback device.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_set_snd_dev(int capture_dev,
+				       int playback_dev);
+
+
+/**
+ * Set pjsua to use null sound device. The null sound device only provides
+ * the timing needed by the conference bridge, and will not interract with
+ * any hardware.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_set_null_snd_dev(void);
+
+
+/*****************************************************************************
+ * Codecs.
+ */
+
+/**
+ * Enum all supported codecs in the system.
+ *
+ * @param id		Array of ID to be initialized.
+ * @param count		On input, specifies max elements in the array.
+ *			On return, it contains actual number of elements
+ *			that have been initialized.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
+				        unsigned *count );
+
+
+/**
+ * Change codec priority.
+ *
+ * @param id		Codec ID.
+ * @param priority	Codec priority, 0-255, where zero means to disable
+ *			the codec.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_codec_set_priority( const pj_str_t *id,
+					       pj_uint8_t priority );
+
+
+/**
+ * Get codec parameters.
+ *
+ * @param id		Codec ID.
+ * @param param		Structure to receive codec parameters.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_codec_get_param( const pj_str_t *id,
+					    pjmedia_codec_param *param );
+
+
+/**
+ * Set codec parameters.
+ *
+ * @param id		Codec ID.
+ * @param param		Codec parameter to set.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
+					    const pjmedia_codec_param *param);
+
+
+
 
 
@@ -857,55 +2172,20 @@
  */
 
-/** String to describe invite session states */
-extern const char *pjsua_inv_state_names[];
-
-/**
- * Parse arguments (pjsua_opt.c).
- */
-PJ_DECL(pj_status_t) pjsua_parse_args(int argc, char *argv[],
-				      pjsua_config *cfg,
-				      pj_str_t *uri_to_call);
-
-/**
- * Load settings from a file.
- */
-PJ_DECL(pj_status_t) pjsua_load_settings(const char *filename,
-					 pjsua_config *cfg,
-					 pj_str_t *uri_to_call);
-
-/**
- * Get pjsua running config.
- */
-PJ_DECL(void) pjsua_get_config(pj_pool_t *pool,
-			       pjsua_config *config);
-
-
-/**
- * Dump settings.
- * If cfg is NULL, it will dump current settings.
- */
-PJ_DECL(int) pjsua_dump_settings(const pjsua_config *cfg,
-				 char *buf, pj_size_t max);
-
-/**
- * Save settings to a file.
- */
-PJ_DECL(pj_status_t) pjsua_save_settings(const char *filename,
-					 const pjsua_config *cfg);
-
-
 /*
  * Verify that valid SIP url is given.
- * @return  PJ_SUCCESS if valid.
+ *
+ * @param c_url		The URL, as NULL terminated string.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
  */
 PJ_DECL(pj_status_t) pjsua_verify_sip_url(const char *c_url);
 
-/*
- * Dump application states.
- */
-PJ_DECL(void) pjsua_dump(pj_bool_t detail);
 
 /**
  * Display error message for the specified error code.
+ *
+ * @param sender	The log sender field.
+ * @param title		Message title for the error.
+ * @param status	Status code.
  */
 PJ_DECL(void) pjsua_perror(const char *sender, const char *title, 
Index: project/trunk/pjsip/include/pjsua-lib/pjsua_console_app.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_console_app.h	(revision 502)
+++ 	(revision )
@@ -1,28 +1,0 @@
-/* $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__
-
-
-void pjsua_console_app_main(const pj_str_t *uri_to_call);
-
-extern pjsip_module pjsua_console_app_msg_logger;
-extern pjsua_callback console_callback;
-
-#endif	/* __PJSUA_CONSOLE_APP_H__ */
Index: /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h
===================================================================
--- /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 503)
+++ /pjproject/trunk/pjsip/include/pjsua-lib/pjsua_internal.h	(revision 503)
@@ -0,0 +1,359 @@
+/* $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_INTERNAL_H__
+#define __PJSUA_INTERNAL_H__
+
+/** 
+ * This is the private header used by pjsua library implementation. 
+ * Applications should not include this file.
+ */
+
+PJ_BEGIN_DECL
+
+/** 
+ * Structure to be attached to invite dialog. 
+ * Given a dialog "dlg", application can retrieve this structure
+ * by accessing dlg->mod_data[pjsua.mod.id].
+ */
+typedef struct pjsua_call
+{
+    unsigned		 index;	    /**< Index in pjsua array.		    */
+    pjsip_inv_session	*inv;	    /**< The invite session.		    */
+    void		*user_data; /**< User/application data.		    */
+    pjsip_status_code	 last_code; /**< Last status code seen.		    */
+    pj_str_t		 last_text; /**< Last status text seen.		    */
+    pj_time_val		 start_time;/**< First INVITE sent/received.	    */
+    pj_time_val		 res_time;  /**< First response sent/received.	    */
+    pj_time_val		 conn_time; /**< Connected/confirmed time.	    */
+    pj_time_val		 dis_time;  /**< Disconnect time.		    */
+    pjsua_acc_id	 acc_id;    /**< Account index being used.	    */
+    pjsua_call_media_status media_st;/**< Media state.			    */
+    pjmedia_dir		 media_dir; /**< Media direction.		    */
+    pjmedia_session	*session;   /**< The media session.		    */
+    int			 conf_slot; /**< Slot # in conference bridge.	    */
+    pjsip_evsub		*xfer_sub;  /**< Xfer server subscription, if this
+					 call was triggered by xfer.	    */
+    pjmedia_sock_info	 skinfo;    /**< Preallocated media sockets.	    */
+    pjmedia_transport	*med_tp;    /**< Media transport.		    */
+    pj_timer_entry	 refresh_tm;/**< Timer to send re-INVITE.	    */
+    pj_timer_entry	 hangup_tm; /**< Timer to hangup call.		    */
+
+    char    last_text_buf_[128];    /**< Buffer for last_text.		    */
+
+} pjsua_call;
+
+
+/**
+ * Server presence subscription list head.
+ */
+typedef struct pjsua_srv_pres
+{
+    PJ_DECL_LIST_MEMBER(struct pjsua_srv_pres);
+    pjsip_evsub	    *sub;
+    char	    *remote;
+} pjsua_srv_pres;
+
+
+/**
+ * Account
+ */
+typedef struct pjsua_acc
+{
+    pjsua_acc_config cfg;	    /**< Account configuration.		*/
+    pj_bool_t	     valid;	    /**< Is this account valid?		*/
+
+    int		     index;	    /**< Index in accounts array.	*/
+    pj_str_t	     user_part;	    /**< User part of local URI.	*/
+    pj_str_t	     host_part;	    /**< Host part of local URI.	*/
+    pj_str_t	     real_contact;  /**< Real contact address.		*/
+
+    pjsip_regc	    *regc;	    /**< Client registration session.   */
+    pj_timer_entry   reg_timer;	    /**< Registration timer.		*/
+    pj_status_t	     reg_last_err;  /**< Last registration error.	*/
+    int		     reg_last_code; /**< Last status last register.	*/
+
+    pjsip_route_hdr  route_set;	    /**< Complete route set inc. outbnd.*/
+
+    unsigned	     cred_cnt;	    /**< Number of credentials.		*/
+    pjsip_cred_info  cred[PJSUA_ACC_MAX_PROXIES]; /**< Complete creds.	*/
+
+    pj_bool_t	     online_status; /**< Our online status.		*/
+    pjsua_srv_pres   pres_srv_list; /**< Server subscription list.	*/
+
+} pjsua_acc;
+
+
+/**
+ *Transport.
+ */
+typedef struct transport_data
+{
+    int			 index;
+    pjsip_transport	*tp;
+} transport_data;
+
+
+/**
+ * Buddy data.
+ */
+typedef struct pjsua_buddy
+{
+    unsigned		 index;	    /**< Buddy index.			*/
+    pj_str_t		 uri;	    /**< Buddy URI.			*/
+    pj_str_t		 contact;   /**< Contact learned from subscrp.	*/
+    pj_str_t		 name;	    /**< Buddy name.			*/
+    pj_str_t		 display;   /**< Buddy display name.		*/
+    pj_str_t		 host;	    /**< Buddy host.			*/
+    unsigned		 port;	    /**< Buddy port.			*/
+    pj_bool_t		 monitor;   /**< Should we monitor?		*/
+    pjsip_evsub		*sub;	    /**< Buddy presence subscription	*/
+    pjsip_pres_status	 status;    /**< Buddy presence status.		*/
+
+} pjsua_buddy;
+
+
+/**
+ * File player/recorder data.
+ */
+typedef struct pjsua_file_data
+{
+    pjmedia_port    *port;
+    unsigned	     slot;
+} pjsua_file_data;
+
+
+/**
+ * Additional parameters for conference bridge.
+ */
+typedef struct pjsua_conf_setting
+{
+    unsigned	channel_count;
+    unsigned	samples_per_frame;
+    unsigned	bits_per_sample;
+} pjsua_conf_setting;
+
+
+/**
+ * Global pjsua application data.
+ */
+struct pjsua_data
+{
+
+    /* Control: */
+    pj_caching_pool	 cp;	    /**< Global pool factory.		*/
+    pj_pool_t		*pool;	    /**< pjsua's private pool.		*/
+    pj_mutex_t		*mutex;	    /**< Mutex protection for this data	*/
+
+    /* Logging: */
+    pjsua_logging_config log_cfg;   /**< Current logging config.	*/
+    pj_oshandle_t	 log_file;  /**<Output log file handle		*/
+
+    /* SIP: */
+    pjsip_endpoint	*endpt;	    /**< Global endpoint.		*/
+    pjsip_module	 mod;	    /**< pjsua's PJSIP module.		*/
+    transport_data	 tpdata[8]; /**< Array of transports.		*/
+
+    /* Threading: */
+    pj_bool_t		 thread_quit_flag;  /**< Thread quit flag.	*/
+    pj_thread_t		*thread[4];	    /**< Array of threads.	*/
+
+    /* Account: */
+    unsigned		 acc_cnt;	     /**< Number of accounts.	*/
+    pjsua_acc_id	 default_acc;	     /**< Default account ID	*/
+    pjsua_acc		 acc[PJSUA_MAX_ACC]; /**< Account array.	*/
+
+    /* Calls: */
+    pjsua_config	 ua_cfg;		/**< UA config.		*/
+    unsigned		 call_cnt;		/**< Call counter.	*/
+    pjsua_call		 calls[PJSUA_MAX_CALLS];/**< Calls array.	*/
+
+    /* Buddy; */
+    unsigned		 buddy_cnt;		    /**< Buddy count.	*/
+    pjsua_buddy		 buddy[PJSUA_MAX_BUDDIES];  /**< Buddy array.	*/
+
+    /* Media: */
+    pjsua_media_config   media_cfg; /**< Media config.			*/
+    pjmedia_endpt	*med_endpt; /**< Media endpoint.		*/
+    pjsua_conf_setting	 mconf_cfg; /**< Additionan conf. bridge. param */
+    pjmedia_conf	*mconf;	    /**< Conference bridge.		*/
+    int			 cap_dev;   /**< Capture device ID.		*/
+    int			 play_dev;  /**< Playback device ID.		*/
+    pjmedia_snd_port	*snd_port;  /**< Sound port.			*/
+    pjmedia_master_port	*null_snd;  /**< Master port for null sound.	*/
+    pjmedia_port	*null_port; /**< Null port.			*/
+
+
+    /* File players: */
+    unsigned		 player_cnt;/**< Number of file players.	*/
+    pjsua_file_data	 player[32];/**< Array of players.		*/
+
+    /* File recorders: */
+    unsigned		 rec_cnt;   /**< Number of file recorders.	*/
+    pjsua_file_data	 recorder[32];/**< Array of file recorders.	*/
+};
+
+
+extern struct pjsua_data pjsua_var;
+
+
+/**
+ * IM callback data.
+ */
+typedef struct pjsua_im_data
+{
+    pjsua_acc_id     acc_id;
+    pjsua_call_id    call_id;
+    pj_str_t	     to;
+    pj_str_t	     body;
+    void	    *user_data;
+} pjsua_im_data;
+
+
+/**
+ * Duplicate IM data.
+ */
+PJ_INLINE(pjsua_im_data*) pjsua_im_data_dup(pj_pool_t *pool, 
+					    const pjsua_im_data *src)
+{
+    pjsua_im_data *dst;
+
+    dst = pj_pool_alloc(pool, sizeof(*dst));
+    dst->acc_id = src->acc_id;
+    dst->call_id = src->call_id;
+    pj_strdup_with_null(pool, &dst->to, &src->to);
+    dst->user_data = src->user_data;
+    pj_strdup_with_null(pool, &dst->body, &src->body);
+
+    return dst;
+}
+
+
+#define PJSUA_LOCK()	    pj_mutex_lock(pjsua_var.mutex);
+#define PJSUA_UNLOCK()	    pj_mutex_unlock(pjsua_var.mutex);
+
+
+
+/**
+ * Handle incoming invite request.
+ */
+pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata);
+
+/**
+ * Init presence.
+ */
+pj_status_t pjsua_pres_init();
+
+/*
+ * Start presence subsystem.
+ */
+pj_status_t pjsua_pres_start(void);
+
+/**
+ * Refresh presence subscriptions
+ */
+void pjsua_pres_refresh(void);
+
+/*
+ * Shutdown presence.
+ */
+void pjsua_pres_shutdown(void);
+
+/**
+ * Terminate server subscription for the account 
+ */
+void pjsua_pres_delete_acc(int acc_id);
+
+/**
+ * Init IM module handler to handle incoming MESSAGE outside dialog.
+ */
+pj_status_t pjsua_im_init(void);
+
+/**
+ * Init call subsystem.
+ */
+pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg);
+
+/**
+ * Start call subsystem.
+ */
+pj_status_t pjsua_call_subsys_start(void);
+
+/**
+ * Init media subsystems.
+ */
+pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg);
+
+/**
+ * Start pjsua media subsystem.
+ */
+pj_status_t pjsua_media_subsys_start(void);
+
+/**
+ * Destroy pjsua media subsystem.
+ */
+pj_status_t pjsua_media_subsys_destroy(void);
+
+/**
+ * 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);
+
+
+/**
+ * Create Accept header for MESSAGE.
+ */
+pjsip_accept_hdr* pjsua_im_create_accept(pj_pool_t *pool);
+
+/*
+ * Add additional headers etc in msg_data specified by application
+ * when sending requests.
+ */
+void pjsua_process_msg_data(pjsip_tx_data *tdata,
+			    const pjsua_msg_data *msg_data);
+
+
+/*
+ * Add route_set to outgoing requests
+ */
+void pjsua_set_msg_route_set( pjsip_tx_data *tdata,
+			      const pjsip_route_hdr *route_set );
+
+
+/*
+ * Simple version of MIME type parsing (it doesn't support parameters)
+ */
+void pjsua_parse_media_type( pj_pool_t *pool,
+			     const pj_str_t *mime,
+			     pjsip_media_type *media_type);
+
+
+PJ_END_DECL
+
+#endif	/* __PJSUA_INTERNAL_H__ */
+
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_acc.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_acc.c	(revision 503)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_acc.c	(revision 503)
@@ -0,0 +1,776 @@
+/* $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_internal.h>
+
+
+#define THIS_FILE		"pjsua_acc.c"
+
+
+/*
+ * Get number of current accounts.
+ */
+PJ_DEF(unsigned) pjsua_acc_get_count(void)
+{
+    return pjsua_var.acc_cnt;
+}
+
+
+/*
+ * Check if the specified account ID is valid.
+ */
+PJ_DEF(pj_bool_t) pjsua_acc_is_valid(pjsua_acc_id acc_id)
+{
+    return acc_id>=0 && acc_id<PJ_ARRAY_SIZE(pjsua_var.acc) &&
+	   pjsua_var.acc[acc_id].valid;
+}
+
+
+/*
+ * Copy account configuration.
+ */
+static void copy_acc_config(pj_pool_t *pool,
+			    pjsua_acc_config *dst,
+			    const pjsua_acc_config *src)
+{
+    unsigned i;
+
+    pj_memcpy(dst, src, sizeof(pjsua_acc_config));
+
+    pj_strdup_with_null(pool, &dst->id, &src->id);
+    pj_strdup_with_null(pool, &dst->reg_uri, &src->reg_uri);
+    pj_strdup_with_null(pool, &dst->contact, &src->contact);
+
+    dst->proxy_cnt = src->proxy_cnt;
+    for (i=0; i<src->proxy_cnt; ++i)
+	pj_strdup_with_null(pool, &dst->proxy[i], &src->proxy[i]);
+
+    dst->reg_timeout = src->reg_timeout;
+    dst->cred_count = src->cred_count;
+
+    for (i=0; i<src->cred_count; ++i) {
+	pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]);
+    }
+}
+
+
+/*
+ * Update account's real contact address.
+ */
+static void update_acc_contact(unsigned acc_id,
+			       unsigned tp_id)
+{
+    pjsua_acc *acc = &pjsua_var.acc[acc_id];
+    pjsip_transport *tp = pjsua_var.tpdata[tp_id].tp;
+    char uri[80];
+
+    /* Transport must be valid */
+    pj_assert(tp != NULL);
+    
+    /* Build URI for the account */
+    pj_ansi_sprintf(uri, "<sip:%.*s:%d;transport=%s>", 
+			 (int)tp->local_name.host.slen,
+			 tp->local_name.host.ptr,
+			 tp->local_name.port,
+			 pjsip_transport_get_type_name(tp->key.type));
+
+
+    pj_strdup2(pjsua_var.pool, &acc->real_contact, uri);
+}
+
+
+/*
+ * Initialize a new account (after configuration is set).
+ */
+static pj_status_t initialize_acc(unsigned acc_id)
+{
+    pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
+    pjsua_acc *acc = &pjsua_var.acc[acc_id];
+    pjsip_uri *uri;
+    pjsip_sip_uri *sip_uri;
+    unsigned i;
+
+    /* Need to parse local_uri to get the elements: */
+
+    uri = pjsip_parse_uri(pjsua_var.pool, acc_cfg->id.ptr,
+			  acc_cfg->id.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)) 
+    {
+	pjsua_perror(THIS_FILE, "Invalid local URI", 
+		     PJSIP_EINVALIDSCHEME);
+	return PJSIP_EINVALIDSCHEME;
+    }
+
+
+    /* Get the SIP URI object: */
+
+    sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri);
+
+    /* Save the user and domain part. These will be used when finding an 
+     * account for incoming requests.
+     */
+    acc->user_part = sip_uri->user;
+    acc->host_part = sip_uri->host;
+
+
+    /* Create Contact header if not present. */
+    if (acc_cfg->contact.slen == 0) {
+	acc_cfg->contact = acc_cfg->id;
+    }
+
+    PJ_TODO(attach_account_to_transport);
+    if (pjsua_var.tpdata[0].tp)
+	update_acc_contact(acc_id, 0);
+
+    /* Build account route-set from outbound proxies and route set from 
+     * account configuration.
+     */
+    pj_list_init(&acc->route_set);
+
+    for (i=0; i<pjsua_var.ua_cfg.outbound_proxy_cnt; ++i) {
+    	pj_str_t hname = { "Route", 5};
+	pjsip_route_hdr *r;
+	pj_str_t tmp;
+
+	pj_strdup_with_null(pjsua_var.pool, &tmp, 
+			    &pjsua_var.ua_cfg.outbound_proxy[i]);
+	r = pjsip_parse_hdr(pjsua_var.pool, &hname, tmp.ptr, tmp.slen, NULL);
+	if (r == NULL) {
+	    pjsua_perror(THIS_FILE, "Invalid outbound proxy URI",
+			 PJSIP_EINVALIDURI);
+	    return PJSIP_EINVALIDURI;
+	}
+	pj_list_push_back(&acc->route_set, r);
+    }
+
+    for (i=0; i<acc_cfg->proxy_cnt; ++i) {
+    	pj_str_t hname = { "Route", 5};
+	pjsip_route_hdr *r;
+	pj_str_t tmp;
+
+	pj_strdup_with_null(pjsua_var.pool, &tmp, &acc_cfg->proxy[i]);
+	r = pjsip_parse_hdr(pjsua_var.pool, &hname, tmp.ptr, tmp.slen, NULL);
+	if (r == NULL) {
+	    pjsua_perror(THIS_FILE, "Invalid URI in account route set",
+			 PJ_EINVAL);
+	    return PJ_EINVAL;
+	}
+	pj_list_push_back(&acc->route_set, r);
+    }
+
+    
+    /* Concatenate credentials from account config and global config */
+    acc->cred_cnt = 0;
+    for (i=0; i<acc_cfg->cred_count; ++i) {
+	acc->cred[acc->cred_cnt++] = acc_cfg->cred_info[i];
+    }
+    for (i=0; i<pjsua_var.ua_cfg.cred_count && 
+	      acc->cred_cnt < PJ_ARRAY_SIZE(acc->cred); ++i)
+    {
+	acc->cred[acc->cred_cnt++] = pjsua_var.ua_cfg.cred_info[i];
+    }
+
+    /* Init presence subscription */
+    pj_list_init(&acc->pres_srv_list);
+
+    /* Mark account as valid */
+    pjsua_var.acc[acc_id].valid = PJ_TRUE;
+
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Add a new account to pjsua.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg,
+				   pj_bool_t is_default,
+				   pjsua_acc_id *p_acc_id)
+{
+    unsigned id;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(pjsua_var.acc_cnt < PJ_ARRAY_SIZE(pjsua_var.acc),
+		     PJ_ETOOMANY);
+
+    /* Must have a transport */
+    PJ_ASSERT_RETURN(pjsua_var.tpdata[0].tp != NULL, PJ_EINVALIDOP);
+
+    PJSUA_LOCK();
+
+    /* Find empty account id. */
+    for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.acc); ++id) {
+	if (pjsua_var.acc[id].valid == PJ_FALSE)
+	    break;
+    }
+
+    /* Expect to find a slot */
+    PJ_ASSERT_ON_FAIL(	id < PJ_ARRAY_SIZE(pjsua_var.acc), 
+			{PJSUA_UNLOCK(); return PJ_EBUG;});
+
+    /* Copy config */
+    copy_acc_config(pjsua_var.pool, &pjsua_var.acc[id].cfg, cfg);
+    
+    /* Normalize registration timeout */
+    if (pjsua_var.acc[id].cfg.reg_uri.slen &&
+	pjsua_var.acc[id].cfg.reg_timeout == 0)
+    {
+	pjsua_var.acc[id].cfg.reg_timeout = PJSUA_REG_INTERVAL;
+    }
+
+    status = initialize_acc(id);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error adding account", status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    if (is_default)
+	pjsua_var.default_acc = id;
+
+    if (p_acc_id)
+	*p_acc_id = id;
+
+    pjsua_var.acc_cnt++;
+
+    PJSUA_UNLOCK();
+
+    PJ_LOG(4,(THIS_FILE, "Account %.*s added with id %d",
+	      (int)cfg->id.slen, cfg->id.ptr, id));
+
+    /* If accounts has registration enabled, start registration */
+    if (pjsua_var.acc[id].cfg.reg_uri.slen)
+	pjsua_acc_set_registration(id, PJ_TRUE);
+
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Add local account
+ */
+PJ_DEF(pj_status_t) pjsua_acc_add_local( pjsua_transport_id tid,
+					 pj_bool_t is_default,
+					 pjsua_acc_id *p_acc_id)
+{
+    pjsua_acc_config cfg;
+    pjsip_transport *tp;
+    char uri[62];
+
+    /* Transport must be valid */
+    tp = pjsua_var.tpdata[tid].tp;
+    PJ_ASSERT_RETURN(tp != NULL, PJ_EINVAL);
+    
+    pjsua_acc_config_default(&cfg);
+
+    /* Build URI for the account */
+    pj_ansi_sprintf(uri, "<sip:%.*s:%d>", 
+			 (int)tp->local_name.host.slen,
+			 tp->local_name.host.ptr,
+			 tp->local_name.port);
+
+    cfg.id = pj_str(uri);
+    
+    return pjsua_acc_add(&cfg, is_default, p_acc_id);
+}
+
+
+/*
+ * Delete account.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_id)
+{
+    PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+    PJSUA_LOCK();
+
+    /* Delete registration */
+    if (pjsua_var.acc[acc_id].regc != NULL) 
+	pjsua_acc_set_registration(acc_id, PJ_FALSE);
+
+    /* Delete server presence subscription */
+    pjsua_pres_delete_acc(acc_id);
+
+    /* Invalidate */
+    pjsua_var.acc[acc_id].valid = PJ_FALSE;
+
+    PJ_TODO(may_need_to_scan_calls);
+
+    PJSUA_UNLOCK();
+
+    PJ_LOG(4,(THIS_FILE, "Account id %d deleted", acc_id));
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify account information.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_modify( pjsua_acc_id acc_id,
+				      const pjsua_acc_config *cfg)
+{
+    PJ_TODO(pjsua_acc_modify);
+    return PJ_EINVALIDOP;
+}
+
+
+/*
+ * Modify account's presence status to be advertised to remote/presence
+ * subscribers.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_set_online_status( pjsua_acc_id acc_id,
+						 pj_bool_t is_online)
+{
+    PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+    pjsua_var.acc[acc_id].online_status = is_online;
+    pjsua_pres_refresh();
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * This callback is called by pjsip_regc when outgoing register
+ * request has completed.
+ */
+static void regc_cb(struct pjsip_regc_cbparam *param)
+{
+
+    pjsua_acc *acc = param->token;
+
+    PJSUA_LOCK();
+
+    /*
+     * Print registration status.
+     */
+    if (param->status!=PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "SIP registration error", 
+		     param->status);
+	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, 
+		   (int)param->reason.slen, param->reason.ptr));
+	pjsip_regc_destroy(acc->regc);
+	acc->regc = NULL;
+
+    } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
+
+	if (param->expiration < 1) {
+	    pjsip_regc_destroy(acc->regc);
+	    acc->regc = NULL;
+	    PJ_LOG(3,(THIS_FILE, "%s: unregistration success",
+		      pjsua_var.acc[acc->index].cfg.id.ptr));
+	} else {
+	    PJ_LOG(3, (THIS_FILE, 
+		       "%s: registration success, status=%d (%.*s), "
+		       "will re-register in %d seconds", 
+		       pjsua_var.acc[acc->index].cfg.id.ptr,
+		       param->code,
+		       (int)param->reason.slen, param->reason.ptr,
+		       param->expiration));
+	}
+
+    } else {
+	PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code));
+    }
+
+    acc->reg_last_err = param->status;
+    acc->reg_last_code = param->code;
+
+    if (pjsua_var.ua_cfg.cb.on_reg_state)
+	(*pjsua_var.ua_cfg.cb.on_reg_state)(acc->index);
+
+    PJSUA_UNLOCK();
+}
+
+
+/*
+ * Initialize client registration.
+ */
+static pj_status_t pjsua_regc_init(int acc_id)
+{
+    pjsua_acc *acc;
+    pj_status_t status;
+
+    acc = &pjsua_var.acc[acc_id];
+
+    if (acc->cfg.reg_uri.slen == 0) {
+	PJ_LOG(3,(THIS_FILE, "Registrar URI is not specified"));
+	return PJ_SUCCESS;
+    }
+
+    /* initialize SIP registration if registrar is configured */
+
+    status = pjsip_regc_create( pjsua_var.endpt, 
+				acc, &regc_cb, &acc->regc);
+
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create client registration", 
+		     status);
+	return status;
+    }
+
+    status = pjsip_regc_init( acc->regc,
+			      &acc->cfg.reg_uri, 
+			      &acc->cfg.id, 
+			      &acc->cfg.id,
+			      1, &acc->real_contact, 
+			      acc->cfg.reg_timeout);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, 
+		     "Client registration initialization error", 
+		     status);
+	return status;
+    }
+
+    /* Set credentials
+     */
+    if (acc->cred_cnt) {
+	pjsip_regc_set_credentials( acc->regc, acc->cred_cnt, acc->cred);
+    }
+
+    /* Set route-set
+     */
+    if (!pj_list_empty(&acc->route_set)) {
+	pjsip_regc_set_route_set( acc->regc, &acc->route_set );
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Update registration or perform unregistration. 
+ */
+PJ_DEF(pj_status_t) pjsua_acc_set_registration( pjsua_acc_id acc_id, 
+						pj_bool_t renew)
+{
+    pj_status_t status = 0;
+    pjsip_tx_data *tdata = 0;
+
+    PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc),
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+    PJSUA_LOCK();
+
+    if (renew) {
+	if (pjsua_var.acc[acc_id].regc == NULL) {
+	    // Need route set.
+	    status = pjsua_regc_init(acc_id);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Unable to create registration", 
+			     status);
+		goto on_return;
+	    }
+	}
+	if (!pjsua_var.acc[acc_id].regc) {
+	    status = PJ_EINVALIDOP;
+	    goto on_return;
+	}
+
+	status = pjsip_regc_register(pjsua_var.acc[acc_id].regc, 1, 
+				     &tdata);
+
+    } else {
+	if (pjsua_var.acc[acc_id].regc == NULL) {
+	    PJ_LOG(3,(THIS_FILE, "Currently not registered"));
+	    status = PJ_EINVALIDOP;
+	    goto on_return;
+	}
+	status = pjsip_regc_unregister(pjsua_var.acc[acc_id].regc, &tdata);
+    }
+
+    if (status == PJ_SUCCESS)
+	status = pjsip_regc_send( pjsua_var.acc[acc_id].regc, tdata );
+
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create/send REGISTER", 
+		     status);
+    } else {
+	PJ_LOG(3,(THIS_FILE, "%s sent",
+	         (renew? "Registration" : "Unregistration")));
+    }
+
+on_return:
+    PJSUA_UNLOCK();
+    return status;
+}
+
+
+/*
+ * Get account information.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_get_info( pjsua_acc_id acc_id,
+					pjsua_acc_info *info)
+{
+    pjsua_acc *acc = &pjsua_var.acc[acc_id];
+    pjsua_acc_config *acc_cfg = &pjsua_var.acc[acc_id].cfg;
+
+    PJ_ASSERT_RETURN(info != NULL, PJ_EINVAL);
+    
+    pj_memset(info, 0, sizeof(pjsua_acc_info));
+
+    PJ_ASSERT_RETURN(acc_id>=0 && acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc), 
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.acc[acc_id].valid, PJ_EINVALIDOP);
+
+    PJSUA_LOCK();
+    
+    if (pjsua_var.acc[acc_id].valid == PJ_FALSE) {
+	PJSUA_UNLOCK();
+	return PJ_EINVALIDOP;
+    }
+
+    info->id = acc_id;
+    info->is_default = (pjsua_var.default_acc == acc_id);
+    info->acc_uri = acc_cfg->id;
+    info->has_registration = (acc->cfg.reg_uri.slen > 0);
+    info->online_status = acc->online_status;
+    
+    if (acc->reg_last_err) {
+	info->status = acc->reg_last_err;
+	pj_strerror(acc->reg_last_err, info->buf_, sizeof(info->buf_));
+	info->status_text = pj_str(info->buf_);
+    } else if (acc->reg_last_code) {
+	if (acc->regc) {
+	    info->status = acc->reg_last_code;
+	    info->status_text = *pjsip_get_status_text(acc->reg_last_code);
+	} else {
+	    info->status = 0;
+	    info->status_text = pj_str("not registered");
+	}
+    } else if (acc->cfg.reg_uri.slen) {
+	info->status = 100;
+	info->status_text = pj_str("In Progress");
+    } else {
+	info->status = 0;
+	info->status_text = pj_str("does not register");
+    }
+    
+    if (acc->regc) {
+	pjsip_regc_info regc_info;
+	pjsip_regc_get_info(acc->regc, &regc_info);
+	info->expires = regc_info.next_reg;
+    } else {
+	info->expires = -1;
+    }
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+
+}
+
+
+/*
+ * Enum accounts all account ids.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_accs(pjsua_acc_id ids[],
+				    unsigned *count )
+{
+    unsigned i, c;
+
+    PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+	if (!pjsua_var.acc[i].valid)
+	    continue;
+	ids[c] = i;
+	++c;
+    }
+
+    *count = c;
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Enum accounts info.
+ */
+PJ_DEF(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[],
+					 unsigned *count )
+{
+    unsigned i, c;
+
+    PJ_ASSERT_RETURN(info && *count, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+	if (!pjsua_var.acc[i].valid)
+	    continue;
+
+	pjsua_acc_get_info(i, &info[c]);
+	++c;
+    }
+
+    *count = c;
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * This is an internal function to find the most appropriate account to
+ * used to reach to the specified URL.
+ */
+PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *url)
+{
+    pj_str_t tmp;
+    pjsip_uri *uri;
+    pjsip_sip_uri *sip_uri;
+    unsigned acc_id;
+
+    PJSUA_LOCK();
+
+    PJ_TODO(dont_use_pjsua_pool);
+
+    pj_strdup_with_null(pjsua_var.pool, &tmp, url);
+
+    uri = pjsip_parse_uri(pjsua_var.pool, tmp.ptr, tmp.slen, 0);
+    if (!uri) {
+	acc_id = pjsua_var.default_acc;
+	goto on_return;
+    }
+
+    if (!PJSIP_URI_SCHEME_IS_SIP(uri) && 
+	!PJSIP_URI_SCHEME_IS_SIPS(uri)) 
+    {
+	/* Return the first account with proxy */
+	for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
+	    if (!pjsua_var.acc[acc_id].valid)
+		continue;
+	    if (!pj_list_empty(&pjsua_var.acc[acc_id].route_set))
+		break;
+	}
+
+	if (acc_id != PJ_ARRAY_SIZE(pjsua_var.acc)) {
+	    /* Found rather matching account */
+	    goto on_return;
+	}
+
+	/* Not found, use default account */
+	acc_id = pjsua_var.default_acc;
+	goto on_return;
+    }
+
+    sip_uri = pjsip_uri_get_uri(uri);
+
+    /* Find matching domain */
+    for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
+	if (!pjsua_var.acc[acc_id].valid)
+	    continue;
+	if (pj_stricmp(&pjsua_var.acc[acc_id].host_part, &sip_uri->host)==0)
+	    break;
+    }
+
+    if (acc_id == PJ_ARRAY_SIZE(pjsua_var.acc)) {
+	/* Just use default account */
+	acc_id = pjsua_var.default_acc;
+    }
+
+on_return:
+    PJSUA_UNLOCK();
+
+    return acc_id;
+}
+
+
+/*
+ * This is an internal function to find the most appropriate account to be
+ * used to handle incoming calls.
+ */
+PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata)
+{
+    pjsip_uri *uri;
+    pjsip_sip_uri *sip_uri;
+    unsigned acc_id;
+
+    uri = rdata->msg_info.to->uri;
+
+    /* Just return default account if To URI is not SIP: */
+    if (!PJSIP_URI_SCHEME_IS_SIP(uri) && 
+	!PJSIP_URI_SCHEME_IS_SIPS(uri)) 
+    {
+	return pjsua_var.default_acc;
+    }
+
+
+    PJSUA_LOCK();
+
+    sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+
+    /* Find account which has matching username and domain. */
+    for (acc_id=0; acc_id < pjsua_var.acc_cnt; ++acc_id) {
+
+	pjsua_acc *acc = &pjsua_var.acc[acc_id];
+
+	if (pj_stricmp(&acc->user_part, &sip_uri->user)==0 &&
+	    pj_stricmp(&acc->host_part, &sip_uri->host)==0) 
+	{
+	    /* Match ! */
+	    PJSUA_UNLOCK();
+	    return acc_id;
+	}
+    }
+
+    /* No matching, try match domain part only. */
+    for (acc_id=0; acc_id < pjsua_var.acc_cnt; ++acc_id) {
+
+	pjsua_acc *acc = &pjsua_var.acc[acc_id];
+
+	if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) {
+	    /* Match ! */
+	    PJSUA_UNLOCK();
+	    return acc_id;
+	}
+    }
+
+    /* Still no match, use default account */
+    PJSUA_UNLOCK();
+    return pjsua_var.default_acc;
+}
+
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 502)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_call.c	(revision 503)
@@ -18,204 +18,157 @@
  */
 #include <pjsua-lib/pjsua.h>
-#include <pj/log.h>
-#include "pjsua_imp.h"
-
-/*
- * pjsua_call.c
- *
- * Call (INVITE) related stuffs.
- */
-
-#define THIS_FILE   "pjsua_call.c"
-
-
-#define REFRESH_CALL_TIMER	0x63
-#define HANGUP_CALL_TIMER	0x64
-
-/* Proto */
-static void schedule_call_timer( pjsua_call *call, pj_timer_entry *e,
-				 int timer_type, int duration );
-
-/*
- * Timer callback when UAS needs to send re-INVITE to see if remote
- * is still there.
- */
-static void call_on_timer(pj_timer_heap_t *ht, pj_timer_entry *e)
-{
-    pjsua_call *call = e->user_data;
-
-    PJ_UNUSED_ARG(ht);
-
-    if (e->id == REFRESH_CALL_TIMER) {
-
-	/* If call is still not connected, hangup. */
-	if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
-	    PJ_LOG(3,(THIS_FILE, "Refresh call timer is called when "
-		      "invite is still not confirmed. Call %d will "
-		      "disconnect.", call->index));
-	    pjsua_call_hangup(call->index);
-	} else {
-	    PJ_LOG(3,(THIS_FILE, "Refreshing call %d", call->index));
-	    schedule_call_timer(call,e,REFRESH_CALL_TIMER,
-				pjsua.config.uas_refresh);
-	    pjsua_call_reinvite(call->index);
-	}
-
-    } else if (e->id == HANGUP_CALL_TIMER) {
-	PJ_LOG(3,(THIS_FILE, "Call %d duration exceeded, disconnecting call",
-			     call->index));
-	pjsua_call_hangup(call->index);
-
-    }
-}
-
-/*
- * Schedule call timer.
- */
-static void schedule_call_timer( pjsua_call *call, pj_timer_entry *e,
-				 int timer_type, int duration )
-{
-    pj_time_val timeout;
-
-    if (duration == 0) {
-	/* Cancel timer. */
-	if (e->id != 0) {
-	    pjsip_endpt_cancel_timer(pjsua.endpt, e);
-	    e->id = 0;
-	}
-
-    } else {
-	/* Schedule timer. */
-	timeout.sec = duration;
-	timeout.msec = 0;
-
-	e->cb = &call_on_timer;
-	e->id = timer_type;
-	e->user_data = call;
-
-	pjsip_endpt_schedule_timer( pjsua.endpt, e, &timeout);
-    }
-}
-
-
-/*
- * Destroy the call's media
- */
-static pj_status_t call_destroy_media(int call_index)
-{
-    pjsua_call *call = &pjsua.calls[call_index];
-
-    if (call->conf_slot > 0) {
-	pjmedia_conf_remove_port(pjsua.mconf, call->conf_slot);
-	call->conf_slot = 0;
-    }
-
-    if (call->session) {
-	/* Destroy session (this will also close RTP/RTCP sockets). */
-	pjmedia_session_destroy(call->session);
-	call->session = NULL;
-
-	PJ_LOG(3,(THIS_FILE, "Media session for call %d is destroyed", 
-			     call_index));
-
-    }
-
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE		"pjsua_call.c"
+
+
+/* This callback receives notification from invite session when the
+ * session state has changed.
+ */
+static void pjsua_call_on_state_changed(pjsip_inv_session *inv, 
+					pjsip_event *e);
+
+/* This callback is called by invite session framework when UAC session
+ * has forked.
+ */
+static void pjsua_call_on_forked( pjsip_inv_session *inv, 
+				  pjsip_event *e);
+
+/*
+ * Callback to be called when SDP offer/answer negotiation has just completed
+ * in the session. This function will start/update media if negotiation
+ * has succeeded.
+ */
+static void pjsua_call_on_media_update(pjsip_inv_session *inv,
+				       pj_status_t status);
+
+/*
+ * Called when session received new offer.
+ */
+static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
+				   const pjmedia_sdp_session *offer);
+
+/*
+ * This callback is called when transaction state has changed in INVITE
+ * session. We use this to trap:
+ *  - incoming REFER request.
+ *  - incoming MESSAGE request.
+ */
+static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
+					    pjsip_transaction *tsx,
+					    pjsip_event *e);
+
+
+/* Destroy the call's media */
+static pj_status_t call_destroy_media(int call_id);
+
+/* Create inactive SDP for call hold. */
+static pj_status_t create_inactive_sdp(pjsua_call *call,
+				       pjmedia_sdp_session **p_answer);
+
+
+/*
+ * Reset call descriptor.
+ */
+static void reset_call(pjsua_call_id id)
+{
+    pjsua_call *call = &pjsua_var.calls[id];
+
+    call->index = id;
+    call->inv = NULL;
+    call->user_data = NULL;
+    call->session = NULL;
+    call->xfer_sub = NULL;
+    call->last_code = 0;
+    call->conf_slot = PJSUA_INVALID_ID;
+    call->last_text.ptr = call->last_text_buf_;
+    call->last_text.slen = 0;
+}
+
+
+/*
+ * Init call subsystem.
+ */
+pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg)
+{
+    pjsip_inv_callback inv_cb;
+    unsigned i;
+    pj_status_t status;
+
+    /* Init calls array. */
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.calls); ++i)
+	reset_call(i);
+
+    /* Copy config */
+    pjsua_config_dup(pjsua_var.pool, &pjsua_var.ua_cfg, cfg);
+
+    /* Initialize invite session callback. */
+    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_var.endpt, &inv_cb);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    return status;
+}
+
+
+/*
+ * Start call subsystem.
+ */
+pj_status_t pjsua_call_subsys_start(void)
+{
+    /* Nothing to do */
     return PJ_SUCCESS;
 }
 
 
-/**
+/*
  * Get maximum number of calls configured in pjsua.
  */
 PJ_DEF(unsigned) pjsua_call_get_max_count(void)
 {
-    return pjsua.config.max_calls;
-}
-
-
-/**
- * Get current number of active calls.
+    return pjsua_var.ua_cfg.max_calls;
+}
+
+
+/*
+ * Get number of currently active calls.
  */
 PJ_DEF(unsigned) pjsua_call_get_count(void)
 {
-    return pjsua.call_cnt;
-}
-
-
-/**
- * Check if the specified call is active.
- */
-PJ_DEF(pj_bool_t) pjsua_call_is_active(unsigned call_index)
-{
-    PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls,
-		     PJ_EINVAL);
-    return pjsua.calls[call_index].inv != NULL &&
-	   pjsua.calls[call_index].inv->state != PJSIP_INV_STATE_DISCONNECTED;
-}
-
-/**
- * Check if call has a media session.
- */
-PJ_DEF(pj_bool_t) pjsua_call_has_media(unsigned call_index)
-{
-    PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, PJ_EINVAL);
-    return pjsua.calls[call_index].session != NULL;
-}
-
-
-/**
- * Get call info.
- */
-PJ_DEF(pj_status_t) pjsua_call_get_info( unsigned call_index,
-					 pjsua_call_info *info)
-{
-    pjsua_call *call;
-
-    PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls,
-		     PJ_EINVAL);
-
-    pj_memset(info, 0, sizeof(pjsua_call_info));
-
-    call = &pjsua.calls[call_index];
-    info->active = pjsua_call_is_active(call_index);
-
-    if (call->inv == NULL)
-	return PJ_SUCCESS;
-
-    info->index = call_index;
-    info->role = call->inv->role;
-    info->local_info = call->inv->dlg->local.info_str;
-    info->remote_info = call->inv->dlg->remote.info_str;
-    info->state = call->inv->state;
-    info->state_text = pj_str((char*)pjsip_inv_state_name(info->state));
-    
-    if (info->state >= PJSIP_INV_STATE_DISCONNECTED) {
-
-	info->total_duration = call->dis_time;
-	PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
-
-	if (call->conn_time.sec) {
-	    info->connect_duration = call->dis_time;
-	    PJ_TIME_VAL_SUB(info->total_duration, call->conn_time);
-	}
-
-    } else if (info->state == PJSIP_INV_STATE_CONFIRMED) {
-
-	pj_gettimeofday(&info->total_duration);
-	PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
-
-	pj_gettimeofday(&info->connect_duration);
-	PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time);
-
-    } else {
-	pj_gettimeofday(&info->total_duration);
-	PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
-    }
-
-    info->last_status = call->last_code;
-    info->last_status_text = *pjsip_get_status_text(info->last_status);
-
-    info->has_media = (call->session != NULL);
-    info->conf_slot = call->conf_slot;
+    return pjsua_var.call_cnt;
+}
+
+
+/*
+ * Enum calls.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_calls( pjsua_call_id ids[],
+				      unsigned *count)
+{
+    unsigned i, c;
+
+    PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    for (i=0, c=0; c<*count && i<pjsua_var.ua_cfg.max_calls; ++i) {
+	if (!pjsua_var.calls[i].inv)
+	    continue;
+	ids[c] = i;
+	++c;
+    }
+
+    *count = c;
+
+    PJSUA_UNLOCK();
 
     return PJ_SUCCESS;
@@ -223,68 +176,68 @@
 
 
-/**
- * Duplicate call info.
- */
-PJ_DEF(void) pjsua_call_info_dup( pj_pool_t *pool,
-				  pjsua_call_info *dst_info,
-				  const pjsua_call_info *src_info)
-{
-    PJ_ASSERT_ON_FAIL(pool && dst_info && src_info, return);
-
-    pj_memcpy(dst_info, src_info, sizeof(pjsua_call_info));
-
-    pj_strdup(pool, &dst_info->local_info, &src_info->local_info);
-    pj_strdup(pool, &dst_info->remote_info, &src_info->remote_info);
-    
-    /* state_text and cause_text belong to pjsip, so don't need to be
-     * duplicated because they'll always be available.
-     */
-}
-
-
-/**
- * Make outgoing call.
- */
-PJ_DEF(pj_status_t) pjsua_call_make_call(unsigned acc_index,
-					 const pj_str_t *dest_uri,
-					 int *p_call_index)
+/*
+ * Make outgoing call to the specified URI using the specified account.
+ */
+PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id,
+					  const pj_str_t *dest_uri,
+					  unsigned options,
+					  void *user_data,
+					  const pjsua_msg_data *msg_data,
+					  pjsua_call_id *p_call_id)
 {
     pjsip_dialog *dlg = NULL;
     pjmedia_sdp_session *offer;
     pjsip_inv_session *inv = NULL;
-    unsigned call_index;
+    pjsua_acc *acc;
+    pjsua_call *call;
+    unsigned call_id;
     pjsip_tx_data *tdata;
     pj_status_t status;
 
 
-    PJ_ASSERT_RETURN(acc_index==0 || acc_index < pjsua.config.acc_cnt, 
+    /* Check that account is valid */
+    PJ_ASSERT_RETURN(acc_id>=0 || acc_id<PJ_ARRAY_SIZE(pjsua_var.acc), 
 		     PJ_EINVAL);
 
+    /* Options must be zero for now */
+    PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    acc = &pjsua_var.acc[acc_id];
+    if (!acc->valid) {
+	pjsua_perror(THIS_FILE, "Unable to make call because account "
+		     "is not valid", PJ_EINVALIDOP);
+	PJSUA_UNLOCK();
+	return PJ_EINVALIDOP;
+    }
 
     /* Find free call slot. */
-    for (call_index=0; call_index<pjsua.config.max_calls; ++call_index) {
-	if (pjsua.calls[call_index].inv == NULL)
+    for (call_id=0; call_id<pjsua_var.ua_cfg.max_calls; ++call_id) {
+	if (pjsua_var.calls[call_id].inv == NULL)
 	    break;
     }
 
-    if (call_index == pjsua.config.max_calls) {
-	PJ_LOG(3,(THIS_FILE, "Error: too many calls!"));
+    if (call_id == pjsua_var.ua_cfg.max_calls) {
+	pjsua_perror(THIS_FILE, "Error making file", PJ_ETOOMANY);
+	PJSUA_UNLOCK();
 	return PJ_ETOOMANY;
     }
 
+    call = &pjsua_var.calls[call_id];
+
     /* Mark call start time. */
-    pj_gettimeofday(&pjsua.calls[call_index].start_time);
+    pj_gettimeofday(&call->start_time);
 
     /* Reset first response time */
-    pjsua.calls[call_index].res_time.sec = 0;
+    call->res_time.sec = 0;
 
     /* Create outgoing dialog: */
     status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
-				   &pjsua.config.acc_config[acc_index].id,
-				   &pjsua.config.acc_config[acc_index].contact,
-				   dest_uri, dest_uri,
-				   &dlg);
+				   &acc->cfg.id, &acc->cfg.contact,
+				   dest_uri, dest_uri, &dlg);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Dialog creation failed", status);
+	PJSUA_UNLOCK();
 	return status;
     }
@@ -292,7 +245,6 @@
     /* Get media capability from media endpoint: */
 
-    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, dlg->pool, 1, 
-				       &pjsua.calls[call_index].skinfo, 
-				       &offer);
+    status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, dlg->pool, 1, 
+				       &call->skinfo, &offer);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status);
@@ -311,22 +263,21 @@
     /* Create and associate our data in the session. */
 
-    pjsua.calls[call_index].inv = inv;
-
-    dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
-    inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
-
+    call->inv = inv;
+
+    dlg->mod_data[pjsua_var.mod.id] = call;
+    inv->mod_data[pjsua_var.mod.id] = call;
+
+    /* Attach user data */
+    call->user_data = user_data;
 
     /* Set dialog Route-Set: */
-
-    if (!pj_list_empty(&pjsua.acc[acc_index].route_set))
-	pjsip_dlg_set_route_set(dlg, &pjsua.acc[acc_index].route_set);
+    if (!pj_list_empty(&acc->route_set))
+	pjsip_dlg_set_route_set(dlg, &acc->route_set);
 
 
     /* Set credentials: */
-    if (pjsua.config.acc_config[acc_index].cred_count) {
-	pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index];
+    if (acc->cred_cnt) {
 	pjsip_auth_clt_set_credentials( &dlg->auth_sess, 
-					acc_cfg->cred_count,
-					acc_cfg->cred_info);
+					acc->cred_cnt, acc->cred);
     }
 
@@ -341,4 +292,8 @@
     }
 
+
+    /* Add additional headers etc */
+
+    pjsua_process_msg_data( tdata, msg_data);
 
     /* Send initial INVITE: */
@@ -357,11 +312,12 @@
     }
 
-
     /* Done. */
 
-    ++pjsua.call_cnt;
-
-    if (p_call_index)
-	*p_call_index = call_index;
+    ++pjsua_var.call_cnt;
+
+    if (p_call_id)
+	*p_call_id = call_id;
+
+    PJSUA_UNLOCK();
 
     return PJ_SUCCESS;
@@ -375,38 +331,9 @@
     }
 
-    if (call_index != -1) {
-	pjsua.calls[call_index].inv = NULL;
-    }
-    return status;
-}
-
-
-/**
- * Answer call.
- */
-PJ_DEF(pj_status_t) pjsua_call_answer(int call_index, int code)
-{
-    pjsip_tx_data *tdata;
-    pj_status_t status;
-
-    PJ_ASSERT_RETURN( call_index >= 0 && 
-		      call_index < (int)pjsua.config.max_calls,
-		      PJ_EINVAL);
-
-    if (pjsua.calls[call_index].inv == NULL) {
-	PJ_LOG(3,(THIS_FILE, "Call %d already disconnected"));
-	return PJSIP_ESESSIONTERMINATED;
-    }
-
-    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);
-
+    if (call_id != -1) {
+	reset_call(call_id);
+    }
+
+    PJSUA_UNLOCK();
     return status;
 }
@@ -415,4 +342,5 @@
 /**
  * Handle incoming INVITE request.
+ * Called by pjsua_core.c
  */
 pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
@@ -424,6 +352,7 @@
     unsigned options = 0;
     pjsip_inv_session *inv = NULL;
-    int acc_index;
-    unsigned call_index;
+    int acc_id;
+    pjsua_call *call;
+    int call_id = -1;
     pjmedia_sdp_session *answer;
     pj_status_t status;
@@ -442,5 +371,5 @@
     /* Verify that we can handle the request. */
     status = pjsip_inv_verify_request(rdata, &options, NULL, NULL,
-				      pjsua.endpt, &response);
+				      pjsua_var.endpt, &response);
     if (status != PJ_SUCCESS) {
 
@@ -453,5 +382,5 @@
 
 	    pjsip_get_response_addr(response->pool, rdata, &res_addr);
-	    pjsip_endpt_send_response(pjsua.endpt, &res_addr, response, 
+	    pjsip_endpt_send_response(pjsua_var.endpt, &res_addr, response, 
 				      NULL, NULL);
 
@@ -459,5 +388,5 @@
 
 	    /* Respond with 500 (Internal Server Error) */
-	    pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
+	    pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
 					  NULL, NULL);
 	}
@@ -472,11 +401,11 @@
 
     /* Find free call slot. */
-    for (call_index=0; call_index < pjsua.config.max_calls; ++call_index) {
-	if (pjsua.calls[call_index].inv == NULL)
+    for (call_id=0; call_id<(int)pjsua_var.ua_cfg.max_calls; ++call_id) {
+	if (pjsua_var.calls[call_id].inv == NULL)
 	    break;
     }
 
-    if (call_index == PJSUA_MAX_CALLS) {
-	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 
+    if (call_id == (int)pjsua_var.ua_cfg.max_calls) {
+	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 
 				      PJSIP_SC_BUSY_HERE, NULL,
 				      NULL, NULL);
@@ -484,37 +413,35 @@
     }
 
+    /* Clear call descriptor */
+    reset_call(call_id);
+
+    call = &pjsua_var.calls[call_id];
+
     /* Mark call start time. */
-    pj_gettimeofday(&pjsua.calls[call_index].start_time);
-
-    /* Reset first response time */
-    pjsua.calls[call_index].res_time.sec = 0;
+    pj_gettimeofday(&call->start_time);
 
     /* Get media capability from media endpoint: */
-
-    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool, 1,
-				       &pjsua.calls[call_index].skinfo, 
-				       &answer );
-    if (status != PJ_SUCCESS) {
-	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
+    status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, 
+				       rdata->tp_info.pool, 1,
+				       &call->skinfo, &answer );
+    if (status != PJ_SUCCESS) {
+	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
 				      NULL, NULL);
-
 	return PJ_TRUE;
     }
 
-    /* TODO: 
-     *
+    /* 
      * Get which account is most likely to be associated with this incoming
      * call. We need the account to find which contact URI to put for
      * the call.
      */
-    acc_index = 0;
+    acc_id = pjsua_acc_find_for_incoming(rdata);
 
     /* Create dialog: */
-
     status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata,
-				   &pjsua.config.acc_config[acc_index].contact,
+				   &pjsua_var.acc[acc_id].cfg.contact, 
 				   &dlg);
     if (status != PJ_SUCCESS) {
-	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL,
+	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 500, NULL,
 				      NULL, NULL);
 
@@ -522,10 +449,14 @@
     }
 
+    /* Set credentials */
+    if (pjsua_var.acc[acc_id].cred_cnt) {
+	pjsip_auth_clt_set_credentials(&dlg->auth_sess, 
+				       pjsua_var.acc[acc_id].cred_cnt,
+				       pjsua_var.acc[acc_id].cred);
+    }
 
     /* Create invite session: */
-
     status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv);
     if (status != PJ_SUCCESS) {
-
 	pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL);
 	pjsip_dlg_terminate(dlg);
@@ -534,10 +465,9 @@
 
 
-    /* Create and attach pjsua data to the dialog: */
-
-    pjsua.calls[call_index].inv = inv;
-
-    dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
-    inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
+    /* Create and attach pjsua_var data to the dialog: */
+    call->inv = inv;
+
+    dlg->mod_data[pjsua_var.mod.id] = call;
+    inv->mod_data[pjsua_var.mod.id] = call;
 
 
@@ -545,26 +475,12 @@
      * If auto-answer flag is set, send 200 straight away, otherwise send 100.
      */
-    
     status = pjsip_inv_initial_answer(inv, rdata, 
-				      (pjsua.config.auto_answer ? 
-				      pjsua.config.auto_answer : 100), 
-				      NULL, NULL, &response);
-    if (status != PJ_SUCCESS) {
-	
-	int st_code;
-
+				      100, NULL, NULL, &response);
+    if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to send answer to incoming INVITE", 
 		     status);
 
-	/* If failed to send 2xx response, there's a good chance that it is
-	 * because SDP negotiation has failed.
-	 */
-	if (pjsua.config.auto_answer/100 == 2)
-	    st_code = PJSIP_SC_UNSUPPORTED_MEDIA_TYPE;
-	else
-	    st_code = 500;
-
-	pjsip_dlg_respond(dlg, rdata, st_code, NULL, NULL, NULL);
-	pjsip_inv_terminate(inv, st_code, PJ_FALSE);
+	pjsip_dlg_respond(dlg, rdata, 500, NULL, NULL, NULL);
+	pjsip_inv_terminate(inv, 500, PJ_FALSE);
 	return PJ_TRUE;
 
@@ -575,47 +491,10 @@
     }
 
-    if (pjsua.config.auto_answer < 200) {
-	PJ_LOG(3,(THIS_FILE,
-		  "\nIncoming call!!\n"
-		  "From: %.*s\n"
-		  "To:   %.*s\n"
-		  "(press 'a' to answer, 'h' to decline)",
-		  (int)dlg->remote.info_str.slen,
-		  dlg->remote.info_str.ptr,
-		  (int)dlg->local.info_str.slen,
-		  dlg->local.info_str.ptr));
-    } else {
-	PJ_LOG(3,(THIS_FILE,
-		  "Call From:%.*s To:%.*s was answered with %d (%s)",
-		  (int)dlg->remote.info_str.slen,
-		  dlg->remote.info_str.ptr,
-		  (int)dlg->local.info_str.slen,
-		  dlg->local.info_str.ptr,
-		  pjsua.config.auto_answer,
-		  pjsip_get_status_text(pjsua.config.auto_answer)->ptr ));
-    }
-
-    ++pjsua.call_cnt;
-
-    /* Schedule timer to refresh. */
-    if (pjsua.config.uas_refresh > 0) {
-	schedule_call_timer( &pjsua.calls[call_index], 
-			     &pjsua.calls[call_index].refresh_tm,
-			     REFRESH_CALL_TIMER,
-			     pjsua.config.uas_refresh);
-    }
-
-    /* Schedule timer to hangup call. */
-    if (pjsua.config.uas_duration > 0) {
-	schedule_call_timer( &pjsua.calls[call_index], 
-			     &pjsua.calls[call_index].hangup_tm,
-			     HANGUP_CALL_TIMER,
-			     pjsua.config.uas_duration);
-    }
+    ++pjsua_var.call_cnt;
+
 
     /* Notify application */
-    if (pjsua.cb.on_incoming_call)
-	pjsua.cb.on_incoming_call(acc_index, call_index, rdata);
-
+    if (pjsua_var.ua_cfg.cb.on_incoming_call)
+	pjsua_var.ua_cfg.cb.on_incoming_call(acc_id, call_id, rdata);
 
     /* This INVITE request has been handled. */
@@ -624,4 +503,1054 @@
 
 
+
+/*
+ * Check if the specified call has active INVITE session and the INVITE
+ * session has not been disconnected.
+ */
+PJ_DEF(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id)
+{
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+    return pjsua_var.calls[call_id].inv != NULL &&
+	   pjsua_var.calls[call_id].inv->state != PJSIP_INV_STATE_DISCONNECTED;
+}
+
+
+/*
+ * Check if call has an active media session.
+ */
+PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id)
+{
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, 
+		     PJ_EINVAL);
+    return pjsua_var.calls[call_id].session != NULL;
+}
+
+
+/*
+ * Get the conference port identification associated with the call.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
+{
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls, 
+		     PJ_EINVAL);
+    return pjsua_var.calls[call_id].conf_slot;
+}
+
+
+/*
+ * Obtain detail information about the specified call.
+ */
+PJ_DEF(pj_status_t) pjsua_call_get_info( pjsua_call_id call_id,
+					 pjsua_call_info *info)
+{
+    pjsua_call *call;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    pj_memset(info, 0, sizeof(*info));
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (call->inv == NULL) {
+	PJSUA_UNLOCK();
+	return PJ_SUCCESS;
+    }
+
+    pjsip_dlg_inc_lock(call->inv->dlg);
+
+
+    /* id and role */
+    info->id = call_id;
+    info->role = call->inv->role;
+
+    /* local info */
+    info->local_info.ptr = info->buf_.local_info;
+    pj_strncpy(&info->local_info, &call->inv->dlg->local.info_str,
+	       sizeof(info->buf_.local_info));
+
+    /* local contact */
+    info->local_contact.ptr = info->buf_.local_contact;
+    info->local_contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR,
+					       call->inv->dlg->local.contact->uri,
+					       info->local_contact.ptr,
+					       sizeof(info->buf_.local_contact));
+
+    /* remote info */
+    info->remote_info.ptr = info->buf_.remote_info;
+    pj_strncpy(&info->remote_info, &call->inv->dlg->remote.info_str,
+	       sizeof(info->buf_.remote_info));
+
+    /* remote contact */
+    if (call->inv->dlg->remote.contact) {
+	int len;
+	info->remote_contact.ptr = info->buf_.remote_contact;
+	len = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR,
+			      call->inv->dlg->remote.contact->uri,
+			      info->remote_contact.ptr,
+			      sizeof(info->buf_.remote_contact));
+	if (len < 0) len = 0;
+	info->remote_contact.slen = len;
+    } else {
+	info->remote_contact.slen = 0;
+    }
+
+    /* call id */
+    info->call_id.ptr = info->buf_.call_id;
+    pj_strncpy(&info->call_id, &call->inv->dlg->call_id->id,
+	       sizeof(info->buf_.call_id));
+
+    /* state, state_text */
+    info->state = call->inv->state;
+    info->state_text = pj_str((char*)pjsip_inv_state_name(info->state));
+
+    /* If call is disconnected, set the last_status from the cause code */
+    if (call->inv->state >= PJSIP_INV_STATE_DISCONNECTED) {
+	/* last_status, last_status_text */
+	info->last_status = call->inv->cause;
+
+	info->last_status_text.ptr = info->buf_.last_status_text;
+	pj_strncpy(&info->last_status_text, &call->inv->cause_text,
+		   sizeof(info->buf_.last_status_text));
+    } else {
+	/* last_status, last_status_text */
+	info->last_status = call->last_code;
+
+	info->last_status_text.ptr = info->buf_.last_status_text;
+	pj_strncpy(&info->last_status_text, &call->last_text,
+		   sizeof(info->buf_.last_status_text));
+    }
+    
+    /* media status and dir */
+    info->media_status = call->media_st;
+    info->media_dir = call->media_dir;
+
+
+    /* conference slot number */
+    info->conf_slot = call->conf_slot;
+
+    /* calculate duration */
+    if (info->state >= PJSIP_INV_STATE_DISCONNECTED) {
+
+	info->total_duration = call->dis_time;
+	PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
+
+	if (call->conn_time.sec) {
+	    info->connect_duration = call->dis_time;
+	    PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time);
+	}
+
+    } else if (info->state == PJSIP_INV_STATE_CONFIRMED) {
+
+	pj_gettimeofday(&info->total_duration);
+	PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
+
+	pj_gettimeofday(&info->connect_duration);
+	PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time);
+
+    } else {
+	pj_gettimeofday(&info->total_duration);
+	PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
+    }
+
+    pjsip_dlg_dec_lock(call->inv->dlg);
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Attach application specific data to the call.
+ */
+PJ_DEF(pj_status_t) pjsua_call_set_user_data( pjsua_call_id call_id,
+					      void *user_data)
+{
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+    pjsua_var.calls[call_id].user_data = user_data;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get user data attached to the call.
+ */
+PJ_DEF(void*) pjsua_call_get_user_data(pjsua_call_id call_id)
+{
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     NULL);
+    return pjsua_var.calls[call_id].user_data;
+}
+
+
+/*
+ * Send response to incoming INVITE request.
+ */
+PJ_DEF(pj_status_t) pjsua_call_answer( pjsua_call_id call_id, 
+				       unsigned code,
+				       const pj_str_t *reason,
+				       const pjsua_msg_data *msg_data)
+{
+    pjsua_call *call;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (call->inv == NULL) {
+	PJ_LOG(3,(THIS_FILE, "Call %d already disconnected", call_id));
+	PJSUA_UNLOCK();
+	return PJSIP_ESESSIONTERMINATED;
+    }
+
+    /* Create response message */
+    status = pjsip_inv_answer(call->inv, code, reason, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error creating response", 
+		     status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    /* Add additional headers etc */
+    pjsua_process_msg_data( tdata, msg_data);
+
+    /* Send the message */
+    status = pjsip_inv_send_msg(call->inv, tdata);
+    if (status != PJ_SUCCESS)
+	pjsua_perror(THIS_FILE, "Error sending response", 
+		     status);
+
+    PJSUA_UNLOCK();
+
+    return status;
+}
+
+
+/*
+ * Hangup call by using method that is appropriate according to the
+ * call state.
+ */
+PJ_DEF(pj_status_t) pjsua_call_hangup(pjsua_call_id call_id,
+				      unsigned code,
+				      const pj_str_t *reason,
+				      const pjsua_msg_data *msg_data)
+{
+    pjsua_call *call;
+    pj_status_t status;
+    pjsip_tx_data *tdata;
+
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	PJSUA_UNLOCK();
+	return PJ_EINVAL;
+    }
+
+    if (code==0) {
+	if (call->inv->state == PJSIP_INV_STATE_CONFIRMED)
+	    code = PJSIP_SC_OK;
+	else if (call->inv->role == PJSIP_ROLE_UAS)
+	    code = PJSIP_SC_DECLINE;
+	else
+	    code = PJSIP_SC_REQUEST_TERMINATED;
+    }
+
+    status = pjsip_inv_end_session(call->inv, code, reason, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, 
+		     "Failed to create end session message", 
+		     status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    /* pjsip_inv_end_session may return PJ_SUCCESS with NULL 
+     * as p_tdata when INVITE transaction has not been answered
+     * with any provisional responses.
+     */
+    if (tdata == NULL) {
+	PJSUA_UNLOCK();
+	return PJ_SUCCESS;
+    }
+
+    /* Add additional headers etc */
+    pjsua_process_msg_data( tdata, msg_data);
+
+    /* Send the message */
+    status = pjsip_inv_send_msg(call->inv, tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, 
+		     "Failed to send end session message", 
+		     status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Put the specified call on hold.
+ */
+PJ_DEF(pj_status_t) pjsua_call_set_hold(pjsua_call_id call_id,
+					const pjsua_msg_data *msg_data)
+{
+    pjmedia_sdp_session *sdp;
+    pjsua_call *call;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+    
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	PJSUA_UNLOCK();
+	return PJSIP_ESESSIONTERMINATED;
+    }
+
+    if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
+	PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed"));
+	PJSUA_UNLOCK();
+	return PJSIP_ESESSIONSTATE;
+    }
+
+    status = create_inactive_sdp(call, &sdp);
+    if (status != PJ_SUCCESS) {
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    /* Create re-INVITE with new offer */
+    status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    /* Add additional headers etc */
+    pjsua_process_msg_data( tdata, msg_data);
+
+    /* Send the request */
+    status = pjsip_inv_send_msg( call->inv, tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Send re-INVITE (to release hold).
+ */
+PJ_DEF(pj_status_t) pjsua_call_reinvite( pjsua_call_id call_id,
+					 pj_bool_t unhold,
+					 const pjsua_msg_data *msg_data)
+{
+    pjmedia_sdp_session *sdp;
+    pjsip_tx_data *tdata;
+    pjsua_call *call;
+    pj_status_t status;
+
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	PJSUA_UNLOCK();
+	return PJSIP_ESESSIONTERMINATED;
+    }
+
+
+    if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
+	PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
+	PJSUA_UNLOCK();
+	return PJSIP_ESESSIONSTATE;
+    }
+
+    /* Create SDP */
+    status = pjmedia_endpt_create_sdp( pjsua_var.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_UNLOCK();
+	return status;
+    }
+
+    /* Create re-INVITE with new offer */
+    status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    /* Add additional headers etc */
+    pjsua_process_msg_data( tdata, msg_data);
+
+    /* Send the request */
+    status = pjsip_inv_send_msg( call->inv, tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Initiate call transfer to the specified address.
+ */
+PJ_DEF(pj_status_t) pjsua_call_xfer( pjsua_call_id call_id, 
+				     const pj_str_t *dest,
+				     const pjsua_msg_data *msg_data)
+{
+    pjsip_evsub *sub;
+    pjsip_tx_data *tdata;
+    pjsua_call *call;
+    pj_status_t status;
+
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+    
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	PJSUA_UNLOCK();
+	return PJSIP_ESESSIONTERMINATED;
+    }
+   
+    /* Create xfer client subscription.
+     * We're not interested in knowing the transfer result, so we
+     * put NULL as the callback.
+     */
+    status = pjsip_xfer_create_uac(call->inv->dlg, NULL, &sub);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create xfer", status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    /*
+     * Create REFER request.
+     */
+    status = pjsip_xfer_initiate(sub, dest, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create REFER request", status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    /* Add additional headers etc */
+    pjsua_process_msg_data( tdata, msg_data);
+
+    /* Send. */
+    status = pjsip_xfer_send_request(sub, tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send REFER request", status);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    /* For simplicity (that's what this program is intended to be!), 
+     * leave the original invite session as it is. More advanced application
+     * may want to hold the INVITE, or terminate the invite, or whatever.
+     */
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+
+}
+
+
+/*
+ * Send DTMF digits to remote using RFC 2833 payload formats.
+ */
+PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id, 
+					  const pj_str_t *digits)
+{
+    pjsua_call *call;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+    
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (!call->session) {
+	PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
+	PJSUA_UNLOCK();
+	return PJ_EINVALIDOP;
+    }
+
+    status = pjmedia_session_dial_dtmf( call->session, 0, digits);
+
+    PJSUA_UNLOCK();
+
+    return status;
+}
+
+
+/**
+ * Send instant messaging inside INVITE session.
+ */
+PJ_DEF(pj_status_t) pjsua_call_send_im( pjsua_call_id call_id, 
+					const pj_str_t *mime_type,
+					const pj_str_t *content,
+					const pjsua_msg_data *msg_data,
+					void *user_data)
+{
+    pjsua_call *call;
+    const pj_str_t mime_text_plain = pj_str("text/plain");
+    pjsip_media_type ctype;
+    pjsua_im_data *im_data;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	PJSUA_UNLOCK();
+	return PJSIP_ESESSIONTERMINATED;
+    }
+
+    /* Lock dialog. */
+    pjsip_dlg_inc_lock(call->inv->dlg);
+
+    /* Set default media type if none is specified */
+    if (mime_type == NULL) {
+	mime_type = &mime_text_plain;
+    }
+
+    /* Create request message. */
+    status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method,
+				       -1, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status);
+	goto on_return;
+    }
+
+    /* Add accept header. */
+    pjsip_msg_add_hdr( tdata->msg, 
+		       (pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
+
+    /* Parse MIME type */
+    pjsua_parse_media_type(tdata->pool, mime_type, &ctype);
+
+    /* Create "text/plain" message body. */
+    tdata->msg->body = pjsip_msg_body_create( tdata->pool, &ctype.type,
+					      &ctype.subtype, content);
+    if (tdata->msg->body == NULL) {
+	pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM);
+	pjsip_tx_data_dec_ref(tdata);
+	goto on_return;
+    }
+
+    /* Add additional headers etc */
+    pjsua_process_msg_data( tdata, msg_data);
+
+    /* Create IM data and attach to the request. */
+    im_data = pj_pool_zalloc(tdata->pool, sizeof(*im_data));
+    im_data->acc_id = call->acc_id;
+    im_data->call_id = call_id;
+    im_data->to = call->inv->dlg->remote.info_str;
+    pj_strdup_with_null(tdata->pool, &im_data->body, content);
+    im_data->user_data = user_data;
+
+
+    /* Send the request. */
+    status = pjsip_dlg_send_request( call->inv->dlg, tdata, 
+				     pjsua_var.mod.id, im_data);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status);
+	goto on_return;
+    }
+
+on_return:
+    pjsip_dlg_dec_lock(call->inv->dlg);
+    PJSUA_UNLOCK();
+    return status;
+}
+
+
+/*
+ * Send IM typing indication inside INVITE session.
+ */
+PJ_DEF(pj_status_t) pjsua_call_send_typing_ind( pjsua_call_id call_id, 
+						pj_bool_t is_typing,
+						const pjsua_msg_data*msg_data)
+{
+    pjsua_call *call;
+    pjsip_tx_data *tdata;
+    pj_status_t status;
+
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    if (!call->inv) {
+	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+	PJSUA_UNLOCK();
+	return PJSIP_ESESSIONTERMINATED;
+    }
+
+    /* Lock dialog. */
+    pjsip_dlg_inc_lock(call->inv->dlg);
+    
+    /* Create request message. */
+    status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method,
+				       -1, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status);
+	goto on_return;
+    }
+
+    /* Create "application/im-iscomposing+xml" msg body. */
+    tdata->msg->body = pjsip_iscomposing_create_body(tdata->pool, is_typing,
+						     NULL, NULL, -1);
+
+    /* Add additional headers etc */
+    pjsua_process_msg_data( tdata, msg_data);
+
+    /* Send the request. */
+    status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status);
+	goto on_return;
+    }
+
+on_return:
+    pjsip_dlg_dec_lock(call->inv->dlg);
+    PJSUA_UNLOCK();
+    return status;
+}
+
+
+/*
+ * Terminate all calls.
+ */
+PJ_DEF(void) pjsua_call_hangup_all(void)
+{
+    unsigned i;
+
+    PJSUA_LOCK();
+
+    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+	if (pjsua_var.calls[i].inv)
+	    pjsua_call_hangup(i, 0, NULL, NULL);
+    }
+
+    PJSUA_UNLOCK();
+}
+
+
+static const char *good_number(char *buf, pj_int32_t val)
+{
+    if (val < 1000) {
+	pj_ansi_sprintf(buf, "%d", val);
+    } else if (val < 1000000) {
+	pj_ansi_sprintf(buf, "%d.%dK", 
+			val / 1000,
+			(val % 1000) / 100);
+    } else {
+	pj_ansi_sprintf(buf, "%d.%02dM", 
+			val / 1000000,
+			(val % 1000000) / 10000);
+    }
+
+    return buf;
+}
+
+
+/* Dump media session */
+static void dump_media_session(const char *indent, 
+			       char *buf, unsigned maxlen,
+			       pjmedia_session *session)
+{
+    unsigned i;
+    char *p = buf, *end = buf+maxlen;
+    int len;
+    pjmedia_session_info info;
+
+    pjmedia_session_get_info(session, &info);
+
+    for (i=0; i<info.stream_cnt; ++i) {
+	pjmedia_rtcp_stat stat;
+	const char *rem_addr;
+	int rem_port;
+	const char *dir;
+	char last_update[40];
+	char packets[16], bytes[16], ipbytes[16];
+	pj_time_val now;
+
+	pjmedia_session_get_stream_stat(session, i, &stat);
+	rem_addr = pj_inet_ntoa(info.stream_info[i].rem_addr.sin_addr);
+	rem_port = pj_ntohs(info.stream_info[i].rem_addr.sin_port);
+
+	if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING)
+	    dir = "sendonly";
+	else if (info.stream_info[i].dir == PJMEDIA_DIR_DECODING)
+	    dir = "recvonly";
+	else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING_DECODING)
+	    dir = "sendrecv";
+	else
+	    dir = "inactive";
+
+	
+	len = pj_ansi_snprintf(buf, end-p, 
+		  "%s  #%d %.*s @%dKHz, %s, peer=%s:%d",
+		  indent, i,
+		  info.stream_info[i].fmt.encoding_name.slen,
+		  info.stream_info[i].fmt.encoding_name.ptr,
+		  info.stream_info[i].fmt.clock_rate / 1000,
+		  dir,
+		  rem_addr, rem_port);
+	if (len < 1 || len > end-p) {
+	    *p = '\0';
+	    return;
+	}
+
+	p += len;
+	*p++ = '\n';
+	*p = '\0';
+
+	if (stat.rx.update_cnt == 0)
+	    strcpy(last_update, "never");
+	else {
+	    pj_gettimeofday(&now);
+	    PJ_TIME_VAL_SUB(now, stat.rx.update);
+	    sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+		    now.sec / 3600,
+		    (now.sec % 3600) / 60,
+		    now.sec % 60,
+		    now.msec);
+	}
+
+	len = pj_ansi_snprintf(p, end-p,
+	       "%s     RX pt=%d, stat last update: %s\n"
+	       "%s        total %spkt %sB (%sB +IP hdr)\n"
+	       "%s        pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
+	       "%s              (msec)    min     avg     max     last\n"
+	       "%s        loss period: %7.3f %7.3f %7.3f %7.3f\n"
+	       "%s        jitter     : %7.3f %7.3f %7.3f %7.3f%s",
+	       indent, info.stream_info[i].fmt.pt,
+	       last_update,
+	       indent,
+	       good_number(packets, stat.rx.pkt),
+	       good_number(bytes, stat.rx.bytes),
+	       good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
+	       indent,
+	       stat.rx.loss,
+	       stat.rx.loss * 100.0 / stat.rx.pkt,
+	       stat.rx.dup, 
+	       stat.rx.dup * 100.0 / stat.rx.pkt,
+	       stat.rx.reorder, 
+	       stat.rx.reorder * 100.0 / stat.rx.pkt,
+	       indent, indent,
+	       stat.rx.loss_period.min / 1000.0, 
+	       stat.rx.loss_period.avg / 1000.0, 
+	       stat.rx.loss_period.max / 1000.0,
+	       stat.rx.loss_period.last / 1000.0,
+	       indent,
+	       stat.rx.jitter.min / 1000.0,
+	       stat.rx.jitter.avg / 1000.0,
+	       stat.rx.jitter.max / 1000.0,
+	       stat.rx.jitter.last / 1000.0,
+	       ""
+	       );
+
+	if (len < 1 || len > end-p) {
+	    *p = '\0';
+	    return;
+	}
+
+	p += len;
+	*p++ = '\n';
+	*p = '\0';
+	
+	if (stat.tx.update_cnt == 0)
+	    strcpy(last_update, "never");
+	else {
+	    pj_gettimeofday(&now);
+	    PJ_TIME_VAL_SUB(now, stat.tx.update);
+	    sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
+		    now.sec / 3600,
+		    (now.sec % 3600) / 60,
+		    now.sec % 60,
+		    now.msec);
+	}
+
+	len = pj_ansi_snprintf(p, end-p,
+	       "%s     TX pt=%d, ptime=%dms, stat last update: %s\n"
+	       "%s        total %spkt %sB (%sB +IP hdr)\n"
+	       "%s        pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
+	       "%s              (msec)    min     avg     max     last\n"
+	       "%s        loss period: %7.3f %7.3f %7.3f %7.3f\n"
+	       "%s        jitter     : %7.3f %7.3f %7.3f %7.3f%s",
+	       indent,
+	       info.stream_info[i].tx_pt,
+	       info.stream_info[i].param->info.frm_ptime *
+		info.stream_info[i].param->setting.frm_per_pkt,
+	       last_update,
+
+	       indent,
+	       good_number(packets, stat.tx.pkt),
+	       good_number(bytes, stat.tx.bytes),
+	       good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
+
+	       indent,
+	       stat.tx.loss,
+	       stat.tx.loss * 100.0 / stat.tx.pkt,
+	       stat.tx.dup, 
+	       stat.tx.dup * 100.0 / stat.tx.pkt,
+	       stat.tx.reorder, 
+	       stat.tx.reorder * 100.0 / stat.tx.pkt,
+
+	       indent, indent,
+	       stat.tx.loss_period.min / 1000.0, 
+	       stat.tx.loss_period.avg / 1000.0, 
+	       stat.tx.loss_period.max / 1000.0,
+	       stat.tx.loss_period.last / 1000.0,
+	       indent,
+	       stat.tx.jitter.min / 1000.0,
+	       stat.tx.jitter.avg / 1000.0,
+	       stat.tx.jitter.max / 1000.0,
+	       stat.tx.jitter.last / 1000.0,
+	       ""
+	       );
+
+	if (len < 1 || len > end-p) {
+	    *p = '\0';
+	    return;
+	}
+
+	p += len;
+	*p++ = '\n';
+	*p = '\0';
+
+	len = pj_ansi_snprintf(p, end-p,
+	       "%s    RTT msec       : %7.3f %7.3f %7.3f %7.3f", 
+	       indent,
+	       stat.rtt.min / 1000.0,
+	       stat.rtt.avg / 1000.0,
+	       stat.rtt.max / 1000.0,
+	       stat.rtt.last / 1000.0
+	       );
+	if (len < 1 || len > end-p) {
+	    *p = '\0';
+	    return;
+	}
+
+	p += len;
+	*p++ = '\n';
+	*p = '\0';
+    }
+}
+
+
+/* Print call info */
+static void print_call(const char *title,
+		       int call_id, 
+		       char *buf, pj_size_t size)
+{
+    int len;
+    pjsip_inv_session *inv = pjsua_var.calls[call_id].inv;
+    pjsip_dialog *dlg = inv->dlg;
+    char userinfo[128];
+
+    /* Dump invite sesion info. */
+
+    len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
+    if (len < 1)
+	pj_ansi_strcpy(userinfo, "<--uri too long-->");
+    else
+	userinfo[len] = '\0';
+    
+    len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
+			   title,
+			   pjsip_inv_state_name(inv->state),
+			   userinfo);
+    if (len < 1 || len >= (int)size) {
+	pj_ansi_strcpy(buf, "<--uri too long-->");
+	len = 18;
+    } else
+	buf[len] = '\0';
+}
+
+
+/*
+ * Dump call and media statistics to string.
+ */
+PJ_DEF(pj_status_t) pjsua_call_dump( pjsua_call_id call_id, 
+				     pj_bool_t with_media, 
+				     char *buffer, 
+				     unsigned maxlen,
+				     const char *indent)
+{
+    pjsua_call *call;
+    pj_time_val duration, res_delay, con_delay;
+    char tmp[128];
+    char *p, *end;
+    int len;
+
+    PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    call = &pjsua_var.calls[call_id];
+
+    *buffer = '\0';
+    p = buffer;
+    end = buffer + maxlen;
+    len = 0;
+
+    if (call->inv == NULL) {
+	PJSUA_UNLOCK();
+	return PJ_EINVALIDOP;
+    }
+
+    print_call(indent, call_id, tmp, sizeof(tmp));
+    
+    len = pj_ansi_strlen(tmp);
+    pj_ansi_strcpy(buffer, tmp);
+
+    p += len;
+    *p++ = '\r';
+    *p++ = '\n';
+
+    /* Calculate call duration */
+    if (call->inv->state >= PJSIP_INV_STATE_CONFIRMED) {
+	pj_gettimeofday(&duration);
+	PJ_TIME_VAL_SUB(duration, call->conn_time);
+	con_delay = call->conn_time;
+	PJ_TIME_VAL_SUB(con_delay, call->start_time);
+    } else {
+	duration.sec = duration.msec = 0;
+	con_delay.sec = con_delay.msec = 0;
+    }
+
+    /* Calculate first response delay */
+    if (call->inv->state >= PJSIP_INV_STATE_EARLY) {
+	res_delay = call->res_time;
+	PJ_TIME_VAL_SUB(res_delay, call->start_time);
+    } else {
+	res_delay.sec = res_delay.msec = 0;
+    }
+
+    /* Print duration */
+    len = pj_ansi_snprintf(p, end-p, 
+		           "%s  Call time: %02dh:%02dm:%02ds, "
+		           "1st res in %d ms, conn in %dms",
+			   indent,
+		           (duration.sec / 3600),
+		           ((duration.sec % 3600)/60),
+		           (duration.sec % 60),
+		           PJ_TIME_VAL_MSEC(res_delay), 
+		           PJ_TIME_VAL_MSEC(con_delay));
+    
+    if (len > 0 && len < end-p) {
+	p += len;
+	*p++ = '\n';
+	*p = '\0';
+    }
+
+    /* Dump session statistics */
+    if (with_media && call->session)
+	dump_media_session(indent, p, end-p, call->session);
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy the call's media
+ */
+static pj_status_t call_destroy_media(int call_id)
+{
+    pjsua_call *call = &pjsua_var.calls[call_id];
+
+    if (call->conf_slot != PJSUA_INVALID_ID) {
+	pjmedia_conf_remove_port(pjsua_var.mconf, call->conf_slot);
+	call->conf_slot = PJSUA_INVALID_ID;
+    }
+
+    if (call->session) {
+	/* Destroy session (this will also close RTP/RTCP sockets). */
+	pjmedia_session_destroy(call->session);
+	call->session = NULL;
+
+	PJ_LOG(4,(THIS_FILE, "Media session for call %d is destroyed", 
+			     call_id));
+
+    }
+
+    call->media_st = PJSUA_CALL_MEDIA_NONE;
+
+    return PJ_SUCCESS;
+}
+
+
 /*
  * This callback receives notification from invite session when the
@@ -631,8 +1560,15 @@
 					pjsip_event *e)
 {
-    pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id];
-
-    if (!call)
+    pjsua_call *call;
+
+    PJSUA_LOCK();
+
+    call = inv->dlg->mod_data[pjsua_var.mod.id];
+
+    if (!call) {
+	PJSUA_UNLOCK();
 	return;
+    }
+
 
     /* Get call times */
@@ -643,4 +1579,7 @@
 		pj_gettimeofday(&call->res_time);
 	    call->last_code = e->body.tsx_state.tsx->status_code;
+	    pj_strncpy(&call->last_text, 
+		       &e->body.tsx_state.tsx->status_text,
+		       sizeof(call->last_text_buf_));
 	    break;
 	case PJSIP_INV_STATE_CONFIRMED:
@@ -651,8 +1590,14 @@
 	    if (e->body.tsx_state.tsx->status_code > call->last_code) {
 		call->last_code = e->body.tsx_state.tsx->status_code;
+		pj_strncpy(&call->last_text, 
+			   &e->body.tsx_state.tsx->status_text,
+			   sizeof(call->last_text_buf_));
 	    }
 	    break;
 	default:
 	    call->last_code = e->body.tsx_state.tsx->status_code;
+	    pj_strncpy(&call->last_text, 
+		       &e->body.tsx_state.tsx->status_text,
+		       sizeof(call->last_text_buf_));
 	    break;
     }
@@ -717,6 +1662,6 @@
 
 
-    if (pjsua.cb.on_call_state)
-	(*pjsua.cb.on_call_state)(call->index, e);
+    if (pjsua_var.ua_cfg.cb.on_call_state)
+	(*pjsua_var.ua_cfg.cb.on_call_state)(call->index, e);
 
     /* call->inv may be NULL now */
@@ -730,231 +1675,11 @@
 	    call_destroy_media(call->index);
 
-	/* Remove timers. */
-	schedule_call_timer(call, &call->refresh_tm, REFRESH_CALL_TIMER, 0);
-	schedule_call_timer(call, &call->hangup_tm, HANGUP_CALL_TIMER, 0);
-
 	/* Free call */
 	call->inv = NULL;
-	--pjsua.call_cnt;
-    }
-}
-
-
-/*
- * Callback called by event framework when the xfer subscription state
- * has changed.
- */
-static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
-{
-    
-    PJ_UNUSED_ARG(event);
-
-    /*
-     * We're only interested when subscription is terminated, to 
-     * clear the xfer_sub member of the inv_data.
-     */
-    if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
-	pjsua_call *call;
-
-	call = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
-	if (!call)
-	    return;
-
-	pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
-	call->xfer_sub = NULL;
-
-	PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated"));
-    }
-}
-
-
-/*
- * Follow transfer (REFER) request.
- */
-static void on_call_transfered( pjsip_inv_session *inv,
-			        pjsip_rx_data *rdata )
-{
-    pj_status_t status;
-    pjsip_tx_data *tdata;
-    pjsua_call *existing_call;
-    int new_call;
-    const pj_str_t str_refer_to = { "Refer-To", 8};
-    pjsip_generic_string_hdr *refer_to;
-    char *uri;
-    pj_str_t tmp;
-    struct pjsip_evsub_user xfer_cb;
-    pjsip_status_code code;
-    pjsip_evsub *sub;
-
-    existing_call = inv->dlg->mod_data[pjsua.mod.id];
-
-    /* Find the Refer-To header */
-    refer_to = (pjsip_generic_string_hdr*)
-	pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL);
-
-    if (refer_to == NULL) {
-	/* Invalid Request.
-	 * No Refer-To header!
-	 */
-	PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!"));
-	pjsip_dlg_respond( inv->dlg, rdata, 400, NULL, NULL, NULL);
-	return;
-    }
-
-    /* Notify callback */
-    code = PJSIP_SC_OK;
-    if (pjsua.cb.on_call_transfered)
-	(*pjsua.cb.on_call_transfered)(existing_call->index,
-				       &refer_to->hvalue, &code);
-
-    if (code < 200)
-	code = 200;
-    if (code >= 300) {
-	/* Application rejects call transfer request */
-	pjsip_dlg_respond( inv->dlg, rdata, code, NULL, NULL, NULL);
-	return;
-    }
-
-    PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s",
-	      (int)inv->dlg->remote.info_str.slen,
-	      inv->dlg->remote.info_str.ptr,
-	      (int)refer_to->hvalue.slen, 
-	      refer_to->hvalue.ptr));
-
-    /* Init callback */
-    pj_memset(&xfer_cb, 0, sizeof(xfer_cb));
-    xfer_cb.on_evsub_state = &xfer_on_evsub_state;
-
-    /* Create transferee event subscription */
-    status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create xfer uas", status);
-	pjsip_dlg_respond( inv->dlg, rdata, 500, NULL, NULL, NULL);
-	return;
-    }
-
-    /* Accept the REFER request, send 200 (OK). */
-    pjsip_xfer_accept(sub, rdata, code, NULL);
-
-    /* Create initial NOTIFY request */
-    status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE,
-				100, NULL, &tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status);
-	return;
-    }
-
-    /* Send initial NOTIFY request */
-    status = pjsip_xfer_send_request( sub, tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status);
-	return;
-    }
-
-    /* We're cheating here.
-     * We need to get a null terminated string from a pj_str_t.
-     * So grab the pointer from the hvalue and NULL terminate it, knowing
-     * that the NULL position will be occupied by a newline. 
-     */
-    uri = refer_to->hvalue.ptr;
-    uri[refer_to->hvalue.slen] = '\0';
-
-    /* Now make the outgoing call. */
-    tmp = pj_str(uri);
-    status = pjsua_call_make_call(existing_call->acc_index, &tmp, &new_call);
-    if (status != PJ_SUCCESS) {
-
-	/* Notify xferer about the error */
-	status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
-				   500, NULL, &tdata);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", 
-			  status);
-	    return;
-	}
-	status = pjsip_xfer_send_request(sub, tdata);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", 
-			  status);
-	    return;
-	}
-	return;
-    }
-
-    /* Put the server subscription in inv_data.
-     * Subsequent state changed in pjsua_inv_on_state_changed() will be
-     * reported back to the server subscription.
-     */
-    pjsua.calls[new_call].xfer_sub = sub;
-
-    /* Put the invite_data in the subscription. */
-    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, &pjsua.calls[new_call]);
-}
-
-
-/*
- * This callback is called when transaction state has changed in INVITE
- * session. We use this to trap:
- *  - incoming REFER request.
- *  - incoming MESSAGE request.
- */
-static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
-					    pjsip_transaction *tsx,
-					    pjsip_event *e)
-{
-    pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id];
-
-    if (tsx->role==PJSIP_ROLE_UAS &&
-	tsx->state==PJSIP_TSX_STATE_TRYING &&
-	pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0)
-    {
-	/*
-	 * Incoming REFER request.
-	 */
-	on_call_transfered(call->inv, e->body.tsx_state.src.rdata);
-
-    }
-    else if (tsx->role==PJSIP_ROLE_UAS &&
-	     tsx->state==PJSIP_TSX_STATE_TRYING &&
-	     pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0)
-    {
-	/*
-	 * Incoming MESSAGE request!
-	 */
-	pjsip_rx_data *rdata;
-	pjsip_msg *msg;
-	pjsip_accept_hdr *accept_hdr;
-	pj_status_t status;
-
-	rdata = e->body.tsx_state.src.rdata;
-	msg = rdata->msg_info.msg;
-
-	/* Request MUST have message body, with Content-Type equal to
-	 * "text/plain".
-	 */
-	if (pjsua_im_accept_pager(rdata, &accept_hdr) == PJ_FALSE) {
-
-	    pjsip_hdr hdr_list;
-
-	    pj_list_init(&hdr_list);
-	    pj_list_push_back(&hdr_list, accept_hdr);
-
-	    pjsip_dlg_respond( inv->dlg, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, 
-			       NULL, &hdr_list, NULL );
-	    return;
-	}
-
-	/* Respond with 200 first, so that remote doesn't retransmit in case
-	 * the UI takes too long to process the message. 
-	 */
-	status = pjsip_dlg_respond( inv->dlg, rdata, 200, NULL, NULL, NULL);
-
-	/* Process MESSAGE request */
-	pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str,
-			       &inv->dlg->local.info_str, rdata);
-    }
-
-}
-
+	--pjsua_var.call_cnt;
+    }
+
+    PJSUA_UNLOCK();
+}
 
 /*
@@ -971,122 +1696,4 @@
 }
 
-
-/*
- * Create inactive SDP for call hold.
- */
-static pj_status_t create_inactive_sdp(pjsua_call *call,
-				       pjmedia_sdp_session **p_answer)
-{
-    pj_status_t status;
-    pjmedia_sdp_conn *conn;
-    pjmedia_sdp_attr *attr;
-    pjmedia_sdp_session *sdp;
-
-    /* Create new offer */
-    status = pjmedia_endpt_create_sdp(pjsua.med_endpt, pjsua.pool, 1,
-				      &call->skinfo, &sdp);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
-	return status;
-    }
-
-    /* Get SDP media connection line */
-    conn = sdp->media[0]->conn;
-    if (!conn)
-	conn = sdp->conn;
-
-    /* Modify address */
-    conn->addr = pj_str("0.0.0.0");
-
-    /* Remove existing directions attributes */
-    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendrecv");
-    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendonly");
-    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "recvonly");
-    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "inactive");
-
-    /* Add inactive attribute */
-    attr = pjmedia_sdp_attr_create(pjsua.pool, "inactive", NULL);
-    pjmedia_sdp_media_add_attr(sdp->media[0], attr);
-
-    *p_answer = sdp;
-
-    return status;
-}
-
-/*
- * Called when session received new offer.
- */
-static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
-				   const pjmedia_sdp_session *offer)
-{
-    pjsua_call *call;
-    pjmedia_sdp_conn *conn;
-    pjmedia_sdp_session *answer;
-    pj_bool_t is_remote_active;
-    pj_status_t status;
-
-    call = inv->dlg->mod_data[pjsua.mod.id];
-
-    /*
-     * See if remote is offering active media (i.e. not on-hold)
-     */
-    is_remote_active = PJ_TRUE;
-
-    conn = offer->media[0]->conn;
-    if (!conn)
-	conn = offer->conn;
-
-    if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 ||
-	pj_strcmp2(&conn->addr, "0")==0)
-    {
-	is_remote_active = PJ_FALSE;
-
-    } 
-    else if (pjmedia_sdp_media_find_attr2(offer->media[0], "inactive", NULL))
-    {
-	is_remote_active = PJ_FALSE;
-    }
-
-    PJ_LOG(4,(THIS_FILE, "Received SDP offer, remote media is %s",
-	      (is_remote_active ? "active" : "inactive")));
-
-    /* Supply candidate answer */
-    if (is_remote_active) {
-	status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1,
-					   &call->skinfo, &answer);
-    } else {
-	status = create_inactive_sdp( call, &answer );
-    }
-
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
-	return;
-    }
-
-    status = pjsip_inv_set_sdp_answer(call->inv, answer);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to set answer", status);
-	return;
-    }
-
-}
-
-#if 0
-/* Disconnect call */
-static void call_disconnect(pjsip_inv_session *inv,
-			    int st_code)
-{
-    pjsip_tx_data *tdata;
-    pj_status_t status;
-
-    status = pjsip_inv_end_session(inv, st_code, NULL, &tdata);
-    if (status == PJ_SUCCESS)
-	status = pjsip_inv_send_msg(inv, tdata);
-
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to disconnect call", status);
-    }
-}
-#endif
 
 /*
@@ -1098,4 +1705,5 @@
 				       pj_status_t status)
 {
+    int prev_media_st = 0;
     pjsua_call *call;
     pjmedia_session_info sess_info;
@@ -1106,5 +1714,7 @@
     char tmp[PJSIP_MAX_URL_SIZE];
 
-    call = inv->dlg->mod_data[pjsua.mod.id];
+    PJSUA_LOCK();
+
+    call = inv->dlg->mod_data[pjsua_var.mod.id];
 
     if (status != PJ_SUCCESS) {
@@ -1120,12 +1730,15 @@
 	    //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
 	}
+
+	PJSUA_UNLOCK();
 	return;
-
     }
 
     /* Destroy existing media session, if any. */
 
-    if (call)
+    if (call) {
+	prev_media_st = call->media_st;
 	call_destroy_media(call->index);
+    }
 
     /* Get local and remote SDP */
@@ -1137,4 +1750,5 @@
 		     status);
 	//call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+	PJSUA_UNLOCK();
 	return;
     }
@@ -1147,9 +1761,7 @@
 		     status);
 	//call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+	PJSUA_UNLOCK();
 	return;
     }
-
-    if (pjsua.config.null_audio)
-	return;
 
     /* Create media session info based on SDP parameters. 
@@ -1157,5 +1769,5 @@
      */    
     status = pjmedia_session_info_from_sdp( call->inv->dlg->pool, 
-					    pjsua.med_endpt, 
+					    pjsua_var.med_endpt, 
 					    1,&sess_info, 
 					    local_sdp, remote_sdp);
@@ -1164,115 +1776,88 @@
 		     status);
 	//call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+	PJSUA_UNLOCK();
 	return;
     }
 
-    /* Override ptime, if this option is specified. */
-    if (pjsua.config.ptime) {
-	sess_info.stream_info[0].param->setting.frm_per_pkt = (pj_uint8_t)
-	    (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;
-    }
-
-    /* Optionally, application may modify other stream settings here
-     * (such as jitter buffer parameters, codec ptime, etc.)
-     */
-
-    /* Create session based on session info. */
-    status = pjmedia_session_create( pjsua.med_endpt, &sess_info,
-				     &call->med_tp,
-				     call, &call->session );
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create media session", 
-		     status);
-	//call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
-	return;
-    }
-
-
-    /* Get the port interface of the first stream in the session.
-     * We need the port interface to add to the conference bridge.
-     */
-    pjmedia_session_get_port(call->session, 0, &media_port);
-
-
-    /*
-     * Add the call to conference bridge.
-     */
-    port_name.ptr = tmp;
-    port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
-				     call->inv->dlg->remote.info->uri,
-				     tmp, sizeof(tmp));
-    if (port_name.slen < 1) {
-	port_name = pj_str("call");
-    }
-    status = pjmedia_conf_add_port( pjsua.mconf, call->inv->pool,
-				    media_port, 
-				    &port_name,
-				    &call->conf_slot);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create conference slot", 
-		     status);
-	call_destroy_media(call->index);
-	//call_disconnect(inv, PJSIP_SC_INTERNAL_SERVER_ERROR);
-	return;
-    }
-
-    /* If auto-play is configured, connect the call to the file player 
-     * port 
-     */
-    if (pjsua.config.auto_play && pjsua.config.wav_file.slen && 
-	call->inv->role == PJSIP_ROLE_UAS) 
+    /* Check if media is put on-hold */
+    if (sess_info.stream_cnt == 0 || 
+	sess_info.stream_info[0].dir == PJMEDIA_DIR_NONE)
     {
 
-	pjmedia_conf_connect_port( pjsua.mconf, pjsua.player[0].slot, 
-				   call->conf_slot, 0);
-
-    }
-    if (pjsua.config.auto_loop && call->inv->role == PJSIP_ROLE_UAS) {
-
-	pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 
-				   call->conf_slot, 0);
-
-    }
-    if (pjsua.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.config.max_calls; ++i) {
-
-	    if (!pjsua.calls[i].session)
-		continue;
-
-	    pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 
-				       pjsua.calls[i].conf_slot, 0);
-	    pjmedia_conf_connect_port( pjsua.mconf, pjsua.calls[i].conf_slot,
-				       call->conf_slot, 0);
+	/* Determine who puts the call on-hold */
+	if (prev_media_st == PJSUA_CALL_MEDIA_ACTIVE) {
+	    if (pjmedia_sdp_neg_was_answer_remote(call->inv->neg)) {
+		/* It was local who offer hold */
+		call->media_st = PJSUA_CALL_MEDIA_LOCAL_HOLD;
+	    } else {
+		call->media_st = PJSUA_CALL_MEDIA_REMOTE_HOLD;
+	    }
 	}
 
-    } 
-    
-    /* Normal operation: if no auto_xx is given, connect new call to 
-     * the sound device port (port zero) in the main conference bridge.
-     */
-    if (pjsua.config.auto_play == 0 && pjsua.config.auto_loop == 0 &&
-	pjsua.config.auto_conf == 0)
+	call->media_dir = PJMEDIA_DIR_NONE;
+
+    } else {
+
+	/* Override ptime, if this option is specified. */
+	PJ_TODO(set_codec_ptime_in_call);
+
+
+	/* Optionally, application may modify other stream settings here
+	 * (such as jitter buffer parameters, codec ptime, etc.)
+	 */
+
+	/* Create session based on session info. */
+	status = pjmedia_session_create( pjsua_var.med_endpt, &sess_info,
+					 &call->med_tp,
+					 call, &call->session );
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to create media session", 
+			 status);
+	    //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+	    PJSUA_UNLOCK();
+	    return;
+	}
+
+
+	/* Get the port interface of the first stream in the session.
+	 * We need the port interface to add to the conference bridge.
+	 */
+	pjmedia_session_get_port(call->session, 0, &media_port);
+
+
+	/*
+	 * Add the call to conference bridge.
+	 */
+	port_name.ptr = tmp;
+	port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
+					 call->inv->dlg->remote.info->uri,
+					 tmp, sizeof(tmp));
+	if (port_name.slen < 1) {
+	    port_name = pj_str("call");
+	}
+	status = pjmedia_conf_add_port( pjsua_var.mconf, call->inv->pool,
+					media_port, 
+					&port_name,
+					(unsigned*)&call->conf_slot);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to create conference slot", 
+			 status);
+	    call_destroy_media(call->index);
+	    //call_disconnect(inv, PJSIP_SC_INTERNAL_SERVER_ERROR);
+	    PJSUA_UNLOCK();
+	    return;
+	}
+
+	/* Call's media state is active */
+	call->media_st = PJSUA_CALL_MEDIA_ACTIVE;
+	call->media_dir = sess_info.stream_info[0].dir;
+    }
+
+    /* Print info. */
     {
-	pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot, 0);
-	pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0, 0);
-    }
-
-
-    /* Done. */
-    {
-	struct pjmedia_session_info sess_info;
 	char info[80];
 	int info_len = 0;
 	unsigned i;
 
-	pjmedia_session_get_info(call->session, &sess_info);
 	for (i=0; i<sess_info.stream_cnt; ++i) {
 	    int len;
@@ -1305,390 +1890,372 @@
 		info_len += len;
 	}
-	PJ_LOG(3,(THIS_FILE,"Media started%s", info));
-    }
-}
-
-
-/*
- * Hangup call.
- */
-PJ_DEF(void) pjsua_call_hangup(int call_index)
-{
+	PJ_LOG(4,(THIS_FILE,"Media updates%s", info));
+    }
+
+    /* Call application callback, if any */
+    if (pjsua_var.ua_cfg.cb.on_call_media_state)
+	pjsua_var.ua_cfg.cb.on_call_media_state(call->index);
+
+
+    PJSUA_UNLOCK();
+}
+
+
+/*
+ * Create inactive SDP for call hold.
+ */
+static pj_status_t create_inactive_sdp(pjsua_call *call,
+				       pjmedia_sdp_session **p_answer)
+{
+    pj_status_t status;
+    pjmedia_sdp_conn *conn;
+    pjmedia_sdp_attr *attr;
+    pjmedia_sdp_session *sdp;
+
+    /* Create new offer */
+    status = pjmedia_endpt_create_sdp(pjsua_var.med_endpt, pjsua_var.pool, 1,
+				      &call->skinfo, &sdp);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
+	return status;
+    }
+
+    /* Get SDP media connection line */
+    conn = sdp->media[0]->conn;
+    if (!conn)
+	conn = sdp->conn;
+
+    /* Modify address */
+    conn->addr = pj_str("0.0.0.0");
+
+    /* Remove existing directions attributes */
+    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendrecv");
+    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendonly");
+    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "recvonly");
+    pjmedia_sdp_media_remove_all_attr(sdp->media[0], "inactive");
+
+    /* Add inactive attribute */
+    attr = pjmedia_sdp_attr_create(pjsua_var.pool, "inactive", NULL);
+    pjmedia_sdp_media_add_attr(sdp->media[0], attr);
+
+    *p_answer = sdp;
+
+    return status;
+}
+
+
+/*
+ * Called when session received new offer.
+ */
+static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
+				   const pjmedia_sdp_session *offer)
+{
+    const char *remote_state;
     pjsua_call *call;
-    int code;
+    pjmedia_sdp_conn *conn;
+    pjmedia_sdp_session *answer;
+    pj_bool_t is_remote_active;
+    pj_status_t status;
+
+    PJSUA_LOCK();
+
+    call = inv->dlg->mod_data[pjsua_var.mod.id];
+
+    /*
+     * See if remote is offering active media (i.e. not on-hold)
+     */
+    is_remote_active = PJ_TRUE;
+
+    conn = offer->media[0]->conn;
+    if (!conn)
+	conn = offer->conn;
+
+    if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 ||
+	pj_strcmp2(&conn->addr, "0")==0)
+    {
+	is_remote_active = PJ_FALSE;
+
+    } 
+    else if (pjmedia_sdp_media_find_attr2(offer->media[0], "inactive", NULL))
+    {
+	is_remote_active = PJ_FALSE;
+    }
+
+    remote_state = (is_remote_active ? "active" : "inactive");
+
+    /* Supply candidate answer */
+    if (call->media_st == PJSUA_CALL_MEDIA_LOCAL_HOLD || !is_remote_active) {
+	PJ_LOG(4,(THIS_FILE, 
+		  "Call %d: RX new media offer, creating inactive SDP "
+		  "(media in offer is %s)", call->index, remote_state));
+	status = create_inactive_sdp( call, &answer );
+    } else {
+	PJ_LOG(4,(THIS_FILE, "Call %d: received updated media offer",
+		  call->index));
+	status = pjmedia_endpt_create_sdp( pjsua_var.med_endpt, 
+					   call->inv->pool, 1,
+					   &call->skinfo, &answer);	
+    }
+
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create local SDP", status);
+	PJSUA_UNLOCK();
+	return;
+    }
+
+    status = pjsip_inv_set_sdp_answer(call->inv, answer);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to set answer", status);
+	PJSUA_UNLOCK();
+	return;
+    }
+
+    PJSUA_UNLOCK();
+}
+
+
+/*
+ * Callback called by event framework when the xfer subscription state
+ * has changed.
+ */
+static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+{
+    
+    PJ_UNUSED_ARG(event);
+
+    /*
+     * We're only interested when subscription is terminated, to 
+     * clear the xfer_sub member of the inv_data.
+     */
+    if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+	pjsua_call *call;
+
+	call = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+	if (!call)
+	    return;
+
+	pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
+	call->xfer_sub = NULL;
+
+	PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated"));
+    }
+}
+
+
+/*
+ * Follow transfer (REFER) request.
+ */
+static void on_call_transfered( pjsip_inv_session *inv,
+			        pjsip_rx_data *rdata )
+{
     pj_status_t status;
     pjsip_tx_data *tdata;
-
-
-    call = &pjsua.calls[call_index];
-
-    if (!call->inv) {
-	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
+    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;
+    pj_str_t tmp;
+    struct pjsip_evsub_user xfer_cb;
+    pjsip_status_code code;
+    pjsip_evsub *sub;
+
+    existing_call = inv->dlg->mod_data[pjsua_var.mod.id];
+
+    /* Find the Refer-To header */
+    refer_to = (pjsip_generic_string_hdr*)
+	pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL);
+
+    if (refer_to == NULL) {
+	/* Invalid Request.
+	 * No Refer-To header!
+	 */
+	PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!"));
+	pjsip_dlg_respond( inv->dlg, rdata, 400, NULL, NULL, NULL);
 	return;
     }
 
-    if (call->inv->state == PJSIP_INV_STATE_CONFIRMED)
-	code = PJSIP_SC_OK;
-    else if (call->inv->role == PJSIP_ROLE_UAS)
-	code = PJSIP_SC_DECLINE;
-    else
-	code = PJSIP_SC_REQUEST_TERMINATED;
-
-    status = pjsip_inv_end_session(call->inv, code, NULL, &tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, 
-		     "Failed to create end session message", 
-		     status);
+    /* Notify callback */
+    code = PJSIP_SC_OK;
+    if (pjsua_var.ua_cfg.cb.on_call_transfered)
+	(*pjsua_var.ua_cfg.cb.on_call_transfered)(existing_call->index,
+						  &refer_to->hvalue, &code);
+
+    if (code < 200)
+	code = 200;
+    if (code >= 300) {
+	/* Application rejects call transfer request */
+	pjsip_dlg_respond( inv->dlg, rdata, code, NULL, NULL, NULL);
 	return;
     }
 
-    /* pjsip_inv_end_session may return PJ_SUCCESS with NULL 
-     * as p_tdata when INVITE transaction has not been answered
-     * with any provisional responses.
+    PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s",
+	      (int)inv->dlg->remote.info_str.slen,
+	      inv->dlg->remote.info_str.ptr,
+	      (int)refer_to->hvalue.slen, 
+	      refer_to->hvalue.ptr));
+
+    /* Init callback */
+    pj_memset(&xfer_cb, 0, sizeof(xfer_cb));
+    xfer_cb.on_evsub_state = &xfer_on_evsub_state;
+
+    /* Create transferee event subscription */
+    status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create xfer uas", status);
+	pjsip_dlg_respond( inv->dlg, rdata, 500, NULL, NULL, NULL);
+	return;
+    }
+
+    /* Accept the REFER request, send 200 (OK). */
+    pjsip_xfer_accept(sub, rdata, code, NULL);
+
+    /* Create initial NOTIFY request */
+    status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE,
+				100, NULL, &tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status);
+	return;
+    }
+
+    /* Send initial NOTIFY request */
+    status = pjsip_xfer_send_request( sub, tdata);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status);
+	return;
+    }
+
+    /* We're cheating here.
+     * We need to get a null terminated string from a pj_str_t.
+     * So grab the pointer from the hvalue and NULL terminate it, knowing
+     * that the NULL position will be occupied by a newline. 
      */
-    if (tdata == NULL)
+    uri = refer_to->hvalue.ptr;
+    uri[refer_to->hvalue.slen] = '\0';
+
+    /* Now make the outgoing call. */
+    tmp = pj_str(uri);
+    status = pjsua_call_make_call(existing_call->acc_id, &tmp, 0,
+				  existing_call->user_data, NULL, 
+				  &new_call);
+    if (status != PJ_SUCCESS) {
+
+	/* Notify xferer about the error */
+	status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED,
+				   500, NULL, &tdata);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", 
+			  status);
+	    return;
+	}
+	status = pjsip_xfer_send_request(sub, tdata);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", 
+			  status);
+	    return;
+	}
 	return;
-
-    status = pjsip_inv_send_msg(call->inv, tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, 
-		     "Failed to send end session message", 
-		     status);
-	return;
-    }
-}
-
-
-/*
- * Put call on-Hold.
- */
-PJ_DEF(pj_status_t) pjsua_call_set_hold(int call_index)
-{
-    pjmedia_sdp_session *sdp;
-    pjsua_call *call;
-    pjsip_tx_data *tdata;
-    pj_status_t status;
-
-    call = &pjsua.calls[call_index];
-    
-    if (!call->inv) {
-	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
-	return PJSIP_ESESSIONTERMINATED;
-    }
-
-    if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
-	PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed"));
-	return PJSIP_ESESSIONSTATE;
-    }
-
-    status = create_inactive_sdp(call, &sdp);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    /* Send re-INVITE with new offer */
-    status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
-	return status;
-    }
-
-    status = pjsip_inv_send_msg( call->inv, tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
-	return status;
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * re-INVITE.
- */
-PJ_DEF(pj_status_t) pjsua_call_reinvite(int call_index)
-{
-    pjmedia_sdp_session *sdp;
-    pjsip_tx_data *tdata;
-    pjsua_call *call;
-    pj_status_t status;
-
-    call = &pjsua.calls[call_index];
-
-    if (!call->inv) {
-	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
-	return PJSIP_ESESSIONTERMINATED;
-    }
-
-
-    if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) {
-	PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed"));
-	return PJSIP_ESESSIONSTATE;
-    }
-
-    /* Create SDP */
-    status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1,
-				       &call->skinfo, &sdp);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", 
-		     status);
-	return status;
-    }
-
-    /* Send re-INVITE with new offer */
-    status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status);
-	return status;
-    }
-
-    status = pjsip_inv_send_msg( call->inv, tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status);
-	return status;
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Transfer call.
- */
-PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest)
-{
-    pjsip_evsub *sub;
-    pjsip_tx_data *tdata;
-    pjsua_call *call;
-    pj_status_t status;
-
-    
-    call = &pjsua.calls[call_index];
-
-    if (!call->inv) {
-	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
-	return PJSIP_ESESSIONTERMINATED;
-    }
-   
-    /* Create xfer client subscription.
-     * We're not interested in knowing the transfer result, so we
-     * put NULL as the callback.
+    }
+
+    /* Put the server subscription in inv_data.
+     * Subsequent state changed in pjsua_inv_on_state_changed() will be
+     * reported back to the server subscription.
      */
-    status = pjsip_xfer_create_uac(call->inv->dlg, NULL, &sub);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create xfer", status);
-	return status;
-    }
-
-    /*
-     * Create REFER request.
-     */
-    status = pjsip_xfer_initiate(sub, dest, &tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create REFER request", status);
-	return status;
-    }
-
-    /* Send. */
-    status = pjsip_xfer_send_request(sub, tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to send REFER request", status);
-	return status;
-    }
-
-    /* For simplicity (that's what this program is intended to be!), 
-     * leave the original invite session as it is. More advanced application
-     * may want to hold the INVITE, or terminate the invite, or whatever.
-     */
-
-    return PJ_SUCCESS;
-}
-
-
-/**
- * Dial DTMF.
- */
-PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( unsigned call_index, 
-					  const pj_str_t *digits)
-{
-    pjsua_call *call = &pjsua.calls[call_index];
-
-    PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, PJ_EINVAL);
-
-    if (!call->session) {
-	PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
-	return -1;
-    }
-
-    return pjmedia_session_dial_dtmf( call->session, 0, digits);
-}
-
-
-/**
- * Send instant messaging inside INVITE session.
- */
-PJ_DEF(pj_status_t) pjsua_call_send_im(int call_index, const pj_str_t *str)
-{
-    pjsua_call *call;
-    const pj_str_t mime_text = pj_str("text");
-    const pj_str_t mime_plain = pj_str("plain");
-    pjsip_tx_data *tdata;
-    pj_status_t status;
-
-    call = &pjsua.calls[call_index];
-
-    if (!call->inv) {
-	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
-	return PJSIP_ESESSIONTERMINATED;
-    }
-
-    /* Lock dialog. */
-    pjsip_dlg_inc_lock(call->inv->dlg);
-    
-    /* Create request message. */
-    status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method,
-				       -1, &tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status);
-	goto on_return;
-    }
-
-    /* Add accept header. */
-    pjsip_msg_add_hdr( tdata->msg, 
-		       (pjsip_hdr*)pjsua_im_create_accept(tdata->pool));
-
-    /* Create "text/plain" message body. */
-    tdata->msg->body = pjsip_msg_body_create( tdata->pool, &mime_text,
-					      &mime_plain, str);
-    if (tdata->msg->body == NULL) {
-	pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM);
-	pjsip_tx_data_dec_ref(tdata);
-	goto on_return;
-    }
-
-    /* Send the request. */
-    status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status);
-	goto on_return;
-    }
-
-on_return:
-    pjsip_dlg_dec_lock(call->inv->dlg);
-    return status;
-}
-
-
-/**
- * Send IM typing indication inside INVITE session.
- */
-PJ_DEF(pj_status_t) pjsua_call_send_typing_ind(int call_index, 
-					       pj_bool_t is_typing)
-{
-    pjsua_call *call;
-    pjsip_tx_data *tdata;
-    pj_status_t status;
-
-    call = &pjsua.calls[call_index];
-
-    if (!call->inv) {
-	PJ_LOG(3,(THIS_FILE,"Call has been disconnected"));
-	return PJSIP_ESESSIONTERMINATED;
-    }
-
-    /* Lock dialog. */
-    pjsip_dlg_inc_lock(call->inv->dlg);
-    
-    /* Create request message. */
-    status = pjsip_dlg_create_request( call->inv->dlg, &pjsip_message_method,
-				       -1, &tdata);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create MESSAGE request", status);
-	goto on_return;
-    }
-
-    /* Create "application/im-iscomposing+xml" msg body. */
-    tdata->msg->body = pjsip_iscomposing_create_body(tdata->pool, is_typing,
-						     NULL, NULL, -1);
-
-    /* Send the request. */
-    status = pjsip_dlg_send_request( call->inv->dlg, tdata, -1, NULL);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to send MESSAGE request", status);
-	goto on_return;
-    }
-
-on_return:
-    pjsip_dlg_dec_lock(call->inv->dlg);
-    return status;
-}
-
-
-/*
- * Terminate all calls.
- */
-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;
-	pjsua_call *call;
-
-	if (pjsua.calls[i].inv == NULL)
-	    continue;
-
-	call = &pjsua.calls[i];
-
-	if (call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
-	    st_code = 200;
-	} else {
-	    st_code = PJSIP_SC_GONE;
+    pjsua_var.calls[new_call].xfer_sub = sub;
+
+    /* Put the invite_data in the subscription. */
+    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, 
+			     &pjsua_var.calls[new_call]);
+}
+
+
+
+/*
+ * This callback is called when transaction state has changed in INVITE
+ * session. We use this to trap:
+ *  - incoming REFER request.
+ *  - incoming MESSAGE request.
+ */
+static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv,
+					    pjsip_transaction *tsx,
+					    pjsip_event *e)
+{
+    pjsua_call *call = inv->dlg->mod_data[pjsua_var.mod.id];
+
+    PJSUA_LOCK();
+
+    if (tsx->role==PJSIP_ROLE_UAS &&
+	tsx->state==PJSIP_TSX_STATE_TRYING &&
+	pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0)
+    {
+	/*
+	 * Incoming REFER request.
+	 */
+	on_call_transfered(call->inv, e->body.tsx_state.src.rdata);
+
+    }
+    else if (tsx->role==PJSIP_ROLE_UAS &&
+	     tsx->state==PJSIP_TSX_STATE_TRYING &&
+	     pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0)
+    {
+	/*
+	 * Incoming MESSAGE request!
+	 */
+	pjsip_rx_data *rdata;
+	pjsip_msg *msg;
+	pjsip_accept_hdr *accept_hdr;
+	pj_status_t status;
+
+	rdata = e->body.tsx_state.src.rdata;
+	msg = rdata->msg_info.msg;
+
+	/* Request MUST have message body, with Content-Type equal to
+	 * "text/plain".
+	 */
+	if (pjsua_im_accept_pager(rdata, &accept_hdr) == PJ_FALSE) {
+
+	    pjsip_hdr hdr_list;
+
+	    pj_list_init(&hdr_list);
+	    pj_list_push_back(&hdr_list, accept_hdr);
+
+	    pjsip_dlg_respond( inv->dlg, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, 
+			       NULL, &hdr_list, NULL );
+	    PJSUA_UNLOCK();
+	    return;
 	}
 
-	if (pjsip_inv_end_session(call->inv, st_code, NULL, &tdata)==0) {
-	    if (tdata)
-		pjsip_inv_send_msg(call->inv, tdata);
+	/* Respond with 200 first, so that remote doesn't retransmit in case
+	 * the UI takes too long to process the message. 
+	 */
+	status = pjsip_dlg_respond( inv->dlg, rdata, 200, NULL, NULL, NULL);
+
+	/* Process MESSAGE request */
+	pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str,
+			       &inv->dlg->local.info_str, rdata);
+
+    }
+    else if (tsx->role == PJSIP_ROLE_UAC &&
+	     pjsip_method_cmp(&tsx->method, &pjsip_message_method)==0)
+    {
+	/* Handle outgoing pager status */
+	if (tsx->status_code >= 200) {
+	    pjsua_im_data *im_data;
+
+	    im_data = tsx->mod_data[pjsua_var.mod.id];
+	    /* im_data can be NULL if this is typing indication */
+
+	    if (im_data && pjsua_var.ua_cfg.cb.on_pager_status) {
+		pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id,
+						    &im_data->to,
+						    &im_data->body,
+						    im_data->user_data,
+						    tsx->status_code,
+						    &tsx->status_text);
+	    }
 	}
     }
-}
-
-
-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, &inv_cb);
-    
-    return status;
-}
-
-/**
- * Replace media transport.
- */
-PJ_DEF(pj_status_t) pjsua_set_call_media_transport( unsigned call_index,
-						    const pjmedia_sock_info *i,
-						    pjmedia_transport *tp)
-{
-    pjsua_call *call = &pjsua.calls[call_index];
-
-    if (i)
-	pj_memcpy(&call->skinfo, i, sizeof(pjmedia_sock_info));
-    
-    if (call->med_tp)
-	(*call->med_tp->op->destroy)(call->med_tp);
-
-    call->med_tp = tp;
-    return PJ_SUCCESS;
-}
+
+
+    PJSUA_UNLOCK();
+}
Index: project/trunk/pjsip/src/pjsua-lib/pjsua_console_app.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_console_app.c	(revision 502)
+++ 	(revision )
@@ -1,926 +1,0 @@
-/* $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, max;
-
-    max = pjsua_call_get_max_count();
-    for (i=current_call+1; i<max; ++i) {
-	if (pjsua_call_is_active(i)) {
-	    current_call = i;
-	    return PJ_TRUE;
-	}
-    }
-
-    for (i=0; i<current_call; ++i) {
-	if (pjsua_call_is_active(i)) {
-	    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, max;
-
-    max = pjsua_call_get_max_count();
-    for (i=current_call-1; i>=0; --i) {
-	if (pjsua_call_is_active(i)) {
-	    current_call = i;
-	    return PJ_TRUE;
-	}
-    }
-
-    for (i=max-1; i>current_call; --i) {
-	if (pjsua_call_is_active(i)) {
-	    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_info call_info;
-
-    PJ_UNUSED_ARG(e);
-
-    pjsua_call_get_info(call_index, &call_info);
-
-    if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
-
-	PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]", 
-		  call_index,
-		  call_info.last_status,
-		  call_info.last_status_text.ptr));
-
-	if ((int)call_index == current_call) {
-	    find_next_call();
-	}
-
-    } else {
-
-	PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s", 
-		  call_index,
-		  call_info.state_text.ptr));
-
-	if (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.
-}
-
-
-/**
- * Notify UI on buddy state changed.
- */
-static void console_on_buddy_state(int buddy_index)
-{
-    pjsua_buddy_info info;
-    pjsua_buddy_get_info(buddy_index, &info);
-
-    PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
-	      (int)info.uri.slen,
-	      info.uri.ptr,
-	      (int)info.status_text.slen,
-	      info.status_text.ptr));
-}
-
-
-/**
- * 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, count;
-
-    puts("Buddy list:");
-
-    count = pjsua_get_buddy_count();
-
-    if (count == 0)
-	puts(" -none-");
-    else {
-	for (i=0; i<count; ++i) {
-	    pjsua_buddy_info info;
-
-	    if (pjsua_buddy_get_info(i, &info) != PJ_SUCCESS)
-		continue;
-
-	    if (!info.is_valid)
-		continue;
-
-	    printf(" [%2d] <%7s>  %.*s\n", 
-		    i+1, info.status_text.ptr, 
-		    (int)info.uri.slen,
-		    info.uri.ptr);
-	}
-    }
-    puts("");
-}
-
-
-/*
- * Print account status.
- */
-static void print_acc_status(int acc_index)
-{
-    char buf[80];
-    pjsua_acc_info info;
-
-    pjsua_acc_get_info(acc_index, &info);
-
-    if (!info.has_registration) {
-	pj_ansi_strcpy(buf, " -not registered to server-");
-
-    } else {
-	pj_ansi_snprintf(buf, sizeof(buf),
-			 "%.*s (%.*s;expires=%d)",
-			 (int)info.status_text.slen,
-			 info.status_text.ptr,
-			 (int)info.acc_id.slen,
-			 info.acc_id.ptr,
-			 info.expires);
-
-    }
-
-    printf("[%2d] Registration status: %s\n", acc_index, buf);
-    printf("     Online status: %s\n", 
-	   (info.online_status ? "Online" : "Invisible"));
-}
-
-/*
- * Show a bit of help.
- */
-static void keystroke_help(void)
-{
-    int i;
-
-    printf(">>>>\n");
-
-    for (i=0; i<(int)pjsua_get_acc_count(); ++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_get_buddy_count(), pjsua_get_buddy_count());
-    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_get_buddy_count()) 
-	{
-	    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;
-    pjsua_conf_port_id id[PJSUA_MAX_CALLS];
-
-    printf("Conference ports:\n");
-
-    count = PJ_ARRAY_SIZE(id);
-    pjsua_conf_enum_port_ids(id, &count);
-
-    for (i=0; i<count; ++i) {
-	char txlist[PJSUA_MAX_CALLS*4+10];
-	unsigned j;
-	pjsua_conf_port_info info;
-
-	pjsua_conf_get_port_info(id[i], &info);
-
-	txlist[0] = '\0';
-	for (j=0; j<info.listener_cnt; ++j) {
-	    char s[10];
-	    pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
-	    pj_ansi_strcat(txlist, s);
-	}
-	printf("Port #%02d[%2dKHz/%dms] %20.*s  transmitting to: %s\n", 
-	       info.slot_id, 
-	       info.clock_rate/1000,
-	       info.samples_per_frame * 1000 / info.clock_rate,
-	       (int)info.name.slen, 
-	       info.name.ptr,
-	       txlist);
-
-    }
-    puts("");
-}
-
-
-void pjsua_console_app_main(const pj_str_t *uri_to_call)
-{
-    char menuin[10];
-    char buf[128];
-    char text[128];
-    int i, count;
-    char *uri;
-    pj_str_t tmp;
-    struct input_result result;
-    pjsua_call_info call_info;
-    pjsua_acc_info acc_info;
-
-
-    /* If user specifies URI to call, then call the URI */
-    if (uri_to_call->slen) {
-	pjsua_call_make_call( current_acc, uri_to_call, 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_get_count());
-	    
-	    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 {
-		    pjsua_buddy_info binfo;
-		    pjsua_buddy_get_info(result.nb_result-1, &binfo);
-		    uri = binfo.uri.ptr;
-		}
-
-	    } else if (result.uri_result) {
-		uri = result.uri_result;
-	    }
-	    
-	    tmp = pj_str(uri);
-	    pjsua_call_make_call( current_acc, &tmp, NULL);
-	    break;
-
-	case 'M':
-	    /* Make multiple calls! : */
-	    printf("(You currently have %d calls)\n", 
-		   pjsua_call_get_count());
-	    
-	    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) {
-		pjsua_buddy_info binfo;
-		if (result.nb_result == -1 || result.nb_result == 0) {
-		    puts("You can't do that with make call!");
-		    continue;
-		}
-		pjsua_buddy_get_info(result.nb_result-1, &binfo);
-		uri = binfo.uri.ptr;
-	    } else {
-		uri =  result.uri_result;
-	    }
-
-	    for (i=0; i<atoi(menuin); ++i) {
-		pj_status_t status;
-	    
-		tmp = pj_str(uri);
-		status = pjsua_call_make_call(current_acc, &tmp, 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 {
-		    pjsua_buddy_info binfo;
-		    pjsua_buddy_get_info(result.nb_result-1, &binfo);
-		    uri = binfo.uri.ptr;
-		}
-
-	    } else if (result.uri_result) {
-		uri = result.uri_result;
-	    }
-	    
-
-	    /* Send typing indication. */
-	    if (i != -1)
-		pjsua_call_send_typing_ind(i, PJ_TRUE);
-	    else {
-		pj_str_t tmp_uri = pj_str(uri);
-		pjsua_im_typing(current_acc, &tmp_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_send_typing_ind(i, PJ_FALSE);
-		else {
-		    pj_str_t tmp_uri = pj_str(uri);
-		    pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE);
-		}
-		continue;
-	    }
-
-	    tmp = pj_str(text);
-
-	    /* Send the IM */
-	    if (i != -1)
-		pjsua_call_send_im(i, &tmp);
-	    else {
-		pj_str_t tmp_uri = pj_str(uri);
-		pjsua_im_send(current_acc, &tmp_uri, &tmp);
-	    }
-
-	    break;
-
-	case 'a':
-
-	    if (current_call != -1) {
-		pjsua_call_get_info(current_call, &call_info);
-	    } else {
-		/* Make compiler happy */
-		call_info.active = 0;
-		call_info.role = PJSIP_ROLE_UAC;
-		call_info.state = PJSIP_INV_STATE_DISCONNECTED;
-	    }
-
-	    if (current_call == -1 || 
-		call_info.active==0 ||
-		call_info.role != PJSIP_ROLE_UAS ||
-		call_info.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) {
-		
-		pjsua_call_get_info(current_call, &call_info);
-		PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s", 
-			  (int)call_info.remote_info.slen, 
-			  call_info.remote_info.ptr));
-
-	    } 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_buddy_info binfo;
-			pjsua_buddy_get_info(result.nb_result-1, &binfo);
-			pjsua_call_xfer( current_call,
-					 &binfo.uri);
-		    }
-
-		} else if (result.uri_result) {
-		    pj_str_t tmp;
-		    tmp = pj_str(result.uri_result);
-		    pjsua_call_xfer( current_call, &tmp);
-		}
-	    }
-	    break;
-
-	case '#':
-	    /*
-	     * Send DTMF strings.
-	     */
-	    if (current_call == -1) {
-		
-		PJ_LOG(3,(THIS_FILE, "No current call"));
-
-	    } else if (!pjsua_call_has_media(current_call)) {
-
-		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 = pjsua_call_dial_dtmf(current_call, &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, count;
-		    count = pjsua_get_buddy_count();
-		    for (i=0; i<count; ++i)
-			pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
-		} else if (result.nb_result == 0) {
-		    puts("Sorry, can only subscribe to buddy's presence, "
-			 "not from existing call");
-		} else {
-		    pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
-		}
-
-	    } 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_acc_set_registration(current_acc, PJ_TRUE);
-		break;
-	    case 'u':
-		/*
-		 * Unregister
-		 */
-		pjsua_acc_set_registration(current_acc, PJ_FALSE);
-		break;
-	    }
-	    break;
-	    
-	case 't':
-	    pjsua_acc_get_info(current_acc, &acc_info);
-	    acc_info.online_status = !acc_info.online_status;
-	    pjsua_acc_set_online_status(current_acc, acc_info.online_status);
-	    printf("Setting %s online status to %s\n",
-		   acc_info.acc_id.ptr,
-		   (acc_info.online_status?"online":"offline"));
-	    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 = pjsua_conf_connect(atoi(src_port), 
-						    atoi(dst_port));
-		    } else {
-			status = pjsua_conf_disconnect(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(NULL, 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:
-    ;
-}
-
-
-/*****************************************************************************
- * 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,
-    NULL,   /* on_incoming_call		*/
-    NULL,   /* default accept transfer	*/
-    &console_on_reg_state,
-    &console_on_buddy_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 502)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_core.c	(revision 503)
@@ -18,176 +18,116 @@
  */
 #include <pjsua-lib/pjsua.h>
-#include "pjsua_imp.h"
-
-/*
- * pjsua_core.c
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE   "pjsua_core.c"
+
+
+/* PJSUA application instance. */
+struct pjsua_data pjsua_var;
+
+
+/* Display error */
+PJ_DEF(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(3,(sender, "%s: %s [status=%d]", title, errmsg, status));
+}
+
+
+static void init_data()
+{
+    unsigned i;
+
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i)
+	pjsua_var.acc[i].index = i;
+    
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata); ++i)
+	pjsua_var.tpdata[i].index = i;
+}
+
+
+
+/*****************************************************************************
+ * 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:
  *
- * Core application functionalities.
- */
-
-#define THIS_FILE   "pjsua_core.c"
-
-
-/* 
- * Global variable.
- */
-struct pjsua pjsua;
-
-
-/* 
- * Default local URI, if none is specified in cmd-line 
- */
-#define PJSUA_LOCAL_URI	    "<sip:user@127.0.0.1>"
-
-
-
-/*
- * Init default application parameters.
- */
-PJ_DEF(void) pjsua_default_config(pjsua_config *cfg)
-{
-    unsigned i;
-
-    pj_memset(cfg, 0, sizeof(pjsua_config));
-
-    cfg->thread_cnt = 1;
-    cfg->media_has_ioqueue = 1;
-    cfg->media_thread_cnt = 1;
-    cfg->udp_port = 5060;
-    cfg->start_rtp_port = 4000;
-    cfg->msg_logging = PJ_TRUE;
-    cfg->max_calls = 4;
-    cfg->conf_ports = 0;
-
-#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
-    pjsua.clock_rate = 44100;
-#endif
-
-    cfg->complexity = 10;
-    cfg->quality = 10;
+ *  - 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 logging_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));
     
-    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.
+    /* Always return false, otherwise messages will not get processed! */
+    return PJ_FALSE;
+}
+
+/* Notification on outgoing messages */
+static pj_status_t logging_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.
      */
-    pjsua.config.log_level = 5;
-    pjsua.config.app_log_level = 4;
-
-
-    /* Init accounts: */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) {
-	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;
-	    }
-
-	}
-    }
-
+
+    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 pjsua_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()		*/
+    &logging_on_rx_msg,			/* on_rx_request()	*/
+    &logging_on_rx_msg,			/* on_rx_response()	*/
+    &logging_on_tx_msg,			/* on_tx_request.	*/
+    &logging_on_tx_msg,			/* on_tx_response()	*/
+    NULL,				/* on_tsx_state()	*/
+
+};
+
+
+/*****************************************************************************
+ * These two functions are the main callbacks registered to PJSIP stack
+ * to receive SIP request and response messages that are outside any
+ * dialogs and any transactions.
+ */
 
 /*
@@ -202,11 +142,16 @@
 static pj_bool_t mod_pjsua_on_rx_request(pjsip_rx_data *rdata)
 {
+    pj_bool_t processed = PJ_FALSE;
+
+    PJSUA_LOCK();
 
     if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) {
 
-	return pjsua_call_on_incoming(rdata);
-    }
-
-    return PJ_FALSE;
+	processed = pjsua_call_on_incoming(rdata);
+    }
+
+    PJSUA_UNLOCK();
+
+    return processed;
 }
 
@@ -230,740 +175,197 @@
 
 
-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;
-}
-
-/**
- * Poll pjsua.
- */
-PJ_DECL(int) pjsua_handle_events(unsigned msec_timeout)
-{
-    unsigned count = 0;
-    pj_time_val tv;
+/*****************************************************************************
+ * Logging.
+ */
+
+/* Log callback */
+static void log_writer(int level, const char *buffer, int len)
+{
+    /* Write to stdout, file, and application callback. */
+
+    if (level <= (int)pjsua_var.log_cfg.console_level)
+	pj_log_write(level, buffer, len);
+
+    if (pjsua_var.log_file) {
+	pj_ssize_t size = len;
+	pj_file_write(pjsua_var.log_file, buffer, &size);
+    }
+
+    if (pjsua_var.log_cfg.cb)
+	(*pjsua_var.log_cfg.cb)(level, buffer, len);
+}
+
+
+/*
+ * Application can call this function at any time (after pjsua_create(), of
+ * course) to change logging settings.
+ */
+PJ_DEF(pj_status_t) pjsua_reconfigure_logging(const pjsua_logging_config *cfg)
+{
     pj_status_t status;
 
-    tv.sec = 0;
-    tv.msec = msec_timeout;
-    pj_time_val_normalize(&tv);
-
-    status = pjsip_endpt_handle_events2(pjsua.endpt, &tv, &count);
-    if (status != PJ_SUCCESS)
-	return -status;
-
-    return count;
-}
-
-
-#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);
+    /* Save config. */
+    pjsua_logging_config_dup(pjsua_var.pool, &pjsua_var.log_cfg, cfg);
+
+    /* Redirect log function to ours */
+    pj_log_set_log_func( &log_writer );
+
+    /* Close existing file, if any */
+    if (pjsua_var.log_file) {
+	pj_file_close(pjsua_var.log_file);
+	pjsua_var.log_file = NULL;
+    }
+
+    /* If output log file is desired, create the file: */
+    if (pjsua_var.log_cfg.log_filename.slen) {
+
+	status = pj_file_open(pjsua_var.pool, 
+			      pjsua_var.log_cfg.log_filename.ptr,
+			      PJ_O_WRONLY, 
+			      &pjsua_var.log_file);
+
 	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "STUN resolve error", status);
-	    pj_sock_close(sock);
+	    pjsua_perror(THIS_FILE, "Error creating log file", status);
 	    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;
+    }
+
+    /* Unregister msg logging if it's previously registered */
+    if (pjsua_msg_logger.id >= 0) {
+	pjsip_endpt_unregister_module(pjsua_var.endpt, &pjsua_msg_logger);
+	pjsua_msg_logger.id = -1;
+    }
+
+    /* Enable SIP message logging */
+    if (pjsua_var.log_cfg.msg_logging)
+	pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_msg_logger);
+
+
     return PJ_SUCCESS;
 }
 
 
-/* 
- * Create RTP and RTCP socket pair, and possibly resolve their public
- * address via STUN.
- */
-static pj_status_t create_rtp_rtcp_sock(pjmedia_sock_info *skinfo)
-{
-    enum { 
-	RTP_RETRY = 100
-    };
-    int i;
-    static pj_uint16_t rtp_port;
-    pj_sockaddr_in mapped_addr[2];
-    pj_status_t status = PJ_SUCCESS;
-    pj_sock_t sock[2];
-
-    if (rtp_port == 0)
-	rtp_port = (pj_uint16_t)pjsua.config.start_rtp_port;
-
-    for (i=0; i<2; ++i)
-	sock[i] = PJ_INVALID_SOCKET;
-
-
-    /* Loop retry to bind RTP and RTCP sockets. */
-    for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
-
-	/* Create and bind RTP socket. */
-	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "socket() error", status);
-	    return status;
-	}
-
-	status = pj_sock_bind_in(sock[0], 0, rtp_port);
-	if (status != PJ_SUCCESS) {
-	    pj_sock_close(sock[0]); 
-	    sock[0] = PJ_INVALID_SOCKET;
-	    continue;
-	}
-
-	/* Create and bind RTCP socket. */
-	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[1]);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "socket() error", status);
-	    pj_sock_close(sock[0]);
-	    return status;
-	}
-
-	status = pj_sock_bind_in(sock[1], 0, (pj_uint16_t)(rtp_port+1));
-	if (status != PJ_SUCCESS) {
-	    pj_sock_close(sock[0]); 
-	    sock[0] = PJ_INVALID_SOCKET;
-
-	    pj_sock_close(sock[1]); 
-	    sock[1] = PJ_INVALID_SOCKET;
-	    continue;
-	}
-
-	/*
-	 * If we're configured to use STUN, then find out the mapped address,
-	 * and make sure that the mapped RTCP port is adjacent with the RTP.
-	 */
-	if (pjsua_has_stun()) {
-	    status=pj_stun_get_mapped_addr(&pjsua.cp.factory, 2, sock,
-					   &pjsua.config.stun_srv1, 
-					   pjsua.config.stun_port1,
-					   &pjsua.config.stun_srv2, 
-					   pjsua.config.stun_port2,
-					   mapped_addr);
-	    if (status != PJ_SUCCESS) {
-		pjsua_perror(THIS_FILE, "STUN resolve error", status);
-		goto on_error;
-	    }
-
-	    if (pj_ntohs(mapped_addr[1].sin_port) == 
-		pj_ntohs(mapped_addr[0].sin_port)+1)
-	    {
-		/* Success! */
-		break;
-	    }
-
-	    pj_sock_close(sock[0]); 
-	    sock[0] = PJ_INVALID_SOCKET;
-
-	    pj_sock_close(sock[1]); 
-	    sock[1] = PJ_INVALID_SOCKET;
-
-	} else {
-	    const pj_str_t *hostname;
-	    pj_sockaddr_in addr;
-
-	    /* Get local IP address. */
-	    hostname = pj_gethostname();
-
-	    pj_memset( &addr, 0, sizeof(addr));
-	    addr.sin_family = PJ_AF_INET;
-	    status = pj_sockaddr_in_set_str_addr( &addr, hostname);
-	    if (status != PJ_SUCCESS) {
-		pjsua_perror(THIS_FILE, "Unresolvable local hostname", 
-			     status);
-		goto on_error;
-	    }
-
-	    for (i=0; i<2; ++i)
-		pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
-
-	    mapped_addr[0].sin_port=pj_htons((pj_uint16_t)rtp_port);
-	    mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(rtp_port+1));
-	    break;
-	}
-    }
-
-    if (sock[0] == PJ_INVALID_SOCKET) {
-	PJ_LOG(1,(THIS_FILE, 
-		  "Unable to find appropriate RTP/RTCP ports combination"));
-	goto on_error;
-    }
-
-
-    skinfo->rtp_sock = sock[0];
-    pj_memcpy(&skinfo->rtp_addr_name, 
-	      &mapped_addr[0], sizeof(pj_sockaddr_in));
-
-    skinfo->rtcp_sock = sock[1];
-    pj_memcpy(&skinfo->rtcp_addr_name, 
-	      &mapped_addr[1], sizeof(pj_sockaddr_in));
-
-    PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
-	      pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr), 
-	      pj_ntohs(skinfo->rtp_addr_name.sin_port)));
-    PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
-	      pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr), 
-	      pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
-
-    rtp_port += 2;
+/*****************************************************************************
+ * PJSUA Base API.
+ */
+
+/* Worker thread function. */
+static int worker_thread(void *arg)
+{
+    enum { TIMEOUT = 10 };
+
+    PJ_UNUSED_ARG(arg);
+
+    while (!pjsua_var.thread_quit_flag) {
+	int count;
+
+	count = pjsua_handle_events(TIMEOUT);
+	if (count < 0)
+	    pj_thread_sleep(TIMEOUT);
+    }
+
+    return 0;
+}
+
+
+/*
+ * Instantiate pjsua application.
+ */
+PJ_DEF(pj_status_t) pjsua_create(void)
+{
+    pj_status_t status;
+
+    /* Init pjsua data */
+    init_data();
+
+    /* Set default logging settings */
+    pjsua_logging_config_default(&pjsua_var.log_cfg);
+
+    /* Init PJLIB: */
+    status = pj_init();
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+    /* Init PJLIB-UTIL: */
+    status = pjlib_util_init();
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+    /* Init caching pool. */
+    pj_caching_pool_init(&pjsua_var.cp, &pj_pool_factory_default_policy, 0);
+
+    /* Create memory pool for application. */
+    pjsua_var.pool = pjsua_pool_create("pjsua", 4000, 4000);
+    
+    PJ_ASSERT_RETURN(pjsua_var.pool, PJ_ENOMEM);
+
+    /* Create mutex */
+    status = pj_mutex_create_recursive(pjsua_var.pool, "pjsua", 
+				       &pjsua_var.mutex);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create mutex", status);
+	return status;
+    }
+
+    /* Must create SIP endpoint to initialize SIP parser. The parser
+     * is needed for example when application needs to call pjsua_verify_url().
+     */
+    status = pjsip_endpt_create(&pjsua_var.cp.factory, 
+				pj_gethostname()->ptr, 
+				&pjsua_var.endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
     return PJ_SUCCESS;
-
-on_error:
-    for (i=0; i<2; ++i) {
-	if (sock[i] != PJ_INVALID_SOCKET)
-	    pj_sock_close(sock[i]);
-    }
-    return status;
-}
-
-
-
-/**
- * Create pjsua application.
- * This initializes pjlib/pjlib-util, and creates memory pool factory to
- * be used by application.
- */
-PJ_DEF(pj_status_t) pjsua_create(void)
-{
+}
+
+
+/*
+ * Initialize pjsua with the specified settings. All the settings are 
+ * optional, and the default values will be used when the config is not
+ * specified.
+ */
+PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg,
+				const pjsua_logging_config *log_cfg,
+				const pjsua_media_config *media_cfg)
+{
+    pjsua_config	 default_cfg;
+    pjsua_media_config	 default_media_cfg;
     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: */
-
-    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, 
-				  pjsua.config.media_has_ioqueue? NULL :
-				       pjsip_endpt_get_ioqueue(pjsua.endpt), 
-				  pjsua.config.media_thread_cnt,
-				  &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;
-    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 = PJMEDIA_CONF_NO_DEVICE;
-
-    /* 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. */
-    pjsua.clock_rate = pjsua.config.clock_rate ? pjsua.config.clock_rate : 16000;
-    pjsua.samples_per_frame = pjsua.clock_rate * 10 / 1000;
-    status = pjmedia_conf_create(pjsua.pool, 
-				 pjsua.config.conf_ports, 
-				 pjsua.clock_rate, 
-				 1, /* mono */
-				 pjsua.samples_per_frame, 
-				 16, 
-				 options,
-				 &pjsua.mconf);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, 
-		     "Media stack initialization has returned error", 
-		     status);
-	return status;
-    }
-
-    if (pjsua.config.null_audio == PJ_FALSE) {
-	pjmedia_port *conf_port;
-
-	/* Create sound device port */
-	status = pjmedia_snd_port_create(pjsua.pool, 
-					 pjsua.config.snd_capture_id,
-					 pjsua.config.snd_player_id, 
-					 pjsua.clock_rate, 1 /* mono */,
-					 pjsua.samples_per_frame, 16,
-					 0, &pjsua.snd_port);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create sound device", status);
-	    return status;
-	}
-
-	/* Get the port interface of the conference bridge */
-	conf_port = pjmedia_conf_get_master_port(pjsua.mconf);
-
-	/* Connect conference port interface to sound port */
-	pjmedia_snd_port_connect( pjsua.snd_port, conf_port);
-
-    } else {
-	pjmedia_port *null_port, *conf_port;
-
-	/* Create NULL port */
-	status = pjmedia_null_port_create(pjsua.pool, pjsua.clock_rate,
-					  1, pjsua.samples_per_frame, 16,
-					  &null_port);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create NULL port", status);
-	    return status;
-	}
-
-	/* Get the port interface of the conference bridge */
-	conf_port = pjmedia_conf_get_master_port(pjsua.mconf);
-
-	/* Create master port to control conference bridge's clock */
-	status = pjmedia_master_port_create(pjsua.pool, null_port, conf_port,
-					    0, &pjsua.master_port);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create master port", status);
-	    return status;
-	}
-    }
-
-    /* Create WAV file player if required: */
-
-    if (pjsua.config.wav_file.slen) {
-
-	status = pjsua_player_create(&pjsua.config.wav_file, NULL);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to create file player", 
-			 status);
-	    return status;
-	}
-    }
-
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Copy account configuration.
- */
-static void copy_acc_config(pj_pool_t *pool,
-			    pjsua_acc_config *dst_acc,
-			    const pjsua_acc_config *src_acc)
-{
-    unsigned j;
-
-    pj_memcpy(dst_acc, src_acc, sizeof(pjsua_acc_config));
-
-    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);
-    }
-}
-
-
-/*
- * Copy configuration.
- */
-void pjsua_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];
-	copy_acc_config(pool, dst_acc, src_acc);
-    }
-
-    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]);
-    }
-}
-
-
-/*****************************************************************************
- * Console application custom logging:
- */
-
-
-static void 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 (pjsua.log_file) {
-	fwrite(buffer, len, 1, pjsua.log_file);
-	fflush(pjsua.log_file);
-    }
-}
-
-
-static pj_status_t logging_init()
-{
-    /* Redirect log function to ours */
-
-    pj_log_set_log_func( &log_writer );
-
-    /* If output log file is desired, create the file: */
-
-    if (pjsua.config.log_filename.slen) {
-	pjsua.log_file = fopen(pjsua.config.log_filename.ptr, "wt");
-	if (pjsua.log_file == NULL) {
-	    PJ_LOG(1,(THIS_FILE, "Unable to open log file %s", 
-		      pjsua.config.log_filename.ptr));   
-	    return -1;
-	}
-    }
-
-    /* Enable SIP message logging */
-    if (pjsua.config.msg_logging)
-	pjsip_endpt_register_module(pjsua.endpt, &pjsua_msg_logger);
-
-    return PJ_SUCCESS;
-}
-
-
-static void logging_shutdown(void)
-{
-    /* Close logging file, if any: */
-
-    if (pjsua.log_file) {
-	fclose(pjsua.log_file);
-	pjsua.log_file = NULL;
-    }
-}
-
-
-/*
- * 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;
-    }
-
-    /* Init buddies array */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.buddies); ++i) {
-	pjsua.buddies[i].index = i;
-    }
-
-    /* Copy configuration */
-    pjsua_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));
-	status = -1;
-	goto on_error;
-    }
-
-
-    /* Init PJLIB logging: */
-
-    pj_log_set_level(pjsua.config.log_level);
-    pj_log_set_decor(pjsua.config.log_decor);
-
-    status = logging_init();
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-
-    /* 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)
-	    goto on_error;
-    
-	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"));
-	    status = PJ_EINVAL;
-	    goto on_error;
-	}
-
-	pjsua.sip_sock = PJ_INVALID_SOCKET;
-    }
-
-
-    /* Init media endpoint */
-    status = init_media();
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-
-    /* 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) {
-		if (pjsua.calls[i].med_tp)
-		    pjsua.calls[i].med_tp->op->destroy(pjsua.calls[i].med_tp);
-	    }
-	    goto on_error;
-	}
-	status = pjmedia_transport_udp_attach(pjsua.med_endpt, NULL,
-					      &pjsua.calls[i].skinfo, 0,
-					      &pjsua.calls[i].med_tp);
-    }
-
-    /* Init PJSIP : */
+
+    /* Create default configurations when the config is not supplied */
+
+    if (ua_cfg == NULL) {
+	pjsua_config_default(&default_cfg);
+	ua_cfg = &default_cfg;
+    }
+
+    if (media_cfg == NULL) {
+	pjsua_media_config_default(&default_media_cfg);
+	media_cfg = &default_media_cfg;
+    }
+
+    /* Initialize logging first so that info/errors can be captured */
+    if (log_cfg) {
+	status = pjsua_reconfigure_logging(log_cfg);
+	PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+    }
+
+    /* Init SIP UA: */
 
     /* Initialize transaction layer: */
-
-    status = pjsip_tsx_layer_init_module(pjsua.endpt);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Transaction layer initialization error", 
-		     status);
-	goto on_error;
-    }
+    status = pjsip_tsx_layer_init_module(pjsua_var.endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
 
     /* Initialize UA layer module: */
-
-    status = pjsip_ua_init_module( pjsua.endpt, NULL );
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "UA layer initialization error", status);
-	goto on_error;
-    }
-
-    /* Initialize and register pjsua's application module: */
-
+    status = pjsip_ua_init_module( pjsua_var.endpt, NULL );
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+    /* Initialize and register PJSUA application module. */
     {
-	pjsip_module my_mod = 
+	const pjsip_module mod_initializer = 
 	{
 	NULL, NULL,		    /* prev, next.			*/
@@ -982,45 +384,68 @@
 	};
 
-	pjsua.mod = my_mod;
-
-	status = pjsip_endpt_register_module(pjsua.endpt, &pjsua.mod);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to register pjsua module", 
-			 status);
-	    goto on_error;
+	pjsua_var.mod = mod_initializer;
+
+	status = pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_var.mod);
+	PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+    }
+
+    
+
+    /* Initialize PJSUA call subsystem: */
+    status = pjsua_call_subsys_init(ua_cfg);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+
+    /* Initialize PJSUA media subsystem */
+    status = pjsua_media_subsys_init(media_cfg);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+
+    /* Init core SIMPLE module : */
+    status = pjsip_evsub_init_module(pjsua_var.endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+    /* Init presence module: */
+    status = pjsip_pres_init_module( pjsua_var.endpt, pjsip_evsub_instance());
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+
+    /* Init xfer/REFER module */
+    status = pjsip_xfer_init_module( pjsua_var.endpt );
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    /* Init pjsua presence handler: */
+    status = pjsua_pres_init();
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Init out-of-dialog MESSAGE request handler. */
+    status = pjsua_im_init();
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    /* Start worker thread if needed. */
+    if (pjsua_var.ua_cfg.thread_cnt) {
+	unsigned i;
+
+	if (pjsua_var.ua_cfg.thread_cnt > PJ_ARRAY_SIZE(pjsua_var.thread))
+	    pjsua_var.ua_cfg.thread_cnt = PJ_ARRAY_SIZE(pjsua_var.thread);
+
+	for (i=0; i<pjsua_var.ua_cfg.thread_cnt; ++i) {
+	    status = pj_thread_create(pjsua_var.pool, "pjsua", &worker_thread,
+				      NULL, 0, 0, &pjsua_var.thread[i]);
+	    if (status != PJ_SUCCESS)
+		goto on_error;
 	}
     }
 
-    /* Initialize invite session module: */
-
-    status = pjsua_call_init();
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Invite usage initialization error", 
-		     status);
-	goto on_error;
-    }
-
-    /* Init core SIMPLE module : */
-
-    pjsip_evsub_init_module(pjsua.endpt);
-
-    /* Init presence module: */
-
-    pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance());
-
-    /* Init xfer/REFER module */
-
-    pjsip_xfer_init_module( pjsua.endpt );
-
-    /* Init pjsua presence handler: */
-
-    pjsua_pres_init();
-
-    /* Init out-of-dialog MESSAGE request handler. */
-
-    pjsua_im_init();
-
-
-    /* Done. */
+    /* Done! */
+
+    PJ_LOG(3,(THIS_FILE, "pjsua version %s for %s initialized", 
+			 PJ_VERSION, PJ_OS_NAME));
+
     return PJ_SUCCESS;
 
@@ -1031,411 +456,4 @@
 
 
-/*
- * Find account for incoming request.
- */
-PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata)
-{
-    pjsip_uri *uri;
-    pjsip_sip_uri *sip_uri;
-    unsigned acc_index;
-
-    uri = rdata->msg_info.to->uri;
-
-    /* Just return default account if To URI is not SIP: */
-    if (!PJSIP_URI_SCHEME_IS_SIP(uri) && 
-	!PJSIP_URI_SCHEME_IS_SIPS(uri)) 
-    {
-	return pjsua.default_acc;
-    }
-
-
-    sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri);
-
-    /* Find account which has matching username and domain. */
-    for (acc_index=0; acc_index < pjsua.config.acc_cnt; ++acc_index) {
-
-	pjsua_acc *acc = &pjsua.acc[acc_index];
-
-	if (pj_stricmp(&acc->user_part, &sip_uri->user)==0 &&
-	    pj_stricmp(&acc->host_part, &sip_uri->host)==0) 
-	{
-	    /* Match ! */
-	    return acc_index;
-	}
-    }
-
-    /* No matching, try match domain part only. */
-    for (acc_index=0; acc_index < pjsua.config.acc_cnt; ++acc_index) {
-
-	pjsua_acc *acc = &pjsua.acc[acc_index];
-
-	if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) {
-	    /* Match ! */
-	    return acc_index;
-	}
-    }
-
-    /* Still no match, use default account */
-    return pjsua.default_acc;
-}
-
-
-/*
- * Find account for outgoing request.
- */
-PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *str_url)
-{
-    pj_str_t tmp;
-    pjsip_uri *uri;
-    pjsip_sip_uri *sip_uri;
-    unsigned i;
-
-    pj_strdup_with_null(pjsua.pool, &tmp, str_url);
-
-    uri = pjsip_parse_uri(pjsua.pool, tmp.ptr, tmp.slen, 0);
-    if (!uri)
-	return pjsua.config.acc_cnt-1;
-
-    if (!PJSIP_URI_SCHEME_IS_SIP(uri) && 
-	!PJSIP_URI_SCHEME_IS_SIPS(uri)) 
-    {
-	/* Return the first account with proxy */
-	for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) {
-	    if (!pjsua.acc[i].valid)
-		continue;
-	    if (pjsua.config.acc_config[i].proxy.slen)
-		break;
-	}
-
-	if (i != PJ_ARRAY_SIZE(pjsua.acc))
-	    return i;
-
-	/* Not found, use default account */
-	return pjsua.default_acc;
-    }
-
-    sip_uri = pjsip_uri_get_uri(uri);
-
-    /* Find matching domain */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) {
-	if (!pjsua.acc[i].valid)
-	    continue;
-	if (pj_stricmp(&pjsua.acc[i].host_part, &sip_uri->host)==0)
-	    break;
-    }
-
-    if (i != PJ_ARRAY_SIZE(pjsua.acc))
-	return i;
-
-    /* Just use default account */
-    return pjsua.default_acc;
-}
-
-
-/*
- * Init account
- */
-static pj_status_t init_acc(unsigned acc_index)
-{
-    pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index];
-    pjsua_acc *acc = &pjsua.acc[acc_index];
-    pjsip_uri *uri;
-    pjsip_sip_uri *sip_uri;
-
-    /* Need to parse local_uri to get the elements: */
-
-    uri = pjsip_parse_uri(pjsua.pool, acc_cfg->id.ptr,
-			  acc_cfg->id.slen, 0);
-    if (uri == NULL) {
-	pjsua_perror(THIS_FILE, "Invalid local URI", 
-		     PJSIP_EINVALIDURI);
-	return PJSIP_EINVALIDURI;
-    }
-
-    /* Local URI MUST be a SIP or SIPS: */
-
-    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;
-    }
-
-
-    /* Get the SIP URI object: */
-
-    sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri);
-
-    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
-	 *  - username is taken from the local URI,
-	 *  - ip-addr in UDP transport's address name (which may have been
-	 *    resolved from STUN.
-	 */
-	
-	/* Build temporary contact string. */
-
-	if (sip_uri->user.slen) {
-
-	    /* With the user part. */
-	    len = pj_ansi_snprintf(contact, sizeof(contact),
-			      "<sip:%.*s@%s:%d>",
-			      (int)sip_uri->user.slen,
-			      sip_uri->user.ptr,
-			      addr, port);
-	} else {
-
-	    /* Without user part */
-
-	    len = pj_ansi_snprintf(contact, sizeof(contact),
-			      "<sip:%s:%d>",
-			      addr, 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, &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);
-    }
-
-    /* Mark account as valid */
-    pjsua.acc[acc_index].valid = PJ_TRUE;
-
-
-    return PJ_SUCCESS;
-}
-
-/*
- * Add a new account.
- */
-PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg,
-				   pjsua_acc_id *acc_index)
-{
-    unsigned index;
-    pj_status_t status;
-
-    PJ_ASSERT_RETURN(pjsua.config.acc_cnt < 
-			PJ_ARRAY_SIZE(pjsua.config.acc_config),
-		     PJ_ETOOMANY);
-
-    /* Find empty account index. */
-    for (index=0; index < PJ_ARRAY_SIZE(pjsua.acc); ++index) {
-	if (pjsua.acc[index].valid == PJ_FALSE)
-	    break;
-    }
-
-    /* Expect to find a slot */
-    PJ_ASSERT_RETURN(index < PJ_ARRAY_SIZE(pjsua.acc), PJ_EBUG);
-
-    copy_acc_config(pjsua.pool, &pjsua.config.acc_config[index], cfg);
-    
-    status = init_acc(index);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Error adding account", status);
-	return status;
-    }
-
-    if (acc_index)
-	*acc_index = index;
-
-    pjsua.config.acc_cnt++;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Delete account.
- */
-PJ_DEF(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_index)
-{
-    PJ_ASSERT_RETURN(acc_index < (int)pjsua.config.acc_cnt, 
-		     PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP);
-
-    /* Delete registration */
-    if (pjsua.acc[acc_index].regc != NULL) 
-	pjsua_acc_set_registration(acc_index, PJ_FALSE);
-
-    /* Invalidate */
-    pjsua.acc[acc_index].valid = PJ_FALSE;
-
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Start pjsua stack.
- * This will start the registration process, if registration is configured.
- */
-PJ_DEF(pj_status_t) pjsua_start(void)
-{
-    int i;  /* Must be signed */
-    unsigned count;
-    pj_status_t status = PJ_SUCCESS;
-
-
-    /* Add UDP transport: */
-    if (pjsua.sip_sock > 0) {
-
-	/* Init the published name for the transport.
-         * Depending whether STUN is used, this may be the STUN mapped
-	 * address, or socket's bound address.
-	 */
-	pjsip_host_port addr_name;
-
-	addr_name.host = pjsua.config.sip_host;
-	addr_name.port = pjsua.config.sip_port;
-
-	/* Create UDP transport from previously created UDP socket: */
-
-	status = pjsip_udp_transport_attach( pjsua.endpt, pjsua.sip_sock,
-					     &addr_name, 1, 
-					     NULL);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Unable to start UDP transport", 
-			 status);
-	    goto on_error;
-	}
-    }
-
-    /* Initialize all unused accounts with default id and contact.
-     */
-    {
-	char buf[80];
-	pj_str_t tmp, id;
-
-	tmp.ptr = buf;
-	tmp.slen = pj_ansi_sprintf(tmp.ptr, "Local <sip:%s:%d>", 
-				   pjsua.config.sip_host.ptr,
-				   pjsua.config.sip_port);
-	pj_strdup_with_null( pjsua.pool, &id, &tmp);
-
-	for (i=pjsua.config.acc_cnt; i<PJ_ARRAY_SIZE(pjsua.config.acc_config);
-	     ++i)
-	{
-	    pjsua_acc_config *acc_cfg = 
-		&pjsua.config.acc_config[pjsua.config.acc_cnt];
-
-	    acc_cfg->id = id;
-	    acc_cfg->contact = id;
-	}
-    }
-    
-
-    /* Add another account as the last one. 
-     * This account corresponds to local endpoint, and is user-less.
-     * This is also the default account.
-     */
-    if (pjsua.config.acc_cnt < PJ_ARRAY_SIZE(pjsua.config.acc_config)) {
-	pjsua.default_acc = pjsua.config.acc_cnt;
-	pjsua.acc[pjsua.default_acc].auto_gen = 1;
-	pjsua.config.acc_cnt++;
-    }
-
-
-    /* Initialize accounts: */
-    for (i=0; i<(int)pjsua.config.acc_cnt; ++i) {
-	status = init_acc(i);
-	if (status != PJ_SUCCESS) {
-	    pjsua_perror(THIS_FILE, "Error initializing account", status);
-	    goto on_error;
-	}
-    }
-
-
-    /* Create worker thread(s), if required: */
-
-    for (i=0; i<(int)pjsua.config.thread_cnt; ++i) {
-	status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_poll,
-				   NULL, 0, 0, &pjsua.threads[i]);
-	if (status != PJ_SUCCESS) {
-	    pjsua.quit_flag = 1;
-	    for (--i; i>=0; --i) {
-		pj_thread_join(pjsua.threads[i]);
-		pj_thread_destroy(pjsua.threads[i]);
-	    }
-	    goto on_error;
-	}
-    }
-
-    /* Start registration: */
-
-    /* Create client registration session: */
-    for (i=0; i<(int)pjsua.config.acc_cnt; ++i) {
-	status = pjsua_regc_init(i);
-	if (status != PJ_SUCCESS)
-	    goto on_error;
-
-	/* Perform registration, if required. */
-	if (pjsua.acc[i].regc) {
-	    pjsua_acc_set_registration(i, PJ_TRUE);
-	}
-    }
-
-
-    /* Re-init buddies */
-    count = pjsua.config.buddy_cnt;
-    pjsua.config.buddy_cnt = 0;
-    for (i=0; i<(int)count; ++i) {
-	pj_str_t uri = pjsua.config.buddy_uri[i];
-	pjsua_buddy_add(&uri, NULL);
-    }
-
-
-    /* Refresh presence */
-    pjsua_pres_refresh();
-
-
-    PJ_LOG(3,(THIS_FILE, "PJSUA version %s started", PJ_VERSION));
-    return PJ_SUCCESS;
-
-on_error:
-    pjsua_destroy();
-    return status;
-}
-
-
 /* Sleep with polling */
 static void busy_sleep(unsigned msec)
@@ -1448,295 +466,30 @@
 
     do {
-	pjsua_poll(NULL);
+	while (pjsua_handle_events(10) > 0)
+	    ;
 	pj_gettimeofday(&now);
     } while (PJ_TIME_VAL_LT(now, timeout));
 }
 
-/**
- * Get maxinum number of conference ports.
- */
-PJ_DEF(unsigned) pjsua_conf_max_ports(void)
-{
-    return pjsua.config.conf_ports;
-}
-
-
-/**
- * Enum all conference port ID.
- */
-PJ_DEF(pj_status_t) pjsua_conf_enum_port_ids( pjsua_conf_port_id id[],
-					      unsigned *count)
-{
-    return pjmedia_conf_enum_ports( pjsua.mconf, (unsigned*)id, count);
-}
-
-
-
-/**
- * Get information about the specified conference port
- */
-PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
-					      pjsua_conf_port_info *info)
-{
-    pjmedia_conf_port_info cinfo;
-    unsigned i, count;
-    pj_status_t status;
-
-    status = pjmedia_conf_get_port_info( pjsua.mconf, id, &cinfo);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    pj_memset(info, 0, sizeof(*info));
-    info->slot_id = id;
-    info->name = cinfo.name;
-    info->clock_rate = cinfo.clock_rate;
-    info->channel_count = cinfo.channel_count;
-    info->samples_per_frame = cinfo.samples_per_frame;
-    info->bits_per_sample = cinfo.bits_per_sample;
-
-    /* Build array of listeners */
-    count = pjsua.config.conf_ports;
-    for (i=0; i<count; ++i) {
-	if (cinfo.listener[i]) {
-	    info->listeners[info->listener_cnt++] = i;
+/*
+ * Destroy pjsua.
+ */
+PJ_DEF(pj_status_t) pjsua_destroy(void)
+{
+    int i;  /* Must be signed */
+
+    /* Signal threads to quit: */
+    pjsua_var.thread_quit_flag = 1;
+
+    /* Wait worker threads to quit: */
+    for (i=0; i<(int)pjsua_var.ua_cfg.thread_cnt; ++i) {
+	if (pjsua_var.thread[i]) {
+	    pj_thread_join(pjsua_var.thread[i]);
+	    pj_thread_destroy(pjsua_var.thread[i]);
+	    pjsua_var.thread[i] = NULL;
 	}
     }
-
-    return PJ_SUCCESS;
-}
-
-
-/**
- * Connect conference port.
- */
-PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id src_port,
-					pjsua_conf_port_id dst_port)
-{
-    return pjmedia_conf_connect_port(pjsua.mconf, src_port, dst_port, 0);
-}
-
-
-/**
- * Connect conference port connection.
- */
-PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id src_port,
-					   pjsua_conf_port_id dst_port)
-{
-    return pjmedia_conf_disconnect_port(pjsua.mconf, src_port, dst_port);
-}
-
-
-
-/**
- * Create a file player.
- */
-PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
-					 pjsua_player_id *id)
-{
-    unsigned slot;
-    char path[128];
-    pjmedia_port *port;
-    pj_status_t status;
-
-    if (pjsua.player_cnt >= PJ_ARRAY_SIZE(pjsua.player))
-	return PJ_ETOOMANY;
-
-    pj_memcpy(path, filename->ptr, filename->slen);
-    path[filename->slen] = '\0';
-    status = pjmedia_wav_player_port_create(pjsua.pool, path,
-					    pjsua.samples_per_frame *
-					       1000 / pjsua.clock_rate,
-					    0, 0, NULL,
-					    &port);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, 
-				   port, filename, &slot);
-    if (status != PJ_SUCCESS) {
-	pjmedia_port_destroy(port);
-	return status;
-    }
-
-    pjsua.player[pjsua.player_cnt].port = port;
-    pjsua.player[pjsua.player_cnt].slot = slot;
-
-    if (id)
-	*id = pjsua.player_cnt;
-
-    ++pjsua.player_cnt;
-
-    return PJ_SUCCESS;
-}
-
-
-/**
- * Get conference port associated with player.
- */
-PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
-{
-    PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL);
-    return pjsua.player[id].slot;
-}
-
-
-/**
- * Re-wind playback.
- */
-PJ_DEF(pj_status_t) pjsua_player_set_pos(pjsua_player_id id,
-					 pj_uint32_t samples)
-{
-    PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua.player[id].port != NULL, PJ_EINVALIDOP);
-
-    return pjmedia_wav_player_port_set_pos(pjsua.player[id].port, samples);
-}
-
-
-/**
- * Get conference port associated with player.
- */
-PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
-{
-    PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL);
-
-    if (pjsua.player[id].port) {
-	pjmedia_port_destroy(pjsua.player[id].port);
-	pjsua.player[id].port = NULL;
-	pjsua.player[id].slot = 0xFFFF;
-	pjsua.player_cnt--;
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-/**
- * Create a file recorder.
- */
-PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
-					   pjsua_recorder_id *id)
-{
-    unsigned slot;
-    char path[128];
-    pjmedia_port *port;
-    pj_status_t status;
-
-    if (pjsua.recorder_cnt >= PJ_ARRAY_SIZE(pjsua.recorder))
-	return PJ_ETOOMANY;
-
-    pj_memcpy(path, filename->ptr, filename->slen);
-    path[filename->slen] = '\0';
-    status = pjmedia_wav_writer_port_create(pjsua.pool, path,
-					    pjsua.clock_rate, 1,
-					    pjsua.samples_per_frame,
-					    16, 0, 0, NULL,
-					    &port);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, 
-				   port, filename, &slot);
-    if (status != PJ_SUCCESS) {
-	pjmedia_port_destroy(port);
-	return status;
-    }
-
-    pjsua.recorder[pjsua.recorder_cnt].port = port;
-    pjsua.recorder[pjsua.recorder_cnt].slot = slot;
-
-    if (*id)
-	*id = pjsua.recorder_cnt;
-
-    ++pjsua.recorder_cnt;
-
-    return PJ_SUCCESS;
-}
-
-
-/**
- * Get conference port associated with recorder.
- */
-PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
-{
-    PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.recorder), PJ_EINVAL);
-    return pjsua.recorder[id].slot;
-}
-
-
-/**
- * Destroy recorder (will complete recording).
- */
-PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
-{
-    PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.recorder), PJ_EINVAL);
-
-    if (pjsua.recorder[id].port) {
-	pjmedia_port_destroy(pjsua.recorder[id].port);
-	pjsua.recorder[id].port = NULL;
-	pjsua.recorder[id].slot = 0xFFFF;
-	pjsua.recorder_cnt--;
-    }
-
-    return PJ_SUCCESS;
-}
-
-/**
- * Enum sound devices.
- */
-PJ_DEF(pj_status_t) pjsua_enum_snd_devices( unsigned *count,
-					    pjmedia_snd_dev_info info[])
-{
-    int i, dev_count;
-
-    dev_count = pjmedia_snd_get_dev_count();
-    if (dev_count > (int)*count)
-	dev_count = *count;
-
-    for (i=0; i<dev_count; ++i) {
-	const pjmedia_snd_dev_info *dev_info;
-	dev_info = pjmedia_snd_get_dev_info(i);
-	pj_memcpy(&info[i], dev_info, sizeof(pjmedia_snd_dev_info));
-    }
-
-    *count = dev_count;
-    return PJ_SUCCESS;
-}
-
-
-/**
- * Select or change sound device.
- */
-PJ_DEF(pj_status_t) pjsua_set_snd_dev( int snd_capture_id,
-				       int snd_player_id)
-{
-    pjsua.config.snd_capture_id = snd_capture_id;
-    pjsua.config.snd_player_id = snd_player_id;
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Destroy pjsua.
- */
-PJ_DEF(pj_status_t) pjsua_destroy(void)
-{
-    int i;  /* Must be signed */
-
-    /* Signal threads to quit: */
-    pjsua.quit_flag = 1;
-
-    /* Wait worker threads to quit: */
-    for (i=0; i<(int)pjsua.config.thread_cnt; ++i) {
-	
-	if (pjsua.threads[i]) {
-	    pj_thread_join(pjsua.threads[i]);
-	    pj_thread_destroy(pjsua.threads[i]);
-	    pjsua.threads[i] = NULL;
-	}
-    }
-
     
-    if (pjsua.endpt) {
+    if (pjsua_var.endpt) {
 	/* Terminate all calls. */
 	pjsua_call_hangup_all();
@@ -1746,6 +499,9 @@
 
 	/* Unregister, if required: */
-	for (i=0; i<(int)pjsua.config.acc_cnt; ++i) {
-	    if (pjsua.acc[i].regc) {
+	for (i=0; i<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+	    if (!pjsua_var.acc[i].valid)
+		continue;
+
+	    if (pjsua_var.acc[i].regc) {
 		pjsua_acc_set_registration(i, PJ_FALSE);
 	    }
@@ -1757,88 +513,288 @@
     }
 
-    /* If we have master port, destroying master port will recursively
-     * destroy conference bridge, otherwise must destroy it manually. 
+    /* Destroy media */
+    pjsua_media_subsys_destroy();
+
+    /* Destroy endpoint. */
+    if (pjsua_var.endpt) {
+	pjsip_endpt_destroy(pjsua_var.endpt);
+	pjsua_var.endpt = NULL;
+    }
+
+    /* Destroy mutex */
+    if (pjsua_var.mutex) {
+	pj_mutex_destroy(pjsua_var.mutex);
+	pjsua_var.mutex = NULL;
+    }
+
+    /* Destroy pool and pool factory. */
+    if (pjsua_var.pool) {
+	pj_pool_release(pjsua_var.pool);
+	pjsua_var.pool = NULL;
+	pj_caching_pool_destroy(&pjsua_var.cp);
+    }
+
+
+    PJ_LOG(4,(THIS_FILE, "PJSUA destroyed..."));
+
+    /* End logging */
+    if (pjsua_var.log_file) {
+	pj_file_close(pjsua_var.log_file);
+	pjsua_var.log_file = NULL;
+    }
+
+    /* Done. */
+    return PJ_SUCCESS;
+}
+
+
+/**
+ * Application is recommended to call this function after all initialization
+ * is done, so that the library can do additional checking set up
+ * additional 
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DEF(pj_status_t) pjsua_start(void)
+{
+    pj_status_t status;
+
+    status = pjsua_call_subsys_start();
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pjsua_media_subsys_start();
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = pjsua_pres_start();
+    if (status != PJ_SUCCESS)
+	return status;
+
+    return PJ_SUCCESS;
+}
+
+
+/**
+ * Poll pjsua for events, and if necessary block the caller thread for
+ * the specified maximum interval (in miliseconds).
+ */
+PJ_DEF(int) pjsua_handle_events(unsigned msec_timeout)
+{
+    unsigned count = 0;
+    pj_time_val tv;
+    pj_status_t status;
+
+    tv.sec = 0;
+    tv.msec = msec_timeout;
+    pj_time_val_normalize(&tv);
+
+    status = pjsip_endpt_handle_events2(pjsua_var.endpt, &tv, &count);
+
+    if (status != PJ_SUCCESS)
+	return -status;
+
+    return count;
+}
+
+
+/*
+ * Create memory pool.
+ */
+PJ_DEF(pj_pool_t*) pjsua_pool_create( const char *name, pj_size_t init_size,
+				      pj_size_t increment)
+{
+    /* Pool factory is thread safe, no need to lock */
+    return pj_pool_create(&pjsua_var.cp.factory, name, init_size, increment, 
+			  NULL);
+}
+
+
+/*
+ * Internal function to get SIP endpoint instance of pjsua, which is
+ * needed for example to register module, create transports, etc.
+ * Probably is only valid after #pjsua_init() is called.
+ */
+PJ_DEF(pjsip_endpoint*) pjsua_get_pjsip_endpt(void)
+{
+    return pjsua_var.endpt;
+}
+
+/*
+ * Internal function to get media endpoint instance.
+ * Only valid after #pjsua_init() is called.
+ */
+PJ_DEF(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void)
+{
+    return pjsua_var.med_endpt;
+}
+
+
+/*****************************************************************************
+ * PJSUA SIP Transport API.
+ */
+
+/*
+ * Create and initialize SIP socket (and possibly resolve public
+ * address via STUN, depending on config).
+ */
+static pj_status_t create_sip_udp_sock(pj_in_addr bound_addr,
+				       int port,
+				       pj_bool_t use_stun,
+				       const pjsua_stun_config *stun_param,
+				       pj_sock_t *p_sock,
+				       pj_sockaddr_in *p_pub_addr)
+{
+    pjsua_stun_config stun;
+    pj_sock_t sock;
+    pj_status_t status;
+
+    PJSUA_LOCK();
+
+    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "socket() error", status);
+	goto on_return;
+    }
+
+    status = pj_sock_bind_in(sock, bound_addr.s_addr, (pj_uint16_t)port);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "bind() error", status);
+	pj_sock_close(sock);
+	goto on_return;
+    }
+
+    /* Copy and normalize STUN param */
+    if (use_stun) {
+	pj_memcpy(&stun, stun_param, sizeof(*stun_param));
+	pjsua_normalize_stun_config(&stun);
+    } else {
+	pj_memset(&stun, 0, sizeof(pjsua_stun_config));
+    }
+
+    /* Get the published address, either by STUN or by resolving
+     * the name of local host.
      */
-    if (pjsua.master_port) {
-	pjmedia_master_port_destroy(pjsua.master_port);
-	pjsua.master_port = NULL;
+    if (stun.stun_srv1.slen) {
+	status = pj_stun_get_mapped_addr(&pjsua_var.cp.factory, 1, &sock,
+				         &stun.stun_srv1, 
+					  stun.stun_port1,
+					 &stun.stun_srv2, 
+					  stun.stun_port2,
+				          p_pub_addr);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Error resolving with STUN", status);
+	    pj_sock_close(sock);
+	    goto on_return;
+	}
+
     } else {
-	if (pjsua.snd_port) {
-	    pjmedia_snd_port_destroy(pjsua.snd_port);
-	    pjsua.snd_port = NULL;
+
+	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);
+	    goto on_return;
 	}
-	if (pjsua.mconf) {
-	    pjmedia_conf_destroy(pjsua.mconf);
-	    pjsua.mconf = NULL;
+
+	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;
+
+on_return:
+
+    PJSUA_UNLOCK();
+
+    PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d",
+	      pj_inet_ntoa(p_pub_addr->sin_addr),
+	      (int)pj_ntohs(p_pub_addr->sin_port)));
+
+    return status;
+}
+
+
+/*
+ * Create SIP transport.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type,
+					    const pjsua_transport_config *cfg,
+					    pjsua_transport_id *p_id)
+{
+    pjsip_transport *tp;
+    unsigned id;
+    pj_status_t status;
+
+    PJSUA_LOCK();
+
+    /* Find empty transport slot */
+    for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) {
+	if (pjsua_var.tpdata[id].tp == NULL)
+	    break;
+    }
+
+    if (id == PJ_ARRAY_SIZE(pjsua_var.tpdata)) {
+	status = PJ_ETOOMANY;
+	pjsua_perror(THIS_FILE, "Error creating transport", status);
+	goto on_return;
+    }
+
+    /* Create the transport */
+    if (type == PJSIP_TRANSPORT_UDP) {
+
+	pjsua_transport_config config;
+	pj_sock_t sock;
+	pj_sockaddr_in pub_addr;
+	pjsip_host_port addr_name;
+
+	/* Supply default config if it's not specified */
+	if (cfg == NULL) {
+	    pjsua_transport_config_default(&config);
+	    cfg = &config;
 	}
-    }
-
-    /* Destroy file players */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.player); ++i) {
-	if (pjsua.player[i].port) {
-	    pjmedia_port_destroy(pjsua.player[i].port);
-	    pjsua.player[i].port = NULL;
+
+	/* Create the socket and possibly resolve the address with STUN */
+	status = create_sip_udp_sock(cfg->ip_addr, cfg->port, cfg->use_stun,
+				     &cfg->stun_config, &sock, &pub_addr);
+	if (status != PJ_SUCCESS)
+	    goto on_return;
+
+	addr_name.host = pj_str(pj_inet_ntoa(pub_addr.sin_addr));
+	addr_name.port = pj_ntohs(pub_addr.sin_port);
+
+	/* Create UDP transport */
+	status = pjsip_udp_transport_attach( pjsua_var.endpt, sock,
+					     &addr_name, 1, 
+					     &tp);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Error creating SIP UDP transport", 
+			 status);
+	    pj_sock_close(sock);
+	    goto on_return;
 	}
-    }
-
-
-    /* Destroy file recorders */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.recorder); ++i) {
-	if (pjsua.recorder[i].port) {
-	    pjmedia_port_destroy(pjsua.recorder[i].port);
-	    pjsua.recorder[i].port = NULL;
-	}
-    }
-
-
-    /* Close transports */
-    for (i=0; i<(int)pjsua.config.max_calls; ++i) {
-	if (pjsua.calls[i].med_tp) {
-	    (*pjsua.calls[i].med_tp->op->destroy)(pjsua.calls[i].med_tp);
-	    pjsua.calls[i].med_tp = NULL;
-	}
-    }
-
-    /* Destroy media endpoint. */
-    if (pjsua.med_endpt) {
-
-	/* Shutdown all codecs: */
-#	if PJMEDIA_HAS_SPEEX_CODEC
-	    pjmedia_codec_speex_deinit();
-#	endif /* PJMEDIA_HAS_SPEEX_CODEC */
-
-#	if PJMEDIA_HAS_GSM_CODEC
-	    pjmedia_codec_gsm_deinit();
-#	endif /* PJMEDIA_HAS_GSM_CODEC */
-
-#	if PJMEDIA_HAS_G711_CODEC
-	    pjmedia_codec_g711_deinit();
-#	endif	/* PJMEDIA_HAS_G711_CODEC */
-
-#	if PJMEDIA_HAS_L16_CODEC
-	    pjmedia_codec_l16_deinit();
-#	endif	/* PJMEDIA_HAS_L16_CODEC */
-
-
-	pjmedia_endpt_destroy(pjsua.med_endpt);
-	pjsua.med_endpt = NULL;
-    }
-
-    /* Destroy endpoint. */
-    if (pjsua.endpt) {
-	pjsip_endpt_destroy(pjsua.endpt);
-	pjsua.endpt = NULL;
-    }
-
-    /* Destroy caching pool. */
-    pj_caching_pool_destroy(&pjsua.cp);
-
-
-    PJ_LOG(4,(THIS_FILE, "PJSUA destroyed..."));
-
-    /* End logging */
-    logging_shutdown();
-
-    /* Done. */
+
+    } else {
+	status = PJSIP_EUNSUPTRANSPORT;
+	pjsua_perror(THIS_FILE, "Error creating transport", status);
+	goto on_return;
+    }
+
+    /* Save the transport */
+    pjsua_var.tpdata[id].tp = tp;
+
+    /* Return the ID */
+    if (p_id) *p_id = id;
+
+    status = PJ_SUCCESS;
+
+on_return:
+
+    PJSUA_UNLOCK();
 
     return PJ_SUCCESS;
@@ -1846,20 +802,253 @@
 
 
-/**
- * Get SIP endpoint instance.
- * Only valid after pjsua_init().
- */
-PJ_DEF(pjsip_endpoint*) pjsua_get_pjsip_endpt(void)
-{
-    return pjsua.endpt;
-}
-
-/**
- * Get media endpoint instance.
- * Only valid after pjsua_init().
- */
-PJ_DEF(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void)
-{
-    return pjsua.med_endpt;
-}
-
+/*
+ * Register transport that has been created by application.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_register( pjsip_transport *tp,
+					      pjsua_transport_id *p_id)
+{
+    unsigned id;
+
+    PJSUA_LOCK();
+
+    /* Find empty transport slot */
+    for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) {
+	if (pjsua_var.tpdata[id].tp == NULL)
+	    break;
+    }
+
+    if (id == PJ_ARRAY_SIZE(pjsua_var.tpdata)) {
+	pjsua_perror(THIS_FILE, "Error creating transport", PJ_ETOOMANY);
+	PJSUA_UNLOCK();
+	return PJ_ETOOMANY;
+    }
+
+    /* Save the transport */
+    pjsua_var.tpdata[id].tp = tp;
+
+    /* Return the ID */
+    if (p_id) *p_id = id;
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Enumerate all transports currently created in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_transports( pjsua_transport_id id[],
+					   unsigned *p_count )
+{
+    unsigned i, count;
+
+    PJSUA_LOCK();
+
+    for (i=0, count=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata) && count<*p_count; 
+	 ++i) 
+    {
+	if (!pjsua_var.tpdata[i].tp)
+	    continue;
+
+	id[count++] = i;
+    }
+
+    *p_count = count;
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get information about transports.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_get_info( pjsua_transport_id id,
+					      pjsua_transport_info *info)
+{
+    pjsip_transport *tp;
+
+    pj_memset(info, 0, sizeof(*info));
+
+    /* Make sure id is in range. */
+    PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.tpdata), PJ_EINVAL);
+
+    /* Make sure that transport exists */
+    PJ_ASSERT_RETURN(pjsua_var.tpdata[id].tp != NULL, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    tp = pjsua_var.tpdata[id].tp;
+    if (tp == NULL) {
+	PJSUA_UNLOCK();
+	return PJ_EINVALIDOP;
+    }
+    
+    info->id = id;
+    info->type = tp->key.type;
+    info->type_name = pj_str(tp->type_name);
+    info->info = pj_str(tp->info);
+    info->flag = tp->flag;
+    info->addr_len = tp->addr_len;
+    info->local_addr = tp->local_addr;
+    info->local_name = tp->local_name;
+    info->usage_count = pj_atomic_get(tp->ref_cnt);
+
+    PJSUA_UNLOCK();
+
+    return PJ_EINVALIDOP;
+}
+
+
+/*
+ * Disable a transport or re-enable it.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_set_enable( pjsua_transport_id id,
+						pj_bool_t enabled)
+{
+    /* Make sure id is in range. */
+    PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.tpdata), PJ_EINVAL);
+
+    /* Make sure that transport exists */
+    PJ_ASSERT_RETURN(pjsua_var.tpdata[id].tp != NULL, PJ_EINVAL);
+
+
+    /* To be done!! */
+    PJ_TODO(pjsua_transport_set_enable);
+
+    return PJ_EINVALIDOP;
+}
+
+
+/*
+ * Close the transport.
+ */
+PJ_DEF(pj_status_t) pjsua_transport_close( pjsua_transport_id id,
+					   pj_bool_t force )
+{
+    /* Make sure id is in range. */
+    PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.tpdata), PJ_EINVAL);
+
+    /* Make sure that transport exists */
+    PJ_ASSERT_RETURN(pjsua_var.tpdata[id].tp != NULL, PJ_EINVAL);
+
+
+    /* To be done!! */
+
+
+    PJ_TODO(pjsua_transport_close);
+
+    return PJ_EINVALIDOP;
+}
+
+
+/*
+ * Add additional headers etc in msg_data specified by application
+ * when sending requests.
+ */
+void pjsua_process_msg_data(pjsip_tx_data *tdata,
+			    const pjsua_msg_data *msg_data)
+{
+    pj_bool_t allow_body;
+    const pjsip_hdr *hdr;
+
+    if (!msg_data)
+	return;
+
+    hdr = msg_data->hdr_list.next;
+    while (hdr && hdr != &msg_data->hdr_list) {
+	pjsip_hdr *new_hdr;
+
+	new_hdr = pjsip_hdr_clone(tdata->pool, hdr);
+	pjsip_msg_add_hdr(tdata->msg, new_hdr);
+
+	hdr = hdr->next;
+    }
+
+    allow_body = (tdata->msg->body == NULL);
+
+    if (allow_body && msg_data->content_type.slen && msg_data->msg_body.slen) {
+	pjsip_media_type ctype;
+	pjsip_msg_body *body;	
+
+	pjsua_parse_media_type(tdata->pool, &msg_data->content_type, &ctype);
+	body = pjsip_msg_body_create(tdata->pool, &ctype.type, &ctype.subtype,
+				     &msg_data->msg_body);
+	tdata->msg->body = body;
+    }
+}
+
+
+/*
+ * Add route_set to outgoing requests
+ */
+void pjsua_set_msg_route_set( pjsip_tx_data *tdata,
+			      const pjsip_route_hdr *route_set )
+{
+    const pjsip_route_hdr *r;
+
+    r = route_set->next;
+    while (r != route_set) {
+	pjsip_route_hdr *new_r;
+
+	new_r = pjsip_hdr_clone(tdata->pool, r);
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)new_r);
+
+	r = r->next;
+    }
+}
+
+
+/*
+ * Simple version of MIME type parsing (it doesn't support parameters)
+ */
+void pjsua_parse_media_type( pj_pool_t *pool,
+			     const pj_str_t *mime,
+			     pjsip_media_type *media_type)
+{
+    pj_str_t tmp;
+    char *pos;
+
+    pj_memset(media_type, 0, sizeof(*media_type));
+
+    pj_strdup_with_null(pool, &tmp, mime);
+
+    pos = pj_strchr(&tmp, '/');
+    if (pos) {
+	media_type->type.ptr = tmp.ptr; 
+	media_type->type.slen = (pos-tmp.ptr);
+	media_type->subtype.ptr = pos+1; 
+	media_type->subtype.slen = tmp.ptr+tmp.slen-pos-1;
+    } else {
+	media_type->type = tmp;
+    }
+}
+
+
+/*
+ * Verify that valid SIP url is given.
+ */
+PJ_DEF(pj_status_t) pjsua_verify_sip_url(const char *c_url)
+{
+    pjsip_uri *p;
+    pj_pool_t *pool;
+    char *url;
+    int len = (c_url ? pj_ansi_strlen(c_url) : 0);
+
+    if (!len) return -1;
+
+    pool = pj_pool_create(&pjsua_var.cp.factory, "check%p", 1024, 0, NULL);
+    if (!pool) return -1;
+
+    url = pj_pool_alloc(pool, len+1);
+    pj_ansi_strcpy(url, c_url);
+
+    p = pjsip_parse_uri(pool, url, len, 0);
+    if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
+	p = NULL;
+
+    pj_pool_release(pool);
+    return p ? 0 : -1;
+}
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_im.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_im.c	(revision 502)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_im.c	(revision 503)
@@ -18,15 +18,8 @@
  */
 #include <pjsua-lib/pjsua.h>
-#include <pj/log.h>
-#include "pjsua_imp.h"
-
-/*
- * pjsua_im.c
- *
- * To handle incoming MESSAGE outside dialog.
- * Incoming MESSAGE inside dialog is hanlded in pjsua_call.c.
- */
-
-#define THIS_FILE   "pjsua_im.c"
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE   "pjsua_im.h"
 
 
@@ -50,4 +43,5 @@
 /* Proto */
 static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata);
+
 
 /* The module instance. */
@@ -137,28 +131,46 @@
  *	    This may trigger pjsua_ui_on_pager() or pjsua_ui_on_typing().
  */
-void pjsua_im_process_pager(int call_index, const pj_str_t *from,
+void pjsua_im_process_pager(int call_id, const pj_str_t *from,
 			    const pj_str_t *to, pjsip_rx_data *rdata)
 {
+    pjsip_contact_hdr *contact_hdr;
+    pj_str_t contact;
     pjsip_msg_body *body = rdata->msg_info.msg->body;
 
     /* Body MUST have been checked before */
     pj_assert(body != NULL);
+
+
+    /* Build remote contact */
+    contact_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
+				     NULL);
+    if (contact_hdr) {
+	contact.ptr = pj_pool_alloc(rdata->tp_info.pool, 
+				    PJSIP_MAX_URL_SIZE);
+	contact.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR,
+				       contact_hdr->uri, contact.ptr,
+				       PJSIP_MAX_URL_SIZE);
+    } else {
+	contact.slen = 0;
+    }
+
 
     if (pj_stricmp(&body->content_type.type, &STR_MIME_TEXT)==0 &&
 	pj_stricmp(&body->content_type.subtype, &STR_MIME_PLAIN)==0)
     {
-	pj_str_t text;
-
-	/* Build the text. */
-	text.ptr = rdata->msg_info.msg->body->data;
-	text.slen = rdata->msg_info.msg->body->len;
-
-	if (pjsua.cb.on_pager)
-	    (*pjsua.cb.on_pager)(call_index, from, to, &text);
+	const pj_str_t mime_text_plain = pj_str("text/plain");
+	pj_str_t text_body;
+	
+	/* Save text body */
+	text_body.ptr = rdata->msg_info.msg->body->data;
+	text_body.slen = rdata->msg_info.msg->body->len;
+
+	if (pjsua_var.ua_cfg.cb.on_pager) {
+	    (*pjsua_var.ua_cfg.cb.on_pager)(call_id, from, to, &contact, 
+					    &mime_text_plain, &text_body);
+	}
 
     } else {
-
 	/* Expecting typing indication */
-
 	pj_status_t status;
 	pj_bool_t is_typing;
@@ -172,6 +184,8 @@
 	}
 
-	if (pjsua.cb.on_typing)
-	    (*pjsua.cb.on_typing)(call_index, from, to, is_typing);
+	if (pjsua_var.ua_cfg.cb.on_typing) {
+	    (*pjsua_var.ua_cfg.cb.on_typing)(call_id, from, to, &contact,
+					     is_typing);
+	}
     }
 
@@ -211,5 +225,5 @@
 	pj_list_push_back(&hdr_list, accept_hdr);
 
-	pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 
+	pjsip_endpt_respond_stateless(pjsua_var.endpt, rdata, 
 				      PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, 
 				      &hdr_list, NULL);
@@ -220,5 +234,5 @@
      * the UI takes too long to process the message. 
      */
-    status = pjsip_endpt_respond( pjsua.endpt, NULL, rdata, 200, NULL,
+    status = pjsip_endpt_respond( pjsua_var.endpt, NULL, rdata, 200, NULL,
 				  NULL, NULL, NULL);
 
@@ -262,43 +276,156 @@
 static void im_callback(void *token, pjsip_event *e)
 {
-    pj_str_t *text = token;
+    pjsua_im_data *im_data = token;
 
     if (e->type == PJSIP_EVENT_TSX_STATE) {
 
 	pjsip_transaction *tsx = e->body.tsx_state.tsx;
+
+	/* Ignore provisional response, if any */
+	if (tsx->status_code < 200)
+	    return;
+
+
+	/* Handle authentication challenges */
+	if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
+	    (tsx->status_code == 401 || tsx->status_code == 407)) 
+	{
+	    pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+	    pjsip_tx_data *tdata;
+	    pjsip_auth_clt_sess auth;
+	    pj_status_t status;
+
+	    PJ_LOG(4,(THIS_FILE, "Resending IM with authentication"));
+
+	    /* Create temporary authentication session */
+	    pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0);
+    
+	    pjsip_auth_clt_set_credentials(&auth, 
+		pjsua_var.acc[im_data->acc_id].cred_cnt,
+		pjsua_var.acc[im_data->acc_id].cred);
+
+	    status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx,
+					       &tdata);
+	    if (status == PJ_SUCCESS) {
+		pjsua_im_data *im_data2;
+
+		/* Must duplicate im_data */
+		im_data2 = pjsua_im_data_dup(tdata->pool, im_data);
+
+		/* Re-send request */
+		status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
+						   im_data2, &im_callback);
+		if (status == PJ_SUCCESS) {
+		    /* Done */
+		    return;
+		}
+	    }
+	}
 
 	if (tsx->status_code/100 == 2) {
 	    PJ_LOG(4,(THIS_FILE, 
 		      "Message \'%s\' delivered successfully",
-		      text->ptr));
+		      im_data->body.ptr));
 	} else {
 	    PJ_LOG(3,(THIS_FILE, 
-		      "Failed to deliver message \'%s\': %s [st_code=%d]",
-		      text->ptr,
-		      pjsip_get_status_text(tsx->status_code)->ptr,
-		      tsx->status_code));
+		      "Failed to deliver message \'%s\': %d/%.*s",
+		      im_data->body.ptr,
+		      tsx->status_code,
+		      (int)tsx->status_text.slen,
+		      tsx->status_text.ptr));
 	}
-    }
-}
-
-
-/**
- * Send IM outside dialog.
- */
-PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const pj_str_t *dst_uri, 
-				  const pj_str_t *str)
+
+	if (pjsua_var.ua_cfg.cb.on_pager_status)
+	    pjsua_var.ua_cfg.cb.on_pager_status(im_data->call_id, 
+					        &im_data->to,
+						&im_data->body,
+						im_data->user_data,
+						tsx->status_code,
+						&tsx->status_text);
+    }
+}
+
+
+/* Outgoing typing indication callback. 
+ * (used to reauthenticate request)
+ */
+static void typing_callback(void *token, pjsip_event *e)
+{
+    pjsua_im_data *im_data = token;
+
+    if (e->type == PJSIP_EVENT_TSX_STATE) {
+
+	pjsip_transaction *tsx = e->body.tsx_state.tsx;
+
+	/* Ignore provisional response, if any */
+	if (tsx->status_code < 200)
+	    return;
+
+	/* Handle authentication challenges */
+	if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG &&
+	    (tsx->status_code == 401 || tsx->status_code == 407)) 
+	{
+	    pjsip_rx_data *rdata = e->body.tsx_state.src.rdata;
+	    pjsip_tx_data *tdata;
+	    pjsip_auth_clt_sess auth;
+	    pj_status_t status;
+
+	    PJ_LOG(4,(THIS_FILE, "Resending IM with authentication"));
+
+	    /* Create temporary authentication session */
+	    pjsip_auth_clt_init(&auth,pjsua_var.endpt,rdata->tp_info.pool, 0);
+    
+	    pjsip_auth_clt_set_credentials(&auth, 
+		pjsua_var.acc[im_data->acc_id].cred_cnt,
+		pjsua_var.acc[im_data->acc_id].cred);
+
+	    status = pjsip_auth_clt_reinit_req(&auth, rdata, tsx->last_tx,
+					       &tdata);
+	    if (status == PJ_SUCCESS) {
+		pjsua_im_data *im_data2;
+
+		/* Must duplicate im_data */
+		im_data2 = pjsua_im_data_dup(tdata->pool, im_data);
+
+		/* Re-send request */
+		status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1,
+						   im_data2, &typing_callback);
+		if (status == PJ_SUCCESS) {
+		    /* Done */
+		    return;
+		}
+	    }
+	}
+
+    }
+}
+
+
+/*
+ * Send instant messaging outside dialog, using the specified account for
+ * route set and authentication.
+ */
+PJ_DEF(pj_status_t) pjsua_im_send( pjsua_acc_id acc_id, 
+				   const pj_str_t *to,
+				   const pj_str_t *mime_type,
+				   const pj_str_t *content,
+				   const pjsua_msg_data *msg_data,
+				   void *user_data)
 {
     pjsip_tx_data *tdata;
+    const pj_str_t mime_text_plain = pj_str("text/plain");
     const pj_str_t STR_CONTACT = { "Contact", 7 };
-    const pj_str_t mime_text = pj_str("text");
-    const pj_str_t mime_plain = pj_str("plain");
-    pj_str_t *text;
+    pjsip_media_type media_type;
+    pjsua_im_data *im_data;
     pj_status_t status;
 
+    /* To and message body must be specified. */
+    PJ_ASSERT_RETURN(to && content, PJ_EINVAL);
+
     /* Create request. */
-    status = pjsip_endpt_create_request(pjsua.endpt, &pjsip_message_method,
-					dst_uri, 
-					&pjsua.config.acc_config[acc_index].id,
-					dst_uri, NULL, NULL, -1, NULL, &tdata);
+    status = pjsip_endpt_create_request(pjsua_var.endpt, 
+					&pjsip_message_method, to, 
+					&pjsua_var.acc[acc_id].cfg.id,
+					to, NULL, NULL, -1, NULL, &tdata);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create request", status);
@@ -314,16 +441,29 @@
 	pjsip_generic_string_hdr_create(tdata->pool, 
 					&STR_CONTACT,
-					&pjsua.config.acc_config[acc_index].contact));
-
-    /* Duplicate text.
-     * We need to keep the text because we will display it when we fail to
-     * send the message.
+					&pjsua_var.acc[acc_id].cfg.contact));
+
+    /* Create IM data to keep message details and give it back to
+     * application on the callback
      */
-    text = pj_pool_alloc(tdata->pool, sizeof(pj_str_t));
-    pj_strdup_with_null(tdata->pool, text, str);
+    im_data = pj_pool_zalloc(tdata->pool, sizeof(*im_data));
+    im_data->acc_id = acc_id;
+    im_data->call_id = PJSUA_INVALID_ID;
+    pj_strdup_with_null(tdata->pool, &im_data->to, to);
+    pj_strdup_with_null(tdata->pool, &im_data->body, content);
+    im_data->user_data = user_data;
+
+
+    /* Set default media type if none is specified */
+    if (mime_type == NULL) {
+	mime_type = &mime_text_plain;
+    }
+
+    /* Parse MIME type */
+    pjsua_parse_media_type(tdata->pool, mime_type, &media_type);
 
     /* Add message body */
-    tdata->msg->body = pjsip_msg_body_create( tdata->pool, &mime_text,
-					      &mime_plain, text);
+    tdata->msg->body = pjsip_msg_body_create( tdata->pool, &media_type.type,
+					      &media_type.subtype, 
+					      &im_data->body);
     if (tdata->msg->body == NULL) {
 	pjsua_perror(THIS_FILE, "Unable to create msg body", PJ_ENOMEM);
@@ -332,7 +472,13 @@
     }
 
+    /* Add additional headers etc. */
+    pjsua_process_msg_data(tdata, msg_data);
+
+    /* Add route set */
+    pjsua_set_msg_route_set(tdata, &pjsua_var.acc[acc_id].route_set);
+
     /* Send request (statefully) */
-    status = pjsip_endpt_send_request( pjsua.endpt, tdata, -1, 
-				       text, &im_callback);
+    status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, 
+				       im_data, &im_callback);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to send request", status);
@@ -344,19 +490,21 @@
 
 
-/**
+/*
  * Send typing indication outside dialog.
  */
-PJ_DEF(pj_status_t) pjsua_im_typing(int acc_index, const pj_str_t *dst_uri, 
-				    pj_bool_t is_typing)
+PJ_DEF(pj_status_t) pjsua_im_typing( pjsua_acc_id acc_id, 
+				     const pj_str_t *to, 
+				     pj_bool_t is_typing,
+				     const pjsua_msg_data *msg_data)
 {
     const pj_str_t STR_CONTACT = { "Contact", 7 };
+    pjsua_im_data *im_data;
     pjsip_tx_data *tdata;
     pj_status_t status;
 
     /* Create request. */
-    status = pjsip_endpt_create_request( pjsua.endpt, &pjsip_message_method,
-					 dst_uri, 
-					 &pjsua.config.acc_config[acc_index].id,
-					 dst_uri, NULL, NULL, -1, NULL, &tdata);
+    status = pjsip_endpt_create_request( pjsua_var.endpt, &pjsip_message_method,
+					 to, &pjsua_var.acc[acc_id].cfg.id,
+					 to, NULL, NULL, -1, NULL, &tdata);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create request", status);
@@ -374,5 +522,5 @@
 	pjsip_generic_string_hdr_create(tdata->pool, 
 					&STR_CONTACT,
-					&pjsua.config.acc_config[acc_index].contact));
+					&pjsua_var.acc[acc_id].cfg.contact));
 
 
@@ -381,7 +529,14 @@
 						      NULL, NULL, -1);
 
+    /* Add additional headers etc. */
+    pjsua_process_msg_data(tdata, msg_data);
+
+    /* Create data to reauthenticate */
+    im_data = pj_pool_zalloc(tdata->pool, sizeof(*im_data));
+    im_data->acc_id = acc_id;
+
     /* Send request (statefully) */
-    status = pjsip_endpt_send_request( pjsua.endpt, tdata, -1, 
-				       NULL, NULL);
+    status = pjsip_endpt_send_request( pjsua_var.endpt, tdata, -1, 
+				       im_data, &typing_callback);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to send request", status);
@@ -402,10 +557,10 @@
 
     /* Register module */
-    status = pjsip_endpt_register_module(pjsua.endpt, &mod_pjsua_im);
+    status = pjsip_endpt_register_module(pjsua_var.endpt, &mod_pjsua_im);
     if (status != PJ_SUCCESS)
 	return status;
 
     /* Register support for MESSAGE method. */
-    pjsip_endpt_add_capability( pjsua.endpt, &mod_pjsua_im, PJSIP_H_ALLOW,
+    pjsip_endpt_add_capability( pjsua_var.endpt, &mod_pjsua_im, PJSIP_H_ALLOW,
 				NULL, 1, &msg_tag);
 
Index: project/trunk/pjsip/src/pjsua-lib/pjsua_imp.h
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_imp.h	(revision 502)
+++ 	(revision )
@@ -1,259 +1,0 @@
-/* $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__
-
-
-
-
-
-/** 
- * 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_call
-{
-    unsigned		 index;	    /**< Index in pjsua array.		    */
-    pjsip_inv_session	*inv;	    /**< The invite session.		    */
-    pjsip_status_code	 last_code; /**<Last status code seen.		    */
-    pj_time_val		 start_time;/**< First INVITE sent/received.	    */
-    pj_time_val		 res_time;  /**< First response sent/received.	    */
-    pj_time_val		 conn_time; /**< Connected/confirmed time.	    */
-    pj_time_val		 dis_time;  /**< Disconnect time.		    */
-    int			 acc_index; /**< Account index being used.	    */
-    pjmedia_session	*session;   /**< The media session.		    */
-    unsigned		 conf_slot; /**< Slot # in conference bridge.	    */
-    pjsip_evsub		*xfer_sub;  /**< Xfer server subscription, if this
-					 call was triggered by xfer.	    */
-    pjmedia_sock_info	 skinfo;    /**< Preallocated media sockets.	    */
-    pjmedia_transport	*med_tp;    /**< Media transport.		    */
-    void		*app_data;  /**< Application data.		    */
-    pj_timer_entry	 refresh_tm;/**< Timer to send re-INVITE.	    */
-    pj_timer_entry	 hangup_tm; /**< Timer to hangup call.		    */
-};
-
-typedef struct pjsua_call pjsua_call;
-
-
-/**
- * Buddy data.
- */
-struct pjsua_buddy
-{
-    unsigned		 index;	    /**< Buddy index.			*/
-    pj_str_t		 name;	    /**< Buddy name.			*/
-    pj_str_t		 display;   /**< Buddy display name.		*/
-    pj_str_t		 host;	    /**< Buddy host.			*/
-    unsigned		 port;	    /**< Buddy port.			*/
-    pj_bool_t		 monitor;   /**< Should we monitor?		*/
-    pjsip_evsub		*sub;	    /**< Buddy presence subscription	*/
-    pjsip_pres_status	 status;    /**< Buddy presence status.		*/
-};
-
-typedef struct pjsua_buddy pjsua_buddy;
-
-
-/**
- * Server presence subscription list head.
- */
-struct pjsua_srv_pres
-{
-    PJ_DECL_LIST_MEMBER(struct pjsua_srv_pres);
-    pjsip_evsub	    *sub;
-    char	    *remote;
-};
-
-typedef struct pjsua_srv_pres pjsua_srv_pres;
-
-
-
-/**
- * Account
- */
-struct pjsua_acc
-{
-    pj_bool_t	     valid;	    /**< Is this account valid?		*/
-    pj_bool_t	     auto_gen;	    /**< Is this account generated.	*/
-    int		     index;	    /**< Index in accounts array.	*/
-    pj_str_t	     user_part;	    /**< User part of local URI.	*/
-    pj_str_t	     host_part;	    /**< Host part of local URI.	*/
-
-    pjsip_regc	    *regc;	    /**< Client registration session.   */
-    pj_timer_entry   reg_timer;	    /**< Registration timer.		*/
-    pj_status_t	     reg_last_err;  /**< Last registration error.	*/
-    int		     reg_last_code; /**< Last status last register.	*/
-
-    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.		*/
-};
-
-
-/**
- * @see pjsua_acc
- */
-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.		*/
-    pjsip_module     mod;	    /**< pjsua's PJSIP module.		*/
-
-    
-    /* Config: */
-    pjsua_config    config;	    /**< PJSUA configs			*/
-
-    /* Log file: */
-    FILE	   *log_file;	    /**< Log file.			*/
-
-    /* Application callback
-    : */
-    pjsua_callback  cb;		    /**< Application callback.		*/
-
-    /* Media:  */
-    pjmedia_endpt   *med_endpt;	    /**< Media endpoint.		*/
-    unsigned	     clock_rate;    /**< Conference bridge's clock rate.*/
-    unsigned	     samples_per_frame; /**< Bridge's frame size.	*/
-    pjmedia_conf    *mconf;	    /**< Media conference.		*/
-    
-    pjmedia_snd_port *snd_port;	    /**< Sound device port.		*/
-    pjmedia_master_port *master_port; /**< Master port, when no snd dev	*/
-    
-    unsigned	     player_cnt;    /**< Number of file player.		*/
-
-    /** Array of file players */
-    struct {
-	unsigned     slot;	    /**< WAV player slot in bridge	*/
-	pjmedia_port *port;	    /**< WAV player port.		*/
-    } player[32];
-
-    unsigned	     recorder_cnt;  /**< Number of file recorders.	*/
-
-    /** Array of file recorders */
-    struct {
-	unsigned     slot;	    /**< Slot # in conf bridge.		*/
-	pjmedia_port *port;	    /**< The recorder media port.	*/
-    } recorder[32];
-
-    /* Account: */
-    int		     default_acc;   /**< Default account to use.	*/
-    pjsua_acc	     acc[PJSUA_MAX_ACC];    /** Client regs array.	*/
-
-
-    /* Threading (optional): */
-    pj_thread_t	    *threads[8];    /**< Thread instances.		*/
-    pj_bool_t	     quit_flag;	    /**< To signal thread to quit.	*/
-
-    /* Transport (UDP): */
-    pj_sock_t	     sip_sock;	    /**< SIP UDP socket.		*/
-    pj_sockaddr_in   sip_sock_name; /**< Public/STUN UDP socket addr.	*/
-
-
-    /* PJSUA Calls: */
-    unsigned	     call_cnt;	    /**< Number of calls.		*/
-    pjsua_call	     calls[PJSUA_MAX_CALLS];	/** Calls array.	*/
-
-    /* SIMPLE and buddy status: */
-    pjsua_buddy	     buddies[PJSUA_MAX_BUDDIES];
-};
-
-
-/** PJSUA instance. */
-extern struct pjsua pjsua;
-
-
-void pjsua_copy_config( pj_pool_t *pool, pjsua_config *dst, 
-			const pjsua_config *src);
-
-
-/**
- * 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();
-
-
-/**
- * Refresh both presence client and server subscriptions.
- */
-void pjsua_pres_refresh(void);
-
-/**
- * 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);
-
-
-extern pjsip_module pjsua_msg_logger;
-
-#endif	/* __PJSUA_IMP_H__ */
-
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 503)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_media.c	(revision 503)
@@ -0,0 +1,1044 @@
+/* $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_internal.h>
+
+
+#define THIS_FILE		"pjsua_media.c"
+
+#define PTIME		    10
+#define FPS		    (1000/PTIME)
+#define DEFAULT_RTP_PORT    4000
+
+
+/* Close existing sound device */
+static void close_snd_dev(void);
+
+
+/**
+ * Init media subsystems.
+ */
+pj_status_t pjsua_media_subsys_init(const pjsua_media_config *cfg)
+{
+    pj_str_t codec_id;
+    pj_status_t status;
+
+    /* Copy configuration */
+    pj_memcpy(&pjsua_var.media_cfg, cfg, sizeof(*cfg));
+
+    /* Normalize configuration */
+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
+    pjsua_var.media_cfg.clock_rate = 44100;
+#endif
+
+    if (pjsua_var.media_cfg.has_ioqueue &&
+	pjsua_var.media_cfg.thread_cnt == 0)
+    {
+	pjsua_var.media_cfg.thread_cnt = 1;
+    }
+
+    if (pjsua_var.media_cfg.max_media_ports < pjsua_var.ua_cfg.max_calls) {
+	pjsua_var.media_cfg.max_media_ports = pjsua_var.ua_cfg.max_calls + 2;
+    }
+
+    /* Create media endpoint. */
+    status = pjmedia_endpt_create(&pjsua_var.cp.factory, 
+				  pjsua_var.media_cfg.has_ioqueue? NULL :
+				     pjsip_endpt_get_ioqueue(pjsua_var.endpt),
+				  pjsua_var.media_cfg.thread_cnt,
+				  &pjsua_var.med_endpt);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, 
+		     "Media stack initialization has returned error", 
+		     status);
+	return status;
+    }
+
+    /* Register all codecs */
+#if PJMEDIA_HAS_SPEEX_CODEC
+    /* Register speex. */
+    status = pjmedia_codec_speex_init(pjsua_var.med_endpt, 
+				      PJMEDIA_SPEEX_NO_UWB,
+				      -1, -1);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Error initializing Speex codec",
+		     status);
+	return status;
+    }
+#endif /* PJMEDIA_HAS_SPEEX_CODEC */
+
+#if PJMEDIA_HAS_GSM_CODEC
+    /* Register GSM */
+    status = pjmedia_codec_gsm_init(pjsua_var.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_var.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_var.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_var.med_endpt),
+	&codec_id, PJMEDIA_CODEC_PRIO_DISABLED);
+
+#endif	/* PJMEDIA_HAS_L16_CODEC */
+
+
+    /* Save additional conference bridge parameters for future
+     * reference.
+     */
+    pjsua_var.mconf_cfg.samples_per_frame = pjsua_var.media_cfg.clock_rate * 
+					    PTIME / 1000;
+    pjsua_var.mconf_cfg.channel_count = 1;
+    pjsua_var.mconf_cfg.bits_per_sample = 16;
+
+    /* Init conference bridge. */
+    status = pjmedia_conf_create(pjsua_var.pool, 
+				 pjsua_var.media_cfg.max_media_ports,
+				 pjsua_var.media_cfg.clock_rate, 
+				 pjsua_var.mconf_cfg.channel_count,
+				 pjsua_var.mconf_cfg.samples_per_frame, 
+				 pjsua_var.mconf_cfg.bits_per_sample, 
+				 PJMEDIA_CONF_NO_DEVICE,
+				 &pjsua_var.mconf);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, 
+		     "Media stack initialization has returned error", 
+		     status);
+	return status;
+    }
+
+    /* Create null port just in case user wants to use null sound. */
+    status = pjmedia_null_port_create(pjsua_var.pool, 
+				      pjsua_var.media_cfg.clock_rate,
+				      pjsua_var.mconf_cfg.channel_count,
+				      pjsua_var.mconf_cfg.samples_per_frame,
+				      pjsua_var.mconf_cfg.bits_per_sample,
+				      &pjsua_var.null_port);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    return PJ_SUCCESS;
+}
+
+
+/* 
+ * Create RTP and RTCP socket pair, and possibly resolve their public
+ * address via STUN.
+ */
+static pj_status_t create_rtp_rtcp_sock(const pjsua_transport_config *cfg,
+					pjmedia_sock_info *skinfo)
+{
+    enum { 
+	RTP_RETRY = 100
+    };
+    int i;
+    static pj_uint16_t rtp_port;
+    pj_sockaddr_in mapped_addr[2];
+    pj_status_t status = PJ_SUCCESS;
+    pj_sock_t sock[2];
+
+    if (rtp_port == 0)
+	rtp_port = (pj_uint16_t)cfg->port;
+
+    for (i=0; i<2; ++i)
+	sock[i] = PJ_INVALID_SOCKET;
+
+
+    /* Loop retry to bind RTP and RTCP sockets. */
+    for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
+
+	/* Create and bind RTP socket. */
+	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "socket() error", status);
+	    return status;
+	}
+
+	status = pj_sock_bind_in(sock[0], cfg->ip_addr.s_addr, rtp_port);
+	if (status != PJ_SUCCESS) {
+	    pj_sock_close(sock[0]); 
+	    sock[0] = PJ_INVALID_SOCKET;
+	    continue;
+	}
+
+	/* Create and bind RTCP socket. */
+	status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[1]);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "socket() error", status);
+	    pj_sock_close(sock[0]);
+	    return status;
+	}
+
+	status = pj_sock_bind_in(sock[1], cfg->ip_addr.s_addr, 
+				 (pj_uint16_t)(rtp_port+1));
+	if (status != PJ_SUCCESS) {
+	    pj_sock_close(sock[0]); 
+	    sock[0] = PJ_INVALID_SOCKET;
+
+	    pj_sock_close(sock[1]); 
+	    sock[1] = PJ_INVALID_SOCKET;
+	    continue;
+	}
+
+	/*
+	 * If we're configured to use STUN, then find out the mapped address,
+	 * and make sure that the mapped RTCP port is adjacent with the RTP.
+	 */
+	if (cfg->stun_config.stun_srv1.slen) {
+	    status=pj_stun_get_mapped_addr(&pjsua_var.cp.factory, 2, sock,
+					   &cfg->stun_config.stun_srv1, 
+					   cfg->stun_config.stun_port1,
+					   &cfg->stun_config.stun_srv2, 
+					   cfg->stun_config.stun_port2,
+					   mapped_addr);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "STUN resolve error", status);
+		goto on_error;
+	    }
+
+	    if (pj_ntohs(mapped_addr[1].sin_port) == 
+		pj_ntohs(mapped_addr[0].sin_port)+1)
+	    {
+		/* Success! */
+		break;
+	    }
+
+	    pj_sock_close(sock[0]); 
+	    sock[0] = PJ_INVALID_SOCKET;
+
+	    pj_sock_close(sock[1]); 
+	    sock[1] = PJ_INVALID_SOCKET;
+
+	} else {
+	    const pj_str_t *hostname;
+	    pj_sockaddr_in addr;
+
+	    /* Get local IP address. */
+	    hostname = pj_gethostname();
+
+	    pj_memset( &addr, 0, sizeof(addr));
+	    addr.sin_family = PJ_AF_INET;
+	    status = pj_sockaddr_in_set_str_addr( &addr, hostname);
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Unresolvable local hostname", 
+			     status);
+		goto on_error;
+	    }
+
+	    for (i=0; i<2; ++i)
+		pj_memcpy(&mapped_addr[i], &addr, sizeof(addr));
+
+	    mapped_addr[0].sin_port=pj_htons((pj_uint16_t)rtp_port);
+	    mapped_addr[1].sin_port=pj_htons((pj_uint16_t)(rtp_port+1));
+	    break;
+	}
+    }
+
+    if (sock[0] == PJ_INVALID_SOCKET) {
+	PJ_LOG(1,(THIS_FILE, 
+		  "Unable to find appropriate RTP/RTCP ports combination"));
+	goto on_error;
+    }
+
+
+    skinfo->rtp_sock = sock[0];
+    pj_memcpy(&skinfo->rtp_addr_name, 
+	      &mapped_addr[0], sizeof(pj_sockaddr_in));
+
+    skinfo->rtcp_sock = sock[1];
+    pj_memcpy(&skinfo->rtcp_addr_name, 
+	      &mapped_addr[1], sizeof(pj_sockaddr_in));
+
+    PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
+	      pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr), 
+	      pj_ntohs(skinfo->rtp_addr_name.sin_port)));
+    PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
+	      pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr), 
+	      pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
+
+    rtp_port += 2;
+    return PJ_SUCCESS;
+
+on_error:
+    for (i=0; i<2; ++i) {
+	if (sock[i] != PJ_INVALID_SOCKET)
+	    pj_sock_close(sock[i]);
+    }
+    return status;
+}
+
+
+/*
+ * Start pjsua media subsystem.
+ */
+pj_status_t pjsua_media_subsys_start(void)
+{
+    pj_status_t status;
+
+    /* Create media for calls, if none is specified */
+    if (pjsua_var.calls[0].med_tp == NULL) {
+	pjsua_transport_config transport_cfg;
+
+	/* Create default transport config */
+	pjsua_transport_config_default(&transport_cfg);
+	transport_cfg.port = DEFAULT_RTP_PORT;
+
+	status = pjsua_media_transports_create(&transport_cfg);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    /* Create sound port if none is created yet */
+    if (pjsua_var.snd_port == NULL) {
+	status = pjsua_set_snd_dev(pjsua_var.cap_dev, pjsua_var.play_dev);
+	if (status != PJ_SUCCESS) {
+	    /* Error opening sound device, use null device */
+	    char errmsg[PJ_ERR_MSG_SIZE];
+
+	    pj_strerror(status, errmsg, sizeof(errmsg));
+	    PJ_LOG(4,(THIS_FILE, 
+		      "Error opening default sound device (%s (status=%d)). "
+		      "Will use NULL device instead",
+		      errmsg, status));
+	    
+	    status = pjsua_set_null_snd_dev();
+	    if (status != PJ_SUCCESS) {
+		pjsua_perror(THIS_FILE, "Error opening NULL sound device",
+			     status);
+		return status;
+	    }
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy pjsua media subsystem.
+ */
+pj_status_t pjsua_media_subsys_destroy(void)
+{
+    unsigned i;
+
+    close_snd_dev();
+
+    if (pjsua_var.mconf) {
+	pjmedia_conf_destroy(pjsua_var.mconf);
+	pjsua_var.mconf = NULL;
+    }
+
+    if (pjsua_var.null_port) {
+	pjmedia_port_destroy(pjsua_var.null_port);
+	pjsua_var.null_port = NULL;
+    }
+
+    /* Destroy file players */
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.player); ++i) {
+	if (pjsua_var.player[i].port) {
+	    pjmedia_port_destroy(pjsua_var.player[i].port);
+	    pjsua_var.player[i].port = NULL;
+	}
+    }
+
+    /* Destroy file recorders */
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.recorder); ++i) {
+	if (pjsua_var.recorder[i].port) {
+	    pjmedia_port_destroy(pjsua_var.recorder[i].port);
+	    pjsua_var.recorder[i].port = NULL;
+	}
+    }
+
+    /* Close media transports */
+    for (i=0; i<(int)pjsua_var.ua_cfg.max_calls; ++i) {
+	if (pjsua_var.calls[i].med_tp) {
+	    (*pjsua_var.calls[i].med_tp->op->destroy)(pjsua_var.calls[i].med_tp);
+	    pjsua_var.calls[i].med_tp = NULL;
+	}
+    }
+
+    /* Destroy media endpoint. */
+    if (pjsua_var.med_endpt) {
+
+	/* Shutdown all codecs: */
+#	if PJMEDIA_HAS_SPEEX_CODEC
+	    pjmedia_codec_speex_deinit();
+#	endif /* PJMEDIA_HAS_SPEEX_CODEC */
+
+#	if PJMEDIA_HAS_GSM_CODEC
+	    pjmedia_codec_gsm_deinit();
+#	endif /* PJMEDIA_HAS_GSM_CODEC */
+
+#	if PJMEDIA_HAS_G711_CODEC
+	    pjmedia_codec_g711_deinit();
+#	endif	/* PJMEDIA_HAS_G711_CODEC */
+
+#	if PJMEDIA_HAS_L16_CODEC
+	    pjmedia_codec_l16_deinit();
+#	endif	/* PJMEDIA_HAS_L16_CODEC */
+
+
+	pjmedia_endpt_destroy(pjsua_var.med_endpt);
+	pjsua_var.med_endpt = NULL;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Create UDP media transports for all the calls. This function creates
+ * one UDP media transport for each call.
+ */
+PJ_DEF(pj_status_t) 
+pjsua_media_transports_create(const pjsua_transport_config *app_cfg)
+{
+    pjsua_transport_config cfg;
+    unsigned i;
+    pj_status_t status;
+
+
+    /* Make sure pjsua_init() has been called */
+    PJ_ASSERT_RETURN(pjsua_var.ua_cfg.max_calls>0, PJ_EINVALIDOP);
+
+    PJSUA_LOCK();
+
+    /* Delete existing media transports */
+    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+	if (pjsua_var.calls[i].med_tp != NULL) {
+	    pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
+	    pjsua_var.calls[i].med_tp = NULL;
+	}
+    }
+
+    /* Copy config */
+    pj_memcpy(&cfg, app_cfg, sizeof(*app_cfg));
+    pjsua_normalize_stun_config(&cfg.stun_config);
+
+    /* Create each media transport */
+    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+
+	status = create_rtp_rtcp_sock(&cfg, &pjsua_var.calls[i].skinfo);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to create RTP/RTCP socket",
+		         status);
+	    goto on_error;
+	}
+	status = pjmedia_transport_udp_attach(pjsua_var.med_endpt, NULL,
+					      &pjsua_var.calls[i].skinfo, 0,
+					      &pjsua_var.calls[i].med_tp);
+	if (status != PJ_SUCCESS) {
+	    pjsua_perror(THIS_FILE, "Unable to create media transport",
+		         status);
+	    goto on_error;
+	}
+    }
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+
+on_error:
+    for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+	if (pjsua_var.calls[i].med_tp != NULL) {
+	    pjsua_var.calls[i].med_tp->op->destroy(pjsua_var.calls[i].med_tp);
+	    pjsua_var.calls[i].med_tp = NULL;
+	}
+    }
+
+    PJSUA_UNLOCK();
+
+    return status;
+}
+
+
+/*
+ * Get maxinum number of conference ports.
+ */
+PJ_DEF(unsigned) pjsua_conf_get_max_ports(void)
+{
+    return pjsua_var.media_cfg.max_media_ports;
+}
+
+
+/*
+ * Get current number of active ports in the bridge.
+ */
+PJ_DEF(unsigned) pjsua_conf_get_active_ports(void)
+{
+    unsigned ports[256];
+    unsigned count = PJ_ARRAY_SIZE(ports);
+    pj_status_t status;
+
+    status = pjmedia_conf_enum_ports(pjsua_var.mconf, ports, &count);
+    if (status != PJ_SUCCESS)
+	count = 0;
+
+    return count;
+}
+
+
+/*
+ * Enumerate all conference ports.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_conf_ports(pjsua_conf_port_id id[],
+					  unsigned *count)
+{
+    return pjmedia_conf_enum_ports(pjsua_var.mconf, (unsigned*)id, count);
+}
+
+
+/*
+ * Get information about the specified conference port
+ */
+PJ_DEF(pj_status_t) pjsua_conf_get_port_info( pjsua_conf_port_id id,
+					      pjsua_conf_port_info *info)
+{
+    pjmedia_conf_port_info cinfo;
+    unsigned i, count;
+    pj_status_t status;
+
+    status = pjmedia_conf_get_port_info( pjsua_var.mconf, id, &cinfo);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pj_memset(info, 0, sizeof(*info));
+    info->slot_id = id;
+    info->name = cinfo.name;
+    info->clock_rate = cinfo.clock_rate;
+    info->channel_count = cinfo.channel_count;
+    info->samples_per_frame = cinfo.samples_per_frame;
+    info->bits_per_sample = cinfo.bits_per_sample;
+
+    /* Build array of listeners */
+    count = pjsua_var.media_cfg.max_media_ports;
+    for (i=0; i<count; ++i) {
+	if (cinfo.listener[i]) {
+	    info->listeners[info->listener_cnt++] = i;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Establish unidirectional media flow from souce to sink. 
+ */
+PJ_DEF(pj_status_t) pjsua_conf_connect( pjsua_conf_port_id source,
+					pjsua_conf_port_id sink)
+{
+    return pjmedia_conf_connect_port(pjsua_var.mconf, source, sink, 0);
+}
+
+
+/*
+ * Disconnect media flow from the source to destination port.
+ */
+PJ_DEF(pj_status_t) pjsua_conf_disconnect( pjsua_conf_port_id source,
+					   pjsua_conf_port_id sink)
+{
+    return pjmedia_conf_disconnect_port(pjsua_var.mconf, source, sink);
+}
+
+
+/*****************************************************************************
+ * File player.
+ */
+
+/*
+ * Create a file player, and automatically connect this player to
+ * the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
+					 unsigned options,
+					 void *user_data,
+					 pjsua_player_id *p_id)
+{
+    unsigned slot, file_id;
+    char path[128];
+    pjmedia_port *port;
+    pj_status_t status;
+
+    if (pjsua_var.player_cnt >= PJ_ARRAY_SIZE(pjsua_var.player))
+	return PJ_ETOOMANY;
+
+    PJSUA_LOCK();
+
+    for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.player); ++file_id) {
+	if (pjsua_var.player[file_id].port == NULL)
+	    break;
+    }
+
+    if (file_id == PJ_ARRAY_SIZE(pjsua_var.player)) {
+	/* This is unexpected */
+	PJSUA_UNLOCK();
+	pj_assert(0);
+	return PJ_EBUG;
+    }
+
+    pj_memcpy(path, filename->ptr, filename->slen);
+    path[filename->slen] = '\0';
+    status = pjmedia_wav_player_port_create(pjsua_var.pool, path,
+					    pjsua_var.mconf_cfg.samples_per_frame *
+					      1000 / pjsua_var.media_cfg.clock_rate, 
+					    0, 0, user_data, &port);
+    if (status != PJ_SUCCESS) {
+	PJSUA_UNLOCK();
+	pjsua_perror(THIS_FILE, "Unable to open file for playback", status);
+	return status;
+    }
+
+    status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool, 
+				   port, filename, &slot);
+    if (status != PJ_SUCCESS) {
+	pjmedia_port_destroy(port);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    pjsua_var.player[file_id].port = port;
+    pjsua_var.player[file_id].slot = slot;
+
+    if (p_id) *p_id = file_id;
+
+    ++pjsua_var.player_cnt;
+
+    PJSUA_UNLOCK();
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get conference port ID associated with player.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
+{
+    PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+
+    return pjsua_var.player[id].slot;
+}
+
+
+/*
+ * Set playback position.
+ */
+PJ_DEF(pj_status_t) pjsua_player_set_pos( pjsua_player_id id,
+					  pj_uint32_t samples)
+{
+    PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+
+    return pjmedia_wav_player_port_set_pos(pjsua_var.player[id].port, samples);
+}
+
+
+/*
+ * Close the file, remove the player from the bridge, and free
+ * resources associated with the file player.
+ */
+PJ_DEF(pj_status_t) pjsua_player_destroy(pjsua_player_id id)
+{
+    PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.player), PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.player[id].port != NULL, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    if (pjsua_var.player[id].port) {
+	pjmedia_conf_remove_port(pjsua_var.mconf, 
+				 pjsua_var.player[id].slot);
+	pjmedia_port_destroy(pjsua_var.player[id].port);
+	pjsua_var.player[id].port = NULL;
+	pjsua_var.player[id].slot = 0xFFFF;
+	pjsua_var.player_cnt--;
+    }
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * File recorder.
+ */
+
+/*
+ * Create a file recorder, and automatically connect this recorder to
+ * the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
+					   unsigned file_format,
+					   const pj_str_t *encoding,
+					   pj_ssize_t max_size,
+					   unsigned options,
+					   void *user_data,
+					   pjsua_recorder_id *p_id)
+{
+    unsigned slot, file_id;
+    char path[128];
+    pjmedia_port *port;
+    pj_status_t status;
+
+    if (pjsua_var.rec_cnt >= PJ_ARRAY_SIZE(pjsua_var.recorder))
+	return PJ_ETOOMANY;
+
+    PJSUA_LOCK();
+
+    for (file_id=0; file_id<PJ_ARRAY_SIZE(pjsua_var.recorder); ++file_id) {
+	if (pjsua_var.recorder[file_id].port == NULL)
+	    break;
+    }
+
+    if (file_id == PJ_ARRAY_SIZE(pjsua_var.recorder)) {
+	/* This is unexpected */
+	PJSUA_UNLOCK();
+	pj_assert(0);
+	return PJ_EBUG;
+    }
+
+    pj_memcpy(path, filename->ptr, filename->slen);
+    path[filename->slen] = '\0';
+    status = pjmedia_wav_writer_port_create(pjsua_var.pool, path, 
+					    pjsua_var.media_cfg.clock_rate, 
+					    pjsua_var.mconf_cfg.channel_count,
+					    pjsua_var.mconf_cfg.samples_per_frame,
+					    pjsua_var.mconf_cfg.bits_per_sample, 
+					    0, 0, user_data, &port);
+    if (status != PJ_SUCCESS) {
+	PJSUA_UNLOCK();
+	pjsua_perror(THIS_FILE, "Unable to open file for recording", status);
+	return status;
+    }
+
+    status = pjmedia_conf_add_port(pjsua_var.mconf, pjsua_var.pool, 
+				   port, filename, &slot);
+    if (status != PJ_SUCCESS) {
+	pjmedia_port_destroy(port);
+	PJSUA_UNLOCK();
+	return status;
+    }
+
+    pjsua_var.recorder[file_id].port = port;
+    pjsua_var.recorder[file_id].slot = slot;
+
+    if (p_id) *p_id = file_id;
+
+    ++pjsua_var.rec_cnt;
+
+    PJSUA_UNLOCK();
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get conference port associated with recorder.
+ */
+PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
+{
+    PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
+
+    return pjsua_var.recorder[id].slot;
+}
+
+
+/*
+ * Destroy recorder (this will complete recording).
+ */
+PJ_DEF(pj_status_t) pjsua_recorder_destroy(pjsua_recorder_id id)
+{
+    PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.recorder), PJ_EINVAL);
+    PJ_ASSERT_RETURN(pjsua_var.recorder[id].port != NULL, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    if (pjsua_var.recorder[id].port) {
+	pjmedia_conf_remove_port(pjsua_var.mconf, 
+				 pjsua_var.recorder[id].slot);
+	pjmedia_port_destroy(pjsua_var.recorder[id].port);
+	pjsua_var.recorder[id].port = NULL;
+	pjsua_var.recorder[id].slot = 0xFFFF;
+	pjsua_var.rec_cnt--;
+    }
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Sound devices.
+ */
+
+/*
+ * Enum sound devices.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_snd_devs( pjmedia_snd_dev_info info[],
+					 unsigned *count)
+{
+    unsigned i, dev_count;
+
+    dev_count = pjmedia_snd_get_dev_count();
+    
+    if (dev_count > *count) dev_count = *count;
+
+    for (i=0; i<dev_count; ++i) {
+	const pjmedia_snd_dev_info *ci;
+
+	ci = pjmedia_snd_get_dev_info(i);
+	pj_memcpy(&info[i], ci, sizeof(*ci));
+    }
+
+    *count = dev_count;
+
+    return PJ_SUCCESS;
+}
+
+
+/* Close existing sound device */
+static void close_snd_dev(void)
+{
+    /* Close sound device */
+    if (pjsua_var.snd_port) {
+	const pjmedia_snd_dev_info *cap_info, *play_info;
+
+	cap_info = pjmedia_snd_get_dev_info(pjsua_var.cap_dev);
+	play_info = pjmedia_snd_get_dev_info(pjsua_var.play_dev);
+
+	PJ_LOG(4,(THIS_FILE, "Closing %s sound playback device and "
+			     "%s sound capture device",
+			     play_info->name, cap_info->name));
+
+	pjmedia_snd_port_disconnect(pjsua_var.snd_port);
+	pjmedia_snd_port_destroy(pjsua_var.snd_port);
+	pjsua_var.snd_port = NULL;
+    }
+
+    /* Close null sound device */
+    if (pjsua_var.null_snd) {
+	PJ_LOG(4,(THIS_FILE, "Closing null sound device.."));
+	pjmedia_master_port_destroy(pjsua_var.null_snd, PJ_FALSE);
+	pjsua_var.null_snd = NULL;
+    }
+}
+
+/*
+ * Select or change sound device. Application may call this function at
+ * any time to replace current sound device.
+ */
+PJ_DEF(pj_status_t) pjsua_set_snd_dev( int capture_dev,
+				       int playback_dev)
+{
+    pjmedia_port *conf_port;
+    const pjmedia_snd_dev_info *cap_info, *play_info;
+    pj_status_t status;
+
+    /* Close existing sound port */
+    close_snd_dev();
+
+
+    cap_info = pjmedia_snd_get_dev_info(capture_dev);
+    play_info = pjmedia_snd_get_dev_info(playback_dev);
+
+    PJ_LOG(4,(THIS_FILE, "Opening %s sound playback device and "
+			 "%s sound capture device..",
+			 play_info->name, cap_info->name));
+
+    /* Create the sound device. Sound port will start immediately. */
+    status = pjmedia_snd_port_create(pjsua_var.pool, capture_dev,
+				     playback_dev, 
+				     pjsua_var.media_cfg.clock_rate, 1,
+				     pjsua_var.media_cfg.clock_rate/FPS,
+				     16, 0, &pjsua_var.snd_port);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to open sound device", status);
+	return status;
+    }
+
+    /* Get the port0 of the conference bridge. */
+    conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
+    pj_assert(conf_port != NULL);
+
+    /* Connect to the conference port */
+    status = pjmedia_snd_port_connect(pjsua_var.snd_port, conf_port);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to connect conference port to "
+				"sound device", status);
+	pjmedia_snd_port_destroy(pjsua_var.snd_port);
+	pjsua_var.snd_port = NULL;
+	return status;
+    }
+
+    /* Save the device IDs */
+    pjsua_var.cap_dev = capture_dev;
+    pjsua_var.play_dev = playback_dev;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Use null sound device.
+ */
+PJ_DEF(pj_status_t) pjsua_set_null_snd_dev(void)
+{
+    pjmedia_port *conf_port;
+    pj_status_t status;
+
+    /* Close existing sound device */
+    close_snd_dev();
+
+    PJ_LOG(4,(THIS_FILE, "Opening null sound device.."));
+
+    /* Get the port0 of the conference bridge. */
+    conf_port = pjmedia_conf_get_master_port(pjsua_var.mconf);
+    pj_assert(conf_port != NULL);
+
+    /* Create master port, connecting port0 of the conference bridge to
+     * a null port.
+     */
+    status = pjmedia_master_port_create(pjsua_var.pool, pjsua_var.null_port,
+					conf_port, 0, &pjsua_var.null_snd);
+    if (status != PJ_SUCCESS) {
+	pjsua_perror(THIS_FILE, "Unable to create null sound device",
+		     status);
+	return status;
+    }
+
+    /* Start the master port */
+    status = pjmedia_master_port_start(pjsua_var.null_snd);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+    return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ * Codecs.
+ */
+
+/*
+ * Enum all supported codecs in the system.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
+				       unsigned *p_count )
+{
+    pjmedia_codec_mgr *codec_mgr;
+    pjmedia_codec_info info[32];
+    unsigned i, count, prio[32];
+    pj_status_t status;
+
+    codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
+    count = PJ_ARRAY_SIZE(info);
+    status = pjmedia_codec_mgr_enum_codecs( codec_mgr, &count, info, prio);
+    if (status != PJ_SUCCESS) {
+	*p_count = 0;
+	return status;
+    }
+
+    if (count > *p_count) count = *p_count;
+
+    for (i=0; i<count; ++i) {
+	pjmedia_codec_info_to_id(&info[i], id[i].buf_, sizeof(id[i].buf_));
+	id[i].codec_id = pj_str(id[i].buf_);
+	id[i].priority = (pj_uint8_t) prio[i];
+    }
+
+    *p_count = count;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Change codec priority.
+ */
+PJ_DEF(pj_status_t) pjsua_codec_set_priority( const pj_str_t *codec_id,
+					      pj_uint8_t priority )
+{
+    pjmedia_codec_mgr *codec_mgr;
+
+    codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
+
+    return pjmedia_codec_mgr_set_codec_priority(codec_mgr, codec_id, 
+					        priority);
+}
+
+
+/*
+ * Get codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_codec_get_param( const pj_str_t *codec_id,
+					   pjmedia_codec_param *param )
+{
+    const pjmedia_codec_info *info;
+    pjmedia_codec_mgr *codec_mgr;
+    unsigned count = 1;
+    pj_status_t status;
+
+    codec_mgr = pjmedia_endpt_get_codec_mgr(pjsua_var.med_endpt);
+
+    status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, codec_id,
+						 &count, &info, NULL);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    if (count != 1)
+	return PJ_ENOTFOUND;
+
+    status = pjmedia_codec_mgr_get_default_param( codec_mgr, info, param);
+    return status;
+}
+
+
+/*
+ * Set codec parameters.
+ */
+PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
+					   const pjmedia_codec_param *param)
+{
+    PJ_TODO(set_codec_param);
+    return PJ_SUCCESS;
+}
+
+
Index: /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_pres.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_pres.c	(revision 502)
+++ /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_pres.c	(revision 503)
@@ -18,19 +18,372 @@
  */
 #include <pjsua-lib/pjsua.h>
-#include "pjsua_imp.h"
-
-/*
- * pjsua_pres.c
- *
- * Presence related stuffs.
- */
+#include <pjsua-lib/pjsua_internal.h>
+
 
 #define THIS_FILE   "pjsua_pres.c"
 
 
-
-/* **************************************************************************
- * THE FOLLOWING PART HANDLES SERVER SUBSCRIPTION
- * **************************************************************************
+/*
+ * Get total number of buddies.
+ */
+PJ_DEF(unsigned) pjsua_get_buddy_count(void)
+{
+    return pjsua_var.buddy_cnt;
+}
+
+
+/*
+ * Check if buddy ID is valid.
+ */
+PJ_DEF(pj_bool_t) pjsua_buddy_is_valid(pjsua_buddy_id buddy_id)
+{
+    return buddy_id>=0 && buddy_id<PJ_ARRAY_SIZE(pjsua_var.buddy) &&
+	   pjsua_var.buddy[buddy_id].uri.slen != 0;
+}
+
+
+/*
+ * Enum buddy IDs.
+ */
+PJ_DEF(pj_status_t) pjsua_enum_buddies( pjsua_buddy_id ids[],
+					unsigned *count)
+{
+    unsigned i, c;
+
+    PJ_ASSERT_RETURN(ids && count, PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+	if (!pjsua_var.buddy[i].uri.slen)
+	    continue;
+	ids[c] = i;
+	++c;
+    }
+
+    *count = c;
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+
+}
+
+
+/*
+ * Get detailed buddy info.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_get_info( pjsua_buddy_id buddy_id,
+					  pjsua_buddy_info *info)
+{
+    int total=0;
+    pjsua_buddy *buddy;
+
+    PJ_ASSERT_RETURN(buddy_id>=0 && 
+		       buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy), 
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    pj_memset(info, 0, sizeof(pjsua_buddy_info));
+
+    buddy = &pjsua_var.buddy[buddy_id];
+    info->id = buddy->index;
+    if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
+	PJSUA_UNLOCK();
+	return PJ_SUCCESS;
+    }
+
+    /* uri */
+    info->uri.ptr = info->buf_ + total;
+    pj_strncpy(&info->uri, &buddy->uri, sizeof(info->buf_)-total);
+    total += info->uri.slen;
+
+    /* contact */
+    info->contact.ptr = info->buf_ + total;
+    pj_strncpy(&info->contact, &buddy->contact, sizeof(info->buf_)-total);
+    total += info->contact.slen;
+	
+    /* status and status text */    
+    if (buddy->sub == NULL || buddy->status.info_cnt==0) {
+	info->status = PJSUA_BUDDY_STATUS_UNKNOWN;
+	info->status_text = pj_str("?");
+    } else if (pjsua_var.buddy[buddy_id].status.info[0].basic_open) {
+	info->status = PJSUA_BUDDY_STATUS_ONLINE;
+	info->status_text = pj_str("Online");
+    } else {
+	info->status = PJSUA_BUDDY_STATUS_OFFLINE;
+	info->status_text = pj_str("Offline");
+    }
+
+    /* monitor pres */
+    info->monitor_pres = buddy->monitor;
+
+    PJSUA_UNLOCK();
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Reset buddy descriptor.
+ */
+static void reset_buddy(pjsua_buddy_id id)
+{
+    pj_memset(&pjsua_var.buddy[id], 0, sizeof(pjsua_var.buddy[id]));
+    pjsua_var.buddy[id].index = id;
+}
+
+
+/*
+ * Add new buddy.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_add( const pjsua_buddy_config *cfg,
+				     pjsua_buddy_id *p_buddy_id)
+{
+    pjsip_name_addr *url;
+    pjsip_sip_uri *sip_uri;
+    int index;
+    pj_str_t tmp;
+
+    PJ_ASSERT_RETURN(pjsua_var.buddy_cnt <= 
+			PJ_ARRAY_SIZE(pjsua_var.buddy),
+		     PJ_ETOOMANY);
+
+    PJSUA_LOCK();
+
+    /* Find empty slot */
+    for (index=0; index<PJ_ARRAY_SIZE(pjsua_var.buddy); ++index) {
+	if (pjsua_var.buddy[index].uri.slen == 0)
+	    break;
+    }
+
+    /* Expect to find an empty slot */
+    if (index == PJ_ARRAY_SIZE(pjsua_var.buddy)) {
+	PJSUA_UNLOCK();
+	/* This shouldn't happen */
+	pj_assert(!"index < PJ_ARRAY_SIZE(pjsua_var.buddy)");
+	return PJ_ETOOMANY;
+    }
+
+
+    /* Get name and display name for buddy */
+    pj_strdup_with_null(pjsua_var.pool, &tmp, &cfg->uri);
+    url = (pjsip_name_addr*)pjsip_parse_uri(pjsua_var.pool, tmp.ptr, tmp.slen,
+					    PJSIP_PARSE_URI_AS_NAMEADDR);
+
+    if (url == NULL) {
+	pjsua_perror(THIS_FILE, "Unable to add buddy", PJSIP_EINVALIDURI);
+	PJSUA_UNLOCK();
+	return PJSIP_EINVALIDURI;
+    }
+
+    /* Reset buddy, to make sure everything is cleared with default
+     * values
+     */
+    reset_buddy(index);
+
+    /* Save URI */
+    pjsua_var.buddy[index].uri = tmp;
+
+    sip_uri = (pjsip_sip_uri*) url->uri;
+    pjsua_var.buddy[index].name = sip_uri->user;
+    pjsua_var.buddy[index].display = url->display;
+    pjsua_var.buddy[index].host = sip_uri->host;
+    pjsua_var.buddy[index].port = sip_uri->port;
+    pjsua_var.buddy[index].monitor = cfg->subscribe;
+    if (pjsua_var.buddy[index].port == 0)
+	pjsua_var.buddy[index].port = 5060;
+
+    if (p_buddy_id)
+	*p_buddy_id = index;
+
+    pjsua_var.buddy_cnt++;
+
+    pjsua_buddy_subscribe_pres(index, cfg->subscribe);
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Delete buddy.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id)
+{
+    PJ_ASSERT_RETURN(buddy_id>=0 && 
+			buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    if (pjsua_var.buddy[buddy_id].uri.slen == 0) {
+	PJSUA_UNLOCK();
+	return PJ_SUCCESS;
+    }
+
+    /* Unsubscribe presence */
+    pjsua_buddy_subscribe_pres(buddy_id, PJ_FALSE);
+
+    /* Remove buddy */
+    pjsua_var.buddy[buddy_id].uri.slen = 0;
+    pjsua_var.buddy_cnt--;
+
+    /* Reset buddy struct */
+    reset_buddy(buddy_id);
+
+    PJSUA_UNLOCK();
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Enable/disable buddy's presence monitoring.
+ */
+PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id buddy_id,
+						pj_bool_t subscribe)
+{
+    pjsua_buddy *buddy;
+
+    PJ_ASSERT_RETURN(buddy_id>=0 && 
+			buddy_id<(int)PJ_ARRAY_SIZE(pjsua_var.buddy),
+		     PJ_EINVAL);
+
+    PJSUA_LOCK();
+
+    buddy = &pjsua_var.buddy[buddy_id];
+    buddy->monitor = subscribe;
+    pjsua_pres_refresh();
+
+    PJSUA_UNLOCK();
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Dump presence subscriptions to log file.
+ */
+PJ_DEF(void) pjsua_pres_dump(pj_bool_t verbose)
+{
+    unsigned acc_id;
+    unsigned i;
+
+    
+    PJSUA_LOCK();
+
+    /*
+     * When no detail is required, just dump number of server and client
+     * subscriptions.
+     */
+    if (verbose == PJ_FALSE) {
+	
+	int count = 0;
+
+	for (acc_id=0; acc_id<PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
+
+	    if (!pjsua_var.acc[acc_id].valid)
+		continue;
+
+	    if (!pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
+		struct pjsua_srv_pres *uapres;
+
+		uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
+		while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
+		    ++count;
+		    uapres = uapres->next;
+		}
+	    }
+	}
+
+	PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d", 
+		  count));
+
+	count = 0;
+
+	for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+	    if (pjsua_var.buddy[i].uri.slen == 0)
+		continue;
+	    if (pjsua_var.buddy[i].sub) {
+		++count;
+	    }
+	}
+
+	PJ_LOG(3,(THIS_FILE, "Number of client/UAC subscriptions: %d", 
+		  count));
+	PJSUA_UNLOCK();
+	return;
+    }
+    
+
+    /*
+     * Dumping all server (UAS) subscriptions
+     */
+    PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
+
+    for (acc_id=0; acc_id<(int)PJ_ARRAY_SIZE(pjsua_var.acc); ++acc_id) {
+
+	if (!pjsua_var.acc[acc_id].valid)
+	    continue;
+
+	PJ_LOG(3,(THIS_FILE, "  %.*s",
+		  (int)pjsua_var.acc[acc_id].cfg.id.slen,
+		  pjsua_var.acc[acc_id].cfg.id.ptr));
+
+	if (pj_list_empty(&pjsua_var.acc[acc_id].pres_srv_list)) {
+
+	    PJ_LOG(3,(THIS_FILE, "  - none - "));
+
+	} else {
+	    struct pjsua_srv_pres *uapres;
+
+	    uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
+	    while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
+	    
+		PJ_LOG(3,(THIS_FILE, "    %10s %s",
+			  pjsip_evsub_get_state_name(uapres->sub),
+			  uapres->remote));
+
+		uapres = uapres->next;
+	    }
+	}
+    }
+
+    /*
+     * Dumping all client (UAC) subscriptions
+     */
+    PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:"));
+
+    if (pjsua_var.buddy_cnt == 0) {
+
+	PJ_LOG(3,(THIS_FILE, "  - no buddy list - "));
+
+    } else {
+	for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+
+	    if (pjsua_var.buddy[i].uri.slen == 0)
+		continue;
+
+	    if (pjsua_var.buddy[i].sub) {
+		PJ_LOG(3,(THIS_FILE, "  %10s %.*s",
+			  pjsip_evsub_get_state_name(pjsua_var.buddy[i].sub),
+			  (int)pjsua_var.buddy[i].uri.slen,
+			  pjsua_var.buddy[i].uri.ptr));
+	    } else {
+		PJ_LOG(3,(THIS_FILE, "  %10s %.*s",
+			  "(null)",
+			  (int)pjsua_var.buddy[i].uri.slen,
+			  pjsua_var.buddy[i].uri.ptr));
+	    }
+	}
+    }
+
+    PJSUA_UNLOCK();
+}
+
+
+/***************************************************************************
+ * Server subscription.
  */
 
@@ -61,8 +414,11 @@
 static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event)
 {
-    pjsua_srv_pres *uapres = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+    pjsua_srv_pres *uapres;
 
     PJ_UNUSED_ARG(event);
 
+    PJSUA_LOCK();
+
+    uapres = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
     if (uapres) {
 	PJ_LOG(3,(THIS_FILE, "Server subscription to %s is %s",
@@ -70,8 +426,10 @@
 
 	if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
-	    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
+	    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
 	    pj_list_erase(uapres);
 	}
     }
+
+    PJSUA_UNLOCK();
 }
 
@@ -81,5 +439,5 @@
 static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
 {
-    int acc_index;
+    int acc_id;
     pjsua_acc_config *acc_config;
     pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
@@ -97,7 +455,9 @@
     /* Incoming SUBSCRIBE: */
 
+    PJSUA_LOCK();
+
     /* Find which account for the incoming request. */
-    acc_index = pjsua_acc_find_for_incoming(rdata);
-    acc_config = &pjsua.config.acc_config[acc_index];
+    acc_id = pjsua_acc_find_for_incoming(rdata);
+    acc_config = &pjsua_var.acc[acc_id].cfg;
 
     /* Create UAS dialog: */
@@ -109,5 +469,6 @@
 		     "Unable to create UAS dialog for subscription", 
 		     status);
-	return PJ_FALSE;
+	PJSUA_UNLOCK();
+	return PJ_TRUE;
     }
 
@@ -122,5 +483,6 @@
 	pjsua_perror(THIS_FILE, "Unable to create server subscription", 
 		     status);
-	return PJ_FALSE;
+	PJSUA_UNLOCK();
+	return PJ_TRUE;
     }
 
@@ -136,8 +498,8 @@
 	uapres->remote[status] = '\0';
 
-    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, uapres);
+    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, uapres);
 
     /* Add server subscription to the list: */
-    pj_list_push_back(&pjsua.acc[acc_index].pres_srv_list, uapres);
+    pj_list_push_back(&pjsua_var.acc[acc_id].pres_srv_list, uapres);
 
 
@@ -149,4 +511,5 @@
 	pj_list_erase(uapres);
 	pjsip_pres_terminate(sub, PJ_FALSE);
+	PJSUA_UNLOCK();
 	return PJ_FALSE;
     }
@@ -156,8 +519,8 @@
     pj_memset(&pres_status, 0, sizeof(pres_status));
     pres_status.info_cnt = 1;
-    pres_status.info[0].basic_open = pjsua.acc[acc_index].online_status;
-    //Both pjsua.local_uri and pjsua.contact_uri are enclosed in "<" and ">"
+    pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
+    //Both pjsua_var.local_uri and pjsua_var.contact_uri are enclosed in "<" and ">"
     //causing XML parsing to fail.
-    //pres_status.info[0].contact = pjsua.local_uri;
+    //pres_status.info[0].contact = pjsua_var.local_uri;
 
     pjsip_pres_set_status(sub, &pres_status);
@@ -174,4 +537,5 @@
 	pj_list_erase(uapres);
 	pjsip_pres_terminate(sub, PJ_FALSE);
+	PJSUA_UNLOCK();
 	return PJ_FALSE;
     }
@@ -180,16 +544,48 @@
     /* Done: */
 
+    PJSUA_UNLOCK();
+
     return PJ_TRUE;
 }
 
 
+/* Terminate server subscription for the account */
+void pjsua_pres_delete_acc(int acc_id)
+{
+    pjsua_srv_pres *uapres;
+
+    uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
+
+    while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
+	
+	pjsip_pres_status pres_status;
+	pj_str_t reason = { "noresource", 10 };
+	pjsip_tx_data *tdata;
+
+	pjsip_pres_get_status(uapres->sub, &pres_status);
+	
+	pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
+	pjsip_pres_set_status(uapres->sub, &pres_status);
+
+	if (pjsip_pres_notify(uapres->sub, 
+			      PJSIP_EVSUB_STATE_TERMINATED, NULL,
+			      &reason, &tdata)==PJ_SUCCESS)
+	{
+	    pjsip_pres_send_request(uapres->sub, tdata);
+	}
+
+	uapres = uapres->next;
+    }
+}
+
+
 /* Refresh subscription (e.g. when our online status has changed) */
-static void refresh_server_subscription(int acc_index)
+static void refresh_server_subscription(int acc_id)
 {
     pjsua_srv_pres *uapres;
 
-    uapres = pjsua.acc[acc_index].pres_srv_list.next;
-
-    while (uapres != &pjsua.acc[acc_index].pres_srv_list) {
+    uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
+
+    while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
 	
 	pjsip_pres_status pres_status;
@@ -197,20 +593,10 @@
 
 	pjsip_pres_get_status(uapres->sub, &pres_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;
+	if (pres_status.info[0].basic_open != pjsua_var.acc[acc_id].online_status) {
+	    pres_status.info[0].basic_open = pjsua_var.acc[acc_id].online_status;
 	    pjsip_pres_set_status(uapres->sub, &pres_status);
 
-	    if (pjsua.quit_flag) {
-		pj_str_t reason = { "noresource", 10 };
-		if (pjsip_pres_notify(uapres->sub, 
-				      PJSIP_EVSUB_STATE_TERMINATED, NULL,
-				      &reason, &tdata)==PJ_SUCCESS)
-		{
-		    pjsip_pres_send_request(uapres->sub, tdata);
-		}
-	    } else {
-		if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS)
-		    pjsip_pres_send_request(uapres->sub, tdata);
-	    }
+	    if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS)
+		pjsip_pres_send_request(uapres->sub, tdata);
 	}
 
@@ -221,7 +607,6 @@
 
 
-/* **************************************************************************
- * THE FOLLOWING PART HANDLES CLIENT SUBSCRIPTION
- * **************************************************************************
+/***************************************************************************
+ * Client subscription.
  */
 
@@ -233,10 +618,12 @@
     PJ_UNUSED_ARG(event);
 
-    buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+    PJSUA_LOCK();
+
+    buddy = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
     if (buddy) {
 	PJ_LOG(3,(THIS_FILE, 
 		  "Presence subscription to %.*s is %s",
-		  (int)pjsua.config.buddy_uri[buddy->index].slen,
-		  pjsua.config.buddy_uri[buddy->index].ptr, 
+		  (int)pjsua_var.buddy[buddy->index].uri.slen,
+		  pjsua_var.buddy[buddy->index].uri.ptr, 
 		  pjsip_evsub_get_state_name(sub)));
 
@@ -244,12 +631,70 @@
 	    buddy->sub = NULL;
 	    buddy->status.info_cnt = 0;
-	    pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL);
+	    pjsip_evsub_set_mod_data(sub, pjsua_var.mod.id, NULL);
 	}
 
 	/* Call callback */
-	if (pjsua.cb.on_buddy_state)
-	    (*pjsua.cb.on_buddy_state)(buddy->index);
-    }
-}
+	if (pjsua_var.ua_cfg.cb.on_buddy_state)
+	    (*pjsua_var.ua_cfg.cb.on_buddy_state)(buddy->index);
+    }
+
+    PJSUA_UNLOCK();
+}
+
+
+/* Callback when transaction state has changed. */
+static void pjsua_evsub_on_tsx_state(pjsip_evsub *sub, 
+				     pjsip_transaction *tsx,
+				     pjsip_event *event)
+{
+    pjsua_buddy *buddy;
+    pjsip_contact_hdr *contact_hdr;
+
+    PJSUA_LOCK();
+
+    buddy = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
+    if (!buddy) {
+	PJSUA_UNLOCK();
+	return;
+    }
+
+    /* We only use this to update buddy's Contact, when it's not
+     * set.
+     */
+    if (buddy->contact.slen != 0) {
+	/* Contact already set */
+	PJSUA_UNLOCK();
+	return;
+    }
+    
+    /* Only care about 2xx response to outgoing SUBSCRIBE */
+    if (tsx->status_code/100 != 2 ||
+	tsx->role != PJSIP_UAC_ROLE ||
+	event->type != PJSIP_EVENT_RX_MSG || 
+	pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method)!=0)
+    {
+	PJSUA_UNLOCK();
+	return;
+    }
+
+    /* Find contact header. */
+    contact_hdr = pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg,
+				     PJSIP_H_CONTACT, NULL);
+    if (!contact_hdr) {
+	PJSUA_UNLOCK();
+	return;
+    }
+
+    buddy->contact.ptr = pj_pool_alloc(pjsua_var.pool, PJSIP_MAX_URL_SIZE);
+    buddy->contact.slen = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR,
+					   contact_hdr->uri,
+					   buddy->contact.ptr, 
+					   PJSIP_MAX_URL_SIZE);
+    if (buddy->contact.slen < 0)
+	buddy->contact.slen = 0;
+
+    PJSUA_UNLOCK();
+}
+
 
 /* Callback called when we receive NOTIFY */
@@ -263,5 +708,7 @@
     pjsua_buddy *buddy;
 
-    buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id);
+    PJSUA_LOCK();
+
+    buddy = pjsip_evsub_get_mod_data(sub, pjsua_var.mod.id);
     if (buddy) {
 	/* Update our info. */
@@ -277,4 +724,6 @@
     PJ_UNUSED_ARG(res_hdr);
     PJ_UNUSED_ARG(p_body);
+
+    PJSUA_UNLOCK();
 }
 
@@ -284,6 +733,5 @@
 {
     &pjsua_evsub_on_state,  
-
-    NULL,   /* on_tsx_state: don't care about transaction state. */
+    &pjsua_evsub_on_tsx_state,
 
     NULL,   /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless 
@@ -305,18 +753,21 @@
 static void subscribe_buddy_presence(unsigned index)
 {
-    int acc_index;
-    pjsua_acc_config *acc_config;
+    pjsua_buddy *buddy;
+    int acc_id;
+    pjsua_acc *acc;
     pjsip_dialog *dlg;
     pjsip_tx_data *tdata;
     pj_status_t status;
 
-    acc_index = pjsua_acc_find_for_outgoing(&pjsua.config.buddy_uri[index]);
-
-    acc_config = &pjsua.config.acc_config[acc_index];
-
+    buddy = &pjsua_var.buddy[index];
+    acc_id = pjsua_acc_find_for_outgoing(&buddy->uri);
+
+    acc = &pjsua_var.acc[acc_id];
+
+    /* Create UAC dialog */
     status = pjsip_dlg_create_uac( pjsip_ua_instance(), 
-				   &acc_config->id,
-				   &acc_config->contact,
-				   &pjsua.config.buddy_uri[index],
+				   &acc->cfg.id,
+				   &acc->cfg.contact,
+				   &buddy->uri,
 				   NULL, &dlg);
     if (status != PJ_SUCCESS) {
@@ -326,14 +777,19 @@
     }
 
-    if (acc_config->cred_count) {
+    /* Set route-set */
+    if (!pj_list_empty(&acc->route_set)) {
+	pjsip_dlg_set_route_set(dlg, &acc->route_set);
+    }
+
+    /* Set credentials */
+    if (acc->cred_cnt) {
 	pjsip_auth_clt_set_credentials( &dlg->auth_sess, 
-					acc_config->cred_count,
-					acc_config->cred_info);
+					acc->cred_cnt, acc->cred);
     }
 
     status = pjsip_pres_create_uac( dlg, &pres_callback, 
-				    &pjsua.buddies[index].sub);
+				    &buddy->sub);
     if (status != PJ_SUCCESS) {
-	pjsua.buddies[index].sub = NULL;
+	pjsua_var.buddy[index].sub = NULL;
 	pjsua_perror(THIS_FILE, "Unable to create presence client", 
 		     status);
@@ -342,11 +798,10 @@
     }
 
-    pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id,
-			     &pjsua.buddies[index]);
-
-    status = pjsip_pres_initiate(pjsua.buddies[index].sub, -1, &tdata);
+    pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy);
+
+    status = pjsip_pres_initiate(buddy->sub, -1, &tdata);
     if (status != PJ_SUCCESS) {
-	pjsip_pres_terminate(pjsua.buddies[index].sub, PJ_FALSE);
-	pjsua.buddies[index].sub = NULL;
+	pjsip_pres_terminate(buddy->sub, PJ_FALSE);
+	buddy->sub = NULL;
 	pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE", 
 		     status);
@@ -354,8 +809,8 @@
     }
 
-    status = pjsip_pres_send_request(pjsua.buddies[index].sub, tdata);
+    status = pjsip_pres_send_request(buddy->sub, tdata);
     if (status != PJ_SUCCESS) {
-	pjsip_pres_terminate(pjsua.buddies[index].sub, PJ_FALSE);
-	pjsua.buddies[index].sub = NULL;
+	pjsip_pres_terminate(buddy->sub, PJ_FALSE);
+	buddy->sub = NULL;
 	pjsua_perror(THIS_FILE, "Unable to send initial SUBSCRIBE", 
 		     status);
@@ -368,25 +823,25 @@
 static void unsubscribe_buddy_presence(unsigned index)
 {
+    pjsua_buddy *buddy;
     pjsip_tx_data *tdata;
     pj_status_t status;
 
-    if (pjsua.buddies[index].sub == NULL)
-	return;
-
-    if (pjsip_evsub_get_state(pjsua.buddies[index].sub) == 
-	PJSIP_EVSUB_STATE_TERMINATED)
-    {
-	pjsua.buddies[index].sub = NULL;
-	return;
-    }
-
-    status = pjsip_pres_initiate( pjsua.buddies[index].sub, 0, &tdata);
+    buddy = &pjsua_var.buddy[index];
+
+    if (buddy->sub == NULL)
+	return;
+
+    if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+	pjsua_var.buddy[index].sub = NULL;
+	return;
+    }
+
+    status = pjsip_pres_initiate( buddy->sub, 0, &tdata);
     if (status == PJ_SUCCESS)
-	status = pjsip_pres_send_request( pjsua.buddies[index].sub, tdata );
+	status = pjsip_pres_send_request( buddy->sub, tdata );
 
     if (status != PJ_SUCCESS) {
-
-	pjsip_pres_terminate(pjsua.buddies[index].sub, PJ_FALSE);
-	pjsua.buddies[index].sub = NULL;
+	pjsip_pres_terminate(buddy->sub, PJ_FALSE);
+	buddy->sub = NULL;
 	pjsua_perror(THIS_FILE, "Unable to unsubscribe presence", 
 		     status);
@@ -396,14 +851,17 @@
 
 /* It does what it says.. */
-static void refresh_client_subscription(void)
+static void refresh_client_subscriptions(void)
 {
     unsigned i;
 
-    for (i=0; i<pjsua.config.buddy_cnt; ++i) {
-
-	if (pjsua.buddies[i].monitor && !pjsua.buddies[i].sub) {
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+
+	if (!pjsua_var.buddy[i].uri.slen)
+	    continue;
+
+	if (pjsua_var.buddy[i].monitor && !pjsua_var.buddy[i].sub) {
 	    subscribe_buddy_presence(i);
 
-	} else if (!pjsua.buddies[i].monitor && pjsua.buddies[i].sub) {
+	} else if (!pjsua_var.buddy[i].monitor && pjsua_var.buddy[i].sub) {
 	    unsubscribe_buddy_presence(i);
 
@@ -418,7 +876,8 @@
 pj_status_t pjsua_pres_init()
 {
+    unsigned i;
     pj_status_t status;
 
-    status = pjsip_endpt_register_module( pjsua.endpt, &mod_pjsua_pres);
+    status = pjsip_endpt_register_module( pjsua_var.endpt, &mod_pjsua_pres);
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to register pjsua presence module", 
@@ -426,296 +885,55 @@
     }
 
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+	reset_buddy(i);
+    }
+
     return status;
 }
 
-/*
- * Get buddy count.
- */
-PJ_DEF(unsigned) pjsua_get_buddy_count(void)
-{
-    return pjsua.config.buddy_cnt;
-}
-
-
-/**
- * Get buddy info.
- */
-PJ_DEF(pj_status_t) pjsua_buddy_get_info(pjsua_buddy_id index,
-					 pjsua_buddy_info *info)
-{
-    pjsua_buddy *buddy;
-
-    PJ_ASSERT_RETURN(index < (int)PJ_ARRAY_SIZE(pjsua.config.buddy_uri), 
-		     PJ_EINVAL);
-
-    pj_memset(info, 0, sizeof(pjsua_buddy_info));
-
-    buddy = &pjsua.buddies[index];
-    info->index = buddy->index;
-    info->is_valid = pjsua.config.buddy_uri[index].slen;
-    if (!info->is_valid)
-	return PJ_SUCCESS;
-
-    info->name = buddy->name;
-    info->display_name = buddy->display;
-    info->host = buddy->host;
-    info->port = buddy->port;
-    info->uri = pjsua.config.buddy_uri[index];
-    
-    if (buddy->sub == NULL || buddy->status.info_cnt==0) {
-	info->status = PJSUA_BUDDY_STATUS_UNKNOWN;
-	info->status_text = pj_str("?");
-    } else if (pjsua.buddies[index].status.info[0].basic_open) {
-	info->status = PJSUA_BUDDY_STATUS_ONLINE;
-	info->status_text = pj_str("Online");
-    } else {
-	info->status = PJSUA_BUDDY_STATUS_OFFLINE;
-	info->status_text = pj_str("Offline");
-    }
-
+
+/*
+ * Start presence subsystem.
+ */
+pj_status_t pjsua_pres_start(void)
+{
+    /* Nothing to do (is it?) */
     return PJ_SUCCESS;
 }
 
 
-/**
- * Add new buddy.
- */
-PJ_DEF(pj_status_t) pjsua_buddy_add( const pj_str_t *uri,
-				     pjsua_buddy_id *buddy_index)
-{
-    pjsip_name_addr *url;
-    pjsip_sip_uri *sip_uri;
-    int index;
-    pj_str_t tmp;
-
-    PJ_ASSERT_RETURN(pjsua.config.buddy_cnt <= 
-			PJ_ARRAY_SIZE(pjsua.config.buddy_uri),
-		     PJ_ETOOMANY);
-
-    /* Find empty slot */
-    for (index=0; index<PJ_ARRAY_SIZE(pjsua.config.buddy_uri); ++index) {
-	if (pjsua.config.buddy_uri[index].slen == 0)
-	    break;
-    }
-
-    /* Expect to find an empty slot */
-    PJ_ASSERT_RETURN(index < PJ_ARRAY_SIZE(pjsua.config.buddy_uri),
-		     PJ_ETOOMANY);
-
-
-    /* Get name and display name for buddy */
-    pj_strdup_with_null(pjsua.pool, &tmp, uri);
-    url = (pjsip_name_addr*)pjsip_parse_uri(pjsua.pool, tmp.ptr, tmp.slen,
-					    PJSIP_PARSE_URI_AS_NAMEADDR);
-
-    if (url == NULL)
-	return PJSIP_EINVALIDURI;
-
-    /* Save URI */
-    pjsua.config.buddy_uri[index] = tmp;
-
-    sip_uri = (pjsip_sip_uri*) url->uri;
-    pjsua.buddies[index].name = sip_uri->user;
-    pjsua.buddies[index].display = url->display;
-    pjsua.buddies[index].host = sip_uri->host;
-    pjsua.buddies[index].port = sip_uri->port;
-    if (pjsua.buddies[index].port == 0)
-	pjsua.buddies[index].port = 5060;
-
-    if (buddy_index)
-	*buddy_index = index;
-
-    pjsua.config.buddy_cnt++;
-
-    return PJ_SUCCESS;
-}
-
-
-
-/**
- * Delete buddy.
- */
-PJ_DEF(pj_status_t) pjsua_buddy_del(pjsua_buddy_id index)
-{
-    PJ_ASSERT_RETURN(index < (int)PJ_ARRAY_SIZE(pjsua.config.buddy_uri), 
-		     PJ_EINVAL);
-
-    if (pjsua.config.buddy_uri[index].slen == 0)
-	return PJ_SUCCESS;
-
-    /* Unsubscribe presence */
-    pjsua_buddy_subscribe_pres(index, PJ_FALSE);
-
-    /* Remove buddy */
-    pjsua.config.buddy_uri[index].slen = 0;
-    pjsua.config.buddy_cnt--;
-
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjsua_buddy_subscribe_pres( pjsua_buddy_id index,
-						pj_bool_t monitor)
-{
-    pjsua_buddy *buddy;
-
-    PJ_ASSERT_RETURN(index < (int)PJ_ARRAY_SIZE(pjsua.config.buddy_uri),
-		     PJ_EINVAL);
-
-    buddy = &pjsua.buddies[index];
-    buddy->monitor = monitor;
+/*
+ * Refresh presence subscriptions
+ */
+void pjsua_pres_refresh()
+{
+    unsigned i;
+
+    refresh_client_subscriptions();
+
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+	if (pjsua_var.acc[i].valid)
+	    refresh_server_subscription(i);
+    }
+}
+
+
+/*
+ * Shutdown presence.
+ */
+void pjsua_pres_shutdown(void)
+{
+    unsigned i;
+
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+	if (!pjsua_var.acc[i].valid)
+	    continue;
+	pjsua_pres_delete_acc(i);
+    }
+
+    for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+	pjsua_var.buddy[i].monitor = 0;
+    }
+
     pjsua_pres_refresh();
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjsua_acc_set_online_status( pjsua_acc_id acc_index,
-						 pj_bool_t is_online)
-{
-    PJ_ASSERT_RETURN(acc_index < (int)PJ_ARRAY_SIZE(pjsua.acc), 
-		     PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP);
-
-    pjsua.acc[acc_index].online_status = is_online;
-    pjsua_pres_refresh();
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Refresh presence
- */
-PJ_DEF(void) pjsua_pres_refresh()
-{
-    unsigned i;
-
-    refresh_client_subscription();
-
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i)
-	refresh_server_subscription(i);
-}
-
-
-/*
- * Shutdown presence.
- */
-void pjsua_pres_shutdown(void)
-{
-    unsigned acc_index;
-    unsigned i;
-
-    for (acc_index=0; acc_index<(int)pjsua.config.acc_cnt; ++acc_index) {
-	pjsua.acc[acc_index].online_status = 0;
-    }
-
-    for (i=0; i<pjsua.config.buddy_cnt; ++i) {
-	pjsua.buddies[i].monitor = 0;
-    }
-
-    pjsua_pres_refresh();
-}
-
-/*
- * Dump presence status.
- */
-void pjsua_pres_dump(pj_bool_t detail)
-{
-    unsigned acc_index;
-    unsigned i;
-
-
-    /*
-     * When no detail is required, just dump number of server and client
-     * subscriptions.
-     */
-    if (detail == PJ_FALSE) {
-	
-	int count = 0;
-
-	for (acc_index=0; acc_index < (int)pjsua.config.acc_cnt; ++acc_index) {
-
-	    if (!pj_list_empty(&pjsua.acc[acc_index].pres_srv_list)) {
-		struct pjsua_srv_pres *uapres;
-
-		uapres = pjsua.acc[acc_index].pres_srv_list.next;
-		while (uapres != &pjsua.acc[acc_index].pres_srv_list) {
-		    ++count;
-		    uapres = uapres->next;
-		}
-	    }
-	}
-
-	PJ_LOG(3,(THIS_FILE, "Number of server/UAS subscriptions: %d", 
-		  count));
-
-	count = 0;
-
-	for (i=0; i<pjsua.config.buddy_cnt; ++i) {
-	    if (pjsua.buddies[i].sub) {
-		++count;
-	    }
-	}
-
-	PJ_LOG(3,(THIS_FILE, "Number of client/UAC subscriptions: %d", 
-		  count));
-	return;
-    }
-    
-
-    /*
-     * Dumping all server (UAS) subscriptions
-     */
-    PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:"));
-
-    for (acc_index=0; acc_index < (int)pjsua.config.acc_cnt; ++acc_index) {
-
-	PJ_LOG(3,(THIS_FILE, "  %.*s",
-		  (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)) {
-
-	    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;
-	    }
-	}
-    }
-
-    /*
-     * Dumping all client (UAC) subscriptions
-     */
-    PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:"));
-
-    if (pjsua.config.buddy_cnt == 0) {
-
-	PJ_LOG(3,(THIS_FILE, "  - no buddy list - "));
-
-    } else {
-	for (i=0; i<pjsua.config.buddy_cnt; ++i) {
-
-	    if (pjsua.buddies[i].sub) {
-		PJ_LOG(3,(THIS_FILE, "  %10s %.*s",
-			  pjsip_evsub_get_state_name(pjsua.buddies[i].sub),
-			  (int)pjsua.config.buddy_uri[i].slen,
-			  pjsua.config.buddy_uri[i].ptr));
-	    } else {
-		PJ_LOG(3,(THIS_FILE, "  %10s %.*s",
-			  "(null)",
-			  (int)pjsua.config.buddy_uri[i].slen,
-			  pjsua.config.buddy_uri[i].ptr));
-	    }
-	}
-    }
-}
-
+}
Index: project/trunk/pjsip/src/pjsua-lib/pjsua_reg.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_reg.c	(revision 502)
+++ 	(revision )
@@ -1,281 +1,0 @@
-/* $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_imp.h"
-
-
-/*
- * pjsua_reg.c
- *
- * Client registration handler.
- */
-
-#define THIS_FILE   "pjsua_reg.c"
-
-
-/*
- * This callback is called by pjsip_regc when outgoing register
- * request has completed.
- */
-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);
-	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));
-	pjsip_regc_destroy(acc->regc);
-	acc->regc = NULL;
-
-    } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
-
-	if (param->expiration < 1) {
-	    pjsip_regc_destroy(acc->regc);
-	    acc->regc = NULL;
-	    PJ_LOG(3,(THIS_FILE, "%s: unregistration success",
-		      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", 
-		       pjsua.config.acc_config[acc->index].id.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));
-    }
-
-    acc->reg_last_err = param->status;
-    acc->reg_last_code = param->code;
-
-    if (pjsua.cb.on_reg_state)
-	(*pjsua.cb.on_reg_state)(acc->index);
-}
-
-
-/**
- * Get number of accounts.
- */
-PJ_DEF(unsigned) pjsua_get_acc_count(void)
-{
-    return pjsua.config.acc_cnt;
-}
-
-
-/**
- * Get account info.
- */
-PJ_DEF(pj_status_t) pjsua_acc_get_info( pjsua_acc_id acc_index,
-					pjsua_acc_info *info)
-{
-    pjsua_acc *acc = &pjsua.acc[acc_index];
-    pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index];
-
-    PJ_ASSERT_RETURN(info != NULL, PJ_EINVAL);
-    
-    pj_memset(info, 0, sizeof(pjsua_acc_info));
-
-    PJ_ASSERT_RETURN(acc_index < (int)pjsua.config.acc_cnt, 
-		     PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP);
-
-    
-    info->index = acc_index;
-    info->acc_id = acc_cfg->id;
-    info->has_registration = (acc->regc != NULL);
-    info->online_status = acc->online_status;
-    
-    if (acc->reg_last_err) {
-	info->status = acc->reg_last_err;
-	pj_strerror(acc->reg_last_err, info->buf, sizeof(info->buf));
-	info->status_text = pj_str(info->buf);
-    } else if (acc->reg_last_code) {
-	info->status = acc->reg_last_code;
-	info->status_text = *pjsip_get_status_text(acc->reg_last_code);
-    } else {
-	info->status = 0;
-	info->status_text = pj_str("In Progress");
-    }
-    
-    if (acc->regc) {
-	pjsip_regc_info regc_info;
-	pjsip_regc_get_info(acc->regc, &regc_info);
-	info->expires = regc_info.next_reg;
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-PJ_DEF(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[],
-					 unsigned *count )
-{
-    unsigned i, c;
-
-    for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua.acc); ++i) {
-	if (!pjsua.acc[i].valid)
-	    continue;
-
-	pjsua_acc_get_info(i, &info[c]);
-	++c;
-    }
-
-    *count = c;
-    return PJ_SUCCESS;
-}
-
-
-/**
- * Enum accounts id.
- */
-PJ_DEF(pj_status_t) pjsua_acc_enum_id( pjsua_acc_id ids[],
-				       unsigned *count )
-{
-    unsigned i, c;
-
-    for (i=0, c=0; c<*count && i<PJ_ARRAY_SIZE(pjsua.acc); ++i) {
-	if (!pjsua.acc[i].valid)
-	    continue;
-	ids[c] = i;
-	++c;
-    }
-
-    *count = c;
-    return PJ_SUCCESS;
-}
-
-
-/*
- * Update registration. If renew is false, then unregistration will be performed.
- */
-PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_index, 
-						pj_bool_t renew)
-{
-    pj_status_t status = 0;
-    pjsip_tx_data *tdata = 0;
-
-    PJ_ASSERT_RETURN(acc_index < (int)pjsua.config.acc_cnt, 
-		     PJ_EINVAL);
-    PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP);
-
-    if (renew) {
-	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 PJ_EINVALIDOP;
-	    }
-	}
-	if (!pjsua.acc[acc_index].regc)
-	    return PJ_EINVALIDOP;
-
-	status = pjsip_regc_register(pjsua.acc[acc_index].regc, 1, 
-				     &tdata);
-
-    } else {
-	if (pjsua.acc[acc_index].regc == NULL) {
-	    PJ_LOG(3,(THIS_FILE, "Currently not registered"));
-	    return PJ_EINVALIDOP;
-	}
-	status = pjsip_regc_unregister(pjsua.acc[acc_index].regc, &tdata);
-    }
-
-    if (status == PJ_SUCCESS)
-	status = pjsip_regc_send( pjsua.acc[acc_index].regc, tdata );
-
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, "Unable to create/send REGISTER", 
-		     status);
-    } else {
-	PJ_LOG(3,(THIS_FILE, "%s sent",
-	         (renew? "Registration" : "Unregistration")));
-    }
-
-    return status;
-}
-
-/*
- * Initialize client registration.
- */
-pj_status_t pjsua_regc_init(int acc_index)
-{
-    pjsua_acc_config *acc_config;
-    pj_status_t status;
-
-    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;
-    }
-
-    /* initialize SIP registration if registrar is configured */
-
-    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", 
-		     status);
-	return status;
-    }
-
-
-    status = pjsip_regc_init( pjsua.acc[acc_index].regc, 
-			      &acc_config->reg_uri, 
-			      &acc_config->id, 
-			      &acc_config->id,
-			      1, &acc_config->contact, 
-			      acc_config->reg_timeout);
-    if (status != PJ_SUCCESS) {
-	pjsua_perror(THIS_FILE, 
-		     "Client registration initialization error", 
-		     status);
-	return status;
-    }
-
-    if (acc_config->cred_count) {
-	pjsip_regc_set_credentials( pjsua.acc[acc_index].regc, 
-				    acc_config->cred_count, 
-				    acc_config->cred_info );
-    }
-
-    pjsip_regc_set_route_set( pjsua.acc[acc_index].regc, 
-			      &pjsua.acc[acc_index].route_set );
-
-    return PJ_SUCCESS;
-}
-
Index: project/trunk/pjsip/src/pjsua-lib/pjsua_settings.c
===================================================================
--- /pjproject/trunk/pjsip/src/pjsua-lib/pjsua_settings.c	(revision 502)
+++ 	(revision )
@@ -1,1435 +1,0 @@
-/* $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 <stdio.h>
-#include "pjsua_imp.h"
-
-
-/*
- * pjsua_settings.c
- *
- * Anything to do with configuration and state dump.
- */
-
-#define THIS_FILE   "pjsua_opt.c"
-
-
-const char *pjsua_inv_state_names[] =
-{
-    "NULL      ",
-    "CALLING   ",
-    "INCOMING  ",
-    "EARLY     ",
-    "CONNECTING",
-    "CONFIRMED ",
-    "DISCONNCTD",
-    "TERMINATED",
-};
-
-
-
-/* Show usage */
-static void usage(void)
-{
-    puts  ("Usage:");
-    puts  ("  pjsua [options]");
-    puts  ("");
-    puts  ("General options:");
-    puts  ("  --help              Display this help screen");
-    puts  ("  --version           Display version info");
-    puts  ("");
-    puts  ("Logging options:");
-    puts  ("  --config-file=file  Read the config/arguments from file.");
-    puts  ("  --log-file=fname    Log to filename (default stderr)");
-    puts  ("  --log-level=N       Set log max level to N (0(none) to 6(trace)) (default=5)");
-    puts  ("  --app-log-level=N   Set log max level for stdout display (default=4)");
-    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       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  ("Transport Options:");
-    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]  Resolve local IP with the specified STUN servers");
-    puts  ("");
-    puts  ("Media Options:");
-    puts  ("  --add-codec=name    Manually add codec (default is to enable all)");
-    puts  ("  --clock-rate=N      Override sound device clock rate");
-    puts  ("  --null-audio        Use NULL audio device");
-    puts  ("  --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  ("  --rtp-port=N        Base port to try for RTP (default=4000)");
-    puts  ("  --complexity=N      Specify encoding complexity (0-10, default=none(-1))");
-    puts  ("  --quality=N         Specify encoding quality (0-10, default=4)");
-    puts  ("  --ptime=MSEC        Override codec ptime to MSEC (default=specific)");
-    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  ("  --max-calls=N       Maximum number of concurrent calls (default:4, max:255)");
-    puts  ("  --uas-refresh=N     Interval in UAS to send re-INVITE (default:-1)");
-    puts  ("  --uas-duration=N    Maximum duration of incoming call (default:-1)");
-    puts  ("");
-    fflush(stdout);
-}
-
-
-
-/*
- * Verify that valid SIP url is given.
- */
-PJ_DEF(pj_status_t) pjsua_verify_sip_url(const char *c_url)
-{
-    pjsip_uri *p;
-    pj_pool_t *pool;
-    char *url;
-    int len = (c_url ? pj_ansi_strlen(c_url) : 0);
-
-    if (!len) return -1;
-
-    pool = pj_pool_create(&pjsua.cp.factory, "check%p", 1024, 0, NULL);
-    if (!pool) return -1;
-
-    url = pj_pool_alloc(pool, len+1);
-    pj_ansi_strcpy(url, c_url);
-
-    p = pjsip_parse_uri(pool, url, len, 0);
-    if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
-	p = NULL;
-
-    pj_pool_release(pool);
-    return p ? 0 : -1;
-}
-
-
-/*
- * Read command arguments from config file.
- */
-static int read_config_file(pj_pool_t *pool, const char *filename, 
-			    int *app_argc, char ***app_argv)
-{
-    int i;
-    FILE *fhnd;
-    char line[200];
-    int argc = 0;
-    char **argv;
-    enum { MAX_ARGS = 64 };
-
-    /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
-    argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
-    argv[argc++] = *app_argv[0];
-
-    /* Open config file. */
-    fhnd = fopen(filename, "rt");
-    if (!fhnd) {
-	PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
-	fflush(stdout);
-	return -1;
-    }
-
-    /* Scan tokens in the file. */
-    while (argc < MAX_ARGS && !feof(fhnd)) {
-	char *token, *p = line;
-
-	if (fgets(line, sizeof(line), fhnd) == NULL) break;
-
-	for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS; 
-	     token = strtok(NULL, " \t\r\n"))
-	{
-	    int token_len;
-	    
-	    if (!token) break;
-	    if (*token == '#') break;
-
-	    token_len = strlen(token);
-	    if (!token_len)
-		continue;
-	    argv[argc] = pj_pool_alloc(pool, token_len+1);
-	    pj_memcpy(argv[argc], token, token_len+1);
-	    ++argc;
-	}
-    }
-
-    /* Copy arguments from command line */
-    for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
-	argv[argc++] = (*app_argv)[i];
-
-    if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
-	PJ_LOG(1,(THIS_FILE, 
-		  "Too many arguments specified in cmd line/config file"));
-	fflush(stdout);
-	fclose(fhnd);
-	return -1;
-    }
-
-    fclose(fhnd);
-
-    /* Assign the new command line back to the original command line. */
-    *app_argc = argc;
-    *app_argv = argv;
-    return 0;
-
-}
-
-static int my_atoi(const char *cs)
-{
-    pj_str_t s;
-    return pj_strtoul(pj_cstr(&s, cs));
-}
-
-
-/* Parse arguments. */
-PJ_DEF(pj_status_t) pjsua_parse_args(int argc, char *argv[],
-				     pjsua_config *cfg,
-				     pj_str_t *uri_to_call)
-{
-    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_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR,
-	   OPT_REG_TIMEOUT, OPT_ID, OPT_CONTACT, 
-	   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_LOOP,
-	   OPT_AUTO_CONF, OPT_CLOCK_RATE,
-	   OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC,
-	   OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME,
-	   OPT_NEXT_ACCOUNT, OPT_MAX_CALLS, OPT_UAS_REFRESH,
-	   OPT_UAS_DURATION,
-    };
-    struct pj_getopt_option long_options[] = {
-	{ "config-file",1, 0, OPT_CONFIG_FILE},
-	{ "log-file",	1, 0, OPT_LOG_FILE},
-	{ "log-level",	1, 0, OPT_LOG_LEVEL},
-	{ "app-log-level",1,0,OPT_APP_LOG_LEVEL},
-	{ "help",	0, 0, OPT_HELP},
-	{ "version",	0, 0, OPT_VERSION},
-	{ "clock-rate",	1, 0, OPT_CLOCK_RATE},
-	{ "null-audio", 0, 0, OPT_NULL_AUDIO},
-	{ "local-port", 1, 0, OPT_LOCAL_PORT},
-	{ "proxy",	1, 0, OPT_PROXY},
-	{ "outbound",	1, 0, OPT_OUTBOUND_PROXY},
-	{ "registrar",	1, 0, OPT_REGISTRAR},
-	{ "reg-timeout",1, 0, OPT_REG_TIMEOUT},
-	{ "id",		1, 0, OPT_ID},
-	{ "contact",	1, 0, OPT_CONTACT},
-	{ "realm",	1, 0, OPT_REALM},
-	{ "username",	1, 0, OPT_USERNAME},
-	{ "password",	1, 0, OPT_PASSWORD},
-	{ "use-stun1",  1, 0, OPT_USE_STUN1},
-	{ "use-stun2",  1, 0, OPT_USE_STUN2},
-	{ "add-buddy",  1, 0, OPT_ADD_BUDDY},
-	{ "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
-	{ "no-presence", 0, 0, OPT_NO_PRESENCE},
-	{ "auto-answer",1, 0, OPT_AUTO_ANSWER},
-	{ "auto-hangup",1, 0, OPT_AUTO_HANGUP},
-	{ "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},
-	{ "rtp-port",	1, 0, OPT_RTP_PORT},
-	{ "add-codec",  1, 0, OPT_ADD_CODEC},
-	{ "complexity",	1, 0, OPT_COMPLEXITY},
-	{ "quality",	1, 0, OPT_QUALITY},
-	{ "ptime",      1, 0, OPT_PTIME},
-	{ "next-account",0,0, OPT_NEXT_ACCOUNT},
-	{ "max-calls",	1, 0, OPT_MAX_CALLS},
-	{ "uas-refresh",1, 0, OPT_UAS_REFRESH},
-	{ "uas-duration",1,0, OPT_UAS_DURATION},
-	{ NULL, 0, 0, 0}
-    };
-    pj_status_t status;
-    pjsua_acc_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) 
-    {
-	switch (c) {
-	case OPT_CONFIG_FILE:
-	    config_file = pj_optarg;
-	    break;
-	}
-	if (config_file)
-	    break;
-    }
-
-    if (config_file) {
-	status = read_config_file(pjsua.pool, config_file, &argc, &argv);
-	if (status != 0)
-	    return status;
-    }
-
-    cfg->acc_cnt = 0;
-    cur_acc = &cfg->acc_config[0];
-
-
-    /* Reinitialize and re-run pj_getopt again, possibly with new arguments
-     * read from config file.
-     */
-    pj_optind = 0;
-    while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
-	char *p;
-	pj_str_t tmp;
-	long lval;
-
-	switch (c) {
-
-	case OPT_LOG_FILE:
-	    cfg->log_filename = pj_str(pj_optarg);
-	    break;
-
-	case OPT_LOG_LEVEL:
-	    c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
-	    if (c < 0 || c > 6) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: expecting integer value 0-6 "
-			  "for --log-level"));
-		return PJ_EINVAL;
-	    }
-	    cfg->log_level = c;
-	    pj_log_set_level( c );
-	    break;
-
-	case OPT_APP_LOG_LEVEL:
-	    cfg->app_log_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
-	    if (cfg->app_log_level < 0 || cfg->app_log_level > 6) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: expecting integer value 0-6 "
-			  "for --app-log-level"));
-		return PJ_EINVAL;
-	    }
-	    break;
-
-	case OPT_HELP:
-	    usage();
-	    return PJ_EINVAL;
-
-	case OPT_VERSION:   /* version */
-	    pj_dump_config();
-	    return PJ_EINVAL;
-
-	case OPT_NULL_AUDIO:
-	    cfg->null_audio = 1;
-	    break;
-
-	case OPT_CLOCK_RATE:
-	    lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
-	    if (lval < 8000 || lval > 48000) {
-		PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
-				     "8000-48000 for clock rate"));
-		return PJ_EINVAL;
-	    }
-	    cfg->clock_rate = lval; 
-	    break;
-
-	case OPT_LOCAL_PORT:   /* local-port */
-	    lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
-	    if (lval < 1 || lval > 65535) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: expecting integer value for "
-			  "--local-port"));
-		return PJ_EINVAL;
-	    }
-	    cfg->udp_port = (pj_uint16_t)lval;
-	    break;
-
-	case OPT_PROXY:   /* proxy */
-	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: invalid SIP URL '%s' "
-			  "in proxy argument", pj_optarg));
-		return PJ_EINVAL;
-	    }
-	    cur_acc->proxy = pj_str(pj_optarg);
-	    break;
-
-	case OPT_OUTBOUND_PROXY:   /* outbound proxy */
-	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: invalid SIP URL '%s' "
-			  "in outbound proxy argument", pj_optarg));
-		return PJ_EINVAL;
-	    }
-	    cfg->outbound_proxy = pj_str(pj_optarg);
-	    break;
-
-	case OPT_REGISTRAR:   /* registrar */
-	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: invalid SIP URL '%s' in "
-			  "registrar argument", pj_optarg));
-		return PJ_EINVAL;
-	    }
-	    cur_acc->reg_uri = pj_str(pj_optarg);
-	    break;
-
-	case OPT_REG_TIMEOUT:   /* reg-timeout */
-	    cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
-	    if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: invalid value for --reg-timeout "
-			  "(expecting 1-3600)"));
-		return PJ_EINVAL;
-	    }
-	    break;
-
-	case OPT_ID:   /* id */
-	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: invalid SIP URL '%s' "
-			  "in local id argument", pj_optarg));
-		return PJ_EINVAL;
-	    }
-	    cur_acc->id = pj_str(pj_optarg);
-	    break;
-
-	case OPT_CONTACT:   /* contact */
-	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: invalid SIP URL '%s' "
-			  "in contact argument", pj_optarg));
-		return PJ_EINVAL;
-	    }
-	    cur_acc->contact = pj_str(pj_optarg);
-	    break;
-
-	case OPT_NEXT_ACCOUNT: /* Add more account. */
-	    cfg->acc_cnt++;
-	    cur_acc = &cfg->acc_config[cfg->acc_cnt - 1];
-	    break;
-
-	case OPT_USERNAME:   /* Default authentication user */
-	    cur_acc->cred_info[0].username = pj_str(pj_optarg);
-	    break;
-
-	case OPT_REALM:	    /* Default authentication realm. */
-	    cur_acc->cred_info[0].realm = pj_str(pj_optarg);
-	    break;
-
-	case OPT_PASSWORD:   /* authentication password */
-	    cur_acc->cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
-	    cur_acc->cred_info[0].data = pj_str(pj_optarg);
-	    break;
-
-	case OPT_USE_STUN1:   /* STUN server 1 */
-	    p = pj_ansi_strchr(pj_optarg, ':');
-	    if (p) {
-		*p = '\0';
-		cfg->stun_srv1 = pj_str(pj_optarg);
-		cfg->stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1));
-		if (cfg->stun_port1 < 1 || cfg->stun_port1 > 65535) {
-		    PJ_LOG(1,(THIS_FILE, 
-			      "Error: expecting port number with "
-			      "option --use-stun1"));
-		    return PJ_EINVAL;
-		}
-	    } else {
-		cfg->stun_port1 = 3478;
-		cfg->stun_srv1 = pj_str(pj_optarg);
-	    }
-	    break;
-
-	case OPT_USE_STUN2:   /* STUN server 2 */
-	    p = pj_ansi_strchr(pj_optarg, ':');
-	    if (p) {
-		*p = '\0';
-		cfg->stun_srv2 = pj_str(pj_optarg);
-		cfg->stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1));
-		if (cfg->stun_port2 < 1 || cfg->stun_port2 > 65535) {
-		    PJ_LOG(1,(THIS_FILE, 
-			      "Error: expecting port number with "
-			      "option --use-stun2"));
-		    return PJ_EINVAL;
-		}
-	    } else {
-		cfg->stun_port2 = 3478;
-		cfg->stun_srv2 = pj_str(pj_optarg);
-	    }
-	    break;
-
-	case OPT_ADD_BUDDY: /* Add to buddy list. */
-	    if (pjsua_verify_sip_url(pj_optarg) != 0) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: invalid URL '%s' in "
-			  "--add-buddy option", pj_optarg));
-		return -1;
-	    }
-	    if (cfg->buddy_cnt == PJSUA_MAX_BUDDIES) {
-		PJ_LOG(1,(THIS_FILE, 
-			  "Error: too many buddies in buddy list."));
-		return -1;
-	    }
-	    cfg->buddy_uri[cfg->buddy_cnt++] = pj_str(pj_optarg);
-	    break;
-
-	case OPT_AUTO_PLAY:
-	    cfg->auto_play = 1;
-	    break;
-
-	case OPT_AUTO_LOOP:
-	    cfg->auto_loop = 1;
-	    break;
-
-	case OPT_AUTO_CONF:
-	    cfg->auto_conf = 1;
-	    break;
-
-	case OPT_PLAY_FILE:
-	    cfg->wav_file = pj_str(pj_optarg);
-	    break;
-
-	case OPT_RTP_PORT:
-	    cfg->start_rtp_port = my_atoi(pj_optarg);
-	    if (cfg->start_rtp_port < 1 || cfg->start_rtp_port > 65535) {
-		PJ_LOG(1,(THIS_FILE,
-			  "Error: rtp-port argument value "
-			  "(expecting 1-65535"));
-		return -1;
-	    }
-	    break;
-
-	case OPT_ADD_CODEC:
-	    cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
-	    break;
-
-	case OPT_COMPLEXITY:
-	    cfg->complexity = my_atoi(pj_optarg);
-	    if (cfg->complexity < 0 || cfg->complexity > 10) {
-		PJ_LOG(1,(THIS_FILE,
-			  "Error: invalid --complexity (expecting 0-10"));
-		return -1;
-	    }
-	    break;
-
-	case OPT_QUALITY:
-	    cfg->quality = my_atoi(pj_optarg);
-	    if (cfg->quality < 0 || cfg->quality > 10) {
-		PJ_LOG(1,(THIS_FILE,
-			  "Error: invalid --quality (expecting 0-10"));
-		return -1;
-	    }
-	    break;
-
-	case OPT_PTIME:
-	    cfg->ptime = my_atoi(pj_optarg);
-	    if (cfg->ptime < 10 || cfg->ptime > 1000) {
-		PJ_LOG(1,(THIS_FILE,
-			  "Error: invalid --ptime option"));
-		return -1;
-	    }
-	    break;
-
-	case OPT_AUTO_ANSWER:
-	    cfg->auto_answer = my_atoi(pj_optarg);
-	    if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
-		PJ_LOG(1,(THIS_FILE,
-			  "Error: invalid code in --auto-answer "
-			  "(expecting 100-699"));
-		return -1;
-	    }
-	    break;
-
-	case OPT_MAX_CALLS:
-	    cfg->max_calls = my_atoi(pj_optarg);
-	    if (cfg->max_calls < 1 || cfg->max_calls > 255) {
-		PJ_LOG(1,(THIS_FILE,"Too many calls for max-calls (1-255)"));
-		return -1;
-	    }
-	    break;
-
-	case OPT_UAS_REFRESH:
-	    cfg->uas_refresh = my_atoi(pj_optarg);
-	    if (cfg->uas_refresh < 1) {
-		PJ_LOG(1,(THIS_FILE,
-			  "Invalid value for --uas-refresh (must be >0)"));
-		return -1;
-	    }
-	    break;
-
-	case OPT_UAS_DURATION:
-	    cfg->uas_duration = my_atoi(pj_optarg);
-	    if (cfg->uas_duration < 1) {
-		PJ_LOG(1,(THIS_FILE,
-			  "Invalid value for --uas-duration "
-			  "(must be >0)"));
-		return -1;
-	    }
-	    break;
-	}
-    }
-
-    if (pj_optind != argc) {
-	pj_str_t uri_arg;
-
-	if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
-	    PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
-	    return -1;
-	}
-	uri_arg = pj_str(argv[pj_optind]);
-	if (uri_to_call)
-	    *uri_to_call = uri_arg;
-	pj_optind++;
-
-	/* Add URI to call to buddy list if it's not already there */
-	for (i=0; i<cfg->buddy_cnt; ++i) {
-	    if (pj_stricmp(&cfg->buddy_uri[i], &uri_arg)==0)
-		break;
-	}
-	if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
-	    cfg->buddy_uri[cfg->buddy_cnt++] = uri_arg;
-	}
-
-    } else {
-	if (uri_to_call)
-	    uri_to_call->slen = 0;
-    }
-
-    if (pj_optind != argc) {
-	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;
-    }
-
-    return PJ_SUCCESS;
-}
-
-
-
-static void print_call(const char *title,
-		       int call_index, 
-		       char *buf, pj_size_t size)
-{
-    int len;
-    pjsip_inv_session *inv = pjsua.calls[call_index].inv;
-    pjsip_dialog *dlg = inv->dlg;
-    char userinfo[128];
-
-    /* Dump invite sesion info. */
-
-    len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
-    if (len < 1)
-	pj_ansi_strcpy(userinfo, "<--uri too long-->");
-    else
-	userinfo[len] = '\0';
-    
-    len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
-			   title,
-			   pjsua_inv_state_names[inv->state],
-			   userinfo);
-    if (len < 1 || len >= (int)size) {
-	pj_ansi_strcpy(buf, "<--uri too long-->");
-	len = 18;
-    } else
-	buf[len] = '\0';
-}
-
-static const char *good_number(char *buf, pj_int32_t val)
-{
-    if (val < 1000) {
-	pj_ansi_sprintf(buf, "%d", val);
-    } else if (val < 1000000) {
-	pj_ansi_sprintf(buf, "%d.%dK", 
-			val / 1000,
-			(val % 1000) / 100);
-    } else {
-	pj_ansi_sprintf(buf, "%d.%02dM", 
-			val / 1000000,
-			(val % 1000000) / 10000);
-    }
-
-    return buf;
-}
-
-static void dump_media_session(const char *indent, 
-			       char *buf, unsigned maxlen,
-			       pjmedia_session *session)
-{
-    unsigned i;
-    char *p = buf, *end = buf+maxlen;
-    int len;
-    pjmedia_session_info info;
-
-    pjmedia_session_get_info(session, &info);
-
-    for (i=0; i<info.stream_cnt; ++i) {
-	pjmedia_rtcp_stat stat;
-	const char *rem_addr;
-	int rem_port;
-	const char *dir;
-	char last_update[40];
-	char packets[16], bytes[16], ipbytes[16];
-	pj_time_val now;
-
-	pjmedia_session_get_stream_stat(session, i, &stat);
-	rem_addr = pj_inet_ntoa(info.stream_info[i].rem_addr.sin_addr);
-	rem_port = pj_ntohs(info.stream_info[i].rem_addr.sin_port);
-
-	if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING)
-	    dir = "sendonly";
-	else if (info.stream_info[i].dir == PJMEDIA_DIR_DECODING)
-	    dir = "recvonly";
-	else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING_DECODING)
-	    dir = "sendrecv";
-	else
-	    dir = "inactive";
-
-	
-	len = pj_ansi_snprintf(buf, end-p, 
-		  "%s  #%d %.*s @%dKHz, %s, peer=%s:%d",
-		  indent, i,
-		  info.stream_info[i].fmt.encoding_name.slen,
-		  info.stream_info[i].fmt.encoding_name.ptr,
-		  info.stream_info[i].fmt.clock_rate / 1000,
-		  dir,
-		  rem_addr, rem_port);
-	if (len < 1 || len > end-p) {
-	    *p = '\0';
-	    return;
-	}
-
-	p += len;
-	*p++ = '\n';
-	*p = '\0';
-
-	if (stat.rx.update_cnt == 0)
-	    strcpy(last_update, "never");
-	else {
-	    pj_gettimeofday(&now);
-	    PJ_TIME_VAL_SUB(now, stat.rx.update);
-	    sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
-		    now.sec / 3600,
-		    (now.sec % 3600) / 60,
-		    now.sec % 60,
-		    now.msec);
-	}
-
-	len = pj_ansi_snprintf(p, end-p,
-	       "%s     RX pt=%d, stat last update: %s\n"
-	       "%s        total %spkt %sB (%sB +IP hdr)\n"
-	       "%s        pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
-	       "%s              (msec)    min     avg     max     last\n"
-	       "%s        loss period: %7.3f %7.3f %7.3f %7.3f\n"
-	       "%s        jitter     : %7.3f %7.3f %7.3f %7.3f%s",
-	       indent, info.stream_info[i].fmt.pt,
-	       last_update,
-	       indent,
-	       good_number(packets, stat.rx.pkt),
-	       good_number(bytes, stat.rx.bytes),
-	       good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32),
-	       indent,
-	       stat.rx.loss,
-	       stat.rx.loss * 100.0 / stat.rx.pkt,
-	       stat.rx.dup, 
-	       stat.rx.dup * 100.0 / stat.rx.pkt,
-	       stat.rx.reorder, 
-	       stat.rx.reorder * 100.0 / stat.rx.pkt,
-	       indent, indent,
-	       stat.rx.loss_period.min / 1000.0, 
-	       stat.rx.loss_period.avg / 1000.0, 
-	       stat.rx.loss_period.max / 1000.0,
-	       stat.rx.loss_period.last / 1000.0,
-	       indent,
-	       stat.rx.jitter.min / 1000.0,
-	       stat.rx.jitter.avg / 1000.0,
-	       stat.rx.jitter.max / 1000.0,
-	       stat.rx.jitter.last / 1000.0,
-	       ""
-	       );
-
-	if (len < 1 || len > end-p) {
-	    *p = '\0';
-	    return;
-	}
-
-	p += len;
-	*p++ = '\n';
-	*p = '\0';
-	
-	if (stat.tx.update_cnt == 0)
-	    strcpy(last_update, "never");
-	else {
-	    pj_gettimeofday(&now);
-	    PJ_TIME_VAL_SUB(now, stat.tx.update);
-	    sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago",
-		    now.sec / 3600,
-		    (now.sec % 3600) / 60,
-		    now.sec % 60,
-		    now.msec);
-	}
-
-	len = pj_ansi_snprintf(p, end-p,
-	       "%s     TX pt=%d, ptime=%dms, stat last update: %s\n"
-	       "%s        total %spkt %sB (%sB +IP hdr)\n"
-	       "%s        pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)\n"
-	       "%s              (msec)    min     avg     max     last\n"
-	       "%s        loss period: %7.3f %7.3f %7.3f %7.3f\n"
-	       "%s        jitter     : %7.3f %7.3f %7.3f %7.3f%s",
-	       indent,
-	       info.stream_info[i].tx_pt,
-	       info.stream_info[i].param->info.frm_ptime *
-		info.stream_info[i].param->setting.frm_per_pkt,
-	       last_update,
-
-	       indent,
-	       good_number(packets, stat.tx.pkt),
-	       good_number(bytes, stat.tx.bytes),
-	       good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32),
-
-	       indent,
-	       stat.tx.loss,
-	       stat.tx.loss * 100.0 / stat.tx.pkt,
-	       stat.tx.dup, 
-	       stat.tx.dup * 100.0 / stat.tx.pkt,
-	       stat.tx.reorder, 
-	       stat.tx.reorder * 100.0 / stat.tx.pkt,
-
-	       indent, indent,
-	       stat.tx.loss_period.min / 1000.0, 
-	       stat.tx.loss_period.avg / 1000.0, 
-	       stat.tx.loss_period.max / 1000.0,
-	       stat.tx.loss_period.last / 1000.0,
-	       indent,
-	       stat.tx.jitter.min / 1000.0,
-	       stat.tx.jitter.avg / 1000.0,
-	       stat.tx.jitter.max / 1000.0,
-	       stat.tx.jitter.last / 1000.0,
-	       ""
-	       );
-
-	if (len < 1 || len > end-p) {
-	    *p = '\0';
-	    return;
-	}
-
-	p += len;
-	*p++ = '\n';
-	*p = '\0';
-
-	len = pj_ansi_snprintf(p, end-p,
-	       "%s    RTT msec       : %7.3f %7.3f %7.3f %7.3f", 
-	       indent,
-	       stat.rtt.min / 1000.0,
-	       stat.rtt.avg / 1000.0,
-	       stat.rtt.max / 1000.0,
-	       stat.rtt.last / 1000.0
-	       );
-	if (len < 1 || len > end-p) {
-	    *p = '\0';
-	    return;
-	}
-
-	p += len;
-	*p++ = '\n';
-	*p = '\0';
-    }
-}
-
-PJ_DEF(void) pjsua_call_dump(int call_index, int with_media, 
-			     char *buffer, unsigned maxlen,
-			     const char *indent)
-{
-    pjsua_call *call = &pjsua.calls[call_index];
-    pj_time_val duration, res_delay, con_delay;
-    char tmp[128];
-    char *p, *end;
-    int len;
-
-    *buffer = '\0';
-    p = buffer;
-    end = buffer + maxlen;
-    len = 0;
-
-    PJ_ASSERT_ON_FAIL(call_index >= 0 && 
-		      call_index < PJ_ARRAY_SIZE(pjsua.calls), return);
-
-    if (call->inv == NULL)
-	return;
-
-    print_call(indent, call_index, tmp, sizeof(tmp));
-    
-    len = pj_ansi_strlen(tmp);
-    pj_ansi_strcpy(buffer, tmp);
-
-    p += len;
-    *p++ = '\r';
-    *p++ = '\n';
-
-    /* Calculate call duration */
-    if (call->inv->state >= PJSIP_INV_STATE_CONFIRMED) {
-	pj_gettimeofday(&duration);
-	PJ_TIME_VAL_SUB(duration, call->conn_time);
-	con_delay = call->conn_time;
-	PJ_TIME_VAL_SUB(con_delay, call->start_time);
-    } else {
-	duration.sec = duration.msec = 0;
-	con_delay.sec = con_delay.msec = 0;
-    }
-
-    /* Calculate first response delay */
-    if (call->inv->state >= PJSIP_INV_STATE_EARLY) {
-	res_delay = call->res_time;
-	PJ_TIME_VAL_SUB(res_delay, call->start_time);
-    } else {
-	res_delay.sec = res_delay.msec = 0;
-    }
-
-    /* Print duration */
-    len = pj_ansi_snprintf(p, end-p, 
-		           "%s  Call time: %02dh:%02dm:%02ds, "
-		           "1st res in %d ms, conn in %dms",
-			   indent,
-		           (duration.sec / 3600),
-		           ((duration.sec % 3600)/60),
-		           (duration.sec % 60),
-		           PJ_TIME_VAL_MSEC(res_delay), 
-		           PJ_TIME_VAL_MSEC(con_delay));
-    
-    if (len > 0 && len < end-p) {
-	p += len;
-	*p++ = '\n';
-	*p = '\0';
-    }
-
-    /* Dump session statistics */
-    if (with_media && call->session)
-	dump_media_session(indent, p, end-p, call->session);
-
-}
-
-/*
- * Dump application states.
- */
-PJ_DEF(void) pjsua_dump(pj_bool_t detail)
-{
-    unsigned old_decor;
-    char buf[1024];
-
-    PJ_LOG(3,(THIS_FILE, "Start dumping application states:"));
-
-    old_decor = pj_log_get_decor();
-    pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
-
-    if (detail)
-	pj_dump_config();
-
-    pjsip_endpt_dump(pjsua.endpt, detail);
-    pjmedia_endpt_dump(pjsua.med_endpt);
-    pjsip_tsx_layer_dump(detail);
-    pjsip_ua_dump(detail);
-
-
-    /* Dump all invite sessions: */
-    PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
-
-    if (pjsua.call_cnt == 0) {
-
-	PJ_LOG(3,(THIS_FILE, "  - no sessions -"));
-
-    } else {
-	unsigned i;
-
-	for (i=0; i<pjsua.config.max_calls; ++i) {
-	    if (pjsua.calls[i].inv) {
-		pjsua_call_dump(i, detail, buf, sizeof(buf), "  ");
-		PJ_LOG(3,(THIS_FILE, "%s", buf));
-	    }
-	}
-    }
-
-    /* Dump presence status */
-    pjsua_pres_dump(detail);
-
-    pj_log_set_decor(old_decor);
-    PJ_LOG(3,(THIS_FILE, "Dump complete"));
-}
-
-
-/*
- * Load settings.
- */
-PJ_DECL(pj_status_t) pjsua_load_settings(const char *filename,
-					 pjsua_config *cfg,
-					 pj_str_t *uri_to_call)
-{
-    int argc = 3;
-    char *argv[4] = { "pjsua", "--config-file", NULL, NULL};
-
-    argv[2] = (char*)filename;
-    return pjsua_parse_args(argc, argv, cfg, uri_to_call);
-}
-
-
-/*
- * Save account settings
- */
-static void save_account_settings(int acc_index, pj_str_t *result)
-{
-    char line[128];
-    pjsua_acc_config *acc_cfg = &pjsua.config.acc_config[acc_index];
-
-    
-    pj_ansi_sprintf(line, "#\n# Account %d:\n#\n", acc_index);
-    pj_strcat2(result, line);
-
-
-    /* Identity */
-    if (acc_cfg->id.slen) {
-	pj_ansi_sprintf(line, "--id %.*s\n", 
-			(int)acc_cfg->id.slen, 
-			acc_cfg->id.ptr);
-	pj_strcat2(result, line);
-    }
-
-    /* Registrar server */
-    if (acc_cfg->reg_uri.slen) {
-	pj_ansi_sprintf(line, "--registrar %.*s\n",
-			      (int)acc_cfg->reg_uri.slen,
-			      acc_cfg->reg_uri.ptr);
-	pj_strcat2(result, line);
-
-	pj_ansi_sprintf(line, "--reg-timeout %u\n",
-			      acc_cfg->reg_timeout);
-	pj_strcat2(result, line);
-    }
-
-
-    /* Proxy */
-    if (acc_cfg->proxy.slen) {
-	pj_ansi_sprintf(line, "--proxy %.*s\n",
-			      (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);
-    }
-
-}
-
-
-
-/*
- * Dump settings.
- */
-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];
-
-    PJ_UNUSED_ARG(max);
-
-    if (config == NULL)
-	config = &pjsua.config;
-
-    cfg.ptr = buf;
-    cfg.slen = 0;
-
-
-    /* Logging. */
-    pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
-    pj_ansi_sprintf(line, "--log-level %d\n",
-		    config->log_level);
-    pj_strcat2(&cfg, line);
-
-    pj_ansi_sprintf(line, "--app-log-level %d\n",
-		    config->app_log_level);
-    pj_strcat2(&cfg, line);
-
-    if (config->log_filename.slen) {
-	pj_ansi_sprintf(line, "--log-file %s\n",
-			config->log_filename.ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-
-    /* Save account settings. */
-    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);
-    }
-
-
-    /* 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);
-    }
-
-    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);
-    }
-
-
-    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);
-    }
-    /* Media clock rate. */
-    if (config->clock_rate) {
-	pj_ansi_sprintf(line, "--clock-rate %d\n",
-			config->clock_rate);
-	pj_strcat2(&cfg, line);
-    }
-
-
-    /* Encoding quality and complexity */
-    if (config->quality > 0) {
-	pj_ansi_sprintf(line, "--quality %d\n",
-			config->quality);
-	pj_strcat2(&cfg, line);
-    }
-    if (config->complexity > 0) {
-	pj_ansi_sprintf(line, "--complexity %d\n",
-			config->complexity);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* ptime */
-    if (config->ptime) {
-	pj_ansi_sprintf(line, "--ptime %d\n",
-			config->ptime);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Start RTP port. */
-    pj_ansi_sprintf(line, "--rtp-port %d\n",
-		    config->start_rtp_port);
-    pj_strcat2(&cfg, line);
-
-    /* Add codec. */
-    for (i=0; i<config->codec_cnt; ++i) {
-	pj_ansi_sprintf(line, "--add-codec %s\n",
-		    config->codec_arg[i].ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-    pj_strcat2(&cfg, "#\n# User agent:\n#\n");
-
-    /* Auto-answer. */
-    if (config->auto_answer != 0) {
-	pj_ansi_sprintf(line, "--auto-answer %d\n",
-			config->auto_answer);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Max calls. */
-    pj_ansi_sprintf(line, "--max-calls %d\n",
-		    config->max_calls);
-    pj_strcat2(&cfg, line);
-
-    /* Uas-refresh. */
-    if (config->uas_refresh > 0) {
-	pj_ansi_sprintf(line, "--uas-refresh %d\n",
-			config->uas_refresh);
-	pj_strcat2(&cfg, line);
-    }
-
-    /* Uas-duration. */
-    if (config->uas_duration > 0) {
-	pj_ansi_sprintf(line, "--uas-duration %d\n",
-			config->uas_duration);
-	pj_strcat2(&cfg, line);
-    }
-
-    pj_strcat2(&cfg, "#\n# Buddies:\n#\n");
-
-    /* Add buddies. */
-    for (i=0; i<config->buddy_cnt; ++i) {
-	pj_ansi_sprintf(line, "--add-buddy %.*s\n",
-			      (int)config->buddy_uri[i].slen,
-			      config->buddy_uri[i].ptr);
-	pj_strcat2(&cfg, line);
-    }
-
-
-    *(cfg.ptr + cfg.slen) = '\0';
-    return cfg.slen;
-}
-
-/*
- * Save settings.
- */
-PJ_DEF(pj_status_t) pjsua_save_settings(const char *filename,
-					const pjsua_config *config)
-{
-    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(config, cfg.ptr, 3800);
-    if (cfg.slen < 1) {
-	pj_pool_release(pool);
-	return PJ_ENOMEM;
-    }
-
-
-    /* Write to file. */
-    fhnd = fopen(filename, "wt");
-    if (!fhnd) {
-	pj_pool_release(pool);
-	return pj_get_os_error();
-    }
-
-    fwrite(cfg.ptr, cfg.slen, 1, fhnd);
-    fclose(fhnd);
-
-    pj_pool_release(pool);
-    return PJ_SUCCESS;
-}
-
-/**
- * Get pjsua running config.
- */
-PJ_DEF(void) pjsua_get_config(pj_pool_t *pool,
-			      pjsua_config *cfg)
-{
-    unsigned i;
-
-    pjsua_copy_config(pool, cfg, &pjsua.config);
-
-    /* Compact buddy uris. */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.config.buddy_uri)-1; ++i) {
-	if (pjsua.config.buddy_uri[i].slen == 0) {
-	    unsigned j;
-
-	    for (j=i+1; j<PJ_ARRAY_SIZE(pjsua.config.buddy_uri); ++j) {
-		if (pjsua.config.buddy_uri[j].slen != 0)
-		    break;
-	    }
-	
-	    if (j == PJ_ARRAY_SIZE(pjsua.config.buddy_uri))
-		break;
-	    else
-		pjsua.config.buddy_uri[i] = pjsua.config.buddy_uri[j];
-	}
-    }
-
-    /* Compact accounts. */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.config.acc_config)-1; ++i) {
-
-	if (pjsua.acc[i].valid == PJ_FALSE || pjsua.acc[i].auto_gen) {
-	    unsigned j;
-
-	    for (j=i+1; j<PJ_ARRAY_SIZE(pjsua.config.acc_config); ++j) {
-		if (pjsua.acc[j].valid && !pjsua.acc[j].auto_gen)
-		    break;
-	    }
-	
-	    if (j == PJ_ARRAY_SIZE(pjsua.config.acc_config)) {
-		break;
-	    } else {
-		pj_memcpy(&pjsua.config.acc_config[i] ,
-			  &pjsua.config.acc_config[j],
-			  sizeof(pjsua_acc_config));
-	    }
-	}
-
-    }
-
-    /* Remove auto generated account from config */
-    for (i=0; i<PJ_ARRAY_SIZE(pjsua.config.acc_config); ++i) {
-	if (pjsua.acc[i].auto_gen)
-	    --cfg->acc_cnt;
-    }
-}
-
-
-
-/*****************************************************************************
- * 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 logging_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 logging_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_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()		*/
-    &logging_on_rx_msg,			/* on_rx_request()	*/
-    &logging_on_rx_msg,			/* on_rx_response()	*/
-    &logging_on_tx_msg,			/* on_tx_request.	*/
-    &logging_on_tx_msg,			/* on_tx_response()	*/
-    NULL,				/* on_tsx_state()	*/
-
-};
-
