blob: 98b6f2c47533589d327f1e0bef37246807c22ad8 [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Benny Prijonoa771a512007-02-19 01:13:53 +00003 * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsua-lib/pjsua.h>
20
21
22#define THIS_FILE "pjsua.c"
Benny Prijono804ff0a2006-09-14 11:17:48 +000023#define NO_LIMIT (int)0x7FFFFFFF
Benny Prijonoeebe9af2006-06-13 22:57:13 +000024
Benny Prijonoe909eac2006-07-27 22:04:56 +000025//#define STEREO_DEMO
Benny Prijonoeebe9af2006-06-13 22:57:13 +000026
Benny Prijono804ff0a2006-09-14 11:17:48 +000027/* Call specific data */
28struct call_data
29{
30 pj_timer_entry timer;
31};
32
Benny Prijonoeebe9af2006-06-13 22:57:13 +000033
34/* Pjsua application data */
35static struct app_config
36{
37 pjsua_config cfg;
38 pjsua_logging_config log_cfg;
39 pjsua_media_config media_cfg;
Benny Prijono4ddad2c2006-10-18 17:16:34 +000040 pj_bool_t no_refersub;
Benny Prijonoe93e2872006-06-28 16:46:49 +000041 pj_bool_t no_tcp;
42 pj_bool_t no_udp;
Benny Prijono6e0e54b2006-12-08 21:58:31 +000043 pj_bool_t use_tls;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000044 pjsua_transport_config udp_cfg;
45 pjsua_transport_config rtp_cfg;
46
47 unsigned acc_cnt;
48 pjsua_acc_config acc_cfg[PJSUA_MAX_ACC];
49
50 unsigned buddy_cnt;
51 pjsua_buddy_config buddy_cfg[PJSUA_MAX_BUDDIES];
52
Benny Prijono804ff0a2006-09-14 11:17:48 +000053 struct call_data call_data[PJSUA_MAX_CALLS];
54
Benny Prijonoeebe9af2006-06-13 22:57:13 +000055 pj_pool_t *pool;
56 /* Compatibility with older pjsua */
57
58 unsigned codec_cnt;
59 pj_str_t codec_arg[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +000060 pj_bool_t null_audio;
Benny Prijono32e4f492007-01-24 00:44:26 +000061 unsigned wav_count;
62 pj_str_t wav_files[32];
Benny Prijono4af234b2007-01-24 02:02:09 +000063 unsigned tone_count;
64 pjmedia_tone_desc tones[32];
65 pjsua_conf_port_id tone_slots[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +000066 pjsua_player_id wav_id;
67 pjsua_conf_port_id wav_port;
68 pj_bool_t auto_play;
69 pj_bool_t auto_loop;
Benny Prijono7ca96da2006-08-07 12:11:40 +000070 pj_bool_t auto_conf;
Benny Prijono1ebd6142006-10-19 15:48:02 +000071 pj_str_t rec_file;
72 pj_bool_t auto_rec;
73 pjsua_recorder_id rec_id;
74 pjsua_conf_port_id rec_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000075 unsigned auto_answer;
76 unsigned duration;
Benny Prijonoe909eac2006-07-27 22:04:56 +000077
78#ifdef STEREO_DEMO
79 pjmedia_snd_port *snd;
80#endif
81
Benny Prijono6dd967c2006-12-26 02:27:14 +000082 float mic_level,
83 speaker_level;
84
Benny Prijono4e5d5512007-03-06 18:11:30 +000085 int capture_dev, playback_dev;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000086} app_config;
87
88
Benny Prijono21b9ad92006-08-15 13:11:22 +000089//static pjsua_acc_id current_acc;
90#define current_acc pjsua_acc_get_default()
Benny Prijonof7b1c392006-11-11 16:46:34 +000091static pjsua_call_id current_call = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000092static pj_str_t uri_arg;
93
Benny Prijono594e4c52006-09-14 18:51:01 +000094#ifdef STEREO_DEMO
Benny Prijonoe909eac2006-07-27 22:04:56 +000095static void stereo_demo();
Benny Prijono594e4c52006-09-14 18:51:01 +000096#endif
Benny Prijonoad2e0ca2007-04-29 12:31:51 +000097pj_status_t app_destroy(void);
Benny Prijonoe909eac2006-07-27 22:04:56 +000098
Benny Prijonoeebe9af2006-06-13 22:57:13 +000099/*****************************************************************************
100 * Configuration manipulation
101 */
102
103/* Show usage */
104static void usage(void)
105{
106 puts ("Usage:");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000107 puts (" pjsua [options] [SIP URL to call]");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000108 puts ("");
109 puts ("General options:");
Benny Prijono804ff0a2006-09-14 11:17:48 +0000110 puts (" --config-file=file Read the config/arguments from file.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000111 puts (" --help Display this help screen");
112 puts (" --version Display version info");
113 puts ("");
114 puts ("Logging options:");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000115 puts (" --log-file=fname Log to filename (default stderr)");
116 puts (" --log-level=N Set log max level to N (0(none) to 6(trace)) (default=5)");
117 puts (" --app-log-level=N Set log max level for stdout display (default=4)");
118 puts ("");
119 puts ("SIP Account options:");
120 puts (" --registrar=url Set the URL of registrar server");
121 puts (" --id=url Set the URL of local ID (used in From header)");
122 puts (" --contact=url Optionally override the Contact information");
123 puts (" --proxy=url Optional URL of proxy server to visit");
124 puts (" May be specified multiple times");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000125 puts (" --reg-timeout=SEC Optional registration interval (default 55)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000126 puts (" --realm=string Set realm");
127 puts (" --username=string Set authentication username");
128 puts (" --password=string Set authentication password");
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000129 puts (" --publish Send presence PUBLISH for this account");
Benny Prijonob67eaac2007-02-18 20:56:00 +0000130 puts (" --next-cred Add another credentials");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000131 puts ("");
132 puts ("SIP Account Control:");
133 puts (" --next-account Add more account");
134 puts ("");
135 puts ("Transport Options:");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000136 puts (" --local-port=port Set TCP/UDP port. This implicitly enables both ");
137 puts (" TCP and UDP transports on the specified port, unless");
138 puts (" if TCP or UDP is disabled.");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000139 puts (" --ip-addr=IP Use the specifed address as SIP and RTP addresses.");
140 puts (" (Hint: the IP may be the public IP of the NAT/router)");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000141 puts (" --no-tcp Disable TCP transport.");
142 puts (" --no-udp Disable UDP transport.");
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000143 puts (" --nameserver=NS Add the specified nameserver to enable SRV resolution");
144 puts (" This option can be specified multiple times.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000145 puts (" --outbound=url Set the URL of global outbound proxy server");
146 puts (" May be specified multiple times");
Benny Prijonoc97608e2007-03-23 16:34:20 +0000147 puts (" --stun-srv=name Set STUN server host or domain");
Benny Prijonof3bbc132006-12-25 06:43:59 +0000148 puts ("");
149 puts ("TLS Options:");
Benny Prijonob67eaac2007-02-18 20:56:00 +0000150 puts (" --use-tls Enable TLS transport (default=no)");
Benny Prijonof3bbc132006-12-25 06:43:59 +0000151 puts (" --tls-ca-file Specify TLS CA file (default=none)");
152 puts (" --tls-cert-file Specify TLS certificate file (default=none)");
153 puts (" --tls-privkey-file Specify TLS private key file (default=none)");
154 puts (" --tls-password Specify TLS password to private key file (default=none)");
155 puts (" --tls-verify-server Verify server's certificate (default=no)");
156 puts (" --tls-verify-client Verify client's certificate (default=no)");
157 puts (" --tls-neg-timeout Specify TLS negotiation timeout (default=no)");
158
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000159 puts ("");
160 puts ("Media Options:");
Benny Prijonoc97608e2007-03-23 16:34:20 +0000161 puts (" --use-ice Enable ICE (default:no)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000162 puts (" --add-codec=name Manually add codec (default is to enable all)");
163 puts (" --clock-rate=N Override sound device clock rate");
164 puts (" --null-audio Use NULL audio device");
Benny Prijono4af234b2007-01-24 02:02:09 +0000165 puts (" --play-file=file Register WAV file in conference bridge.");
166 puts (" This can be specified multiple times.");
Benny Prijonob67eaac2007-02-18 20:56:00 +0000167 puts (" --play-tone=FORMAT Register tone to the conference bridge.");
168 puts (" FORMAT is 'F1,F2,ON,OFF', where F1,F2 are");
169 puts (" frequencies, and ON,OFF=on/off duration in msec.");
Benny Prijono4af234b2007-01-24 02:02:09 +0000170 puts (" This can be specified multiple times.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000171 puts (" --auto-play Automatically play the file (to incoming calls only)");
172 puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP");
Benny Prijono7ca96da2006-08-07 12:11:40 +0000173 puts (" --auto-conf Automatically put calls in conference with others");
Benny Prijono1ebd6142006-10-19 15:48:02 +0000174 puts (" --rec-file=file Open file recorder (extension can be .wav or .mp3");
175 puts (" --auto-rec Automatically record conversation");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000176 puts (" --rtp-port=N Base port to try for RTP (default=4000)");
Benny Prijono00cae612006-07-31 15:19:36 +0000177 puts (" --quality=N Specify media quality (0-10, default=6)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000178 puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)");
Benny Prijono0a12f002006-07-26 17:05:39 +0000179 puts (" --no-vad Disable VAD/silence detector (default=vad enabled)");
Benny Prijonod79f25c2006-08-02 19:41:37 +0000180 puts (" --ec-tail=MSEC Set echo canceller tail length (default=256)");
Benny Prijono00cae612006-07-31 15:19:36 +0000181 puts (" --ilbc-mode=MODE Set iLBC codec mode (20 or 30, default is 20)");
182 puts (" --rx-drop-pct=PCT Drop PCT percent of RX RTP (for pkt lost sim, default: 0)");
183 puts (" --tx-drop-pct=PCT Drop PCT percent of TX RTP (for pkt lost sim, default: 0)");
Benny Prijono4e5d5512007-03-06 18:11:30 +0000184 puts (" --capture-dev=id Audio capture device ID (default=-1)");
185 puts (" --playback-dev=id Audio playback device ID (default=-1)");
Benny Prijono00cae612006-07-31 15:19:36 +0000186
Benny Prijono0a12f002006-07-26 17:05:39 +0000187
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000188 puts ("");
189 puts ("Buddy List (can be more than one):");
190 puts (" --add-buddy url Add the specified URL to the buddy list.");
191 puts ("");
192 puts ("User Agent options:");
193 puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)");
194 puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)");
Benny Prijonof521eb02006-08-06 23:07:25 +0000195 puts (" --thread-cnt=N Number of worker threads (default:1)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000196 puts (" --duration=SEC Set maximum call duration (default:no limit)");
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000197 puts (" --norefersub Suppress event subscription when transfering calls");
Benny Prijono804ff0a2006-09-14 11:17:48 +0000198
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000199 puts ("");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000200 puts ("When URL is specified, pjsua will immediately initiate call to that URL");
201 puts ("");
202
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000203 fflush(stdout);
204}
205
206
207/* Set default config. */
208static void default_config(struct app_config *cfg)
209{
Benny Prijono56315612006-07-18 14:39:40 +0000210 char tmp[80];
Benny Prijono1febfdf2007-02-18 01:35:04 +0000211 unsigned i;
Benny Prijono56315612006-07-18 14:39:40 +0000212
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000213 pjsua_config_default(&cfg->cfg);
Benny Prijono56315612006-07-18 14:39:40 +0000214 pj_ansi_sprintf(tmp, "PJSUA v%s/%s", PJ_VERSION, PJ_OS_NAME);
215 pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
216
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000217 pjsua_logging_config_default(&cfg->log_cfg);
218 pjsua_media_config_default(&cfg->media_cfg);
219 pjsua_transport_config_default(&cfg->udp_cfg);
220 cfg->udp_cfg.port = 5060;
221 pjsua_transport_config_default(&cfg->rtp_cfg);
222 cfg->rtp_cfg.port = 4000;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000223 cfg->duration = NO_LIMIT;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000224 cfg->wav_id = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000225 cfg->rec_id = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000226 cfg->wav_port = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000227 cfg->rec_port = PJSUA_INVALID_ID;
Benny Prijono6dd967c2006-12-26 02:27:14 +0000228 cfg->mic_level = cfg->speaker_level = 1.0;
Benny Prijono4e5d5512007-03-06 18:11:30 +0000229 cfg->capture_dev = PJSUA_INVALID_ID;
230 cfg->playback_dev = PJSUA_INVALID_ID;
Benny Prijono1febfdf2007-02-18 01:35:04 +0000231
232 for (i=0; i<PJ_ARRAY_SIZE(cfg->acc_cfg); ++i)
233 pjsua_acc_config_default(&cfg->acc_cfg[i]);
234
235 for (i=0; i<PJ_ARRAY_SIZE(cfg->buddy_cfg); ++i)
236 pjsua_buddy_config_default(&cfg->buddy_cfg[i]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000237}
238
239
240/*
241 * Read command arguments from config file.
242 */
243static int read_config_file(pj_pool_t *pool, const char *filename,
244 int *app_argc, char ***app_argv)
245{
246 int i;
247 FILE *fhnd;
248 char line[200];
249 int argc = 0;
250 char **argv;
251 enum { MAX_ARGS = 64 };
252
253 /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
254 argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
255 argv[argc++] = *app_argv[0];
256
257 /* Open config file. */
258 fhnd = fopen(filename, "rt");
259 if (!fhnd) {
260 PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
261 fflush(stdout);
262 return -1;
263 }
264
265 /* Scan tokens in the file. */
266 while (argc < MAX_ARGS && !feof(fhnd)) {
267 char *token, *p = line;
268
269 if (fgets(line, sizeof(line), fhnd) == NULL) break;
270
271 for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS;
272 token = strtok(NULL, " \t\r\n"))
273 {
274 int token_len;
275
276 if (!token) break;
277 if (*token == '#') break;
278
279 token_len = strlen(token);
280 if (!token_len)
281 continue;
282 argv[argc] = pj_pool_alloc(pool, token_len+1);
283 pj_memcpy(argv[argc], token, token_len+1);
284 ++argc;
285 }
286 }
287
288 /* Copy arguments from command line */
289 for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
290 argv[argc++] = (*app_argv)[i];
291
292 if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
293 PJ_LOG(1,(THIS_FILE,
294 "Too many arguments specified in cmd line/config file"));
295 fflush(stdout);
296 fclose(fhnd);
297 return -1;
298 }
299
300 fclose(fhnd);
301
302 /* Assign the new command line back to the original command line. */
303 *app_argc = argc;
304 *app_argv = argv;
305 return 0;
306
307}
308
309static int my_atoi(const char *cs)
310{
311 pj_str_t s;
Benny Prijono1e2dbe62007-06-15 04:15:16 +0000312
313 pj_cstr(&s, cs);
314 if (cs[0] == '-') {
315 s.ptr++, s.slen--;
316 return 0 - (int)pj_strtoul(&s);
317 } else if (cs[0] == '+') {
318 s.ptr++, s.slen--;
319 return pj_strtoul(&s);
320 } else {
321 return pj_strtoul(&s);
322 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000323}
324
325
326/* Parse arguments. */
327static pj_status_t parse_args(int argc, char *argv[],
328 struct app_config *cfg,
329 pj_str_t *uri_to_call)
330{
331 int c;
332 int option_index;
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000333 enum { OPT_CONFIG_FILE=127, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000334 OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
Benny Prijono0a5cad82006-09-26 13:21:02 +0000335 OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY,
336 OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000337 OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
Benny Prijonoebbf6892007-03-24 17:37:25 +0000338 OPT_NAMESERVER, OPT_STUN_DOMAIN, OPT_STUN_SRV,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000339 OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
340 OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000341 OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_USE_ICE,
Benny Prijono4af234b2007-01-24 02:02:09 +0000342 OPT_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC,
343 OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC,
Benny Prijono0a12f002006-07-26 17:05:39 +0000344 OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
Benny Prijonod79f25c2006-08-02 19:41:37 +0000345 OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL,
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000346 OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
Benny Prijonof521eb02006-08-06 23:07:25 +0000347 OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000348 OPT_NOREFERSUB,
Benny Prijonof3bbc132006-12-25 06:43:59 +0000349 OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_CERT_FILE, OPT_TLS_PRIV_FILE,
350 OPT_TLS_PASSWORD, OPT_TLS_VERIFY_SERVER, OPT_TLS_VERIFY_CLIENT,
351 OPT_TLS_NEG_TIMEOUT,
Benny Prijono4e5d5512007-03-06 18:11:30 +0000352 OPT_CAPTURE_DEV, OPT_PLAYBACK_DEV,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000353 };
354 struct pj_getopt_option long_options[] = {
355 { "config-file",1, 0, OPT_CONFIG_FILE},
356 { "log-file", 1, 0, OPT_LOG_FILE},
357 { "log-level", 1, 0, OPT_LOG_LEVEL},
358 { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
359 { "help", 0, 0, OPT_HELP},
360 { "version", 0, 0, OPT_VERSION},
361 { "clock-rate", 1, 0, OPT_CLOCK_RATE},
362 { "null-audio", 0, 0, OPT_NULL_AUDIO},
363 { "local-port", 1, 0, OPT_LOCAL_PORT},
Benny Prijono0a5cad82006-09-26 13:21:02 +0000364 { "ip-addr", 1, 0, OPT_IP_ADDR},
Benny Prijonoe93e2872006-06-28 16:46:49 +0000365 { "no-tcp", 0, 0, OPT_NO_TCP},
366 { "no-udp", 0, 0, OPT_NO_UDP},
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000367 { "norefersub", 0, 0, OPT_NOREFERSUB},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000368 { "proxy", 1, 0, OPT_PROXY},
369 { "outbound", 1, 0, OPT_OUTBOUND_PROXY},
370 { "registrar", 1, 0, OPT_REGISTRAR},
371 { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000372 { "publish", 0, 0, OPT_PUBLISH},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000373 { "id", 1, 0, OPT_ID},
374 { "contact", 1, 0, OPT_CONTACT},
375 { "realm", 1, 0, OPT_REALM},
376 { "username", 1, 0, OPT_USERNAME},
377 { "password", 1, 0, OPT_PASSWORD},
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000378 { "nameserver", 1, 0, OPT_NAMESERVER},
Benny Prijonoebbf6892007-03-24 17:37:25 +0000379 { "stun-domain",1, 0, OPT_STUN_DOMAIN},
Benny Prijonoc97608e2007-03-23 16:34:20 +0000380 { "stun-srv", 1, 0, OPT_STUN_SRV},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000381 { "add-buddy", 1, 0, OPT_ADD_BUDDY},
382 { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
383 { "no-presence", 0, 0, OPT_NO_PRESENCE},
384 { "auto-answer",1, 0, OPT_AUTO_ANSWER},
385 { "auto-hangup",1, 0, OPT_AUTO_HANGUP},
386 { "auto-play", 0, 0, OPT_AUTO_PLAY},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000387 { "auto-rec", 0, 0, OPT_AUTO_REC},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000388 { "auto-loop", 0, 0, OPT_AUTO_LOOP},
389 { "auto-conf", 0, 0, OPT_AUTO_CONF},
390 { "play-file", 1, 0, OPT_PLAY_FILE},
Benny Prijono4af234b2007-01-24 02:02:09 +0000391 { "play-tone", 1, 0, OPT_PLAY_TONE},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000392 { "rec-file", 1, 0, OPT_REC_FILE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000393 { "rtp-port", 1, 0, OPT_RTP_PORT},
Benny Prijonoc97608e2007-03-23 16:34:20 +0000394 { "use-ice", 0, 0, OPT_USE_ICE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000395 { "add-codec", 1, 0, OPT_ADD_CODEC},
396 { "complexity", 1, 0, OPT_COMPLEXITY},
397 { "quality", 1, 0, OPT_QUALITY},
398 { "ptime", 1, 0, OPT_PTIME},
Benny Prijono0a12f002006-07-26 17:05:39 +0000399 { "no-vad", 0, 0, OPT_NO_VAD},
Benny Prijonod79f25c2006-08-02 19:41:37 +0000400 { "ec-tail", 1, 0, OPT_EC_TAIL},
Benny Prijono00cae612006-07-31 15:19:36 +0000401 { "ilbc-mode", 1, 0, OPT_ILBC_MODE},
402 { "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
403 { "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000404 { "next-account",0,0, OPT_NEXT_ACCOUNT},
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000405 { "next-cred", 0, 0, OPT_NEXT_CRED},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000406 { "max-calls", 1, 0, OPT_MAX_CALLS},
Benny Prijonof521eb02006-08-06 23:07:25 +0000407 { "duration", 1, 0, OPT_DURATION},
408 { "thread-cnt", 1, 0, OPT_THREAD_CNT},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000409 { "use-tls", 0, 0, OPT_USE_TLS},
410 { "tls-ca-file",1, 0, OPT_TLS_CA_FILE},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000411 { "tls-cert-file",1,0, OPT_TLS_CERT_FILE},
412 { "tls-privkey-file",1,0, OPT_TLS_PRIV_FILE},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000413 { "tls-password",1,0, OPT_TLS_PASSWORD},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000414 { "tls-verify-server", 0, 0, OPT_TLS_VERIFY_SERVER},
415 { "tls-verify-client", 0, 0, OPT_TLS_VERIFY_CLIENT},
416 { "tls-neg-timeout", 1, 0, OPT_TLS_NEG_TIMEOUT},
Benny Prijono4e5d5512007-03-06 18:11:30 +0000417 { "capture-dev", 1, 0, OPT_CAPTURE_DEV},
418 { "playback-dev", 1, 0, OPT_PLAYBACK_DEV},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000419 { NULL, 0, 0, 0}
420 };
421 pj_status_t status;
422 pjsua_acc_config *cur_acc;
423 char *config_file = NULL;
424 unsigned i;
425
426 /* Run pj_getopt once to see if user specifies config file to read. */
Benny Prijonof762ee72006-12-01 11:14:37 +0000427 pj_optind = 0;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000428 while ((c=pj_getopt_long(argc, argv, "", long_options,
429 &option_index)) != -1)
430 {
431 switch (c) {
432 case OPT_CONFIG_FILE:
433 config_file = pj_optarg;
434 break;
435 }
436 if (config_file)
437 break;
438 }
439
440 if (config_file) {
441 status = read_config_file(app_config.pool, config_file, &argc, &argv);
442 if (status != 0)
443 return status;
444 }
445
446 cfg->acc_cnt = 0;
447 cur_acc = &cfg->acc_cfg[0];
448
449
450 /* Reinitialize and re-run pj_getopt again, possibly with new arguments
451 * read from config file.
452 */
453 pj_optind = 0;
454 while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000455 pj_str_t tmp;
456 long lval;
457
458 switch (c) {
459
Benny Prijono6f137482006-06-15 11:31:36 +0000460 case OPT_CONFIG_FILE:
461 /* Ignore as this has been processed before */
462 break;
463
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000464 case OPT_LOG_FILE:
465 cfg->log_cfg.log_filename = pj_str(pj_optarg);
466 break;
467
468 case OPT_LOG_LEVEL:
469 c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
470 if (c < 0 || c > 6) {
471 PJ_LOG(1,(THIS_FILE,
472 "Error: expecting integer value 0-6 "
473 "for --log-level"));
474 return PJ_EINVAL;
475 }
476 cfg->log_cfg.level = c;
477 pj_log_set_level( c );
478 break;
479
480 case OPT_APP_LOG_LEVEL:
481 cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
482 if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
483 PJ_LOG(1,(THIS_FILE,
484 "Error: expecting integer value 0-6 "
485 "for --app-log-level"));
486 return PJ_EINVAL;
487 }
488 break;
489
490 case OPT_HELP:
491 usage();
492 return PJ_EINVAL;
493
494 case OPT_VERSION: /* version */
495 pj_dump_config();
496 return PJ_EINVAL;
497
498 case OPT_NULL_AUDIO:
499 cfg->null_audio = PJ_TRUE;
500 break;
501
502 case OPT_CLOCK_RATE:
503 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
504 if (lval < 8000 || lval > 48000) {
505 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
506 "8000-48000 for clock rate"));
507 return PJ_EINVAL;
508 }
509 cfg->media_cfg.clock_rate = lval;
510 break;
511
512 case OPT_LOCAL_PORT: /* local-port */
513 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
Benny Prijonoe347cb02007-02-14 14:36:13 +0000514 if (lval < 0 || lval > 65535) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000515 PJ_LOG(1,(THIS_FILE,
516 "Error: expecting integer value for "
517 "--local-port"));
518 return PJ_EINVAL;
519 }
520 cfg->udp_cfg.port = (pj_uint16_t)lval;
521 break;
522
Benny Prijono0a5cad82006-09-26 13:21:02 +0000523 case OPT_IP_ADDR: /* ip-addr */
524 cfg->udp_cfg.public_addr = pj_str(pj_optarg);
525 cfg->rtp_cfg.public_addr = pj_str(pj_optarg);
526 break;
527
Benny Prijonoe93e2872006-06-28 16:46:49 +0000528 case OPT_NO_UDP: /* no-udp */
529 if (cfg->no_tcp) {
530 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
531 return PJ_EINVAL;
532 }
533
534 cfg->no_udp = PJ_TRUE;
535 break;
536
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000537 case OPT_NOREFERSUB: /* norefersub */
538 cfg->no_refersub = PJ_TRUE;
539 break;
540
Benny Prijonoe93e2872006-06-28 16:46:49 +0000541 case OPT_NO_TCP: /* no-tcp */
542 if (cfg->no_udp) {
543 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
544 return PJ_EINVAL;
545 }
546
547 cfg->no_tcp = PJ_TRUE;
548 break;
549
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000550 case OPT_PROXY: /* proxy */
551 if (pjsua_verify_sip_url(pj_optarg) != 0) {
552 PJ_LOG(1,(THIS_FILE,
553 "Error: invalid SIP URL '%s' "
554 "in proxy argument", pj_optarg));
555 return PJ_EINVAL;
556 }
557 cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
558 break;
559
560 case OPT_OUTBOUND_PROXY: /* outbound proxy */
561 if (pjsua_verify_sip_url(pj_optarg) != 0) {
562 PJ_LOG(1,(THIS_FILE,
563 "Error: invalid SIP URL '%s' "
564 "in outbound proxy argument", pj_optarg));
565 return PJ_EINVAL;
566 }
567 cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
568 break;
569
570 case OPT_REGISTRAR: /* registrar */
571 if (pjsua_verify_sip_url(pj_optarg) != 0) {
572 PJ_LOG(1,(THIS_FILE,
573 "Error: invalid SIP URL '%s' in "
574 "registrar argument", pj_optarg));
575 return PJ_EINVAL;
576 }
577 cur_acc->reg_uri = pj_str(pj_optarg);
578 break;
579
580 case OPT_REG_TIMEOUT: /* reg-timeout */
581 cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
582 if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
583 PJ_LOG(1,(THIS_FILE,
584 "Error: invalid value for --reg-timeout "
585 "(expecting 1-3600)"));
586 return PJ_EINVAL;
587 }
588 break;
589
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000590 case OPT_PUBLISH: /* publish */
591 cur_acc->publish_enabled = PJ_TRUE;
592 break;
593
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000594 case OPT_ID: /* id */
595 if (pjsua_verify_sip_url(pj_optarg) != 0) {
596 PJ_LOG(1,(THIS_FILE,
597 "Error: invalid SIP URL '%s' "
598 "in local id argument", pj_optarg));
599 return PJ_EINVAL;
600 }
601 cur_acc->id = pj_str(pj_optarg);
602 break;
603
604 case OPT_CONTACT: /* contact */
605 if (pjsua_verify_sip_url(pj_optarg) != 0) {
606 PJ_LOG(1,(THIS_FILE,
607 "Error: invalid SIP URL '%s' "
608 "in contact argument", pj_optarg));
609 return PJ_EINVAL;
610 }
Benny Prijonob4a17c92006-07-10 14:40:21 +0000611 cur_acc->force_contact = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000612 break;
613
614 case OPT_NEXT_ACCOUNT: /* Add more account. */
615 cfg->acc_cnt++;
Benny Prijono56315612006-07-18 14:39:40 +0000616 cur_acc = &cfg->acc_cfg[cfg->acc_cnt];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000617 break;
618
619 case OPT_USERNAME: /* Default authentication user */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000620 cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
621 cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("digest");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000622 break;
623
624 case OPT_REALM: /* Default authentication realm. */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000625 cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000626 break;
627
628 case OPT_PASSWORD: /* authentication password */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000629 cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
630 cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
631 break;
632
633 case OPT_NEXT_CRED: /* next credential */
634 cur_acc->cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000635 break;
636
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000637 case OPT_NAMESERVER: /* nameserver */
638 cfg->cfg.nameserver[cfg->cfg.nameserver_count++] = pj_str(pj_optarg);
639 if (cfg->cfg.nameserver_count > PJ_ARRAY_SIZE(cfg->cfg.nameserver)) {
640 PJ_LOG(1,(THIS_FILE, "Error: too many nameservers"));
641 return PJ_ETOOMANY;
642 }
643 break;
644
Benny Prijonoebbf6892007-03-24 17:37:25 +0000645 case OPT_STUN_DOMAIN: /* STUN domain */
646 cfg->cfg.stun_domain = pj_str(pj_optarg);
647 break;
648
Benny Prijonoc97608e2007-03-23 16:34:20 +0000649 case OPT_STUN_SRV: /* STUN server */
Benny Prijonoebbf6892007-03-24 17:37:25 +0000650 cfg->cfg.stun_host = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000651 break;
652
653 case OPT_ADD_BUDDY: /* Add to buddy list. */
654 if (pjsua_verify_sip_url(pj_optarg) != 0) {
655 PJ_LOG(1,(THIS_FILE,
656 "Error: invalid URL '%s' in "
657 "--add-buddy option", pj_optarg));
658 return -1;
659 }
660 if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
661 PJ_LOG(1,(THIS_FILE,
662 "Error: too many buddies in buddy list."));
663 return -1;
664 }
665 cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
666 cfg->buddy_cnt++;
667 break;
668
669 case OPT_AUTO_PLAY:
670 cfg->auto_play = 1;
671 break;
672
Benny Prijono1ebd6142006-10-19 15:48:02 +0000673 case OPT_AUTO_REC:
674 cfg->auto_rec = 1;
675 break;
676
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000677 case OPT_AUTO_LOOP:
678 cfg->auto_loop = 1;
679 break;
680
Benny Prijono7ca96da2006-08-07 12:11:40 +0000681 case OPT_AUTO_CONF:
682 cfg->auto_conf = 1;
683 break;
684
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000685 case OPT_PLAY_FILE:
Benny Prijono32e4f492007-01-24 00:44:26 +0000686 cfg->wav_files[cfg->wav_count++] = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000687 break;
688
Benny Prijono4af234b2007-01-24 02:02:09 +0000689 case OPT_PLAY_TONE:
690 {
691 int f1, f2, on, off;
692 int n;
693
694 n = sscanf(pj_optarg, "%d,%d,%d,%d", &f1, &f2, &on, &off);
695 if (n != 4) {
696 puts("Expecting f1,f2,on,off in --play-tone");
697 return -1;
698 }
699
700 cfg->tones[cfg->tone_count].freq1 = (short)f1;
701 cfg->tones[cfg->tone_count].freq2 = (short)f2;
702 cfg->tones[cfg->tone_count].on_msec = (short)on;
703 cfg->tones[cfg->tone_count].off_msec = (short)off;
704 ++cfg->tone_count;
705 }
706 break;
707
Benny Prijono1ebd6142006-10-19 15:48:02 +0000708 case OPT_REC_FILE:
709 cfg->rec_file = pj_str(pj_optarg);
710 break;
711
Benny Prijonoc97608e2007-03-23 16:34:20 +0000712 case OPT_USE_ICE:
713 cfg->media_cfg.enable_ice = PJ_TRUE;
714 break;
715
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000716 case OPT_RTP_PORT:
717 cfg->rtp_cfg.port = my_atoi(pj_optarg);
718 if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
719 PJ_LOG(1,(THIS_FILE,
720 "Error: rtp-port argument value "
721 "(expecting 1-65535"));
722 return -1;
723 }
724 break;
725
726 case OPT_ADD_CODEC:
727 cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
728 break;
729
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000730 /* These options were no longer valid after new pjsua */
731 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000732 case OPT_COMPLEXITY:
733 cfg->complexity = my_atoi(pj_optarg);
734 if (cfg->complexity < 0 || cfg->complexity > 10) {
735 PJ_LOG(1,(THIS_FILE,
736 "Error: invalid --complexity (expecting 0-10"));
737 return -1;
738 }
739 break;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000740 */
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000741
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000742 case OPT_DURATION:
743 cfg->duration = my_atoi(pj_optarg);
744 break;
745
Benny Prijonof521eb02006-08-06 23:07:25 +0000746 case OPT_THREAD_CNT:
747 cfg->cfg.thread_cnt = my_atoi(pj_optarg);
748 if (cfg->cfg.thread_cnt > 128) {
749 PJ_LOG(1,(THIS_FILE,
750 "Error: invalid --thread-cnt option"));
751 return -1;
752 }
753 break;
754
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000755 case OPT_PTIME:
Benny Prijono0a12f002006-07-26 17:05:39 +0000756 cfg->media_cfg.ptime = my_atoi(pj_optarg);
757 if (cfg->media_cfg.ptime < 10 || cfg->media_cfg.ptime > 1000) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000758 PJ_LOG(1,(THIS_FILE,
759 "Error: invalid --ptime option"));
760 return -1;
761 }
762 break;
763
Benny Prijono0a12f002006-07-26 17:05:39 +0000764 case OPT_NO_VAD:
765 cfg->media_cfg.no_vad = PJ_TRUE;
766 break;
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000767
Benny Prijonod79f25c2006-08-02 19:41:37 +0000768 case OPT_EC_TAIL:
769 cfg->media_cfg.ec_tail_len = my_atoi(pj_optarg);
770 if (cfg->media_cfg.ec_tail_len > 1000) {
771 PJ_LOG(1,(THIS_FILE, "I think the ec-tail length setting "
772 "is too big"));
773 return -1;
774 }
775 break;
776
Benny Prijono0498d902006-06-19 14:49:14 +0000777 case OPT_QUALITY:
778 cfg->media_cfg.quality = my_atoi(pj_optarg);
779 if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
780 PJ_LOG(1,(THIS_FILE,
781 "Error: invalid --quality (expecting 0-10"));
782 return -1;
783 }
784 break;
785
Benny Prijono00cae612006-07-31 15:19:36 +0000786 case OPT_ILBC_MODE:
787 cfg->media_cfg.ilbc_mode = my_atoi(pj_optarg);
788 if (cfg->media_cfg.ilbc_mode!=20 && cfg->media_cfg.ilbc_mode!=30) {
789 PJ_LOG(1,(THIS_FILE,
790 "Error: invalid --ilbc-mode (expecting 20 or 30"));
791 return -1;
792 }
793 break;
794
795 case OPT_RX_DROP_PCT:
796 cfg->media_cfg.rx_drop_pct = my_atoi(pj_optarg);
797 if (cfg->media_cfg.rx_drop_pct > 100) {
798 PJ_LOG(1,(THIS_FILE,
799 "Error: invalid --rx-drop-pct (expecting <= 100"));
800 return -1;
801 }
802 break;
803
804 case OPT_TX_DROP_PCT:
805 cfg->media_cfg.tx_drop_pct = my_atoi(pj_optarg);
806 if (cfg->media_cfg.tx_drop_pct > 100) {
807 PJ_LOG(1,(THIS_FILE,
808 "Error: invalid --tx-drop-pct (expecting <= 100"));
809 return -1;
810 }
811 break;
812
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000813 case OPT_AUTO_ANSWER:
814 cfg->auto_answer = my_atoi(pj_optarg);
815 if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
816 PJ_LOG(1,(THIS_FILE,
817 "Error: invalid code in --auto-answer "
818 "(expecting 100-699"));
819 return -1;
820 }
821 break;
822
823 case OPT_MAX_CALLS:
824 cfg->cfg.max_calls = my_atoi(pj_optarg);
Benny Prijono48af79c2006-07-22 12:49:17 +0000825 if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > PJSUA_MAX_CALLS) {
Benny Prijono804ff0a2006-09-14 11:17:48 +0000826 PJ_LOG(1,(THIS_FILE,"Error: maximum call setting exceeds "
827 "compile time limit (PJSUA_MAX_CALLS=%d)",
Benny Prijono48af79c2006-07-22 12:49:17 +0000828 PJSUA_MAX_CALLS));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000829 return -1;
830 }
831 break;
832
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000833 case OPT_USE_TLS:
834 cfg->use_tls = PJ_TRUE;
Benny Prijonof3bbc132006-12-25 06:43:59 +0000835#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
836 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
837 return -1;
838#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000839 break;
840
841 case OPT_TLS_CA_FILE:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000842 cfg->udp_cfg.tls_setting.ca_list_file = pj_str(pj_optarg);
843#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
844 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
845 return -1;
846#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000847 break;
848
Benny Prijonof3bbc132006-12-25 06:43:59 +0000849 case OPT_TLS_CERT_FILE:
850 cfg->udp_cfg.tls_setting.cert_file = pj_str(pj_optarg);
851#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
852 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
853 return -1;
854#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000855 break;
856
Benny Prijonof3bbc132006-12-25 06:43:59 +0000857 case OPT_TLS_PRIV_FILE:
858 cfg->udp_cfg.tls_setting.privkey_file = pj_str(pj_optarg);
859 break;
860
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000861 case OPT_TLS_PASSWORD:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000862 cfg->udp_cfg.tls_setting.password = pj_str(pj_optarg);
863#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
864 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
865 return -1;
866#endif
867 break;
868
869 case OPT_TLS_VERIFY_SERVER:
870 cfg->udp_cfg.tls_setting.verify_server = PJ_TRUE;
871 break;
872
873 case OPT_TLS_VERIFY_CLIENT:
874 cfg->udp_cfg.tls_setting.verify_client = PJ_TRUE;
875 break;
876
877 case OPT_TLS_NEG_TIMEOUT:
878 cfg->udp_cfg.tls_setting.timeout.sec = atoi(pj_optarg);
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000879 break;
880
Benny Prijono4e5d5512007-03-06 18:11:30 +0000881 case OPT_CAPTURE_DEV:
882 cfg->capture_dev = atoi(pj_optarg);
883 break;
884
885 case OPT_PLAYBACK_DEV:
886 cfg->playback_dev = atoi(pj_optarg);
887 break;
888
Benny Prijono22a300a2006-06-14 20:04:55 +0000889 default:
Benny Prijono787b8692006-06-19 12:40:03 +0000890 PJ_LOG(1,(THIS_FILE,
Benny Prijonod6388ac2006-09-09 13:23:09 +0000891 "Argument \"%s\" is not valid. Use --help to see help",
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000892 argv[pj_optind-1]));
Benny Prijono22a300a2006-06-14 20:04:55 +0000893 return -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000894 }
895 }
896
897 if (pj_optind != argc) {
898 pj_str_t uri_arg;
899
900 if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
901 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
902 return -1;
903 }
904 uri_arg = pj_str(argv[pj_optind]);
905 if (uri_to_call)
906 *uri_to_call = uri_arg;
907 pj_optind++;
908
909 /* Add URI to call to buddy list if it's not already there */
910 for (i=0; i<cfg->buddy_cnt; ++i) {
911 if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
912 break;
913 }
914 if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
915 cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
916 }
917
918 } else {
919 if (uri_to_call)
920 uri_to_call->slen = 0;
921 }
922
923 if (pj_optind != argc) {
924 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
925 return PJ_EINVAL;
926 }
927
Benny Prijono56315612006-07-18 14:39:40 +0000928 if (cfg->acc_cfg[cfg->acc_cnt].id.slen)
929 cfg->acc_cnt++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000930
931 for (i=0; i<cfg->acc_cnt; ++i) {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000932 if (cfg->acc_cfg[i].cred_info[cfg->acc_cfg[i].cred_count].username.slen)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000933 {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000934 cfg->acc_cfg[i].cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000935 }
936 }
937
938
939 return PJ_SUCCESS;
940}
941
942
943/*
944 * Save account settings
945 */
946static void write_account_settings(int acc_index, pj_str_t *result)
947{
948 unsigned i;
949 char line[128];
950 pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
951
952
953 pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
954 pj_strcat2(result, line);
955
956
957 /* Identity */
958 if (acc_cfg->id.slen) {
959 pj_ansi_sprintf(line, "--id %.*s\n",
960 (int)acc_cfg->id.slen,
961 acc_cfg->id.ptr);
962 pj_strcat2(result, line);
963 }
964
965 /* Registrar server */
966 if (acc_cfg->reg_uri.slen) {
967 pj_ansi_sprintf(line, "--registrar %.*s\n",
968 (int)acc_cfg->reg_uri.slen,
969 acc_cfg->reg_uri.ptr);
970 pj_strcat2(result, line);
971
972 pj_ansi_sprintf(line, "--reg-timeout %u\n",
973 acc_cfg->reg_timeout);
974 pj_strcat2(result, line);
975 }
976
977 /* Contact */
Benny Prijonob4a17c92006-07-10 14:40:21 +0000978 if (acc_cfg->force_contact.slen) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000979 pj_ansi_sprintf(line, "--contact %.*s\n",
Benny Prijonob4a17c92006-07-10 14:40:21 +0000980 (int)acc_cfg->force_contact.slen,
981 acc_cfg->force_contact.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000982 pj_strcat2(result, line);
983 }
984
985 /* Proxy */
986 for (i=0; i<acc_cfg->proxy_cnt; ++i) {
987 pj_ansi_sprintf(line, "--proxy %.*s\n",
988 (int)acc_cfg->proxy[i].slen,
989 acc_cfg->proxy[i].ptr);
990 pj_strcat2(result, line);
991 }
992
993 /* Credentials */
994 for (i=0; i<acc_cfg->cred_count; ++i) {
995 if (acc_cfg->cred_info[i].realm.slen) {
996 pj_ansi_sprintf(line, "--realm %.*s\n",
997 (int)acc_cfg->cred_info[i].realm.slen,
998 acc_cfg->cred_info[i].realm.ptr);
999 pj_strcat2(result, line);
1000 }
1001
1002 if (acc_cfg->cred_info[i].username.slen) {
1003 pj_ansi_sprintf(line, "--username %.*s\n",
1004 (int)acc_cfg->cred_info[i].username.slen,
1005 acc_cfg->cred_info[i].username.ptr);
1006 pj_strcat2(result, line);
1007 }
1008
1009 if (acc_cfg->cred_info[i].data.slen) {
1010 pj_ansi_sprintf(line, "--password %.*s\n",
1011 (int)acc_cfg->cred_info[i].data.slen,
1012 acc_cfg->cred_info[i].data.ptr);
1013 pj_strcat2(result, line);
1014 }
Benny Prijonoeef4f8c2006-06-19 12:07:44 +00001015
1016 if (i != acc_cfg->cred_count - 1)
1017 pj_strcat2(result, "--next-cred\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001018 }
1019
1020}
1021
1022
1023/*
1024 * Write settings.
1025 */
1026static int write_settings(const struct app_config *config,
1027 char *buf, pj_size_t max)
1028{
1029 unsigned acc_index;
1030 unsigned i;
1031 pj_str_t cfg;
1032 char line[128];
1033
1034 PJ_UNUSED_ARG(max);
1035
1036 cfg.ptr = buf;
1037 cfg.slen = 0;
1038
1039 /* Logging. */
1040 pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
1041 pj_ansi_sprintf(line, "--log-level %d\n",
1042 config->log_cfg.level);
1043 pj_strcat2(&cfg, line);
1044
1045 pj_ansi_sprintf(line, "--app-log-level %d\n",
1046 config->log_cfg.console_level);
1047 pj_strcat2(&cfg, line);
1048
1049 if (config->log_cfg.log_filename.slen) {
1050 pj_ansi_sprintf(line, "--log-file %.*s\n",
1051 (int)config->log_cfg.log_filename.slen,
1052 config->log_cfg.log_filename.ptr);
1053 pj_strcat2(&cfg, line);
1054 }
1055
1056
1057 /* Save account settings. */
1058 for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
1059
1060 write_account_settings(acc_index, &cfg);
1061
1062 if (acc_index < config->acc_cnt-1)
1063 pj_strcat2(&cfg, "--next-account\n");
1064 }
1065
1066
1067 pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
1068
1069 /* Outbound proxy */
1070 for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
1071 pj_ansi_sprintf(line, "--outbound %.*s\n",
1072 (int)config->cfg.outbound_proxy[i].slen,
1073 config->cfg.outbound_proxy[i].ptr);
1074 pj_strcat2(&cfg, line);
1075 }
1076
1077
1078 /* UDP Transport. */
1079 pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
1080 pj_strcat2(&cfg, line);
1081
Benny Prijono0a5cad82006-09-26 13:21:02 +00001082 /* IP address, if any. */
1083 if (config->udp_cfg.public_addr.slen) {
1084 pj_ansi_sprintf(line, "--ip-addr %.*s\n",
1085 (int)config->udp_cfg.public_addr.slen,
1086 config->udp_cfg.public_addr.ptr);
1087 pj_strcat2(&cfg, line);
1088 }
1089
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001090 /* No TCP ? */
1091 if (config->no_tcp) {
1092 pj_strcat2(&cfg, "--no-tcp\n");
1093 }
1094
1095 /* No UDP ? */
1096 if (config->no_udp) {
1097 pj_strcat2(&cfg, "--no-udp\n");
1098 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001099
1100 /* STUN */
Benny Prijonoebbf6892007-03-24 17:37:25 +00001101 if (config->cfg.stun_domain.slen) {
1102 pj_ansi_sprintf(line, "--stun-domain %.*s\n",
1103 (int)config->cfg.stun_domain.slen,
1104 config->cfg.stun_domain.ptr);
1105 pj_strcat2(&cfg, line);
1106 }
1107 if (config->cfg.stun_host.slen) {
Benny Prijonoc97608e2007-03-23 16:34:20 +00001108 pj_ansi_sprintf(line, "--stun-srv %.*s\n",
Benny Prijonoebbf6892007-03-24 17:37:25 +00001109 (int)config->cfg.stun_host.slen,
1110 config->cfg.stun_host.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001111 pj_strcat2(&cfg, line);
1112 }
1113
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001114
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001115 /* TLS */
1116 if (config->use_tls)
1117 pj_strcat2(&cfg, "--use-tls\n");
Benny Prijonof3bbc132006-12-25 06:43:59 +00001118 if (config->udp_cfg.tls_setting.ca_list_file.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001119 pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001120 (int)config->udp_cfg.tls_setting.ca_list_file.slen,
1121 config->udp_cfg.tls_setting.ca_list_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001122 pj_strcat2(&cfg, line);
1123 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001124 if (config->udp_cfg.tls_setting.cert_file.slen) {
1125 pj_ansi_sprintf(line, "--tls-cert-file %.*s\n",
1126 (int)config->udp_cfg.tls_setting.cert_file.slen,
1127 config->udp_cfg.tls_setting.cert_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001128 pj_strcat2(&cfg, line);
1129 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001130 if (config->udp_cfg.tls_setting.privkey_file.slen) {
1131 pj_ansi_sprintf(line, "--tls-privkey-file %.*s\n",
1132 (int)config->udp_cfg.tls_setting.privkey_file.slen,
1133 config->udp_cfg.tls_setting.privkey_file.ptr);
1134 pj_strcat2(&cfg, line);
1135 }
1136
1137 if (config->udp_cfg.tls_setting.password.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001138 pj_ansi_sprintf(line, "--tls-password %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001139 (int)config->udp_cfg.tls_setting.password.slen,
1140 config->udp_cfg.tls_setting.password.ptr);
1141 pj_strcat2(&cfg, line);
1142 }
1143
1144 if (config->udp_cfg.tls_setting.verify_server)
1145 pj_strcat2(&cfg, "--tls-verify-server\n");
1146
1147 if (config->udp_cfg.tls_setting.verify_client)
1148 pj_strcat2(&cfg, "--tls-verify-client\n");
1149
1150 if (config->udp_cfg.tls_setting.timeout.sec) {
1151 pj_ansi_sprintf(line, "--tls-neg-timeout %d\n",
Benny Prijonoe960bb52007-01-21 17:53:39 +00001152 (int)config->udp_cfg.tls_setting.timeout.sec);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001153 pj_strcat2(&cfg, line);
1154 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001155
1156 pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
1157
1158
1159 /* Media */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001160 if (config->media_cfg.enable_ice)
1161 pj_strcat2(&cfg, "--use-ice\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001162 if (config->null_audio)
1163 pj_strcat2(&cfg, "--null-audio\n");
1164 if (config->auto_play)
1165 pj_strcat2(&cfg, "--auto-play\n");
1166 if (config->auto_loop)
1167 pj_strcat2(&cfg, "--auto-loop\n");
Benny Prijono7ca96da2006-08-07 12:11:40 +00001168 if (config->auto_conf)
1169 pj_strcat2(&cfg, "--auto-conf\n");
Benny Prijono32e4f492007-01-24 00:44:26 +00001170 for (i=0; i<config->wav_count; ++i) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001171 pj_ansi_sprintf(line, "--play-file %s\n",
Benny Prijono32e4f492007-01-24 00:44:26 +00001172 config->wav_files[i].ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001173 pj_strcat2(&cfg, line);
1174 }
Benny Prijono4af234b2007-01-24 02:02:09 +00001175 for (i=0; i<config->tone_count; ++i) {
1176 pj_ansi_sprintf(line, "--play-tone %d,%d,%d,%d\n",
1177 config->tones[i].freq1, config->tones[i].freq2,
1178 config->tones[i].on_msec, config->tones[i].off_msec);
1179 pj_strcat2(&cfg, line);
1180 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001181 if (config->rec_file.slen) {
1182 pj_ansi_sprintf(line, "--rec-file %s\n",
1183 config->rec_file.ptr);
1184 pj_strcat2(&cfg, line);
1185 }
1186 if (config->auto_rec)
1187 pj_strcat2(&cfg, "--auto-rec\n");
Benny Prijono4e5d5512007-03-06 18:11:30 +00001188 if (config->capture_dev != PJSUA_INVALID_ID) {
1189 pj_ansi_sprintf(line, "--capture-dev %d\n", config->capture_dev);
1190 pj_strcat2(&cfg, line);
1191 }
1192 if (config->playback_dev != PJSUA_INVALID_ID) {
1193 pj_ansi_sprintf(line, "--playback-dev %d\n", config->playback_dev);
1194 pj_strcat2(&cfg, line);
1195 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001196
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001197 /* Media clock rate. */
Benny Prijono70972992006-08-05 11:13:58 +00001198 if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001199 pj_ansi_sprintf(line, "--clock-rate %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001200 config->media_cfg.clock_rate);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001201 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001202 } else {
1203 pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
1204 config->media_cfg.clock_rate);
1205 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001206 }
Benny Prijono70972992006-08-05 11:13:58 +00001207
1208 /* quality */
1209 if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001210 pj_ansi_sprintf(line, "--quality %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001211 config->media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001212 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001213 } else {
1214 pj_ansi_sprintf(line, "#using default --quality %d\n",
1215 config->media_cfg.quality);
1216 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001217 }
Benny Prijono0498d902006-06-19 14:49:14 +00001218
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001219
1220 /* ptime */
Benny Prijono2adfe292007-05-11 10:36:08 +00001221 if (config->media_cfg.ptime) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001222 pj_ansi_sprintf(line, "--ptime %d\n",
Benny Prijono2adfe292007-05-11 10:36:08 +00001223 config->media_cfg.ptime);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001224 pj_strcat2(&cfg, line);
1225 }
1226
Benny Prijono70972992006-08-05 11:13:58 +00001227 /* no-vad */
1228 if (config->media_cfg.no_vad) {
1229 pj_strcat2(&cfg, "--no-vad\n");
1230 }
1231
1232 /* ec-tail */
1233 if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
1234 pj_ansi_sprintf(line, "--ec-tail %d\n",
1235 config->media_cfg.ec_tail_len);
1236 pj_strcat2(&cfg, line);
1237 } else {
1238 pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
1239 config->media_cfg.ec_tail_len);
1240 pj_strcat2(&cfg, line);
1241 }
1242
1243
1244 /* ilbc-mode */
1245 if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
1246 pj_ansi_sprintf(line, "--ilbc-mode %d\n",
1247 config->media_cfg.ilbc_mode);
1248 pj_strcat2(&cfg, line);
1249 } else {
1250 pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
1251 config->media_cfg.ilbc_mode);
1252 pj_strcat2(&cfg, line);
1253 }
1254
1255 /* RTP drop */
1256 if (config->media_cfg.tx_drop_pct) {
1257 pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
1258 config->media_cfg.tx_drop_pct);
1259 pj_strcat2(&cfg, line);
1260
1261 }
1262 if (config->media_cfg.rx_drop_pct) {
1263 pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
1264 config->media_cfg.rx_drop_pct);
1265 pj_strcat2(&cfg, line);
1266
1267 }
1268
1269
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001270 /* Start RTP port. */
1271 pj_ansi_sprintf(line, "--rtp-port %d\n",
1272 config->rtp_cfg.port);
1273 pj_strcat2(&cfg, line);
1274
1275 /* Add codec. */
1276 for (i=0; i<config->codec_cnt; ++i) {
1277 pj_ansi_sprintf(line, "--add-codec %s\n",
1278 config->codec_arg[i].ptr);
1279 pj_strcat2(&cfg, line);
1280 }
1281
1282 pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
1283
1284 /* Auto-answer. */
1285 if (config->auto_answer != 0) {
1286 pj_ansi_sprintf(line, "--auto-answer %d\n",
1287 config->auto_answer);
1288 pj_strcat2(&cfg, line);
1289 }
1290
1291 /* Max calls. */
1292 pj_ansi_sprintf(line, "--max-calls %d\n",
1293 config->cfg.max_calls);
1294 pj_strcat2(&cfg, line);
1295
1296 /* Uas-duration. */
Benny Prijono804ff0a2006-09-14 11:17:48 +00001297 if (config->duration != NO_LIMIT) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001298 pj_ansi_sprintf(line, "--duration %d\n",
1299 config->duration);
1300 pj_strcat2(&cfg, line);
1301 }
1302
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001303 /* norefersub ? */
1304 if (config->no_refersub) {
1305 pj_strcat2(&cfg, "--norefersub\n");
1306 }
1307
1308
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001309 pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
1310
1311 /* Add buddies. */
1312 for (i=0; i<config->buddy_cnt; ++i) {
1313 pj_ansi_sprintf(line, "--add-buddy %.*s\n",
1314 (int)config->buddy_cfg[i].uri.slen,
1315 config->buddy_cfg[i].uri.ptr);
1316 pj_strcat2(&cfg, line);
1317 }
1318
1319
1320 *(cfg.ptr + cfg.slen) = '\0';
1321 return cfg.slen;
1322}
1323
1324
1325/*
1326 * Dump application states.
1327 */
1328static void app_dump(pj_bool_t detail)
1329{
Benny Prijonoda9785b2007-04-02 20:43:06 +00001330 pjsua_dump(detail);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001331}
1332
1333
1334/*****************************************************************************
1335 * Console application
1336 */
1337
1338/*
1339 * Find next call when current call is disconnected or when user
1340 * press ']'
1341 */
1342static pj_bool_t find_next_call(void)
1343{
1344 int i, max;
1345
1346 max = pjsua_call_get_max_count();
1347 for (i=current_call+1; i<max; ++i) {
1348 if (pjsua_call_is_active(i)) {
1349 current_call = i;
1350 return PJ_TRUE;
1351 }
1352 }
1353
1354 for (i=0; i<current_call; ++i) {
1355 if (pjsua_call_is_active(i)) {
1356 current_call = i;
1357 return PJ_TRUE;
1358 }
1359 }
1360
1361 current_call = PJSUA_INVALID_ID;
1362 return PJ_FALSE;
1363}
1364
1365
1366/*
1367 * Find previous call when user press '['
1368 */
1369static pj_bool_t find_prev_call(void)
1370{
1371 int i, max;
1372
1373 max = pjsua_call_get_max_count();
1374 for (i=current_call-1; i>=0; --i) {
1375 if (pjsua_call_is_active(i)) {
1376 current_call = i;
1377 return PJ_TRUE;
1378 }
1379 }
1380
1381 for (i=max-1; i>current_call; --i) {
1382 if (pjsua_call_is_active(i)) {
1383 current_call = i;
1384 return PJ_TRUE;
1385 }
1386 }
1387
1388 current_call = PJSUA_INVALID_ID;
1389 return PJ_FALSE;
1390}
1391
1392
Benny Prijono804ff0a2006-09-14 11:17:48 +00001393/* Callback from timer when the maximum call duration has been
1394 * exceeded.
1395 */
1396static void call_timeout_callback(pj_timer_heap_t *timer_heap,
1397 struct pj_timer_entry *entry)
1398{
1399 pjsua_call_id call_id = entry->id;
1400 pjsua_msg_data msg_data;
1401 pjsip_generic_string_hdr warn;
1402 pj_str_t hname = pj_str("Warning");
1403 pj_str_t hvalue = pj_str("399 pjsua \"Call duration exceeded\"");
1404
1405 PJ_UNUSED_ARG(timer_heap);
1406
Benny Prijono148c9dd2006-09-19 13:37:53 +00001407 if (call_id == PJSUA_INVALID_ID) {
1408 PJ_LOG(1,(THIS_FILE, "Invalid call ID in timer callback"));
1409 return;
1410 }
1411
Benny Prijono804ff0a2006-09-14 11:17:48 +00001412 /* Add warning header */
1413 pjsua_msg_data_init(&msg_data);
1414 pjsip_generic_string_hdr_init2(&warn, &hname, &hvalue);
1415 pj_list_push_back(&msg_data.hdr_list, &warn);
1416
1417 /* Call duration has been exceeded; disconnect the call */
1418 PJ_LOG(3,(THIS_FILE, "Duration (%d seconds) has been exceeded "
1419 "for call %d, disconnecting the call",
1420 app_config.duration, call_id));
1421 entry->id = PJSUA_INVALID_ID;
1422 pjsua_call_hangup(call_id, 200, NULL, &msg_data);
1423}
1424
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001425
1426/*
1427 * Handler when invite state has changed.
1428 */
1429static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
1430{
1431 pjsua_call_info call_info;
1432
1433 PJ_UNUSED_ARG(e);
1434
1435 pjsua_call_get_info(call_id, &call_info);
1436
1437 if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
1438
Benny Prijono804ff0a2006-09-14 11:17:48 +00001439 /* Cancel duration timer, if any */
1440 if (app_config.call_data[call_id].timer.id != PJSUA_INVALID_ID) {
1441 struct call_data *cd = &app_config.call_data[call_id];
1442 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1443
1444 cd->timer.id = PJSUA_INVALID_ID;
1445 pjsip_endpt_cancel_timer(endpt, &cd->timer);
1446 }
1447
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001448 PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",
1449 call_id,
1450 call_info.last_status,
1451 call_info.last_status_text.ptr));
1452
1453 if (call_id == current_call) {
1454 find_next_call();
1455 }
1456
Benny Prijono4be63b52006-11-25 14:50:25 +00001457 /* Dump media state upon disconnected */
1458 if (1) {
1459 char buf[1024];
1460 pjsua_call_dump(call_id, PJ_TRUE, buf,
1461 sizeof(buf), " ");
1462 PJ_LOG(5,(THIS_FILE,
1463 "Call %d disconnected, dumping media stats\n%s",
1464 call_id, buf));
1465 }
1466
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001467 } else {
1468
Benny Prijono804ff0a2006-09-14 11:17:48 +00001469 if (app_config.duration!=NO_LIMIT &&
1470 call_info.state == PJSIP_INV_STATE_CONFIRMED)
1471 {
1472 /* Schedule timer to hangup call after the specified duration */
1473 struct call_data *cd = &app_config.call_data[call_id];
1474 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1475 pj_time_val delay;
1476
1477 cd->timer.id = call_id;
1478 delay.sec = app_config.duration;
1479 delay.msec = 0;
1480 pjsip_endpt_schedule_timer(endpt, &cd->timer, &delay);
1481 }
1482
Benny Prijono4be63b52006-11-25 14:50:25 +00001483 if (call_info.state == PJSIP_INV_STATE_EARLY) {
1484 int code;
1485 pj_str_t reason;
1486 pjsip_msg *msg;
1487
1488 /* This can only occur because of TX or RX message */
1489 pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
1490
1491 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
1492 msg = e->body.tsx_state.src.rdata->msg_info.msg;
1493 } else {
1494 msg = e->body.tsx_state.src.tdata->msg;
1495 }
1496
1497 code = msg->line.status.code;
1498 reason = msg->line.status.reason;
1499
1500 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)",
1501 call_id, call_info.state_text.ptr,
1502 code, (int)reason.slen, reason.ptr));
1503 } else {
1504 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s",
1505 call_id,
1506 call_info.state_text.ptr));
1507 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001508
1509 if (current_call==PJSUA_INVALID_ID)
1510 current_call = call_id;
1511
1512 }
1513}
1514
1515
1516/**
1517 * Handler when there is incoming call.
1518 */
1519static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
1520 pjsip_rx_data *rdata)
1521{
1522 pjsua_call_info call_info;
1523
1524 PJ_UNUSED_ARG(acc_id);
1525 PJ_UNUSED_ARG(rdata);
1526
1527 pjsua_call_get_info(call_id, &call_info);
1528
1529 if (app_config.auto_answer > 0) {
1530 pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL);
1531 }
1532
1533 if (app_config.auto_answer < 200) {
1534 PJ_LOG(3,(THIS_FILE,
1535 "Incoming call for account %d!\n"
1536 "From: %s\n"
1537 "To: %s\n"
1538 "Press a to answer or h to reject call",
1539 acc_id,
1540 call_info.remote_info.ptr,
1541 call_info.local_info.ptr));
1542 }
1543}
1544
1545
1546/*
1547 * Callback on media state changed event.
1548 * The action may connect the call to sound device, to file, or
1549 * to loop the call.
1550 */
1551static void on_call_media_state(pjsua_call_id call_id)
1552{
1553 pjsua_call_info call_info;
1554
1555 pjsua_call_get_info(call_id, &call_info);
1556
1557 if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
1558 pj_bool_t connect_sound = PJ_TRUE;
1559
1560 /* Loopback sound, if desired */
1561 if (app_config.auto_loop) {
1562 pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
1563 connect_sound = PJ_FALSE;
Benny Prijono1ebd6142006-10-19 15:48:02 +00001564
1565 /* Automatically record conversation, if desired */
1566 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1567 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1568 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001569 }
1570
1571 /* Stream a file, if desired */
1572 if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) {
1573 pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
1574 connect_sound = PJ_FALSE;
1575 }
1576
Benny Prijono7ca96da2006-08-07 12:11:40 +00001577 /* Put call in conference with other calls, if desired */
1578 if (app_config.auto_conf) {
1579 pjsua_call_id call_ids[PJSUA_MAX_CALLS];
Benny Prijono10861bc2006-08-07 13:22:43 +00001580 unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
Benny Prijono7ca96da2006-08-07 12:11:40 +00001581 unsigned i;
1582
1583 /* Get all calls, and establish media connection between
1584 * this call and other calls.
1585 */
1586 pjsua_enum_calls(call_ids, &call_cnt);
Benny Prijono10861bc2006-08-07 13:22:43 +00001587
Benny Prijono7ca96da2006-08-07 12:11:40 +00001588 for (i=0; i<call_cnt; ++i) {
1589 if (call_ids[i] == call_id)
1590 continue;
1591
1592 if (!pjsua_call_has_media(call_ids[i]))
1593 continue;
1594
1595 pjsua_conf_connect(call_info.conf_slot,
1596 pjsua_call_get_conf_port(call_ids[i]));
1597 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1598 call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001599
1600 /* Automatically record conversation, if desired */
1601 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1602 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1603 app_config.rec_port);
1604 }
1605
Benny Prijono7ca96da2006-08-07 12:11:40 +00001606 }
1607
1608 /* Also connect call to local sound device */
1609 connect_sound = PJ_TRUE;
1610 }
1611
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001612 /* Otherwise connect to sound device */
1613 if (connect_sound) {
1614 pjsua_conf_connect(call_info.conf_slot, 0);
1615 pjsua_conf_connect(0, call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001616
1617 /* Automatically record conversation, if desired */
1618 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1619 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1620 pjsua_conf_connect(0, app_config.rec_port);
1621 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001622 }
1623
1624 PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
1625
1626 } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) {
1627 PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
1628 call_id));
1629 } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) {
1630 PJ_LOG(3,(THIS_FILE,
1631 "Media for call %d is suspended (hold) by remote",
1632 call_id));
1633 } else {
1634 PJ_LOG(3,(THIS_FILE,
1635 "Media for call %d is inactive",
1636 call_id));
1637 }
1638}
1639
Benny Prijono0875ae82006-12-26 00:11:48 +00001640/*
1641 * DTMF callback.
1642 */
1643static void call_on_dtmf_callback(pjsua_call_id call_id, int dtmf)
1644{
1645 PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, dtmf));
1646}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001647
1648/*
1649 * Handler registration status has changed.
1650 */
1651static void on_reg_state(pjsua_acc_id acc_id)
1652{
1653 PJ_UNUSED_ARG(acc_id);
1654
1655 // Log already written.
1656}
1657
1658
1659/*
1660 * Handler on buddy state changed.
1661 */
1662static void on_buddy_state(pjsua_buddy_id buddy_id)
1663{
1664 pjsua_buddy_info info;
1665 pjsua_buddy_get_info(buddy_id, &info);
1666
1667 PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
1668 (int)info.uri.slen,
1669 info.uri.ptr,
1670 (int)info.status_text.slen,
1671 info.status_text.ptr));
1672}
1673
1674
1675/**
1676 * Incoming IM message (i.e. MESSAGE request)!
1677 */
1678static void on_pager(pjsua_call_id call_id, const pj_str_t *from,
1679 const pj_str_t *to, const pj_str_t *contact,
1680 const pj_str_t *mime_type, const pj_str_t *text)
1681{
1682 /* Note: call index may be -1 */
1683 PJ_UNUSED_ARG(call_id);
1684 PJ_UNUSED_ARG(to);
1685 PJ_UNUSED_ARG(contact);
1686 PJ_UNUSED_ARG(mime_type);
1687
Benny Prijonof4b538d2007-05-14 16:45:20 +00001688 PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s (%.*s)",
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001689 (int)from->slen, from->ptr,
Benny Prijonof4b538d2007-05-14 16:45:20 +00001690 (int)text->slen, text->ptr,
1691 (int)mime_type->slen, mime_type->ptr));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001692}
1693
1694
1695/**
1696 * Received typing indication
1697 */
1698static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
1699 const pj_str_t *to, const pj_str_t *contact,
1700 pj_bool_t is_typing)
1701{
1702 PJ_UNUSED_ARG(call_id);
1703 PJ_UNUSED_ARG(to);
1704 PJ_UNUSED_ARG(contact);
1705
1706 PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
1707 (int)from->slen, from->ptr,
1708 (is_typing?"is typing..":"has stopped typing")));
1709}
1710
1711
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001712/**
1713 * Call transfer request status.
1714 */
1715static void on_call_transfer_status(pjsua_call_id call_id,
1716 int status_code,
1717 const pj_str_t *status_text,
1718 pj_bool_t final,
1719 pj_bool_t *p_cont)
1720{
1721 PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s",
1722 call_id, status_code,
1723 (int)status_text->slen, status_text->ptr,
1724 (final ? "[final]" : "")));
1725
1726 if (status_code/100 == 2) {
1727 PJ_LOG(3,(THIS_FILE,
1728 "Call %d: call transfered successfully, disconnecting call",
1729 call_id));
1730 pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL);
1731 *p_cont = PJ_FALSE;
1732 }
1733}
1734
1735
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001736/*
Benny Prijonof7b1c392006-11-11 16:46:34 +00001737 * Notification that call is being replaced.
1738 */
1739static void on_call_replaced(pjsua_call_id old_call_id,
1740 pjsua_call_id new_call_id)
1741{
1742 pjsua_call_info old_ci, new_ci;
1743
1744 pjsua_call_get_info(old_call_id, &old_ci);
1745 pjsua_call_get_info(new_call_id, &new_ci);
1746
1747 PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by "
1748 "call %d with %.*s",
1749 old_call_id,
1750 (int)old_ci.remote_info.slen, old_ci.remote_info.ptr,
1751 new_call_id,
1752 (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
1753}
1754
1755
1756/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001757 * Print buddy list.
1758 */
1759static void print_buddy_list(void)
1760{
1761 pjsua_buddy_id ids[64];
1762 int i;
1763 unsigned count = PJ_ARRAY_SIZE(ids);
1764
1765 puts("Buddy list:");
1766
1767 pjsua_enum_buddies(ids, &count);
1768
1769 if (count == 0)
1770 puts(" -none-");
1771 else {
1772 for (i=0; i<(int)count; ++i) {
1773 pjsua_buddy_info info;
1774
1775 if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
1776 continue;
1777
1778 printf(" [%2d] <%7s> %.*s\n",
1779 ids[i]+1, info.status_text.ptr,
1780 (int)info.uri.slen,
1781 info.uri.ptr);
1782 }
1783 }
1784 puts("");
1785}
1786
1787
1788/*
1789 * Print account status.
1790 */
1791static void print_acc_status(int acc_id)
1792{
1793 char buf[80];
1794 pjsua_acc_info info;
1795
1796 pjsua_acc_get_info(acc_id, &info);
1797
1798 if (!info.has_registration) {
1799 pj_ansi_snprintf(buf, sizeof(buf), "%.*s",
1800 (int)info.status_text.slen,
1801 info.status_text.ptr);
1802
1803 } else {
1804 pj_ansi_snprintf(buf, sizeof(buf),
1805 "%d/%.*s (expires=%d)",
1806 info.status,
1807 (int)info.status_text.slen,
1808 info.status_text.ptr,
1809 info.expires);
1810
1811 }
1812
1813 printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
1814 acc_id, (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
1815 printf(" Online status: %s\n",
1816 (info.online_status ? "Online" : "Invisible"));
1817}
1818
1819
1820/*
1821 * Show a bit of help.
1822 */
1823static void keystroke_help(void)
1824{
1825 pjsua_acc_id acc_ids[16];
1826 unsigned count = PJ_ARRAY_SIZE(acc_ids);
1827 int i;
1828
1829 printf(">>>>\n");
1830
1831 pjsua_enum_accs(acc_ids, &count);
1832
1833 printf("Account list:\n");
1834 for (i=0; i<(int)count; ++i)
1835 print_acc_status(acc_ids[i]);
1836
1837 print_buddy_list();
1838
1839 //puts("Commands:");
1840 puts("+=============================================================================+");
1841 puts("| Call Commands: | Buddy, IM & Presence: | Account: |");
1842 puts("| | | |");
1843 puts("| m Make new call | +b Add new buddy .| +a Add new accnt |");
1844 puts("| M Make multiple calls | -b Delete buddy | -a Delete accnt. |");
1845 puts("| a Answer call | !b Modify buddy | !a Modify accnt. |");
1846 puts("| h Hangup call (ha=all) | i Send IM | rr (Re-)register |");
1847 puts("| H Hold call | s Subscribe presence | ru Unregister |");
1848 puts("| v re-inVite (release hold) | u Unsubscribe presence | > Cycle next ac.|");
1849 puts("| ] Select next dialog | t ToGgle Online status | < Cycle prev ac.|");
1850 puts("| [ Select previous dialog +--------------------------+-------------------+");
1851 puts("| x Xfer call | Media Commands: | Status & Config: |");
Benny Prijonof7b1c392006-11-11 16:46:34 +00001852 puts("| X Xfer with Replaces | | |");
1853 puts("| # Send DTMF string | cl List ports | d Dump status |");
1854 puts("| dq Dump curr. call quality | cc Connect port | dd Dump detailed |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001855 puts("| | cd Disconnect port | dc Dump config |");
Benny Prijono6dd967c2006-12-26 02:27:14 +00001856 puts("| S Send arbitrary REQUEST | V Adjust audio Volume | f Save config |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001857 puts("+------------------------------+--------------------------+-------------------+");
Benny Prijono990042e2007-01-21 19:36:00 +00001858 puts("| q QUIT sleep N: console sleep for N ms |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001859 puts("+=============================================================================+");
Benny Prijono48af79c2006-07-22 12:49:17 +00001860
1861 i = pjsua_call_get_count();
1862 printf("You have %d active call%s\n", i, (i>1?"s":""));
Benny Prijonof7b1c392006-11-11 16:46:34 +00001863
1864 if (current_call != PJSUA_INVALID_ID) {
1865 pjsua_call_info ci;
1866 if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS)
1867 printf("Current call id=%d to %.*s [%.*s]\n", current_call,
1868 (int)ci.remote_info.slen, ci.remote_info.ptr,
1869 (int)ci.state_text.slen, ci.state_text.ptr);
1870 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001871}
1872
1873
1874/*
1875 * Input simple string
1876 */
1877static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1878{
1879 char *p;
1880
1881 printf("%s (empty to cancel): ", title); fflush(stdout);
1882 fgets(buf, len, stdin);
1883
1884 /* Remove trailing newlines. */
1885 for (p=buf; ; ++p) {
1886 if (*p=='\r' || *p=='\n') *p='\0';
1887 else if (!*p) break;
1888 }
1889
1890 if (!*buf)
1891 return PJ_FALSE;
1892
1893 return PJ_TRUE;
1894}
1895
1896
1897#define NO_NB -2
1898struct input_result
1899{
1900 int nb_result;
1901 char *uri_result;
1902};
1903
1904
1905/*
1906 * Input URL.
1907 */
1908static void ui_input_url(const char *title, char *buf, int len,
1909 struct input_result *result)
1910{
1911 result->nb_result = NO_NB;
1912 result->uri_result = NULL;
1913
1914 print_buddy_list();
1915
1916 printf("Choices:\n"
1917 " 0 For current dialog.\n"
1918 " -1 All %d buddies in buddy list\n"
1919 " [1 -%2d] Select from buddy list\n"
1920 " URL An URL\n"
1921 " <Enter> Empty input (or 'q') to cancel\n"
1922 , pjsua_get_buddy_count(), pjsua_get_buddy_count());
1923 printf("%s: ", title);
1924
1925 fflush(stdout);
1926 fgets(buf, len, stdin);
1927 len = strlen(buf);
1928
1929 /* Left trim */
1930 while (pj_isspace(*buf)) {
1931 ++buf;
1932 --len;
1933 }
1934
1935 /* Remove trailing newlines */
1936 while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
1937 buf[--len] = '\0';
1938
1939 if (len == 0 || buf[0]=='q')
1940 return;
1941
1942 if (pj_isdigit(*buf) || *buf=='-') {
1943
1944 int i;
1945
1946 if (*buf=='-')
1947 i = 1;
1948 else
1949 i = 0;
1950
1951 for (; i<len; ++i) {
1952 if (!pj_isdigit(buf[i])) {
1953 puts("Invalid input");
1954 return;
1955 }
1956 }
1957
1958 result->nb_result = my_atoi(buf);
1959
1960 if (result->nb_result >= 0 &&
1961 result->nb_result <= (int)pjsua_get_buddy_count())
1962 {
1963 return;
1964 }
1965 if (result->nb_result == -1)
1966 return;
1967
1968 puts("Invalid input");
1969 result->nb_result = NO_NB;
1970 return;
1971
1972 } else {
1973 pj_status_t status;
1974
1975 if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
1976 pjsua_perror(THIS_FILE, "Invalid URL", status);
1977 return;
1978 }
1979
1980 result->uri_result = buf;
1981 }
1982}
1983
1984/*
1985 * List the ports in conference bridge
1986 */
1987static void conf_list(void)
1988{
1989 unsigned i, count;
1990 pjsua_conf_port_id id[PJSUA_MAX_CALLS];
1991
1992 printf("Conference ports:\n");
1993
1994 count = PJ_ARRAY_SIZE(id);
1995 pjsua_enum_conf_ports(id, &count);
1996
1997 for (i=0; i<count; ++i) {
1998 char txlist[PJSUA_MAX_CALLS*4+10];
1999 unsigned j;
2000 pjsua_conf_port_info info;
2001
2002 pjsua_conf_get_port_info(id[i], &info);
2003
2004 txlist[0] = '\0';
2005 for (j=0; j<info.listener_cnt; ++j) {
2006 char s[10];
2007 pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
2008 pj_ansi_strcat(txlist, s);
2009 }
2010 printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n",
2011 info.slot_id,
2012 info.clock_rate/1000,
2013 info.samples_per_frame * 1000 / info.clock_rate,
2014 (int)info.name.slen,
2015 info.name.ptr,
2016 txlist);
2017
2018 }
2019 puts("");
2020}
2021
2022
2023/*
Benny Prijono56315612006-07-18 14:39:40 +00002024 * Send arbitrary request to remote host
2025 */
2026static void send_request(char *cstr_method, const pj_str_t *dst_uri)
2027{
2028 pj_str_t str_method;
2029 pjsip_method method;
2030 pjsip_tx_data *tdata;
Benny Prijono56315612006-07-18 14:39:40 +00002031 pjsip_endpoint *endpt;
2032 pj_status_t status;
2033
2034 endpt = pjsua_get_pjsip_endpt();
2035
2036 str_method = pj_str(cstr_method);
2037 pjsip_method_init_np(&method, &str_method);
2038
Benny Prijonofff245c2007-04-02 11:44:47 +00002039 status = pjsua_acc_create_request(current_acc, &method, dst_uri, &tdata);
Benny Prijono56315612006-07-18 14:39:40 +00002040
2041 status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
2042 if (status != PJ_SUCCESS) {
2043 pjsua_perror(THIS_FILE, "Unable to send request", status);
Benny Prijono56315612006-07-18 14:39:40 +00002044 return;
2045 }
2046}
2047
2048
2049/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002050 * Main "user interface" loop.
2051 */
2052void console_app_main(const pj_str_t *uri_to_call)
2053{
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002054 char menuin[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002055 char buf[128];
2056 char text[128];
2057 int i, count;
2058 char *uri;
2059 pj_str_t tmp;
2060 struct input_result result;
2061 pjsua_call_info call_info;
2062 pjsua_acc_info acc_info;
2063
2064
2065 /* If user specifies URI to call, then call the URI */
2066 if (uri_to_call->slen) {
2067 pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, NULL);
2068 }
2069
2070 keystroke_help();
2071
2072 for (;;) {
2073
2074 printf(">>> ");
2075 fflush(stdout);
2076
Benny Prijono990042e2007-01-21 19:36:00 +00002077 if (fgets(menuin, sizeof(menuin), stdin) == NULL) {
2078 /*
2079 * Be friendly to users who redirect commands into
2080 * program, when file ends, resume with kbd.
2081 * If exit is desired end script with q for quit
2082 */
2083 /* Reopen stdin/stdout/stderr to /dev/console */
2084#if defined(PJ_WIN32) && PJ_WIN32!=0
2085 if (freopen ("CONIN$", "r", stdin) == NULL) {
2086#else
2087 if (1) {
2088#endif
2089 puts("Cannot switch back to console from file redirection");
2090 menuin[0] = 'q';
2091 menuin[1] = '\0';
2092 } else {
2093 puts("Switched back to console from file redirection");
2094 continue;
2095 }
2096 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002097
2098 switch (menuin[0]) {
2099
2100 case 'm':
2101 /* Make call! : */
2102 printf("(You currently have %d calls)\n",
2103 pjsua_call_get_count());
2104
2105 uri = NULL;
2106 ui_input_url("Make call", buf, sizeof(buf), &result);
2107 if (result.nb_result != NO_NB) {
2108
2109 if (result.nb_result == -1 || result.nb_result == 0) {
2110 puts("You can't do that with make call!");
2111 continue;
2112 } else {
2113 pjsua_buddy_info binfo;
2114 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2115 uri = binfo.uri.ptr;
2116 }
2117
2118 } else if (result.uri_result) {
2119 uri = result.uri_result;
2120 }
2121
2122 tmp = pj_str(uri);
2123 pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL);
2124 break;
2125
2126 case 'M':
2127 /* Make multiple calls! : */
2128 printf("(You currently have %d calls)\n",
2129 pjsua_call_get_count());
2130
2131 if (!simple_input("Number of calls", menuin, sizeof(menuin)))
2132 continue;
2133
2134 count = my_atoi(menuin);
2135 if (count < 1)
2136 continue;
2137
2138 ui_input_url("Make call", buf, sizeof(buf), &result);
2139 if (result.nb_result != NO_NB) {
2140 pjsua_buddy_info binfo;
2141 if (result.nb_result == -1 || result.nb_result == 0) {
2142 puts("You can't do that with make call!");
2143 continue;
2144 }
2145 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2146 uri = binfo.uri.ptr;
2147 } else {
2148 uri = result.uri_result;
2149 }
2150
2151 for (i=0; i<my_atoi(menuin); ++i) {
2152 pj_status_t status;
2153
2154 tmp = pj_str(uri);
2155 status = pjsua_call_make_call(current_acc, &tmp, 0, NULL,
2156 NULL, NULL);
2157 if (status != PJ_SUCCESS)
2158 break;
2159 }
2160 break;
2161
2162 case 'i':
2163 /* Send instant messaeg */
2164
2165 /* i is for call index to send message, if any */
2166 i = -1;
2167
2168 /* Make compiler happy. */
2169 uri = NULL;
2170
2171 /* Input destination. */
2172 ui_input_url("Send IM to", buf, sizeof(buf), &result);
2173 if (result.nb_result != NO_NB) {
2174
2175 if (result.nb_result == -1) {
2176 puts("You can't send broadcast IM like that!");
2177 continue;
2178
2179 } else if (result.nb_result == 0) {
2180
2181 i = current_call;
2182
2183 } else {
2184 pjsua_buddy_info binfo;
2185 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2186 uri = binfo.uri.ptr;
2187 }
2188
2189 } else if (result.uri_result) {
2190 uri = result.uri_result;
2191 }
2192
2193
2194 /* Send typing indication. */
2195 if (i != -1)
2196 pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
2197 else {
2198 pj_str_t tmp_uri = pj_str(uri);
2199 pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
2200 }
2201
2202 /* Input the IM . */
2203 if (!simple_input("Message", text, sizeof(text))) {
2204 /*
2205 * Cancelled.
2206 * Send typing notification too, saying we're not typing.
2207 */
2208 if (i != -1)
2209 pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
2210 else {
2211 pj_str_t tmp_uri = pj_str(uri);
2212 pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
2213 }
2214 continue;
2215 }
2216
2217 tmp = pj_str(text);
2218
2219 /* Send the IM */
2220 if (i != -1)
2221 pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
2222 else {
2223 pj_str_t tmp_uri = pj_str(uri);
2224 pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
2225 }
2226
2227 break;
2228
2229 case 'a':
2230
2231 if (current_call != -1) {
2232 pjsua_call_get_info(current_call, &call_info);
2233 } else {
2234 /* Make compiler happy */
2235 call_info.role = PJSIP_ROLE_UAC;
2236 call_info.state = PJSIP_INV_STATE_DISCONNECTED;
2237 }
2238
2239 if (current_call == -1 ||
2240 call_info.role != PJSIP_ROLE_UAS ||
2241 call_info.state >= PJSIP_INV_STATE_CONNECTING)
2242 {
2243 puts("No pending incoming call");
2244 fflush(stdout);
2245 continue;
2246
2247 } else {
Benny Prijono20d36722007-02-22 14:52:24 +00002248 int st_code;
2249 char contact[120];
2250 pj_str_t hname = { "Contact", 7 };
2251 pj_str_t hvalue;
2252 pjsip_generic_string_hdr hcontact;
2253 pjsua_msg_data msg_data;
2254
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002255 if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
2256 continue;
2257
Benny Prijono20d36722007-02-22 14:52:24 +00002258 st_code = my_atoi(buf);
2259 if (st_code < 100)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002260 continue;
2261
Benny Prijono20d36722007-02-22 14:52:24 +00002262 pjsua_msg_data_init(&msg_data);
2263
2264 if (st_code/100 == 3) {
2265 if (!simple_input("Enter URL to be put in Contact",
2266 contact, sizeof(contact)))
2267 continue;
2268 hvalue = pj_str(contact);
2269 pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
2270
2271 pj_list_push_back(&msg_data.hdr_list, &hcontact);
2272 }
2273
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002274 /*
2275 * Must check again!
2276 * Call may have been disconnected while we're waiting for
2277 * keyboard input.
2278 */
2279 if (current_call == -1) {
2280 puts("Call has been disconnected");
2281 fflush(stdout);
2282 continue;
2283 }
2284
Benny Prijono20d36722007-02-22 14:52:24 +00002285 pjsua_call_answer(current_call, st_code, NULL, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002286 }
2287
2288 break;
2289
2290
2291 case 'h':
2292
2293 if (current_call == -1) {
2294 puts("No current call");
2295 fflush(stdout);
2296 continue;
2297
2298 } else if (menuin[1] == 'a') {
2299
2300 /* Hangup all calls */
2301 pjsua_call_hangup_all();
2302
2303 } else {
2304
2305 /* Hangup current calls */
2306 pjsua_call_hangup(current_call, 0, NULL, NULL);
2307 }
2308 break;
2309
2310 case ']':
2311 case '[':
2312 /*
2313 * Cycle next/prev dialog.
2314 */
2315 if (menuin[0] == ']') {
2316 find_next_call();
2317
2318 } else {
2319 find_prev_call();
2320 }
2321
2322 if (current_call != -1) {
2323
2324 pjsua_call_get_info(current_call, &call_info);
2325 PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s",
2326 (int)call_info.remote_info.slen,
2327 call_info.remote_info.ptr));
2328
2329 } else {
2330 PJ_LOG(3,(THIS_FILE,"No current dialog"));
2331 }
2332 break;
2333
2334
2335 case '>':
2336 case '<':
2337 if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
2338 break;
2339
2340 i = my_atoi(buf);
2341 if (pjsua_acc_is_valid(i)) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00002342 pjsua_acc_set_default(i);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002343 PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
2344 } else {
2345 PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
2346 }
2347 break;
2348
2349
2350 case '+':
2351 if (menuin[1] == 'b') {
2352
2353 pjsua_buddy_config buddy_cfg;
2354 pjsua_buddy_id buddy_id;
2355 pj_status_t status;
2356
2357 if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
2358 break;
2359
2360 if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) {
2361 printf("Invalid SIP URI '%s'\n", buf);
2362 break;
2363 }
2364
Benny Prijonoac623b32006-07-03 15:19:31 +00002365 pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002366
2367 buddy_cfg.uri = pj_str(buf);
2368 buddy_cfg.subscribe = PJ_TRUE;
2369
2370 status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
2371 if (status == PJ_SUCCESS) {
2372 printf("New buddy '%s' added at index %d\n",
2373 buf, buddy_id+1);
2374 }
2375
2376 } else if (menuin[1] == 'a') {
2377
2378 printf("Sorry, this command is not supported yet\n");
2379
2380 } else {
2381 printf("Invalid input %s\n", menuin);
2382 }
2383 break;
2384
2385 case '-':
2386 if (menuin[1] == 'b') {
2387 if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
2388 break;
2389
2390 i = my_atoi(buf) - 1;
2391
2392 if (!pjsua_buddy_is_valid(i)) {
2393 printf("Invalid buddy id %d\n", i);
2394 } else {
2395 pjsua_buddy_del(i);
2396 printf("Buddy %d deleted\n", i);
2397 }
2398
2399 } else if (menuin[1] == 'a') {
2400
2401 if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
2402 break;
2403
2404 i = my_atoi(buf);
2405
2406 if (!pjsua_acc_is_valid(i)) {
2407 printf("Invalid account id %d\n", i);
2408 } else {
2409 pjsua_acc_del(i);
2410 printf("Account %d deleted\n", i);
2411 }
2412
2413 } else {
2414 printf("Invalid input %s\n", menuin);
2415 }
2416 break;
2417
2418 case 'H':
2419 /*
2420 * Hold call.
2421 */
2422 if (current_call != -1) {
2423
2424 pjsua_call_set_hold(current_call, NULL);
2425
2426 } else {
2427 PJ_LOG(3,(THIS_FILE, "No current call"));
2428 }
2429 break;
2430
2431 case 'v':
2432 /*
2433 * Send re-INVITE (to release hold, etc).
2434 */
2435 if (current_call != -1) {
2436
2437 pjsua_call_reinvite(current_call, PJ_TRUE, NULL);
2438
2439 } else {
2440 PJ_LOG(3,(THIS_FILE, "No current call"));
2441 }
2442 break;
2443
2444 case 'x':
2445 /*
2446 * Transfer call.
2447 */
2448 if (current_call == -1) {
2449
2450 PJ_LOG(3,(THIS_FILE, "No current call"));
2451
2452 } else {
2453 int call = current_call;
Benny Prijonod524e822006-09-22 12:48:18 +00002454 pjsua_msg_data msg_data;
2455 pjsip_generic_string_hdr refer_sub;
2456 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2457 pj_str_t STR_FALSE = { "false", 5 };
Benny Prijonof7b1c392006-11-11 16:46:34 +00002458 pjsua_call_info ci;
2459
2460 pjsua_call_get_info(current_call, &ci);
2461 printf("Transfering current call [%d] %.*s\n",
2462 current_call,
2463 (int)ci.remote_info.slen, ci.remote_info.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002464
2465 ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
2466
2467 /* Check if call is still there. */
2468
2469 if (call != current_call) {
2470 puts("Call has been disconnected");
2471 continue;
2472 }
2473
Benny Prijonod524e822006-09-22 12:48:18 +00002474 pjsua_msg_data_init(&msg_data);
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002475 if (app_config.no_refersub) {
2476 /* Add Refer-Sub: false in outgoing REFER request */
2477 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2478 &STR_FALSE);
2479 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2480 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002481 if (result.nb_result != NO_NB) {
2482 if (result.nb_result == -1 || result.nb_result == 0)
2483 puts("You can't do that with transfer call!");
2484 else {
2485 pjsua_buddy_info binfo;
2486 pjsua_buddy_get_info(result.nb_result-1, &binfo);
Benny Prijonod524e822006-09-22 12:48:18 +00002487 pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002488 }
2489
2490 } else if (result.uri_result) {
2491 pj_str_t tmp;
2492 tmp = pj_str(result.uri_result);
Benny Prijonod524e822006-09-22 12:48:18 +00002493 pjsua_call_xfer( current_call, &tmp, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002494 }
2495 }
2496 break;
2497
Benny Prijonof7b1c392006-11-11 16:46:34 +00002498 case 'X':
2499 /*
2500 * Transfer call with replaces.
2501 */
2502 if (current_call == -1) {
2503
2504 PJ_LOG(3,(THIS_FILE, "No current call"));
2505
2506 } else {
2507 int call = current_call;
2508 int dst_call;
2509 pjsua_msg_data msg_data;
2510 pjsip_generic_string_hdr refer_sub;
2511 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2512 pj_str_t STR_FALSE = { "false", 5 };
2513 pjsua_call_id ids[PJSUA_MAX_CALLS];
2514 pjsua_call_info ci;
2515 unsigned i, count;
2516
2517 count = PJ_ARRAY_SIZE(ids);
2518 pjsua_enum_calls(ids, &count);
2519
2520 if (count <= 1) {
2521 puts("There are no other calls");
2522 continue;
2523 }
2524
2525 pjsua_call_get_info(current_call, &ci);
2526 printf("Transfer call [%d] %.*s to one of the following:\n",
2527 current_call,
2528 (int)ci.remote_info.slen, ci.remote_info.ptr);
2529
2530 for (i=0; i<count; ++i) {
2531 pjsua_call_info call_info;
2532
2533 if (ids[i] == call)
2534 continue;
2535
2536 pjsua_call_get_info(ids[i], &call_info);
2537 printf("%d %.*s [%.*s]\n",
2538 ids[i],
2539 (int)call_info.remote_info.slen,
2540 call_info.remote_info.ptr,
2541 (int)call_info.state_text.slen,
2542 call_info.state_text.ptr);
2543 }
2544
2545 if (!simple_input("Enter call number to be replaced",
2546 buf, sizeof(buf)))
2547 continue;
2548
2549 dst_call = my_atoi(buf);
2550
2551 /* Check if call is still there. */
2552
2553 if (call != current_call) {
2554 puts("Call has been disconnected");
2555 continue;
2556 }
2557
2558 /* Check that destination call is valid. */
2559 if (dst_call == call) {
2560 puts("Destination call number must not be the same "
2561 "as the call being transfered");
2562 continue;
2563 }
2564 if (dst_call >= PJSUA_MAX_CALLS) {
2565 puts("Invalid destination call number");
2566 continue;
2567 }
2568 if (!pjsua_call_is_active(dst_call)) {
2569 puts("Invalid destination call number");
2570 continue;
2571 }
2572
2573 pjsua_msg_data_init(&msg_data);
2574 if (app_config.no_refersub) {
2575 /* Add Refer-Sub: false in outgoing REFER request */
2576 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2577 &STR_FALSE);
2578 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2579 }
2580
2581 pjsua_call_xfer_replaces(call, dst_call, 0, &msg_data);
2582 }
2583 break;
2584
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002585 case '#':
2586 /*
2587 * Send DTMF strings.
2588 */
2589 if (current_call == -1) {
2590
2591 PJ_LOG(3,(THIS_FILE, "No current call"));
2592
2593 } else if (!pjsua_call_has_media(current_call)) {
2594
2595 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
2596
2597 } else {
2598 pj_str_t digits;
2599 int call = current_call;
2600 pj_status_t status;
2601
2602 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
2603 sizeof(buf)))
2604 {
2605 break;
2606 }
2607
2608 if (call != current_call) {
2609 puts("Call has been disconnected");
2610 continue;
2611 }
2612
2613 digits = pj_str(buf);
2614 status = pjsua_call_dial_dtmf(current_call, &digits);
2615 if (status != PJ_SUCCESS) {
2616 pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
2617 } else {
2618 puts("DTMF digits enqueued for transmission");
2619 }
2620 }
2621 break;
2622
Benny Prijono56315612006-07-18 14:39:40 +00002623 case 'S':
2624 /*
2625 * Send arbitrary request
2626 */
2627 if (pjsua_acc_get_count() == 0) {
2628 puts("Sorry, need at least one account configured");
2629 break;
2630 }
2631
2632 puts("Send arbitrary request to remote host");
2633
2634 /* Input METHOD */
2635 if (!simple_input("Request method:",text,sizeof(text)))
2636 break;
2637
2638 /* Input destination URI */
2639 uri = NULL;
2640 ui_input_url("Destination URI", buf, sizeof(buf), &result);
2641 if (result.nb_result != NO_NB) {
2642
2643 if (result.nb_result == -1 || result.nb_result == 0) {
2644 puts("Sorry you can't do that!");
2645 continue;
2646 } else {
2647 pjsua_buddy_info binfo;
2648 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2649 uri = binfo.uri.ptr;
2650 }
2651
2652 } else if (result.uri_result) {
2653 uri = result.uri_result;
2654 }
2655
2656 tmp = pj_str(uri);
2657
2658 send_request(text, &tmp);
2659 break;
2660
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002661 case 's':
Benny Prijono990042e2007-01-21 19:36:00 +00002662 if (pj_ansi_strnicmp(menuin, "sleep", 5)==0) {
2663 pj_str_t tmp;
2664 int delay;
2665
2666 tmp.ptr = menuin+6;
2667 tmp.slen = pj_ansi_strlen(menuin)-7;
2668
2669 if (tmp.slen < 1) {
2670 puts("Usage: sleep MSEC");
2671 break;
2672 }
2673
2674 delay = pj_strtoul(&tmp);
2675 if (delay < 0) delay = 0;
2676 pj_thread_sleep(delay);
2677 break;
2678 }
2679 /* Continue below */
2680
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002681 case 'u':
2682 /*
2683 * Subscribe/unsubscribe presence.
2684 */
2685 ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
2686 if (result.nb_result != NO_NB) {
2687 if (result.nb_result == -1) {
2688 int i, count;
2689 count = pjsua_get_buddy_count();
2690 for (i=0; i<count; ++i)
2691 pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
2692 } else if (result.nb_result == 0) {
2693 puts("Sorry, can only subscribe to buddy's presence, "
2694 "not from existing call");
2695 } else {
2696 pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
2697 }
2698
2699 } else if (result.uri_result) {
2700 puts("Sorry, can only subscribe to buddy's presence, "
2701 "not arbitrary URL (for now)");
2702 }
2703
2704 break;
2705
2706 case 'r':
2707 switch (menuin[1]) {
2708 case 'r':
2709 /*
2710 * Re-Register.
2711 */
2712 pjsua_acc_set_registration(current_acc, PJ_TRUE);
2713 break;
2714 case 'u':
2715 /*
2716 * Unregister
2717 */
2718 pjsua_acc_set_registration(current_acc, PJ_FALSE);
2719 break;
2720 }
2721 break;
2722
2723 case 't':
2724 pjsua_acc_get_info(current_acc, &acc_info);
2725 acc_info.online_status = !acc_info.online_status;
2726 pjsua_acc_set_online_status(current_acc, acc_info.online_status);
2727 printf("Setting %s online status to %s\n",
2728 acc_info.acc_uri.ptr,
2729 (acc_info.online_status?"online":"offline"));
2730 break;
2731
2732 case 'c':
2733 switch (menuin[1]) {
2734 case 'l':
2735 conf_list();
2736 break;
2737 case 'c':
2738 case 'd':
2739 {
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002740 char tmp[10], src_port[10], dst_port[10];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002741 pj_status_t status;
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002742 int cnt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002743 const char *src_title, *dst_title;
2744
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002745 cnt = sscanf(menuin, "%s %s %s", tmp, src_port, dst_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002746
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002747 if (cnt != 3) {
2748 conf_list();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002749
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002750 src_title = (menuin[1]=='c'?
2751 "Connect src port #":
2752 "Disconnect src port #");
2753 dst_title = (menuin[1]=='c'?
2754 "To dst port #":
2755 "From dst port #");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002756
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002757 if (!simple_input(src_title, src_port, sizeof(src_port)))
2758 break;
2759
2760 if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
2761 break;
2762 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002763
2764 if (menuin[1]=='c') {
2765 status = pjsua_conf_connect(my_atoi(src_port),
2766 my_atoi(dst_port));
2767 } else {
2768 status = pjsua_conf_disconnect(my_atoi(src_port),
2769 my_atoi(dst_port));
2770 }
2771 if (status == PJ_SUCCESS) {
2772 puts("Success");
2773 } else {
2774 puts("ERROR!!");
2775 }
2776 }
2777 break;
2778 }
2779 break;
2780
Benny Prijono6dd967c2006-12-26 02:27:14 +00002781 case 'V':
2782 /* Adjust audio volume */
2783 sprintf(buf, "Adjust mic level: [%4.1fx] ", app_config.mic_level);
2784 if (simple_input(buf,text,sizeof(text))) {
2785 char *err;
2786 app_config.mic_level = (float)strtod(text, &err);
2787 pjsua_conf_adjust_rx_level(0, app_config.mic_level);
2788 }
2789 sprintf(buf, "Adjust speaker level: [%4.1fx] ",
2790 app_config.speaker_level);
2791 if (simple_input(buf,text,sizeof(text))) {
2792 char *err;
2793 app_config.speaker_level = (float)strtod(text, &err);
2794 pjsua_conf_adjust_tx_level(0, app_config.speaker_level);
2795 }
2796
2797 break;
2798
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002799 case 'd':
2800 if (menuin[1] == 'c') {
2801 char settings[2000];
2802 int len;
2803
2804 len = write_settings(&app_config, settings, sizeof(settings));
2805 if (len < 1)
2806 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2807 else
2808 PJ_LOG(3,(THIS_FILE,
2809 "Dumping configuration (%d bytes):\n%s\n",
2810 len, settings));
Benny Prijono819506c2006-06-22 22:29:51 +00002811
2812 } else if (menuin[1] == 'q') {
2813
2814 if (current_call != PJSUA_INVALID_ID) {
2815 char buf[1024];
2816 pjsua_call_dump(current_call, PJ_TRUE, buf,
2817 sizeof(buf), " ");
2818 PJ_LOG(3,(THIS_FILE, "\n%s", buf));
2819 } else {
2820 PJ_LOG(3,(THIS_FILE, "No current call"));
2821 }
2822
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002823 } else {
2824 app_dump(menuin[1]=='d');
2825 }
2826 break;
2827
2828
2829 case 'f':
2830 if (simple_input("Enter output filename", buf, sizeof(buf))) {
2831 char settings[2000];
2832 int len;
2833
2834 len = write_settings(&app_config, settings, sizeof(settings));
2835 if (len < 1)
2836 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2837 else {
2838 pj_oshandle_t fd;
2839 pj_status_t status;
2840
2841 status = pj_file_open(app_config.pool, buf,
2842 PJ_O_WRONLY, &fd);
2843 if (status != PJ_SUCCESS) {
2844 pjsua_perror(THIS_FILE, "Unable to open file", status);
2845 } else {
2846 pj_ssize_t size = len;
2847 pj_file_write(fd, settings, &size);
2848 pj_file_close(fd);
2849
2850 printf("Settings successfully written to '%s'\n", buf);
2851 }
2852 }
2853
2854 }
2855 break;
2856
2857
2858 case 'q':
2859 goto on_exit;
2860
2861
2862 default:
2863 if (menuin[0] != '\n' && menuin[0] != '\r') {
2864 printf("Invalid input %s", menuin);
2865 }
2866 keystroke_help();
2867 break;
2868 }
2869 }
2870
2871on_exit:
2872 ;
2873}
2874
2875
2876/*****************************************************************************
2877 * Public API
2878 */
2879
2880pj_status_t app_init(int argc, char *argv[])
2881{
Benny Prijonoe93e2872006-06-28 16:46:49 +00002882 pjsua_transport_id transport_id = -1;
Benny Prijonoe347cb02007-02-14 14:36:13 +00002883 pjsua_transport_config tcp_cfg;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002884 unsigned i;
2885 pj_status_t status;
2886
2887 /* Create pjsua */
2888 status = pjsua_create();
2889 if (status != PJ_SUCCESS)
2890 return status;
2891
2892 /* Create pool for application */
2893 app_config.pool = pjsua_pool_create("pjsua", 4000, 4000);
2894
2895 /* Initialize default config */
2896 default_config(&app_config);
2897
2898 /* Parse the arguments */
2899 status = parse_args(argc, argv, &app_config, &uri_arg);
2900 if (status != PJ_SUCCESS)
2901 return status;
2902
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002903 /* Initialize application callbacks */
2904 app_config.cfg.cb.on_call_state = &on_call_state;
2905 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
2906 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
Benny Prijono0875ae82006-12-26 00:11:48 +00002907 app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002908 app_config.cfg.cb.on_reg_state = &on_reg_state;
2909 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
2910 app_config.cfg.cb.on_pager = &on_pager;
2911 app_config.cfg.cb.on_typing = &on_typing;
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002912 app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
Benny Prijonof7b1c392006-11-11 16:46:34 +00002913 app_config.cfg.cb.on_call_replaced = &on_call_replaced;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002914
2915 /* Initialize pjsua */
2916 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
2917 &app_config.media_cfg);
2918 if (status != PJ_SUCCESS)
2919 return status;
2920
Benny Prijonoe909eac2006-07-27 22:04:56 +00002921#ifdef STEREO_DEMO
2922 stereo_demo();
2923#endif
2924
Benny Prijono804ff0a2006-09-14 11:17:48 +00002925 /* Initialize calls data */
2926 for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
2927 app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
2928 app_config.call_data[i].timer.cb = &call_timeout_callback;
2929 }
2930
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002931 /* Optionally registers WAV file */
Benny Prijono32e4f492007-01-24 00:44:26 +00002932 for (i=0; i<app_config.wav_count; ++i) {
2933 pjsua_player_id wav_id;
2934
2935 status = pjsua_player_create(&app_config.wav_files[i], 0,
2936 &wav_id);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002937 if (status != PJ_SUCCESS)
2938 goto on_error;
Benny Prijono22a300a2006-06-14 20:04:55 +00002939
Benny Prijono72c04d22007-02-17 22:20:58 +00002940 if (app_config.wav_id == PJSUA_INVALID_ID) {
Benny Prijono32e4f492007-01-24 00:44:26 +00002941 app_config.wav_id = wav_id;
2942 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
2943 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002944 }
2945
Benny Prijono4af234b2007-01-24 02:02:09 +00002946 /* Optionally registers tone players */
2947 for (i=0; i<app_config.tone_count; ++i) {
2948 pjmedia_port *tport;
2949 char name[80];
2950 pj_str_t label;
2951 pj_status_t status;
2952
2953 pj_ansi_snprintf(name, sizeof(name), "tone-%d,%d",
2954 app_config.tones[i].freq1,
2955 app_config.tones[i].freq2);
2956 label = pj_str(name);
2957 status = pjmedia_tonegen_create2(app_config.pool, &label,
2958 8000, 1, 160, 16,
2959 PJMEDIA_TONEGEN_LOOP, &tport);
2960 if (status != PJ_SUCCESS) {
2961 pjsua_perror(THIS_FILE, "Unable to create tone generator", status);
2962 goto on_error;
2963 }
2964
2965 status = pjsua_conf_add_port(app_config.pool, tport,
2966 &app_config.tone_slots[i]);
2967 pj_assert(status == PJ_SUCCESS);
2968
2969 status = pjmedia_tonegen_play(tport, 1, &app_config.tones[i], 0);
2970 pj_assert(status == PJ_SUCCESS);
2971 }
2972
Benny Prijono1ebd6142006-10-19 15:48:02 +00002973 /* Optionally create recorder file, if any. */
2974 if (app_config.rec_file.slen) {
2975 status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
2976 &app_config.rec_id);
2977 if (status != PJ_SUCCESS)
2978 goto on_error;
2979
2980 app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
2981 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002982
Benny Prijonoe347cb02007-02-14 14:36:13 +00002983 pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
2984
Benny Prijono87ef89a2007-01-14 00:39:45 +00002985 /* Add UDP transport unless it's disabled. */
2986 if (!app_config.no_udp) {
2987 pjsua_acc_id aid;
2988
2989 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
2990 &app_config.udp_cfg,
2991 &transport_id);
2992 if (status != PJ_SUCCESS)
2993 goto on_error;
2994
2995 /* Add local account */
2996 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
2997 //pjsua_acc_set_transport(aid, transport_id);
2998 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
Benny Prijonoe347cb02007-02-14 14:36:13 +00002999
3000 if (app_config.udp_cfg.port == 0) {
3001 pjsua_transport_info ti;
3002 pj_sockaddr_in *a;
3003
3004 pjsua_transport_get_info(transport_id, &ti);
3005 a = (pj_sockaddr_in*)&ti.local_addr;
3006
3007 tcp_cfg.port = pj_ntohs(a->sin_port);
3008 }
Benny Prijono87ef89a2007-01-14 00:39:45 +00003009 }
3010
Benny Prijonoe93e2872006-06-28 16:46:49 +00003011 /* Add TCP transport unless it's disabled */
3012 if (!app_config.no_tcp) {
3013 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
Benny Prijonoe347cb02007-02-14 14:36:13 +00003014 &tcp_cfg,
Benny Prijonoe93e2872006-06-28 16:46:49 +00003015 &transport_id);
3016 if (status != PJ_SUCCESS)
3017 goto on_error;
3018
3019 /* Add local account */
Benny Prijono21b9ad92006-08-15 13:11:22 +00003020 pjsua_acc_add_local(transport_id, PJ_TRUE, NULL);
Benny Prijonoe93e2872006-06-28 16:46:49 +00003021 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
3022
3023 }
3024
Benny Prijonoe93e2872006-06-28 16:46:49 +00003025
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003026#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
3027 /* Add TLS transport when application wants one */
3028 if (app_config.use_tls) {
Benny Prijonof3bbc132006-12-25 06:43:59 +00003029
3030 pjsua_acc_id acc_id;
3031
3032 /* Set TLS port as TCP port+1 */
Benny Prijonoafc47be2007-02-14 14:44:55 +00003033 tcp_cfg.port++;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003034 status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
Benny Prijonoafc47be2007-02-14 14:44:55 +00003035 &tcp_cfg,
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003036 &transport_id);
Benny Prijonoafc47be2007-02-14 14:44:55 +00003037 tcp_cfg.port--;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003038 if (status != PJ_SUCCESS)
3039 goto on_error;
Benny Prijonof3bbc132006-12-25 06:43:59 +00003040
3041 /* Add local account */
3042 pjsua_acc_add_local(transport_id, PJ_FALSE, &acc_id);
3043 pjsua_acc_set_online_status(acc_id, PJ_TRUE);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003044 }
3045#endif
3046
Benny Prijonoe93e2872006-06-28 16:46:49 +00003047 if (transport_id == -1) {
3048 PJ_LOG(3,(THIS_FILE, "Error: no transport is configured"));
3049 status = -1;
3050 goto on_error;
3051 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003052
3053
3054 /* Add accounts */
3055 for (i=0; i<app_config.acc_cnt; ++i) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00003056 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003057 if (status != PJ_SUCCESS)
3058 goto on_error;
3059 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
3060 }
3061
3062 /* Add buddies */
3063 for (i=0; i<app_config.buddy_cnt; ++i) {
3064 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
3065 if (status != PJ_SUCCESS)
3066 goto on_error;
3067 }
3068
3069 /* Optionally set codec orders */
3070 for (i=0; i<app_config.codec_cnt; ++i) {
3071 pjsua_codec_set_priority(&app_config.codec_arg[i],
3072 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
3073 }
3074
3075 /* Add RTP transports */
3076 status = pjsua_media_transports_create(&app_config.rtp_cfg);
3077 if (status != PJ_SUCCESS)
3078 goto on_error;
3079
Benny Prijono22a300a2006-06-14 20:04:55 +00003080 /* Use null sound device? */
Benny Prijonoe909eac2006-07-27 22:04:56 +00003081#ifndef STEREO_DEMO
Benny Prijono22a300a2006-06-14 20:04:55 +00003082 if (app_config.null_audio) {
3083 status = pjsua_set_null_snd_dev();
3084 if (status != PJ_SUCCESS)
3085 return status;
3086 }
Benny Prijonoe909eac2006-07-27 22:04:56 +00003087#endif
Benny Prijono22a300a2006-06-14 20:04:55 +00003088
Benny Prijono4e5d5512007-03-06 18:11:30 +00003089 if (app_config.capture_dev != PJSUA_INVALID_ID
3090 || app_config.playback_dev != PJSUA_INVALID_ID) {
3091 status
3092 = pjsua_set_snd_dev(app_config.capture_dev, app_config.playback_dev);
3093 if (status != PJ_SUCCESS)
3094 goto on_error;
3095 }
3096
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003097 return PJ_SUCCESS;
3098
3099on_error:
Benny Prijonoad2e0ca2007-04-29 12:31:51 +00003100 app_destroy();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003101 return status;
3102}
3103
3104
3105pj_status_t app_main(void)
3106{
3107 pj_status_t status;
3108
3109 /* Start pjsua */
3110 status = pjsua_start();
3111 if (status != PJ_SUCCESS) {
Benny Prijonoad2e0ca2007-04-29 12:31:51 +00003112 app_destroy();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003113 return status;
3114 }
3115
3116 console_app_main(&uri_arg);
3117
3118 return PJ_SUCCESS;
3119}
3120
3121pj_status_t app_destroy(void)
3122{
Benny Prijonof762ee72006-12-01 11:14:37 +00003123 pj_status_t status;
Benny Prijono4af234b2007-01-24 02:02:09 +00003124 unsigned i;
Benny Prijonof762ee72006-12-01 11:14:37 +00003125
Benny Prijonoe909eac2006-07-27 22:04:56 +00003126#ifdef STEREO_DEMO
3127 if (app_config.snd) {
3128 pjmedia_snd_port_destroy(app_config.snd);
3129 app_config.snd = NULL;
3130 }
3131#endif
3132
Benny Prijono4af234b2007-01-24 02:02:09 +00003133 /* Close tone generators */
3134 for (i=0; i<app_config.tone_count; ++i) {
3135 pjsua_conf_remove_port(app_config.tone_slots[i]);
3136 }
3137
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003138 if (app_config.pool) {
3139 pj_pool_release(app_config.pool);
3140 app_config.pool = NULL;
3141 }
3142
Benny Prijonof762ee72006-12-01 11:14:37 +00003143 status = pjsua_destroy();
3144
3145 pj_bzero(&app_config, sizeof(app_config));
3146
3147 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003148}
Benny Prijonoe909eac2006-07-27 22:04:56 +00003149
3150
3151#ifdef STEREO_DEMO
3152static void stereo_demo()
3153{
3154 pjmedia_port *conf, *splitter, *ch1;
Benny Prijonoe909eac2006-07-27 22:04:56 +00003155 pj_status_t status;
3156
3157 /* Disable existing sound device */
3158 conf = pjsua_set_no_snd_dev();
3159
Benny Prijonoe909eac2006-07-27 22:04:56 +00003160 /* Create stereo-mono splitter/combiner */
3161 status = pjmedia_splitcomb_create(app_config.pool,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003162 conf->info.clock_rate /* clock rate */,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003163 2 /* stereo */,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003164 2 * conf->info.samples_per_frame,
3165 conf->info.bits_per_sample,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003166 0 /* options */,
3167 &splitter);
3168 pj_assert(status == PJ_SUCCESS);
3169
3170 /* Connect channel0 (left channel?) to conference port slot0 */
3171 status = pjmedia_splitcomb_set_channel(splitter, 0 /* ch0 */,
3172 0 /*options*/,
3173 conf);
3174 pj_assert(status == PJ_SUCCESS);
3175
3176 /* Create reverse channel for channel1 (right channel?)... */
3177 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
3178 splitter,
3179 1 /* ch1 */,
3180 0 /* options */,
3181 &ch1);
3182 pj_assert(status == PJ_SUCCESS);
3183
3184 /* .. and register it to conference bridge (it would be slot1
3185 * if there's no other devices connected to the bridge)
3186 */
3187 status = pjsua_conf_add_port(app_config.pool, ch1, NULL);
3188 pj_assert(status == PJ_SUCCESS);
3189
3190 /* Create sound device */
3191 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003192 conf->info.clock_rate,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003193 2 /* stereo */,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003194 2 * conf->info.samples_per_frame,
3195 conf->info.bits_per_sample,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003196 0, &app_config.snd);
3197 pj_assert(status == PJ_SUCCESS);
3198
3199
3200 /* Connect the splitter to the sound device */
3201 status = pjmedia_snd_port_connect(app_config.snd, splitter);
3202 pj_assert(status == PJ_SUCCESS);
3203
3204}
3205#endif
3206