blob: 24868a9f02ec7ff1ab8fc8860912fee2d5c484c0 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsua-lib/pjsua.h>
20
21
22#define THIS_FILE "pjsua.c"
Benny Prijono804ff0a2006-09-14 11:17:48 +000023#define NO_LIMIT (int)0x7FFFFFFF
Benny Prijonoeebe9af2006-06-13 22:57:13 +000024
Benny Prijonoe909eac2006-07-27 22:04:56 +000025//#define STEREO_DEMO
Benny Prijonoeebe9af2006-06-13 22:57:13 +000026
Benny Prijono804ff0a2006-09-14 11:17:48 +000027/* Call specific data */
28struct call_data
29{
30 pj_timer_entry timer;
31};
32
Benny Prijonoeebe9af2006-06-13 22:57:13 +000033
34/* Pjsua application data */
35static struct app_config
36{
37 pjsua_config cfg;
38 pjsua_logging_config log_cfg;
39 pjsua_media_config media_cfg;
Benny Prijono4ddad2c2006-10-18 17:16:34 +000040 pj_bool_t no_refersub;
Benny Prijonoe93e2872006-06-28 16:46:49 +000041 pj_bool_t no_tcp;
42 pj_bool_t no_udp;
Benny Prijono6e0e54b2006-12-08 21:58:31 +000043 pj_bool_t use_tls;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000044 pjsua_transport_config udp_cfg;
45 pjsua_transport_config rtp_cfg;
46
47 unsigned acc_cnt;
48 pjsua_acc_config acc_cfg[PJSUA_MAX_ACC];
49
50 unsigned buddy_cnt;
51 pjsua_buddy_config buddy_cfg[PJSUA_MAX_BUDDIES];
52
Benny Prijono804ff0a2006-09-14 11:17:48 +000053 struct call_data call_data[PJSUA_MAX_CALLS];
54
Benny Prijonoeebe9af2006-06-13 22:57:13 +000055 pj_pool_t *pool;
56 /* Compatibility with older pjsua */
57
58 unsigned codec_cnt;
59 pj_str_t codec_arg[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +000060 pj_bool_t null_audio;
61 pj_str_t wav_file;
62 pjsua_player_id wav_id;
63 pjsua_conf_port_id wav_port;
64 pj_bool_t auto_play;
65 pj_bool_t auto_loop;
Benny Prijono7ca96da2006-08-07 12:11:40 +000066 pj_bool_t auto_conf;
Benny Prijono1ebd6142006-10-19 15:48:02 +000067 pj_str_t rec_file;
68 pj_bool_t auto_rec;
69 pjsua_recorder_id rec_id;
70 pjsua_conf_port_id rec_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000071 unsigned ptime;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000072 unsigned auto_answer;
73 unsigned duration;
Benny Prijonoe909eac2006-07-27 22:04:56 +000074
75#ifdef STEREO_DEMO
76 pjmedia_snd_port *snd;
77#endif
78
Benny Prijonoeebe9af2006-06-13 22:57:13 +000079} app_config;
80
81
Benny Prijono21b9ad92006-08-15 13:11:22 +000082//static pjsua_acc_id current_acc;
83#define current_acc pjsua_acc_get_default()
Benny Prijonof7b1c392006-11-11 16:46:34 +000084static pjsua_call_id current_call = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000085static pj_str_t uri_arg;
86
Benny Prijono594e4c52006-09-14 18:51:01 +000087#ifdef STEREO_DEMO
Benny Prijonoe909eac2006-07-27 22:04:56 +000088static void stereo_demo();
Benny Prijono594e4c52006-09-14 18:51:01 +000089#endif
Benny Prijonoe909eac2006-07-27 22:04:56 +000090
Benny Prijonoeebe9af2006-06-13 22:57:13 +000091/*****************************************************************************
92 * Configuration manipulation
93 */
94
95/* Show usage */
96static void usage(void)
97{
98 puts ("Usage:");
Benny Prijono0a5cad82006-09-26 13:21:02 +000099 puts (" pjsua [options] [SIP URL to call]");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000100 puts ("");
101 puts ("General options:");
Benny Prijono804ff0a2006-09-14 11:17:48 +0000102 puts (" --config-file=file Read the config/arguments from file.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000103 puts (" --help Display this help screen");
104 puts (" --version Display version info");
105 puts ("");
106 puts ("Logging options:");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000107 puts (" --log-file=fname Log to filename (default stderr)");
108 puts (" --log-level=N Set log max level to N (0(none) to 6(trace)) (default=5)");
109 puts (" --app-log-level=N Set log max level for stdout display (default=4)");
110 puts ("");
111 puts ("SIP Account options:");
112 puts (" --registrar=url Set the URL of registrar server");
113 puts (" --id=url Set the URL of local ID (used in From header)");
114 puts (" --contact=url Optionally override the Contact information");
115 puts (" --proxy=url Optional URL of proxy server to visit");
116 puts (" May be specified multiple times");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000117 puts (" --reg-timeout=SEC Optional registration interval (default 55)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000118 puts (" --realm=string Set realm");
119 puts (" --username=string Set authentication username");
120 puts (" --password=string Set authentication password");
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000121 puts (" --publish Send presence PUBLISH for this account");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000122 puts (" --next-cred Add another credentials");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000123 puts ("");
124 puts ("SIP Account Control:");
125 puts (" --next-account Add more account");
126 puts ("");
127 puts ("Transport Options:");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000128 puts (" --local-port=port Set TCP/UDP port. This implicitly enables both ");
129 puts (" TCP and UDP transports on the specified port, unless");
130 puts (" if TCP or UDP is disabled.");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000131 puts (" --ip-addr=IP Use the specifed address as SIP and RTP addresses.");
132 puts (" (Hint: the IP may be the public IP of the NAT/router)");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000133 puts (" --no-tcp Disable TCP transport.");
134 puts (" --no-udp Disable UDP transport.");
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000135 puts (" --nameserver=NS Add the specified nameserver to enable SRV resolution");
136 puts (" This option can be specified multiple times.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000137 puts (" --outbound=url Set the URL of global outbound proxy server");
138 puts (" May be specified multiple times");
139 puts (" --use-stun1=host[:port]");
140 puts (" --use-stun2=host[:port] Resolve local IP with the specified STUN servers");
Benny Prijonof3bbc132006-12-25 06:43:59 +0000141 puts ("");
142 puts ("TLS Options:");
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000143 puts (" --use-tls Enable TLS transport");
Benny Prijonof3bbc132006-12-25 06:43:59 +0000144 puts (" --tls-ca-file Specify TLS CA file (default=none)");
145 puts (" --tls-cert-file Specify TLS certificate file (default=none)");
146 puts (" --tls-privkey-file Specify TLS private key file (default=none)");
147 puts (" --tls-password Specify TLS password to private key file (default=none)");
148 puts (" --tls-verify-server Verify server's certificate (default=no)");
149 puts (" --tls-verify-client Verify client's certificate (default=no)");
150 puts (" --tls-neg-timeout Specify TLS negotiation timeout (default=no)");
151
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000152 puts ("");
153 puts ("Media Options:");
154 puts (" --add-codec=name Manually add codec (default is to enable all)");
155 puts (" --clock-rate=N Override sound device clock rate");
156 puts (" --null-audio Use NULL audio device");
Benny Prijono1ebd6142006-10-19 15:48:02 +0000157 puts (" --play-file=file Register WAV file in conference bridge");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000158 puts (" --auto-play Automatically play the file (to incoming calls only)");
159 puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP");
Benny Prijono7ca96da2006-08-07 12:11:40 +0000160 puts (" --auto-conf Automatically put calls in conference with others");
Benny Prijono1ebd6142006-10-19 15:48:02 +0000161 puts (" --rec-file=file Open file recorder (extension can be .wav or .mp3");
162 puts (" --auto-rec Automatically record conversation");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000163 puts (" --rtp-port=N Base port to try for RTP (default=4000)");
Benny Prijono00cae612006-07-31 15:19:36 +0000164 puts (" --quality=N Specify media quality (0-10, default=6)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000165 puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)");
Benny Prijono0a12f002006-07-26 17:05:39 +0000166 puts (" --no-vad Disable VAD/silence detector (default=vad enabled)");
Benny Prijonod79f25c2006-08-02 19:41:37 +0000167 puts (" --ec-tail=MSEC Set echo canceller tail length (default=256)");
Benny Prijono00cae612006-07-31 15:19:36 +0000168 puts (" --ilbc-mode=MODE Set iLBC codec mode (20 or 30, default is 20)");
169 puts (" --rx-drop-pct=PCT Drop PCT percent of RX RTP (for pkt lost sim, default: 0)");
170 puts (" --tx-drop-pct=PCT Drop PCT percent of TX RTP (for pkt lost sim, default: 0)");
171
Benny Prijono0a12f002006-07-26 17:05:39 +0000172
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000173 puts ("");
174 puts ("Buddy List (can be more than one):");
175 puts (" --add-buddy url Add the specified URL to the buddy list.");
176 puts ("");
177 puts ("User Agent options:");
178 puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)");
179 puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)");
Benny Prijonof521eb02006-08-06 23:07:25 +0000180 puts (" --thread-cnt=N Number of worker threads (default:1)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000181 puts (" --duration=SEC Set maximum call duration (default:no limit)");
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000182 puts (" --norefersub Suppress event subscription when transfering calls");
Benny Prijono804ff0a2006-09-14 11:17:48 +0000183
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000184 puts ("");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000185 puts ("When URL is specified, pjsua will immediately initiate call to that URL");
186 puts ("");
187
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000188 fflush(stdout);
189}
190
191
192/* Set default config. */
193static void default_config(struct app_config *cfg)
194{
Benny Prijono56315612006-07-18 14:39:40 +0000195 char tmp[80];
196
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000197 pjsua_config_default(&cfg->cfg);
Benny Prijono56315612006-07-18 14:39:40 +0000198 pj_ansi_sprintf(tmp, "PJSUA v%s/%s", PJ_VERSION, PJ_OS_NAME);
199 pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
200
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000201 pjsua_logging_config_default(&cfg->log_cfg);
202 pjsua_media_config_default(&cfg->media_cfg);
203 pjsua_transport_config_default(&cfg->udp_cfg);
204 cfg->udp_cfg.port = 5060;
205 pjsua_transport_config_default(&cfg->rtp_cfg);
206 cfg->rtp_cfg.port = 4000;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000207 cfg->duration = NO_LIMIT;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000208 cfg->wav_id = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000209 cfg->rec_id = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000210 cfg->wav_port = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000211 cfg->rec_port = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000212}
213
214
215/*
216 * Read command arguments from config file.
217 */
218static int read_config_file(pj_pool_t *pool, const char *filename,
219 int *app_argc, char ***app_argv)
220{
221 int i;
222 FILE *fhnd;
223 char line[200];
224 int argc = 0;
225 char **argv;
226 enum { MAX_ARGS = 64 };
227
228 /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
229 argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
230 argv[argc++] = *app_argv[0];
231
232 /* Open config file. */
233 fhnd = fopen(filename, "rt");
234 if (!fhnd) {
235 PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
236 fflush(stdout);
237 return -1;
238 }
239
240 /* Scan tokens in the file. */
241 while (argc < MAX_ARGS && !feof(fhnd)) {
242 char *token, *p = line;
243
244 if (fgets(line, sizeof(line), fhnd) == NULL) break;
245
246 for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS;
247 token = strtok(NULL, " \t\r\n"))
248 {
249 int token_len;
250
251 if (!token) break;
252 if (*token == '#') break;
253
254 token_len = strlen(token);
255 if (!token_len)
256 continue;
257 argv[argc] = pj_pool_alloc(pool, token_len+1);
258 pj_memcpy(argv[argc], token, token_len+1);
259 ++argc;
260 }
261 }
262
263 /* Copy arguments from command line */
264 for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
265 argv[argc++] = (*app_argv)[i];
266
267 if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
268 PJ_LOG(1,(THIS_FILE,
269 "Too many arguments specified in cmd line/config file"));
270 fflush(stdout);
271 fclose(fhnd);
272 return -1;
273 }
274
275 fclose(fhnd);
276
277 /* Assign the new command line back to the original command line. */
278 *app_argc = argc;
279 *app_argv = argv;
280 return 0;
281
282}
283
284static int my_atoi(const char *cs)
285{
286 pj_str_t s;
287 return pj_strtoul(pj_cstr(&s, cs));
288}
289
290
291/* Parse arguments. */
292static pj_status_t parse_args(int argc, char *argv[],
293 struct app_config *cfg,
294 pj_str_t *uri_to_call)
295{
296 int c;
297 int option_index;
298 enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
299 OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
Benny Prijono0a5cad82006-09-26 13:21:02 +0000300 OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY,
301 OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000302 OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000303 OPT_NAMESERVER, OPT_USE_STUN1, OPT_USE_STUN2,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000304 OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
305 OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
306 OPT_AUTO_CONF, OPT_CLOCK_RATE,
Benny Prijono00cae612006-07-31 15:19:36 +0000307 OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC, OPT_ILBC_MODE,
Benny Prijono1ebd6142006-10-19 15:48:02 +0000308 OPT_REC_FILE, OPT_AUTO_REC,
Benny Prijono0a12f002006-07-26 17:05:39 +0000309 OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
Benny Prijonod79f25c2006-08-02 19:41:37 +0000310 OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL,
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000311 OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
Benny Prijonof521eb02006-08-06 23:07:25 +0000312 OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000313 OPT_NOREFERSUB,
Benny Prijonof3bbc132006-12-25 06:43:59 +0000314 OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_CERT_FILE, OPT_TLS_PRIV_FILE,
315 OPT_TLS_PASSWORD, OPT_TLS_VERIFY_SERVER, OPT_TLS_VERIFY_CLIENT,
316 OPT_TLS_NEG_TIMEOUT,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000317 };
318 struct pj_getopt_option long_options[] = {
319 { "config-file",1, 0, OPT_CONFIG_FILE},
320 { "log-file", 1, 0, OPT_LOG_FILE},
321 { "log-level", 1, 0, OPT_LOG_LEVEL},
322 { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
323 { "help", 0, 0, OPT_HELP},
324 { "version", 0, 0, OPT_VERSION},
325 { "clock-rate", 1, 0, OPT_CLOCK_RATE},
326 { "null-audio", 0, 0, OPT_NULL_AUDIO},
327 { "local-port", 1, 0, OPT_LOCAL_PORT},
Benny Prijono0a5cad82006-09-26 13:21:02 +0000328 { "ip-addr", 1, 0, OPT_IP_ADDR},
Benny Prijonoe93e2872006-06-28 16:46:49 +0000329 { "no-tcp", 0, 0, OPT_NO_TCP},
330 { "no-udp", 0, 0, OPT_NO_UDP},
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000331 { "norefersub", 0, 0, OPT_NOREFERSUB},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000332 { "proxy", 1, 0, OPT_PROXY},
333 { "outbound", 1, 0, OPT_OUTBOUND_PROXY},
334 { "registrar", 1, 0, OPT_REGISTRAR},
335 { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000336 { "publish", 0, 0, OPT_PUBLISH},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000337 { "id", 1, 0, OPT_ID},
338 { "contact", 1, 0, OPT_CONTACT},
339 { "realm", 1, 0, OPT_REALM},
340 { "username", 1, 0, OPT_USERNAME},
341 { "password", 1, 0, OPT_PASSWORD},
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000342 { "nameserver", 1, 0, OPT_NAMESERVER},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000343 { "use-stun1", 1, 0, OPT_USE_STUN1},
344 { "use-stun2", 1, 0, OPT_USE_STUN2},
345 { "add-buddy", 1, 0, OPT_ADD_BUDDY},
346 { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
347 { "no-presence", 0, 0, OPT_NO_PRESENCE},
348 { "auto-answer",1, 0, OPT_AUTO_ANSWER},
349 { "auto-hangup",1, 0, OPT_AUTO_HANGUP},
350 { "auto-play", 0, 0, OPT_AUTO_PLAY},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000351 { "auto-rec", 0, 0, OPT_AUTO_REC},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000352 { "auto-loop", 0, 0, OPT_AUTO_LOOP},
353 { "auto-conf", 0, 0, OPT_AUTO_CONF},
354 { "play-file", 1, 0, OPT_PLAY_FILE},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000355 { "rec-file", 1, 0, OPT_REC_FILE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000356 { "rtp-port", 1, 0, OPT_RTP_PORT},
357 { "add-codec", 1, 0, OPT_ADD_CODEC},
358 { "complexity", 1, 0, OPT_COMPLEXITY},
359 { "quality", 1, 0, OPT_QUALITY},
360 { "ptime", 1, 0, OPT_PTIME},
Benny Prijono0a12f002006-07-26 17:05:39 +0000361 { "no-vad", 0, 0, OPT_NO_VAD},
Benny Prijonod79f25c2006-08-02 19:41:37 +0000362 { "ec-tail", 1, 0, OPT_EC_TAIL},
Benny Prijono00cae612006-07-31 15:19:36 +0000363 { "ilbc-mode", 1, 0, OPT_ILBC_MODE},
364 { "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
365 { "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000366 { "next-account",0,0, OPT_NEXT_ACCOUNT},
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000367 { "next-cred", 0, 0, OPT_NEXT_CRED},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000368 { "max-calls", 1, 0, OPT_MAX_CALLS},
Benny Prijonof521eb02006-08-06 23:07:25 +0000369 { "duration", 1, 0, OPT_DURATION},
370 { "thread-cnt", 1, 0, OPT_THREAD_CNT},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000371 { "use-tls", 0, 0, OPT_USE_TLS},
372 { "tls-ca-file",1, 0, OPT_TLS_CA_FILE},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000373 { "tls-cert-file",1,0, OPT_TLS_CERT_FILE},
374 { "tls-privkey-file",1,0, OPT_TLS_PRIV_FILE},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000375 { "tls-password",1,0, OPT_TLS_PASSWORD},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000376 { "tls-verify-server", 0, 0, OPT_TLS_VERIFY_SERVER},
377 { "tls-verify-client", 0, 0, OPT_TLS_VERIFY_CLIENT},
378 { "tls-neg-timeout", 1, 0, OPT_TLS_NEG_TIMEOUT},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000379 { NULL, 0, 0, 0}
380 };
381 pj_status_t status;
382 pjsua_acc_config *cur_acc;
383 char *config_file = NULL;
384 unsigned i;
385
386 /* Run pj_getopt once to see if user specifies config file to read. */
Benny Prijonof762ee72006-12-01 11:14:37 +0000387 pj_optind = 0;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000388 while ((c=pj_getopt_long(argc, argv, "", long_options,
389 &option_index)) != -1)
390 {
391 switch (c) {
392 case OPT_CONFIG_FILE:
393 config_file = pj_optarg;
394 break;
395 }
396 if (config_file)
397 break;
398 }
399
400 if (config_file) {
401 status = read_config_file(app_config.pool, config_file, &argc, &argv);
402 if (status != 0)
403 return status;
404 }
405
406 cfg->acc_cnt = 0;
407 cur_acc = &cfg->acc_cfg[0];
408
409
410 /* Reinitialize and re-run pj_getopt again, possibly with new arguments
411 * read from config file.
412 */
413 pj_optind = 0;
414 while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
415 char *p;
416 pj_str_t tmp;
417 long lval;
418
419 switch (c) {
420
Benny Prijono6f137482006-06-15 11:31:36 +0000421 case OPT_CONFIG_FILE:
422 /* Ignore as this has been processed before */
423 break;
424
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000425 case OPT_LOG_FILE:
426 cfg->log_cfg.log_filename = pj_str(pj_optarg);
427 break;
428
429 case OPT_LOG_LEVEL:
430 c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
431 if (c < 0 || c > 6) {
432 PJ_LOG(1,(THIS_FILE,
433 "Error: expecting integer value 0-6 "
434 "for --log-level"));
435 return PJ_EINVAL;
436 }
437 cfg->log_cfg.level = c;
438 pj_log_set_level( c );
439 break;
440
441 case OPT_APP_LOG_LEVEL:
442 cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
443 if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
444 PJ_LOG(1,(THIS_FILE,
445 "Error: expecting integer value 0-6 "
446 "for --app-log-level"));
447 return PJ_EINVAL;
448 }
449 break;
450
451 case OPT_HELP:
452 usage();
453 return PJ_EINVAL;
454
455 case OPT_VERSION: /* version */
456 pj_dump_config();
457 return PJ_EINVAL;
458
459 case OPT_NULL_AUDIO:
460 cfg->null_audio = PJ_TRUE;
461 break;
462
463 case OPT_CLOCK_RATE:
464 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
465 if (lval < 8000 || lval > 48000) {
466 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
467 "8000-48000 for clock rate"));
468 return PJ_EINVAL;
469 }
470 cfg->media_cfg.clock_rate = lval;
471 break;
472
473 case OPT_LOCAL_PORT: /* local-port */
474 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
475 if (lval < 1 || lval > 65535) {
476 PJ_LOG(1,(THIS_FILE,
477 "Error: expecting integer value for "
478 "--local-port"));
479 return PJ_EINVAL;
480 }
481 cfg->udp_cfg.port = (pj_uint16_t)lval;
482 break;
483
Benny Prijono0a5cad82006-09-26 13:21:02 +0000484 case OPT_IP_ADDR: /* ip-addr */
485 cfg->udp_cfg.public_addr = pj_str(pj_optarg);
486 cfg->rtp_cfg.public_addr = pj_str(pj_optarg);
487 break;
488
Benny Prijonoe93e2872006-06-28 16:46:49 +0000489 case OPT_NO_UDP: /* no-udp */
490 if (cfg->no_tcp) {
491 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
492 return PJ_EINVAL;
493 }
494
495 cfg->no_udp = PJ_TRUE;
496 break;
497
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000498 case OPT_NOREFERSUB: /* norefersub */
499 cfg->no_refersub = PJ_TRUE;
500 break;
501
Benny Prijonoe93e2872006-06-28 16:46:49 +0000502 case OPT_NO_TCP: /* no-tcp */
503 if (cfg->no_udp) {
504 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
505 return PJ_EINVAL;
506 }
507
508 cfg->no_tcp = PJ_TRUE;
509 break;
510
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000511 case OPT_PROXY: /* proxy */
512 if (pjsua_verify_sip_url(pj_optarg) != 0) {
513 PJ_LOG(1,(THIS_FILE,
514 "Error: invalid SIP URL '%s' "
515 "in proxy argument", pj_optarg));
516 return PJ_EINVAL;
517 }
518 cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
519 break;
520
521 case OPT_OUTBOUND_PROXY: /* outbound proxy */
522 if (pjsua_verify_sip_url(pj_optarg) != 0) {
523 PJ_LOG(1,(THIS_FILE,
524 "Error: invalid SIP URL '%s' "
525 "in outbound proxy argument", pj_optarg));
526 return PJ_EINVAL;
527 }
528 cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
529 break;
530
531 case OPT_REGISTRAR: /* registrar */
532 if (pjsua_verify_sip_url(pj_optarg) != 0) {
533 PJ_LOG(1,(THIS_FILE,
534 "Error: invalid SIP URL '%s' in "
535 "registrar argument", pj_optarg));
536 return PJ_EINVAL;
537 }
538 cur_acc->reg_uri = pj_str(pj_optarg);
539 break;
540
541 case OPT_REG_TIMEOUT: /* reg-timeout */
542 cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
543 if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
544 PJ_LOG(1,(THIS_FILE,
545 "Error: invalid value for --reg-timeout "
546 "(expecting 1-3600)"));
547 return PJ_EINVAL;
548 }
549 break;
550
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000551 case OPT_PUBLISH: /* publish */
552 cur_acc->publish_enabled = PJ_TRUE;
553 break;
554
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000555 case OPT_ID: /* id */
556 if (pjsua_verify_sip_url(pj_optarg) != 0) {
557 PJ_LOG(1,(THIS_FILE,
558 "Error: invalid SIP URL '%s' "
559 "in local id argument", pj_optarg));
560 return PJ_EINVAL;
561 }
562 cur_acc->id = pj_str(pj_optarg);
563 break;
564
565 case OPT_CONTACT: /* contact */
566 if (pjsua_verify_sip_url(pj_optarg) != 0) {
567 PJ_LOG(1,(THIS_FILE,
568 "Error: invalid SIP URL '%s' "
569 "in contact argument", pj_optarg));
570 return PJ_EINVAL;
571 }
Benny Prijonob4a17c92006-07-10 14:40:21 +0000572 cur_acc->force_contact = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000573 break;
574
575 case OPT_NEXT_ACCOUNT: /* Add more account. */
576 cfg->acc_cnt++;
Benny Prijono56315612006-07-18 14:39:40 +0000577 cur_acc = &cfg->acc_cfg[cfg->acc_cnt];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000578 break;
579
580 case OPT_USERNAME: /* Default authentication user */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000581 cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
582 cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("digest");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000583 break;
584
585 case OPT_REALM: /* Default authentication realm. */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000586 cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000587 break;
588
589 case OPT_PASSWORD: /* authentication password */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000590 cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
591 cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
592 break;
593
594 case OPT_NEXT_CRED: /* next credential */
595 cur_acc->cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000596 break;
597
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000598 case OPT_NAMESERVER: /* nameserver */
599 cfg->cfg.nameserver[cfg->cfg.nameserver_count++] = pj_str(pj_optarg);
600 if (cfg->cfg.nameserver_count > PJ_ARRAY_SIZE(cfg->cfg.nameserver)) {
601 PJ_LOG(1,(THIS_FILE, "Error: too many nameservers"));
602 return PJ_ETOOMANY;
603 }
604 break;
605
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000606 case OPT_USE_STUN1: /* STUN server 1 */
607 p = pj_ansi_strchr(pj_optarg, ':');
608 if (p) {
609 *p = '\0';
610 cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
611 cfg->udp_cfg.stun_config.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1));
612 if (cfg->udp_cfg.stun_config.stun_port1 < 1 || cfg->udp_cfg.stun_config.stun_port1 > 65535) {
613 PJ_LOG(1,(THIS_FILE,
614 "Error: expecting port number with "
615 "option --use-stun1"));
616 return PJ_EINVAL;
617 }
618 } else {
619 cfg->udp_cfg.stun_config.stun_port1 = 3478;
620 cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
621 }
622 cfg->udp_cfg.use_stun = PJ_TRUE;
623 break;
624
625 case OPT_USE_STUN2: /* STUN server 2 */
626 p = pj_ansi_strchr(pj_optarg, ':');
627 if (p) {
628 *p = '\0';
629 cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
630 cfg->udp_cfg.stun_config.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1));
631 if (cfg->udp_cfg.stun_config.stun_port2 < 1 || cfg->udp_cfg.stun_config.stun_port2 > 65535) {
632 PJ_LOG(1,(THIS_FILE,
633 "Error: expecting port number with "
634 "option --use-stun2"));
635 return PJ_EINVAL;
636 }
637 } else {
638 cfg->udp_cfg.stun_config.stun_port2 = 3478;
639 cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
640 }
641 break;
642
643 case OPT_ADD_BUDDY: /* Add to buddy list. */
644 if (pjsua_verify_sip_url(pj_optarg) != 0) {
645 PJ_LOG(1,(THIS_FILE,
646 "Error: invalid URL '%s' in "
647 "--add-buddy option", pj_optarg));
648 return -1;
649 }
650 if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
651 PJ_LOG(1,(THIS_FILE,
652 "Error: too many buddies in buddy list."));
653 return -1;
654 }
655 cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
656 cfg->buddy_cnt++;
657 break;
658
659 case OPT_AUTO_PLAY:
660 cfg->auto_play = 1;
661 break;
662
Benny Prijono1ebd6142006-10-19 15:48:02 +0000663 case OPT_AUTO_REC:
664 cfg->auto_rec = 1;
665 break;
666
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000667 case OPT_AUTO_LOOP:
668 cfg->auto_loop = 1;
669 break;
670
Benny Prijono7ca96da2006-08-07 12:11:40 +0000671 case OPT_AUTO_CONF:
672 cfg->auto_conf = 1;
673 break;
674
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000675 case OPT_PLAY_FILE:
676 cfg->wav_file = pj_str(pj_optarg);
677 break;
678
Benny Prijono1ebd6142006-10-19 15:48:02 +0000679 case OPT_REC_FILE:
680 cfg->rec_file = pj_str(pj_optarg);
681 break;
682
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000683 case OPT_RTP_PORT:
684 cfg->rtp_cfg.port = my_atoi(pj_optarg);
685 if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
686 PJ_LOG(1,(THIS_FILE,
687 "Error: rtp-port argument value "
688 "(expecting 1-65535"));
689 return -1;
690 }
691 break;
692
693 case OPT_ADD_CODEC:
694 cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
695 break;
696
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000697 /* These options were no longer valid after new pjsua */
698 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000699 case OPT_COMPLEXITY:
700 cfg->complexity = my_atoi(pj_optarg);
701 if (cfg->complexity < 0 || cfg->complexity > 10) {
702 PJ_LOG(1,(THIS_FILE,
703 "Error: invalid --complexity (expecting 0-10"));
704 return -1;
705 }
706 break;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000707 */
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000708
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000709 case OPT_DURATION:
710 cfg->duration = my_atoi(pj_optarg);
711 break;
712
Benny Prijonof521eb02006-08-06 23:07:25 +0000713 case OPT_THREAD_CNT:
714 cfg->cfg.thread_cnt = my_atoi(pj_optarg);
715 if (cfg->cfg.thread_cnt > 128) {
716 PJ_LOG(1,(THIS_FILE,
717 "Error: invalid --thread-cnt option"));
718 return -1;
719 }
720 break;
721
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000722 case OPT_PTIME:
Benny Prijono0a12f002006-07-26 17:05:39 +0000723 cfg->media_cfg.ptime = my_atoi(pj_optarg);
724 if (cfg->media_cfg.ptime < 10 || cfg->media_cfg.ptime > 1000) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000725 PJ_LOG(1,(THIS_FILE,
726 "Error: invalid --ptime option"));
727 return -1;
728 }
729 break;
730
Benny Prijono0a12f002006-07-26 17:05:39 +0000731 case OPT_NO_VAD:
732 cfg->media_cfg.no_vad = PJ_TRUE;
733 break;
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000734
Benny Prijonod79f25c2006-08-02 19:41:37 +0000735 case OPT_EC_TAIL:
736 cfg->media_cfg.ec_tail_len = my_atoi(pj_optarg);
737 if (cfg->media_cfg.ec_tail_len > 1000) {
738 PJ_LOG(1,(THIS_FILE, "I think the ec-tail length setting "
739 "is too big"));
740 return -1;
741 }
742 break;
743
Benny Prijono0498d902006-06-19 14:49:14 +0000744 case OPT_QUALITY:
745 cfg->media_cfg.quality = my_atoi(pj_optarg);
746 if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
747 PJ_LOG(1,(THIS_FILE,
748 "Error: invalid --quality (expecting 0-10"));
749 return -1;
750 }
751 break;
752
Benny Prijono00cae612006-07-31 15:19:36 +0000753 case OPT_ILBC_MODE:
754 cfg->media_cfg.ilbc_mode = my_atoi(pj_optarg);
755 if (cfg->media_cfg.ilbc_mode!=20 && cfg->media_cfg.ilbc_mode!=30) {
756 PJ_LOG(1,(THIS_FILE,
757 "Error: invalid --ilbc-mode (expecting 20 or 30"));
758 return -1;
759 }
760 break;
761
762 case OPT_RX_DROP_PCT:
763 cfg->media_cfg.rx_drop_pct = my_atoi(pj_optarg);
764 if (cfg->media_cfg.rx_drop_pct > 100) {
765 PJ_LOG(1,(THIS_FILE,
766 "Error: invalid --rx-drop-pct (expecting <= 100"));
767 return -1;
768 }
769 break;
770
771 case OPT_TX_DROP_PCT:
772 cfg->media_cfg.tx_drop_pct = my_atoi(pj_optarg);
773 if (cfg->media_cfg.tx_drop_pct > 100) {
774 PJ_LOG(1,(THIS_FILE,
775 "Error: invalid --tx-drop-pct (expecting <= 100"));
776 return -1;
777 }
778 break;
779
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000780 case OPT_AUTO_ANSWER:
781 cfg->auto_answer = my_atoi(pj_optarg);
782 if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
783 PJ_LOG(1,(THIS_FILE,
784 "Error: invalid code in --auto-answer "
785 "(expecting 100-699"));
786 return -1;
787 }
788 break;
789
790 case OPT_MAX_CALLS:
791 cfg->cfg.max_calls = my_atoi(pj_optarg);
Benny Prijono48af79c2006-07-22 12:49:17 +0000792 if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > PJSUA_MAX_CALLS) {
Benny Prijono804ff0a2006-09-14 11:17:48 +0000793 PJ_LOG(1,(THIS_FILE,"Error: maximum call setting exceeds "
794 "compile time limit (PJSUA_MAX_CALLS=%d)",
Benny Prijono48af79c2006-07-22 12:49:17 +0000795 PJSUA_MAX_CALLS));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000796 return -1;
797 }
798 break;
799
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000800 case OPT_USE_TLS:
801 cfg->use_tls = PJ_TRUE;
Benny Prijonof3bbc132006-12-25 06:43:59 +0000802#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
803 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
804 return -1;
805#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000806 break;
807
808 case OPT_TLS_CA_FILE:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000809 cfg->udp_cfg.tls_setting.ca_list_file = pj_str(pj_optarg);
810#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
811 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
812 return -1;
813#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000814 break;
815
Benny Prijonof3bbc132006-12-25 06:43:59 +0000816 case OPT_TLS_CERT_FILE:
817 cfg->udp_cfg.tls_setting.cert_file = pj_str(pj_optarg);
818#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
819 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
820 return -1;
821#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000822 break;
823
Benny Prijonof3bbc132006-12-25 06:43:59 +0000824 case OPT_TLS_PRIV_FILE:
825 cfg->udp_cfg.tls_setting.privkey_file = pj_str(pj_optarg);
826 break;
827
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000828 case OPT_TLS_PASSWORD:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000829 cfg->udp_cfg.tls_setting.password = pj_str(pj_optarg);
830#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
831 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
832 return -1;
833#endif
834 break;
835
836 case OPT_TLS_VERIFY_SERVER:
837 cfg->udp_cfg.tls_setting.verify_server = PJ_TRUE;
838 break;
839
840 case OPT_TLS_VERIFY_CLIENT:
841 cfg->udp_cfg.tls_setting.verify_client = PJ_TRUE;
842 break;
843
844 case OPT_TLS_NEG_TIMEOUT:
845 cfg->udp_cfg.tls_setting.timeout.sec = atoi(pj_optarg);
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000846 break;
847
Benny Prijono22a300a2006-06-14 20:04:55 +0000848 default:
Benny Prijono787b8692006-06-19 12:40:03 +0000849 PJ_LOG(1,(THIS_FILE,
Benny Prijonod6388ac2006-09-09 13:23:09 +0000850 "Argument \"%s\" is not valid. Use --help to see help",
851 argv[pj_optind-1]));
Benny Prijono22a300a2006-06-14 20:04:55 +0000852 return -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000853 }
854 }
855
856 if (pj_optind != argc) {
857 pj_str_t uri_arg;
858
859 if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
860 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
861 return -1;
862 }
863 uri_arg = pj_str(argv[pj_optind]);
864 if (uri_to_call)
865 *uri_to_call = uri_arg;
866 pj_optind++;
867
868 /* Add URI to call to buddy list if it's not already there */
869 for (i=0; i<cfg->buddy_cnt; ++i) {
870 if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
871 break;
872 }
873 if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
874 cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
875 }
876
877 } else {
878 if (uri_to_call)
879 uri_to_call->slen = 0;
880 }
881
882 if (pj_optind != argc) {
883 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
884 return PJ_EINVAL;
885 }
886
Benny Prijono56315612006-07-18 14:39:40 +0000887 if (cfg->acc_cfg[cfg->acc_cnt].id.slen)
888 cfg->acc_cnt++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000889
890 for (i=0; i<cfg->acc_cnt; ++i) {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000891 if (cfg->acc_cfg[i].cred_info[cfg->acc_cfg[i].cred_count].username.slen)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000892 {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000893 cfg->acc_cfg[i].cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000894 }
895 }
896
897
898 return PJ_SUCCESS;
899}
900
901
902/*
903 * Save account settings
904 */
905static void write_account_settings(int acc_index, pj_str_t *result)
906{
907 unsigned i;
908 char line[128];
909 pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
910
911
912 pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
913 pj_strcat2(result, line);
914
915
916 /* Identity */
917 if (acc_cfg->id.slen) {
918 pj_ansi_sprintf(line, "--id %.*s\n",
919 (int)acc_cfg->id.slen,
920 acc_cfg->id.ptr);
921 pj_strcat2(result, line);
922 }
923
924 /* Registrar server */
925 if (acc_cfg->reg_uri.slen) {
926 pj_ansi_sprintf(line, "--registrar %.*s\n",
927 (int)acc_cfg->reg_uri.slen,
928 acc_cfg->reg_uri.ptr);
929 pj_strcat2(result, line);
930
931 pj_ansi_sprintf(line, "--reg-timeout %u\n",
932 acc_cfg->reg_timeout);
933 pj_strcat2(result, line);
934 }
935
936 /* Contact */
Benny Prijonob4a17c92006-07-10 14:40:21 +0000937 if (acc_cfg->force_contact.slen) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000938 pj_ansi_sprintf(line, "--contact %.*s\n",
Benny Prijonob4a17c92006-07-10 14:40:21 +0000939 (int)acc_cfg->force_contact.slen,
940 acc_cfg->force_contact.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000941 pj_strcat2(result, line);
942 }
943
944 /* Proxy */
945 for (i=0; i<acc_cfg->proxy_cnt; ++i) {
946 pj_ansi_sprintf(line, "--proxy %.*s\n",
947 (int)acc_cfg->proxy[i].slen,
948 acc_cfg->proxy[i].ptr);
949 pj_strcat2(result, line);
950 }
951
952 /* Credentials */
953 for (i=0; i<acc_cfg->cred_count; ++i) {
954 if (acc_cfg->cred_info[i].realm.slen) {
955 pj_ansi_sprintf(line, "--realm %.*s\n",
956 (int)acc_cfg->cred_info[i].realm.slen,
957 acc_cfg->cred_info[i].realm.ptr);
958 pj_strcat2(result, line);
959 }
960
961 if (acc_cfg->cred_info[i].username.slen) {
962 pj_ansi_sprintf(line, "--username %.*s\n",
963 (int)acc_cfg->cred_info[i].username.slen,
964 acc_cfg->cred_info[i].username.ptr);
965 pj_strcat2(result, line);
966 }
967
968 if (acc_cfg->cred_info[i].data.slen) {
969 pj_ansi_sprintf(line, "--password %.*s\n",
970 (int)acc_cfg->cred_info[i].data.slen,
971 acc_cfg->cred_info[i].data.ptr);
972 pj_strcat2(result, line);
973 }
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000974
975 if (i != acc_cfg->cred_count - 1)
976 pj_strcat2(result, "--next-cred\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000977 }
978
979}
980
981
982/*
983 * Write settings.
984 */
985static int write_settings(const struct app_config *config,
986 char *buf, pj_size_t max)
987{
988 unsigned acc_index;
989 unsigned i;
990 pj_str_t cfg;
991 char line[128];
992
993 PJ_UNUSED_ARG(max);
994
995 cfg.ptr = buf;
996 cfg.slen = 0;
997
998 /* Logging. */
999 pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
1000 pj_ansi_sprintf(line, "--log-level %d\n",
1001 config->log_cfg.level);
1002 pj_strcat2(&cfg, line);
1003
1004 pj_ansi_sprintf(line, "--app-log-level %d\n",
1005 config->log_cfg.console_level);
1006 pj_strcat2(&cfg, line);
1007
1008 if (config->log_cfg.log_filename.slen) {
1009 pj_ansi_sprintf(line, "--log-file %.*s\n",
1010 (int)config->log_cfg.log_filename.slen,
1011 config->log_cfg.log_filename.ptr);
1012 pj_strcat2(&cfg, line);
1013 }
1014
1015
1016 /* Save account settings. */
1017 for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
1018
1019 write_account_settings(acc_index, &cfg);
1020
1021 if (acc_index < config->acc_cnt-1)
1022 pj_strcat2(&cfg, "--next-account\n");
1023 }
1024
1025
1026 pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
1027
1028 /* Outbound proxy */
1029 for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
1030 pj_ansi_sprintf(line, "--outbound %.*s\n",
1031 (int)config->cfg.outbound_proxy[i].slen,
1032 config->cfg.outbound_proxy[i].ptr);
1033 pj_strcat2(&cfg, line);
1034 }
1035
1036
1037 /* UDP Transport. */
1038 pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
1039 pj_strcat2(&cfg, line);
1040
Benny Prijono0a5cad82006-09-26 13:21:02 +00001041 /* IP address, if any. */
1042 if (config->udp_cfg.public_addr.slen) {
1043 pj_ansi_sprintf(line, "--ip-addr %.*s\n",
1044 (int)config->udp_cfg.public_addr.slen,
1045 config->udp_cfg.public_addr.ptr);
1046 pj_strcat2(&cfg, line);
1047 }
1048
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001049 /* No TCP ? */
1050 if (config->no_tcp) {
1051 pj_strcat2(&cfg, "--no-tcp\n");
1052 }
1053
1054 /* No UDP ? */
1055 if (config->no_udp) {
1056 pj_strcat2(&cfg, "--no-udp\n");
1057 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001058
1059 /* STUN */
1060 if (config->udp_cfg.stun_config.stun_port1) {
1061 pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
1062 (int)config->udp_cfg.stun_config.stun_srv1.slen,
1063 config->udp_cfg.stun_config.stun_srv1.ptr,
1064 config->udp_cfg.stun_config.stun_port1);
1065 pj_strcat2(&cfg, line);
1066 }
1067
1068 if (config->udp_cfg.stun_config.stun_port2) {
1069 pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
1070 (int)config->udp_cfg.stun_config.stun_srv2.slen,
1071 config->udp_cfg.stun_config.stun_srv2.ptr,
1072 config->udp_cfg.stun_config.stun_port2);
1073 pj_strcat2(&cfg, line);
1074 }
1075
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001076 /* TLS */
1077 if (config->use_tls)
1078 pj_strcat2(&cfg, "--use-tls\n");
Benny Prijonof3bbc132006-12-25 06:43:59 +00001079 if (config->udp_cfg.tls_setting.ca_list_file.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001080 pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001081 (int)config->udp_cfg.tls_setting.ca_list_file.slen,
1082 config->udp_cfg.tls_setting.ca_list_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001083 pj_strcat2(&cfg, line);
1084 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001085 if (config->udp_cfg.tls_setting.cert_file.slen) {
1086 pj_ansi_sprintf(line, "--tls-cert-file %.*s\n",
1087 (int)config->udp_cfg.tls_setting.cert_file.slen,
1088 config->udp_cfg.tls_setting.cert_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001089 pj_strcat2(&cfg, line);
1090 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001091 if (config->udp_cfg.tls_setting.privkey_file.slen) {
1092 pj_ansi_sprintf(line, "--tls-privkey-file %.*s\n",
1093 (int)config->udp_cfg.tls_setting.privkey_file.slen,
1094 config->udp_cfg.tls_setting.privkey_file.ptr);
1095 pj_strcat2(&cfg, line);
1096 }
1097
1098 if (config->udp_cfg.tls_setting.password.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001099 pj_ansi_sprintf(line, "--tls-password %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001100 (int)config->udp_cfg.tls_setting.password.slen,
1101 config->udp_cfg.tls_setting.password.ptr);
1102 pj_strcat2(&cfg, line);
1103 }
1104
1105 if (config->udp_cfg.tls_setting.verify_server)
1106 pj_strcat2(&cfg, "--tls-verify-server\n");
1107
1108 if (config->udp_cfg.tls_setting.verify_client)
1109 pj_strcat2(&cfg, "--tls-verify-client\n");
1110
1111 if (config->udp_cfg.tls_setting.timeout.sec) {
1112 pj_ansi_sprintf(line, "--tls-neg-timeout %d\n",
1113 config->udp_cfg.tls_setting.timeout.sec);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001114 pj_strcat2(&cfg, line);
1115 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001116
1117 pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
1118
1119
1120 /* Media */
1121 if (config->null_audio)
1122 pj_strcat2(&cfg, "--null-audio\n");
1123 if (config->auto_play)
1124 pj_strcat2(&cfg, "--auto-play\n");
1125 if (config->auto_loop)
1126 pj_strcat2(&cfg, "--auto-loop\n");
Benny Prijono7ca96da2006-08-07 12:11:40 +00001127 if (config->auto_conf)
1128 pj_strcat2(&cfg, "--auto-conf\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001129 if (config->wav_file.slen) {
1130 pj_ansi_sprintf(line, "--play-file %s\n",
1131 config->wav_file.ptr);
1132 pj_strcat2(&cfg, line);
1133 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001134 if (config->rec_file.slen) {
1135 pj_ansi_sprintf(line, "--rec-file %s\n",
1136 config->rec_file.ptr);
1137 pj_strcat2(&cfg, line);
1138 }
1139 if (config->auto_rec)
1140 pj_strcat2(&cfg, "--auto-rec\n");
1141
1142
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001143 /* Media clock rate. */
Benny Prijono70972992006-08-05 11:13:58 +00001144 if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001145 pj_ansi_sprintf(line, "--clock-rate %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001146 config->media_cfg.clock_rate);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001147 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001148 } else {
1149 pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
1150 config->media_cfg.clock_rate);
1151 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001152 }
Benny Prijono70972992006-08-05 11:13:58 +00001153
1154 /* quality */
1155 if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001156 pj_ansi_sprintf(line, "--quality %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001157 config->media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001158 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001159 } else {
1160 pj_ansi_sprintf(line, "#using default --quality %d\n",
1161 config->media_cfg.quality);
1162 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001163 }
Benny Prijono0498d902006-06-19 14:49:14 +00001164
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001165
1166 /* ptime */
1167 if (config->ptime) {
1168 pj_ansi_sprintf(line, "--ptime %d\n",
1169 config->ptime);
1170 pj_strcat2(&cfg, line);
1171 }
1172
Benny Prijono70972992006-08-05 11:13:58 +00001173 /* no-vad */
1174 if (config->media_cfg.no_vad) {
1175 pj_strcat2(&cfg, "--no-vad\n");
1176 }
1177
1178 /* ec-tail */
1179 if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
1180 pj_ansi_sprintf(line, "--ec-tail %d\n",
1181 config->media_cfg.ec_tail_len);
1182 pj_strcat2(&cfg, line);
1183 } else {
1184 pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
1185 config->media_cfg.ec_tail_len);
1186 pj_strcat2(&cfg, line);
1187 }
1188
1189
1190 /* ilbc-mode */
1191 if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
1192 pj_ansi_sprintf(line, "--ilbc-mode %d\n",
1193 config->media_cfg.ilbc_mode);
1194 pj_strcat2(&cfg, line);
1195 } else {
1196 pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
1197 config->media_cfg.ilbc_mode);
1198 pj_strcat2(&cfg, line);
1199 }
1200
1201 /* RTP drop */
1202 if (config->media_cfg.tx_drop_pct) {
1203 pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
1204 config->media_cfg.tx_drop_pct);
1205 pj_strcat2(&cfg, line);
1206
1207 }
1208 if (config->media_cfg.rx_drop_pct) {
1209 pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
1210 config->media_cfg.rx_drop_pct);
1211 pj_strcat2(&cfg, line);
1212
1213 }
1214
1215
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001216 /* Start RTP port. */
1217 pj_ansi_sprintf(line, "--rtp-port %d\n",
1218 config->rtp_cfg.port);
1219 pj_strcat2(&cfg, line);
1220
1221 /* Add codec. */
1222 for (i=0; i<config->codec_cnt; ++i) {
1223 pj_ansi_sprintf(line, "--add-codec %s\n",
1224 config->codec_arg[i].ptr);
1225 pj_strcat2(&cfg, line);
1226 }
1227
1228 pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
1229
1230 /* Auto-answer. */
1231 if (config->auto_answer != 0) {
1232 pj_ansi_sprintf(line, "--auto-answer %d\n",
1233 config->auto_answer);
1234 pj_strcat2(&cfg, line);
1235 }
1236
1237 /* Max calls. */
1238 pj_ansi_sprintf(line, "--max-calls %d\n",
1239 config->cfg.max_calls);
1240 pj_strcat2(&cfg, line);
1241
1242 /* Uas-duration. */
Benny Prijono804ff0a2006-09-14 11:17:48 +00001243 if (config->duration != NO_LIMIT) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001244 pj_ansi_sprintf(line, "--duration %d\n",
1245 config->duration);
1246 pj_strcat2(&cfg, line);
1247 }
1248
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001249 /* norefersub ? */
1250 if (config->no_refersub) {
1251 pj_strcat2(&cfg, "--norefersub\n");
1252 }
1253
1254
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001255 pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
1256
1257 /* Add buddies. */
1258 for (i=0; i<config->buddy_cnt; ++i) {
1259 pj_ansi_sprintf(line, "--add-buddy %.*s\n",
1260 (int)config->buddy_cfg[i].uri.slen,
1261 config->buddy_cfg[i].uri.ptr);
1262 pj_strcat2(&cfg, line);
1263 }
1264
1265
1266 *(cfg.ptr + cfg.slen) = '\0';
1267 return cfg.slen;
1268}
1269
1270
1271/*
1272 * Dump application states.
1273 */
1274static void app_dump(pj_bool_t detail)
1275{
1276 unsigned old_decor;
1277 char buf[1024];
1278
1279 PJ_LOG(3,(THIS_FILE, "Start dumping application states:"));
1280
1281 old_decor = pj_log_get_decor();
1282 pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
1283
1284 if (detail)
1285 pj_dump_config();
1286
1287 pjsip_endpt_dump(pjsua_get_pjsip_endpt(), detail);
1288 pjmedia_endpt_dump(pjsua_get_pjmedia_endpt());
1289 pjsip_tsx_layer_dump(detail);
1290 pjsip_ua_dump(detail);
1291
1292
1293 /* Dump all invite sessions: */
1294 PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
1295
1296 if (pjsua_call_get_count() == 0) {
1297
1298 PJ_LOG(3,(THIS_FILE, " - no sessions -"));
1299
1300 } else {
1301 unsigned i;
1302
1303 for (i=0; i<app_config.cfg.max_calls; ++i) {
1304 if (pjsua_call_is_active(i)) {
1305 pjsua_call_dump(i, detail, buf, sizeof(buf), " ");
1306 PJ_LOG(3,(THIS_FILE, "%s", buf));
1307 }
1308 }
1309 }
1310
1311 /* Dump presence status */
1312 pjsua_pres_dump(detail);
1313
1314 pj_log_set_decor(old_decor);
1315 PJ_LOG(3,(THIS_FILE, "Dump complete"));
1316}
1317
1318
1319/*****************************************************************************
1320 * Console application
1321 */
1322
1323/*
1324 * Find next call when current call is disconnected or when user
1325 * press ']'
1326 */
1327static pj_bool_t find_next_call(void)
1328{
1329 int i, max;
1330
1331 max = pjsua_call_get_max_count();
1332 for (i=current_call+1; i<max; ++i) {
1333 if (pjsua_call_is_active(i)) {
1334 current_call = i;
1335 return PJ_TRUE;
1336 }
1337 }
1338
1339 for (i=0; i<current_call; ++i) {
1340 if (pjsua_call_is_active(i)) {
1341 current_call = i;
1342 return PJ_TRUE;
1343 }
1344 }
1345
1346 current_call = PJSUA_INVALID_ID;
1347 return PJ_FALSE;
1348}
1349
1350
1351/*
1352 * Find previous call when user press '['
1353 */
1354static pj_bool_t find_prev_call(void)
1355{
1356 int i, max;
1357
1358 max = pjsua_call_get_max_count();
1359 for (i=current_call-1; i>=0; --i) {
1360 if (pjsua_call_is_active(i)) {
1361 current_call = i;
1362 return PJ_TRUE;
1363 }
1364 }
1365
1366 for (i=max-1; i>current_call; --i) {
1367 if (pjsua_call_is_active(i)) {
1368 current_call = i;
1369 return PJ_TRUE;
1370 }
1371 }
1372
1373 current_call = PJSUA_INVALID_ID;
1374 return PJ_FALSE;
1375}
1376
1377
Benny Prijono804ff0a2006-09-14 11:17:48 +00001378/* Callback from timer when the maximum call duration has been
1379 * exceeded.
1380 */
1381static void call_timeout_callback(pj_timer_heap_t *timer_heap,
1382 struct pj_timer_entry *entry)
1383{
1384 pjsua_call_id call_id = entry->id;
1385 pjsua_msg_data msg_data;
1386 pjsip_generic_string_hdr warn;
1387 pj_str_t hname = pj_str("Warning");
1388 pj_str_t hvalue = pj_str("399 pjsua \"Call duration exceeded\"");
1389
1390 PJ_UNUSED_ARG(timer_heap);
1391
Benny Prijono148c9dd2006-09-19 13:37:53 +00001392 if (call_id == PJSUA_INVALID_ID) {
1393 PJ_LOG(1,(THIS_FILE, "Invalid call ID in timer callback"));
1394 return;
1395 }
1396
Benny Prijono804ff0a2006-09-14 11:17:48 +00001397 /* Add warning header */
1398 pjsua_msg_data_init(&msg_data);
1399 pjsip_generic_string_hdr_init2(&warn, &hname, &hvalue);
1400 pj_list_push_back(&msg_data.hdr_list, &warn);
1401
1402 /* Call duration has been exceeded; disconnect the call */
1403 PJ_LOG(3,(THIS_FILE, "Duration (%d seconds) has been exceeded "
1404 "for call %d, disconnecting the call",
1405 app_config.duration, call_id));
1406 entry->id = PJSUA_INVALID_ID;
1407 pjsua_call_hangup(call_id, 200, NULL, &msg_data);
1408}
1409
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001410
1411/*
1412 * Handler when invite state has changed.
1413 */
1414static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
1415{
1416 pjsua_call_info call_info;
1417
1418 PJ_UNUSED_ARG(e);
1419
1420 pjsua_call_get_info(call_id, &call_info);
1421
1422 if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
1423
Benny Prijono804ff0a2006-09-14 11:17:48 +00001424 /* Cancel duration timer, if any */
1425 if (app_config.call_data[call_id].timer.id != PJSUA_INVALID_ID) {
1426 struct call_data *cd = &app_config.call_data[call_id];
1427 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1428
1429 cd->timer.id = PJSUA_INVALID_ID;
1430 pjsip_endpt_cancel_timer(endpt, &cd->timer);
1431 }
1432
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001433 PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",
1434 call_id,
1435 call_info.last_status,
1436 call_info.last_status_text.ptr));
1437
1438 if (call_id == current_call) {
1439 find_next_call();
1440 }
1441
Benny Prijono4be63b52006-11-25 14:50:25 +00001442 /* Dump media state upon disconnected */
1443 if (1) {
1444 char buf[1024];
1445 pjsua_call_dump(call_id, PJ_TRUE, buf,
1446 sizeof(buf), " ");
1447 PJ_LOG(5,(THIS_FILE,
1448 "Call %d disconnected, dumping media stats\n%s",
1449 call_id, buf));
1450 }
1451
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001452 } else {
1453
Benny Prijono804ff0a2006-09-14 11:17:48 +00001454 if (app_config.duration!=NO_LIMIT &&
1455 call_info.state == PJSIP_INV_STATE_CONFIRMED)
1456 {
1457 /* Schedule timer to hangup call after the specified duration */
1458 struct call_data *cd = &app_config.call_data[call_id];
1459 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1460 pj_time_val delay;
1461
1462 cd->timer.id = call_id;
1463 delay.sec = app_config.duration;
1464 delay.msec = 0;
1465 pjsip_endpt_schedule_timer(endpt, &cd->timer, &delay);
1466 }
1467
Benny Prijono4be63b52006-11-25 14:50:25 +00001468 if (call_info.state == PJSIP_INV_STATE_EARLY) {
1469 int code;
1470 pj_str_t reason;
1471 pjsip_msg *msg;
1472
1473 /* This can only occur because of TX or RX message */
1474 pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
1475
1476 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
1477 msg = e->body.tsx_state.src.rdata->msg_info.msg;
1478 } else {
1479 msg = e->body.tsx_state.src.tdata->msg;
1480 }
1481
1482 code = msg->line.status.code;
1483 reason = msg->line.status.reason;
1484
1485 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)",
1486 call_id, call_info.state_text.ptr,
1487 code, (int)reason.slen, reason.ptr));
1488 } else {
1489 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s",
1490 call_id,
1491 call_info.state_text.ptr));
1492 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001493
1494 if (current_call==PJSUA_INVALID_ID)
1495 current_call = call_id;
1496
1497 }
1498}
1499
1500
1501/**
1502 * Handler when there is incoming call.
1503 */
1504static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
1505 pjsip_rx_data *rdata)
1506{
1507 pjsua_call_info call_info;
1508
1509 PJ_UNUSED_ARG(acc_id);
1510 PJ_UNUSED_ARG(rdata);
1511
1512 pjsua_call_get_info(call_id, &call_info);
1513
1514 if (app_config.auto_answer > 0) {
1515 pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL);
1516 }
1517
1518 if (app_config.auto_answer < 200) {
1519 PJ_LOG(3,(THIS_FILE,
1520 "Incoming call for account %d!\n"
1521 "From: %s\n"
1522 "To: %s\n"
1523 "Press a to answer or h to reject call",
1524 acc_id,
1525 call_info.remote_info.ptr,
1526 call_info.local_info.ptr));
1527 }
1528}
1529
1530
1531/*
1532 * Callback on media state changed event.
1533 * The action may connect the call to sound device, to file, or
1534 * to loop the call.
1535 */
1536static void on_call_media_state(pjsua_call_id call_id)
1537{
1538 pjsua_call_info call_info;
1539
1540 pjsua_call_get_info(call_id, &call_info);
1541
1542 if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
1543 pj_bool_t connect_sound = PJ_TRUE;
1544
1545 /* Loopback sound, if desired */
1546 if (app_config.auto_loop) {
1547 pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
1548 connect_sound = PJ_FALSE;
Benny Prijono1ebd6142006-10-19 15:48:02 +00001549
1550 /* Automatically record conversation, if desired */
1551 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1552 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1553 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001554 }
1555
1556 /* Stream a file, if desired */
1557 if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) {
1558 pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
1559 connect_sound = PJ_FALSE;
1560 }
1561
Benny Prijono7ca96da2006-08-07 12:11:40 +00001562 /* Put call in conference with other calls, if desired */
1563 if (app_config.auto_conf) {
1564 pjsua_call_id call_ids[PJSUA_MAX_CALLS];
Benny Prijono10861bc2006-08-07 13:22:43 +00001565 unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
Benny Prijono7ca96da2006-08-07 12:11:40 +00001566 unsigned i;
1567
1568 /* Get all calls, and establish media connection between
1569 * this call and other calls.
1570 */
1571 pjsua_enum_calls(call_ids, &call_cnt);
Benny Prijono10861bc2006-08-07 13:22:43 +00001572
Benny Prijono7ca96da2006-08-07 12:11:40 +00001573 for (i=0; i<call_cnt; ++i) {
1574 if (call_ids[i] == call_id)
1575 continue;
1576
1577 if (!pjsua_call_has_media(call_ids[i]))
1578 continue;
1579
1580 pjsua_conf_connect(call_info.conf_slot,
1581 pjsua_call_get_conf_port(call_ids[i]));
1582 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1583 call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001584
1585 /* Automatically record conversation, if desired */
1586 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1587 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1588 app_config.rec_port);
1589 }
1590
Benny Prijono7ca96da2006-08-07 12:11:40 +00001591 }
1592
1593 /* Also connect call to local sound device */
1594 connect_sound = PJ_TRUE;
1595 }
1596
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001597 /* Otherwise connect to sound device */
1598 if (connect_sound) {
1599 pjsua_conf_connect(call_info.conf_slot, 0);
1600 pjsua_conf_connect(0, call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001601
1602 /* Automatically record conversation, if desired */
1603 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1604 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1605 pjsua_conf_connect(0, app_config.rec_port);
1606 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001607 }
1608
1609 PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
1610
1611 } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) {
1612 PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
1613 call_id));
1614 } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) {
1615 PJ_LOG(3,(THIS_FILE,
1616 "Media for call %d is suspended (hold) by remote",
1617 call_id));
1618 } else {
1619 PJ_LOG(3,(THIS_FILE,
1620 "Media for call %d is inactive",
1621 call_id));
1622 }
1623}
1624
Benny Prijono0875ae82006-12-26 00:11:48 +00001625/*
1626 * DTMF callback.
1627 */
1628static void call_on_dtmf_callback(pjsua_call_id call_id, int dtmf)
1629{
1630 PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, dtmf));
1631}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001632
1633/*
1634 * Handler registration status has changed.
1635 */
1636static void on_reg_state(pjsua_acc_id acc_id)
1637{
1638 PJ_UNUSED_ARG(acc_id);
1639
1640 // Log already written.
1641}
1642
1643
1644/*
1645 * Handler on buddy state changed.
1646 */
1647static void on_buddy_state(pjsua_buddy_id buddy_id)
1648{
1649 pjsua_buddy_info info;
1650 pjsua_buddy_get_info(buddy_id, &info);
1651
1652 PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
1653 (int)info.uri.slen,
1654 info.uri.ptr,
1655 (int)info.status_text.slen,
1656 info.status_text.ptr));
1657}
1658
1659
1660/**
1661 * Incoming IM message (i.e. MESSAGE request)!
1662 */
1663static void on_pager(pjsua_call_id call_id, const pj_str_t *from,
1664 const pj_str_t *to, const pj_str_t *contact,
1665 const pj_str_t *mime_type, const pj_str_t *text)
1666{
1667 /* Note: call index may be -1 */
1668 PJ_UNUSED_ARG(call_id);
1669 PJ_UNUSED_ARG(to);
1670 PJ_UNUSED_ARG(contact);
1671 PJ_UNUSED_ARG(mime_type);
1672
1673 PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s",
1674 (int)from->slen, from->ptr,
1675 (int)text->slen, text->ptr));
1676}
1677
1678
1679/**
1680 * Received typing indication
1681 */
1682static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
1683 const pj_str_t *to, const pj_str_t *contact,
1684 pj_bool_t is_typing)
1685{
1686 PJ_UNUSED_ARG(call_id);
1687 PJ_UNUSED_ARG(to);
1688 PJ_UNUSED_ARG(contact);
1689
1690 PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
1691 (int)from->slen, from->ptr,
1692 (is_typing?"is typing..":"has stopped typing")));
1693}
1694
1695
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001696/**
1697 * Call transfer request status.
1698 */
1699static void on_call_transfer_status(pjsua_call_id call_id,
1700 int status_code,
1701 const pj_str_t *status_text,
1702 pj_bool_t final,
1703 pj_bool_t *p_cont)
1704{
1705 PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s",
1706 call_id, status_code,
1707 (int)status_text->slen, status_text->ptr,
1708 (final ? "[final]" : "")));
1709
1710 if (status_code/100 == 2) {
1711 PJ_LOG(3,(THIS_FILE,
1712 "Call %d: call transfered successfully, disconnecting call",
1713 call_id));
1714 pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL);
1715 *p_cont = PJ_FALSE;
1716 }
1717}
1718
1719
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001720/*
Benny Prijonof7b1c392006-11-11 16:46:34 +00001721 * Notification that call is being replaced.
1722 */
1723static void on_call_replaced(pjsua_call_id old_call_id,
1724 pjsua_call_id new_call_id)
1725{
1726 pjsua_call_info old_ci, new_ci;
1727
1728 pjsua_call_get_info(old_call_id, &old_ci);
1729 pjsua_call_get_info(new_call_id, &new_ci);
1730
1731 PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by "
1732 "call %d with %.*s",
1733 old_call_id,
1734 (int)old_ci.remote_info.slen, old_ci.remote_info.ptr,
1735 new_call_id,
1736 (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
1737}
1738
1739
1740/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001741 * Print buddy list.
1742 */
1743static void print_buddy_list(void)
1744{
1745 pjsua_buddy_id ids[64];
1746 int i;
1747 unsigned count = PJ_ARRAY_SIZE(ids);
1748
1749 puts("Buddy list:");
1750
1751 pjsua_enum_buddies(ids, &count);
1752
1753 if (count == 0)
1754 puts(" -none-");
1755 else {
1756 for (i=0; i<(int)count; ++i) {
1757 pjsua_buddy_info info;
1758
1759 if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
1760 continue;
1761
1762 printf(" [%2d] <%7s> %.*s\n",
1763 ids[i]+1, info.status_text.ptr,
1764 (int)info.uri.slen,
1765 info.uri.ptr);
1766 }
1767 }
1768 puts("");
1769}
1770
1771
1772/*
1773 * Print account status.
1774 */
1775static void print_acc_status(int acc_id)
1776{
1777 char buf[80];
1778 pjsua_acc_info info;
1779
1780 pjsua_acc_get_info(acc_id, &info);
1781
1782 if (!info.has_registration) {
1783 pj_ansi_snprintf(buf, sizeof(buf), "%.*s",
1784 (int)info.status_text.slen,
1785 info.status_text.ptr);
1786
1787 } else {
1788 pj_ansi_snprintf(buf, sizeof(buf),
1789 "%d/%.*s (expires=%d)",
1790 info.status,
1791 (int)info.status_text.slen,
1792 info.status_text.ptr,
1793 info.expires);
1794
1795 }
1796
1797 printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
1798 acc_id, (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
1799 printf(" Online status: %s\n",
1800 (info.online_status ? "Online" : "Invisible"));
1801}
1802
1803
1804/*
1805 * Show a bit of help.
1806 */
1807static void keystroke_help(void)
1808{
1809 pjsua_acc_id acc_ids[16];
1810 unsigned count = PJ_ARRAY_SIZE(acc_ids);
1811 int i;
1812
1813 printf(">>>>\n");
1814
1815 pjsua_enum_accs(acc_ids, &count);
1816
1817 printf("Account list:\n");
1818 for (i=0; i<(int)count; ++i)
1819 print_acc_status(acc_ids[i]);
1820
1821 print_buddy_list();
1822
1823 //puts("Commands:");
1824 puts("+=============================================================================+");
1825 puts("| Call Commands: | Buddy, IM & Presence: | Account: |");
1826 puts("| | | |");
1827 puts("| m Make new call | +b Add new buddy .| +a Add new accnt |");
1828 puts("| M Make multiple calls | -b Delete buddy | -a Delete accnt. |");
1829 puts("| a Answer call | !b Modify buddy | !a Modify accnt. |");
1830 puts("| h Hangup call (ha=all) | i Send IM | rr (Re-)register |");
1831 puts("| H Hold call | s Subscribe presence | ru Unregister |");
1832 puts("| v re-inVite (release hold) | u Unsubscribe presence | > Cycle next ac.|");
1833 puts("| ] Select next dialog | t ToGgle Online status | < Cycle prev ac.|");
1834 puts("| [ Select previous dialog +--------------------------+-------------------+");
1835 puts("| x Xfer call | Media Commands: | Status & Config: |");
Benny Prijonof7b1c392006-11-11 16:46:34 +00001836 puts("| X Xfer with Replaces | | |");
1837 puts("| # Send DTMF string | cl List ports | d Dump status |");
1838 puts("| dq Dump curr. call quality | cc Connect port | dd Dump detailed |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001839 puts("| | cd Disconnect port | dc Dump config |");
Benny Prijono56315612006-07-18 14:39:40 +00001840 puts("| S Send arbitrary REQUEST | | f Save config |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001841 puts("+------------------------------+--------------------------+-------------------+");
1842 puts("| q QUIT |");
1843 puts("+=============================================================================+");
Benny Prijono48af79c2006-07-22 12:49:17 +00001844
1845 i = pjsua_call_get_count();
1846 printf("You have %d active call%s\n", i, (i>1?"s":""));
Benny Prijonof7b1c392006-11-11 16:46:34 +00001847
1848 if (current_call != PJSUA_INVALID_ID) {
1849 pjsua_call_info ci;
1850 if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS)
1851 printf("Current call id=%d to %.*s [%.*s]\n", current_call,
1852 (int)ci.remote_info.slen, ci.remote_info.ptr,
1853 (int)ci.state_text.slen, ci.state_text.ptr);
1854 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001855}
1856
1857
1858/*
1859 * Input simple string
1860 */
1861static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1862{
1863 char *p;
1864
1865 printf("%s (empty to cancel): ", title); fflush(stdout);
1866 fgets(buf, len, stdin);
1867
1868 /* Remove trailing newlines. */
1869 for (p=buf; ; ++p) {
1870 if (*p=='\r' || *p=='\n') *p='\0';
1871 else if (!*p) break;
1872 }
1873
1874 if (!*buf)
1875 return PJ_FALSE;
1876
1877 return PJ_TRUE;
1878}
1879
1880
1881#define NO_NB -2
1882struct input_result
1883{
1884 int nb_result;
1885 char *uri_result;
1886};
1887
1888
1889/*
1890 * Input URL.
1891 */
1892static void ui_input_url(const char *title, char *buf, int len,
1893 struct input_result *result)
1894{
1895 result->nb_result = NO_NB;
1896 result->uri_result = NULL;
1897
1898 print_buddy_list();
1899
1900 printf("Choices:\n"
1901 " 0 For current dialog.\n"
1902 " -1 All %d buddies in buddy list\n"
1903 " [1 -%2d] Select from buddy list\n"
1904 " URL An URL\n"
1905 " <Enter> Empty input (or 'q') to cancel\n"
1906 , pjsua_get_buddy_count(), pjsua_get_buddy_count());
1907 printf("%s: ", title);
1908
1909 fflush(stdout);
1910 fgets(buf, len, stdin);
1911 len = strlen(buf);
1912
1913 /* Left trim */
1914 while (pj_isspace(*buf)) {
1915 ++buf;
1916 --len;
1917 }
1918
1919 /* Remove trailing newlines */
1920 while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
1921 buf[--len] = '\0';
1922
1923 if (len == 0 || buf[0]=='q')
1924 return;
1925
1926 if (pj_isdigit(*buf) || *buf=='-') {
1927
1928 int i;
1929
1930 if (*buf=='-')
1931 i = 1;
1932 else
1933 i = 0;
1934
1935 for (; i<len; ++i) {
1936 if (!pj_isdigit(buf[i])) {
1937 puts("Invalid input");
1938 return;
1939 }
1940 }
1941
1942 result->nb_result = my_atoi(buf);
1943
1944 if (result->nb_result >= 0 &&
1945 result->nb_result <= (int)pjsua_get_buddy_count())
1946 {
1947 return;
1948 }
1949 if (result->nb_result == -1)
1950 return;
1951
1952 puts("Invalid input");
1953 result->nb_result = NO_NB;
1954 return;
1955
1956 } else {
1957 pj_status_t status;
1958
1959 if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
1960 pjsua_perror(THIS_FILE, "Invalid URL", status);
1961 return;
1962 }
1963
1964 result->uri_result = buf;
1965 }
1966}
1967
1968/*
1969 * List the ports in conference bridge
1970 */
1971static void conf_list(void)
1972{
1973 unsigned i, count;
1974 pjsua_conf_port_id id[PJSUA_MAX_CALLS];
1975
1976 printf("Conference ports:\n");
1977
1978 count = PJ_ARRAY_SIZE(id);
1979 pjsua_enum_conf_ports(id, &count);
1980
1981 for (i=0; i<count; ++i) {
1982 char txlist[PJSUA_MAX_CALLS*4+10];
1983 unsigned j;
1984 pjsua_conf_port_info info;
1985
1986 pjsua_conf_get_port_info(id[i], &info);
1987
1988 txlist[0] = '\0';
1989 for (j=0; j<info.listener_cnt; ++j) {
1990 char s[10];
1991 pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
1992 pj_ansi_strcat(txlist, s);
1993 }
1994 printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n",
1995 info.slot_id,
1996 info.clock_rate/1000,
1997 info.samples_per_frame * 1000 / info.clock_rate,
1998 (int)info.name.slen,
1999 info.name.ptr,
2000 txlist);
2001
2002 }
2003 puts("");
2004}
2005
2006
2007/*
Benny Prijono56315612006-07-18 14:39:40 +00002008 * Send arbitrary request to remote host
2009 */
2010static void send_request(char *cstr_method, const pj_str_t *dst_uri)
2011{
2012 pj_str_t str_method;
2013 pjsip_method method;
2014 pjsip_tx_data *tdata;
2015 pjsua_acc_info acc_info;
2016 pjsip_endpoint *endpt;
2017 pj_status_t status;
2018
2019 endpt = pjsua_get_pjsip_endpt();
2020
2021 str_method = pj_str(cstr_method);
2022 pjsip_method_init_np(&method, &str_method);
2023
2024 pjsua_acc_get_info(current_acc, &acc_info);
2025
2026 status = pjsip_endpt_create_request(endpt, &method, dst_uri,
2027 &acc_info.acc_uri, dst_uri,
2028 NULL, NULL, -1, NULL, &tdata);
2029 if (status != PJ_SUCCESS) {
2030 pjsua_perror(THIS_FILE, "Unable to create request", status);
2031 return;
2032 }
2033
2034 status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
2035 if (status != PJ_SUCCESS) {
2036 pjsua_perror(THIS_FILE, "Unable to send request", status);
Benny Prijono56315612006-07-18 14:39:40 +00002037 return;
2038 }
2039}
2040
2041
2042/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002043 * Main "user interface" loop.
2044 */
2045void console_app_main(const pj_str_t *uri_to_call)
2046{
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002047 char menuin[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002048 char buf[128];
2049 char text[128];
2050 int i, count;
2051 char *uri;
2052 pj_str_t tmp;
2053 struct input_result result;
2054 pjsua_call_info call_info;
2055 pjsua_acc_info acc_info;
2056
2057
2058 /* If user specifies URI to call, then call the URI */
2059 if (uri_to_call->slen) {
2060 pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, NULL);
2061 }
2062
2063 keystroke_help();
2064
2065 for (;;) {
2066
2067 printf(">>> ");
2068 fflush(stdout);
2069
2070 fgets(menuin, sizeof(menuin), stdin);
2071
2072 switch (menuin[0]) {
2073
2074 case 'm':
2075 /* Make call! : */
2076 printf("(You currently have %d calls)\n",
2077 pjsua_call_get_count());
2078
2079 uri = NULL;
2080 ui_input_url("Make call", buf, sizeof(buf), &result);
2081 if (result.nb_result != NO_NB) {
2082
2083 if (result.nb_result == -1 || result.nb_result == 0) {
2084 puts("You can't do that with make call!");
2085 continue;
2086 } else {
2087 pjsua_buddy_info binfo;
2088 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2089 uri = binfo.uri.ptr;
2090 }
2091
2092 } else if (result.uri_result) {
2093 uri = result.uri_result;
2094 }
2095
2096 tmp = pj_str(uri);
2097 pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL);
2098 break;
2099
2100 case 'M':
2101 /* Make multiple calls! : */
2102 printf("(You currently have %d calls)\n",
2103 pjsua_call_get_count());
2104
2105 if (!simple_input("Number of calls", menuin, sizeof(menuin)))
2106 continue;
2107
2108 count = my_atoi(menuin);
2109 if (count < 1)
2110 continue;
2111
2112 ui_input_url("Make call", buf, sizeof(buf), &result);
2113 if (result.nb_result != NO_NB) {
2114 pjsua_buddy_info binfo;
2115 if (result.nb_result == -1 || result.nb_result == 0) {
2116 puts("You can't do that with make call!");
2117 continue;
2118 }
2119 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2120 uri = binfo.uri.ptr;
2121 } else {
2122 uri = result.uri_result;
2123 }
2124
2125 for (i=0; i<my_atoi(menuin); ++i) {
2126 pj_status_t status;
2127
2128 tmp = pj_str(uri);
2129 status = pjsua_call_make_call(current_acc, &tmp, 0, NULL,
2130 NULL, NULL);
2131 if (status != PJ_SUCCESS)
2132 break;
2133 }
2134 break;
2135
2136 case 'i':
2137 /* Send instant messaeg */
2138
2139 /* i is for call index to send message, if any */
2140 i = -1;
2141
2142 /* Make compiler happy. */
2143 uri = NULL;
2144
2145 /* Input destination. */
2146 ui_input_url("Send IM to", buf, sizeof(buf), &result);
2147 if (result.nb_result != NO_NB) {
2148
2149 if (result.nb_result == -1) {
2150 puts("You can't send broadcast IM like that!");
2151 continue;
2152
2153 } else if (result.nb_result == 0) {
2154
2155 i = current_call;
2156
2157 } else {
2158 pjsua_buddy_info binfo;
2159 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2160 uri = binfo.uri.ptr;
2161 }
2162
2163 } else if (result.uri_result) {
2164 uri = result.uri_result;
2165 }
2166
2167
2168 /* Send typing indication. */
2169 if (i != -1)
2170 pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
2171 else {
2172 pj_str_t tmp_uri = pj_str(uri);
2173 pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
2174 }
2175
2176 /* Input the IM . */
2177 if (!simple_input("Message", text, sizeof(text))) {
2178 /*
2179 * Cancelled.
2180 * Send typing notification too, saying we're not typing.
2181 */
2182 if (i != -1)
2183 pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
2184 else {
2185 pj_str_t tmp_uri = pj_str(uri);
2186 pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
2187 }
2188 continue;
2189 }
2190
2191 tmp = pj_str(text);
2192
2193 /* Send the IM */
2194 if (i != -1)
2195 pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
2196 else {
2197 pj_str_t tmp_uri = pj_str(uri);
2198 pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
2199 }
2200
2201 break;
2202
2203 case 'a':
2204
2205 if (current_call != -1) {
2206 pjsua_call_get_info(current_call, &call_info);
2207 } else {
2208 /* Make compiler happy */
2209 call_info.role = PJSIP_ROLE_UAC;
2210 call_info.state = PJSIP_INV_STATE_DISCONNECTED;
2211 }
2212
2213 if (current_call == -1 ||
2214 call_info.role != PJSIP_ROLE_UAS ||
2215 call_info.state >= PJSIP_INV_STATE_CONNECTING)
2216 {
2217 puts("No pending incoming call");
2218 fflush(stdout);
2219 continue;
2220
2221 } else {
2222 if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
2223 continue;
2224
2225 if (my_atoi(buf) < 100)
2226 continue;
2227
2228 /*
2229 * Must check again!
2230 * Call may have been disconnected while we're waiting for
2231 * keyboard input.
2232 */
2233 if (current_call == -1) {
2234 puts("Call has been disconnected");
2235 fflush(stdout);
2236 continue;
2237 }
2238
2239 pjsua_call_answer(current_call, my_atoi(buf), NULL, NULL);
2240 }
2241
2242 break;
2243
2244
2245 case 'h':
2246
2247 if (current_call == -1) {
2248 puts("No current call");
2249 fflush(stdout);
2250 continue;
2251
2252 } else if (menuin[1] == 'a') {
2253
2254 /* Hangup all calls */
2255 pjsua_call_hangup_all();
2256
2257 } else {
2258
2259 /* Hangup current calls */
2260 pjsua_call_hangup(current_call, 0, NULL, NULL);
2261 }
2262 break;
2263
2264 case ']':
2265 case '[':
2266 /*
2267 * Cycle next/prev dialog.
2268 */
2269 if (menuin[0] == ']') {
2270 find_next_call();
2271
2272 } else {
2273 find_prev_call();
2274 }
2275
2276 if (current_call != -1) {
2277
2278 pjsua_call_get_info(current_call, &call_info);
2279 PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s",
2280 (int)call_info.remote_info.slen,
2281 call_info.remote_info.ptr));
2282
2283 } else {
2284 PJ_LOG(3,(THIS_FILE,"No current dialog"));
2285 }
2286 break;
2287
2288
2289 case '>':
2290 case '<':
2291 if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
2292 break;
2293
2294 i = my_atoi(buf);
2295 if (pjsua_acc_is_valid(i)) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00002296 pjsua_acc_set_default(i);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002297 PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
2298 } else {
2299 PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
2300 }
2301 break;
2302
2303
2304 case '+':
2305 if (menuin[1] == 'b') {
2306
2307 pjsua_buddy_config buddy_cfg;
2308 pjsua_buddy_id buddy_id;
2309 pj_status_t status;
2310
2311 if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
2312 break;
2313
2314 if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) {
2315 printf("Invalid SIP URI '%s'\n", buf);
2316 break;
2317 }
2318
Benny Prijonoac623b32006-07-03 15:19:31 +00002319 pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002320
2321 buddy_cfg.uri = pj_str(buf);
2322 buddy_cfg.subscribe = PJ_TRUE;
2323
2324 status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
2325 if (status == PJ_SUCCESS) {
2326 printf("New buddy '%s' added at index %d\n",
2327 buf, buddy_id+1);
2328 }
2329
2330 } else if (menuin[1] == 'a') {
2331
2332 printf("Sorry, this command is not supported yet\n");
2333
2334 } else {
2335 printf("Invalid input %s\n", menuin);
2336 }
2337 break;
2338
2339 case '-':
2340 if (menuin[1] == 'b') {
2341 if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
2342 break;
2343
2344 i = my_atoi(buf) - 1;
2345
2346 if (!pjsua_buddy_is_valid(i)) {
2347 printf("Invalid buddy id %d\n", i);
2348 } else {
2349 pjsua_buddy_del(i);
2350 printf("Buddy %d deleted\n", i);
2351 }
2352
2353 } else if (menuin[1] == 'a') {
2354
2355 if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
2356 break;
2357
2358 i = my_atoi(buf);
2359
2360 if (!pjsua_acc_is_valid(i)) {
2361 printf("Invalid account id %d\n", i);
2362 } else {
2363 pjsua_acc_del(i);
2364 printf("Account %d deleted\n", i);
2365 }
2366
2367 } else {
2368 printf("Invalid input %s\n", menuin);
2369 }
2370 break;
2371
2372 case 'H':
2373 /*
2374 * Hold call.
2375 */
2376 if (current_call != -1) {
2377
2378 pjsua_call_set_hold(current_call, NULL);
2379
2380 } else {
2381 PJ_LOG(3,(THIS_FILE, "No current call"));
2382 }
2383 break;
2384
2385 case 'v':
2386 /*
2387 * Send re-INVITE (to release hold, etc).
2388 */
2389 if (current_call != -1) {
2390
2391 pjsua_call_reinvite(current_call, PJ_TRUE, NULL);
2392
2393 } else {
2394 PJ_LOG(3,(THIS_FILE, "No current call"));
2395 }
2396 break;
2397
2398 case 'x':
2399 /*
2400 * Transfer call.
2401 */
2402 if (current_call == -1) {
2403
2404 PJ_LOG(3,(THIS_FILE, "No current call"));
2405
2406 } else {
2407 int call = current_call;
Benny Prijonod524e822006-09-22 12:48:18 +00002408 pjsua_msg_data msg_data;
2409 pjsip_generic_string_hdr refer_sub;
2410 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2411 pj_str_t STR_FALSE = { "false", 5 };
Benny Prijonof7b1c392006-11-11 16:46:34 +00002412 pjsua_call_info ci;
2413
2414 pjsua_call_get_info(current_call, &ci);
2415 printf("Transfering current call [%d] %.*s\n",
2416 current_call,
2417 (int)ci.remote_info.slen, ci.remote_info.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002418
2419 ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
2420
2421 /* Check if call is still there. */
2422
2423 if (call != current_call) {
2424 puts("Call has been disconnected");
2425 continue;
2426 }
2427
Benny Prijonod524e822006-09-22 12:48:18 +00002428 pjsua_msg_data_init(&msg_data);
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002429 if (app_config.no_refersub) {
2430 /* Add Refer-Sub: false in outgoing REFER request */
2431 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2432 &STR_FALSE);
2433 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2434 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002435 if (result.nb_result != NO_NB) {
2436 if (result.nb_result == -1 || result.nb_result == 0)
2437 puts("You can't do that with transfer call!");
2438 else {
2439 pjsua_buddy_info binfo;
2440 pjsua_buddy_get_info(result.nb_result-1, &binfo);
Benny Prijonod524e822006-09-22 12:48:18 +00002441 pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002442 }
2443
2444 } else if (result.uri_result) {
2445 pj_str_t tmp;
2446 tmp = pj_str(result.uri_result);
Benny Prijonod524e822006-09-22 12:48:18 +00002447 pjsua_call_xfer( current_call, &tmp, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002448 }
2449 }
2450 break;
2451
Benny Prijonof7b1c392006-11-11 16:46:34 +00002452 case 'X':
2453 /*
2454 * Transfer call with replaces.
2455 */
2456 if (current_call == -1) {
2457
2458 PJ_LOG(3,(THIS_FILE, "No current call"));
2459
2460 } else {
2461 int call = current_call;
2462 int dst_call;
2463 pjsua_msg_data msg_data;
2464 pjsip_generic_string_hdr refer_sub;
2465 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2466 pj_str_t STR_FALSE = { "false", 5 };
2467 pjsua_call_id ids[PJSUA_MAX_CALLS];
2468 pjsua_call_info ci;
2469 unsigned i, count;
2470
2471 count = PJ_ARRAY_SIZE(ids);
2472 pjsua_enum_calls(ids, &count);
2473
2474 if (count <= 1) {
2475 puts("There are no other calls");
2476 continue;
2477 }
2478
2479 pjsua_call_get_info(current_call, &ci);
2480 printf("Transfer call [%d] %.*s to one of the following:\n",
2481 current_call,
2482 (int)ci.remote_info.slen, ci.remote_info.ptr);
2483
2484 for (i=0; i<count; ++i) {
2485 pjsua_call_info call_info;
2486
2487 if (ids[i] == call)
2488 continue;
2489
2490 pjsua_call_get_info(ids[i], &call_info);
2491 printf("%d %.*s [%.*s]\n",
2492 ids[i],
2493 (int)call_info.remote_info.slen,
2494 call_info.remote_info.ptr,
2495 (int)call_info.state_text.slen,
2496 call_info.state_text.ptr);
2497 }
2498
2499 if (!simple_input("Enter call number to be replaced",
2500 buf, sizeof(buf)))
2501 continue;
2502
2503 dst_call = my_atoi(buf);
2504
2505 /* Check if call is still there. */
2506
2507 if (call != current_call) {
2508 puts("Call has been disconnected");
2509 continue;
2510 }
2511
2512 /* Check that destination call is valid. */
2513 if (dst_call == call) {
2514 puts("Destination call number must not be the same "
2515 "as the call being transfered");
2516 continue;
2517 }
2518 if (dst_call >= PJSUA_MAX_CALLS) {
2519 puts("Invalid destination call number");
2520 continue;
2521 }
2522 if (!pjsua_call_is_active(dst_call)) {
2523 puts("Invalid destination call number");
2524 continue;
2525 }
2526
2527 pjsua_msg_data_init(&msg_data);
2528 if (app_config.no_refersub) {
2529 /* Add Refer-Sub: false in outgoing REFER request */
2530 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2531 &STR_FALSE);
2532 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2533 }
2534
2535 pjsua_call_xfer_replaces(call, dst_call, 0, &msg_data);
2536 }
2537 break;
2538
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002539 case '#':
2540 /*
2541 * Send DTMF strings.
2542 */
2543 if (current_call == -1) {
2544
2545 PJ_LOG(3,(THIS_FILE, "No current call"));
2546
2547 } else if (!pjsua_call_has_media(current_call)) {
2548
2549 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
2550
2551 } else {
2552 pj_str_t digits;
2553 int call = current_call;
2554 pj_status_t status;
2555
2556 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
2557 sizeof(buf)))
2558 {
2559 break;
2560 }
2561
2562 if (call != current_call) {
2563 puts("Call has been disconnected");
2564 continue;
2565 }
2566
2567 digits = pj_str(buf);
2568 status = pjsua_call_dial_dtmf(current_call, &digits);
2569 if (status != PJ_SUCCESS) {
2570 pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
2571 } else {
2572 puts("DTMF digits enqueued for transmission");
2573 }
2574 }
2575 break;
2576
Benny Prijono56315612006-07-18 14:39:40 +00002577 case 'S':
2578 /*
2579 * Send arbitrary request
2580 */
2581 if (pjsua_acc_get_count() == 0) {
2582 puts("Sorry, need at least one account configured");
2583 break;
2584 }
2585
2586 puts("Send arbitrary request to remote host");
2587
2588 /* Input METHOD */
2589 if (!simple_input("Request method:",text,sizeof(text)))
2590 break;
2591
2592 /* Input destination URI */
2593 uri = NULL;
2594 ui_input_url("Destination URI", buf, sizeof(buf), &result);
2595 if (result.nb_result != NO_NB) {
2596
2597 if (result.nb_result == -1 || result.nb_result == 0) {
2598 puts("Sorry you can't do that!");
2599 continue;
2600 } else {
2601 pjsua_buddy_info binfo;
2602 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2603 uri = binfo.uri.ptr;
2604 }
2605
2606 } else if (result.uri_result) {
2607 uri = result.uri_result;
2608 }
2609
2610 tmp = pj_str(uri);
2611
2612 send_request(text, &tmp);
2613 break;
2614
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002615 case 's':
2616 case 'u':
2617 /*
2618 * Subscribe/unsubscribe presence.
2619 */
2620 ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
2621 if (result.nb_result != NO_NB) {
2622 if (result.nb_result == -1) {
2623 int i, count;
2624 count = pjsua_get_buddy_count();
2625 for (i=0; i<count; ++i)
2626 pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
2627 } else if (result.nb_result == 0) {
2628 puts("Sorry, can only subscribe to buddy's presence, "
2629 "not from existing call");
2630 } else {
2631 pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
2632 }
2633
2634 } else if (result.uri_result) {
2635 puts("Sorry, can only subscribe to buddy's presence, "
2636 "not arbitrary URL (for now)");
2637 }
2638
2639 break;
2640
2641 case 'r':
2642 switch (menuin[1]) {
2643 case 'r':
2644 /*
2645 * Re-Register.
2646 */
2647 pjsua_acc_set_registration(current_acc, PJ_TRUE);
2648 break;
2649 case 'u':
2650 /*
2651 * Unregister
2652 */
2653 pjsua_acc_set_registration(current_acc, PJ_FALSE);
2654 break;
2655 }
2656 break;
2657
2658 case 't':
2659 pjsua_acc_get_info(current_acc, &acc_info);
2660 acc_info.online_status = !acc_info.online_status;
2661 pjsua_acc_set_online_status(current_acc, acc_info.online_status);
2662 printf("Setting %s online status to %s\n",
2663 acc_info.acc_uri.ptr,
2664 (acc_info.online_status?"online":"offline"));
2665 break;
2666
2667 case 'c':
2668 switch (menuin[1]) {
2669 case 'l':
2670 conf_list();
2671 break;
2672 case 'c':
2673 case 'd':
2674 {
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002675 char tmp[10], src_port[10], dst_port[10];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002676 pj_status_t status;
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002677 int cnt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002678 const char *src_title, *dst_title;
2679
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002680 cnt = sscanf(menuin, "%s %s %s", tmp, src_port, dst_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002681
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002682 if (cnt != 3) {
2683 conf_list();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002684
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002685 src_title = (menuin[1]=='c'?
2686 "Connect src port #":
2687 "Disconnect src port #");
2688 dst_title = (menuin[1]=='c'?
2689 "To dst port #":
2690 "From dst port #");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002691
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002692 if (!simple_input(src_title, src_port, sizeof(src_port)))
2693 break;
2694
2695 if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
2696 break;
2697 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002698
2699 if (menuin[1]=='c') {
2700 status = pjsua_conf_connect(my_atoi(src_port),
2701 my_atoi(dst_port));
2702 } else {
2703 status = pjsua_conf_disconnect(my_atoi(src_port),
2704 my_atoi(dst_port));
2705 }
2706 if (status == PJ_SUCCESS) {
2707 puts("Success");
2708 } else {
2709 puts("ERROR!!");
2710 }
2711 }
2712 break;
2713 }
2714 break;
2715
2716 case 'd':
2717 if (menuin[1] == 'c') {
2718 char settings[2000];
2719 int len;
2720
2721 len = write_settings(&app_config, settings, sizeof(settings));
2722 if (len < 1)
2723 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2724 else
2725 PJ_LOG(3,(THIS_FILE,
2726 "Dumping configuration (%d bytes):\n%s\n",
2727 len, settings));
Benny Prijono819506c2006-06-22 22:29:51 +00002728
2729 } else if (menuin[1] == 'q') {
2730
2731 if (current_call != PJSUA_INVALID_ID) {
2732 char buf[1024];
2733 pjsua_call_dump(current_call, PJ_TRUE, buf,
2734 sizeof(buf), " ");
2735 PJ_LOG(3,(THIS_FILE, "\n%s", buf));
2736 } else {
2737 PJ_LOG(3,(THIS_FILE, "No current call"));
2738 }
2739
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002740 } else {
2741 app_dump(menuin[1]=='d');
2742 }
2743 break;
2744
2745
2746 case 'f':
2747 if (simple_input("Enter output filename", buf, sizeof(buf))) {
2748 char settings[2000];
2749 int len;
2750
2751 len = write_settings(&app_config, settings, sizeof(settings));
2752 if (len < 1)
2753 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2754 else {
2755 pj_oshandle_t fd;
2756 pj_status_t status;
2757
2758 status = pj_file_open(app_config.pool, buf,
2759 PJ_O_WRONLY, &fd);
2760 if (status != PJ_SUCCESS) {
2761 pjsua_perror(THIS_FILE, "Unable to open file", status);
2762 } else {
2763 pj_ssize_t size = len;
2764 pj_file_write(fd, settings, &size);
2765 pj_file_close(fd);
2766
2767 printf("Settings successfully written to '%s'\n", buf);
2768 }
2769 }
2770
2771 }
2772 break;
2773
2774
2775 case 'q':
2776 goto on_exit;
2777
2778
2779 default:
2780 if (menuin[0] != '\n' && menuin[0] != '\r') {
2781 printf("Invalid input %s", menuin);
2782 }
2783 keystroke_help();
2784 break;
2785 }
2786 }
2787
2788on_exit:
2789 ;
2790}
2791
2792
2793/*****************************************************************************
2794 * Public API
2795 */
2796
2797pj_status_t app_init(int argc, char *argv[])
2798{
Benny Prijonoe93e2872006-06-28 16:46:49 +00002799 pjsua_transport_id transport_id = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002800 unsigned i;
2801 pj_status_t status;
2802
2803 /* Create pjsua */
2804 status = pjsua_create();
2805 if (status != PJ_SUCCESS)
2806 return status;
2807
2808 /* Create pool for application */
2809 app_config.pool = pjsua_pool_create("pjsua", 4000, 4000);
2810
2811 /* Initialize default config */
2812 default_config(&app_config);
2813
2814 /* Parse the arguments */
2815 status = parse_args(argc, argv, &app_config, &uri_arg);
2816 if (status != PJ_SUCCESS)
2817 return status;
2818
2819 /* Copy udp_cfg STUN config to rtp_cfg */
2820 app_config.rtp_cfg.use_stun = app_config.udp_cfg.use_stun;
2821 app_config.rtp_cfg.stun_config = app_config.udp_cfg.stun_config;
2822
2823
2824 /* Initialize application callbacks */
2825 app_config.cfg.cb.on_call_state = &on_call_state;
2826 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
2827 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
Benny Prijono0875ae82006-12-26 00:11:48 +00002828 app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002829 app_config.cfg.cb.on_reg_state = &on_reg_state;
2830 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
2831 app_config.cfg.cb.on_pager = &on_pager;
2832 app_config.cfg.cb.on_typing = &on_typing;
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002833 app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
Benny Prijonof7b1c392006-11-11 16:46:34 +00002834 app_config.cfg.cb.on_call_replaced = &on_call_replaced;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002835
2836 /* Initialize pjsua */
2837 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
2838 &app_config.media_cfg);
2839 if (status != PJ_SUCCESS)
2840 return status;
2841
Benny Prijonoe909eac2006-07-27 22:04:56 +00002842#ifdef STEREO_DEMO
2843 stereo_demo();
2844#endif
2845
Benny Prijono804ff0a2006-09-14 11:17:48 +00002846 /* Initialize calls data */
2847 for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
2848 app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
2849 app_config.call_data[i].timer.cb = &call_timeout_callback;
2850 }
2851
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002852 /* Optionally registers WAV file */
2853 if (app_config.wav_file.slen) {
Benny Prijono6fd4b8f2006-06-22 18:51:50 +00002854 status = pjsua_player_create(&app_config.wav_file, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002855 &app_config.wav_id);
2856 if (status != PJ_SUCCESS)
2857 goto on_error;
Benny Prijono22a300a2006-06-14 20:04:55 +00002858
2859 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002860 }
2861
Benny Prijono1ebd6142006-10-19 15:48:02 +00002862 /* Optionally create recorder file, if any. */
2863 if (app_config.rec_file.slen) {
2864 status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
2865 &app_config.rec_id);
2866 if (status != PJ_SUCCESS)
2867 goto on_error;
2868
2869 app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
2870 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002871
Benny Prijonoe93e2872006-06-28 16:46:49 +00002872 /* Add TCP transport unless it's disabled */
2873 if (!app_config.no_tcp) {
2874 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
2875 &app_config.udp_cfg,
2876 &transport_id);
2877 if (status != PJ_SUCCESS)
2878 goto on_error;
2879
2880 /* Add local account */
Benny Prijono21b9ad92006-08-15 13:11:22 +00002881 pjsua_acc_add_local(transport_id, PJ_TRUE, NULL);
Benny Prijonoe93e2872006-06-28 16:46:49 +00002882 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2883
2884 }
2885
Benny Prijonoe93e2872006-06-28 16:46:49 +00002886 /* Add UDP transport unless it's disabled. */
2887 if (!app_config.no_udp) {
2888 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
2889 &app_config.udp_cfg,
2890 &transport_id);
2891 if (status != PJ_SUCCESS)
2892 goto on_error;
2893
2894 /* Add local account */
Benny Prijono21b9ad92006-08-15 13:11:22 +00002895 pjsua_acc_add_local(transport_id, PJ_TRUE, NULL);
Benny Prijonoe93e2872006-06-28 16:46:49 +00002896 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2897 }
2898
Benny Prijono6e0e54b2006-12-08 21:58:31 +00002899#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
2900 /* Add TLS transport when application wants one */
2901 if (app_config.use_tls) {
Benny Prijonof3bbc132006-12-25 06:43:59 +00002902
2903 pjsua_acc_id acc_id;
2904
2905 /* Set TLS port as TCP port+1 */
2906 app_config.udp_cfg.port++;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00002907 status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
2908 &app_config.udp_cfg,
2909 &transport_id);
Benny Prijonof3bbc132006-12-25 06:43:59 +00002910 app_config.udp_cfg.port--;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00002911 if (status != PJ_SUCCESS)
2912 goto on_error;
Benny Prijonof3bbc132006-12-25 06:43:59 +00002913
2914 /* Add local account */
2915 pjsua_acc_add_local(transport_id, PJ_FALSE, &acc_id);
2916 pjsua_acc_set_online_status(acc_id, PJ_TRUE);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00002917 }
2918#endif
2919
Benny Prijonoe93e2872006-06-28 16:46:49 +00002920 if (transport_id == -1) {
2921 PJ_LOG(3,(THIS_FILE, "Error: no transport is configured"));
2922 status = -1;
2923 goto on_error;
2924 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002925
2926
2927 /* Add accounts */
2928 for (i=0; i<app_config.acc_cnt; ++i) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00002929 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002930 if (status != PJ_SUCCESS)
2931 goto on_error;
2932 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2933 }
2934
2935 /* Add buddies */
2936 for (i=0; i<app_config.buddy_cnt; ++i) {
2937 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
2938 if (status != PJ_SUCCESS)
2939 goto on_error;
2940 }
2941
2942 /* Optionally set codec orders */
2943 for (i=0; i<app_config.codec_cnt; ++i) {
2944 pjsua_codec_set_priority(&app_config.codec_arg[i],
2945 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
2946 }
2947
2948 /* Add RTP transports */
2949 status = pjsua_media_transports_create(&app_config.rtp_cfg);
2950 if (status != PJ_SUCCESS)
2951 goto on_error;
2952
Benny Prijono22a300a2006-06-14 20:04:55 +00002953 /* Use null sound device? */
Benny Prijonoe909eac2006-07-27 22:04:56 +00002954#ifndef STEREO_DEMO
Benny Prijono22a300a2006-06-14 20:04:55 +00002955 if (app_config.null_audio) {
2956 status = pjsua_set_null_snd_dev();
2957 if (status != PJ_SUCCESS)
2958 return status;
2959 }
Benny Prijonoe909eac2006-07-27 22:04:56 +00002960#endif
Benny Prijono22a300a2006-06-14 20:04:55 +00002961
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002962 return PJ_SUCCESS;
2963
2964on_error:
2965 pjsua_destroy();
2966 return status;
2967}
2968
2969
2970pj_status_t app_main(void)
2971{
2972 pj_status_t status;
2973
2974 /* Start pjsua */
2975 status = pjsua_start();
2976 if (status != PJ_SUCCESS) {
2977 pjsua_destroy();
2978 return status;
2979 }
2980
2981 console_app_main(&uri_arg);
2982
2983 return PJ_SUCCESS;
2984}
2985
2986pj_status_t app_destroy(void)
2987{
Benny Prijonof762ee72006-12-01 11:14:37 +00002988 pj_status_t status;
2989
Benny Prijonoe909eac2006-07-27 22:04:56 +00002990#ifdef STEREO_DEMO
2991 if (app_config.snd) {
2992 pjmedia_snd_port_destroy(app_config.snd);
2993 app_config.snd = NULL;
2994 }
2995#endif
2996
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002997 if (app_config.pool) {
2998 pj_pool_release(app_config.pool);
2999 app_config.pool = NULL;
3000 }
3001
Benny Prijonof762ee72006-12-01 11:14:37 +00003002 status = pjsua_destroy();
3003
3004 pj_bzero(&app_config, sizeof(app_config));
3005
3006 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003007}
Benny Prijonoe909eac2006-07-27 22:04:56 +00003008
3009
3010#ifdef STEREO_DEMO
3011static void stereo_demo()
3012{
3013 pjmedia_port *conf, *splitter, *ch1;
3014 unsigned clock;
3015 pj_status_t status;
3016
3017 /* Disable existing sound device */
3018 conf = pjsua_set_no_snd_dev();
3019
3020 clock = app_config.media_cfg.clock_rate;
3021
3022 /* Create stereo-mono splitter/combiner */
3023 status = pjmedia_splitcomb_create(app_config.pool,
3024 clock /* clock rate */,
3025 2 /* stereo */,
3026 clock*2*10/1000/* 10ms samples * 2ch */,
3027 16 /* bits */,
3028 0 /* options */,
3029 &splitter);
3030 pj_assert(status == PJ_SUCCESS);
3031
3032 /* Connect channel0 (left channel?) to conference port slot0 */
3033 status = pjmedia_splitcomb_set_channel(splitter, 0 /* ch0 */,
3034 0 /*options*/,
3035 conf);
3036 pj_assert(status == PJ_SUCCESS);
3037
3038 /* Create reverse channel for channel1 (right channel?)... */
3039 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
3040 splitter,
3041 1 /* ch1 */,
3042 0 /* options */,
3043 &ch1);
3044 pj_assert(status == PJ_SUCCESS);
3045
3046 /* .. and register it to conference bridge (it would be slot1
3047 * if there's no other devices connected to the bridge)
3048 */
3049 status = pjsua_conf_add_port(app_config.pool, ch1, NULL);
3050 pj_assert(status == PJ_SUCCESS);
3051
3052 /* Create sound device */
3053 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
3054 clock /* clock rate */,
3055 2 /* stereo */,
3056 clock*2*10/1000 /* 10 ms samples * 2ch */,
3057 16 /* bits */,
3058 0, &app_config.snd);
3059 pj_assert(status == PJ_SUCCESS);
3060
3061
3062 /* Connect the splitter to the sound device */
3063 status = pjmedia_snd_port_connect(app_config.snd, splitter);
3064 pj_assert(status == PJ_SUCCESS);
3065
3066}
3067#endif
3068