blob: b59365ac5344102c594c91b1c00c40b613afc8dd [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"
23
Benny Prijonoe909eac2006-07-27 22:04:56 +000024//#define STEREO_DEMO
Benny Prijonoeebe9af2006-06-13 22:57:13 +000025
26
27/* Pjsua application data */
28static struct app_config
29{
30 pjsua_config cfg;
31 pjsua_logging_config log_cfg;
32 pjsua_media_config media_cfg;
Benny Prijonoe93e2872006-06-28 16:46:49 +000033 pj_bool_t no_tcp;
34 pj_bool_t no_udp;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000035 pjsua_transport_config udp_cfg;
36 pjsua_transport_config rtp_cfg;
37
38 unsigned acc_cnt;
39 pjsua_acc_config acc_cfg[PJSUA_MAX_ACC];
40
41 unsigned buddy_cnt;
42 pjsua_buddy_config buddy_cfg[PJSUA_MAX_BUDDIES];
43
44 pj_pool_t *pool;
45 /* Compatibility with older pjsua */
46
47 unsigned codec_cnt;
48 pj_str_t codec_arg[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +000049 pj_bool_t null_audio;
50 pj_str_t wav_file;
51 pjsua_player_id wav_id;
52 pjsua_conf_port_id wav_port;
53 pj_bool_t auto_play;
54 pj_bool_t auto_loop;
55 unsigned ptime;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000056 unsigned auto_answer;
57 unsigned duration;
Benny Prijonoe909eac2006-07-27 22:04:56 +000058
59#ifdef STEREO_DEMO
60 pjmedia_snd_port *snd;
61#endif
62
Benny Prijonoeebe9af2006-06-13 22:57:13 +000063} app_config;
64
65
66static pjsua_acc_id current_acc;
67static pjsua_call_id current_call;
68static pj_str_t uri_arg;
69
Benny Prijonoe909eac2006-07-27 22:04:56 +000070static void stereo_demo();
71
Benny Prijonoeebe9af2006-06-13 22:57:13 +000072/*****************************************************************************
73 * Configuration manipulation
74 */
75
76/* Show usage */
77static void usage(void)
78{
79 puts ("Usage:");
80 puts (" pjsua [options]");
81 puts ("");
82 puts ("General options:");
83 puts (" --help Display this help screen");
84 puts (" --version Display version info");
85 puts ("");
86 puts ("Logging options:");
87 puts (" --config-file=file Read the config/arguments from file.");
88 puts (" --log-file=fname Log to filename (default stderr)");
89 puts (" --log-level=N Set log max level to N (0(none) to 6(trace)) (default=5)");
90 puts (" --app-log-level=N Set log max level for stdout display (default=4)");
91 puts ("");
92 puts ("SIP Account options:");
93 puts (" --registrar=url Set the URL of registrar server");
94 puts (" --id=url Set the URL of local ID (used in From header)");
95 puts (" --contact=url Optionally override the Contact information");
96 puts (" --proxy=url Optional URL of proxy server to visit");
97 puts (" May be specified multiple times");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +000098 puts (" --reg-timeout=SEC Optional registration interval (default 55)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +000099 puts (" --realm=string Set realm");
100 puts (" --username=string Set authentication username");
101 puts (" --password=string Set authentication password");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000102 puts (" --next-cred Add another credentials");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000103 puts ("");
104 puts ("SIP Account Control:");
105 puts (" --next-account Add more account");
106 puts ("");
107 puts ("Transport Options:");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000108 puts (" --local-port=port Set TCP/UDP port. This implicitly enables both ");
109 puts (" TCP and UDP transports on the specified port, unless");
110 puts (" if TCP or UDP is disabled.");
111 puts (" --no-tcp Disable TCP transport.");
112 puts (" --no-udp Disable UDP transport.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000113 puts (" --outbound=url Set the URL of global outbound proxy server");
114 puts (" May be specified multiple times");
115 puts (" --use-stun1=host[:port]");
116 puts (" --use-stun2=host[:port] Resolve local IP with the specified STUN servers");
117 puts ("");
118 puts ("Media Options:");
119 puts (" --add-codec=name Manually add codec (default is to enable all)");
120 puts (" --clock-rate=N Override sound device clock rate");
121 puts (" --null-audio Use NULL audio device");
122 puts (" --play-file=file Play WAV file in conference bridge");
123 puts (" --auto-play Automatically play the file (to incoming calls only)");
124 puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP");
125 puts (" --rtp-port=N Base port to try for RTP (default=4000)");
Benny Prijono00cae612006-07-31 15:19:36 +0000126 puts (" --quality=N Specify media quality (0-10, default=6)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000127 puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)");
Benny Prijono0a12f002006-07-26 17:05:39 +0000128 puts (" --no-vad Disable VAD/silence detector (default=vad enabled)");
Benny Prijonod79f25c2006-08-02 19:41:37 +0000129 puts (" --ec-tail=MSEC Set echo canceller tail length (default=256)");
Benny Prijono00cae612006-07-31 15:19:36 +0000130 puts (" --ilbc-mode=MODE Set iLBC codec mode (20 or 30, default is 20)");
131 puts (" --rx-drop-pct=PCT Drop PCT percent of RX RTP (for pkt lost sim, default: 0)");
132 puts (" --tx-drop-pct=PCT Drop PCT percent of TX RTP (for pkt lost sim, default: 0)");
133
Benny Prijono0a12f002006-07-26 17:05:39 +0000134
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000135 puts ("");
136 puts ("Buddy List (can be more than one):");
137 puts (" --add-buddy url Add the specified URL to the buddy list.");
138 puts ("");
139 puts ("User Agent options:");
140 puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)");
141 puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000142 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000143 puts (" --duration=SEC Set maximum call duration (default:no limit)");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000144 */
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000145 puts ("");
146 fflush(stdout);
147}
148
149
150/* Set default config. */
151static void default_config(struct app_config *cfg)
152{
Benny Prijono56315612006-07-18 14:39:40 +0000153 char tmp[80];
154
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000155 pjsua_config_default(&cfg->cfg);
Benny Prijono56315612006-07-18 14:39:40 +0000156 pj_ansi_sprintf(tmp, "PJSUA v%s/%s", PJ_VERSION, PJ_OS_NAME);
157 pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
158
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000159 pjsua_logging_config_default(&cfg->log_cfg);
160 pjsua_media_config_default(&cfg->media_cfg);
161 pjsua_transport_config_default(&cfg->udp_cfg);
162 cfg->udp_cfg.port = 5060;
163 pjsua_transport_config_default(&cfg->rtp_cfg);
164 cfg->rtp_cfg.port = 4000;
165 cfg->duration = (unsigned)-1;
166 cfg->wav_id = PJSUA_INVALID_ID;
167 cfg->wav_port = PJSUA_INVALID_ID;
168}
169
170
171/*
172 * Read command arguments from config file.
173 */
174static int read_config_file(pj_pool_t *pool, const char *filename,
175 int *app_argc, char ***app_argv)
176{
177 int i;
178 FILE *fhnd;
179 char line[200];
180 int argc = 0;
181 char **argv;
182 enum { MAX_ARGS = 64 };
183
184 /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
185 argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
186 argv[argc++] = *app_argv[0];
187
188 /* Open config file. */
189 fhnd = fopen(filename, "rt");
190 if (!fhnd) {
191 PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
192 fflush(stdout);
193 return -1;
194 }
195
196 /* Scan tokens in the file. */
197 while (argc < MAX_ARGS && !feof(fhnd)) {
198 char *token, *p = line;
199
200 if (fgets(line, sizeof(line), fhnd) == NULL) break;
201
202 for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS;
203 token = strtok(NULL, " \t\r\n"))
204 {
205 int token_len;
206
207 if (!token) break;
208 if (*token == '#') break;
209
210 token_len = strlen(token);
211 if (!token_len)
212 continue;
213 argv[argc] = pj_pool_alloc(pool, token_len+1);
214 pj_memcpy(argv[argc], token, token_len+1);
215 ++argc;
216 }
217 }
218
219 /* Copy arguments from command line */
220 for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
221 argv[argc++] = (*app_argv)[i];
222
223 if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
224 PJ_LOG(1,(THIS_FILE,
225 "Too many arguments specified in cmd line/config file"));
226 fflush(stdout);
227 fclose(fhnd);
228 return -1;
229 }
230
231 fclose(fhnd);
232
233 /* Assign the new command line back to the original command line. */
234 *app_argc = argc;
235 *app_argv = argv;
236 return 0;
237
238}
239
240static int my_atoi(const char *cs)
241{
242 pj_str_t s;
243 return pj_strtoul(pj_cstr(&s, cs));
244}
245
246
247/* Parse arguments. */
248static pj_status_t parse_args(int argc, char *argv[],
249 struct app_config *cfg,
250 pj_str_t *uri_to_call)
251{
252 int c;
253 int option_index;
254 enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
255 OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
256 OPT_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR,
257 OPT_REG_TIMEOUT, OPT_ID, OPT_CONTACT,
258 OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
259 OPT_USE_STUN1, OPT_USE_STUN2,
260 OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
261 OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
262 OPT_AUTO_CONF, OPT_CLOCK_RATE,
Benny Prijono00cae612006-07-31 15:19:36 +0000263 OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC, OPT_ILBC_MODE,
Benny Prijono0a12f002006-07-26 17:05:39 +0000264 OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
Benny Prijonod79f25c2006-08-02 19:41:37 +0000265 OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL,
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000266 OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
Benny Prijonoe93e2872006-06-28 16:46:49 +0000267 OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000268 };
269 struct pj_getopt_option long_options[] = {
270 { "config-file",1, 0, OPT_CONFIG_FILE},
271 { "log-file", 1, 0, OPT_LOG_FILE},
272 { "log-level", 1, 0, OPT_LOG_LEVEL},
273 { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
274 { "help", 0, 0, OPT_HELP},
275 { "version", 0, 0, OPT_VERSION},
276 { "clock-rate", 1, 0, OPT_CLOCK_RATE},
277 { "null-audio", 0, 0, OPT_NULL_AUDIO},
278 { "local-port", 1, 0, OPT_LOCAL_PORT},
Benny Prijonoe93e2872006-06-28 16:46:49 +0000279 { "no-tcp", 0, 0, OPT_NO_TCP},
280 { "no-udp", 0, 0, OPT_NO_UDP},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000281 { "proxy", 1, 0, OPT_PROXY},
282 { "outbound", 1, 0, OPT_OUTBOUND_PROXY},
283 { "registrar", 1, 0, OPT_REGISTRAR},
284 { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
285 { "id", 1, 0, OPT_ID},
286 { "contact", 1, 0, OPT_CONTACT},
287 { "realm", 1, 0, OPT_REALM},
288 { "username", 1, 0, OPT_USERNAME},
289 { "password", 1, 0, OPT_PASSWORD},
290 { "use-stun1", 1, 0, OPT_USE_STUN1},
291 { "use-stun2", 1, 0, OPT_USE_STUN2},
292 { "add-buddy", 1, 0, OPT_ADD_BUDDY},
293 { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
294 { "no-presence", 0, 0, OPT_NO_PRESENCE},
295 { "auto-answer",1, 0, OPT_AUTO_ANSWER},
296 { "auto-hangup",1, 0, OPT_AUTO_HANGUP},
297 { "auto-play", 0, 0, OPT_AUTO_PLAY},
298 { "auto-loop", 0, 0, OPT_AUTO_LOOP},
299 { "auto-conf", 0, 0, OPT_AUTO_CONF},
300 { "play-file", 1, 0, OPT_PLAY_FILE},
301 { "rtp-port", 1, 0, OPT_RTP_PORT},
302 { "add-codec", 1, 0, OPT_ADD_CODEC},
303 { "complexity", 1, 0, OPT_COMPLEXITY},
304 { "quality", 1, 0, OPT_QUALITY},
305 { "ptime", 1, 0, OPT_PTIME},
Benny Prijono0a12f002006-07-26 17:05:39 +0000306 { "no-vad", 0, 0, OPT_NO_VAD},
Benny Prijonod79f25c2006-08-02 19:41:37 +0000307 { "ec-tail", 1, 0, OPT_EC_TAIL},
Benny Prijono00cae612006-07-31 15:19:36 +0000308 { "ilbc-mode", 1, 0, OPT_ILBC_MODE},
309 { "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
310 { "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000311 { "next-account",0,0, OPT_NEXT_ACCOUNT},
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000312 { "next-cred", 0, 0, OPT_NEXT_CRED},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000313 { "max-calls", 1, 0, OPT_MAX_CALLS},
314 { "duration",1,0, OPT_DURATION},
315 { NULL, 0, 0, 0}
316 };
317 pj_status_t status;
318 pjsua_acc_config *cur_acc;
319 char *config_file = NULL;
320 unsigned i;
321
322 /* Run pj_getopt once to see if user specifies config file to read. */
323 while ((c=pj_getopt_long(argc, argv, "", long_options,
324 &option_index)) != -1)
325 {
326 switch (c) {
327 case OPT_CONFIG_FILE:
328 config_file = pj_optarg;
329 break;
330 }
331 if (config_file)
332 break;
333 }
334
335 if (config_file) {
336 status = read_config_file(app_config.pool, config_file, &argc, &argv);
337 if (status != 0)
338 return status;
339 }
340
341 cfg->acc_cnt = 0;
342 cur_acc = &cfg->acc_cfg[0];
343
344
345 /* Reinitialize and re-run pj_getopt again, possibly with new arguments
346 * read from config file.
347 */
348 pj_optind = 0;
349 while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
350 char *p;
351 pj_str_t tmp;
352 long lval;
353
354 switch (c) {
355
Benny Prijono6f137482006-06-15 11:31:36 +0000356 case OPT_CONFIG_FILE:
357 /* Ignore as this has been processed before */
358 break;
359
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000360 case OPT_LOG_FILE:
361 cfg->log_cfg.log_filename = pj_str(pj_optarg);
362 break;
363
364 case OPT_LOG_LEVEL:
365 c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
366 if (c < 0 || c > 6) {
367 PJ_LOG(1,(THIS_FILE,
368 "Error: expecting integer value 0-6 "
369 "for --log-level"));
370 return PJ_EINVAL;
371 }
372 cfg->log_cfg.level = c;
373 pj_log_set_level( c );
374 break;
375
376 case OPT_APP_LOG_LEVEL:
377 cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
378 if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
379 PJ_LOG(1,(THIS_FILE,
380 "Error: expecting integer value 0-6 "
381 "for --app-log-level"));
382 return PJ_EINVAL;
383 }
384 break;
385
386 case OPT_HELP:
387 usage();
388 return PJ_EINVAL;
389
390 case OPT_VERSION: /* version */
391 pj_dump_config();
392 return PJ_EINVAL;
393
394 case OPT_NULL_AUDIO:
395 cfg->null_audio = PJ_TRUE;
396 break;
397
398 case OPT_CLOCK_RATE:
399 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
400 if (lval < 8000 || lval > 48000) {
401 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
402 "8000-48000 for clock rate"));
403 return PJ_EINVAL;
404 }
405 cfg->media_cfg.clock_rate = lval;
406 break;
407
408 case OPT_LOCAL_PORT: /* local-port */
409 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
410 if (lval < 1 || lval > 65535) {
411 PJ_LOG(1,(THIS_FILE,
412 "Error: expecting integer value for "
413 "--local-port"));
414 return PJ_EINVAL;
415 }
416 cfg->udp_cfg.port = (pj_uint16_t)lval;
417 break;
418
Benny Prijonoe93e2872006-06-28 16:46:49 +0000419 case OPT_NO_UDP: /* no-udp */
420 if (cfg->no_tcp) {
421 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
422 return PJ_EINVAL;
423 }
424
425 cfg->no_udp = PJ_TRUE;
426 break;
427
428 case OPT_NO_TCP: /* no-tcp */
429 if (cfg->no_udp) {
430 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
431 return PJ_EINVAL;
432 }
433
434 cfg->no_tcp = PJ_TRUE;
435 break;
436
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000437 case OPT_PROXY: /* proxy */
438 if (pjsua_verify_sip_url(pj_optarg) != 0) {
439 PJ_LOG(1,(THIS_FILE,
440 "Error: invalid SIP URL '%s' "
441 "in proxy argument", pj_optarg));
442 return PJ_EINVAL;
443 }
444 cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
445 break;
446
447 case OPT_OUTBOUND_PROXY: /* outbound proxy */
448 if (pjsua_verify_sip_url(pj_optarg) != 0) {
449 PJ_LOG(1,(THIS_FILE,
450 "Error: invalid SIP URL '%s' "
451 "in outbound proxy argument", pj_optarg));
452 return PJ_EINVAL;
453 }
454 cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
455 break;
456
457 case OPT_REGISTRAR: /* registrar */
458 if (pjsua_verify_sip_url(pj_optarg) != 0) {
459 PJ_LOG(1,(THIS_FILE,
460 "Error: invalid SIP URL '%s' in "
461 "registrar argument", pj_optarg));
462 return PJ_EINVAL;
463 }
464 cur_acc->reg_uri = pj_str(pj_optarg);
465 break;
466
467 case OPT_REG_TIMEOUT: /* reg-timeout */
468 cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
469 if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
470 PJ_LOG(1,(THIS_FILE,
471 "Error: invalid value for --reg-timeout "
472 "(expecting 1-3600)"));
473 return PJ_EINVAL;
474 }
475 break;
476
477 case OPT_ID: /* id */
478 if (pjsua_verify_sip_url(pj_optarg) != 0) {
479 PJ_LOG(1,(THIS_FILE,
480 "Error: invalid SIP URL '%s' "
481 "in local id argument", pj_optarg));
482 return PJ_EINVAL;
483 }
484 cur_acc->id = pj_str(pj_optarg);
485 break;
486
487 case OPT_CONTACT: /* contact */
488 if (pjsua_verify_sip_url(pj_optarg) != 0) {
489 PJ_LOG(1,(THIS_FILE,
490 "Error: invalid SIP URL '%s' "
491 "in contact argument", pj_optarg));
492 return PJ_EINVAL;
493 }
Benny Prijonob4a17c92006-07-10 14:40:21 +0000494 cur_acc->force_contact = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000495 break;
496
497 case OPT_NEXT_ACCOUNT: /* Add more account. */
498 cfg->acc_cnt++;
Benny Prijono56315612006-07-18 14:39:40 +0000499 cur_acc = &cfg->acc_cfg[cfg->acc_cnt];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000500 break;
501
502 case OPT_USERNAME: /* Default authentication user */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000503 cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
504 cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("digest");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000505 break;
506
507 case OPT_REALM: /* Default authentication realm. */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000508 cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000509 break;
510
511 case OPT_PASSWORD: /* authentication password */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000512 cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
513 cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
514 break;
515
516 case OPT_NEXT_CRED: /* next credential */
517 cur_acc->cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000518 break;
519
520 case OPT_USE_STUN1: /* STUN server 1 */
521 p = pj_ansi_strchr(pj_optarg, ':');
522 if (p) {
523 *p = '\0';
524 cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
525 cfg->udp_cfg.stun_config.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1));
526 if (cfg->udp_cfg.stun_config.stun_port1 < 1 || cfg->udp_cfg.stun_config.stun_port1 > 65535) {
527 PJ_LOG(1,(THIS_FILE,
528 "Error: expecting port number with "
529 "option --use-stun1"));
530 return PJ_EINVAL;
531 }
532 } else {
533 cfg->udp_cfg.stun_config.stun_port1 = 3478;
534 cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
535 }
536 cfg->udp_cfg.use_stun = PJ_TRUE;
537 break;
538
539 case OPT_USE_STUN2: /* STUN server 2 */
540 p = pj_ansi_strchr(pj_optarg, ':');
541 if (p) {
542 *p = '\0';
543 cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
544 cfg->udp_cfg.stun_config.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1));
545 if (cfg->udp_cfg.stun_config.stun_port2 < 1 || cfg->udp_cfg.stun_config.stun_port2 > 65535) {
546 PJ_LOG(1,(THIS_FILE,
547 "Error: expecting port number with "
548 "option --use-stun2"));
549 return PJ_EINVAL;
550 }
551 } else {
552 cfg->udp_cfg.stun_config.stun_port2 = 3478;
553 cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
554 }
555 break;
556
557 case OPT_ADD_BUDDY: /* Add to buddy list. */
558 if (pjsua_verify_sip_url(pj_optarg) != 0) {
559 PJ_LOG(1,(THIS_FILE,
560 "Error: invalid URL '%s' in "
561 "--add-buddy option", pj_optarg));
562 return -1;
563 }
564 if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
565 PJ_LOG(1,(THIS_FILE,
566 "Error: too many buddies in buddy list."));
567 return -1;
568 }
569 cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
570 cfg->buddy_cnt++;
571 break;
572
573 case OPT_AUTO_PLAY:
574 cfg->auto_play = 1;
575 break;
576
577 case OPT_AUTO_LOOP:
578 cfg->auto_loop = 1;
579 break;
580
581 case OPT_PLAY_FILE:
582 cfg->wav_file = pj_str(pj_optarg);
583 break;
584
585 case OPT_RTP_PORT:
586 cfg->rtp_cfg.port = my_atoi(pj_optarg);
587 if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
588 PJ_LOG(1,(THIS_FILE,
589 "Error: rtp-port argument value "
590 "(expecting 1-65535"));
591 return -1;
592 }
593 break;
594
595 case OPT_ADD_CODEC:
596 cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
597 break;
598
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000599 /* These options were no longer valid after new pjsua */
600 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000601 case OPT_COMPLEXITY:
602 cfg->complexity = my_atoi(pj_optarg);
603 if (cfg->complexity < 0 || cfg->complexity > 10) {
604 PJ_LOG(1,(THIS_FILE,
605 "Error: invalid --complexity (expecting 0-10"));
606 return -1;
607 }
608 break;
609
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000610 case OPT_DURATION:
611 cfg->duration = my_atoi(pj_optarg);
612 break;
Benny Prijono0a12f002006-07-26 17:05:39 +0000613 */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000614
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000615 case OPT_PTIME:
Benny Prijono0a12f002006-07-26 17:05:39 +0000616 cfg->media_cfg.ptime = my_atoi(pj_optarg);
617 if (cfg->media_cfg.ptime < 10 || cfg->media_cfg.ptime > 1000) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000618 PJ_LOG(1,(THIS_FILE,
619 "Error: invalid --ptime option"));
620 return -1;
621 }
622 break;
623
Benny Prijono0a12f002006-07-26 17:05:39 +0000624 case OPT_NO_VAD:
625 cfg->media_cfg.no_vad = PJ_TRUE;
626 break;
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000627
Benny Prijonod79f25c2006-08-02 19:41:37 +0000628 case OPT_EC_TAIL:
629 cfg->media_cfg.ec_tail_len = my_atoi(pj_optarg);
630 if (cfg->media_cfg.ec_tail_len > 1000) {
631 PJ_LOG(1,(THIS_FILE, "I think the ec-tail length setting "
632 "is too big"));
633 return -1;
634 }
635 break;
636
Benny Prijono0498d902006-06-19 14:49:14 +0000637 case OPT_QUALITY:
638 cfg->media_cfg.quality = my_atoi(pj_optarg);
639 if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
640 PJ_LOG(1,(THIS_FILE,
641 "Error: invalid --quality (expecting 0-10"));
642 return -1;
643 }
644 break;
645
Benny Prijono00cae612006-07-31 15:19:36 +0000646 case OPT_ILBC_MODE:
647 cfg->media_cfg.ilbc_mode = my_atoi(pj_optarg);
648 if (cfg->media_cfg.ilbc_mode!=20 && cfg->media_cfg.ilbc_mode!=30) {
649 PJ_LOG(1,(THIS_FILE,
650 "Error: invalid --ilbc-mode (expecting 20 or 30"));
651 return -1;
652 }
653 break;
654
655 case OPT_RX_DROP_PCT:
656 cfg->media_cfg.rx_drop_pct = my_atoi(pj_optarg);
657 if (cfg->media_cfg.rx_drop_pct > 100) {
658 PJ_LOG(1,(THIS_FILE,
659 "Error: invalid --rx-drop-pct (expecting <= 100"));
660 return -1;
661 }
662 break;
663
664 case OPT_TX_DROP_PCT:
665 cfg->media_cfg.tx_drop_pct = my_atoi(pj_optarg);
666 if (cfg->media_cfg.tx_drop_pct > 100) {
667 PJ_LOG(1,(THIS_FILE,
668 "Error: invalid --tx-drop-pct (expecting <= 100"));
669 return -1;
670 }
671 break;
672
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000673 case OPT_AUTO_ANSWER:
674 cfg->auto_answer = my_atoi(pj_optarg);
675 if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
676 PJ_LOG(1,(THIS_FILE,
677 "Error: invalid code in --auto-answer "
678 "(expecting 100-699"));
679 return -1;
680 }
681 break;
682
683 case OPT_MAX_CALLS:
684 cfg->cfg.max_calls = my_atoi(pj_optarg);
Benny Prijono48af79c2006-07-22 12:49:17 +0000685 if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > PJSUA_MAX_CALLS) {
686 PJ_LOG(1,(THIS_FILE,"Too many calls for max-calls (1-%d)",
687 PJSUA_MAX_CALLS));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000688 return -1;
689 }
690 break;
691
Benny Prijono22a300a2006-06-14 20:04:55 +0000692 default:
Benny Prijono787b8692006-06-19 12:40:03 +0000693 PJ_LOG(1,(THIS_FILE,
694 "Argument \"--%s\" is not valid. Use --help to see help",
695 long_options[option_index].name));
Benny Prijono22a300a2006-06-14 20:04:55 +0000696 return -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000697 }
698 }
699
700 if (pj_optind != argc) {
701 pj_str_t uri_arg;
702
703 if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
704 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
705 return -1;
706 }
707 uri_arg = pj_str(argv[pj_optind]);
708 if (uri_to_call)
709 *uri_to_call = uri_arg;
710 pj_optind++;
711
712 /* Add URI to call to buddy list if it's not already there */
713 for (i=0; i<cfg->buddy_cnt; ++i) {
714 if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
715 break;
716 }
717 if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
718 cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
719 }
720
721 } else {
722 if (uri_to_call)
723 uri_to_call->slen = 0;
724 }
725
726 if (pj_optind != argc) {
727 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
728 return PJ_EINVAL;
729 }
730
Benny Prijono56315612006-07-18 14:39:40 +0000731 if (cfg->acc_cfg[cfg->acc_cnt].id.slen)
732 cfg->acc_cnt++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000733
734 for (i=0; i<cfg->acc_cnt; ++i) {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000735 if (cfg->acc_cfg[i].cred_info[cfg->acc_cfg[i].cred_count].username.slen)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000736 {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000737 cfg->acc_cfg[i].cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000738 }
739 }
740
741
742 return PJ_SUCCESS;
743}
744
745
746/*
747 * Save account settings
748 */
749static void write_account_settings(int acc_index, pj_str_t *result)
750{
751 unsigned i;
752 char line[128];
753 pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
754
755
756 pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
757 pj_strcat2(result, line);
758
759
760 /* Identity */
761 if (acc_cfg->id.slen) {
762 pj_ansi_sprintf(line, "--id %.*s\n",
763 (int)acc_cfg->id.slen,
764 acc_cfg->id.ptr);
765 pj_strcat2(result, line);
766 }
767
768 /* Registrar server */
769 if (acc_cfg->reg_uri.slen) {
770 pj_ansi_sprintf(line, "--registrar %.*s\n",
771 (int)acc_cfg->reg_uri.slen,
772 acc_cfg->reg_uri.ptr);
773 pj_strcat2(result, line);
774
775 pj_ansi_sprintf(line, "--reg-timeout %u\n",
776 acc_cfg->reg_timeout);
777 pj_strcat2(result, line);
778 }
779
780 /* Contact */
Benny Prijonob4a17c92006-07-10 14:40:21 +0000781 if (acc_cfg->force_contact.slen) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000782 pj_ansi_sprintf(line, "--contact %.*s\n",
Benny Prijonob4a17c92006-07-10 14:40:21 +0000783 (int)acc_cfg->force_contact.slen,
784 acc_cfg->force_contact.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000785 pj_strcat2(result, line);
786 }
787
788 /* Proxy */
789 for (i=0; i<acc_cfg->proxy_cnt; ++i) {
790 pj_ansi_sprintf(line, "--proxy %.*s\n",
791 (int)acc_cfg->proxy[i].slen,
792 acc_cfg->proxy[i].ptr);
793 pj_strcat2(result, line);
794 }
795
796 /* Credentials */
797 for (i=0; i<acc_cfg->cred_count; ++i) {
798 if (acc_cfg->cred_info[i].realm.slen) {
799 pj_ansi_sprintf(line, "--realm %.*s\n",
800 (int)acc_cfg->cred_info[i].realm.slen,
801 acc_cfg->cred_info[i].realm.ptr);
802 pj_strcat2(result, line);
803 }
804
805 if (acc_cfg->cred_info[i].username.slen) {
806 pj_ansi_sprintf(line, "--username %.*s\n",
807 (int)acc_cfg->cred_info[i].username.slen,
808 acc_cfg->cred_info[i].username.ptr);
809 pj_strcat2(result, line);
810 }
811
812 if (acc_cfg->cred_info[i].data.slen) {
813 pj_ansi_sprintf(line, "--password %.*s\n",
814 (int)acc_cfg->cred_info[i].data.slen,
815 acc_cfg->cred_info[i].data.ptr);
816 pj_strcat2(result, line);
817 }
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000818
819 if (i != acc_cfg->cred_count - 1)
820 pj_strcat2(result, "--next-cred\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000821 }
822
823}
824
825
826/*
827 * Write settings.
828 */
829static int write_settings(const struct app_config *config,
830 char *buf, pj_size_t max)
831{
832 unsigned acc_index;
833 unsigned i;
834 pj_str_t cfg;
835 char line[128];
836
837 PJ_UNUSED_ARG(max);
838
839 cfg.ptr = buf;
840 cfg.slen = 0;
841
842 /* Logging. */
843 pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
844 pj_ansi_sprintf(line, "--log-level %d\n",
845 config->log_cfg.level);
846 pj_strcat2(&cfg, line);
847
848 pj_ansi_sprintf(line, "--app-log-level %d\n",
849 config->log_cfg.console_level);
850 pj_strcat2(&cfg, line);
851
852 if (config->log_cfg.log_filename.slen) {
853 pj_ansi_sprintf(line, "--log-file %.*s\n",
854 (int)config->log_cfg.log_filename.slen,
855 config->log_cfg.log_filename.ptr);
856 pj_strcat2(&cfg, line);
857 }
858
859
860 /* Save account settings. */
861 for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
862
863 write_account_settings(acc_index, &cfg);
864
865 if (acc_index < config->acc_cnt-1)
866 pj_strcat2(&cfg, "--next-account\n");
867 }
868
869
870 pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
871
872 /* Outbound proxy */
873 for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
874 pj_ansi_sprintf(line, "--outbound %.*s\n",
875 (int)config->cfg.outbound_proxy[i].slen,
876 config->cfg.outbound_proxy[i].ptr);
877 pj_strcat2(&cfg, line);
878 }
879
880
881 /* UDP Transport. */
882 pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
883 pj_strcat2(&cfg, line);
884
885
886 /* STUN */
887 if (config->udp_cfg.stun_config.stun_port1) {
888 pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
889 (int)config->udp_cfg.stun_config.stun_srv1.slen,
890 config->udp_cfg.stun_config.stun_srv1.ptr,
891 config->udp_cfg.stun_config.stun_port1);
892 pj_strcat2(&cfg, line);
893 }
894
895 if (config->udp_cfg.stun_config.stun_port2) {
896 pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
897 (int)config->udp_cfg.stun_config.stun_srv2.slen,
898 config->udp_cfg.stun_config.stun_srv2.ptr,
899 config->udp_cfg.stun_config.stun_port2);
900 pj_strcat2(&cfg, line);
901 }
902
903
904 pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
905
906
907 /* Media */
908 if (config->null_audio)
909 pj_strcat2(&cfg, "--null-audio\n");
910 if (config->auto_play)
911 pj_strcat2(&cfg, "--auto-play\n");
912 if (config->auto_loop)
913 pj_strcat2(&cfg, "--auto-loop\n");
914 if (config->wav_file.slen) {
915 pj_ansi_sprintf(line, "--play-file %s\n",
916 config->wav_file.ptr);
917 pj_strcat2(&cfg, line);
918 }
919 /* Media clock rate. */
Benny Prijono70972992006-08-05 11:13:58 +0000920 if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000921 pj_ansi_sprintf(line, "--clock-rate %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +0000922 config->media_cfg.clock_rate);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000923 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +0000924 } else {
925 pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
926 config->media_cfg.clock_rate);
927 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000928 }
Benny Prijono70972992006-08-05 11:13:58 +0000929
930 /* quality */
931 if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000932 pj_ansi_sprintf(line, "--quality %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +0000933 config->media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000934 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +0000935 } else {
936 pj_ansi_sprintf(line, "#using default --quality %d\n",
937 config->media_cfg.quality);
938 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000939 }
Benny Prijono0498d902006-06-19 14:49:14 +0000940
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000941
942 /* ptime */
943 if (config->ptime) {
944 pj_ansi_sprintf(line, "--ptime %d\n",
945 config->ptime);
946 pj_strcat2(&cfg, line);
947 }
948
Benny Prijono70972992006-08-05 11:13:58 +0000949 /* no-vad */
950 if (config->media_cfg.no_vad) {
951 pj_strcat2(&cfg, "--no-vad\n");
952 }
953
954 /* ec-tail */
955 if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
956 pj_ansi_sprintf(line, "--ec-tail %d\n",
957 config->media_cfg.ec_tail_len);
958 pj_strcat2(&cfg, line);
959 } else {
960 pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
961 config->media_cfg.ec_tail_len);
962 pj_strcat2(&cfg, line);
963 }
964
965
966 /* ilbc-mode */
967 if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
968 pj_ansi_sprintf(line, "--ilbc-mode %d\n",
969 config->media_cfg.ilbc_mode);
970 pj_strcat2(&cfg, line);
971 } else {
972 pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
973 config->media_cfg.ilbc_mode);
974 pj_strcat2(&cfg, line);
975 }
976
977 /* RTP drop */
978 if (config->media_cfg.tx_drop_pct) {
979 pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
980 config->media_cfg.tx_drop_pct);
981 pj_strcat2(&cfg, line);
982
983 }
984 if (config->media_cfg.rx_drop_pct) {
985 pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
986 config->media_cfg.rx_drop_pct);
987 pj_strcat2(&cfg, line);
988
989 }
990
991
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000992 /* Start RTP port. */
993 pj_ansi_sprintf(line, "--rtp-port %d\n",
994 config->rtp_cfg.port);
995 pj_strcat2(&cfg, line);
996
997 /* Add codec. */
998 for (i=0; i<config->codec_cnt; ++i) {
999 pj_ansi_sprintf(line, "--add-codec %s\n",
1000 config->codec_arg[i].ptr);
1001 pj_strcat2(&cfg, line);
1002 }
1003
1004 pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
1005
1006 /* Auto-answer. */
1007 if (config->auto_answer != 0) {
1008 pj_ansi_sprintf(line, "--auto-answer %d\n",
1009 config->auto_answer);
1010 pj_strcat2(&cfg, line);
1011 }
1012
1013 /* Max calls. */
1014 pj_ansi_sprintf(line, "--max-calls %d\n",
1015 config->cfg.max_calls);
1016 pj_strcat2(&cfg, line);
1017
1018 /* Uas-duration. */
1019 if (config->duration != (unsigned)-1) {
1020 pj_ansi_sprintf(line, "--duration %d\n",
1021 config->duration);
1022 pj_strcat2(&cfg, line);
1023 }
1024
1025 pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
1026
1027 /* Add buddies. */
1028 for (i=0; i<config->buddy_cnt; ++i) {
1029 pj_ansi_sprintf(line, "--add-buddy %.*s\n",
1030 (int)config->buddy_cfg[i].uri.slen,
1031 config->buddy_cfg[i].uri.ptr);
1032 pj_strcat2(&cfg, line);
1033 }
1034
1035
1036 *(cfg.ptr + cfg.slen) = '\0';
1037 return cfg.slen;
1038}
1039
1040
1041/*
1042 * Dump application states.
1043 */
1044static void app_dump(pj_bool_t detail)
1045{
1046 unsigned old_decor;
1047 char buf[1024];
1048
1049 PJ_LOG(3,(THIS_FILE, "Start dumping application states:"));
1050
1051 old_decor = pj_log_get_decor();
1052 pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
1053
1054 if (detail)
1055 pj_dump_config();
1056
1057 pjsip_endpt_dump(pjsua_get_pjsip_endpt(), detail);
1058 pjmedia_endpt_dump(pjsua_get_pjmedia_endpt());
1059 pjsip_tsx_layer_dump(detail);
1060 pjsip_ua_dump(detail);
1061
1062
1063 /* Dump all invite sessions: */
1064 PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
1065
1066 if (pjsua_call_get_count() == 0) {
1067
1068 PJ_LOG(3,(THIS_FILE, " - no sessions -"));
1069
1070 } else {
1071 unsigned i;
1072
1073 for (i=0; i<app_config.cfg.max_calls; ++i) {
1074 if (pjsua_call_is_active(i)) {
1075 pjsua_call_dump(i, detail, buf, sizeof(buf), " ");
1076 PJ_LOG(3,(THIS_FILE, "%s", buf));
1077 }
1078 }
1079 }
1080
1081 /* Dump presence status */
1082 pjsua_pres_dump(detail);
1083
1084 pj_log_set_decor(old_decor);
1085 PJ_LOG(3,(THIS_FILE, "Dump complete"));
1086}
1087
1088
1089/*****************************************************************************
1090 * Console application
1091 */
1092
1093/*
1094 * Find next call when current call is disconnected or when user
1095 * press ']'
1096 */
1097static pj_bool_t find_next_call(void)
1098{
1099 int i, max;
1100
1101 max = pjsua_call_get_max_count();
1102 for (i=current_call+1; i<max; ++i) {
1103 if (pjsua_call_is_active(i)) {
1104 current_call = i;
1105 return PJ_TRUE;
1106 }
1107 }
1108
1109 for (i=0; i<current_call; ++i) {
1110 if (pjsua_call_is_active(i)) {
1111 current_call = i;
1112 return PJ_TRUE;
1113 }
1114 }
1115
1116 current_call = PJSUA_INVALID_ID;
1117 return PJ_FALSE;
1118}
1119
1120
1121/*
1122 * Find previous call when user press '['
1123 */
1124static pj_bool_t find_prev_call(void)
1125{
1126 int i, max;
1127
1128 max = pjsua_call_get_max_count();
1129 for (i=current_call-1; i>=0; --i) {
1130 if (pjsua_call_is_active(i)) {
1131 current_call = i;
1132 return PJ_TRUE;
1133 }
1134 }
1135
1136 for (i=max-1; i>current_call; --i) {
1137 if (pjsua_call_is_active(i)) {
1138 current_call = i;
1139 return PJ_TRUE;
1140 }
1141 }
1142
1143 current_call = PJSUA_INVALID_ID;
1144 return PJ_FALSE;
1145}
1146
1147
1148
1149/*
1150 * Handler when invite state has changed.
1151 */
1152static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
1153{
1154 pjsua_call_info call_info;
1155
1156 PJ_UNUSED_ARG(e);
1157
1158 pjsua_call_get_info(call_id, &call_info);
1159
1160 if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
1161
1162 PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",
1163 call_id,
1164 call_info.last_status,
1165 call_info.last_status_text.ptr));
1166
1167 if (call_id == current_call) {
1168 find_next_call();
1169 }
1170
1171 } else {
1172
1173 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s",
1174 call_id,
1175 call_info.state_text.ptr));
1176
1177 if (current_call==PJSUA_INVALID_ID)
1178 current_call = call_id;
1179
1180 }
1181}
1182
1183
1184/**
1185 * Handler when there is incoming call.
1186 */
1187static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
1188 pjsip_rx_data *rdata)
1189{
1190 pjsua_call_info call_info;
1191
1192 PJ_UNUSED_ARG(acc_id);
1193 PJ_UNUSED_ARG(rdata);
1194
1195 pjsua_call_get_info(call_id, &call_info);
1196
1197 if (app_config.auto_answer > 0) {
1198 pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL);
1199 }
1200
1201 if (app_config.auto_answer < 200) {
1202 PJ_LOG(3,(THIS_FILE,
1203 "Incoming call for account %d!\n"
1204 "From: %s\n"
1205 "To: %s\n"
1206 "Press a to answer or h to reject call",
1207 acc_id,
1208 call_info.remote_info.ptr,
1209 call_info.local_info.ptr));
1210 }
1211}
1212
1213
1214/*
1215 * Callback on media state changed event.
1216 * The action may connect the call to sound device, to file, or
1217 * to loop the call.
1218 */
1219static void on_call_media_state(pjsua_call_id call_id)
1220{
1221 pjsua_call_info call_info;
1222
1223 pjsua_call_get_info(call_id, &call_info);
1224
1225 if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
1226 pj_bool_t connect_sound = PJ_TRUE;
1227
1228 /* Loopback sound, if desired */
1229 if (app_config.auto_loop) {
1230 pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
1231 connect_sound = PJ_FALSE;
1232 }
1233
1234 /* Stream a file, if desired */
1235 if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) {
1236 pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
1237 connect_sound = PJ_FALSE;
1238 }
1239
1240 /* Otherwise connect to sound device */
1241 if (connect_sound) {
1242 pjsua_conf_connect(call_info.conf_slot, 0);
1243 pjsua_conf_connect(0, call_info.conf_slot);
1244 }
1245
1246 PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
1247
1248 } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) {
1249 PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
1250 call_id));
1251 } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) {
1252 PJ_LOG(3,(THIS_FILE,
1253 "Media for call %d is suspended (hold) by remote",
1254 call_id));
1255 } else {
1256 PJ_LOG(3,(THIS_FILE,
1257 "Media for call %d is inactive",
1258 call_id));
1259 }
1260}
1261
1262
1263/*
1264 * Handler registration status has changed.
1265 */
1266static void on_reg_state(pjsua_acc_id acc_id)
1267{
1268 PJ_UNUSED_ARG(acc_id);
1269
1270 // Log already written.
1271}
1272
1273
1274/*
1275 * Handler on buddy state changed.
1276 */
1277static void on_buddy_state(pjsua_buddy_id buddy_id)
1278{
1279 pjsua_buddy_info info;
1280 pjsua_buddy_get_info(buddy_id, &info);
1281
1282 PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
1283 (int)info.uri.slen,
1284 info.uri.ptr,
1285 (int)info.status_text.slen,
1286 info.status_text.ptr));
1287}
1288
1289
1290/**
1291 * Incoming IM message (i.e. MESSAGE request)!
1292 */
1293static void on_pager(pjsua_call_id call_id, const pj_str_t *from,
1294 const pj_str_t *to, const pj_str_t *contact,
1295 const pj_str_t *mime_type, const pj_str_t *text)
1296{
1297 /* Note: call index may be -1 */
1298 PJ_UNUSED_ARG(call_id);
1299 PJ_UNUSED_ARG(to);
1300 PJ_UNUSED_ARG(contact);
1301 PJ_UNUSED_ARG(mime_type);
1302
1303 PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s",
1304 (int)from->slen, from->ptr,
1305 (int)text->slen, text->ptr));
1306}
1307
1308
1309/**
1310 * Received typing indication
1311 */
1312static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
1313 const pj_str_t *to, const pj_str_t *contact,
1314 pj_bool_t is_typing)
1315{
1316 PJ_UNUSED_ARG(call_id);
1317 PJ_UNUSED_ARG(to);
1318 PJ_UNUSED_ARG(contact);
1319
1320 PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
1321 (int)from->slen, from->ptr,
1322 (is_typing?"is typing..":"has stopped typing")));
1323}
1324
1325
1326/*
1327 * Print buddy list.
1328 */
1329static void print_buddy_list(void)
1330{
1331 pjsua_buddy_id ids[64];
1332 int i;
1333 unsigned count = PJ_ARRAY_SIZE(ids);
1334
1335 puts("Buddy list:");
1336
1337 pjsua_enum_buddies(ids, &count);
1338
1339 if (count == 0)
1340 puts(" -none-");
1341 else {
1342 for (i=0; i<(int)count; ++i) {
1343 pjsua_buddy_info info;
1344
1345 if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
1346 continue;
1347
1348 printf(" [%2d] <%7s> %.*s\n",
1349 ids[i]+1, info.status_text.ptr,
1350 (int)info.uri.slen,
1351 info.uri.ptr);
1352 }
1353 }
1354 puts("");
1355}
1356
1357
1358/*
1359 * Print account status.
1360 */
1361static void print_acc_status(int acc_id)
1362{
1363 char buf[80];
1364 pjsua_acc_info info;
1365
1366 pjsua_acc_get_info(acc_id, &info);
1367
1368 if (!info.has_registration) {
1369 pj_ansi_snprintf(buf, sizeof(buf), "%.*s",
1370 (int)info.status_text.slen,
1371 info.status_text.ptr);
1372
1373 } else {
1374 pj_ansi_snprintf(buf, sizeof(buf),
1375 "%d/%.*s (expires=%d)",
1376 info.status,
1377 (int)info.status_text.slen,
1378 info.status_text.ptr,
1379 info.expires);
1380
1381 }
1382
1383 printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
1384 acc_id, (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
1385 printf(" Online status: %s\n",
1386 (info.online_status ? "Online" : "Invisible"));
1387}
1388
1389
1390/*
1391 * Show a bit of help.
1392 */
1393static void keystroke_help(void)
1394{
1395 pjsua_acc_id acc_ids[16];
1396 unsigned count = PJ_ARRAY_SIZE(acc_ids);
1397 int i;
1398
1399 printf(">>>>\n");
1400
1401 pjsua_enum_accs(acc_ids, &count);
1402
1403 printf("Account list:\n");
1404 for (i=0; i<(int)count; ++i)
1405 print_acc_status(acc_ids[i]);
1406
1407 print_buddy_list();
1408
1409 //puts("Commands:");
1410 puts("+=============================================================================+");
1411 puts("| Call Commands: | Buddy, IM & Presence: | Account: |");
1412 puts("| | | |");
1413 puts("| m Make new call | +b Add new buddy .| +a Add new accnt |");
1414 puts("| M Make multiple calls | -b Delete buddy | -a Delete accnt. |");
1415 puts("| a Answer call | !b Modify buddy | !a Modify accnt. |");
1416 puts("| h Hangup call (ha=all) | i Send IM | rr (Re-)register |");
1417 puts("| H Hold call | s Subscribe presence | ru Unregister |");
1418 puts("| v re-inVite (release hold) | u Unsubscribe presence | > Cycle next ac.|");
1419 puts("| ] Select next dialog | t ToGgle Online status | < Cycle prev ac.|");
1420 puts("| [ Select previous dialog +--------------------------+-------------------+");
1421 puts("| x Xfer call | Media Commands: | Status & Config: |");
1422 puts("| # Send DTMF string | | |");
Benny Prijono819506c2006-06-22 22:29:51 +00001423 puts("| dq Dump curr. call quality | cl List ports | d Dump status |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001424 puts("| | cc Connect port | dd Dump detailed |");
1425 puts("| | cd Disconnect port | dc Dump config |");
Benny Prijono56315612006-07-18 14:39:40 +00001426 puts("| S Send arbitrary REQUEST | | f Save config |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001427 puts("+------------------------------+--------------------------+-------------------+");
1428 puts("| q QUIT |");
1429 puts("+=============================================================================+");
Benny Prijono48af79c2006-07-22 12:49:17 +00001430
1431 i = pjsua_call_get_count();
1432 printf("You have %d active call%s\n", i, (i>1?"s":""));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001433}
1434
1435
1436/*
1437 * Input simple string
1438 */
1439static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1440{
1441 char *p;
1442
1443 printf("%s (empty to cancel): ", title); fflush(stdout);
1444 fgets(buf, len, stdin);
1445
1446 /* Remove trailing newlines. */
1447 for (p=buf; ; ++p) {
1448 if (*p=='\r' || *p=='\n') *p='\0';
1449 else if (!*p) break;
1450 }
1451
1452 if (!*buf)
1453 return PJ_FALSE;
1454
1455 return PJ_TRUE;
1456}
1457
1458
1459#define NO_NB -2
1460struct input_result
1461{
1462 int nb_result;
1463 char *uri_result;
1464};
1465
1466
1467/*
1468 * Input URL.
1469 */
1470static void ui_input_url(const char *title, char *buf, int len,
1471 struct input_result *result)
1472{
1473 result->nb_result = NO_NB;
1474 result->uri_result = NULL;
1475
1476 print_buddy_list();
1477
1478 printf("Choices:\n"
1479 " 0 For current dialog.\n"
1480 " -1 All %d buddies in buddy list\n"
1481 " [1 -%2d] Select from buddy list\n"
1482 " URL An URL\n"
1483 " <Enter> Empty input (or 'q') to cancel\n"
1484 , pjsua_get_buddy_count(), pjsua_get_buddy_count());
1485 printf("%s: ", title);
1486
1487 fflush(stdout);
1488 fgets(buf, len, stdin);
1489 len = strlen(buf);
1490
1491 /* Left trim */
1492 while (pj_isspace(*buf)) {
1493 ++buf;
1494 --len;
1495 }
1496
1497 /* Remove trailing newlines */
1498 while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
1499 buf[--len] = '\0';
1500
1501 if (len == 0 || buf[0]=='q')
1502 return;
1503
1504 if (pj_isdigit(*buf) || *buf=='-') {
1505
1506 int i;
1507
1508 if (*buf=='-')
1509 i = 1;
1510 else
1511 i = 0;
1512
1513 for (; i<len; ++i) {
1514 if (!pj_isdigit(buf[i])) {
1515 puts("Invalid input");
1516 return;
1517 }
1518 }
1519
1520 result->nb_result = my_atoi(buf);
1521
1522 if (result->nb_result >= 0 &&
1523 result->nb_result <= (int)pjsua_get_buddy_count())
1524 {
1525 return;
1526 }
1527 if (result->nb_result == -1)
1528 return;
1529
1530 puts("Invalid input");
1531 result->nb_result = NO_NB;
1532 return;
1533
1534 } else {
1535 pj_status_t status;
1536
1537 if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
1538 pjsua_perror(THIS_FILE, "Invalid URL", status);
1539 return;
1540 }
1541
1542 result->uri_result = buf;
1543 }
1544}
1545
1546/*
1547 * List the ports in conference bridge
1548 */
1549static void conf_list(void)
1550{
1551 unsigned i, count;
1552 pjsua_conf_port_id id[PJSUA_MAX_CALLS];
1553
1554 printf("Conference ports:\n");
1555
1556 count = PJ_ARRAY_SIZE(id);
1557 pjsua_enum_conf_ports(id, &count);
1558
1559 for (i=0; i<count; ++i) {
1560 char txlist[PJSUA_MAX_CALLS*4+10];
1561 unsigned j;
1562 pjsua_conf_port_info info;
1563
1564 pjsua_conf_get_port_info(id[i], &info);
1565
1566 txlist[0] = '\0';
1567 for (j=0; j<info.listener_cnt; ++j) {
1568 char s[10];
1569 pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
1570 pj_ansi_strcat(txlist, s);
1571 }
1572 printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n",
1573 info.slot_id,
1574 info.clock_rate/1000,
1575 info.samples_per_frame * 1000 / info.clock_rate,
1576 (int)info.name.slen,
1577 info.name.ptr,
1578 txlist);
1579
1580 }
1581 puts("");
1582}
1583
1584
1585/*
Benny Prijono56315612006-07-18 14:39:40 +00001586 * Send arbitrary request to remote host
1587 */
1588static void send_request(char *cstr_method, const pj_str_t *dst_uri)
1589{
1590 pj_str_t str_method;
1591 pjsip_method method;
1592 pjsip_tx_data *tdata;
1593 pjsua_acc_info acc_info;
1594 pjsip_endpoint *endpt;
1595 pj_status_t status;
1596
1597 endpt = pjsua_get_pjsip_endpt();
1598
1599 str_method = pj_str(cstr_method);
1600 pjsip_method_init_np(&method, &str_method);
1601
1602 pjsua_acc_get_info(current_acc, &acc_info);
1603
1604 status = pjsip_endpt_create_request(endpt, &method, dst_uri,
1605 &acc_info.acc_uri, dst_uri,
1606 NULL, NULL, -1, NULL, &tdata);
1607 if (status != PJ_SUCCESS) {
1608 pjsua_perror(THIS_FILE, "Unable to create request", status);
1609 return;
1610 }
1611
1612 status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
1613 if (status != PJ_SUCCESS) {
1614 pjsua_perror(THIS_FILE, "Unable to send request", status);
1615 pjsip_tx_data_dec_ref(tdata);
1616 return;
1617 }
1618}
1619
1620
1621/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001622 * Main "user interface" loop.
1623 */
1624void console_app_main(const pj_str_t *uri_to_call)
1625{
1626 char menuin[10];
1627 char buf[128];
1628 char text[128];
1629 int i, count;
1630 char *uri;
1631 pj_str_t tmp;
1632 struct input_result result;
1633 pjsua_call_info call_info;
1634 pjsua_acc_info acc_info;
1635
1636
1637 /* If user specifies URI to call, then call the URI */
1638 if (uri_to_call->slen) {
1639 pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, NULL);
1640 }
1641
1642 keystroke_help();
1643
1644 for (;;) {
1645
1646 printf(">>> ");
1647 fflush(stdout);
1648
1649 fgets(menuin, sizeof(menuin), stdin);
1650
1651 switch (menuin[0]) {
1652
1653 case 'm':
1654 /* Make call! : */
1655 printf("(You currently have %d calls)\n",
1656 pjsua_call_get_count());
1657
1658 uri = NULL;
1659 ui_input_url("Make call", buf, sizeof(buf), &result);
1660 if (result.nb_result != NO_NB) {
1661
1662 if (result.nb_result == -1 || result.nb_result == 0) {
1663 puts("You can't do that with make call!");
1664 continue;
1665 } else {
1666 pjsua_buddy_info binfo;
1667 pjsua_buddy_get_info(result.nb_result-1, &binfo);
1668 uri = binfo.uri.ptr;
1669 }
1670
1671 } else if (result.uri_result) {
1672 uri = result.uri_result;
1673 }
1674
1675 tmp = pj_str(uri);
1676 pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL);
1677 break;
1678
1679 case 'M':
1680 /* Make multiple calls! : */
1681 printf("(You currently have %d calls)\n",
1682 pjsua_call_get_count());
1683
1684 if (!simple_input("Number of calls", menuin, sizeof(menuin)))
1685 continue;
1686
1687 count = my_atoi(menuin);
1688 if (count < 1)
1689 continue;
1690
1691 ui_input_url("Make call", buf, sizeof(buf), &result);
1692 if (result.nb_result != NO_NB) {
1693 pjsua_buddy_info binfo;
1694 if (result.nb_result == -1 || result.nb_result == 0) {
1695 puts("You can't do that with make call!");
1696 continue;
1697 }
1698 pjsua_buddy_get_info(result.nb_result-1, &binfo);
1699 uri = binfo.uri.ptr;
1700 } else {
1701 uri = result.uri_result;
1702 }
1703
1704 for (i=0; i<my_atoi(menuin); ++i) {
1705 pj_status_t status;
1706
1707 tmp = pj_str(uri);
1708 status = pjsua_call_make_call(current_acc, &tmp, 0, NULL,
1709 NULL, NULL);
1710 if (status != PJ_SUCCESS)
1711 break;
1712 }
1713 break;
1714
1715 case 'i':
1716 /* Send instant messaeg */
1717
1718 /* i is for call index to send message, if any */
1719 i = -1;
1720
1721 /* Make compiler happy. */
1722 uri = NULL;
1723
1724 /* Input destination. */
1725 ui_input_url("Send IM to", buf, sizeof(buf), &result);
1726 if (result.nb_result != NO_NB) {
1727
1728 if (result.nb_result == -1) {
1729 puts("You can't send broadcast IM like that!");
1730 continue;
1731
1732 } else if (result.nb_result == 0) {
1733
1734 i = current_call;
1735
1736 } else {
1737 pjsua_buddy_info binfo;
1738 pjsua_buddy_get_info(result.nb_result-1, &binfo);
1739 uri = binfo.uri.ptr;
1740 }
1741
1742 } else if (result.uri_result) {
1743 uri = result.uri_result;
1744 }
1745
1746
1747 /* Send typing indication. */
1748 if (i != -1)
1749 pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
1750 else {
1751 pj_str_t tmp_uri = pj_str(uri);
1752 pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
1753 }
1754
1755 /* Input the IM . */
1756 if (!simple_input("Message", text, sizeof(text))) {
1757 /*
1758 * Cancelled.
1759 * Send typing notification too, saying we're not typing.
1760 */
1761 if (i != -1)
1762 pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
1763 else {
1764 pj_str_t tmp_uri = pj_str(uri);
1765 pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
1766 }
1767 continue;
1768 }
1769
1770 tmp = pj_str(text);
1771
1772 /* Send the IM */
1773 if (i != -1)
1774 pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
1775 else {
1776 pj_str_t tmp_uri = pj_str(uri);
1777 pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
1778 }
1779
1780 break;
1781
1782 case 'a':
1783
1784 if (current_call != -1) {
1785 pjsua_call_get_info(current_call, &call_info);
1786 } else {
1787 /* Make compiler happy */
1788 call_info.role = PJSIP_ROLE_UAC;
1789 call_info.state = PJSIP_INV_STATE_DISCONNECTED;
1790 }
1791
1792 if (current_call == -1 ||
1793 call_info.role != PJSIP_ROLE_UAS ||
1794 call_info.state >= PJSIP_INV_STATE_CONNECTING)
1795 {
1796 puts("No pending incoming call");
1797 fflush(stdout);
1798 continue;
1799
1800 } else {
1801 if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
1802 continue;
1803
1804 if (my_atoi(buf) < 100)
1805 continue;
1806
1807 /*
1808 * Must check again!
1809 * Call may have been disconnected while we're waiting for
1810 * keyboard input.
1811 */
1812 if (current_call == -1) {
1813 puts("Call has been disconnected");
1814 fflush(stdout);
1815 continue;
1816 }
1817
1818 pjsua_call_answer(current_call, my_atoi(buf), NULL, NULL);
1819 }
1820
1821 break;
1822
1823
1824 case 'h':
1825
1826 if (current_call == -1) {
1827 puts("No current call");
1828 fflush(stdout);
1829 continue;
1830
1831 } else if (menuin[1] == 'a') {
1832
1833 /* Hangup all calls */
1834 pjsua_call_hangup_all();
1835
1836 } else {
1837
1838 /* Hangup current calls */
1839 pjsua_call_hangup(current_call, 0, NULL, NULL);
1840 }
1841 break;
1842
1843 case ']':
1844 case '[':
1845 /*
1846 * Cycle next/prev dialog.
1847 */
1848 if (menuin[0] == ']') {
1849 find_next_call();
1850
1851 } else {
1852 find_prev_call();
1853 }
1854
1855 if (current_call != -1) {
1856
1857 pjsua_call_get_info(current_call, &call_info);
1858 PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s",
1859 (int)call_info.remote_info.slen,
1860 call_info.remote_info.ptr));
1861
1862 } else {
1863 PJ_LOG(3,(THIS_FILE,"No current dialog"));
1864 }
1865 break;
1866
1867
1868 case '>':
1869 case '<':
1870 if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
1871 break;
1872
1873 i = my_atoi(buf);
1874 if (pjsua_acc_is_valid(i)) {
1875 current_acc = i;
1876 PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
1877 } else {
1878 PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
1879 }
1880 break;
1881
1882
1883 case '+':
1884 if (menuin[1] == 'b') {
1885
1886 pjsua_buddy_config buddy_cfg;
1887 pjsua_buddy_id buddy_id;
1888 pj_status_t status;
1889
1890 if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
1891 break;
1892
1893 if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) {
1894 printf("Invalid SIP URI '%s'\n", buf);
1895 break;
1896 }
1897
Benny Prijonoac623b32006-07-03 15:19:31 +00001898 pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001899
1900 buddy_cfg.uri = pj_str(buf);
1901 buddy_cfg.subscribe = PJ_TRUE;
1902
1903 status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
1904 if (status == PJ_SUCCESS) {
1905 printf("New buddy '%s' added at index %d\n",
1906 buf, buddy_id+1);
1907 }
1908
1909 } else if (menuin[1] == 'a') {
1910
1911 printf("Sorry, this command is not supported yet\n");
1912
1913 } else {
1914 printf("Invalid input %s\n", menuin);
1915 }
1916 break;
1917
1918 case '-':
1919 if (menuin[1] == 'b') {
1920 if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
1921 break;
1922
1923 i = my_atoi(buf) - 1;
1924
1925 if (!pjsua_buddy_is_valid(i)) {
1926 printf("Invalid buddy id %d\n", i);
1927 } else {
1928 pjsua_buddy_del(i);
1929 printf("Buddy %d deleted\n", i);
1930 }
1931
1932 } else if (menuin[1] == 'a') {
1933
1934 if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
1935 break;
1936
1937 i = my_atoi(buf);
1938
1939 if (!pjsua_acc_is_valid(i)) {
1940 printf("Invalid account id %d\n", i);
1941 } else {
1942 pjsua_acc_del(i);
1943 printf("Account %d deleted\n", i);
1944 }
1945
1946 } else {
1947 printf("Invalid input %s\n", menuin);
1948 }
1949 break;
1950
1951 case 'H':
1952 /*
1953 * Hold call.
1954 */
1955 if (current_call != -1) {
1956
1957 pjsua_call_set_hold(current_call, NULL);
1958
1959 } else {
1960 PJ_LOG(3,(THIS_FILE, "No current call"));
1961 }
1962 break;
1963
1964 case 'v':
1965 /*
1966 * Send re-INVITE (to release hold, etc).
1967 */
1968 if (current_call != -1) {
1969
1970 pjsua_call_reinvite(current_call, PJ_TRUE, NULL);
1971
1972 } else {
1973 PJ_LOG(3,(THIS_FILE, "No current call"));
1974 }
1975 break;
1976
1977 case 'x':
1978 /*
1979 * Transfer call.
1980 */
1981 if (current_call == -1) {
1982
1983 PJ_LOG(3,(THIS_FILE, "No current call"));
1984
1985 } else {
1986 int call = current_call;
1987
1988 ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
1989
1990 /* Check if call is still there. */
1991
1992 if (call != current_call) {
1993 puts("Call has been disconnected");
1994 continue;
1995 }
1996
1997 if (result.nb_result != NO_NB) {
1998 if (result.nb_result == -1 || result.nb_result == 0)
1999 puts("You can't do that with transfer call!");
2000 else {
2001 pjsua_buddy_info binfo;
2002 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2003 pjsua_call_xfer( current_call, &binfo.uri, NULL);
2004 }
2005
2006 } else if (result.uri_result) {
2007 pj_str_t tmp;
2008 tmp = pj_str(result.uri_result);
2009 pjsua_call_xfer( current_call, &tmp, NULL);
2010 }
2011 }
2012 break;
2013
2014 case '#':
2015 /*
2016 * Send DTMF strings.
2017 */
2018 if (current_call == -1) {
2019
2020 PJ_LOG(3,(THIS_FILE, "No current call"));
2021
2022 } else if (!pjsua_call_has_media(current_call)) {
2023
2024 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
2025
2026 } else {
2027 pj_str_t digits;
2028 int call = current_call;
2029 pj_status_t status;
2030
2031 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
2032 sizeof(buf)))
2033 {
2034 break;
2035 }
2036
2037 if (call != current_call) {
2038 puts("Call has been disconnected");
2039 continue;
2040 }
2041
2042 digits = pj_str(buf);
2043 status = pjsua_call_dial_dtmf(current_call, &digits);
2044 if (status != PJ_SUCCESS) {
2045 pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
2046 } else {
2047 puts("DTMF digits enqueued for transmission");
2048 }
2049 }
2050 break;
2051
Benny Prijono56315612006-07-18 14:39:40 +00002052 case 'S':
2053 /*
2054 * Send arbitrary request
2055 */
2056 if (pjsua_acc_get_count() == 0) {
2057 puts("Sorry, need at least one account configured");
2058 break;
2059 }
2060
2061 puts("Send arbitrary request to remote host");
2062
2063 /* Input METHOD */
2064 if (!simple_input("Request method:",text,sizeof(text)))
2065 break;
2066
2067 /* Input destination URI */
2068 uri = NULL;
2069 ui_input_url("Destination URI", buf, sizeof(buf), &result);
2070 if (result.nb_result != NO_NB) {
2071
2072 if (result.nb_result == -1 || result.nb_result == 0) {
2073 puts("Sorry you can't do that!");
2074 continue;
2075 } else {
2076 pjsua_buddy_info binfo;
2077 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2078 uri = binfo.uri.ptr;
2079 }
2080
2081 } else if (result.uri_result) {
2082 uri = result.uri_result;
2083 }
2084
2085 tmp = pj_str(uri);
2086
2087 send_request(text, &tmp);
2088 break;
2089
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002090 case 's':
2091 case 'u':
2092 /*
2093 * Subscribe/unsubscribe presence.
2094 */
2095 ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
2096 if (result.nb_result != NO_NB) {
2097 if (result.nb_result == -1) {
2098 int i, count;
2099 count = pjsua_get_buddy_count();
2100 for (i=0; i<count; ++i)
2101 pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
2102 } else if (result.nb_result == 0) {
2103 puts("Sorry, can only subscribe to buddy's presence, "
2104 "not from existing call");
2105 } else {
2106 pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
2107 }
2108
2109 } else if (result.uri_result) {
2110 puts("Sorry, can only subscribe to buddy's presence, "
2111 "not arbitrary URL (for now)");
2112 }
2113
2114 break;
2115
2116 case 'r':
2117 switch (menuin[1]) {
2118 case 'r':
2119 /*
2120 * Re-Register.
2121 */
2122 pjsua_acc_set_registration(current_acc, PJ_TRUE);
2123 break;
2124 case 'u':
2125 /*
2126 * Unregister
2127 */
2128 pjsua_acc_set_registration(current_acc, PJ_FALSE);
2129 break;
2130 }
2131 break;
2132
2133 case 't':
2134 pjsua_acc_get_info(current_acc, &acc_info);
2135 acc_info.online_status = !acc_info.online_status;
2136 pjsua_acc_set_online_status(current_acc, acc_info.online_status);
2137 printf("Setting %s online status to %s\n",
2138 acc_info.acc_uri.ptr,
2139 (acc_info.online_status?"online":"offline"));
2140 break;
2141
2142 case 'c':
2143 switch (menuin[1]) {
2144 case 'l':
2145 conf_list();
2146 break;
2147 case 'c':
2148 case 'd':
2149 {
2150 char src_port[10], dst_port[10];
2151 pj_status_t status;
2152 const char *src_title, *dst_title;
2153
2154 conf_list();
2155
2156 src_title = (menuin[1]=='c'?
2157 "Connect src port #":
2158 "Disconnect src port #");
2159 dst_title = (menuin[1]=='c'?
2160 "To dst port #":
2161 "From dst port #");
2162
2163 if (!simple_input(src_title, src_port, sizeof(src_port)))
2164 break;
2165
2166 if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
2167 break;
2168
2169 if (menuin[1]=='c') {
2170 status = pjsua_conf_connect(my_atoi(src_port),
2171 my_atoi(dst_port));
2172 } else {
2173 status = pjsua_conf_disconnect(my_atoi(src_port),
2174 my_atoi(dst_port));
2175 }
2176 if (status == PJ_SUCCESS) {
2177 puts("Success");
2178 } else {
2179 puts("ERROR!!");
2180 }
2181 }
2182 break;
2183 }
2184 break;
2185
2186 case 'd':
2187 if (menuin[1] == 'c') {
2188 char settings[2000];
2189 int len;
2190
2191 len = write_settings(&app_config, settings, sizeof(settings));
2192 if (len < 1)
2193 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2194 else
2195 PJ_LOG(3,(THIS_FILE,
2196 "Dumping configuration (%d bytes):\n%s\n",
2197 len, settings));
Benny Prijono819506c2006-06-22 22:29:51 +00002198
2199 } else if (menuin[1] == 'q') {
2200
2201 if (current_call != PJSUA_INVALID_ID) {
2202 char buf[1024];
2203 pjsua_call_dump(current_call, PJ_TRUE, buf,
2204 sizeof(buf), " ");
2205 PJ_LOG(3,(THIS_FILE, "\n%s", buf));
2206 } else {
2207 PJ_LOG(3,(THIS_FILE, "No current call"));
2208 }
2209
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002210 } else {
2211 app_dump(menuin[1]=='d');
2212 }
2213 break;
2214
2215
2216 case 'f':
2217 if (simple_input("Enter output filename", buf, sizeof(buf))) {
2218 char settings[2000];
2219 int len;
2220
2221 len = write_settings(&app_config, settings, sizeof(settings));
2222 if (len < 1)
2223 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2224 else {
2225 pj_oshandle_t fd;
2226 pj_status_t status;
2227
2228 status = pj_file_open(app_config.pool, buf,
2229 PJ_O_WRONLY, &fd);
2230 if (status != PJ_SUCCESS) {
2231 pjsua_perror(THIS_FILE, "Unable to open file", status);
2232 } else {
2233 pj_ssize_t size = len;
2234 pj_file_write(fd, settings, &size);
2235 pj_file_close(fd);
2236
2237 printf("Settings successfully written to '%s'\n", buf);
2238 }
2239 }
2240
2241 }
2242 break;
2243
2244
2245 case 'q':
2246 goto on_exit;
2247
2248
2249 default:
2250 if (menuin[0] != '\n' && menuin[0] != '\r') {
2251 printf("Invalid input %s", menuin);
2252 }
2253 keystroke_help();
2254 break;
2255 }
2256 }
2257
2258on_exit:
2259 ;
2260}
2261
2262
2263/*****************************************************************************
2264 * Public API
2265 */
2266
2267pj_status_t app_init(int argc, char *argv[])
2268{
Benny Prijonoe93e2872006-06-28 16:46:49 +00002269 pjsua_transport_id transport_id = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002270 unsigned i;
2271 pj_status_t status;
2272
2273 /* Create pjsua */
2274 status = pjsua_create();
2275 if (status != PJ_SUCCESS)
2276 return status;
2277
2278 /* Create pool for application */
2279 app_config.pool = pjsua_pool_create("pjsua", 4000, 4000);
2280
2281 /* Initialize default config */
2282 default_config(&app_config);
2283
2284 /* Parse the arguments */
2285 status = parse_args(argc, argv, &app_config, &uri_arg);
2286 if (status != PJ_SUCCESS)
2287 return status;
2288
2289 /* Copy udp_cfg STUN config to rtp_cfg */
2290 app_config.rtp_cfg.use_stun = app_config.udp_cfg.use_stun;
2291 app_config.rtp_cfg.stun_config = app_config.udp_cfg.stun_config;
2292
2293
2294 /* Initialize application callbacks */
2295 app_config.cfg.cb.on_call_state = &on_call_state;
2296 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
2297 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
2298 app_config.cfg.cb.on_reg_state = &on_reg_state;
2299 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
2300 app_config.cfg.cb.on_pager = &on_pager;
2301 app_config.cfg.cb.on_typing = &on_typing;
2302
2303 /* Initialize pjsua */
2304 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
2305 &app_config.media_cfg);
2306 if (status != PJ_SUCCESS)
2307 return status;
2308
Benny Prijonoe909eac2006-07-27 22:04:56 +00002309#ifdef STEREO_DEMO
2310 stereo_demo();
2311#endif
2312
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002313 /* Optionally registers WAV file */
2314 if (app_config.wav_file.slen) {
Benny Prijono6fd4b8f2006-06-22 18:51:50 +00002315 status = pjsua_player_create(&app_config.wav_file, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002316 &app_config.wav_id);
2317 if (status != PJ_SUCCESS)
2318 goto on_error;
Benny Prijono22a300a2006-06-14 20:04:55 +00002319
2320 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002321 }
2322
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002323
Benny Prijonoe93e2872006-06-28 16:46:49 +00002324 /* Add TCP transport unless it's disabled */
2325 if (!app_config.no_tcp) {
2326 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
2327 &app_config.udp_cfg,
2328 &transport_id);
2329 if (status != PJ_SUCCESS)
2330 goto on_error;
2331
2332 /* Add local account */
2333 pjsua_acc_add_local(transport_id, PJ_TRUE, &current_acc);
2334 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2335
2336 }
2337
2338
2339 /* Add UDP transport unless it's disabled. */
2340 if (!app_config.no_udp) {
2341 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
2342 &app_config.udp_cfg,
2343 &transport_id);
2344 if (status != PJ_SUCCESS)
2345 goto on_error;
2346
2347 /* Add local account */
2348 pjsua_acc_add_local(transport_id, PJ_TRUE, &current_acc);
2349 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2350 }
2351
2352 if (transport_id == -1) {
2353 PJ_LOG(3,(THIS_FILE, "Error: no transport is configured"));
2354 status = -1;
2355 goto on_error;
2356 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002357
2358
2359 /* Add accounts */
2360 for (i=0; i<app_config.acc_cnt; ++i) {
2361 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, &current_acc);
2362 if (status != PJ_SUCCESS)
2363 goto on_error;
2364 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2365 }
2366
2367 /* Add buddies */
2368 for (i=0; i<app_config.buddy_cnt; ++i) {
2369 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
2370 if (status != PJ_SUCCESS)
2371 goto on_error;
2372 }
2373
2374 /* Optionally set codec orders */
2375 for (i=0; i<app_config.codec_cnt; ++i) {
2376 pjsua_codec_set_priority(&app_config.codec_arg[i],
2377 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
2378 }
2379
2380 /* Add RTP transports */
2381 status = pjsua_media_transports_create(&app_config.rtp_cfg);
2382 if (status != PJ_SUCCESS)
2383 goto on_error;
2384
Benny Prijono22a300a2006-06-14 20:04:55 +00002385 /* Use null sound device? */
Benny Prijonoe909eac2006-07-27 22:04:56 +00002386#ifndef STEREO_DEMO
Benny Prijono22a300a2006-06-14 20:04:55 +00002387 if (app_config.null_audio) {
2388 status = pjsua_set_null_snd_dev();
2389 if (status != PJ_SUCCESS)
2390 return status;
2391 }
Benny Prijonoe909eac2006-07-27 22:04:56 +00002392#endif
Benny Prijono22a300a2006-06-14 20:04:55 +00002393
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002394 return PJ_SUCCESS;
2395
2396on_error:
2397 pjsua_destroy();
2398 return status;
2399}
2400
2401
2402pj_status_t app_main(void)
2403{
2404 pj_status_t status;
2405
2406 /* Start pjsua */
2407 status = pjsua_start();
2408 if (status != PJ_SUCCESS) {
2409 pjsua_destroy();
2410 return status;
2411 }
2412
2413 console_app_main(&uri_arg);
2414
2415 return PJ_SUCCESS;
2416}
2417
2418pj_status_t app_destroy(void)
2419{
Benny Prijonoe909eac2006-07-27 22:04:56 +00002420#ifdef STEREO_DEMO
2421 if (app_config.snd) {
2422 pjmedia_snd_port_destroy(app_config.snd);
2423 app_config.snd = NULL;
2424 }
2425#endif
2426
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002427 if (app_config.pool) {
2428 pj_pool_release(app_config.pool);
2429 app_config.pool = NULL;
2430 }
2431
2432 return pjsua_destroy();
2433}
Benny Prijonoe909eac2006-07-27 22:04:56 +00002434
2435
2436#ifdef STEREO_DEMO
2437static void stereo_demo()
2438{
2439 pjmedia_port *conf, *splitter, *ch1;
2440 unsigned clock;
2441 pj_status_t status;
2442
2443 /* Disable existing sound device */
2444 conf = pjsua_set_no_snd_dev();
2445
2446 clock = app_config.media_cfg.clock_rate;
2447
2448 /* Create stereo-mono splitter/combiner */
2449 status = pjmedia_splitcomb_create(app_config.pool,
2450 clock /* clock rate */,
2451 2 /* stereo */,
2452 clock*2*10/1000/* 10ms samples * 2ch */,
2453 16 /* bits */,
2454 0 /* options */,
2455 &splitter);
2456 pj_assert(status == PJ_SUCCESS);
2457
2458 /* Connect channel0 (left channel?) to conference port slot0 */
2459 status = pjmedia_splitcomb_set_channel(splitter, 0 /* ch0 */,
2460 0 /*options*/,
2461 conf);
2462 pj_assert(status == PJ_SUCCESS);
2463
2464 /* Create reverse channel for channel1 (right channel?)... */
2465 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
2466 splitter,
2467 1 /* ch1 */,
2468 0 /* options */,
2469 &ch1);
2470 pj_assert(status == PJ_SUCCESS);
2471
2472 /* .. and register it to conference bridge (it would be slot1
2473 * if there's no other devices connected to the bridge)
2474 */
2475 status = pjsua_conf_add_port(app_config.pool, ch1, NULL);
2476 pj_assert(status == PJ_SUCCESS);
2477
2478 /* Create sound device */
2479 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
2480 clock /* clock rate */,
2481 2 /* stereo */,
2482 clock*2*10/1000 /* 10 ms samples * 2ch */,
2483 16 /* bits */,
2484 0, &app_config.snd);
2485 pj_assert(status == PJ_SUCCESS);
2486
2487
2488 /* Connect the splitter to the sound device */
2489 status = pjmedia_snd_port_connect(app_config.snd, splitter);
2490 pj_assert(status == PJ_SUCCESS);
2491
2492}
2493#endif
2494