summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-06-13 22:57:13 +0000
committerBenny Prijono <bennylp@teluu.com>2006-06-13 22:57:13 +0000
commit5263415f8300e09213e4dd3b684d3c16b8263f9f (patch)
tree619e3c631a84a5791e4fa9fdf56601afcc0830a9
parent94e741d055535156504bfbb182c8b63412299fb9 (diff)
-- REWRITE OF PJSUA API --
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@503 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r--pjlib/src/pj/config.c2
-rw-r--r--pjsip-apps/build/activex-pjsua.dsp2
-rw-r--r--pjsip-apps/build/pjsua.dsp4
-rw-r--r--pjsip-apps/src/pjsua/main.c58
-rw-r--r--pjsip-apps/src/pjsua/pjsua.c2143
-rw-r--r--pjsip/build/pjsua_lib.dsp16
-rw-r--r--pjsip/include/pjsua-lib/pjsua.h1956
-rw-r--r--pjsip/include/pjsua-lib/pjsua_console_app.h28
-rw-r--r--pjsip/include/pjsua-lib/pjsua_internal.h359
-rw-r--r--pjsip/src/pjsua-lib/pjsua_acc.c776
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c2591
-rw-r--r--pjsip/src/pjsua-lib/pjsua_console_app.c926
-rw-r--r--pjsip/src/pjsua-lib/pjsua_core.c2113
-rw-r--r--pjsip/src/pjsua-lib/pjsua_im.c281
-rw-r--r--pjsip/src/pjsua-lib/pjsua_imp.h259
-rw-r--r--pjsip/src/pjsua-lib/pjsua_media.c1044
-rw-r--r--pjsip/src/pjsua-lib/pjsua_pres.c908
-rw-r--r--pjsip/src/pjsua-lib/pjsua_reg.c281
-rw-r--r--pjsip/src/pjsua-lib/pjsua_settings.c1435
19 files changed, 8971 insertions, 6211 deletions
diff --git a/pjlib/src/pj/config.c b/pjlib/src/pj/config.c
index e7fa7be9..2a6aefd3 100644
--- a/pjlib/src/pj/config.c
+++ b/pjlib/src/pj/config.c
@@ -21,7 +21,7 @@
#include <pj/ioqueue.h>
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)
{
diff --git a/pjsip-apps/build/activex-pjsua.dsp b/pjsip-apps/build/activex-pjsua.dsp
index 6791a89c..529e8115 100644
--- a/pjsip-apps/build/activex-pjsua.dsp
+++ b/pjsip-apps/build/activex-pjsua.dsp
@@ -323,7 +323,7 @@ SOURCE="..\src\activex-pjsua\app.h"
# End Source File
# Begin Source File
-SOURCE="..\src\activex-pjsua\pjsua.h"
+SOURCE="..\src\activex-pjsua\pjsua-structs.h"
# End Source File
# Begin Source File
diff --git a/pjsip-apps/build/pjsua.dsp b/pjsip-apps/build/pjsua.dsp
index d555f841..8697fb69 100644
--- a/pjsip-apps/build/pjsua.dsp
+++ b/pjsip-apps/build/pjsua.dsp
@@ -92,6 +92,10 @@ LINK32=link.exe
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"
diff --git a/pjsip-apps/src/pjsua/main.c b/pjsip-apps/src/pjsua/main.c
index b5774e73..7775687d 100644
--- a/pjsip-apps/src/pjsua/main.c
+++ b/pjsip-apps/src/pjsua/main.c
@@ -17,62 +17,24 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#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)
- return 1;
-
-
- /* Init pjsua */
- if (pjsua_init(&cfg, &console_callback) != PJ_SUCCESS)
+ if (app_init(argc, argv) != 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;
}
diff --git a/pjsip-apps/src/pjsua/pjsua.c b/pjsip-apps/src/pjsua/pjsua.c
new file mode 100644
index 00000000..666779eb
--- /dev/null
+++ b/pjsip-apps/src/pjsua/pjsua.c
@@ -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();
+}
diff --git a/pjsip/build/pjsua_lib.dsp b/pjsip/build/pjsua_lib.dsp
index d3f42ba7..3d2c8bcf 100644
--- a/pjsip/build/pjsua_lib.dsp
+++ b/pjsip/build/pjsua_lib.dsp
@@ -87,11 +87,11 @@ LIB32=link.exe -lib
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# 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
@@ -103,20 +103,12 @@ SOURCE="..\src\pjsua-lib\pjsua_im.c"
# End Source File
# 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
# Begin Group "Header Files"
@@ -127,7 +119,7 @@ SOURCE="..\include\pjsua-lib\pjsua.h"
# End Source File
# Begin Source File
-SOURCE="..\include\pjsua-lib\pjsua_console_app.h"
+SOURCE="..\include\pjsua-lib\pjsua_internal.h"
# End Source File
# End Group
# End Target
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index dba8560c..65e0cc72 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -56,7 +56,15 @@ PJ_BEGIN_DECL
* Max simultaneous calls.
*/
#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
@@ -64,236 +72,661 @@ PJ_BEGIN_DECL
* 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
+typedef struct pjsua_acc_config
{
- /** SIP URL for account ID (mandatory) */
+ /**
+ * 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;
+ /**
+ * 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];
- /** Default timeout (mandatory) */
- pj_int32_t reg_timeout;
+ /**
+ * 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. */
+ /**
+ * Number of credentials in the credential array.
+ */
unsigned cred_count;
- /** Array of credentials. */
- pjsip_cred_info cred_info[4];
+ /**
+ * 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;
/**
- * @see pjsua_acc_config
+ * Call this function to initialize account config with default values.
+ *
+ * @param cfg The account config to be initialized.
*/
-typedef struct pjsua_acc_config pjsua_acc_config;
+PJ_INLINE(void) pjsua_acc_config_default(pjsua_acc_config *cfg)
+{
+ pj_memset(cfg, 0, sizeof(*cfg));
+
+ cfg->reg_timeout = PJSUA_REG_INTERVAL;
+}
+
/**
- * PJSUA settings.
+ * Account info. Application can query account info by calling
+ * #pjsua_acc_get_info().
*/
-struct pjsua_config
+typedef struct pjsua_acc_info
{
- /** 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)
+ /**
+ * The account ID.
*/
- unsigned udp_port;
+ pjsua_acc_id id;
- /** 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)
+ /**
+ * Flag to indicate whether this is the default account.
*/
- pj_str_t sip_host;
+ pj_bool_t is_default;
- /** Optional port number to publish in the port part of Contact header.
- * This must be specified if UDP transport is disabled.
- * (default: 0)
+ /**
+ * Account URI
*/
- unsigned sip_port;
+ pj_str_t acc_uri;
- /** 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)
+ /**
+ * Flag to tell whether this account has registration setting
+ * (reg_uri is not empty).
*/
- unsigned start_rtp_port;
+ pj_bool_t has_registration;
/**
- * Enable incoming and outgoing message logging (default: 1).
+ * An up to date expiration interval for account registration session.
*/
- pj_bool_t msg_logging;
+ int expires;
- /** Maximum calls to support (default: 4) */
- unsigned max_calls;
+ /**
+ * 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;
- /** Maximum slots in the conference bridge (default: 0/calculated
- * as max_calls*2
+ /**
+ * Presence online status for this account.
+ */
+ pj_bool_t online_status;
+
+ /**
+ * Buffer that is used internally to store the status text.
*/
- unsigned conf_ports;
+ char buf_[PJ_ERR_MSG_SIZE];
- /** Number of worker threads (value >=0, default: 1) */
- unsigned thread_cnt;
+} pjsua_acc_info;
- /** 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)
+/**
+ * STUN configuration.
+ */
+typedef struct pjsua_stun_config
+{
+ /**
+ * The first STUN server IP address or hostname.
*/
pj_str_t stun_srv1;
- /** First STUN port number */
+ /**
+ * Port number of the first STUN server.
+ * If zero, default STUN port will be used.
+ */
unsigned stun_port1;
-
- /** Second STUN server IP address */
+
+ /**
+ * 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;
- /** Second STUN server port number */
+ /**
+ * Port number of the second STUN server.
+ * If zero, default STUN port will be used.
+ */
unsigned stun_port2;
- /** Sound player device ID (default: 0) */
- unsigned snd_player_id;
+} 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));
+}
- /** 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).
+/**
+ * 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 clock_rate;
+ unsigned port;
- /** Do not use sound device (default: 0). */
- pj_bool_t null_audio;
+ /**
+ * Optional address where the socket should be bound.
+ */
+ pj_in_addr ip_addr;
- /** WAV file to load for auto_play (default: NULL) */
- pj_str_t wav_file;
+ /**
+ * Flag to indicate whether STUN should be used.
+ */
+ pj_bool_t use_stun;
- /** Auto play WAV file for calls? (default: no) */
- pj_bool_t auto_play;
+ /**
+ * STUN configuration, must be specified when STUN is used.
+ */
+ pjsua_stun_config stun_config;
- /** Auto loopback calls? (default: no) */
- pj_bool_t auto_loop;
+} pjsua_transport_config;
- /** Automatically put calls to conference? (default: no) */
- pj_bool_t auto_conf;
- /** Speex codec complexity? (default: 10) */
- unsigned complexity;
+/**
+ * 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;
- /** Speex codec quality? (default: 10) */
- unsigned quality;
+ /**
+ * Local/bound address.
+ */
+ pj_sockaddr local_addr;
- /** Codec ptime? (default: 0 (follows the codec)) */
- unsigned ptime;
+ /**
+ * Published address (or transport address name).
+ */
+ pjsip_host_port local_name;
- /** Number of additional codecs/"--add-codec" with pjsua (default: 0) */
- unsigned codec_cnt;
+ /**
+ * Current number of objects currently referencing this transport.
+ */
+ unsigned usage_count;
+
+
+} pjsua_transport_info;
- /** Additional codecs/"--add-codec" options */
- pj_str_t codec_arg[32];
- /** SIP status code to be automatically sent to incoming calls
- * (default: 100).
+/**
+ * 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 auto_answer;
+ unsigned clock_rate;
- /** Periodic time to refresh call with re-INVITE (default: 0)
+ /**
+ * 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 uas_refresh;
+ unsigned max_media_ports;
- /** Maximum incoming call duration (default: 3600) */
- unsigned uas_duration;
+ /**
+ * 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;
- /** Outbound proxy (default: none) */
- pj_str_t outbound_proxy;
+ /**
+ * Specify the number of worker threads to handle incoming RTP
+ * packets. A value of one is recommended for most applications.
+ */
+ unsigned thread_cnt;
- /** Number of SIP accounts */
- unsigned acc_cnt;
- /** SIP accounts configuration */
- pjsua_acc_config acc_config[32];
+} pjsua_media_config;
- /** Logging verbosity (default: 5). */
- unsigned log_level;
- /** Logging to be displayed to stdout (default: 4) */
- unsigned app_log_level;
+/**
+ * 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;
+
+ /**
+ * Input verbosity level. Value 5 is reasonable.
+ */
+ unsigned level;
- /** Log decoration */
- unsigned log_decor;
+ /**
+ * Verbosity level for console. Value 4 is reasonable.
+ */
+ unsigned console_level;
+
+ /**
+ * Log decoration.
+ */
+ unsigned decor;
- /** Optional log filename (default: NULL) */
+ /**
+ * Optional log filename.
+ */
pj_str_t log_filename;
- /** Number of buddies in address book (default: 0) */
- unsigned buddy_cnt;
+ /**
+ * 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;
- /** Buddies URI */
- pj_str_t buddy_uri[256];
-};
+} pjsua_buddy_config;
/**
- * @see pjsua_config
+ * Buddy's online status.
*/
-typedef struct pjsua_config pjsua_config;
+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;
+
+
+/**
* Application callbacks.
*/
-struct pjsua_callback
+typedef struct pjsua_callback
{
/**
* Notify application when invite state has changed.
* Application may then query the call info to get the
* 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);
+
+ /**
* Notify application on call being transfered.
* Application can decide to accept/reject transfer request
* by setting the code (default is 200). When this callback
* is not defined, the default behavior is to accept the
* 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);
@@ -312,289 +745,801 @@ struct pjsua_callback
/**
* 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);
+ 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;
+
+
-};
/**
- * @see pjsua_callback
+ * PJSUA settings.
*/
-typedef struct pjsua_callback pjsua_callback;
+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;
/**
- * Call info.
+ * Use this function to initialize pjsua config.
+ *
+ * @param cfg pjsua config to be initialized.
*/
-struct pjsua_call_info
+PJ_INLINE(void) pjsua_config_default(pjsua_config *cfg)
{
- unsigned index;
- pj_bool_t active;
- pjsip_role_e role;
- pj_str_t local_info;
- pj_str_t remote_info;
- pjsip_inv_state state;
- pj_str_t state_text;
- pjsip_status_code last_status;
- pj_str_t last_status_text;
- pj_time_val connect_duration;
- pj_time_val total_duration;
- pj_bool_t has_media;
- pjsua_conf_port_id conf_slot;
-};
+ pj_memset(cfg, 0, sizeof(*cfg));
-typedef struct pjsua_call_info pjsua_call_info;
+ cfg->max_calls = 4;
+ cfg->thread_cnt = 1;
+}
-enum pjsua_buddy_status
+/**
+ * Duplicate credential.
+ */
+PJ_INLINE(void) pjsip_cred_dup( pj_pool_t *pool,
+ pjsip_cred_info *dst,
+ const pjsip_cred_info *src)
{
- PJSUA_BUDDY_STATUS_UNKNOWN,
- PJSUA_BUDDY_STATUS_ONLINE,
- PJSUA_BUDDY_STATUS_OFFLINE,
-};
+ 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);
-typedef enum pjsua_buddy_status pjsua_buddy_status;
+}
/**
- * Buddy info.
+ * Duplicate pjsua_config.
*/
-struct pjsua_buddy_info
+PJ_INLINE(void) pjsua_config_dup(pj_pool_t *pool,
+ pjsua_config *dst,
+ const pjsua_config *src)
{
- pjsua_buddy_id index;
- pj_bool_t is_valid;
- 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;
-};
+ 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]);
+ }
-typedef struct pjsua_buddy_info pjsua_buddy_info;
+ for (i=0; i<src->cred_count; ++i) {
+ pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]);
+ }
+}
/**
- * Account info.
+ * Call media status.
*/
-struct pjsua_acc_info
+typedef enum pjsua_call_media_status
{
- 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];
-};
+ PJSUA_CALL_MEDIA_NONE,
+ PJSUA_CALL_MEDIA_ACTIVE,
+ PJSUA_CALL_MEDIA_LOCAL_HOLD,
+ PJSUA_CALL_MEDIA_REMOTE_HOLD,
+} pjsua_call_media_status;
+
+
+/**
+ * Call info.
+ */
+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;
+
+ /** 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;
+
-typedef struct pjsua_acc_info pjsua_acc_info;
/**
* Conference port info.
*/
-struct pjsua_conf_port_info
+typedef struct pjsua_conf_port_info
{
+ /** Conference port number. */
pjsua_conf_port_id slot_id;
+
+ /** Port name. */
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];
-};
+ /** Array of listeners (in other words, ports where this port is
+ * transmitting to.
+ */
+ pjsua_conf_port_id listeners[PJSUA_MAX_CONF_PORTS];
-typedef struct pjsua_conf_port_info pjsua_conf_port_info;
+} pjsua_conf_port_info;
-/*****************************************************************************
- * PJSUA API (defined in pjsua_core.c).
+/**
+ * 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;
+
/**
- * Initialize pjsua settings with default parameters.
+ * This structure describes additional information to be sent with
+ * outgoing SIP message.
*/
-PJ_DECL(void) pjsua_default_config(pjsua_config *cfg);
+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;
/**
- * Validate configuration.
+ * 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 Core API
*/
-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.
+ * 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);
/**
- * Initialize pjsua application with the specified settings.
+ * Initialize pjsua with the specified settings. All the settings are
+ * optional, and the default values will be used when the config is not
+ * specified.
*
- * This will initialize all libraries, create endpoint instance, and register
- * pjsip modules.
+ * @param ua_cfg User agent configuration.
+ * @param log_cfg Optional logging configuration.
+ * @param media_cfg Optional media configuration.
*
- * Application may register module after calling this function.
+ * @return PJ_SUCCESS on success, or the appropriate error code.
*/
-PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg,
- const pjsua_callback *cb);
+PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *ua_cfg,
+ const pjsua_logging_config *log_cfg,
+ const pjsua_media_config *media_cfg);
/**
- * Start pjsua stack. Application calls this after pjsua settings has been
- * configured.
+ * Application is recommended to call this function after all initialization
+ * is done, so that the library can do additional checking set up
+ * additional
*
- * This will start the transport, worker threads (if any), and registration
- * process, if registration is configured.
+ * @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);
/**
- * 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);
+
+/*****************************************************************************
+ * 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);
+
+
/**
- * Replace media transport.
+ * 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_set_call_media_transport(unsigned call_index,
- const pjmedia_sock_info *i,
- pjmedia_transport *tp);
+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 Call API (defined in pjsua_call.c).
+ * 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.
+ *
+ * @param call_id Call identification.
+ *
+ * @return Non-zero if call is active.
*/
-PJ_DECL(pj_bool_t) pjsua_call_is_active(unsigned call_index);
+PJ_DECL(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id);
/**
- * Check if call has a media session.
+ * 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(unsigned call_index);
+PJ_DECL(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id);
/**
- * Get call info.
+ * 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(unsigned call_index,
+PJ_DECL(pj_status_t) pjsua_call_get_info(pjsua_call_id call_id,
pjsua_call_info *info);
/**
- * Duplicate call info.
+ * 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(void) pjsua_call_info_dup(pj_pool_t *pool,
- pjsua_call_info *dst_info,
- const pjsua_call_info *src_info);
+PJ_DECL(pj_status_t) pjsua_call_set_user_data(pjsua_call_id call_id,
+ void *user_data);
/**
- * Make outgoing call.
+ * Get user data attached to the call.
+ *
+ * @param call_id Call identification.
+ *
+ * @return The user data.
*/
-PJ_DECL(pj_status_t) pjsua_call_make_call(unsigned acc_id,
- const pj_str_t *dst_uri,
- int *p_call_index);
+PJ_DECL(void*) pjsua_call_get_user_data(pjsua_call_id call_id);
/**
- * Answer call.
+ * 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(int call_index, int 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.
+ * 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(void) pjsua_call_hangup(int call_index);
+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 call on-hold.
+ * 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(int call_index);
+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).
+ *
+ * @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(int call_index);
+PJ_DECL(pj_status_t) pjsua_call_reinvite(pjsua_call_id call_id,
+ pj_bool_t unhold,
+ const pjsua_msg_data *msg_data);
/**
- * Transfer call.
+ * 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(unsigned call_index, const pj_str_t *dest);
+PJ_DECL(pj_status_t) pjsua_call_xfer(pjsua_call_id call_id,
+ const pj_str_t *dest,
+ const pjsua_msg_data *msg_data);
/**
- * Dial DTMF.
+ * 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(unsigned call_index,
+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.
+ *
+ * @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(int call_index, const pj_str_t *text);
+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.
+ *
+ * @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(int call_index,
- pj_bool_t is_typing);
+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);
/**
* Terminate all calls.
@@ -604,83 +1549,192 @@ PJ_DECL(void) pjsua_call_hangup_all(void);
/**
* Dump call and media statistics to string.
+ *
+ * @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(void) pjsua_call_dump(int call_index, int with_media,
- char *buffer, unsigned maxlen,
- const char *indent);
+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).
+ * PJSUA Account and Client Registration API.
*/
/**
- * Get number of accounts.
+ * Get number of current accounts.
+ *
+ * @return Current number of accounts.
*/
-PJ_DECL(unsigned) pjsua_get_acc_count(void);
+PJ_DECL(unsigned) pjsua_acc_get_count(void);
+
/**
- * Get account info.
+ * 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_status_t) pjsua_acc_get_info(pjsua_acc_id acc_id,
- pjsua_acc_info *info);
+PJ_DECL(pj_bool_t) pjsua_acc_is_valid(pjsua_acc_id acc_id);
/**
- * Enum accounts 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_enum_id( pjsua_acc_id ids[],
- unsigned *count );
+PJ_DECL(pj_status_t) pjsua_acc_add(const pjsua_acc_config *cfg,
+ pj_bool_t is_default,
+ pjsua_acc_id *p_acc_id);
/**
- * Enum accounts info.
+ * 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_enum_info( pjsua_acc_info info[],
- unsigned *count );
+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);
/**
- * Find account for outgoing request.
+ * 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(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *url);
+PJ_DECL(pj_status_t) pjsua_acc_modify(pjsua_acc_id acc_id,
+ const pjsua_acc_config *cfg);
+
/**
- * Find account for incoming request.
+ * 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(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata);
+PJ_DECL(pj_status_t) pjsua_acc_set_online_status(pjsua_acc_id acc_id,
+ pj_bool_t is_online);
+
/**
- * 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.
+ * 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_add(const pjsua_acc_config *cfg,
- pjsua_acc_id *acc_id);
+PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_id,
+ pj_bool_t renew);
+
/**
- * Delete account.
+ * 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_del(pjsua_acc_id acc_id);
+PJ_DECL(pj_status_t) pjsua_acc_get_info(pjsua_acc_id acc_id,
+ pjsua_acc_info *info);
/**
- * Set account's presence status.
+ * 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_acc_set_online_status(pjsua_acc_id acc_id,
- pj_bool_t is_online);
+PJ_DECL(pj_status_t) pjsua_enum_accs(pjsua_acc_id ids[],
+ unsigned *count );
/**
- * Update registration or perform unregistration. If renew argument is zero,
- * this will start unregistration process.
+ * 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_set_registration(pjsua_acc_id acc_id,
- pj_bool_t renew);
+PJ_DECL(pj_status_t) pjsua_acc_enum_info( pjsua_acc_info info[],
+ unsigned *count );
+/**
+ * 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);
+
+
+/**
+ * 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);
+
/*****************************************************************************
@@ -688,41 +1742,88 @@ PJ_DECL(pj_status_t) pjsua_acc_set_registration(pjsua_acc_id acc_id,
*/
/**
- * Get buddy count.
+ * Get total number of buddies.
+ *
+ * @return Number of buddies.
*/
PJ_DECL(unsigned) pjsua_get_buddy_count(void);
/**
- * Get buddy info.
+ * 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_index,
+PJ_DECL(pj_status_t) pjsua_buddy_get_info(pjsua_buddy_id buddy_id,
pjsua_buddy_info *info);
/**
* Add new buddy.
+ *
+ * @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 pj_str_t *uri,
- pjsua_buddy_id *buddy_index);
+PJ_DECL(pj_status_t) pjsua_buddy_add(const pjsua_buddy_config *cfg,
+ pjsua_buddy_id *p_buddy_id);
/**
* Delete buddy.
+ *
+ * @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_index);
+PJ_DECL(pj_status_t) pjsua_buddy_del(pjsua_buddy_id buddy_id);
/**
* Enable/disable buddy's presence monitoring.
+ *
+ * @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_index,
- pj_bool_t monitor);
+PJ_DECL(pj_status_t) pjsua_buddy_subscribe_pres(pjsua_buddy_id buddy_id,
+ pj_bool_t subscribe);
/**
- * Dump presence subscriptions.
+ * Dump presence subscriptions to log file.
+ *
+ * @param verbose Yes or no.
*/
-PJ_DECL(void) pjsua_pres_dump(pj_bool_t detail);
+PJ_DECL(void) pjsua_pres_dump(pj_bool_t verbose);
/*****************************************************************************
@@ -737,176 +1838,355 @@ extern const pjsip_method pjsip_message_method;
/**
- * Send IM outside dialog.
+ * 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(int acc_id, const pj_str_t *dst_uri,
- const pj_str_t *text);
+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.
+ *
+ * @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(int acc_id, const pj_str_t *dst_uri,
- pj_bool_t is_typing);
+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.
+ *
+ * @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_max_ports(void);
+PJ_DECL(unsigned) pjsua_conf_get_active_ports(void);
/**
- * Enum all conference ports.
+ * 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_conf_enum_port_ids(pjsua_conf_port_id id[],
- unsigned *count);
+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,
pjsua_conf_port_info *info);
/**
- * Connect conference port.
+ * 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 src_port,
- pjsua_conf_port_id dst_port);
+PJ_DECL(pj_status_t) pjsua_conf_connect(pjsua_conf_port_id source,
+ pjsua_conf_port_id sink);
/**
- * Connect conference port connection.
+ * 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 src_port,
- pjsua_conf_port_id dst_port);
+PJ_DECL(pj_status_t) pjsua_conf_disconnect(pjsua_conf_port_id source,
+ pjsua_conf_port_id sink);
+/*****************************************************************************
+ * File player.
+ */
+
/**
- * Create a 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);
+ unsigned options,
+ void *user_data,
+ pjsua_player_id *p_id);
/**
- * Get conference port associated with player.
+ * 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);
/**
* 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,
pj_uint32_t samples);
/**
- * 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);
+/*****************************************************************************
+ * File recorder.
+ */
/**
- * Create a 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);
/**
- * 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.
+ *
+ * @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_devices(unsigned *count,
- pjmedia_snd_dev_info info[]);
+PJ_DECL(pj_status_t) pjsua_enum_snd_devs(pjmedia_snd_dev_info info[],
+ unsigned *count);
/**
- * Select or change sound device.
- * This will only change the device ID in configuration (not changing
- * the current device).
+ * 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 snd_capture_id,
- int snd_player_id);
+PJ_DECL(pj_status_t) pjsua_set_snd_dev(int capture_dev,
+ int playback_dev);
-/*****************************************************************************
- * Utilities.
+/**
+ * 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);
-/** String to describe invite session states */
-extern const char *pjsua_inv_state_names[];
-/**
- * Parse arguments (pjsua_opt.c).
+/*****************************************************************************
+ * Codecs.
*/
-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.
+ * 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_load_settings(const char *filename,
- pjsua_config *cfg,
- pj_str_t *uri_to_call);
+PJ_DECL(pj_status_t) pjsua_enum_codecs( pjsua_codec_info id[],
+ unsigned *count );
+
/**
- * Get pjsua running config.
+ * 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(void) pjsua_get_config(pj_pool_t *pool,
- pjsua_config *config);
+PJ_DECL(pj_status_t) pjsua_codec_set_priority( const pj_str_t *id,
+ pj_uint8_t priority );
/**
- * Dump settings.
- * If cfg is NULL, it will dump current settings.
+ * 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(int) pjsua_dump_settings(const pjsua_config *cfg,
- char *buf, pj_size_t max);
+PJ_DECL(pj_status_t) pjsua_codec_get_param( const pj_str_t *id,
+ pjmedia_codec_param *param );
+
/**
- * Save settings to a file.
+ * 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_save_settings(const char *filename,
- const pjsua_config *cfg);
+PJ_DECL(pj_status_t) pjsua_codec_set_param( const pj_str_t *id,
+ const pjmedia_codec_param *param);
+
+
+
+/*****************************************************************************
+ * Utilities.
+ *
+ */
+
/*
* 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,
pj_status_t status);
diff --git a/pjsip/include/pjsua-lib/pjsua_console_app.h b/pjsip/include/pjsua-lib/pjsua_console_app.h
deleted file mode 100644
index 8be93714..00000000
--- a/pjsip/include/pjsua-lib/pjsua_console_app.h
+++ /dev/null
@@ -1,28 +0,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__ */
diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h
new file mode 100644
index 00000000..8b061c5b
--- /dev/null
+++ b/pjsip/include/pjsua-lib/pjsua_internal.h
@@ -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__ */
+
diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c
new file mode 100644
index 00000000..b16f5fdf
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_acc.c
@@ -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;
+}
+
diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c
index 507e3c93..92c1f92e 100644
--- a/pjsip/src/pjsua-lib/pjsua_call.c
+++ b/pjsip/src/pjsua-lib/pjsua_call.c
@@ -17,283 +17,235 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjsua-lib/pjsua.h>
-#include <pj/log.h>
-#include "pjsua_imp.h"
+#include <pjsua-lib/pjsua_internal.h>
-/*
- * pjsua_call.c
- *
- * Call (INVITE) related stuffs.
- */
-#define THIS_FILE "pjsua_call.c"
+#define THIS_FILE "pjsua_call.c"
-#define REFRESH_CALL_TIMER 0x63
-#define HANGUP_CALL_TIMER 0x64
+/* 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);
-/* Proto */
-static void schedule_call_timer( pjsua_call *call, pj_timer_entry *e,
- int timer_type, int duration );
+/* 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);
/*
- * Timer callback when UAS needs to send re-INVITE to see if remote
- * is still there.
+ * 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 call_on_timer(pj_timer_heap_t *ht, pj_timer_entry *e)
-{
- pjsua_call *call = e->user_data;
+static void pjsua_call_on_media_update(pjsip_inv_session *inv,
+ pj_status_t status);
- PJ_UNUSED_ARG(ht);
+/*
+ * 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);
- 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);
- }
+/* Destroy the call's media */
+static pj_status_t call_destroy_media(int call_id);
- } 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);
+/* Create inactive SDP for call hold. */
+static pj_status_t create_inactive_sdp(pjsua_call *call,
+ pjmedia_sdp_session **p_answer);
- }
-}
/*
- * Schedule call timer.
+ * Reset call descriptor.
*/
-static void schedule_call_timer( pjsua_call *call, pj_timer_entry *e,
- int timer_type, int duration )
+static void reset_call(pjsua_call_id id)
{
- 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);
- }
+ 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;
}
/*
- * Destroy the call's media
+ * Init call subsystem.
*/
-static pj_status_t call_destroy_media(int call_index)
+pj_status_t pjsua_call_subsys_init(const pjsua_config *cfg)
{
- 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;
- }
+ pjsip_inv_callback inv_cb;
+ unsigned i;
+ pj_status_t status;
- if (call->session) {
- /* Destroy session (this will also close RTP/RTCP sockets). */
- pjmedia_session_destroy(call->session);
- call->session = NULL;
+ /* Init calls array. */
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.calls); ++i)
+ reset_call(i);
- PJ_LOG(3,(THIS_FILE, "Media session for call %d is destroyed",
- call_index));
+ /* 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;
- return PJ_SUCCESS;
-}
+ /* Initialize invite session module: */
+ status = pjsip_inv_usage_init(pjsua_var.endpt, &inv_cb);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
-/**
- * Get maximum number of calls configured in pjsua.
- */
-PJ_DEF(unsigned) pjsua_call_get_max_count(void)
-{
- return pjsua.config.max_calls;
+ return status;
}
-/**
- * Get current number of active calls.
+/*
+ * Start call subsystem.
*/
-PJ_DEF(unsigned) pjsua_call_get_count(void)
+pj_status_t pjsua_call_subsys_start(void)
{
- return pjsua.call_cnt;
+ /* Nothing to do */
+ return PJ_SUCCESS;
}
-/**
- * Check if the specified call is active.
+/*
+ * Get maximum number of calls configured in pjsua.
*/
-PJ_DEF(pj_bool_t) pjsua_call_is_active(unsigned call_index)
+PJ_DEF(unsigned) pjsua_call_get_max_count(void)
{
- 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;
+ return pjsua_var.ua_cfg.max_calls;
}
-/**
- * Check if call has a media session.
+
+/*
+ * Get number of currently active calls.
*/
-PJ_DEF(pj_bool_t) pjsua_call_has_media(unsigned call_index)
+PJ_DEF(unsigned) pjsua_call_get_count(void)
{
- PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, PJ_EINVAL);
- return pjsua.calls[call_index].session != NULL;
+ return pjsua_var.call_cnt;
}
-/**
- * Get call info.
+/*
+ * Enum calls.
*/
-PJ_DEF(pj_status_t) pjsua_call_get_info( unsigned call_index,
- pjsua_call_info *info)
+PJ_DEF(pj_status_t) pjsua_enum_calls( pjsua_call_id ids[],
+ unsigned *count)
{
- 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);
+ unsigned i, c;
- if (call->conn_time.sec) {
- info->connect_duration = call->dis_time;
- PJ_TIME_VAL_SUB(info->total_duration, call->conn_time);
- }
+ PJ_ASSERT_RETURN(ids && *count, PJ_EINVAL);
- } else if (info->state == PJSIP_INV_STATE_CONFIRMED) {
+ PJSUA_LOCK();
- 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);
+ 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;
}
- info->last_status = call->last_code;
- info->last_status_text = *pjsip_get_status_text(info->last_status);
+ *count = c;
- info->has_media = (call->session != NULL);
- info->conf_slot = call->conf_slot;
+ PJSUA_UNLOCK();
return PJ_SUCCESS;
}
-/**
- * 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.
+/*
+ * Make outgoing call to the specified URI using the specified account.
*/
-PJ_DEF(pj_status_t) pjsua_call_make_call(unsigned acc_index,
- const pj_str_t *dest_uri,
- int *p_call_index)
+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;
}
/* 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);
goto on_error;
@@ -310,24 +262,23 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(unsigned acc_index,
/* Create and associate our data in the session. */
- pjsua.calls[call_index].inv = inv;
+ call->inv = inv;
- dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
- inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index];
+ 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,6 +292,10 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(unsigned acc_index,
}
+ /* Add additional headers etc */
+
+ pjsua_process_msg_data( tdata, msg_data);
+
/* Send initial INVITE: */
status = pjsip_inv_send_msg(inv, tdata);
@@ -356,13 +311,14 @@ PJ_DEF(pj_status_t) pjsua_call_make_call(unsigned acc_index,
goto on_error;
}
-
/* Done. */
- ++pjsua.call_cnt;
+ ++pjsua_var.call_cnt;
+
+ if (p_call_id)
+ *p_call_id = call_id;
- if (p_call_index)
- *p_call_index = call_index;
+ PJSUA_UNLOCK();
return PJ_SUCCESS;
@@ -374,46 +330,18 @@ on_error:
pjsip_dlg_terminate(dlg);
}
- 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;
+ if (call_id != -1) {
+ reset_call(call_id);
}
- 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);
-
+ PJSUA_UNLOCK();
return status;
}
/**
* Handle incoming INVITE request.
+ * Called by pjsua_core.c
*/
pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
{
@@ -423,8 +351,9 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
pjsip_tx_data *response = NULL;
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;
@@ -441,7 +370,7 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
/* 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) {
/*
@@ -452,13 +381,13 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
pjsip_response_addr res_addr;
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);
} else {
/* 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);
}
@@ -471,101 +400,88 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
*/
/* 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);
return PJ_TRUE;
}
- /* Mark call start time. */
- pj_gettimeofday(&pjsua.calls[call_index].start_time);
+ /* Clear call descriptor */
+ reset_call(call_id);
- /* Reset first response time */
- pjsua.calls[call_index].res_time.sec = 0;
+ call = &pjsua_var.calls[call_id];
- /* Get media capability from media endpoint: */
+ /* Mark call start time. */
+ pj_gettimeofday(&call->start_time);
- status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool, 1,
- &pjsua.calls[call_index].skinfo,
- &answer );
+ /* Get media capability from media endpoint: */
+ 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.endpt, rdata, 500, NULL,
+ 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);
return PJ_TRUE;
}
+ /* 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);
return PJ_TRUE;
}
- /* Create and attach pjsua data to the dialog: */
+ /* Create and attach pjsua_var data to the dialog: */
+ call->inv = inv;
- 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];
+ dlg->mod_data[pjsua_var.mod.id] = call;
+ inv->mod_data[pjsua_var.mod.id] = call;
/* Must answer with some response to initial INVITE.
* 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);
+ 100, NULL, NULL, &response);
if (status != PJ_SUCCESS) {
-
- int st_code;
-
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;
} else {
@@ -574,898 +490,478 @@ pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata)
pjsua_perror(THIS_FILE, "Unable to send 100 response", status);
}
- 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);
- }
+ ++pjsua_var.call_cnt;
- /* 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);
- }
/* 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. */
return PJ_TRUE;
}
+
/*
- * This callback receives notification from invite session when the
- * session state has changed.
+ * Check if the specified call has active INVITE session and the INVITE
+ * session has not been disconnected.
*/
-static void pjsua_call_on_state_changed(pjsip_inv_session *inv,
- pjsip_event *e)
+PJ_DEF(pj_bool_t) pjsua_call_is_active(pjsua_call_id call_id)
{
- pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id];
-
- if (!call)
- return;
-
- /* Get call times */
- switch (inv->state) {
- case PJSIP_INV_STATE_EARLY:
- case PJSIP_INV_STATE_CONNECTING:
- if (call->res_time.sec == 0)
- pj_gettimeofday(&call->res_time);
- call->last_code = e->body.tsx_state.tsx->status_code;
- break;
- case PJSIP_INV_STATE_CONFIRMED:
- pj_gettimeofday(&call->conn_time);
- break;
- case PJSIP_INV_STATE_DISCONNECTED:
- pj_gettimeofday(&call->dis_time);
- if (e->body.tsx_state.tsx->status_code > call->last_code) {
- call->last_code = e->body.tsx_state.tsx->status_code;
- }
- break;
- default:
- call->last_code = e->body.tsx_state.tsx->status_code;
- break;
- }
-
- /* If this is an outgoing INVITE that was created because of
- * REFER/transfer, send NOTIFY to transferer.
- */
- if (call->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) {
- int st_code = -1;
- pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE;
-
-
- switch (call->inv->state) {
- case PJSIP_INV_STATE_NULL:
- case PJSIP_INV_STATE_CALLING:
- /* Do nothing */
- break;
-
- case PJSIP_INV_STATE_EARLY:
- case PJSIP_INV_STATE_CONNECTING:
- st_code = e->body.tsx_state.tsx->status_code;
- ev_state = PJSIP_EVSUB_STATE_ACTIVE;
- break;
-
- case PJSIP_INV_STATE_CONFIRMED:
- /* When state is confirmed, send the final 200/OK and terminate
- * subscription.
- */
- st_code = e->body.tsx_state.tsx->status_code;
- ev_state = PJSIP_EVSUB_STATE_TERMINATED;
- break;
-
- case PJSIP_INV_STATE_DISCONNECTED:
- st_code = e->body.tsx_state.tsx->status_code;
- ev_state = PJSIP_EVSUB_STATE_TERMINATED;
- break;
-
- case PJSIP_INV_STATE_INCOMING:
- /* Nothing to do. Just to keep gcc from complaining about
- * unused enums.
- */
- break;
- }
-
- if (st_code != -1) {
- pjsip_tx_data *tdata;
- pj_status_t status;
-
- status = pjsip_xfer_notify( call->xfer_sub,
- ev_state, st_code,
- NULL, &tdata);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status);
- } else {
- status = pjsip_xfer_send_request(call->xfer_sub, tdata);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status);
- }
- }
- }
- }
-
-
- if (pjsua.cb.on_call_state)
- (*pjsua.cb.on_call_state)(call->index, e);
-
- /* call->inv may be NULL now */
-
- /* Destroy media session when invite session is disconnected. */
- if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
-
- pj_assert(call != NULL);
-
- if (call)
- 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;
- }
+ 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;
}
/*
- * Callback called by event framework when the xfer subscription state
- * has changed.
+ * Check if call has an active media session.
*/
-static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event)
+PJ_DEF(pj_bool_t) pjsua_call_has_media(pjsua_call_id call_id)
{
-
- 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"));
- }
+ 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;
}
/*
- * Follow transfer (REFER) request.
+ * Get the conference port identification associated with the call.
*/
-static void on_call_transfered( pjsip_inv_session *inv,
- pjsip_rx_data *rdata )
+PJ_DEF(pjsua_conf_port_id) pjsua_call_get_conf_port(pjsua_call_id call_id)
{
- 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;
- }
+ 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;
+}
- /* 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;
- }
+/*
+ * 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_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));
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ PJ_EINVAL);
- /* Init callback */
- pj_memset(&xfer_cb, 0, sizeof(xfer_cb));
- xfer_cb.on_evsub_state = &xfer_on_evsub_state;
+ pj_memset(info, 0, sizeof(*info));
- /* 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;
- }
+ PJSUA_LOCK();
- /* Accept the REFER request, send 200 (OK). */
- pjsip_xfer_accept(sub, rdata, code, NULL);
+ call = &pjsua_var.calls[call_id];
- /* 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;
+ if (call->inv == NULL) {
+ PJSUA_UNLOCK();
+ return PJ_SUCCESS;
}
- /* 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;
- }
+ pjsip_dlg_inc_lock(call->inv->dlg);
- /* 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) {
+ /* id and role */
+ info->id = call_id;
+ info->role = call->inv->role;
- /* 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;
+ /* 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;
}
- /* 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]);
-}
+ /* 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));
-/*
- * 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 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;
- 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);
+ 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));
}
- 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;
+
+ /* media status and dir */
+ info->media_status = call->media_st;
+ info->media_dir = call->media_dir;
- 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) {
+ /* conference slot number */
+ info->conf_slot = call->conf_slot;
- pjsip_hdr hdr_list;
+ /* calculate duration */
+ if (info->state >= PJSIP_INV_STATE_DISCONNECTED) {
- pj_list_init(&hdr_list);
- pj_list_push_back(&hdr_list, accept_hdr);
+ info->total_duration = call->dis_time;
+ PJ_TIME_VAL_SUB(info->total_duration, call->start_time);
- pjsip_dlg_respond( inv->dlg, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE,
- NULL, &hdr_list, NULL );
- return;
+ if (call->conn_time.sec) {
+ info->connect_duration = call->dis_time;
+ PJ_TIME_VAL_SUB(info->connect_duration, call->conn_time);
}
- /* 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);
+ } else if (info->state == PJSIP_INV_STATE_CONFIRMED) {
- /* Process MESSAGE request */
- pjsua_im_process_pager(call->index, &inv->dlg->remote.info_str,
- &inv->dlg->local.info_str, rdata);
- }
+ 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);
+ }
-/*
- * 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)
-{
- PJ_UNUSED_ARG(inv);
- PJ_UNUSED_ARG(e);
+ pjsip_dlg_dec_lock(call->inv->dlg);
+ PJSUA_UNLOCK();
- PJ_TODO(HANDLE_FORKED_DIALOG);
+ return PJ_SUCCESS;
}
/*
- * Create inactive SDP for call hold.
+ * Attach application specific data to the call.
*/
-static pj_status_t create_inactive_sdp(pjsua_call *call,
- pjmedia_sdp_session **p_answer)
+PJ_DEF(pj_status_t) pjsua_call_set_user_data( pjsua_call_id call_id,
+ void *user_data)
{
- 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;
+ 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 status;
+ return PJ_SUCCESS;
}
+
/*
- * Called when session received new offer.
+ * Get user data attached to the call.
*/
-static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
- const pjmedia_sdp_session *offer)
+PJ_DEF(void*) pjsua_call_get_user_data(pjsua_call_id call_id)
{
- 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;
- }
-
+ PJ_ASSERT_RETURN(call_id>=0 && call_id<(int)pjsua_var.ua_cfg.max_calls,
+ NULL);
+ return pjsua_var.calls[call_id].user_data;
}
-#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
/*
- * 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.
+ * Send response to incoming INVITE request.
*/
-static void pjsua_call_on_media_update(pjsip_inv_session *inv,
- pj_status_t status)
+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;
- pjmedia_session_info sess_info;
- const pjmedia_sdp_session *local_sdp;
- const pjmedia_sdp_session *remote_sdp;
- pjmedia_port *media_port;
- pj_str_t port_name;
- char tmp[PJSIP_MAX_URL_SIZE];
-
- call = inv->dlg->mod_data[pjsua.mod.id];
-
- if (status != PJ_SUCCESS) {
-
- pjsua_perror(THIS_FILE, "SDP negotiation has failed", status);
-
- /* Disconnect call if we're not in the middle of initializing an
- * UAS dialog and if this is not a re-INVITE
- */
- if (inv->state != PJSIP_INV_STATE_NULL &&
- inv->state != PJSIP_INV_STATE_CONFIRMED)
- {
- //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
- }
- return;
-
- }
-
- /* Destroy existing media session, if any. */
-
- if (call)
- call_destroy_media(call->index);
-
- /* Get local and remote SDP */
-
- status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE,
- "Unable to retrieve currently active local SDP",
- status);
- //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
- return;
- }
+ 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);
- status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE,
- "Unable to retrieve currently active remote SDP",
- status);
- //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
- return;
- }
+ PJSUA_LOCK();
- if (pjsua.config.null_audio)
- return;
+ call = &pjsua_var.calls[call_id];
- /* Create media session info based on SDP parameters.
- * We only support one stream per session at the moment
- */
- status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
- pjsua.med_endpt,
- 1,&sess_info,
- local_sdp, remote_sdp);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to create media session",
- status);
- //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
- 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;
+ if (call->inv == NULL) {
+ PJ_LOG(3,(THIS_FILE, "Call %d already disconnected", call_id));
+ PJSUA_UNLOCK();
+ return PJSIP_ESESSIONTERMINATED;
}
- /* 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 );
+ /* Create response message */
+ status = pjsip_inv_answer(call->inv, code, reason, NULL, &tdata);
if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to create media session",
+ pjsua_perror(THIS_FILE, "Error creating response",
status);
- //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
- return;
+ PJSUA_UNLOCK();
+ return status;
}
+ /* Add additional headers etc */
+ pjsua_process_msg_data( tdata, msg_data);
- /* 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",
+ /* Send the message */
+ status = pjsip_inv_send_msg(call->inv, tdata);
+ if (status != PJ_SUCCESS)
+ pjsua_perror(THIS_FILE, "Error sending response",
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)
- {
- pjmedia_conf_connect_port( pjsua.mconf, pjsua.player[0].slot,
- call->conf_slot, 0);
+ PJSUA_UNLOCK();
- }
- 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);
- }
-
- }
-
- /* 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)
- {
- 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;
- const char *dir;
- pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
-
- switch (strm_info->dir) {
- case PJMEDIA_DIR_NONE:
- dir = "inactive";
- break;
- case PJMEDIA_DIR_ENCODING:
- dir = "sendonly";
- break;
- case PJMEDIA_DIR_DECODING:
- dir = "recvonly";
- break;
- case PJMEDIA_DIR_ENCODING_DECODING:
- dir = "sendrecv";
- break;
- default:
- dir = "unknown";
- break;
- }
- len = pj_ansi_sprintf( info+info_len,
- ", stream #%d: %.*s (%s)", i,
- (int)strm_info->fmt.encoding_name.slen,
- strm_info->fmt.encoding_name.ptr,
- dir);
- if (len > 0)
- info_len += len;
- }
- PJ_LOG(3,(THIS_FILE,"Media started%s", info));
- }
+ return status;
}
/*
- * Hangup call.
+ * Hangup call by using method that is appropriate according to the
+ * call state.
*/
-PJ_DEF(void) pjsua_call_hangup(int call_index)
+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;
- int code;
pj_status_t status;
pjsip_tx_data *tdata;
- call = &pjsua.calls[call_index];
+ 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"));
- return;
+ PJSUA_UNLOCK();
+ return PJ_EINVAL;
}
- 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;
+ 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, NULL, &tdata);
+ 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);
- return;
+ 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)
- return;
+ 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);
- return;
+ PJSUA_UNLOCK();
+ return status;
}
+
+ PJSUA_UNLOCK();
+
+ return PJ_SUCCESS;
}
/*
- * Put call on-Hold.
+ * Put the specified call on hold.
*/
-PJ_DEF(pj_status_t) pjsua_call_set_hold(int call_index)
+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;
- call = &pjsua.calls[call_index];
+ 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)
+ if (status != PJ_SUCCESS) {
+ PJSUA_UNLOCK();
return status;
+ }
- /* Send re-INVITE with new offer */
+ /* 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;
}
/*
- * re-INVITE.
+ * Send re-INVITE (to release hold).
*/
-PJ_DEF(pj_status_t) pjsua_call_reinvite(int call_index)
+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;
- call = &pjsua.calls[call_index];
+
+ 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.med_endpt, call->inv->pool, 1,
- &call->skinfo, &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;
}
- /* Send re-INVITE with new offer */
+ /* 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;
}
/*
- * Transfer call.
+ * Initiate call transfer to the specified address.
*/
-PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest)
+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);
- call = &pjsua.calls[call_index];
+ 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;
}
@@ -1476,6 +972,7 @@ PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest)
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;
}
@@ -1485,13 +982,18 @@ PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest)
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;
}
@@ -1500,50 +1002,81 @@ PJ_DEF(pj_status_t) pjsua_call_xfer(unsigned call_index, const pj_str_t *dest)
* may want to hold the INVITE, or terminate the invite, or whatever.
*/
+ PJSUA_UNLOCK();
+
return PJ_SUCCESS;
+
}
-/**
- * Dial DTMF.
+/*
+ * Send DTMF digits to remote using RFC 2833 payload formats.
*/
-PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( unsigned call_index,
+PJ_DEF(pj_status_t) pjsua_call_dial_dtmf( pjsua_call_id call_id,
const pj_str_t *digits)
{
- pjsua_call *call = &pjsua.calls[call_index];
+ pjsua_call *call;
+ pj_status_t status;
- PJ_ASSERT_RETURN(call_index < pjsua.config.max_calls, PJ_EINVAL);
+ 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!"));
- return -1;
+ PJSUA_UNLOCK();
+ return PJ_EINVALIDOP;
}
- return pjmedia_session_dial_dtmf( call->session, 0, digits);
+ 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(int call_index, const pj_str_t *str)
+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 = pj_str("text");
- const pj_str_t mime_plain = pj_str("plain");
+ 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;
- call = &pjsua.calls[call_index];
+
+ 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);
@@ -1556,17 +1089,33 @@ PJ_DEF(pj_status_t) pjsua_call_send_im(int call_index, const pj_str_t *str)
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, &mime_text,
- &mime_plain, str);
+ 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, -1, NULL);
+ 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;
@@ -1574,24 +1123,33 @@ PJ_DEF(pj_status_t) pjsua_call_send_im(int call_index, const pj_str_t *str)
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(int call_index,
- pj_bool_t is_typing)
+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;
- call = &pjsua.calls[call_index];
+
+ 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;
}
@@ -1610,6 +1168,9 @@ PJ_DEF(pj_status_t) pjsua_call_send_typing_ind(int call_index,
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) {
@@ -1619,6 +1180,7 @@ PJ_DEF(pj_status_t) pjsua_call_send_typing_ind(int call_index,
on_return:
pjsip_dlg_dec_lock(call->inv->dlg);
+ PJSUA_UNLOCK();
return status;
}
@@ -1630,65 +1192,1070 @@ 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;
+ PJSUA_LOCK();
- if (pjsua.calls[i].inv == NULL)
- continue;
+ for (i=0; i<pjsua_var.ua_cfg.max_calls; ++i) {
+ if (pjsua_var.calls[i].inv)
+ pjsua_call_hangup(i, 0, NULL, NULL);
+ }
- call = &pjsua.calls[i];
+ PJSUA_UNLOCK();
+}
- if (call->inv->state == PJSIP_INV_STATE_CONFIRMED) {
- st_code = 200;
- } else {
- st_code = PJSIP_SC_GONE;
+
+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;
}
- if (pjsip_inv_end_session(call->inv, st_code, NULL, &tdata)==0) {
- if (tdata)
- pjsip_inv_send_msg(call->inv, tdata);
+ 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_status_t pjsua_call_init(void)
+/* Print call info */
+static void print_call(const char *title,
+ int call_id,
+ char *buf, pj_size_t size)
{
- /* Initialize invite session callback. */
- pjsip_inv_callback inv_cb;
- pj_status_t status;
+ int len;
+ pjsip_inv_session *inv = pjsua_var.calls[call_id].inv;
+ pjsip_dialog *dlg = inv->dlg;
+ char userinfo[128];
- 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;
+ /* 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';
+}
- /* Initialize invite session module: */
- status = pjsip_inv_usage_init(pjsua.endpt, &inv_cb);
+
+/*
+ * 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
+ * session state has changed.
+ */
+static void pjsua_call_on_state_changed(pjsip_inv_session *inv,
+ pjsip_event *e)
+{
+ pjsua_call *call;
+
+ PJSUA_LOCK();
+
+ call = inv->dlg->mod_data[pjsua_var.mod.id];
+
+ if (!call) {
+ PJSUA_UNLOCK();
+ return;
+ }
+
+
+ /* Get call times */
+ switch (inv->state) {
+ case PJSIP_INV_STATE_EARLY:
+ case PJSIP_INV_STATE_CONNECTING:
+ if (call->res_time.sec == 0)
+ 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:
+ pj_gettimeofday(&call->conn_time);
+ break;
+ case PJSIP_INV_STATE_DISCONNECTED:
+ pj_gettimeofday(&call->dis_time);
+ 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;
+ }
+
+ /* If this is an outgoing INVITE that was created because of
+ * REFER/transfer, send NOTIFY to transferer.
+ */
+ if (call->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) {
+ int st_code = -1;
+ pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE;
+
+
+ switch (call->inv->state) {
+ case PJSIP_INV_STATE_NULL:
+ case PJSIP_INV_STATE_CALLING:
+ /* Do nothing */
+ break;
+
+ case PJSIP_INV_STATE_EARLY:
+ case PJSIP_INV_STATE_CONNECTING:
+ st_code = e->body.tsx_state.tsx->status_code;
+ ev_state = PJSIP_EVSUB_STATE_ACTIVE;
+ break;
+
+ case PJSIP_INV_STATE_CONFIRMED:
+ /* When state is confirmed, send the final 200/OK and terminate
+ * subscription.
+ */
+ st_code = e->body.tsx_state.tsx->status_code;
+ ev_state = PJSIP_EVSUB_STATE_TERMINATED;
+ break;
+
+ case PJSIP_INV_STATE_DISCONNECTED:
+ st_code = e->body.tsx_state.tsx->status_code;
+ ev_state = PJSIP_EVSUB_STATE_TERMINATED;
+ break;
+
+ case PJSIP_INV_STATE_INCOMING:
+ /* Nothing to do. Just to keep gcc from complaining about
+ * unused enums.
+ */
+ break;
+ }
+
+ if (st_code != -1) {
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_xfer_notify( call->xfer_sub,
+ ev_state, st_code,
+ NULL, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status);
+ } else {
+ status = pjsip_xfer_send_request(call->xfer_sub, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status);
+ }
+ }
+ }
+ }
+
+
+ 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 */
+
+ /* Destroy media session when invite session is disconnected. */
+ if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+
+ pj_assert(call != NULL);
+
+ if (call)
+ call_destroy_media(call->index);
+
+ /* Free call */
+ call->inv = NULL;
+ --pjsua_var.call_cnt;
+ }
+
+ PJSUA_UNLOCK();
+}
+
+/*
+ * 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)
+{
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(e);
+
+ PJ_TODO(HANDLE_FORKED_DIALOG);
+}
+
+
+/*
+ * 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)
+{
+ int prev_media_st = 0;
+ pjsua_call *call;
+ pjmedia_session_info sess_info;
+ const pjmedia_sdp_session *local_sdp;
+ const pjmedia_sdp_session *remote_sdp;
+ pjmedia_port *media_port;
+ pj_str_t port_name;
+ char tmp[PJSIP_MAX_URL_SIZE];
+
+ PJSUA_LOCK();
+
+ call = inv->dlg->mod_data[pjsua_var.mod.id];
+
+ if (status != PJ_SUCCESS) {
+
+ pjsua_perror(THIS_FILE, "SDP negotiation has failed", status);
+
+ /* Disconnect call if we're not in the middle of initializing an
+ * UAS dialog and if this is not a re-INVITE
+ */
+ if (inv->state != PJSIP_INV_STATE_NULL &&
+ inv->state != PJSIP_INV_STATE_CONFIRMED)
+ {
+ //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+ }
+
+ PJSUA_UNLOCK();
+ return;
+ }
+
+ /* Destroy existing media session, if any. */
+
+ if (call) {
+ prev_media_st = call->media_st;
+ call_destroy_media(call->index);
+ }
+
+ /* Get local and remote SDP */
+
+ status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Unable to retrieve currently active local SDP",
+ status);
+ //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+ PJSUA_UNLOCK();
+ return;
+ }
+
+
+ status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp);
+ if (status != PJ_SUCCESS) {
+ pjsua_perror(THIS_FILE,
+ "Unable to retrieve currently active remote SDP",
+ status);
+ //call_disconnect(inv, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE);
+ PJSUA_UNLOCK();
+ return;
+ }
+
+ /* Create media session info based on SDP parameters.
+ * We only support one stream per session at the moment
+ */
+ status = pjmedia_session_info_from_sdp( call->inv->dlg->pool,
+ pjsua_var.med_endpt,
+ 1,&sess_info,
+ local_sdp, remote_sdp);
+ 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;
+ }
+
+ /* Check if media is put on-hold */
+ if (sess_info.stream_cnt == 0 ||
+ sess_info.stream_info[0].dir == PJMEDIA_DIR_NONE)
+ {
+
+ /* 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;
+ }
+ }
+
+ 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. */
+ {
+ char info[80];
+ int info_len = 0;
+ unsigned i;
+
+ for (i=0; i<sess_info.stream_cnt; ++i) {
+ int len;
+ const char *dir;
+ pjmedia_stream_info *strm_info = &sess_info.stream_info[i];
+
+ switch (strm_info->dir) {
+ case PJMEDIA_DIR_NONE:
+ dir = "inactive";
+ break;
+ case PJMEDIA_DIR_ENCODING:
+ dir = "sendonly";
+ break;
+ case PJMEDIA_DIR_DECODING:
+ dir = "recvonly";
+ break;
+ case PJMEDIA_DIR_ENCODING_DECODING:
+ dir = "sendrecv";
+ break;
+ default:
+ dir = "unknown";
+ break;
+ }
+ len = pj_ansi_sprintf( info+info_len,
+ ", stream #%d: %.*s (%s)", i,
+ (int)strm_info->fmt.encoding_name.slen,
+ strm_info->fmt.encoding_name.ptr,
+ dir);
+ if (len > 0)
+ info_len += len;
+ }
+ 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;
}
-/**
- * Replace media transport.
+
+/*
+ * Called when session received new offer.
*/
-PJ_DEF(pj_status_t) pjsua_set_call_media_transport( unsigned call_index,
- const pjmedia_sock_info *i,
- pjmedia_transport *tp)
+static void pjsua_call_on_rx_offer(pjsip_inv_session *inv,
+ const pjmedia_sdp_session *offer)
{
- pjsua_call *call = &pjsua.calls[call_index];
+ const char *remote_state;
+ pjsua_call *call;
+ 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;
+ }
- if (i)
- pj_memcpy(&call->skinfo, i, sizeof(pjmedia_sock_info));
+ 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)
+{
- if (call->med_tp)
- (*call->med_tp->op->destroy)(call->med_tp);
+ PJ_UNUSED_ARG(event);
- call->med_tp = tp;
- return PJ_SUCCESS;
+ /*
+ * 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;
+ 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;
+ }
+
+ /* 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;
+ }
+
+ 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_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;
+ }
+
+ /* 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_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;
+ }
+
+ /* 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);
+ }
+ }
+ }
+
+
+ PJSUA_UNLOCK();
}
diff --git a/pjsip/src/pjsua-lib/pjsua_console_app.c b/pjsip/src/pjsua-lib/pjsua_console_app.c
deleted file mode 100644
index a54dd689..00000000
--- a/pjsip/src/pjsua-lib/pjsua_console_app.c
+++ /dev/null
@@ -1,926 +0,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,
-};
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 01b6c8c4..e269fa07 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -17,178 +17,118 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjsua-lib/pjsua.h>
-#include "pjsua_imp.h"
+#include <pjsua-lib/pjsua_internal.h>
-/*
- * pjsua_core.c
- *
- * Core application functionalities.
- */
#define THIS_FILE "pjsua_core.c"
-/*
- * Global variable.
- */
-struct pjsua pjsua;
+/* PJSUA application instance. */
+struct pjsua_data pjsua_var;
-/*
- * Default local URI, if none is specified in cmd-line
- */
-#define PJSUA_LOCAL_URI "<sip:user@127.0.0.1>"
+/* 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));
+}
-/*
- * Init default application parameters.
- */
-PJ_DEF(void) pjsua_default_config(pjsua_config *cfg)
+static void init_data()
{
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;
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i)
+ pjsua_var.acc[i].index = i;
- cfg->auto_answer = 100;
- cfg->uas_duration = 3600;
-
- /* Default logging settings: */
- cfg->log_level = 5;
- cfg->app_log_level = 4;
- cfg->log_decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME |
- PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE;
-
-
- /* Also init logging settings in pjsua.config, because log
- * may be written before pjsua_init() is called.
- */
- pjsua.config.log_level = 5;
- pjsua.config.app_log_level = 4;
-
-
- /* Init accounts: */
- for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) {
- cfg->acc_config[i].reg_timeout = 55;
- }
-
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.tpdata); ++i)
+ pjsua_var.tpdata[i].index = i;
}
-#define strncpy_with_null(dst,src,len) \
-do { \
- strncpy(dst, src, len); \
- dst[len-1] = '\0'; \
-} while (0)
-
+/*****************************************************************************
+ * 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.
+ *
+ */
-PJ_DEF(pj_status_t) pjsua_test_config( const pjsua_config *cfg,
- char *errmsg,
- int len)
+/* Notification on incoming messages */
+static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
{
- 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;
- }
+ 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;
+}
- if (acc_cfg->cred_info[j].username.slen == 0) {
- strncpy_with_null(errmsg, "missing username in account", len);
- return -1;
- }
+/* 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. */
+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.
+ */
/*
* Handler for receiving incoming requests.
@@ -201,13 +141,18 @@ PJ_DEF(pj_status_t) pjsua_test_config( const pjsua_config *cfg,
*/
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);
+ processed = pjsua_call_on_incoming(rdata);
}
- return PJ_FALSE;
+ PJSUA_UNLOCK();
+
+ return processed;
}
@@ -229,742 +174,199 @@ static pj_bool_t mod_pjsua_on_rx_response(pjsip_rx_data *rdata)
}
-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.
+/*****************************************************************************
+ * Logging.
*/
-PJ_DECL(int) pjsua_handle_events(unsigned msec_timeout)
+
+/* Log callback */
+static void log_writer(int level, const char *buffer, int len)
{
- unsigned count = 0;
- pj_time_val tv;
- pj_status_t status;
+ /* Write to stdout, file, and application callback. */
- tv.sec = 0;
- tv.msec = msec_timeout;
- pj_time_val_normalize(&tv);
+ if (level <= (int)pjsua_var.log_cfg.console_level)
+ pj_log_write(level, buffer, len);
- status = pjsip_endpt_handle_events2(pjsua.endpt, &tv, &count);
- if (status != PJ_SUCCESS)
- return -status;
+ if (pjsua_var.log_file) {
+ pj_ssize_t size = len;
+ pj_file_write(pjsua_var.log_file, buffer, &size);
+ }
- return count;
+ if (pjsua_var.log_cfg.cb)
+ (*pjsua_var.log_cfg.cb)(level, buffer, len);
}
-#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).
+ * Application can call this function at any time (after pjsua_create(), of
+ * course) to change logging settings.
*/
-static pj_status_t create_sip_udp_sock(int port,
- pj_sock_t *p_sock,
- pj_sockaddr_in *p_pub_addr)
+PJ_DEF(pj_status_t) pjsua_reconfigure_logging(const pjsua_logging_config *cfg)
{
- 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;
- }
+ /* Save config. */
+ pjsua_logging_config_dup(pjsua_var.pool, &pjsua_var.log_cfg, cfg);
- status = pj_sock_bind_in(sock, 0, (pj_uint16_t)port);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "bind() error", status);
- pj_sock_close(sock);
- return status;
- }
-
- if (pjsua_has_stun()) {
- status = pj_stun_get_mapped_addr(&pjsua.cp.factory, 1, &sock,
- &pjsua.config.stun_srv1,
- pjsua.config.stun_port1,
- &pjsua.config.stun_srv2,
- pjsua.config.stun_port2,
- p_pub_addr);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "STUN resolve error", status);
- pj_sock_close(sock);
- return status;
- }
-
- } else {
-
- const pj_str_t *hostname = pj_gethostname();
- struct pj_hostent he;
-
- status = pj_gethostbyname(hostname, &he);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Unable to resolve local host", status);
- pj_sock_close(sock);
- return status;
- }
+ /* Redirect log function to ours */
+ pj_log_set_log_func( &log_writer );
- 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;
+ /* Close existing file, if any */
+ if (pjsua_var.log_file) {
+ pj_file_close(pjsua_var.log_file);
+ pjsua_var.log_file = NULL;
}
- *p_sock = sock;
- return PJ_SUCCESS;
-}
-
-
-/*
- * Create RTP and RTCP socket pair, and possibly resolve their public
- * address via STUN.
- */
-static pj_status_t create_rtp_rtcp_sock(pjmedia_sock_info *skinfo)
-{
- enum {
- RTP_RETRY = 100
- };
- int i;
- static pj_uint16_t rtp_port;
- pj_sockaddr_in mapped_addr[2];
- pj_status_t status = PJ_SUCCESS;
- pj_sock_t sock[2];
-
- if (rtp_port == 0)
- rtp_port = (pj_uint16_t)pjsua.config.start_rtp_port;
-
- for (i=0; i<2; ++i)
- sock[i] = PJ_INVALID_SOCKET;
-
-
- /* Loop retry to bind RTP and RTCP sockets. */
- for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) {
-
- /* Create and bind RTP socket. */
- status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[0]);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "socket() error", status);
- return status;
- }
+ /* If output log file is desired, create the file: */
+ if (pjsua_var.log_cfg.log_filename.slen) {
- 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;
- }
+ status = pj_file_open(pjsua_var.pool,
+ pjsua_var.log_cfg.log_filename.ptr,
+ PJ_O_WRONLY,
+ &pjsua_var.log_file);
- /* 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]);
+ pjsua_perror(THIS_FILE, "Error creating log file", status);
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;
+ /* 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);
- skinfo->rtp_sock = sock[0];
- pj_memcpy(&skinfo->rtp_addr_name,
- &mapped_addr[0], sizeof(pj_sockaddr_in));
-
- skinfo->rtcp_sock = sock[1];
- pj_memcpy(&skinfo->rtcp_addr_name,
- &mapped_addr[1], sizeof(pj_sockaddr_in));
-
- PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d",
- pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr),
- pj_ntohs(skinfo->rtp_addr_name.sin_port)));
- PJ_LOG(4,(THIS_FILE, "RTCP socket reachable at %s:%d",
- pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr),
- pj_ntohs(skinfo->rtcp_addr_name.sin_port)));
- rtp_port += 2;
return PJ_SUCCESS;
-
-on_error:
- for (i=0; i<2; ++i) {
- if (sock[i] != PJ_INVALID_SOCKET)
- pj_sock_close(sock[i]);
- }
- return status;
}
-
-/**
- * Create pjsua application.
- * This initializes pjlib/pjlib-util, and creates memory pool factory to
- * be used by application.
+/*****************************************************************************
+ * PJSUA Base API.
*/
-PJ_DEF(pj_status_t) pjsua_create(void)
-{
- pj_status_t status;
- /* Init PJLIB: */
-
- status = pj_init();
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "pj_init() error", status);
- return status;
- }
-
- /* Init PJLIB-UTIL: */
-
- status = pjlib_util_init();
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "pjlib_util_init() error", status);
- return status;
- }
-
- /* Init memory pool: */
-
- /* Init caching pool. */
- pj_caching_pool_init(&pjsua.cp, &pj_pool_factory_default_policy, 0);
-
- /* Create memory pool for application. */
- pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL);
+/* Worker thread function. */
+static int worker_thread(void *arg)
+{
+ enum { TIMEOUT = 10 };
- /* Must create endpoint to initialize SIP parser. */
- /* Create global endpoint: */
+ PJ_UNUSED_ARG(arg);
- 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;
- }
+ while (!pjsua_var.thread_quit_flag) {
+ int count;
- /* 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;
+ count = pjsua_handle_events(TIMEOUT);
+ if (count < 0)
+ pj_thread_sleep(TIMEOUT);
}
-
- return PJ_SUCCESS;
+ return 0;
}
-
/*
- * Init media.
+ * Instantiate pjsua application.
*/
-static pj_status_t init_media(void)
+PJ_DEF(pj_status_t) pjsua_create(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;
- }
+ /* Init pjsua data */
+ init_data();
- /* 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);
+ /* Set default logging settings */
+ pjsua_logging_config_default(&pjsua_var.log_cfg);
-#endif /* PJMEDIA_HAS_L16_CODEC */
+ /* Init PJLIB: */
+ status = pj_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
- /* 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 PJLIB-UTIL: */
+ status = pjlib_util_init();
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
- /* Init options for conference bridge. */
- options = PJMEDIA_CONF_NO_DEVICE;
+ /* Init caching pool. */
+ pj_caching_pool_init(&pjsua_var.cp, &pj_pool_factory_default_policy, 0);
- /* Calculate maximum number of ports, if it's not specified */
- if (pjsua.config.conf_ports == 0) {
- pjsua.config.conf_ports = 3 * pjsua.config.max_calls;
- }
+ /* Create memory pool for application. */
+ pjsua_var.pool = pjsua_pool_create("pjsua", 4000, 4000);
+
+ PJ_ASSERT_RETURN(pjsua_var.pool, PJ_ENOMEM);
- /* 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);
+ /* Create mutex */
+ status = pj_mutex_create_recursive(pjsua_var.pool, "pjsua",
+ &pjsua_var.mutex);
if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE,
- "Media stack initialization has returned error",
- status);
+ pjsua_perror(THIS_FILE, "Unable to create mutex", 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;
- }
- }
+ /* 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);
- /* 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.
+ * 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_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg,
- const pjsua_callback *cb)
+PJ_DEF(pj_status_t) pjsua_init( const pjsua_config *ua_cfg,
+ const pjsua_logging_config *log_cfg,
+ const pjsua_media_config *media_cfg)
{
- char errmsg[80];
- unsigned i;
+ pjsua_config default_cfg;
+ pjsua_media_config default_media_cfg;
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));
+ /* Create default configurations when the config is not supplied */
- /* 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;
+ if (ua_cfg == NULL) {
+ pjsua_config_default(&default_cfg);
+ ua_cfg = &default_cfg;
}
-
- /* 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;
+ if (media_cfg == NULL) {
+ pjsua_media_config_default(&default_media_cfg);
+ media_cfg = &default_media_cfg;
}
-
- /* 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);
+ /* 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 PJSIP : */
+ /* Init SIP UA: */
/* Initialize transaction layer: */
+ status = pjsip_tsx_layer_init_module(pjsua_var.endpt);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
- status = pjsip_tsx_layer_init_module(pjsua.endpt);
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Transaction layer initialization error",
- status);
- goto on_error;
- }
/* Initialize UA layer module: */
+ status = pjsip_ua_init_module( pjsua_var.endpt, NULL );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
- 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: */
+ /* Initialize and register PJSUA application module. */
{
- pjsip_module my_mod =
+ const pjsip_module mod_initializer =
{
NULL, NULL, /* prev, next. */
{ "mod-pjsua", 9 }, /* Name. */
@@ -981,47 +383,70 @@ PJ_DECL(pj_status_t) pjsua_init(const pjsua_config *cfg,
NULL, /* on_tsx_state() */
};
- pjsua.mod = my_mod;
+ pjsua_var.mod = mod_initializer;
- 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;
- }
+ status = pjsip_endpt_register_module(pjsua_var.endpt, &pjsua_var.mod);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
}
- /* Initialize invite session module: */
+
- status = pjsua_call_init();
- if (status != PJ_SUCCESS) {
- pjsua_perror(THIS_FILE, "Invite usage initialization error",
- 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);
- pjsip_evsub_init_module(pjsua.endpt);
/* Init presence module: */
+ status = pjsip_pres_init_module( pjsua_var.endpt, pjsip_evsub_instance());
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
- pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance());
/* Init xfer/REFER module */
-
- pjsip_xfer_init_module( pjsua.endpt );
+ status = pjsip_xfer_init_module( pjsua_var.endpt );
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
/* Init pjsua presence handler: */
-
- pjsua_pres_init();
+ 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;
- pjsua_im_init();
+ /* 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;
+ }
+ }
+
+ /* Done! */
+
+ PJ_LOG(3,(THIS_FILE, "pjsua version %s for %s initialized",
+ PJ_VERSION, PJ_OS_NAME));
- /* Done. */
return PJ_SUCCESS;
on_error:
@@ -1030,836 +455,600 @@ on_error:
}
-/*
- * Find account for incoming request.
- */
-PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata)
+/* Sleep with polling */
+static void busy_sleep(unsigned msec)
{
- 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];
+ pj_time_val timeout, now;
- if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) {
- /* Match ! */
- return acc_index;
- }
- }
+ pj_gettimeofday(&timeout);
+ timeout.msec += msec;
+ pj_time_val_normalize(&timeout);
- /* Still no match, use default account */
- return pjsua.default_acc;
+ do {
+ while (pjsua_handle_events(10) > 0)
+ ;
+ pj_gettimeofday(&now);
+ } while (PJ_TIME_VAL_LT(now, timeout));
}
-
/*
- * Find account for outgoing request.
+ * Destroy pjsua.
*/
-PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *str_url)
+PJ_DEF(pj_status_t) pjsua_destroy(void)
{
- pj_str_t tmp;
- pjsip_uri *uri;
- pjsip_sip_uri *sip_uri;
- unsigned i;
-
- pj_strdup_with_null(pjsua.pool, &tmp, str_url);
+ int i; /* Must be signed */
- uri = pjsip_parse_uri(pjsua.pool, tmp.ptr, tmp.slen, 0);
- if (!uri)
- return pjsua.config.acc_cnt-1;
+ /* Signal threads to quit: */
+ pjsua_var.thread_quit_flag = 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;
+ /* 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;
}
-
- 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 (pjsua_var.endpt) {
+ /* Terminate all calls. */
+ pjsua_call_hangup_all();
- if (i != PJ_ARRAY_SIZE(pjsua.acc))
- return i;
+ /* Terminate all presence subscriptions. */
+ pjsua_pres_shutdown();
- /* Just use default account */
- return pjsua.default_acc;
-}
+ /* Unregister, if required: */
+ 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);
+ }
+ }
-/*
- * 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;
+ /* Wait for some time to allow unregistration to complete: */
+ PJ_LOG(4,(THIS_FILE, "Shutting down..."));
+ busy_sleep(1000);
}
- /* Local URI MUST be a SIP or SIPS: */
+ /* Destroy media */
+ pjsua_media_subsys_destroy();
- 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;
+ /* Destroy endpoint. */
+ if (pjsua_var.endpt) {
+ pjsip_endpt_destroy(pjsua_var.endpt);
+ pjsua_var.endpt = NULL;
}
-
- /* 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);
-
+ /* Destroy mutex */
+ if (pjsua_var.mutex) {
+ pj_mutex_destroy(pjsua_var.mutex);
+ pjsua_var.mutex = NULL;
}
-
- /* 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);
+ /* 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);
}
- 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;
+ 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;
}
-/*
- * Add a new account.
+
+/**
+ * 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_acc_add( const pjsua_acc_config *cfg,
- pjsua_acc_id *acc_index)
+PJ_DEF(pj_status_t) pjsua_start(void)
{
- 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);
+ status = pjsua_call_subsys_start();
+ if (status != PJ_SUCCESS)
return status;
- }
- if (acc_index)
- *acc_index = index;
+ status = pjsua_media_subsys_start();
+ if (status != PJ_SUCCESS)
+ return status;
- pjsua.config.acc_cnt++;
+ status = pjsua_pres_start();
+ if (status != PJ_SUCCESS)
+ return status;
return PJ_SUCCESS;
}
-/*
- * Delete account.
+/**
+ * Poll pjsua for events, and if necessary block the caller thread for
+ * the specified maximum interval (in miliseconds).
*/
-PJ_DEF(pj_status_t) pjsua_acc_del(pjsua_acc_id acc_index)
+PJ_DEF(int) pjsua_handle_events(unsigned msec_timeout)
{
- PJ_ASSERT_RETURN(acc_index < (int)pjsua.config.acc_cnt,
- PJ_EINVAL);
- PJ_ASSERT_RETURN(pjsua.acc[acc_index].valid, PJ_EINVALIDOP);
+ unsigned count = 0;
+ pj_time_val tv;
+ pj_status_t status;
+
+ tv.sec = 0;
+ tv.msec = msec_timeout;
+ pj_time_val_normalize(&tv);
- /* Delete registration */
- if (pjsua.acc[acc_index].regc != NULL)
- pjsua_acc_set_registration(acc_index, PJ_FALSE);
+ status = pjsip_endpt_handle_events2(pjsua_var.endpt, &tv, &count);
- /* Invalidate */
- pjsua.acc[acc_index].valid = PJ_FALSE;
+ if (status != PJ_SUCCESS)
+ return -status;
- return PJ_SUCCESS;
+ return count;
}
/*
- * Start pjsua stack.
- * This will start the registration process, if registration is configured.
+ * Create memory pool.
*/
-PJ_DEF(pj_status_t) pjsua_start(void)
+PJ_DEF(pj_pool_t*) pjsua_pool_create( const char *name, pj_size_t init_size,
+ pj_size_t increment)
{
- int i; /* Must be signed */
- unsigned count;
- pj_status_t status = PJ_SUCCESS;
-
-
- /* Add UDP transport: */
- if (pjsua.sip_sock > 0) {
+ /* Pool factory is thread safe, no need to lock */
+ return pj_pool_create(&pjsua_var.cp.factory, name, init_size, increment,
+ NULL);
+}
- /* 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;
+/*
+ * 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;
+}
- /* Create UDP transport from previously created UDP socket: */
+/*
+ * 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;
+}
- 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;
+/*****************************************************************************
+ * PJSUA SIP Transport API.
+ */
- 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);
+/*
+ * 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;
- 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];
+ PJSUA_LOCK();
- acc_cfg->id = id;
- acc_cfg->contact = id;
- }
+ 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;
}
-
- /* 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++;
+ 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;
}
-
- /* 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;
- }
+ /* 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));
}
-
- /* 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]);
+ /* Get the published address, either by STUN or by resolving
+ * the name of local host.
+ */
+ 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.quit_flag = 1;
- for (--i; i>=0; --i) {
- pj_thread_join(pjsua.threads[i]);
- pj_thread_destroy(pjsua.threads[i]);
- }
- goto on_error;
+ pjsua_perror(THIS_FILE, "Error resolving with STUN", status);
+ pj_sock_close(sock);
+ goto on_return;
}
- }
- /* Start registration: */
+ } else {
- /* 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;
+ const pj_str_t *hostname = pj_gethostname();
+ struct pj_hostent he;
- /* Perform registration, if required. */
- if (pjsua.acc[i].regc) {
- pjsua_acc_set_registration(i, PJ_TRUE);
+ 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;
}
- }
-
- /* 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);
+ 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;
- /* Refresh presence */
- pjsua_pres_refresh();
+on_return:
+ PJSUA_UNLOCK();
- PJ_LOG(3,(THIS_FILE, "PJSUA version %s started", PJ_VERSION));
- return PJ_SUCCESS;
+ 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)));
-on_error:
- pjsua_destroy();
return status;
}
-/* Sleep with polling */
-static void busy_sleep(unsigned msec)
+/*
+ * 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)
{
- pj_time_val timeout, now;
+ pjsip_transport *tp;
+ unsigned id;
+ pj_status_t status;
- pj_gettimeofday(&timeout);
- timeout.msec += msec;
- pj_time_val_normalize(&timeout);
+ PJSUA_LOCK();
- do {
- pjsua_poll(NULL);
- 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;
-}
+ /* 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;
+ }
-/**
- * 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);
-}
+ /* 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;
+ }
-/**
- * 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;
+ /* 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;
- status = pjmedia_conf_get_port_info( pjsua.mconf, id, &cinfo);
- if (status != PJ_SUCCESS)
- return status;
+ addr_name.host = pj_str(pj_inet_ntoa(pub_addr.sin_addr));
+ addr_name.port = pj_ntohs(pub_addr.sin_port);
- 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;
+ /* 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;
}
+
+ } else {
+ status = PJSIP_EUNSUPTRANSPORT;
+ pjsua_perror(THIS_FILE, "Error creating transport", status);
+ goto on_return;
}
- return PJ_SUCCESS;
-}
+ /* Save the transport */
+ pjsua_var.tpdata[id].tp = tp;
+ /* Return the ID */
+ if (p_id) *p_id = id;
-/**
- * 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);
-}
+ status = PJ_SUCCESS;
+on_return:
-/**
- * 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);
-}
+ PJSUA_UNLOCK();
+ return PJ_SUCCESS;
+}
-/**
- * Create a file player.
+/*
+ * Register transport that has been created by application.
*/
-PJ_DEF(pj_status_t) pjsua_player_create( const pj_str_t *filename,
- pjsua_player_id *id)
+PJ_DEF(pj_status_t) pjsua_transport_register( pjsip_transport *tp,
+ pjsua_transport_id *p_id)
{
- unsigned slot;
- char path[128];
- pjmedia_port *port;
- pj_status_t status;
+ unsigned id;
- if (pjsua.player_cnt >= PJ_ARRAY_SIZE(pjsua.player))
- return PJ_ETOOMANY;
+ PJSUA_LOCK();
- 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;
+ /* Find empty transport slot */
+ for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) {
+ if (pjsua_var.tpdata[id].tp == NULL)
+ break;
+ }
- status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool,
- port, filename, &slot);
- if (status != PJ_SUCCESS) {
- pjmedia_port_destroy(port);
- return status;
+ if (id == PJ_ARRAY_SIZE(pjsua_var.tpdata)) {
+ pjsua_perror(THIS_FILE, "Error creating transport", PJ_ETOOMANY);
+ PJSUA_UNLOCK();
+ return PJ_ETOOMANY;
}
- pjsua.player[pjsua.player_cnt].port = port;
- pjsua.player[pjsua.player_cnt].slot = slot;
+ /* Save the transport */
+ pjsua_var.tpdata[id].tp = tp;
- if (id)
- *id = pjsua.player_cnt;
+ /* Return the ID */
+ if (p_id) *p_id = id;
- ++pjsua.player_cnt;
+ PJSUA_UNLOCK();
return PJ_SUCCESS;
}
-/**
- * Get conference port associated with player.
+/*
+ * Enumerate all transports currently created in the system.
*/
-PJ_DEF(pjsua_conf_port_id) pjsua_player_get_conf_port(pjsua_player_id id)
+PJ_DEF(pj_status_t) pjsua_enum_transports( pjsua_transport_id id[],
+ unsigned *p_count )
{
- PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.player), PJ_EINVAL);
- return pjsua.player[id].slot;
-}
-
+ unsigned i, count;
-/**
- * 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);
+ PJSUA_LOCK();
- return pjmedia_wav_player_port_set_pos(pjsua.player[id].port, samples);
-}
+ 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;
+ }
-/**
- * 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);
+ *p_count = count;
- 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--;
- }
+ PJSUA_UNLOCK();
return PJ_SUCCESS;
}
-/**
- * Create a file recorder.
+/*
+ * Get information about transports.
*/
-PJ_DEF(pj_status_t) pjsua_recorder_create( const pj_str_t *filename,
- pjsua_recorder_id *id)
+PJ_DEF(pj_status_t) pjsua_transport_get_info( pjsua_transport_id id,
+ pjsua_transport_info *info)
{
- unsigned slot;
- char path[128];
- pjmedia_port *port;
- pj_status_t status;
+ pjsip_transport *tp;
- 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;
- }
+ pj_memset(info, 0, sizeof(*info));
- pjsua.recorder[pjsua.recorder_cnt].port = port;
- pjsua.recorder[pjsua.recorder_cnt].slot = slot;
+ /* Make sure id is in range. */
+ PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.tpdata), PJ_EINVAL);
- if (*id)
- *id = pjsua.recorder_cnt;
+ /* Make sure that transport exists */
+ PJ_ASSERT_RETURN(pjsua_var.tpdata[id].tp != NULL, PJ_EINVAL);
- ++pjsua.recorder_cnt;
+ PJSUA_LOCK();
- return PJ_SUCCESS;
+ 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;
}
-/**
- * Get conference port associated with recorder.
+/*
+ * Disable a transport or re-enable it.
*/
-PJ_DEF(pjsua_conf_port_id) pjsua_recorder_get_conf_port(pjsua_recorder_id id)
+PJ_DEF(pj_status_t) pjsua_transport_set_enable( pjsua_transport_id id,
+ pj_bool_t enabled)
{
- PJ_ASSERT_RETURN(id>=0 && id < PJ_ARRAY_SIZE(pjsua.recorder), PJ_EINVAL);
- return pjsua.recorder[id].slot;
-}
+ /* 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);
-/**
- * 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--;
- }
+ /* To be done!! */
+ PJ_TODO(pjsua_transport_set_enable);
- return PJ_SUCCESS;
+ return PJ_EINVALIDOP;
}
-/**
- * Enum sound devices.
+
+/*
+ * Close the transport.
*/
-PJ_DEF(pj_status_t) pjsua_enum_snd_devices( unsigned *count,
- pjmedia_snd_dev_info info[])
+PJ_DEF(pj_status_t) pjsua_transport_close( pjsua_transport_id id,
+ pj_bool_t force )
{
- int i, dev_count;
+ /* Make sure id is in range. */
+ PJ_ASSERT_RETURN(id>=0 && id<PJ_ARRAY_SIZE(pjsua_var.tpdata), PJ_EINVAL);
- dev_count = pjmedia_snd_get_dev_count();
- if (dev_count > (int)*count)
- dev_count = *count;
+ /* Make sure that transport exists */
+ PJ_ASSERT_RETURN(pjsua_var.tpdata[id].tp != NULL, PJ_EINVAL);
- 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;
-}
+ /* To be done!! */
-/**
- * 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;
+ PJ_TODO(pjsua_transport_close);
+
+ return PJ_EINVALIDOP;
}
/*
- * Destroy pjsua.
+ * Add additional headers etc in msg_data specified by application
+ * when sending requests.
*/
-PJ_DEF(pj_status_t) pjsua_destroy(void)
+void pjsua_process_msg_data(pjsip_tx_data *tdata,
+ const pjsua_msg_data *msg_data)
{
- 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) {
- /* Terminate all calls. */
- pjsua_call_hangup_all();
+ pj_bool_t allow_body;
+ const pjsip_hdr *hdr;
- /* Terminate all presence subscriptions. */
- pjsua_pres_shutdown();
+ if (!msg_data)
+ return;
- /* Unregister, if required: */
- for (i=0; i<(int)pjsua.config.acc_cnt; ++i) {
- if (pjsua.acc[i].regc) {
- pjsua_acc_set_registration(i, PJ_FALSE);
- }
- }
+ hdr = msg_data->hdr_list.next;
+ while (hdr && hdr != &msg_data->hdr_list) {
+ pjsip_hdr *new_hdr;
- /* Wait for some time to allow unregistration to complete: */
- PJ_LOG(4,(THIS_FILE, "Shutting down..."));
- busy_sleep(1000);
- }
+ new_hdr = pjsip_hdr_clone(tdata->pool, hdr);
+ pjsip_msg_add_hdr(tdata->msg, new_hdr);
- /* If we have master port, destroying master port will recursively
- * destroy conference bridge, otherwise must destroy it manually.
- */
- if (pjsua.master_port) {
- pjmedia_master_port_destroy(pjsua.master_port);
- pjsua.master_port = NULL;
- } else {
- if (pjsua.snd_port) {
- pjmedia_snd_port_destroy(pjsua.snd_port);
- pjsua.snd_port = NULL;
- }
- if (pjsua.mconf) {
- pjmedia_conf_destroy(pjsua.mconf);
- pjsua.mconf = NULL;
- }
+ hdr = hdr->next;
}
- /* 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;
- }
- }
+ 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;
- /* 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;
- }
+ 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;
}
+}
- /* 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;
- }
- }
+/*
+ * 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;
- /* Destroy media endpoint. */
- if (pjsua.med_endpt) {
+ r = route_set->next;
+ while (r != route_set) {
+ pjsip_route_hdr *new_r;
- /* Shutdown all codecs: */
-# if PJMEDIA_HAS_SPEEX_CODEC
- pjmedia_codec_speex_deinit();
-# endif /* PJMEDIA_HAS_SPEEX_CODEC */
+ new_r = pjsip_hdr_clone(tdata->pool, r);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)new_r);
-# if PJMEDIA_HAS_GSM_CODEC
- pjmedia_codec_gsm_deinit();
-# endif /* PJMEDIA_HAS_GSM_CODEC */
+ r = r->next;
+ }
+}
-# 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 */
+/*
+ * 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));
- pjmedia_endpt_destroy(pjsua.med_endpt);
- pjsua.med_endpt = NULL;
- }
+ pj_strdup_with_null(pool, &tmp, mime);
- /* Destroy endpoint. */
- if (pjsua.endpt) {
- pjsip_endpt_destroy(pjsua.endpt);
- pjsua.endpt = NULL;
+ 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;
}
+}
- /* Destroy caching pool. */
- pj_caching_pool_destroy(&pjsua.cp);
-
-
- PJ_LOG(4,(THIS_FILE, "PJSUA destroyed..."));
- /* End logging */
- logging_shutdown();
+/*
+ * 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);
- /* Done. */
+ if (!len) return -1;
- return PJ_SUCCESS;
-}
+ 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);
-/**
- * Get SIP endpoint instance.
- * Only valid after pjsua_init().
- */
-PJ_DEF(pjsip_endpoint*) pjsua_get_pjsip_endpt(void)
-{
- return pjsua.endpt;
-}
+ p = pjsip_parse_uri(pool, url, len, 0);
+ if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0)
+ p = NULL;
-/**
- * Get media endpoint instance.
- * Only valid after pjsua_init().
- */
-PJ_DEF(pjmedia_endpt*) pjsua_get_pjmedia_endpt(void)
-{
- return pjsua.med_endpt;
+ pj_pool_release(pool);
+ return p ? 0 : -1;
}
-
diff --git a/pjsip/src/pjsua-lib/pjsua_im.c b/pjsip/src/pjsua-lib/pjsua_im.c
index 807f2fac..9ef58a06 100644
--- a/pjsip/src/pjsua-lib/pjsua_im.c
+++ b/pjsip/src/pjsua-lib/pjsua_im.c
@@ -17,17 +17,10 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjsua-lib/pjsua.h>
-#include <pj/log.h>
-#include "pjsua_imp.h"
+#include <pjsua-lib/pjsua_internal.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"
+#define THIS_FILE "pjsua_im.h"
/* Declare MESSAGE method */
@@ -50,6 +43,7 @@ const pjsip_method pjsip_message_method =
/* Proto */
static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata);
+
/* The module instance. */
static pjsip_module mod_pjsua_im =
{
@@ -136,30 +130,48 @@ pj_bool_t pjsua_im_accept_pager(pjsip_rx_data *rdata,
* Private: process pager message.
* 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;
@@ -171,8 +183,10 @@ void pjsua_im_process_pager(int call_index, const pj_str_t *from,
return;
}
- 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);
+ }
}
}
@@ -210,7 +224,7 @@ static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata)
pj_list_init(&hdr_list);
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);
return PJ_TRUE;
@@ -219,7 +233,7 @@ static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata)
/* Respond with 200 first, so that remote doesn't retransmit in case
* 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);
/* For the source URI, we use Contact header if present, since
@@ -261,45 +275,158 @@ static pj_bool_t im_on_rx_request(pjsip_rx_data *rdata)
/* Outgoing IM callback. */
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));
}
+
+ 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);
}
}
-/**
- * Send IM outside dialog.
+/* Outgoing typing indication callback.
+ * (used to reauthenticate request)
*/
-PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const pj_str_t *dst_uri,
- const pj_str_t *str)
+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);
return status;
@@ -313,27 +440,46 @@ PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const pj_str_t *dst_uri,
pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
pjsip_generic_string_hdr_create(tdata->pool,
&STR_CONTACT,
- &pjsua.config.acc_config[acc_index].contact));
+ &pjsua_var.acc[acc_id].cfg.contact));
- /* Duplicate text.
- * We need to keep the text because we will display it when we fail to
- * send the message.
+ /* 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);
pjsip_tx_data_dec_ref(tdata);
return PJ_ENOMEM;
}
+ /* 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);
return status;
@@ -343,21 +489,23 @@ PJ_DEF(pj_status_t) pjsua_im_send(int acc_index, const pj_str_t *dst_uri,
}
-/**
+/*
* 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);
return status;
@@ -373,16 +521,23 @@ PJ_DEF(pj_status_t) pjsua_im_typing(int acc_index, const pj_str_t *dst_uri,
pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)
pjsip_generic_string_hdr_create(tdata->pool,
&STR_CONTACT,
- &pjsua.config.acc_config[acc_index].contact));
+ &pjsua_var.acc[acc_id].cfg.contact));
/* 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);
+
+ /* 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);
return status;
@@ -401,12 +556,12 @@ pj_status_t pjsua_im_init(void)
pj_status_t status;
/* 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);
return PJ_SUCCESS;
diff --git a/pjsip/src/pjsua-lib/pjsua_imp.h b/pjsip/src/pjsua-lib/pjsua_imp.h
deleted file mode 100644
index 117a2d12..00000000
--- a/pjsip/src/pjsua-lib/pjsua_imp.h
+++ /dev/null
@@ -1,259 +0,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__ */
-
diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c
new file mode 100644
index 00000000..9fae480e
--- /dev/null
+++ b/pjsip/src/pjsua-lib/pjsua_media.c
@@ -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;
+}
+
+
diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c
index b24ffd3c..b1764662 100644
--- a/pjsip/src/pjsua-lib/pjsua_pres.c
+++ b/pjsip/src/pjsua-lib/pjsua_pres.c
@@ -17,21 +17,374 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjsua-lib/pjsua.h>
-#include "pjsua_imp.h"
+#include <pjsua-lib/pjsua_internal.h>
+
+
+#define THIS_FILE "pjsua_pres.c"
+
/*
- * pjsua_pres.c
- *
- * Presence related stuffs.
+ * Get total number of buddies.
*/
+PJ_DEF(unsigned) pjsua_get_buddy_count(void)
+{
+ return pjsua_var.buddy_cnt;
+}
-#define THIS_FILE "pjsua_pres.c"
+/*
+ * 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:"));
-/* **************************************************************************
- * THE FOLLOWING PART HANDLES SERVER SUBSCRIPTION
- * **************************************************************************
+ 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.
*/
/* Proto */
@@ -60,19 +413,24 @@ static pjsip_module mod_pjsua_pres =
/* Callback called when *server* subscription state has changed. */
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",
uapres->remote, pjsip_evsub_get_state_name(sub)));
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();
}
/* This is called when request is received.
@@ -80,7 +438,7 @@ static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event)
*/
static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
{
- int acc_index;
+ int acc_id;
pjsua_acc_config *acc_config;
pjsip_method *req_method = &rdata->msg_info.msg->line.req.method;
pjsua_srv_pres *uapres;
@@ -96,9 +454,11 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
/* 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: */
status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata,
@@ -108,7 +468,8 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
pjsua_perror(THIS_FILE,
"Unable to create UAS dialog for subscription",
status);
- return PJ_FALSE;
+ PJSUA_UNLOCK();
+ return PJ_TRUE;
}
/* Init callback: */
@@ -121,7 +482,8 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
pjsip_dlg_terminate(dlg);
pjsua_perror(THIS_FILE, "Unable to create server subscription",
status);
- return PJ_FALSE;
+ PJSUA_UNLOCK();
+ return PJ_TRUE;
}
/* Attach our data to the subscription: */
@@ -135,10 +497,10 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
else
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);
/* Create and send 200 (OK) to the SUBSCRIBE request: */
@@ -148,6 +510,7 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
status);
pj_list_erase(uapres);
pjsip_pres_terminate(sub, PJ_FALSE);
+ PJSUA_UNLOCK();
return PJ_FALSE;
}
@@ -155,10 +518,10 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
/* Set our online status: */
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);
@@ -173,45 +536,68 @@ static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata)
status);
pj_list_erase(uapres);
pjsip_pres_terminate(sub, PJ_FALSE);
+ PJSUA_UNLOCK();
return PJ_FALSE;
}
/* 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;
+ uapres = pjsua_var.acc[acc_id].pres_srv_list.next;
- while (uapres != &pjsua.acc[acc_index].pres_srv_list) {
+ while (uapres != &pjsua_var.acc[acc_id].pres_srv_list) {
pjsip_pres_status pres_status;
pjsip_tx_data *tdata;
pjsip_pres_get_status(uapres->sub, &pres_status);
- if (pres_status.info[0].basic_open != pjsua.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);
}
uapres = uapres->next;
@@ -220,9 +606,8 @@ static void refresh_server_subscription(int acc_index)
-/* **************************************************************************
- * THE FOLLOWING PART HANDLES CLIENT SUBSCRIPTION
- * **************************************************************************
+/***************************************************************************
+ * Client subscription.
*/
/* Callback called when *client* subscription state has changed. */
@@ -232,26 +617,86 @@ static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event)
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)));
if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) {
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 */
static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub,
pjsip_rx_data *rdata,
@@ -262,7 +707,9 @@ static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub,
{
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. */
pjsip_pres_get_status(sub, &buddy->status);
@@ -276,6 +723,8 @@ static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub,
PJ_UNUSED_ARG(p_st_text);
PJ_UNUSED_ARG(res_hdr);
PJ_UNUSED_ARG(p_body);
+
+ PJSUA_UNLOCK();
}
@@ -283,8 +732,7 @@ static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub,
static pjsip_evsub_user pres_callback =
{
&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
* we want to authenticate
@@ -304,20 +752,23 @@ static pjsip_evsub_user pres_callback =
/* It does what it says.. */
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]);
+ buddy = &pjsua_var.buddy[index];
+ acc_id = pjsua_acc_find_for_outgoing(&buddy->uri);
- acc_config = &pjsua.config.acc_config[acc_index];
+ 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) {
pjsua_perror(THIS_FILE, "Unable to create dialog",
@@ -325,38 +776,42 @@ static void subscribe_buddy_presence(unsigned index)
return;
}
- 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);
pjsip_dlg_terminate(dlg);
return;
}
- pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id,
- &pjsua.buddies[index]);
+ pjsip_evsub_set_mod_data(buddy->sub, pjsua_var.mod.id, buddy);
- status = pjsip_pres_initiate(pjsua.buddies[index].sub, -1, &tdata);
+ 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);
return;
}
- 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);
return;
@@ -367,27 +822,27 @@ static void subscribe_buddy_presence(unsigned index)
/* It does what it says... */
static void unsubscribe_buddy_presence(unsigned index)
{
+ pjsua_buddy *buddy;
pjsip_tx_data *tdata;
pj_status_t status;
- if (pjsua.buddies[index].sub == NULL)
+ buddy = &pjsua_var.buddy[index];
+
+ if (buddy->sub == NULL)
return;
- if (pjsip_evsub_get_state(pjsua.buddies[index].sub) ==
- PJSIP_EVSUB_STATE_TERMINATED)
- {
- pjsua.buddies[index].sub = NULL;
+ if (pjsip_evsub_get_state(buddy->sub) == PJSIP_EVSUB_STATE_TERMINATED) {
+ pjsua_var.buddy[index].sub = NULL;
return;
}
- status = pjsip_pres_initiate( pjsua.buddies[index].sub, 0, &tdata);
+ 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);
}
@@ -395,16 +850,19 @@ static void unsubscribe_buddy_presence(unsigned index)
/* 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) {
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
- if (pjsua.buddies[i].monitor && !pjsua.buddies[i].sub) {
+ 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);
}
@@ -417,182 +875,46 @@ static void refresh_client_subscription(void)
*/
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",
status);
}
- 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");
- }
-
- 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;
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+ reset_buddy(i);
}
- /* 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;
+ return status;
}
-
-/**
- * Delete buddy.
+/*
+ * Start presence subsystem.
*/
-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;
- 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_status_t pjsua_pres_start(void)
{
- 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();
+ /* Nothing to do (is it?) */
return PJ_SUCCESS;
}
/*
- * Refresh presence
+ * Refresh presence subscriptions
*/
-PJ_DEF(void) pjsua_pres_refresh()
+void pjsua_pres_refresh()
{
unsigned i;
- refresh_client_subscription();
+ refresh_client_subscriptions();
- for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i)
- refresh_server_subscription(i);
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (pjsua_var.acc[i].valid)
+ refresh_server_subscription(i);
+ }
}
@@ -601,121 +923,17 @@ PJ_DEF(void) pjsua_pres_refresh()
*/
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<PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+ if (!pjsua_var.acc[i].valid)
+ continue;
+ pjsua_pres_delete_acc(i);
}
- for (i=0; i<pjsua.config.buddy_cnt; ++i) {
- pjsua.buddies[i].monitor = 0;
+ for (i=0; i<PJ_ARRAY_SIZE(pjsua_var.buddy); ++i) {
+ pjsua_var.buddy[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));
- }
- }
- }
-}
-
diff --git a/pjsip/src/pjsua-lib/pjsua_reg.c b/pjsip/src/pjsua-lib/pjsua_reg.c
deleted file mode 100644
index 54dbcbd2..00000000
--- a/pjsip/src/pjsua-lib/pjsua_reg.c
+++ /dev/null
@@ -1,281 +0,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;
-}
-
diff --git a/pjsip/src/pjsua-lib/pjsua_settings.c b/pjsip/src/pjsua-lib/pjsua_settings.c
deleted file mode 100644
index db30312d..00000000
--- a/pjsip/src/pjsua-lib/pjsua_settings.c
+++ /dev/null
@@ -1,1435 +0,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() */
-
-};
-