blob: 85f983e4fa8b9c6072ced3d6eb66f08e05fa7c70 [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
24
25
26/* Pjsua application data */
27static struct app_config
28{
29 pjsua_config cfg;
30 pjsua_logging_config log_cfg;
31 pjsua_media_config media_cfg;
Benny Prijonoe93e2872006-06-28 16:46:49 +000032 pj_bool_t no_tcp;
33 pj_bool_t no_udp;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000034 pjsua_transport_config udp_cfg;
35 pjsua_transport_config rtp_cfg;
36
37 unsigned acc_cnt;
38 pjsua_acc_config acc_cfg[PJSUA_MAX_ACC];
39
40 unsigned buddy_cnt;
41 pjsua_buddy_config buddy_cfg[PJSUA_MAX_BUDDIES];
42
43 pj_pool_t *pool;
44 /* Compatibility with older pjsua */
45
46 unsigned codec_cnt;
47 pj_str_t codec_arg[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +000048 pj_bool_t null_audio;
49 pj_str_t wav_file;
50 pjsua_player_id wav_id;
51 pjsua_conf_port_id wav_port;
52 pj_bool_t auto_play;
53 pj_bool_t auto_loop;
54 unsigned ptime;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000055 unsigned auto_answer;
56 unsigned duration;
57} app_config;
58
59
60static pjsua_acc_id current_acc;
61static pjsua_call_id current_call;
62static pj_str_t uri_arg;
63
64/*****************************************************************************
65 * Configuration manipulation
66 */
67
68/* Show usage */
69static void usage(void)
70{
71 puts ("Usage:");
72 puts (" pjsua [options]");
73 puts ("");
74 puts ("General options:");
75 puts (" --help Display this help screen");
76 puts (" --version Display version info");
77 puts ("");
78 puts ("Logging options:");
79 puts (" --config-file=file Read the config/arguments from file.");
80 puts (" --log-file=fname Log to filename (default stderr)");
81 puts (" --log-level=N Set log max level to N (0(none) to 6(trace)) (default=5)");
82 puts (" --app-log-level=N Set log max level for stdout display (default=4)");
83 puts ("");
84 puts ("SIP Account options:");
85 puts (" --registrar=url Set the URL of registrar server");
86 puts (" --id=url Set the URL of local ID (used in From header)");
87 puts (" --contact=url Optionally override the Contact information");
88 puts (" --proxy=url Optional URL of proxy server to visit");
89 puts (" May be specified multiple times");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +000090 puts (" --reg-timeout=SEC Optional registration interval (default 55)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +000091 puts (" --realm=string Set realm");
92 puts (" --username=string Set authentication username");
93 puts (" --password=string Set authentication password");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +000094 puts (" --next-cred Add another credentials");
Benny Prijonoeebe9af2006-06-13 22:57:13 +000095 puts ("");
96 puts ("SIP Account Control:");
97 puts (" --next-account Add more account");
98 puts ("");
99 puts ("Transport Options:");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000100 puts (" --local-port=port Set TCP/UDP port. This implicitly enables both ");
101 puts (" TCP and UDP transports on the specified port, unless");
102 puts (" if TCP or UDP is disabled.");
103 puts (" --no-tcp Disable TCP transport.");
104 puts (" --no-udp Disable UDP transport.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000105 puts (" --outbound=url Set the URL of global outbound proxy server");
106 puts (" May be specified multiple times");
107 puts (" --use-stun1=host[:port]");
108 puts (" --use-stun2=host[:port] Resolve local IP with the specified STUN servers");
109 puts ("");
110 puts ("Media Options:");
111 puts (" --add-codec=name Manually add codec (default is to enable all)");
112 puts (" --clock-rate=N Override sound device clock rate");
113 puts (" --null-audio Use NULL audio device");
114 puts (" --play-file=file Play WAV file in conference bridge");
115 puts (" --auto-play Automatically play the file (to incoming calls only)");
116 puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP");
117 puts (" --rtp-port=N Base port to try for RTP (default=4000)");
Benny Prijono0498d902006-06-19 14:49:14 +0000118 puts (" --quality=N Specify media quality (0-10, default=10)");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000119 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000120 puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000121 */
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000122 puts ("");
123 puts ("Buddy List (can be more than one):");
124 puts (" --add-buddy url Add the specified URL to the buddy list.");
125 puts ("");
126 puts ("User Agent options:");
127 puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)");
128 puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000129 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000130 puts (" --duration=SEC Set maximum call duration (default:no limit)");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000131 */
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000132 puts ("");
133 fflush(stdout);
134}
135
136
137/* Set default config. */
138static void default_config(struct app_config *cfg)
139{
140 pjsua_config_default(&cfg->cfg);
141 pjsua_logging_config_default(&cfg->log_cfg);
142 pjsua_media_config_default(&cfg->media_cfg);
143 pjsua_transport_config_default(&cfg->udp_cfg);
144 cfg->udp_cfg.port = 5060;
145 pjsua_transport_config_default(&cfg->rtp_cfg);
146 cfg->rtp_cfg.port = 4000;
147 cfg->duration = (unsigned)-1;
148 cfg->wav_id = PJSUA_INVALID_ID;
149 cfg->wav_port = PJSUA_INVALID_ID;
150}
151
152
153/*
154 * Read command arguments from config file.
155 */
156static int read_config_file(pj_pool_t *pool, const char *filename,
157 int *app_argc, char ***app_argv)
158{
159 int i;
160 FILE *fhnd;
161 char line[200];
162 int argc = 0;
163 char **argv;
164 enum { MAX_ARGS = 64 };
165
166 /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
167 argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
168 argv[argc++] = *app_argv[0];
169
170 /* Open config file. */
171 fhnd = fopen(filename, "rt");
172 if (!fhnd) {
173 PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
174 fflush(stdout);
175 return -1;
176 }
177
178 /* Scan tokens in the file. */
179 while (argc < MAX_ARGS && !feof(fhnd)) {
180 char *token, *p = line;
181
182 if (fgets(line, sizeof(line), fhnd) == NULL) break;
183
184 for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS;
185 token = strtok(NULL, " \t\r\n"))
186 {
187 int token_len;
188
189 if (!token) break;
190 if (*token == '#') break;
191
192 token_len = strlen(token);
193 if (!token_len)
194 continue;
195 argv[argc] = pj_pool_alloc(pool, token_len+1);
196 pj_memcpy(argv[argc], token, token_len+1);
197 ++argc;
198 }
199 }
200
201 /* Copy arguments from command line */
202 for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
203 argv[argc++] = (*app_argv)[i];
204
205 if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
206 PJ_LOG(1,(THIS_FILE,
207 "Too many arguments specified in cmd line/config file"));
208 fflush(stdout);
209 fclose(fhnd);
210 return -1;
211 }
212
213 fclose(fhnd);
214
215 /* Assign the new command line back to the original command line. */
216 *app_argc = argc;
217 *app_argv = argv;
218 return 0;
219
220}
221
222static int my_atoi(const char *cs)
223{
224 pj_str_t s;
225 return pj_strtoul(pj_cstr(&s, cs));
226}
227
228
229/* Parse arguments. */
230static pj_status_t parse_args(int argc, char *argv[],
231 struct app_config *cfg,
232 pj_str_t *uri_to_call)
233{
234 int c;
235 int option_index;
236 enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
237 OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
238 OPT_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR,
239 OPT_REG_TIMEOUT, OPT_ID, OPT_CONTACT,
240 OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
241 OPT_USE_STUN1, OPT_USE_STUN2,
242 OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
243 OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
244 OPT_AUTO_CONF, OPT_CLOCK_RATE,
245 OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC,
246 OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME,
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000247 OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
Benny Prijonoe93e2872006-06-28 16:46:49 +0000248 OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000249 };
250 struct pj_getopt_option long_options[] = {
251 { "config-file",1, 0, OPT_CONFIG_FILE},
252 { "log-file", 1, 0, OPT_LOG_FILE},
253 { "log-level", 1, 0, OPT_LOG_LEVEL},
254 { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
255 { "help", 0, 0, OPT_HELP},
256 { "version", 0, 0, OPT_VERSION},
257 { "clock-rate", 1, 0, OPT_CLOCK_RATE},
258 { "null-audio", 0, 0, OPT_NULL_AUDIO},
259 { "local-port", 1, 0, OPT_LOCAL_PORT},
Benny Prijonoe93e2872006-06-28 16:46:49 +0000260 { "no-tcp", 0, 0, OPT_NO_TCP},
261 { "no-udp", 0, 0, OPT_NO_UDP},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000262 { "proxy", 1, 0, OPT_PROXY},
263 { "outbound", 1, 0, OPT_OUTBOUND_PROXY},
264 { "registrar", 1, 0, OPT_REGISTRAR},
265 { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
266 { "id", 1, 0, OPT_ID},
267 { "contact", 1, 0, OPT_CONTACT},
268 { "realm", 1, 0, OPT_REALM},
269 { "username", 1, 0, OPT_USERNAME},
270 { "password", 1, 0, OPT_PASSWORD},
271 { "use-stun1", 1, 0, OPT_USE_STUN1},
272 { "use-stun2", 1, 0, OPT_USE_STUN2},
273 { "add-buddy", 1, 0, OPT_ADD_BUDDY},
274 { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
275 { "no-presence", 0, 0, OPT_NO_PRESENCE},
276 { "auto-answer",1, 0, OPT_AUTO_ANSWER},
277 { "auto-hangup",1, 0, OPT_AUTO_HANGUP},
278 { "auto-play", 0, 0, OPT_AUTO_PLAY},
279 { "auto-loop", 0, 0, OPT_AUTO_LOOP},
280 { "auto-conf", 0, 0, OPT_AUTO_CONF},
281 { "play-file", 1, 0, OPT_PLAY_FILE},
282 { "rtp-port", 1, 0, OPT_RTP_PORT},
283 { "add-codec", 1, 0, OPT_ADD_CODEC},
284 { "complexity", 1, 0, OPT_COMPLEXITY},
285 { "quality", 1, 0, OPT_QUALITY},
286 { "ptime", 1, 0, OPT_PTIME},
287 { "next-account",0,0, OPT_NEXT_ACCOUNT},
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000288 { "next-cred", 0, 0, OPT_NEXT_CRED},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000289 { "max-calls", 1, 0, OPT_MAX_CALLS},
290 { "duration",1,0, OPT_DURATION},
291 { NULL, 0, 0, 0}
292 };
293 pj_status_t status;
294 pjsua_acc_config *cur_acc;
295 char *config_file = NULL;
296 unsigned i;
297
298 /* Run pj_getopt once to see if user specifies config file to read. */
299 while ((c=pj_getopt_long(argc, argv, "", long_options,
300 &option_index)) != -1)
301 {
302 switch (c) {
303 case OPT_CONFIG_FILE:
304 config_file = pj_optarg;
305 break;
306 }
307 if (config_file)
308 break;
309 }
310
311 if (config_file) {
312 status = read_config_file(app_config.pool, config_file, &argc, &argv);
313 if (status != 0)
314 return status;
315 }
316
317 cfg->acc_cnt = 0;
318 cur_acc = &cfg->acc_cfg[0];
319
320
321 /* Reinitialize and re-run pj_getopt again, possibly with new arguments
322 * read from config file.
323 */
324 pj_optind = 0;
325 while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
326 char *p;
327 pj_str_t tmp;
328 long lval;
329
330 switch (c) {
331
Benny Prijono6f137482006-06-15 11:31:36 +0000332 case OPT_CONFIG_FILE:
333 /* Ignore as this has been processed before */
334 break;
335
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000336 case OPT_LOG_FILE:
337 cfg->log_cfg.log_filename = pj_str(pj_optarg);
338 break;
339
340 case OPT_LOG_LEVEL:
341 c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
342 if (c < 0 || c > 6) {
343 PJ_LOG(1,(THIS_FILE,
344 "Error: expecting integer value 0-6 "
345 "for --log-level"));
346 return PJ_EINVAL;
347 }
348 cfg->log_cfg.level = c;
349 pj_log_set_level( c );
350 break;
351
352 case OPT_APP_LOG_LEVEL:
353 cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
354 if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
355 PJ_LOG(1,(THIS_FILE,
356 "Error: expecting integer value 0-6 "
357 "for --app-log-level"));
358 return PJ_EINVAL;
359 }
360 break;
361
362 case OPT_HELP:
363 usage();
364 return PJ_EINVAL;
365
366 case OPT_VERSION: /* version */
367 pj_dump_config();
368 return PJ_EINVAL;
369
370 case OPT_NULL_AUDIO:
371 cfg->null_audio = PJ_TRUE;
372 break;
373
374 case OPT_CLOCK_RATE:
375 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
376 if (lval < 8000 || lval > 48000) {
377 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
378 "8000-48000 for clock rate"));
379 return PJ_EINVAL;
380 }
381 cfg->media_cfg.clock_rate = lval;
382 break;
383
384 case OPT_LOCAL_PORT: /* local-port */
385 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
386 if (lval < 1 || lval > 65535) {
387 PJ_LOG(1,(THIS_FILE,
388 "Error: expecting integer value for "
389 "--local-port"));
390 return PJ_EINVAL;
391 }
392 cfg->udp_cfg.port = (pj_uint16_t)lval;
393 break;
394
Benny Prijonoe93e2872006-06-28 16:46:49 +0000395 case OPT_NO_UDP: /* no-udp */
396 if (cfg->no_tcp) {
397 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
398 return PJ_EINVAL;
399 }
400
401 cfg->no_udp = PJ_TRUE;
402 break;
403
404 case OPT_NO_TCP: /* no-tcp */
405 if (cfg->no_udp) {
406 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
407 return PJ_EINVAL;
408 }
409
410 cfg->no_tcp = PJ_TRUE;
411 break;
412
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000413 case OPT_PROXY: /* proxy */
414 if (pjsua_verify_sip_url(pj_optarg) != 0) {
415 PJ_LOG(1,(THIS_FILE,
416 "Error: invalid SIP URL '%s' "
417 "in proxy argument", pj_optarg));
418 return PJ_EINVAL;
419 }
420 cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
421 break;
422
423 case OPT_OUTBOUND_PROXY: /* outbound proxy */
424 if (pjsua_verify_sip_url(pj_optarg) != 0) {
425 PJ_LOG(1,(THIS_FILE,
426 "Error: invalid SIP URL '%s' "
427 "in outbound proxy argument", pj_optarg));
428 return PJ_EINVAL;
429 }
430 cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
431 break;
432
433 case OPT_REGISTRAR: /* registrar */
434 if (pjsua_verify_sip_url(pj_optarg) != 0) {
435 PJ_LOG(1,(THIS_FILE,
436 "Error: invalid SIP URL '%s' in "
437 "registrar argument", pj_optarg));
438 return PJ_EINVAL;
439 }
440 cur_acc->reg_uri = pj_str(pj_optarg);
441 break;
442
443 case OPT_REG_TIMEOUT: /* reg-timeout */
444 cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
445 if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
446 PJ_LOG(1,(THIS_FILE,
447 "Error: invalid value for --reg-timeout "
448 "(expecting 1-3600)"));
449 return PJ_EINVAL;
450 }
451 break;
452
453 case OPT_ID: /* id */
454 if (pjsua_verify_sip_url(pj_optarg) != 0) {
455 PJ_LOG(1,(THIS_FILE,
456 "Error: invalid SIP URL '%s' "
457 "in local id argument", pj_optarg));
458 return PJ_EINVAL;
459 }
460 cur_acc->id = pj_str(pj_optarg);
461 break;
462
463 case OPT_CONTACT: /* contact */
464 if (pjsua_verify_sip_url(pj_optarg) != 0) {
465 PJ_LOG(1,(THIS_FILE,
466 "Error: invalid SIP URL '%s' "
467 "in contact argument", pj_optarg));
468 return PJ_EINVAL;
469 }
470 cur_acc->contact = pj_str(pj_optarg);
471 break;
472
473 case OPT_NEXT_ACCOUNT: /* Add more account. */
474 cfg->acc_cnt++;
475 cur_acc = &cfg->acc_cfg[cfg->acc_cnt - 1];
476 break;
477
478 case OPT_USERNAME: /* Default authentication user */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000479 cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
480 cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("digest");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000481 break;
482
483 case OPT_REALM: /* Default authentication realm. */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000484 cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000485 break;
486
487 case OPT_PASSWORD: /* authentication password */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000488 cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
489 cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
490 break;
491
492 case OPT_NEXT_CRED: /* next credential */
493 cur_acc->cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000494 break;
495
496 case OPT_USE_STUN1: /* STUN server 1 */
497 p = pj_ansi_strchr(pj_optarg, ':');
498 if (p) {
499 *p = '\0';
500 cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
501 cfg->udp_cfg.stun_config.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1));
502 if (cfg->udp_cfg.stun_config.stun_port1 < 1 || cfg->udp_cfg.stun_config.stun_port1 > 65535) {
503 PJ_LOG(1,(THIS_FILE,
504 "Error: expecting port number with "
505 "option --use-stun1"));
506 return PJ_EINVAL;
507 }
508 } else {
509 cfg->udp_cfg.stun_config.stun_port1 = 3478;
510 cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
511 }
512 cfg->udp_cfg.use_stun = PJ_TRUE;
513 break;
514
515 case OPT_USE_STUN2: /* STUN server 2 */
516 p = pj_ansi_strchr(pj_optarg, ':');
517 if (p) {
518 *p = '\0';
519 cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
520 cfg->udp_cfg.stun_config.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1));
521 if (cfg->udp_cfg.stun_config.stun_port2 < 1 || cfg->udp_cfg.stun_config.stun_port2 > 65535) {
522 PJ_LOG(1,(THIS_FILE,
523 "Error: expecting port number with "
524 "option --use-stun2"));
525 return PJ_EINVAL;
526 }
527 } else {
528 cfg->udp_cfg.stun_config.stun_port2 = 3478;
529 cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
530 }
531 break;
532
533 case OPT_ADD_BUDDY: /* Add to buddy list. */
534 if (pjsua_verify_sip_url(pj_optarg) != 0) {
535 PJ_LOG(1,(THIS_FILE,
536 "Error: invalid URL '%s' in "
537 "--add-buddy option", pj_optarg));
538 return -1;
539 }
540 if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
541 PJ_LOG(1,(THIS_FILE,
542 "Error: too many buddies in buddy list."));
543 return -1;
544 }
545 cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
546 cfg->buddy_cnt++;
547 break;
548
549 case OPT_AUTO_PLAY:
550 cfg->auto_play = 1;
551 break;
552
553 case OPT_AUTO_LOOP:
554 cfg->auto_loop = 1;
555 break;
556
557 case OPT_PLAY_FILE:
558 cfg->wav_file = pj_str(pj_optarg);
559 break;
560
561 case OPT_RTP_PORT:
562 cfg->rtp_cfg.port = my_atoi(pj_optarg);
563 if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
564 PJ_LOG(1,(THIS_FILE,
565 "Error: rtp-port argument value "
566 "(expecting 1-65535"));
567 return -1;
568 }
569 break;
570
571 case OPT_ADD_CODEC:
572 cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
573 break;
574
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000575 /* These options were no longer valid after new pjsua */
576 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000577 case OPT_COMPLEXITY:
578 cfg->complexity = my_atoi(pj_optarg);
579 if (cfg->complexity < 0 || cfg->complexity > 10) {
580 PJ_LOG(1,(THIS_FILE,
581 "Error: invalid --complexity (expecting 0-10"));
582 return -1;
583 }
584 break;
585
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000586 case OPT_DURATION:
587 cfg->duration = my_atoi(pj_optarg);
588 break;
589
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000590 case OPT_PTIME:
591 cfg->ptime = my_atoi(pj_optarg);
592 if (cfg->ptime < 10 || cfg->ptime > 1000) {
593 PJ_LOG(1,(THIS_FILE,
594 "Error: invalid --ptime option"));
595 return -1;
596 }
597 break;
598
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000599 */
600
Benny Prijono0498d902006-06-19 14:49:14 +0000601 case OPT_QUALITY:
602 cfg->media_cfg.quality = my_atoi(pj_optarg);
603 if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
604 PJ_LOG(1,(THIS_FILE,
605 "Error: invalid --quality (expecting 0-10"));
606 return -1;
607 }
608 break;
609
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000610 case OPT_AUTO_ANSWER:
611 cfg->auto_answer = my_atoi(pj_optarg);
612 if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
613 PJ_LOG(1,(THIS_FILE,
614 "Error: invalid code in --auto-answer "
615 "(expecting 100-699"));
616 return -1;
617 }
618 break;
619
620 case OPT_MAX_CALLS:
621 cfg->cfg.max_calls = my_atoi(pj_optarg);
622 if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > 255) {
623 PJ_LOG(1,(THIS_FILE,"Too many calls for max-calls (1-255)"));
624 return -1;
625 }
626 break;
627
Benny Prijono22a300a2006-06-14 20:04:55 +0000628 default:
Benny Prijono787b8692006-06-19 12:40:03 +0000629 PJ_LOG(1,(THIS_FILE,
630 "Argument \"--%s\" is not valid. Use --help to see help",
631 long_options[option_index].name));
Benny Prijono22a300a2006-06-14 20:04:55 +0000632 return -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000633 }
634 }
635
636 if (pj_optind != argc) {
637 pj_str_t uri_arg;
638
639 if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
640 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
641 return -1;
642 }
643 uri_arg = pj_str(argv[pj_optind]);
644 if (uri_to_call)
645 *uri_to_call = uri_arg;
646 pj_optind++;
647
648 /* Add URI to call to buddy list if it's not already there */
649 for (i=0; i<cfg->buddy_cnt; ++i) {
650 if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
651 break;
652 }
653 if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
654 cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
655 }
656
657 } else {
658 if (uri_to_call)
659 uri_to_call->slen = 0;
660 }
661
662 if (pj_optind != argc) {
663 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
664 return PJ_EINVAL;
665 }
666
667 if (cfg->acc_cfg[0].id.slen && cfg->acc_cnt==0)
668 cfg->acc_cnt = 1;
669
670 for (i=0; i<cfg->acc_cnt; ++i) {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000671 if (cfg->acc_cfg[i].cred_info[cfg->acc_cfg[i].cred_count].username.slen)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000672 {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000673 cfg->acc_cfg[i].cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000674 }
675 }
676
677
678 return PJ_SUCCESS;
679}
680
681
682/*
683 * Save account settings
684 */
685static void write_account_settings(int acc_index, pj_str_t *result)
686{
687 unsigned i;
688 char line[128];
689 pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
690
691
692 pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
693 pj_strcat2(result, line);
694
695
696 /* Identity */
697 if (acc_cfg->id.slen) {
698 pj_ansi_sprintf(line, "--id %.*s\n",
699 (int)acc_cfg->id.slen,
700 acc_cfg->id.ptr);
701 pj_strcat2(result, line);
702 }
703
704 /* Registrar server */
705 if (acc_cfg->reg_uri.slen) {
706 pj_ansi_sprintf(line, "--registrar %.*s\n",
707 (int)acc_cfg->reg_uri.slen,
708 acc_cfg->reg_uri.ptr);
709 pj_strcat2(result, line);
710
711 pj_ansi_sprintf(line, "--reg-timeout %u\n",
712 acc_cfg->reg_timeout);
713 pj_strcat2(result, line);
714 }
715
716 /* Contact */
717 if (acc_cfg->contact.slen) {
718 pj_ansi_sprintf(line, "--contact %.*s\n",
719 (int)acc_cfg->contact.slen,
720 acc_cfg->contact.ptr);
721 pj_strcat2(result, line);
722 }
723
724 /* Proxy */
725 for (i=0; i<acc_cfg->proxy_cnt; ++i) {
726 pj_ansi_sprintf(line, "--proxy %.*s\n",
727 (int)acc_cfg->proxy[i].slen,
728 acc_cfg->proxy[i].ptr);
729 pj_strcat2(result, line);
730 }
731
732 /* Credentials */
733 for (i=0; i<acc_cfg->cred_count; ++i) {
734 if (acc_cfg->cred_info[i].realm.slen) {
735 pj_ansi_sprintf(line, "--realm %.*s\n",
736 (int)acc_cfg->cred_info[i].realm.slen,
737 acc_cfg->cred_info[i].realm.ptr);
738 pj_strcat2(result, line);
739 }
740
741 if (acc_cfg->cred_info[i].username.slen) {
742 pj_ansi_sprintf(line, "--username %.*s\n",
743 (int)acc_cfg->cred_info[i].username.slen,
744 acc_cfg->cred_info[i].username.ptr);
745 pj_strcat2(result, line);
746 }
747
748 if (acc_cfg->cred_info[i].data.slen) {
749 pj_ansi_sprintf(line, "--password %.*s\n",
750 (int)acc_cfg->cred_info[i].data.slen,
751 acc_cfg->cred_info[i].data.ptr);
752 pj_strcat2(result, line);
753 }
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000754
755 if (i != acc_cfg->cred_count - 1)
756 pj_strcat2(result, "--next-cred\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000757 }
758
759}
760
761
762/*
763 * Write settings.
764 */
765static int write_settings(const struct app_config *config,
766 char *buf, pj_size_t max)
767{
768 unsigned acc_index;
769 unsigned i;
770 pj_str_t cfg;
771 char line[128];
772
773 PJ_UNUSED_ARG(max);
774
775 cfg.ptr = buf;
776 cfg.slen = 0;
777
778 /* Logging. */
779 pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
780 pj_ansi_sprintf(line, "--log-level %d\n",
781 config->log_cfg.level);
782 pj_strcat2(&cfg, line);
783
784 pj_ansi_sprintf(line, "--app-log-level %d\n",
785 config->log_cfg.console_level);
786 pj_strcat2(&cfg, line);
787
788 if (config->log_cfg.log_filename.slen) {
789 pj_ansi_sprintf(line, "--log-file %.*s\n",
790 (int)config->log_cfg.log_filename.slen,
791 config->log_cfg.log_filename.ptr);
792 pj_strcat2(&cfg, line);
793 }
794
795
796 /* Save account settings. */
797 for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
798
799 write_account_settings(acc_index, &cfg);
800
801 if (acc_index < config->acc_cnt-1)
802 pj_strcat2(&cfg, "--next-account\n");
803 }
804
805
806 pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
807
808 /* Outbound proxy */
809 for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
810 pj_ansi_sprintf(line, "--outbound %.*s\n",
811 (int)config->cfg.outbound_proxy[i].slen,
812 config->cfg.outbound_proxy[i].ptr);
813 pj_strcat2(&cfg, line);
814 }
815
816
817 /* UDP Transport. */
818 pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
819 pj_strcat2(&cfg, line);
820
821
822 /* STUN */
823 if (config->udp_cfg.stun_config.stun_port1) {
824 pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
825 (int)config->udp_cfg.stun_config.stun_srv1.slen,
826 config->udp_cfg.stun_config.stun_srv1.ptr,
827 config->udp_cfg.stun_config.stun_port1);
828 pj_strcat2(&cfg, line);
829 }
830
831 if (config->udp_cfg.stun_config.stun_port2) {
832 pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
833 (int)config->udp_cfg.stun_config.stun_srv2.slen,
834 config->udp_cfg.stun_config.stun_srv2.ptr,
835 config->udp_cfg.stun_config.stun_port2);
836 pj_strcat2(&cfg, line);
837 }
838
839
840 pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
841
842
843 /* Media */
844 if (config->null_audio)
845 pj_strcat2(&cfg, "--null-audio\n");
846 if (config->auto_play)
847 pj_strcat2(&cfg, "--auto-play\n");
848 if (config->auto_loop)
849 pj_strcat2(&cfg, "--auto-loop\n");
850 if (config->wav_file.slen) {
851 pj_ansi_sprintf(line, "--play-file %s\n",
852 config->wav_file.ptr);
853 pj_strcat2(&cfg, line);
854 }
855 /* Media clock rate. */
Benny Prijono0498d902006-06-19 14:49:14 +0000856 if (config->media_cfg.clock_rate) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000857 pj_ansi_sprintf(line, "--clock-rate %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +0000858 config->media_cfg.clock_rate);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000859 pj_strcat2(&cfg, line);
860 }
Benny Prijono0498d902006-06-19 14:49:14 +0000861 if (config->media_cfg.quality != 10) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000862 pj_ansi_sprintf(line, "--quality %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +0000863 config->media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000864 pj_strcat2(&cfg, line);
865 }
Benny Prijono0498d902006-06-19 14:49:14 +0000866
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000867
868 /* ptime */
869 if (config->ptime) {
870 pj_ansi_sprintf(line, "--ptime %d\n",
871 config->ptime);
872 pj_strcat2(&cfg, line);
873 }
874
875 /* Start RTP port. */
876 pj_ansi_sprintf(line, "--rtp-port %d\n",
877 config->rtp_cfg.port);
878 pj_strcat2(&cfg, line);
879
880 /* Add codec. */
881 for (i=0; i<config->codec_cnt; ++i) {
882 pj_ansi_sprintf(line, "--add-codec %s\n",
883 config->codec_arg[i].ptr);
884 pj_strcat2(&cfg, line);
885 }
886
887 pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
888
889 /* Auto-answer. */
890 if (config->auto_answer != 0) {
891 pj_ansi_sprintf(line, "--auto-answer %d\n",
892 config->auto_answer);
893 pj_strcat2(&cfg, line);
894 }
895
896 /* Max calls. */
897 pj_ansi_sprintf(line, "--max-calls %d\n",
898 config->cfg.max_calls);
899 pj_strcat2(&cfg, line);
900
901 /* Uas-duration. */
902 if (config->duration != (unsigned)-1) {
903 pj_ansi_sprintf(line, "--duration %d\n",
904 config->duration);
905 pj_strcat2(&cfg, line);
906 }
907
908 pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
909
910 /* Add buddies. */
911 for (i=0; i<config->buddy_cnt; ++i) {
912 pj_ansi_sprintf(line, "--add-buddy %.*s\n",
913 (int)config->buddy_cfg[i].uri.slen,
914 config->buddy_cfg[i].uri.ptr);
915 pj_strcat2(&cfg, line);
916 }
917
918
919 *(cfg.ptr + cfg.slen) = '\0';
920 return cfg.slen;
921}
922
923
924/*
925 * Dump application states.
926 */
927static void app_dump(pj_bool_t detail)
928{
929 unsigned old_decor;
930 char buf[1024];
931
932 PJ_LOG(3,(THIS_FILE, "Start dumping application states:"));
933
934 old_decor = pj_log_get_decor();
935 pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
936
937 if (detail)
938 pj_dump_config();
939
940 pjsip_endpt_dump(pjsua_get_pjsip_endpt(), detail);
941 pjmedia_endpt_dump(pjsua_get_pjmedia_endpt());
942 pjsip_tsx_layer_dump(detail);
943 pjsip_ua_dump(detail);
944
945
946 /* Dump all invite sessions: */
947 PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
948
949 if (pjsua_call_get_count() == 0) {
950
951 PJ_LOG(3,(THIS_FILE, " - no sessions -"));
952
953 } else {
954 unsigned i;
955
956 for (i=0; i<app_config.cfg.max_calls; ++i) {
957 if (pjsua_call_is_active(i)) {
958 pjsua_call_dump(i, detail, buf, sizeof(buf), " ");
959 PJ_LOG(3,(THIS_FILE, "%s", buf));
960 }
961 }
962 }
963
964 /* Dump presence status */
965 pjsua_pres_dump(detail);
966
967 pj_log_set_decor(old_decor);
968 PJ_LOG(3,(THIS_FILE, "Dump complete"));
969}
970
971
972/*****************************************************************************
973 * Console application
974 */
975
976/*
977 * Find next call when current call is disconnected or when user
978 * press ']'
979 */
980static pj_bool_t find_next_call(void)
981{
982 int i, max;
983
984 max = pjsua_call_get_max_count();
985 for (i=current_call+1; i<max; ++i) {
986 if (pjsua_call_is_active(i)) {
987 current_call = i;
988 return PJ_TRUE;
989 }
990 }
991
992 for (i=0; i<current_call; ++i) {
993 if (pjsua_call_is_active(i)) {
994 current_call = i;
995 return PJ_TRUE;
996 }
997 }
998
999 current_call = PJSUA_INVALID_ID;
1000 return PJ_FALSE;
1001}
1002
1003
1004/*
1005 * Find previous call when user press '['
1006 */
1007static pj_bool_t find_prev_call(void)
1008{
1009 int i, max;
1010
1011 max = pjsua_call_get_max_count();
1012 for (i=current_call-1; i>=0; --i) {
1013 if (pjsua_call_is_active(i)) {
1014 current_call = i;
1015 return PJ_TRUE;
1016 }
1017 }
1018
1019 for (i=max-1; i>current_call; --i) {
1020 if (pjsua_call_is_active(i)) {
1021 current_call = i;
1022 return PJ_TRUE;
1023 }
1024 }
1025
1026 current_call = PJSUA_INVALID_ID;
1027 return PJ_FALSE;
1028}
1029
1030
1031
1032/*
1033 * Handler when invite state has changed.
1034 */
1035static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
1036{
1037 pjsua_call_info call_info;
1038
1039 PJ_UNUSED_ARG(e);
1040
1041 pjsua_call_get_info(call_id, &call_info);
1042
1043 if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
1044
1045 PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",
1046 call_id,
1047 call_info.last_status,
1048 call_info.last_status_text.ptr));
1049
1050 if (call_id == current_call) {
1051 find_next_call();
1052 }
1053
1054 } else {
1055
1056 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s",
1057 call_id,
1058 call_info.state_text.ptr));
1059
1060 if (current_call==PJSUA_INVALID_ID)
1061 current_call = call_id;
1062
1063 }
1064}
1065
1066
1067/**
1068 * Handler when there is incoming call.
1069 */
1070static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
1071 pjsip_rx_data *rdata)
1072{
1073 pjsua_call_info call_info;
1074
1075 PJ_UNUSED_ARG(acc_id);
1076 PJ_UNUSED_ARG(rdata);
1077
1078 pjsua_call_get_info(call_id, &call_info);
1079
1080 if (app_config.auto_answer > 0) {
1081 pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL);
1082 }
1083
1084 if (app_config.auto_answer < 200) {
1085 PJ_LOG(3,(THIS_FILE,
1086 "Incoming call for account %d!\n"
1087 "From: %s\n"
1088 "To: %s\n"
1089 "Press a to answer or h to reject call",
1090 acc_id,
1091 call_info.remote_info.ptr,
1092 call_info.local_info.ptr));
1093 }
1094}
1095
1096
1097/*
1098 * Callback on media state changed event.
1099 * The action may connect the call to sound device, to file, or
1100 * to loop the call.
1101 */
1102static void on_call_media_state(pjsua_call_id call_id)
1103{
1104 pjsua_call_info call_info;
1105
1106 pjsua_call_get_info(call_id, &call_info);
1107
1108 if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
1109 pj_bool_t connect_sound = PJ_TRUE;
1110
1111 /* Loopback sound, if desired */
1112 if (app_config.auto_loop) {
1113 pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
1114 connect_sound = PJ_FALSE;
1115 }
1116
1117 /* Stream a file, if desired */
1118 if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) {
1119 pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
1120 connect_sound = PJ_FALSE;
1121 }
1122
1123 /* Otherwise connect to sound device */
1124 if (connect_sound) {
1125 pjsua_conf_connect(call_info.conf_slot, 0);
1126 pjsua_conf_connect(0, call_info.conf_slot);
1127 }
1128
1129 PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
1130
1131 } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) {
1132 PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
1133 call_id));
1134 } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) {
1135 PJ_LOG(3,(THIS_FILE,
1136 "Media for call %d is suspended (hold) by remote",
1137 call_id));
1138 } else {
1139 PJ_LOG(3,(THIS_FILE,
1140 "Media for call %d is inactive",
1141 call_id));
1142 }
1143}
1144
1145
1146/*
1147 * Handler registration status has changed.
1148 */
1149static void on_reg_state(pjsua_acc_id acc_id)
1150{
1151 PJ_UNUSED_ARG(acc_id);
1152
1153 // Log already written.
1154}
1155
1156
1157/*
1158 * Handler on buddy state changed.
1159 */
1160static void on_buddy_state(pjsua_buddy_id buddy_id)
1161{
1162 pjsua_buddy_info info;
1163 pjsua_buddy_get_info(buddy_id, &info);
1164
1165 PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
1166 (int)info.uri.slen,
1167 info.uri.ptr,
1168 (int)info.status_text.slen,
1169 info.status_text.ptr));
1170}
1171
1172
1173/**
1174 * Incoming IM message (i.e. MESSAGE request)!
1175 */
1176static void on_pager(pjsua_call_id call_id, const pj_str_t *from,
1177 const pj_str_t *to, const pj_str_t *contact,
1178 const pj_str_t *mime_type, const pj_str_t *text)
1179{
1180 /* Note: call index may be -1 */
1181 PJ_UNUSED_ARG(call_id);
1182 PJ_UNUSED_ARG(to);
1183 PJ_UNUSED_ARG(contact);
1184 PJ_UNUSED_ARG(mime_type);
1185
1186 PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s",
1187 (int)from->slen, from->ptr,
1188 (int)text->slen, text->ptr));
1189}
1190
1191
1192/**
1193 * Received typing indication
1194 */
1195static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
1196 const pj_str_t *to, const pj_str_t *contact,
1197 pj_bool_t is_typing)
1198{
1199 PJ_UNUSED_ARG(call_id);
1200 PJ_UNUSED_ARG(to);
1201 PJ_UNUSED_ARG(contact);
1202
1203 PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
1204 (int)from->slen, from->ptr,
1205 (is_typing?"is typing..":"has stopped typing")));
1206}
1207
1208
1209/*
1210 * Print buddy list.
1211 */
1212static void print_buddy_list(void)
1213{
1214 pjsua_buddy_id ids[64];
1215 int i;
1216 unsigned count = PJ_ARRAY_SIZE(ids);
1217
1218 puts("Buddy list:");
1219
1220 pjsua_enum_buddies(ids, &count);
1221
1222 if (count == 0)
1223 puts(" -none-");
1224 else {
1225 for (i=0; i<(int)count; ++i) {
1226 pjsua_buddy_info info;
1227
1228 if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
1229 continue;
1230
1231 printf(" [%2d] <%7s> %.*s\n",
1232 ids[i]+1, info.status_text.ptr,
1233 (int)info.uri.slen,
1234 info.uri.ptr);
1235 }
1236 }
1237 puts("");
1238}
1239
1240
1241/*
1242 * Print account status.
1243 */
1244static void print_acc_status(int acc_id)
1245{
1246 char buf[80];
1247 pjsua_acc_info info;
1248
1249 pjsua_acc_get_info(acc_id, &info);
1250
1251 if (!info.has_registration) {
1252 pj_ansi_snprintf(buf, sizeof(buf), "%.*s",
1253 (int)info.status_text.slen,
1254 info.status_text.ptr);
1255
1256 } else {
1257 pj_ansi_snprintf(buf, sizeof(buf),
1258 "%d/%.*s (expires=%d)",
1259 info.status,
1260 (int)info.status_text.slen,
1261 info.status_text.ptr,
1262 info.expires);
1263
1264 }
1265
1266 printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
1267 acc_id, (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
1268 printf(" Online status: %s\n",
1269 (info.online_status ? "Online" : "Invisible"));
1270}
1271
1272
1273/*
1274 * Show a bit of help.
1275 */
1276static void keystroke_help(void)
1277{
1278 pjsua_acc_id acc_ids[16];
1279 unsigned count = PJ_ARRAY_SIZE(acc_ids);
1280 int i;
1281
1282 printf(">>>>\n");
1283
1284 pjsua_enum_accs(acc_ids, &count);
1285
1286 printf("Account list:\n");
1287 for (i=0; i<(int)count; ++i)
1288 print_acc_status(acc_ids[i]);
1289
1290 print_buddy_list();
1291
1292 //puts("Commands:");
1293 puts("+=============================================================================+");
1294 puts("| Call Commands: | Buddy, IM & Presence: | Account: |");
1295 puts("| | | |");
1296 puts("| m Make new call | +b Add new buddy .| +a Add new accnt |");
1297 puts("| M Make multiple calls | -b Delete buddy | -a Delete accnt. |");
1298 puts("| a Answer call | !b Modify buddy | !a Modify accnt. |");
1299 puts("| h Hangup call (ha=all) | i Send IM | rr (Re-)register |");
1300 puts("| H Hold call | s Subscribe presence | ru Unregister |");
1301 puts("| v re-inVite (release hold) | u Unsubscribe presence | > Cycle next ac.|");
1302 puts("| ] Select next dialog | t ToGgle Online status | < Cycle prev ac.|");
1303 puts("| [ Select previous dialog +--------------------------+-------------------+");
1304 puts("| x Xfer call | Media Commands: | Status & Config: |");
1305 puts("| # Send DTMF string | | |");
Benny Prijono819506c2006-06-22 22:29:51 +00001306 puts("| dq Dump curr. call quality | cl List ports | d Dump status |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001307 puts("| | cc Connect port | dd Dump detailed |");
1308 puts("| | cd Disconnect port | dc Dump config |");
1309 puts("| | | f Save config |");
1310 puts("+------------------------------+--------------------------+-------------------+");
1311 puts("| q QUIT |");
1312 puts("+=============================================================================+");
1313}
1314
1315
1316/*
1317 * Input simple string
1318 */
1319static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1320{
1321 char *p;
1322
1323 printf("%s (empty to cancel): ", title); fflush(stdout);
1324 fgets(buf, len, stdin);
1325
1326 /* Remove trailing newlines. */
1327 for (p=buf; ; ++p) {
1328 if (*p=='\r' || *p=='\n') *p='\0';
1329 else if (!*p) break;
1330 }
1331
1332 if (!*buf)
1333 return PJ_FALSE;
1334
1335 return PJ_TRUE;
1336}
1337
1338
1339#define NO_NB -2
1340struct input_result
1341{
1342 int nb_result;
1343 char *uri_result;
1344};
1345
1346
1347/*
1348 * Input URL.
1349 */
1350static void ui_input_url(const char *title, char *buf, int len,
1351 struct input_result *result)
1352{
1353 result->nb_result = NO_NB;
1354 result->uri_result = NULL;
1355
1356 print_buddy_list();
1357
1358 printf("Choices:\n"
1359 " 0 For current dialog.\n"
1360 " -1 All %d buddies in buddy list\n"
1361 " [1 -%2d] Select from buddy list\n"
1362 " URL An URL\n"
1363 " <Enter> Empty input (or 'q') to cancel\n"
1364 , pjsua_get_buddy_count(), pjsua_get_buddy_count());
1365 printf("%s: ", title);
1366
1367 fflush(stdout);
1368 fgets(buf, len, stdin);
1369 len = strlen(buf);
1370
1371 /* Left trim */
1372 while (pj_isspace(*buf)) {
1373 ++buf;
1374 --len;
1375 }
1376
1377 /* Remove trailing newlines */
1378 while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
1379 buf[--len] = '\0';
1380
1381 if (len == 0 || buf[0]=='q')
1382 return;
1383
1384 if (pj_isdigit(*buf) || *buf=='-') {
1385
1386 int i;
1387
1388 if (*buf=='-')
1389 i = 1;
1390 else
1391 i = 0;
1392
1393 for (; i<len; ++i) {
1394 if (!pj_isdigit(buf[i])) {
1395 puts("Invalid input");
1396 return;
1397 }
1398 }
1399
1400 result->nb_result = my_atoi(buf);
1401
1402 if (result->nb_result >= 0 &&
1403 result->nb_result <= (int)pjsua_get_buddy_count())
1404 {
1405 return;
1406 }
1407 if (result->nb_result == -1)
1408 return;
1409
1410 puts("Invalid input");
1411 result->nb_result = NO_NB;
1412 return;
1413
1414 } else {
1415 pj_status_t status;
1416
1417 if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
1418 pjsua_perror(THIS_FILE, "Invalid URL", status);
1419 return;
1420 }
1421
1422 result->uri_result = buf;
1423 }
1424}
1425
1426/*
1427 * List the ports in conference bridge
1428 */
1429static void conf_list(void)
1430{
1431 unsigned i, count;
1432 pjsua_conf_port_id id[PJSUA_MAX_CALLS];
1433
1434 printf("Conference ports:\n");
1435
1436 count = PJ_ARRAY_SIZE(id);
1437 pjsua_enum_conf_ports(id, &count);
1438
1439 for (i=0; i<count; ++i) {
1440 char txlist[PJSUA_MAX_CALLS*4+10];
1441 unsigned j;
1442 pjsua_conf_port_info info;
1443
1444 pjsua_conf_get_port_info(id[i], &info);
1445
1446 txlist[0] = '\0';
1447 for (j=0; j<info.listener_cnt; ++j) {
1448 char s[10];
1449 pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
1450 pj_ansi_strcat(txlist, s);
1451 }
1452 printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n",
1453 info.slot_id,
1454 info.clock_rate/1000,
1455 info.samples_per_frame * 1000 / info.clock_rate,
1456 (int)info.name.slen,
1457 info.name.ptr,
1458 txlist);
1459
1460 }
1461 puts("");
1462}
1463
1464
1465/*
1466 * Main "user interface" loop.
1467 */
1468void console_app_main(const pj_str_t *uri_to_call)
1469{
1470 char menuin[10];
1471 char buf[128];
1472 char text[128];
1473 int i, count;
1474 char *uri;
1475 pj_str_t tmp;
1476 struct input_result result;
1477 pjsua_call_info call_info;
1478 pjsua_acc_info acc_info;
1479
1480
1481 /* If user specifies URI to call, then call the URI */
1482 if (uri_to_call->slen) {
1483 pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, NULL);
1484 }
1485
1486 keystroke_help();
1487
1488 for (;;) {
1489
1490 printf(">>> ");
1491 fflush(stdout);
1492
1493 fgets(menuin, sizeof(menuin), stdin);
1494
1495 switch (menuin[0]) {
1496
1497 case 'm':
1498 /* Make call! : */
1499 printf("(You currently have %d calls)\n",
1500 pjsua_call_get_count());
1501
1502 uri = NULL;
1503 ui_input_url("Make call", buf, sizeof(buf), &result);
1504 if (result.nb_result != NO_NB) {
1505
1506 if (result.nb_result == -1 || result.nb_result == 0) {
1507 puts("You can't do that with make call!");
1508 continue;
1509 } else {
1510 pjsua_buddy_info binfo;
1511 pjsua_buddy_get_info(result.nb_result-1, &binfo);
1512 uri = binfo.uri.ptr;
1513 }
1514
1515 } else if (result.uri_result) {
1516 uri = result.uri_result;
1517 }
1518
1519 tmp = pj_str(uri);
1520 pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL);
1521 break;
1522
1523 case 'M':
1524 /* Make multiple calls! : */
1525 printf("(You currently have %d calls)\n",
1526 pjsua_call_get_count());
1527
1528 if (!simple_input("Number of calls", menuin, sizeof(menuin)))
1529 continue;
1530
1531 count = my_atoi(menuin);
1532 if (count < 1)
1533 continue;
1534
1535 ui_input_url("Make call", buf, sizeof(buf), &result);
1536 if (result.nb_result != NO_NB) {
1537 pjsua_buddy_info binfo;
1538 if (result.nb_result == -1 || result.nb_result == 0) {
1539 puts("You can't do that with make call!");
1540 continue;
1541 }
1542 pjsua_buddy_get_info(result.nb_result-1, &binfo);
1543 uri = binfo.uri.ptr;
1544 } else {
1545 uri = result.uri_result;
1546 }
1547
1548 for (i=0; i<my_atoi(menuin); ++i) {
1549 pj_status_t status;
1550
1551 tmp = pj_str(uri);
1552 status = pjsua_call_make_call(current_acc, &tmp, 0, NULL,
1553 NULL, NULL);
1554 if (status != PJ_SUCCESS)
1555 break;
1556 }
1557 break;
1558
1559 case 'i':
1560 /* Send instant messaeg */
1561
1562 /* i is for call index to send message, if any */
1563 i = -1;
1564
1565 /* Make compiler happy. */
1566 uri = NULL;
1567
1568 /* Input destination. */
1569 ui_input_url("Send IM to", buf, sizeof(buf), &result);
1570 if (result.nb_result != NO_NB) {
1571
1572 if (result.nb_result == -1) {
1573 puts("You can't send broadcast IM like that!");
1574 continue;
1575
1576 } else if (result.nb_result == 0) {
1577
1578 i = current_call;
1579
1580 } else {
1581 pjsua_buddy_info binfo;
1582 pjsua_buddy_get_info(result.nb_result-1, &binfo);
1583 uri = binfo.uri.ptr;
1584 }
1585
1586 } else if (result.uri_result) {
1587 uri = result.uri_result;
1588 }
1589
1590
1591 /* Send typing indication. */
1592 if (i != -1)
1593 pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
1594 else {
1595 pj_str_t tmp_uri = pj_str(uri);
1596 pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
1597 }
1598
1599 /* Input the IM . */
1600 if (!simple_input("Message", text, sizeof(text))) {
1601 /*
1602 * Cancelled.
1603 * Send typing notification too, saying we're not typing.
1604 */
1605 if (i != -1)
1606 pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
1607 else {
1608 pj_str_t tmp_uri = pj_str(uri);
1609 pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
1610 }
1611 continue;
1612 }
1613
1614 tmp = pj_str(text);
1615
1616 /* Send the IM */
1617 if (i != -1)
1618 pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
1619 else {
1620 pj_str_t tmp_uri = pj_str(uri);
1621 pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
1622 }
1623
1624 break;
1625
1626 case 'a':
1627
1628 if (current_call != -1) {
1629 pjsua_call_get_info(current_call, &call_info);
1630 } else {
1631 /* Make compiler happy */
1632 call_info.role = PJSIP_ROLE_UAC;
1633 call_info.state = PJSIP_INV_STATE_DISCONNECTED;
1634 }
1635
1636 if (current_call == -1 ||
1637 call_info.role != PJSIP_ROLE_UAS ||
1638 call_info.state >= PJSIP_INV_STATE_CONNECTING)
1639 {
1640 puts("No pending incoming call");
1641 fflush(stdout);
1642 continue;
1643
1644 } else {
1645 if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
1646 continue;
1647
1648 if (my_atoi(buf) < 100)
1649 continue;
1650
1651 /*
1652 * Must check again!
1653 * Call may have been disconnected while we're waiting for
1654 * keyboard input.
1655 */
1656 if (current_call == -1) {
1657 puts("Call has been disconnected");
1658 fflush(stdout);
1659 continue;
1660 }
1661
1662 pjsua_call_answer(current_call, my_atoi(buf), NULL, NULL);
1663 }
1664
1665 break;
1666
1667
1668 case 'h':
1669
1670 if (current_call == -1) {
1671 puts("No current call");
1672 fflush(stdout);
1673 continue;
1674
1675 } else if (menuin[1] == 'a') {
1676
1677 /* Hangup all calls */
1678 pjsua_call_hangup_all();
1679
1680 } else {
1681
1682 /* Hangup current calls */
1683 pjsua_call_hangup(current_call, 0, NULL, NULL);
1684 }
1685 break;
1686
1687 case ']':
1688 case '[':
1689 /*
1690 * Cycle next/prev dialog.
1691 */
1692 if (menuin[0] == ']') {
1693 find_next_call();
1694
1695 } else {
1696 find_prev_call();
1697 }
1698
1699 if (current_call != -1) {
1700
1701 pjsua_call_get_info(current_call, &call_info);
1702 PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s",
1703 (int)call_info.remote_info.slen,
1704 call_info.remote_info.ptr));
1705
1706 } else {
1707 PJ_LOG(3,(THIS_FILE,"No current dialog"));
1708 }
1709 break;
1710
1711
1712 case '>':
1713 case '<':
1714 if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
1715 break;
1716
1717 i = my_atoi(buf);
1718 if (pjsua_acc_is_valid(i)) {
1719 current_acc = i;
1720 PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
1721 } else {
1722 PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
1723 }
1724 break;
1725
1726
1727 case '+':
1728 if (menuin[1] == 'b') {
1729
1730 pjsua_buddy_config buddy_cfg;
1731 pjsua_buddy_id buddy_id;
1732 pj_status_t status;
1733
1734 if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
1735 break;
1736
1737 if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) {
1738 printf("Invalid SIP URI '%s'\n", buf);
1739 break;
1740 }
1741
Benny Prijonoac623b32006-07-03 15:19:31 +00001742 pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001743
1744 buddy_cfg.uri = pj_str(buf);
1745 buddy_cfg.subscribe = PJ_TRUE;
1746
1747 status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
1748 if (status == PJ_SUCCESS) {
1749 printf("New buddy '%s' added at index %d\n",
1750 buf, buddy_id+1);
1751 }
1752
1753 } else if (menuin[1] == 'a') {
1754
1755 printf("Sorry, this command is not supported yet\n");
1756
1757 } else {
1758 printf("Invalid input %s\n", menuin);
1759 }
1760 break;
1761
1762 case '-':
1763 if (menuin[1] == 'b') {
1764 if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
1765 break;
1766
1767 i = my_atoi(buf) - 1;
1768
1769 if (!pjsua_buddy_is_valid(i)) {
1770 printf("Invalid buddy id %d\n", i);
1771 } else {
1772 pjsua_buddy_del(i);
1773 printf("Buddy %d deleted\n", i);
1774 }
1775
1776 } else if (menuin[1] == 'a') {
1777
1778 if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
1779 break;
1780
1781 i = my_atoi(buf);
1782
1783 if (!pjsua_acc_is_valid(i)) {
1784 printf("Invalid account id %d\n", i);
1785 } else {
1786 pjsua_acc_del(i);
1787 printf("Account %d deleted\n", i);
1788 }
1789
1790 } else {
1791 printf("Invalid input %s\n", menuin);
1792 }
1793 break;
1794
1795 case 'H':
1796 /*
1797 * Hold call.
1798 */
1799 if (current_call != -1) {
1800
1801 pjsua_call_set_hold(current_call, NULL);
1802
1803 } else {
1804 PJ_LOG(3,(THIS_FILE, "No current call"));
1805 }
1806 break;
1807
1808 case 'v':
1809 /*
1810 * Send re-INVITE (to release hold, etc).
1811 */
1812 if (current_call != -1) {
1813
1814 pjsua_call_reinvite(current_call, PJ_TRUE, NULL);
1815
1816 } else {
1817 PJ_LOG(3,(THIS_FILE, "No current call"));
1818 }
1819 break;
1820
1821 case 'x':
1822 /*
1823 * Transfer call.
1824 */
1825 if (current_call == -1) {
1826
1827 PJ_LOG(3,(THIS_FILE, "No current call"));
1828
1829 } else {
1830 int call = current_call;
1831
1832 ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
1833
1834 /* Check if call is still there. */
1835
1836 if (call != current_call) {
1837 puts("Call has been disconnected");
1838 continue;
1839 }
1840
1841 if (result.nb_result != NO_NB) {
1842 if (result.nb_result == -1 || result.nb_result == 0)
1843 puts("You can't do that with transfer call!");
1844 else {
1845 pjsua_buddy_info binfo;
1846 pjsua_buddy_get_info(result.nb_result-1, &binfo);
1847 pjsua_call_xfer( current_call, &binfo.uri, NULL);
1848 }
1849
1850 } else if (result.uri_result) {
1851 pj_str_t tmp;
1852 tmp = pj_str(result.uri_result);
1853 pjsua_call_xfer( current_call, &tmp, NULL);
1854 }
1855 }
1856 break;
1857
1858 case '#':
1859 /*
1860 * Send DTMF strings.
1861 */
1862 if (current_call == -1) {
1863
1864 PJ_LOG(3,(THIS_FILE, "No current call"));
1865
1866 } else if (!pjsua_call_has_media(current_call)) {
1867
1868 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
1869
1870 } else {
1871 pj_str_t digits;
1872 int call = current_call;
1873 pj_status_t status;
1874
1875 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
1876 sizeof(buf)))
1877 {
1878 break;
1879 }
1880
1881 if (call != current_call) {
1882 puts("Call has been disconnected");
1883 continue;
1884 }
1885
1886 digits = pj_str(buf);
1887 status = pjsua_call_dial_dtmf(current_call, &digits);
1888 if (status != PJ_SUCCESS) {
1889 pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
1890 } else {
1891 puts("DTMF digits enqueued for transmission");
1892 }
1893 }
1894 break;
1895
1896 case 's':
1897 case 'u':
1898 /*
1899 * Subscribe/unsubscribe presence.
1900 */
1901 ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
1902 if (result.nb_result != NO_NB) {
1903 if (result.nb_result == -1) {
1904 int i, count;
1905 count = pjsua_get_buddy_count();
1906 for (i=0; i<count; ++i)
1907 pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
1908 } else if (result.nb_result == 0) {
1909 puts("Sorry, can only subscribe to buddy's presence, "
1910 "not from existing call");
1911 } else {
1912 pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
1913 }
1914
1915 } else if (result.uri_result) {
1916 puts("Sorry, can only subscribe to buddy's presence, "
1917 "not arbitrary URL (for now)");
1918 }
1919
1920 break;
1921
1922 case 'r':
1923 switch (menuin[1]) {
1924 case 'r':
1925 /*
1926 * Re-Register.
1927 */
1928 pjsua_acc_set_registration(current_acc, PJ_TRUE);
1929 break;
1930 case 'u':
1931 /*
1932 * Unregister
1933 */
1934 pjsua_acc_set_registration(current_acc, PJ_FALSE);
1935 break;
1936 }
1937 break;
1938
1939 case 't':
1940 pjsua_acc_get_info(current_acc, &acc_info);
1941 acc_info.online_status = !acc_info.online_status;
1942 pjsua_acc_set_online_status(current_acc, acc_info.online_status);
1943 printf("Setting %s online status to %s\n",
1944 acc_info.acc_uri.ptr,
1945 (acc_info.online_status?"online":"offline"));
1946 break;
1947
1948 case 'c':
1949 switch (menuin[1]) {
1950 case 'l':
1951 conf_list();
1952 break;
1953 case 'c':
1954 case 'd':
1955 {
1956 char src_port[10], dst_port[10];
1957 pj_status_t status;
1958 const char *src_title, *dst_title;
1959
1960 conf_list();
1961
1962 src_title = (menuin[1]=='c'?
1963 "Connect src port #":
1964 "Disconnect src port #");
1965 dst_title = (menuin[1]=='c'?
1966 "To dst port #":
1967 "From dst port #");
1968
1969 if (!simple_input(src_title, src_port, sizeof(src_port)))
1970 break;
1971
1972 if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
1973 break;
1974
1975 if (menuin[1]=='c') {
1976 status = pjsua_conf_connect(my_atoi(src_port),
1977 my_atoi(dst_port));
1978 } else {
1979 status = pjsua_conf_disconnect(my_atoi(src_port),
1980 my_atoi(dst_port));
1981 }
1982 if (status == PJ_SUCCESS) {
1983 puts("Success");
1984 } else {
1985 puts("ERROR!!");
1986 }
1987 }
1988 break;
1989 }
1990 break;
1991
1992 case 'd':
1993 if (menuin[1] == 'c') {
1994 char settings[2000];
1995 int len;
1996
1997 len = write_settings(&app_config, settings, sizeof(settings));
1998 if (len < 1)
1999 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2000 else
2001 PJ_LOG(3,(THIS_FILE,
2002 "Dumping configuration (%d bytes):\n%s\n",
2003 len, settings));
Benny Prijono819506c2006-06-22 22:29:51 +00002004
2005 } else if (menuin[1] == 'q') {
2006
2007 if (current_call != PJSUA_INVALID_ID) {
2008 char buf[1024];
2009 pjsua_call_dump(current_call, PJ_TRUE, buf,
2010 sizeof(buf), " ");
2011 PJ_LOG(3,(THIS_FILE, "\n%s", buf));
2012 } else {
2013 PJ_LOG(3,(THIS_FILE, "No current call"));
2014 }
2015
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002016 } else {
2017 app_dump(menuin[1]=='d');
2018 }
2019 break;
2020
2021
2022 case 'f':
2023 if (simple_input("Enter output filename", buf, sizeof(buf))) {
2024 char settings[2000];
2025 int len;
2026
2027 len = write_settings(&app_config, settings, sizeof(settings));
2028 if (len < 1)
2029 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2030 else {
2031 pj_oshandle_t fd;
2032 pj_status_t status;
2033
2034 status = pj_file_open(app_config.pool, buf,
2035 PJ_O_WRONLY, &fd);
2036 if (status != PJ_SUCCESS) {
2037 pjsua_perror(THIS_FILE, "Unable to open file", status);
2038 } else {
2039 pj_ssize_t size = len;
2040 pj_file_write(fd, settings, &size);
2041 pj_file_close(fd);
2042
2043 printf("Settings successfully written to '%s'\n", buf);
2044 }
2045 }
2046
2047 }
2048 break;
2049
2050
2051 case 'q':
2052 goto on_exit;
2053
2054
2055 default:
2056 if (menuin[0] != '\n' && menuin[0] != '\r') {
2057 printf("Invalid input %s", menuin);
2058 }
2059 keystroke_help();
2060 break;
2061 }
2062 }
2063
2064on_exit:
2065 ;
2066}
2067
2068
2069/*****************************************************************************
2070 * Public API
2071 */
2072
2073pj_status_t app_init(int argc, char *argv[])
2074{
Benny Prijonoe93e2872006-06-28 16:46:49 +00002075 pjsua_transport_id transport_id = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002076 unsigned i;
2077 pj_status_t status;
2078
2079 /* Create pjsua */
2080 status = pjsua_create();
2081 if (status != PJ_SUCCESS)
2082 return status;
2083
2084 /* Create pool for application */
2085 app_config.pool = pjsua_pool_create("pjsua", 4000, 4000);
2086
2087 /* Initialize default config */
2088 default_config(&app_config);
2089
2090 /* Parse the arguments */
2091 status = parse_args(argc, argv, &app_config, &uri_arg);
2092 if (status != PJ_SUCCESS)
2093 return status;
2094
2095 /* Copy udp_cfg STUN config to rtp_cfg */
2096 app_config.rtp_cfg.use_stun = app_config.udp_cfg.use_stun;
2097 app_config.rtp_cfg.stun_config = app_config.udp_cfg.stun_config;
2098
2099
2100 /* Initialize application callbacks */
2101 app_config.cfg.cb.on_call_state = &on_call_state;
2102 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
2103 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
2104 app_config.cfg.cb.on_reg_state = &on_reg_state;
2105 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
2106 app_config.cfg.cb.on_pager = &on_pager;
2107 app_config.cfg.cb.on_typing = &on_typing;
2108
2109 /* Initialize pjsua */
2110 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
2111 &app_config.media_cfg);
2112 if (status != PJ_SUCCESS)
2113 return status;
2114
2115 /* Optionally registers WAV file */
2116 if (app_config.wav_file.slen) {
Benny Prijono6fd4b8f2006-06-22 18:51:50 +00002117 status = pjsua_player_create(&app_config.wav_file, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002118 &app_config.wav_id);
2119 if (status != PJ_SUCCESS)
2120 goto on_error;
Benny Prijono22a300a2006-06-14 20:04:55 +00002121
2122 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002123 }
2124
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002125
Benny Prijonoe93e2872006-06-28 16:46:49 +00002126 /* Add TCP transport unless it's disabled */
2127 if (!app_config.no_tcp) {
2128 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
2129 &app_config.udp_cfg,
2130 &transport_id);
2131 if (status != PJ_SUCCESS)
2132 goto on_error;
2133
2134 /* Add local account */
2135 pjsua_acc_add_local(transport_id, PJ_TRUE, &current_acc);
2136 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2137
2138 }
2139
2140
2141 /* Add UDP transport unless it's disabled. */
2142 if (!app_config.no_udp) {
2143 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
2144 &app_config.udp_cfg,
2145 &transport_id);
2146 if (status != PJ_SUCCESS)
2147 goto on_error;
2148
2149 /* Add local account */
2150 pjsua_acc_add_local(transport_id, PJ_TRUE, &current_acc);
2151 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2152 }
2153
2154 if (transport_id == -1) {
2155 PJ_LOG(3,(THIS_FILE, "Error: no transport is configured"));
2156 status = -1;
2157 goto on_error;
2158 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002159
2160
2161 /* Add accounts */
2162 for (i=0; i<app_config.acc_cnt; ++i) {
2163 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, &current_acc);
2164 if (status != PJ_SUCCESS)
2165 goto on_error;
2166 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2167 }
2168
2169 /* Add buddies */
2170 for (i=0; i<app_config.buddy_cnt; ++i) {
2171 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
2172 if (status != PJ_SUCCESS)
2173 goto on_error;
2174 }
2175
2176 /* Optionally set codec orders */
2177 for (i=0; i<app_config.codec_cnt; ++i) {
2178 pjsua_codec_set_priority(&app_config.codec_arg[i],
2179 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
2180 }
2181
2182 /* Add RTP transports */
2183 status = pjsua_media_transports_create(&app_config.rtp_cfg);
2184 if (status != PJ_SUCCESS)
2185 goto on_error;
2186
Benny Prijono22a300a2006-06-14 20:04:55 +00002187 /* Use null sound device? */
2188 if (app_config.null_audio) {
2189 status = pjsua_set_null_snd_dev();
2190 if (status != PJ_SUCCESS)
2191 return status;
2192 }
2193
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002194 return PJ_SUCCESS;
2195
2196on_error:
2197 pjsua_destroy();
2198 return status;
2199}
2200
2201
2202pj_status_t app_main(void)
2203{
2204 pj_status_t status;
2205
2206 /* Start pjsua */
2207 status = pjsua_start();
2208 if (status != PJ_SUCCESS) {
2209 pjsua_destroy();
2210 return status;
2211 }
2212
2213 console_app_main(&uri_arg);
2214
2215 return PJ_SUCCESS;
2216}
2217
2218pj_status_t app_destroy(void)
2219{
2220 if (app_config.pool) {
2221 pj_pool_release(app_config.pool);
2222 app_config.pool = NULL;
2223 }
2224
2225 return pjsua_destroy();
2226}