blob: 1b9584b862e60d820316780722204ba84e9fdb4b [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;
312 return pj_strtoul(pj_cstr(&s, cs));
313}
314
315
316/* Parse arguments. */
317static pj_status_t parse_args(int argc, char *argv[],
318 struct app_config *cfg,
319 pj_str_t *uri_to_call)
320{
321 int c;
322 int option_index;
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000323 enum { OPT_CONFIG_FILE=127, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000324 OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
Benny Prijono0a5cad82006-09-26 13:21:02 +0000325 OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY,
326 OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000327 OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
Benny Prijonoebbf6892007-03-24 17:37:25 +0000328 OPT_NAMESERVER, OPT_STUN_DOMAIN, OPT_STUN_SRV,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000329 OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
330 OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000331 OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_USE_ICE,
Benny Prijono4af234b2007-01-24 02:02:09 +0000332 OPT_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC,
333 OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC,
Benny Prijono0a12f002006-07-26 17:05:39 +0000334 OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
Benny Prijonod79f25c2006-08-02 19:41:37 +0000335 OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL,
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000336 OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
Benny Prijonof521eb02006-08-06 23:07:25 +0000337 OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000338 OPT_NOREFERSUB,
Benny Prijonof3bbc132006-12-25 06:43:59 +0000339 OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_CERT_FILE, OPT_TLS_PRIV_FILE,
340 OPT_TLS_PASSWORD, OPT_TLS_VERIFY_SERVER, OPT_TLS_VERIFY_CLIENT,
341 OPT_TLS_NEG_TIMEOUT,
Benny Prijono4e5d5512007-03-06 18:11:30 +0000342 OPT_CAPTURE_DEV, OPT_PLAYBACK_DEV,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000343 };
344 struct pj_getopt_option long_options[] = {
345 { "config-file",1, 0, OPT_CONFIG_FILE},
346 { "log-file", 1, 0, OPT_LOG_FILE},
347 { "log-level", 1, 0, OPT_LOG_LEVEL},
348 { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
349 { "help", 0, 0, OPT_HELP},
350 { "version", 0, 0, OPT_VERSION},
351 { "clock-rate", 1, 0, OPT_CLOCK_RATE},
352 { "null-audio", 0, 0, OPT_NULL_AUDIO},
353 { "local-port", 1, 0, OPT_LOCAL_PORT},
Benny Prijono0a5cad82006-09-26 13:21:02 +0000354 { "ip-addr", 1, 0, OPT_IP_ADDR},
Benny Prijonoe93e2872006-06-28 16:46:49 +0000355 { "no-tcp", 0, 0, OPT_NO_TCP},
356 { "no-udp", 0, 0, OPT_NO_UDP},
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000357 { "norefersub", 0, 0, OPT_NOREFERSUB},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000358 { "proxy", 1, 0, OPT_PROXY},
359 { "outbound", 1, 0, OPT_OUTBOUND_PROXY},
360 { "registrar", 1, 0, OPT_REGISTRAR},
361 { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000362 { "publish", 0, 0, OPT_PUBLISH},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000363 { "id", 1, 0, OPT_ID},
364 { "contact", 1, 0, OPT_CONTACT},
365 { "realm", 1, 0, OPT_REALM},
366 { "username", 1, 0, OPT_USERNAME},
367 { "password", 1, 0, OPT_PASSWORD},
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000368 { "nameserver", 1, 0, OPT_NAMESERVER},
Benny Prijonoebbf6892007-03-24 17:37:25 +0000369 { "stun-domain",1, 0, OPT_STUN_DOMAIN},
Benny Prijonoc97608e2007-03-23 16:34:20 +0000370 { "stun-srv", 1, 0, OPT_STUN_SRV},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000371 { "add-buddy", 1, 0, OPT_ADD_BUDDY},
372 { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
373 { "no-presence", 0, 0, OPT_NO_PRESENCE},
374 { "auto-answer",1, 0, OPT_AUTO_ANSWER},
375 { "auto-hangup",1, 0, OPT_AUTO_HANGUP},
376 { "auto-play", 0, 0, OPT_AUTO_PLAY},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000377 { "auto-rec", 0, 0, OPT_AUTO_REC},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000378 { "auto-loop", 0, 0, OPT_AUTO_LOOP},
379 { "auto-conf", 0, 0, OPT_AUTO_CONF},
380 { "play-file", 1, 0, OPT_PLAY_FILE},
Benny Prijono4af234b2007-01-24 02:02:09 +0000381 { "play-tone", 1, 0, OPT_PLAY_TONE},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000382 { "rec-file", 1, 0, OPT_REC_FILE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000383 { "rtp-port", 1, 0, OPT_RTP_PORT},
Benny Prijonoc97608e2007-03-23 16:34:20 +0000384 { "use-ice", 0, 0, OPT_USE_ICE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000385 { "add-codec", 1, 0, OPT_ADD_CODEC},
386 { "complexity", 1, 0, OPT_COMPLEXITY},
387 { "quality", 1, 0, OPT_QUALITY},
388 { "ptime", 1, 0, OPT_PTIME},
Benny Prijono0a12f002006-07-26 17:05:39 +0000389 { "no-vad", 0, 0, OPT_NO_VAD},
Benny Prijonod79f25c2006-08-02 19:41:37 +0000390 { "ec-tail", 1, 0, OPT_EC_TAIL},
Benny Prijono00cae612006-07-31 15:19:36 +0000391 { "ilbc-mode", 1, 0, OPT_ILBC_MODE},
392 { "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
393 { "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000394 { "next-account",0,0, OPT_NEXT_ACCOUNT},
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000395 { "next-cred", 0, 0, OPT_NEXT_CRED},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000396 { "max-calls", 1, 0, OPT_MAX_CALLS},
Benny Prijonof521eb02006-08-06 23:07:25 +0000397 { "duration", 1, 0, OPT_DURATION},
398 { "thread-cnt", 1, 0, OPT_THREAD_CNT},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000399 { "use-tls", 0, 0, OPT_USE_TLS},
400 { "tls-ca-file",1, 0, OPT_TLS_CA_FILE},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000401 { "tls-cert-file",1,0, OPT_TLS_CERT_FILE},
402 { "tls-privkey-file",1,0, OPT_TLS_PRIV_FILE},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000403 { "tls-password",1,0, OPT_TLS_PASSWORD},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000404 { "tls-verify-server", 0, 0, OPT_TLS_VERIFY_SERVER},
405 { "tls-verify-client", 0, 0, OPT_TLS_VERIFY_CLIENT},
406 { "tls-neg-timeout", 1, 0, OPT_TLS_NEG_TIMEOUT},
Benny Prijono4e5d5512007-03-06 18:11:30 +0000407 { "capture-dev", 1, 0, OPT_CAPTURE_DEV},
408 { "playback-dev", 1, 0, OPT_PLAYBACK_DEV},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000409 { NULL, 0, 0, 0}
410 };
411 pj_status_t status;
412 pjsua_acc_config *cur_acc;
413 char *config_file = NULL;
414 unsigned i;
415
416 /* Run pj_getopt once to see if user specifies config file to read. */
Benny Prijonof762ee72006-12-01 11:14:37 +0000417 pj_optind = 0;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000418 while ((c=pj_getopt_long(argc, argv, "", long_options,
419 &option_index)) != -1)
420 {
421 switch (c) {
422 case OPT_CONFIG_FILE:
423 config_file = pj_optarg;
424 break;
425 }
426 if (config_file)
427 break;
428 }
429
430 if (config_file) {
431 status = read_config_file(app_config.pool, config_file, &argc, &argv);
432 if (status != 0)
433 return status;
434 }
435
436 cfg->acc_cnt = 0;
437 cur_acc = &cfg->acc_cfg[0];
438
439
440 /* Reinitialize and re-run pj_getopt again, possibly with new arguments
441 * read from config file.
442 */
443 pj_optind = 0;
444 while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000445 pj_str_t tmp;
446 long lval;
447
448 switch (c) {
449
Benny Prijono6f137482006-06-15 11:31:36 +0000450 case OPT_CONFIG_FILE:
451 /* Ignore as this has been processed before */
452 break;
453
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000454 case OPT_LOG_FILE:
455 cfg->log_cfg.log_filename = pj_str(pj_optarg);
456 break;
457
458 case OPT_LOG_LEVEL:
459 c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
460 if (c < 0 || c > 6) {
461 PJ_LOG(1,(THIS_FILE,
462 "Error: expecting integer value 0-6 "
463 "for --log-level"));
464 return PJ_EINVAL;
465 }
466 cfg->log_cfg.level = c;
467 pj_log_set_level( c );
468 break;
469
470 case OPT_APP_LOG_LEVEL:
471 cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
472 if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
473 PJ_LOG(1,(THIS_FILE,
474 "Error: expecting integer value 0-6 "
475 "for --app-log-level"));
476 return PJ_EINVAL;
477 }
478 break;
479
480 case OPT_HELP:
481 usage();
482 return PJ_EINVAL;
483
484 case OPT_VERSION: /* version */
485 pj_dump_config();
486 return PJ_EINVAL;
487
488 case OPT_NULL_AUDIO:
489 cfg->null_audio = PJ_TRUE;
490 break;
491
492 case OPT_CLOCK_RATE:
493 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
494 if (lval < 8000 || lval > 48000) {
495 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
496 "8000-48000 for clock rate"));
497 return PJ_EINVAL;
498 }
499 cfg->media_cfg.clock_rate = lval;
500 break;
501
502 case OPT_LOCAL_PORT: /* local-port */
503 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
Benny Prijonoe347cb02007-02-14 14:36:13 +0000504 if (lval < 0 || lval > 65535) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000505 PJ_LOG(1,(THIS_FILE,
506 "Error: expecting integer value for "
507 "--local-port"));
508 return PJ_EINVAL;
509 }
510 cfg->udp_cfg.port = (pj_uint16_t)lval;
511 break;
512
Benny Prijono0a5cad82006-09-26 13:21:02 +0000513 case OPT_IP_ADDR: /* ip-addr */
514 cfg->udp_cfg.public_addr = pj_str(pj_optarg);
515 cfg->rtp_cfg.public_addr = pj_str(pj_optarg);
516 break;
517
Benny Prijonoe93e2872006-06-28 16:46:49 +0000518 case OPT_NO_UDP: /* no-udp */
519 if (cfg->no_tcp) {
520 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
521 return PJ_EINVAL;
522 }
523
524 cfg->no_udp = PJ_TRUE;
525 break;
526
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000527 case OPT_NOREFERSUB: /* norefersub */
528 cfg->no_refersub = PJ_TRUE;
529 break;
530
Benny Prijonoe93e2872006-06-28 16:46:49 +0000531 case OPT_NO_TCP: /* no-tcp */
532 if (cfg->no_udp) {
533 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
534 return PJ_EINVAL;
535 }
536
537 cfg->no_tcp = PJ_TRUE;
538 break;
539
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000540 case OPT_PROXY: /* proxy */
541 if (pjsua_verify_sip_url(pj_optarg) != 0) {
542 PJ_LOG(1,(THIS_FILE,
543 "Error: invalid SIP URL '%s' "
544 "in proxy argument", pj_optarg));
545 return PJ_EINVAL;
546 }
547 cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
548 break;
549
550 case OPT_OUTBOUND_PROXY: /* outbound proxy */
551 if (pjsua_verify_sip_url(pj_optarg) != 0) {
552 PJ_LOG(1,(THIS_FILE,
553 "Error: invalid SIP URL '%s' "
554 "in outbound proxy argument", pj_optarg));
555 return PJ_EINVAL;
556 }
557 cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
558 break;
559
560 case OPT_REGISTRAR: /* registrar */
561 if (pjsua_verify_sip_url(pj_optarg) != 0) {
562 PJ_LOG(1,(THIS_FILE,
563 "Error: invalid SIP URL '%s' in "
564 "registrar argument", pj_optarg));
565 return PJ_EINVAL;
566 }
567 cur_acc->reg_uri = pj_str(pj_optarg);
568 break;
569
570 case OPT_REG_TIMEOUT: /* reg-timeout */
571 cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
572 if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
573 PJ_LOG(1,(THIS_FILE,
574 "Error: invalid value for --reg-timeout "
575 "(expecting 1-3600)"));
576 return PJ_EINVAL;
577 }
578 break;
579
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000580 case OPT_PUBLISH: /* publish */
581 cur_acc->publish_enabled = PJ_TRUE;
582 break;
583
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000584 case OPT_ID: /* id */
585 if (pjsua_verify_sip_url(pj_optarg) != 0) {
586 PJ_LOG(1,(THIS_FILE,
587 "Error: invalid SIP URL '%s' "
588 "in local id argument", pj_optarg));
589 return PJ_EINVAL;
590 }
591 cur_acc->id = pj_str(pj_optarg);
592 break;
593
594 case OPT_CONTACT: /* contact */
595 if (pjsua_verify_sip_url(pj_optarg) != 0) {
596 PJ_LOG(1,(THIS_FILE,
597 "Error: invalid SIP URL '%s' "
598 "in contact argument", pj_optarg));
599 return PJ_EINVAL;
600 }
Benny Prijonob4a17c92006-07-10 14:40:21 +0000601 cur_acc->force_contact = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000602 break;
603
604 case OPT_NEXT_ACCOUNT: /* Add more account. */
605 cfg->acc_cnt++;
Benny Prijono56315612006-07-18 14:39:40 +0000606 cur_acc = &cfg->acc_cfg[cfg->acc_cnt];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000607 break;
608
609 case OPT_USERNAME: /* Default authentication user */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000610 cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
611 cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("digest");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000612 break;
613
614 case OPT_REALM: /* Default authentication realm. */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000615 cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000616 break;
617
618 case OPT_PASSWORD: /* authentication password */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000619 cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
620 cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
621 break;
622
623 case OPT_NEXT_CRED: /* next credential */
624 cur_acc->cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000625 break;
626
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000627 case OPT_NAMESERVER: /* nameserver */
628 cfg->cfg.nameserver[cfg->cfg.nameserver_count++] = pj_str(pj_optarg);
629 if (cfg->cfg.nameserver_count > PJ_ARRAY_SIZE(cfg->cfg.nameserver)) {
630 PJ_LOG(1,(THIS_FILE, "Error: too many nameservers"));
631 return PJ_ETOOMANY;
632 }
633 break;
634
Benny Prijonoebbf6892007-03-24 17:37:25 +0000635 case OPT_STUN_DOMAIN: /* STUN domain */
636 cfg->cfg.stun_domain = pj_str(pj_optarg);
637 break;
638
Benny Prijonoc97608e2007-03-23 16:34:20 +0000639 case OPT_STUN_SRV: /* STUN server */
Benny Prijonoebbf6892007-03-24 17:37:25 +0000640 cfg->cfg.stun_host = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000641 break;
642
643 case OPT_ADD_BUDDY: /* Add to buddy list. */
644 if (pjsua_verify_sip_url(pj_optarg) != 0) {
645 PJ_LOG(1,(THIS_FILE,
646 "Error: invalid URL '%s' in "
647 "--add-buddy option", pj_optarg));
648 return -1;
649 }
650 if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
651 PJ_LOG(1,(THIS_FILE,
652 "Error: too many buddies in buddy list."));
653 return -1;
654 }
655 cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
656 cfg->buddy_cnt++;
657 break;
658
659 case OPT_AUTO_PLAY:
660 cfg->auto_play = 1;
661 break;
662
Benny Prijono1ebd6142006-10-19 15:48:02 +0000663 case OPT_AUTO_REC:
664 cfg->auto_rec = 1;
665 break;
666
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000667 case OPT_AUTO_LOOP:
668 cfg->auto_loop = 1;
669 break;
670
Benny Prijono7ca96da2006-08-07 12:11:40 +0000671 case OPT_AUTO_CONF:
672 cfg->auto_conf = 1;
673 break;
674
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000675 case OPT_PLAY_FILE:
Benny Prijono32e4f492007-01-24 00:44:26 +0000676 cfg->wav_files[cfg->wav_count++] = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000677 break;
678
Benny Prijono4af234b2007-01-24 02:02:09 +0000679 case OPT_PLAY_TONE:
680 {
681 int f1, f2, on, off;
682 int n;
683
684 n = sscanf(pj_optarg, "%d,%d,%d,%d", &f1, &f2, &on, &off);
685 if (n != 4) {
686 puts("Expecting f1,f2,on,off in --play-tone");
687 return -1;
688 }
689
690 cfg->tones[cfg->tone_count].freq1 = (short)f1;
691 cfg->tones[cfg->tone_count].freq2 = (short)f2;
692 cfg->tones[cfg->tone_count].on_msec = (short)on;
693 cfg->tones[cfg->tone_count].off_msec = (short)off;
694 ++cfg->tone_count;
695 }
696 break;
697
Benny Prijono1ebd6142006-10-19 15:48:02 +0000698 case OPT_REC_FILE:
699 cfg->rec_file = pj_str(pj_optarg);
700 break;
701
Benny Prijonoc97608e2007-03-23 16:34:20 +0000702 case OPT_USE_ICE:
703 cfg->media_cfg.enable_ice = PJ_TRUE;
704 break;
705
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000706 case OPT_RTP_PORT:
707 cfg->rtp_cfg.port = my_atoi(pj_optarg);
708 if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
709 PJ_LOG(1,(THIS_FILE,
710 "Error: rtp-port argument value "
711 "(expecting 1-65535"));
712 return -1;
713 }
714 break;
715
716 case OPT_ADD_CODEC:
717 cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
718 break;
719
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000720 /* These options were no longer valid after new pjsua */
721 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000722 case OPT_COMPLEXITY:
723 cfg->complexity = my_atoi(pj_optarg);
724 if (cfg->complexity < 0 || cfg->complexity > 10) {
725 PJ_LOG(1,(THIS_FILE,
726 "Error: invalid --complexity (expecting 0-10"));
727 return -1;
728 }
729 break;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000730 */
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000731
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000732 case OPT_DURATION:
733 cfg->duration = my_atoi(pj_optarg);
734 break;
735
Benny Prijonof521eb02006-08-06 23:07:25 +0000736 case OPT_THREAD_CNT:
737 cfg->cfg.thread_cnt = my_atoi(pj_optarg);
738 if (cfg->cfg.thread_cnt > 128) {
739 PJ_LOG(1,(THIS_FILE,
740 "Error: invalid --thread-cnt option"));
741 return -1;
742 }
743 break;
744
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000745 case OPT_PTIME:
Benny Prijono0a12f002006-07-26 17:05:39 +0000746 cfg->media_cfg.ptime = my_atoi(pj_optarg);
747 if (cfg->media_cfg.ptime < 10 || cfg->media_cfg.ptime > 1000) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000748 PJ_LOG(1,(THIS_FILE,
749 "Error: invalid --ptime option"));
750 return -1;
751 }
752 break;
753
Benny Prijono0a12f002006-07-26 17:05:39 +0000754 case OPT_NO_VAD:
755 cfg->media_cfg.no_vad = PJ_TRUE;
756 break;
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000757
Benny Prijonod79f25c2006-08-02 19:41:37 +0000758 case OPT_EC_TAIL:
759 cfg->media_cfg.ec_tail_len = my_atoi(pj_optarg);
760 if (cfg->media_cfg.ec_tail_len > 1000) {
761 PJ_LOG(1,(THIS_FILE, "I think the ec-tail length setting "
762 "is too big"));
763 return -1;
764 }
765 break;
766
Benny Prijono0498d902006-06-19 14:49:14 +0000767 case OPT_QUALITY:
768 cfg->media_cfg.quality = my_atoi(pj_optarg);
769 if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
770 PJ_LOG(1,(THIS_FILE,
771 "Error: invalid --quality (expecting 0-10"));
772 return -1;
773 }
774 break;
775
Benny Prijono00cae612006-07-31 15:19:36 +0000776 case OPT_ILBC_MODE:
777 cfg->media_cfg.ilbc_mode = my_atoi(pj_optarg);
778 if (cfg->media_cfg.ilbc_mode!=20 && cfg->media_cfg.ilbc_mode!=30) {
779 PJ_LOG(1,(THIS_FILE,
780 "Error: invalid --ilbc-mode (expecting 20 or 30"));
781 return -1;
782 }
783 break;
784
785 case OPT_RX_DROP_PCT:
786 cfg->media_cfg.rx_drop_pct = my_atoi(pj_optarg);
787 if (cfg->media_cfg.rx_drop_pct > 100) {
788 PJ_LOG(1,(THIS_FILE,
789 "Error: invalid --rx-drop-pct (expecting <= 100"));
790 return -1;
791 }
792 break;
793
794 case OPT_TX_DROP_PCT:
795 cfg->media_cfg.tx_drop_pct = my_atoi(pj_optarg);
796 if (cfg->media_cfg.tx_drop_pct > 100) {
797 PJ_LOG(1,(THIS_FILE,
798 "Error: invalid --tx-drop-pct (expecting <= 100"));
799 return -1;
800 }
801 break;
802
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000803 case OPT_AUTO_ANSWER:
804 cfg->auto_answer = my_atoi(pj_optarg);
805 if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
806 PJ_LOG(1,(THIS_FILE,
807 "Error: invalid code in --auto-answer "
808 "(expecting 100-699"));
809 return -1;
810 }
811 break;
812
813 case OPT_MAX_CALLS:
814 cfg->cfg.max_calls = my_atoi(pj_optarg);
Benny Prijono48af79c2006-07-22 12:49:17 +0000815 if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > PJSUA_MAX_CALLS) {
Benny Prijono804ff0a2006-09-14 11:17:48 +0000816 PJ_LOG(1,(THIS_FILE,"Error: maximum call setting exceeds "
817 "compile time limit (PJSUA_MAX_CALLS=%d)",
Benny Prijono48af79c2006-07-22 12:49:17 +0000818 PJSUA_MAX_CALLS));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000819 return -1;
820 }
821 break;
822
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000823 case OPT_USE_TLS:
824 cfg->use_tls = PJ_TRUE;
Benny Prijonof3bbc132006-12-25 06:43:59 +0000825#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
826 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
827 return -1;
828#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000829 break;
830
831 case OPT_TLS_CA_FILE:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000832 cfg->udp_cfg.tls_setting.ca_list_file = pj_str(pj_optarg);
833#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
834 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
835 return -1;
836#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000837 break;
838
Benny Prijonof3bbc132006-12-25 06:43:59 +0000839 case OPT_TLS_CERT_FILE:
840 cfg->udp_cfg.tls_setting.cert_file = pj_str(pj_optarg);
841#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
842 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
843 return -1;
844#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000845 break;
846
Benny Prijonof3bbc132006-12-25 06:43:59 +0000847 case OPT_TLS_PRIV_FILE:
848 cfg->udp_cfg.tls_setting.privkey_file = pj_str(pj_optarg);
849 break;
850
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000851 case OPT_TLS_PASSWORD:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000852 cfg->udp_cfg.tls_setting.password = pj_str(pj_optarg);
853#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
854 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
855 return -1;
856#endif
857 break;
858
859 case OPT_TLS_VERIFY_SERVER:
860 cfg->udp_cfg.tls_setting.verify_server = PJ_TRUE;
861 break;
862
863 case OPT_TLS_VERIFY_CLIENT:
864 cfg->udp_cfg.tls_setting.verify_client = PJ_TRUE;
865 break;
866
867 case OPT_TLS_NEG_TIMEOUT:
868 cfg->udp_cfg.tls_setting.timeout.sec = atoi(pj_optarg);
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000869 break;
870
Benny Prijono4e5d5512007-03-06 18:11:30 +0000871 case OPT_CAPTURE_DEV:
872 cfg->capture_dev = atoi(pj_optarg);
873 break;
874
875 case OPT_PLAYBACK_DEV:
876 cfg->playback_dev = atoi(pj_optarg);
877 break;
878
Benny Prijono22a300a2006-06-14 20:04:55 +0000879 default:
Benny Prijono787b8692006-06-19 12:40:03 +0000880 PJ_LOG(1,(THIS_FILE,
Benny Prijonod6388ac2006-09-09 13:23:09 +0000881 "Argument \"%s\" is not valid. Use --help to see help",
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000882 argv[pj_optind-1]));
Benny Prijono22a300a2006-06-14 20:04:55 +0000883 return -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000884 }
885 }
886
887 if (pj_optind != argc) {
888 pj_str_t uri_arg;
889
890 if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
891 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
892 return -1;
893 }
894 uri_arg = pj_str(argv[pj_optind]);
895 if (uri_to_call)
896 *uri_to_call = uri_arg;
897 pj_optind++;
898
899 /* Add URI to call to buddy list if it's not already there */
900 for (i=0; i<cfg->buddy_cnt; ++i) {
901 if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
902 break;
903 }
904 if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
905 cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
906 }
907
908 } else {
909 if (uri_to_call)
910 uri_to_call->slen = 0;
911 }
912
913 if (pj_optind != argc) {
914 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
915 return PJ_EINVAL;
916 }
917
Benny Prijono56315612006-07-18 14:39:40 +0000918 if (cfg->acc_cfg[cfg->acc_cnt].id.slen)
919 cfg->acc_cnt++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000920
921 for (i=0; i<cfg->acc_cnt; ++i) {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000922 if (cfg->acc_cfg[i].cred_info[cfg->acc_cfg[i].cred_count].username.slen)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000923 {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000924 cfg->acc_cfg[i].cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000925 }
926 }
927
928
929 return PJ_SUCCESS;
930}
931
932
933/*
934 * Save account settings
935 */
936static void write_account_settings(int acc_index, pj_str_t *result)
937{
938 unsigned i;
939 char line[128];
940 pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
941
942
943 pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
944 pj_strcat2(result, line);
945
946
947 /* Identity */
948 if (acc_cfg->id.slen) {
949 pj_ansi_sprintf(line, "--id %.*s\n",
950 (int)acc_cfg->id.slen,
951 acc_cfg->id.ptr);
952 pj_strcat2(result, line);
953 }
954
955 /* Registrar server */
956 if (acc_cfg->reg_uri.slen) {
957 pj_ansi_sprintf(line, "--registrar %.*s\n",
958 (int)acc_cfg->reg_uri.slen,
959 acc_cfg->reg_uri.ptr);
960 pj_strcat2(result, line);
961
962 pj_ansi_sprintf(line, "--reg-timeout %u\n",
963 acc_cfg->reg_timeout);
964 pj_strcat2(result, line);
965 }
966
967 /* Contact */
Benny Prijonob4a17c92006-07-10 14:40:21 +0000968 if (acc_cfg->force_contact.slen) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000969 pj_ansi_sprintf(line, "--contact %.*s\n",
Benny Prijonob4a17c92006-07-10 14:40:21 +0000970 (int)acc_cfg->force_contact.slen,
971 acc_cfg->force_contact.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000972 pj_strcat2(result, line);
973 }
974
975 /* Proxy */
976 for (i=0; i<acc_cfg->proxy_cnt; ++i) {
977 pj_ansi_sprintf(line, "--proxy %.*s\n",
978 (int)acc_cfg->proxy[i].slen,
979 acc_cfg->proxy[i].ptr);
980 pj_strcat2(result, line);
981 }
982
983 /* Credentials */
984 for (i=0; i<acc_cfg->cred_count; ++i) {
985 if (acc_cfg->cred_info[i].realm.slen) {
986 pj_ansi_sprintf(line, "--realm %.*s\n",
987 (int)acc_cfg->cred_info[i].realm.slen,
988 acc_cfg->cred_info[i].realm.ptr);
989 pj_strcat2(result, line);
990 }
991
992 if (acc_cfg->cred_info[i].username.slen) {
993 pj_ansi_sprintf(line, "--username %.*s\n",
994 (int)acc_cfg->cred_info[i].username.slen,
995 acc_cfg->cred_info[i].username.ptr);
996 pj_strcat2(result, line);
997 }
998
999 if (acc_cfg->cred_info[i].data.slen) {
1000 pj_ansi_sprintf(line, "--password %.*s\n",
1001 (int)acc_cfg->cred_info[i].data.slen,
1002 acc_cfg->cred_info[i].data.ptr);
1003 pj_strcat2(result, line);
1004 }
Benny Prijonoeef4f8c2006-06-19 12:07:44 +00001005
1006 if (i != acc_cfg->cred_count - 1)
1007 pj_strcat2(result, "--next-cred\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001008 }
1009
1010}
1011
1012
1013/*
1014 * Write settings.
1015 */
1016static int write_settings(const struct app_config *config,
1017 char *buf, pj_size_t max)
1018{
1019 unsigned acc_index;
1020 unsigned i;
1021 pj_str_t cfg;
1022 char line[128];
1023
1024 PJ_UNUSED_ARG(max);
1025
1026 cfg.ptr = buf;
1027 cfg.slen = 0;
1028
1029 /* Logging. */
1030 pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
1031 pj_ansi_sprintf(line, "--log-level %d\n",
1032 config->log_cfg.level);
1033 pj_strcat2(&cfg, line);
1034
1035 pj_ansi_sprintf(line, "--app-log-level %d\n",
1036 config->log_cfg.console_level);
1037 pj_strcat2(&cfg, line);
1038
1039 if (config->log_cfg.log_filename.slen) {
1040 pj_ansi_sprintf(line, "--log-file %.*s\n",
1041 (int)config->log_cfg.log_filename.slen,
1042 config->log_cfg.log_filename.ptr);
1043 pj_strcat2(&cfg, line);
1044 }
1045
1046
1047 /* Save account settings. */
1048 for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
1049
1050 write_account_settings(acc_index, &cfg);
1051
1052 if (acc_index < config->acc_cnt-1)
1053 pj_strcat2(&cfg, "--next-account\n");
1054 }
1055
1056
1057 pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
1058
1059 /* Outbound proxy */
1060 for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
1061 pj_ansi_sprintf(line, "--outbound %.*s\n",
1062 (int)config->cfg.outbound_proxy[i].slen,
1063 config->cfg.outbound_proxy[i].ptr);
1064 pj_strcat2(&cfg, line);
1065 }
1066
1067
1068 /* UDP Transport. */
1069 pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
1070 pj_strcat2(&cfg, line);
1071
Benny Prijono0a5cad82006-09-26 13:21:02 +00001072 /* IP address, if any. */
1073 if (config->udp_cfg.public_addr.slen) {
1074 pj_ansi_sprintf(line, "--ip-addr %.*s\n",
1075 (int)config->udp_cfg.public_addr.slen,
1076 config->udp_cfg.public_addr.ptr);
1077 pj_strcat2(&cfg, line);
1078 }
1079
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001080 /* No TCP ? */
1081 if (config->no_tcp) {
1082 pj_strcat2(&cfg, "--no-tcp\n");
1083 }
1084
1085 /* No UDP ? */
1086 if (config->no_udp) {
1087 pj_strcat2(&cfg, "--no-udp\n");
1088 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001089
1090 /* STUN */
Benny Prijonoebbf6892007-03-24 17:37:25 +00001091 if (config->cfg.stun_domain.slen) {
1092 pj_ansi_sprintf(line, "--stun-domain %.*s\n",
1093 (int)config->cfg.stun_domain.slen,
1094 config->cfg.stun_domain.ptr);
1095 pj_strcat2(&cfg, line);
1096 }
1097 if (config->cfg.stun_host.slen) {
Benny Prijonoc97608e2007-03-23 16:34:20 +00001098 pj_ansi_sprintf(line, "--stun-srv %.*s\n",
Benny Prijonoebbf6892007-03-24 17:37:25 +00001099 (int)config->cfg.stun_host.slen,
1100 config->cfg.stun_host.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001101 pj_strcat2(&cfg, line);
1102 }
1103
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001104
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001105 /* TLS */
1106 if (config->use_tls)
1107 pj_strcat2(&cfg, "--use-tls\n");
Benny Prijonof3bbc132006-12-25 06:43:59 +00001108 if (config->udp_cfg.tls_setting.ca_list_file.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001109 pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001110 (int)config->udp_cfg.tls_setting.ca_list_file.slen,
1111 config->udp_cfg.tls_setting.ca_list_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001112 pj_strcat2(&cfg, line);
1113 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001114 if (config->udp_cfg.tls_setting.cert_file.slen) {
1115 pj_ansi_sprintf(line, "--tls-cert-file %.*s\n",
1116 (int)config->udp_cfg.tls_setting.cert_file.slen,
1117 config->udp_cfg.tls_setting.cert_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001118 pj_strcat2(&cfg, line);
1119 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001120 if (config->udp_cfg.tls_setting.privkey_file.slen) {
1121 pj_ansi_sprintf(line, "--tls-privkey-file %.*s\n",
1122 (int)config->udp_cfg.tls_setting.privkey_file.slen,
1123 config->udp_cfg.tls_setting.privkey_file.ptr);
1124 pj_strcat2(&cfg, line);
1125 }
1126
1127 if (config->udp_cfg.tls_setting.password.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001128 pj_ansi_sprintf(line, "--tls-password %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001129 (int)config->udp_cfg.tls_setting.password.slen,
1130 config->udp_cfg.tls_setting.password.ptr);
1131 pj_strcat2(&cfg, line);
1132 }
1133
1134 if (config->udp_cfg.tls_setting.verify_server)
1135 pj_strcat2(&cfg, "--tls-verify-server\n");
1136
1137 if (config->udp_cfg.tls_setting.verify_client)
1138 pj_strcat2(&cfg, "--tls-verify-client\n");
1139
1140 if (config->udp_cfg.tls_setting.timeout.sec) {
1141 pj_ansi_sprintf(line, "--tls-neg-timeout %d\n",
Benny Prijonoe960bb52007-01-21 17:53:39 +00001142 (int)config->udp_cfg.tls_setting.timeout.sec);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001143 pj_strcat2(&cfg, line);
1144 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001145
1146 pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
1147
1148
1149 /* Media */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001150 if (config->media_cfg.enable_ice)
1151 pj_strcat2(&cfg, "--use-ice\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001152 if (config->null_audio)
1153 pj_strcat2(&cfg, "--null-audio\n");
1154 if (config->auto_play)
1155 pj_strcat2(&cfg, "--auto-play\n");
1156 if (config->auto_loop)
1157 pj_strcat2(&cfg, "--auto-loop\n");
Benny Prijono7ca96da2006-08-07 12:11:40 +00001158 if (config->auto_conf)
1159 pj_strcat2(&cfg, "--auto-conf\n");
Benny Prijono32e4f492007-01-24 00:44:26 +00001160 for (i=0; i<config->wav_count; ++i) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001161 pj_ansi_sprintf(line, "--play-file %s\n",
Benny Prijono32e4f492007-01-24 00:44:26 +00001162 config->wav_files[i].ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001163 pj_strcat2(&cfg, line);
1164 }
Benny Prijono4af234b2007-01-24 02:02:09 +00001165 for (i=0; i<config->tone_count; ++i) {
1166 pj_ansi_sprintf(line, "--play-tone %d,%d,%d,%d\n",
1167 config->tones[i].freq1, config->tones[i].freq2,
1168 config->tones[i].on_msec, config->tones[i].off_msec);
1169 pj_strcat2(&cfg, line);
1170 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001171 if (config->rec_file.slen) {
1172 pj_ansi_sprintf(line, "--rec-file %s\n",
1173 config->rec_file.ptr);
1174 pj_strcat2(&cfg, line);
1175 }
1176 if (config->auto_rec)
1177 pj_strcat2(&cfg, "--auto-rec\n");
Benny Prijono4e5d5512007-03-06 18:11:30 +00001178 if (config->capture_dev != PJSUA_INVALID_ID) {
1179 pj_ansi_sprintf(line, "--capture-dev %d\n", config->capture_dev);
1180 pj_strcat2(&cfg, line);
1181 }
1182 if (config->playback_dev != PJSUA_INVALID_ID) {
1183 pj_ansi_sprintf(line, "--playback-dev %d\n", config->playback_dev);
1184 pj_strcat2(&cfg, line);
1185 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001186
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001187 /* Media clock rate. */
Benny Prijono70972992006-08-05 11:13:58 +00001188 if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001189 pj_ansi_sprintf(line, "--clock-rate %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001190 config->media_cfg.clock_rate);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001191 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001192 } else {
1193 pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
1194 config->media_cfg.clock_rate);
1195 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001196 }
Benny Prijono70972992006-08-05 11:13:58 +00001197
1198 /* quality */
1199 if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001200 pj_ansi_sprintf(line, "--quality %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001201 config->media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001202 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001203 } else {
1204 pj_ansi_sprintf(line, "#using default --quality %d\n",
1205 config->media_cfg.quality);
1206 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001207 }
Benny Prijono0498d902006-06-19 14:49:14 +00001208
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001209
1210 /* ptime */
Benny Prijono2adfe292007-05-11 10:36:08 +00001211 if (config->media_cfg.ptime) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001212 pj_ansi_sprintf(line, "--ptime %d\n",
Benny Prijono2adfe292007-05-11 10:36:08 +00001213 config->media_cfg.ptime);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001214 pj_strcat2(&cfg, line);
1215 }
1216
Benny Prijono70972992006-08-05 11:13:58 +00001217 /* no-vad */
1218 if (config->media_cfg.no_vad) {
1219 pj_strcat2(&cfg, "--no-vad\n");
1220 }
1221
1222 /* ec-tail */
1223 if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
1224 pj_ansi_sprintf(line, "--ec-tail %d\n",
1225 config->media_cfg.ec_tail_len);
1226 pj_strcat2(&cfg, line);
1227 } else {
1228 pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
1229 config->media_cfg.ec_tail_len);
1230 pj_strcat2(&cfg, line);
1231 }
1232
1233
1234 /* ilbc-mode */
1235 if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
1236 pj_ansi_sprintf(line, "--ilbc-mode %d\n",
1237 config->media_cfg.ilbc_mode);
1238 pj_strcat2(&cfg, line);
1239 } else {
1240 pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
1241 config->media_cfg.ilbc_mode);
1242 pj_strcat2(&cfg, line);
1243 }
1244
1245 /* RTP drop */
1246 if (config->media_cfg.tx_drop_pct) {
1247 pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
1248 config->media_cfg.tx_drop_pct);
1249 pj_strcat2(&cfg, line);
1250
1251 }
1252 if (config->media_cfg.rx_drop_pct) {
1253 pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
1254 config->media_cfg.rx_drop_pct);
1255 pj_strcat2(&cfg, line);
1256
1257 }
1258
1259
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001260 /* Start RTP port. */
1261 pj_ansi_sprintf(line, "--rtp-port %d\n",
1262 config->rtp_cfg.port);
1263 pj_strcat2(&cfg, line);
1264
1265 /* Add codec. */
1266 for (i=0; i<config->codec_cnt; ++i) {
1267 pj_ansi_sprintf(line, "--add-codec %s\n",
1268 config->codec_arg[i].ptr);
1269 pj_strcat2(&cfg, line);
1270 }
1271
1272 pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
1273
1274 /* Auto-answer. */
1275 if (config->auto_answer != 0) {
1276 pj_ansi_sprintf(line, "--auto-answer %d\n",
1277 config->auto_answer);
1278 pj_strcat2(&cfg, line);
1279 }
1280
1281 /* Max calls. */
1282 pj_ansi_sprintf(line, "--max-calls %d\n",
1283 config->cfg.max_calls);
1284 pj_strcat2(&cfg, line);
1285
1286 /* Uas-duration. */
Benny Prijono804ff0a2006-09-14 11:17:48 +00001287 if (config->duration != NO_LIMIT) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001288 pj_ansi_sprintf(line, "--duration %d\n",
1289 config->duration);
1290 pj_strcat2(&cfg, line);
1291 }
1292
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001293 /* norefersub ? */
1294 if (config->no_refersub) {
1295 pj_strcat2(&cfg, "--norefersub\n");
1296 }
1297
1298
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001299 pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
1300
1301 /* Add buddies. */
1302 for (i=0; i<config->buddy_cnt; ++i) {
1303 pj_ansi_sprintf(line, "--add-buddy %.*s\n",
1304 (int)config->buddy_cfg[i].uri.slen,
1305 config->buddy_cfg[i].uri.ptr);
1306 pj_strcat2(&cfg, line);
1307 }
1308
1309
1310 *(cfg.ptr + cfg.slen) = '\0';
1311 return cfg.slen;
1312}
1313
1314
1315/*
1316 * Dump application states.
1317 */
1318static void app_dump(pj_bool_t detail)
1319{
Benny Prijonoda9785b2007-04-02 20:43:06 +00001320 pjsua_dump(detail);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001321}
1322
1323
1324/*****************************************************************************
1325 * Console application
1326 */
1327
1328/*
1329 * Find next call when current call is disconnected or when user
1330 * press ']'
1331 */
1332static pj_bool_t find_next_call(void)
1333{
1334 int i, max;
1335
1336 max = pjsua_call_get_max_count();
1337 for (i=current_call+1; i<max; ++i) {
1338 if (pjsua_call_is_active(i)) {
1339 current_call = i;
1340 return PJ_TRUE;
1341 }
1342 }
1343
1344 for (i=0; i<current_call; ++i) {
1345 if (pjsua_call_is_active(i)) {
1346 current_call = i;
1347 return PJ_TRUE;
1348 }
1349 }
1350
1351 current_call = PJSUA_INVALID_ID;
1352 return PJ_FALSE;
1353}
1354
1355
1356/*
1357 * Find previous call when user press '['
1358 */
1359static pj_bool_t find_prev_call(void)
1360{
1361 int i, max;
1362
1363 max = pjsua_call_get_max_count();
1364 for (i=current_call-1; i>=0; --i) {
1365 if (pjsua_call_is_active(i)) {
1366 current_call = i;
1367 return PJ_TRUE;
1368 }
1369 }
1370
1371 for (i=max-1; i>current_call; --i) {
1372 if (pjsua_call_is_active(i)) {
1373 current_call = i;
1374 return PJ_TRUE;
1375 }
1376 }
1377
1378 current_call = PJSUA_INVALID_ID;
1379 return PJ_FALSE;
1380}
1381
1382
Benny Prijono804ff0a2006-09-14 11:17:48 +00001383/* Callback from timer when the maximum call duration has been
1384 * exceeded.
1385 */
1386static void call_timeout_callback(pj_timer_heap_t *timer_heap,
1387 struct pj_timer_entry *entry)
1388{
1389 pjsua_call_id call_id = entry->id;
1390 pjsua_msg_data msg_data;
1391 pjsip_generic_string_hdr warn;
1392 pj_str_t hname = pj_str("Warning");
1393 pj_str_t hvalue = pj_str("399 pjsua \"Call duration exceeded\"");
1394
1395 PJ_UNUSED_ARG(timer_heap);
1396
Benny Prijono148c9dd2006-09-19 13:37:53 +00001397 if (call_id == PJSUA_INVALID_ID) {
1398 PJ_LOG(1,(THIS_FILE, "Invalid call ID in timer callback"));
1399 return;
1400 }
1401
Benny Prijono804ff0a2006-09-14 11:17:48 +00001402 /* Add warning header */
1403 pjsua_msg_data_init(&msg_data);
1404 pjsip_generic_string_hdr_init2(&warn, &hname, &hvalue);
1405 pj_list_push_back(&msg_data.hdr_list, &warn);
1406
1407 /* Call duration has been exceeded; disconnect the call */
1408 PJ_LOG(3,(THIS_FILE, "Duration (%d seconds) has been exceeded "
1409 "for call %d, disconnecting the call",
1410 app_config.duration, call_id));
1411 entry->id = PJSUA_INVALID_ID;
1412 pjsua_call_hangup(call_id, 200, NULL, &msg_data);
1413}
1414
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001415
1416/*
1417 * Handler when invite state has changed.
1418 */
1419static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
1420{
1421 pjsua_call_info call_info;
1422
1423 PJ_UNUSED_ARG(e);
1424
1425 pjsua_call_get_info(call_id, &call_info);
1426
1427 if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
1428
Benny Prijono804ff0a2006-09-14 11:17:48 +00001429 /* Cancel duration timer, if any */
1430 if (app_config.call_data[call_id].timer.id != PJSUA_INVALID_ID) {
1431 struct call_data *cd = &app_config.call_data[call_id];
1432 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1433
1434 cd->timer.id = PJSUA_INVALID_ID;
1435 pjsip_endpt_cancel_timer(endpt, &cd->timer);
1436 }
1437
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001438 PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",
1439 call_id,
1440 call_info.last_status,
1441 call_info.last_status_text.ptr));
1442
1443 if (call_id == current_call) {
1444 find_next_call();
1445 }
1446
Benny Prijono4be63b52006-11-25 14:50:25 +00001447 /* Dump media state upon disconnected */
1448 if (1) {
1449 char buf[1024];
1450 pjsua_call_dump(call_id, PJ_TRUE, buf,
1451 sizeof(buf), " ");
1452 PJ_LOG(5,(THIS_FILE,
1453 "Call %d disconnected, dumping media stats\n%s",
1454 call_id, buf));
1455 }
1456
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001457 } else {
1458
Benny Prijono804ff0a2006-09-14 11:17:48 +00001459 if (app_config.duration!=NO_LIMIT &&
1460 call_info.state == PJSIP_INV_STATE_CONFIRMED)
1461 {
1462 /* Schedule timer to hangup call after the specified duration */
1463 struct call_data *cd = &app_config.call_data[call_id];
1464 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1465 pj_time_val delay;
1466
1467 cd->timer.id = call_id;
1468 delay.sec = app_config.duration;
1469 delay.msec = 0;
1470 pjsip_endpt_schedule_timer(endpt, &cd->timer, &delay);
1471 }
1472
Benny Prijono4be63b52006-11-25 14:50:25 +00001473 if (call_info.state == PJSIP_INV_STATE_EARLY) {
1474 int code;
1475 pj_str_t reason;
1476 pjsip_msg *msg;
1477
1478 /* This can only occur because of TX or RX message */
1479 pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
1480
1481 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
1482 msg = e->body.tsx_state.src.rdata->msg_info.msg;
1483 } else {
1484 msg = e->body.tsx_state.src.tdata->msg;
1485 }
1486
1487 code = msg->line.status.code;
1488 reason = msg->line.status.reason;
1489
1490 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)",
1491 call_id, call_info.state_text.ptr,
1492 code, (int)reason.slen, reason.ptr));
1493 } else {
1494 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s",
1495 call_id,
1496 call_info.state_text.ptr));
1497 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001498
1499 if (current_call==PJSUA_INVALID_ID)
1500 current_call = call_id;
1501
1502 }
1503}
1504
1505
1506/**
1507 * Handler when there is incoming call.
1508 */
1509static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
1510 pjsip_rx_data *rdata)
1511{
1512 pjsua_call_info call_info;
1513
1514 PJ_UNUSED_ARG(acc_id);
1515 PJ_UNUSED_ARG(rdata);
1516
1517 pjsua_call_get_info(call_id, &call_info);
1518
1519 if (app_config.auto_answer > 0) {
1520 pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL);
1521 }
1522
1523 if (app_config.auto_answer < 200) {
1524 PJ_LOG(3,(THIS_FILE,
1525 "Incoming call for account %d!\n"
1526 "From: %s\n"
1527 "To: %s\n"
1528 "Press a to answer or h to reject call",
1529 acc_id,
1530 call_info.remote_info.ptr,
1531 call_info.local_info.ptr));
1532 }
1533}
1534
1535
1536/*
1537 * Callback on media state changed event.
1538 * The action may connect the call to sound device, to file, or
1539 * to loop the call.
1540 */
1541static void on_call_media_state(pjsua_call_id call_id)
1542{
1543 pjsua_call_info call_info;
1544
1545 pjsua_call_get_info(call_id, &call_info);
1546
1547 if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
1548 pj_bool_t connect_sound = PJ_TRUE;
1549
1550 /* Loopback sound, if desired */
1551 if (app_config.auto_loop) {
1552 pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
1553 connect_sound = PJ_FALSE;
Benny Prijono1ebd6142006-10-19 15:48:02 +00001554
1555 /* Automatically record conversation, if desired */
1556 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1557 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1558 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001559 }
1560
1561 /* Stream a file, if desired */
1562 if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) {
1563 pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
1564 connect_sound = PJ_FALSE;
1565 }
1566
Benny Prijono7ca96da2006-08-07 12:11:40 +00001567 /* Put call in conference with other calls, if desired */
1568 if (app_config.auto_conf) {
1569 pjsua_call_id call_ids[PJSUA_MAX_CALLS];
Benny Prijono10861bc2006-08-07 13:22:43 +00001570 unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
Benny Prijono7ca96da2006-08-07 12:11:40 +00001571 unsigned i;
1572
1573 /* Get all calls, and establish media connection between
1574 * this call and other calls.
1575 */
1576 pjsua_enum_calls(call_ids, &call_cnt);
Benny Prijono10861bc2006-08-07 13:22:43 +00001577
Benny Prijono7ca96da2006-08-07 12:11:40 +00001578 for (i=0; i<call_cnt; ++i) {
1579 if (call_ids[i] == call_id)
1580 continue;
1581
1582 if (!pjsua_call_has_media(call_ids[i]))
1583 continue;
1584
1585 pjsua_conf_connect(call_info.conf_slot,
1586 pjsua_call_get_conf_port(call_ids[i]));
1587 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1588 call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001589
1590 /* Automatically record conversation, if desired */
1591 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1592 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1593 app_config.rec_port);
1594 }
1595
Benny Prijono7ca96da2006-08-07 12:11:40 +00001596 }
1597
1598 /* Also connect call to local sound device */
1599 connect_sound = PJ_TRUE;
1600 }
1601
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001602 /* Otherwise connect to sound device */
1603 if (connect_sound) {
1604 pjsua_conf_connect(call_info.conf_slot, 0);
1605 pjsua_conf_connect(0, call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001606
1607 /* Automatically record conversation, if desired */
1608 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1609 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1610 pjsua_conf_connect(0, app_config.rec_port);
1611 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001612 }
1613
1614 PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
1615
1616 } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) {
1617 PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
1618 call_id));
1619 } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) {
1620 PJ_LOG(3,(THIS_FILE,
1621 "Media for call %d is suspended (hold) by remote",
1622 call_id));
1623 } else {
1624 PJ_LOG(3,(THIS_FILE,
1625 "Media for call %d is inactive",
1626 call_id));
1627 }
1628}
1629
Benny Prijono0875ae82006-12-26 00:11:48 +00001630/*
1631 * DTMF callback.
1632 */
1633static void call_on_dtmf_callback(pjsua_call_id call_id, int dtmf)
1634{
1635 PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, dtmf));
1636}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001637
1638/*
1639 * Handler registration status has changed.
1640 */
1641static void on_reg_state(pjsua_acc_id acc_id)
1642{
1643 PJ_UNUSED_ARG(acc_id);
1644
1645 // Log already written.
1646}
1647
1648
1649/*
1650 * Handler on buddy state changed.
1651 */
1652static void on_buddy_state(pjsua_buddy_id buddy_id)
1653{
1654 pjsua_buddy_info info;
1655 pjsua_buddy_get_info(buddy_id, &info);
1656
1657 PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
1658 (int)info.uri.slen,
1659 info.uri.ptr,
1660 (int)info.status_text.slen,
1661 info.status_text.ptr));
1662}
1663
1664
1665/**
1666 * Incoming IM message (i.e. MESSAGE request)!
1667 */
1668static void on_pager(pjsua_call_id call_id, const pj_str_t *from,
1669 const pj_str_t *to, const pj_str_t *contact,
1670 const pj_str_t *mime_type, const pj_str_t *text)
1671{
1672 /* Note: call index may be -1 */
1673 PJ_UNUSED_ARG(call_id);
1674 PJ_UNUSED_ARG(to);
1675 PJ_UNUSED_ARG(contact);
1676 PJ_UNUSED_ARG(mime_type);
1677
Benny Prijonof4b538d2007-05-14 16:45:20 +00001678 PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s (%.*s)",
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001679 (int)from->slen, from->ptr,
Benny Prijonof4b538d2007-05-14 16:45:20 +00001680 (int)text->slen, text->ptr,
1681 (int)mime_type->slen, mime_type->ptr));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001682}
1683
1684
1685/**
1686 * Received typing indication
1687 */
1688static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
1689 const pj_str_t *to, const pj_str_t *contact,
1690 pj_bool_t is_typing)
1691{
1692 PJ_UNUSED_ARG(call_id);
1693 PJ_UNUSED_ARG(to);
1694 PJ_UNUSED_ARG(contact);
1695
1696 PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
1697 (int)from->slen, from->ptr,
1698 (is_typing?"is typing..":"has stopped typing")));
1699}
1700
1701
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001702/**
1703 * Call transfer request status.
1704 */
1705static void on_call_transfer_status(pjsua_call_id call_id,
1706 int status_code,
1707 const pj_str_t *status_text,
1708 pj_bool_t final,
1709 pj_bool_t *p_cont)
1710{
1711 PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s",
1712 call_id, status_code,
1713 (int)status_text->slen, status_text->ptr,
1714 (final ? "[final]" : "")));
1715
1716 if (status_code/100 == 2) {
1717 PJ_LOG(3,(THIS_FILE,
1718 "Call %d: call transfered successfully, disconnecting call",
1719 call_id));
1720 pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL);
1721 *p_cont = PJ_FALSE;
1722 }
1723}
1724
1725
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001726/*
Benny Prijonof7b1c392006-11-11 16:46:34 +00001727 * Notification that call is being replaced.
1728 */
1729static void on_call_replaced(pjsua_call_id old_call_id,
1730 pjsua_call_id new_call_id)
1731{
1732 pjsua_call_info old_ci, new_ci;
1733
1734 pjsua_call_get_info(old_call_id, &old_ci);
1735 pjsua_call_get_info(new_call_id, &new_ci);
1736
1737 PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by "
1738 "call %d with %.*s",
1739 old_call_id,
1740 (int)old_ci.remote_info.slen, old_ci.remote_info.ptr,
1741 new_call_id,
1742 (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
1743}
1744
1745
1746/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001747 * Print buddy list.
1748 */
1749static void print_buddy_list(void)
1750{
1751 pjsua_buddy_id ids[64];
1752 int i;
1753 unsigned count = PJ_ARRAY_SIZE(ids);
1754
1755 puts("Buddy list:");
1756
1757 pjsua_enum_buddies(ids, &count);
1758
1759 if (count == 0)
1760 puts(" -none-");
1761 else {
1762 for (i=0; i<(int)count; ++i) {
1763 pjsua_buddy_info info;
1764
1765 if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
1766 continue;
1767
1768 printf(" [%2d] <%7s> %.*s\n",
1769 ids[i]+1, info.status_text.ptr,
1770 (int)info.uri.slen,
1771 info.uri.ptr);
1772 }
1773 }
1774 puts("");
1775}
1776
1777
1778/*
1779 * Print account status.
1780 */
1781static void print_acc_status(int acc_id)
1782{
1783 char buf[80];
1784 pjsua_acc_info info;
1785
1786 pjsua_acc_get_info(acc_id, &info);
1787
1788 if (!info.has_registration) {
1789 pj_ansi_snprintf(buf, sizeof(buf), "%.*s",
1790 (int)info.status_text.slen,
1791 info.status_text.ptr);
1792
1793 } else {
1794 pj_ansi_snprintf(buf, sizeof(buf),
1795 "%d/%.*s (expires=%d)",
1796 info.status,
1797 (int)info.status_text.slen,
1798 info.status_text.ptr,
1799 info.expires);
1800
1801 }
1802
1803 printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
1804 acc_id, (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
1805 printf(" Online status: %s\n",
1806 (info.online_status ? "Online" : "Invisible"));
1807}
1808
1809
1810/*
1811 * Show a bit of help.
1812 */
1813static void keystroke_help(void)
1814{
1815 pjsua_acc_id acc_ids[16];
1816 unsigned count = PJ_ARRAY_SIZE(acc_ids);
1817 int i;
1818
1819 printf(">>>>\n");
1820
1821 pjsua_enum_accs(acc_ids, &count);
1822
1823 printf("Account list:\n");
1824 for (i=0; i<(int)count; ++i)
1825 print_acc_status(acc_ids[i]);
1826
1827 print_buddy_list();
1828
1829 //puts("Commands:");
1830 puts("+=============================================================================+");
1831 puts("| Call Commands: | Buddy, IM & Presence: | Account: |");
1832 puts("| | | |");
1833 puts("| m Make new call | +b Add new buddy .| +a Add new accnt |");
1834 puts("| M Make multiple calls | -b Delete buddy | -a Delete accnt. |");
1835 puts("| a Answer call | !b Modify buddy | !a Modify accnt. |");
1836 puts("| h Hangup call (ha=all) | i Send IM | rr (Re-)register |");
1837 puts("| H Hold call | s Subscribe presence | ru Unregister |");
1838 puts("| v re-inVite (release hold) | u Unsubscribe presence | > Cycle next ac.|");
1839 puts("| ] Select next dialog | t ToGgle Online status | < Cycle prev ac.|");
1840 puts("| [ Select previous dialog +--------------------------+-------------------+");
1841 puts("| x Xfer call | Media Commands: | Status & Config: |");
Benny Prijonof7b1c392006-11-11 16:46:34 +00001842 puts("| X Xfer with Replaces | | |");
1843 puts("| # Send DTMF string | cl List ports | d Dump status |");
1844 puts("| dq Dump curr. call quality | cc Connect port | dd Dump detailed |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001845 puts("| | cd Disconnect port | dc Dump config |");
Benny Prijono6dd967c2006-12-26 02:27:14 +00001846 puts("| S Send arbitrary REQUEST | V Adjust audio Volume | f Save config |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001847 puts("+------------------------------+--------------------------+-------------------+");
Benny Prijono990042e2007-01-21 19:36:00 +00001848 puts("| q QUIT sleep N: console sleep for N ms |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001849 puts("+=============================================================================+");
Benny Prijono48af79c2006-07-22 12:49:17 +00001850
1851 i = pjsua_call_get_count();
1852 printf("You have %d active call%s\n", i, (i>1?"s":""));
Benny Prijonof7b1c392006-11-11 16:46:34 +00001853
1854 if (current_call != PJSUA_INVALID_ID) {
1855 pjsua_call_info ci;
1856 if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS)
1857 printf("Current call id=%d to %.*s [%.*s]\n", current_call,
1858 (int)ci.remote_info.slen, ci.remote_info.ptr,
1859 (int)ci.state_text.slen, ci.state_text.ptr);
1860 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001861}
1862
1863
1864/*
1865 * Input simple string
1866 */
1867static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1868{
1869 char *p;
1870
1871 printf("%s (empty to cancel): ", title); fflush(stdout);
1872 fgets(buf, len, stdin);
1873
1874 /* Remove trailing newlines. */
1875 for (p=buf; ; ++p) {
1876 if (*p=='\r' || *p=='\n') *p='\0';
1877 else if (!*p) break;
1878 }
1879
1880 if (!*buf)
1881 return PJ_FALSE;
1882
1883 return PJ_TRUE;
1884}
1885
1886
1887#define NO_NB -2
1888struct input_result
1889{
1890 int nb_result;
1891 char *uri_result;
1892};
1893
1894
1895/*
1896 * Input URL.
1897 */
1898static void ui_input_url(const char *title, char *buf, int len,
1899 struct input_result *result)
1900{
1901 result->nb_result = NO_NB;
1902 result->uri_result = NULL;
1903
1904 print_buddy_list();
1905
1906 printf("Choices:\n"
1907 " 0 For current dialog.\n"
1908 " -1 All %d buddies in buddy list\n"
1909 " [1 -%2d] Select from buddy list\n"
1910 " URL An URL\n"
1911 " <Enter> Empty input (or 'q') to cancel\n"
1912 , pjsua_get_buddy_count(), pjsua_get_buddy_count());
1913 printf("%s: ", title);
1914
1915 fflush(stdout);
1916 fgets(buf, len, stdin);
1917 len = strlen(buf);
1918
1919 /* Left trim */
1920 while (pj_isspace(*buf)) {
1921 ++buf;
1922 --len;
1923 }
1924
1925 /* Remove trailing newlines */
1926 while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
1927 buf[--len] = '\0';
1928
1929 if (len == 0 || buf[0]=='q')
1930 return;
1931
1932 if (pj_isdigit(*buf) || *buf=='-') {
1933
1934 int i;
1935
1936 if (*buf=='-')
1937 i = 1;
1938 else
1939 i = 0;
1940
1941 for (; i<len; ++i) {
1942 if (!pj_isdigit(buf[i])) {
1943 puts("Invalid input");
1944 return;
1945 }
1946 }
1947
1948 result->nb_result = my_atoi(buf);
1949
1950 if (result->nb_result >= 0 &&
1951 result->nb_result <= (int)pjsua_get_buddy_count())
1952 {
1953 return;
1954 }
1955 if (result->nb_result == -1)
1956 return;
1957
1958 puts("Invalid input");
1959 result->nb_result = NO_NB;
1960 return;
1961
1962 } else {
1963 pj_status_t status;
1964
1965 if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
1966 pjsua_perror(THIS_FILE, "Invalid URL", status);
1967 return;
1968 }
1969
1970 result->uri_result = buf;
1971 }
1972}
1973
1974/*
1975 * List the ports in conference bridge
1976 */
1977static void conf_list(void)
1978{
1979 unsigned i, count;
1980 pjsua_conf_port_id id[PJSUA_MAX_CALLS];
1981
1982 printf("Conference ports:\n");
1983
1984 count = PJ_ARRAY_SIZE(id);
1985 pjsua_enum_conf_ports(id, &count);
1986
1987 for (i=0; i<count; ++i) {
1988 char txlist[PJSUA_MAX_CALLS*4+10];
1989 unsigned j;
1990 pjsua_conf_port_info info;
1991
1992 pjsua_conf_get_port_info(id[i], &info);
1993
1994 txlist[0] = '\0';
1995 for (j=0; j<info.listener_cnt; ++j) {
1996 char s[10];
1997 pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
1998 pj_ansi_strcat(txlist, s);
1999 }
2000 printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n",
2001 info.slot_id,
2002 info.clock_rate/1000,
2003 info.samples_per_frame * 1000 / info.clock_rate,
2004 (int)info.name.slen,
2005 info.name.ptr,
2006 txlist);
2007
2008 }
2009 puts("");
2010}
2011
2012
2013/*
Benny Prijono56315612006-07-18 14:39:40 +00002014 * Send arbitrary request to remote host
2015 */
2016static void send_request(char *cstr_method, const pj_str_t *dst_uri)
2017{
2018 pj_str_t str_method;
2019 pjsip_method method;
2020 pjsip_tx_data *tdata;
Benny Prijono56315612006-07-18 14:39:40 +00002021 pjsip_endpoint *endpt;
2022 pj_status_t status;
2023
2024 endpt = pjsua_get_pjsip_endpt();
2025
2026 str_method = pj_str(cstr_method);
2027 pjsip_method_init_np(&method, &str_method);
2028
Benny Prijonofff245c2007-04-02 11:44:47 +00002029 status = pjsua_acc_create_request(current_acc, &method, dst_uri, &tdata);
Benny Prijono56315612006-07-18 14:39:40 +00002030
2031 status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
2032 if (status != PJ_SUCCESS) {
2033 pjsua_perror(THIS_FILE, "Unable to send request", status);
Benny Prijono56315612006-07-18 14:39:40 +00002034 return;
2035 }
2036}
2037
2038
2039/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002040 * Main "user interface" loop.
2041 */
2042void console_app_main(const pj_str_t *uri_to_call)
2043{
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002044 char menuin[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002045 char buf[128];
2046 char text[128];
2047 int i, count;
2048 char *uri;
2049 pj_str_t tmp;
2050 struct input_result result;
2051 pjsua_call_info call_info;
2052 pjsua_acc_info acc_info;
2053
2054
2055 /* If user specifies URI to call, then call the URI */
2056 if (uri_to_call->slen) {
2057 pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, NULL);
2058 }
2059
2060 keystroke_help();
2061
2062 for (;;) {
2063
2064 printf(">>> ");
2065 fflush(stdout);
2066
Benny Prijono990042e2007-01-21 19:36:00 +00002067 if (fgets(menuin, sizeof(menuin), stdin) == NULL) {
2068 /*
2069 * Be friendly to users who redirect commands into
2070 * program, when file ends, resume with kbd.
2071 * If exit is desired end script with q for quit
2072 */
2073 /* Reopen stdin/stdout/stderr to /dev/console */
2074#if defined(PJ_WIN32) && PJ_WIN32!=0
2075 if (freopen ("CONIN$", "r", stdin) == NULL) {
2076#else
2077 if (1) {
2078#endif
2079 puts("Cannot switch back to console from file redirection");
2080 menuin[0] = 'q';
2081 menuin[1] = '\0';
2082 } else {
2083 puts("Switched back to console from file redirection");
2084 continue;
2085 }
2086 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002087
2088 switch (menuin[0]) {
2089
2090 case 'm':
2091 /* Make call! : */
2092 printf("(You currently have %d calls)\n",
2093 pjsua_call_get_count());
2094
2095 uri = NULL;
2096 ui_input_url("Make call", buf, sizeof(buf), &result);
2097 if (result.nb_result != NO_NB) {
2098
2099 if (result.nb_result == -1 || result.nb_result == 0) {
2100 puts("You can't do that with make call!");
2101 continue;
2102 } else {
2103 pjsua_buddy_info binfo;
2104 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2105 uri = binfo.uri.ptr;
2106 }
2107
2108 } else if (result.uri_result) {
2109 uri = result.uri_result;
2110 }
2111
2112 tmp = pj_str(uri);
2113 pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL);
2114 break;
2115
2116 case 'M':
2117 /* Make multiple calls! : */
2118 printf("(You currently have %d calls)\n",
2119 pjsua_call_get_count());
2120
2121 if (!simple_input("Number of calls", menuin, sizeof(menuin)))
2122 continue;
2123
2124 count = my_atoi(menuin);
2125 if (count < 1)
2126 continue;
2127
2128 ui_input_url("Make call", buf, sizeof(buf), &result);
2129 if (result.nb_result != NO_NB) {
2130 pjsua_buddy_info binfo;
2131 if (result.nb_result == -1 || result.nb_result == 0) {
2132 puts("You can't do that with make call!");
2133 continue;
2134 }
2135 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2136 uri = binfo.uri.ptr;
2137 } else {
2138 uri = result.uri_result;
2139 }
2140
2141 for (i=0; i<my_atoi(menuin); ++i) {
2142 pj_status_t status;
2143
2144 tmp = pj_str(uri);
2145 status = pjsua_call_make_call(current_acc, &tmp, 0, NULL,
2146 NULL, NULL);
2147 if (status != PJ_SUCCESS)
2148 break;
2149 }
2150 break;
2151
2152 case 'i':
2153 /* Send instant messaeg */
2154
2155 /* i is for call index to send message, if any */
2156 i = -1;
2157
2158 /* Make compiler happy. */
2159 uri = NULL;
2160
2161 /* Input destination. */
2162 ui_input_url("Send IM to", buf, sizeof(buf), &result);
2163 if (result.nb_result != NO_NB) {
2164
2165 if (result.nb_result == -1) {
2166 puts("You can't send broadcast IM like that!");
2167 continue;
2168
2169 } else if (result.nb_result == 0) {
2170
2171 i = current_call;
2172
2173 } else {
2174 pjsua_buddy_info binfo;
2175 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2176 uri = binfo.uri.ptr;
2177 }
2178
2179 } else if (result.uri_result) {
2180 uri = result.uri_result;
2181 }
2182
2183
2184 /* Send typing indication. */
2185 if (i != -1)
2186 pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
2187 else {
2188 pj_str_t tmp_uri = pj_str(uri);
2189 pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
2190 }
2191
2192 /* Input the IM . */
2193 if (!simple_input("Message", text, sizeof(text))) {
2194 /*
2195 * Cancelled.
2196 * Send typing notification too, saying we're not typing.
2197 */
2198 if (i != -1)
2199 pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
2200 else {
2201 pj_str_t tmp_uri = pj_str(uri);
2202 pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
2203 }
2204 continue;
2205 }
2206
2207 tmp = pj_str(text);
2208
2209 /* Send the IM */
2210 if (i != -1)
2211 pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
2212 else {
2213 pj_str_t tmp_uri = pj_str(uri);
2214 pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
2215 }
2216
2217 break;
2218
2219 case 'a':
2220
2221 if (current_call != -1) {
2222 pjsua_call_get_info(current_call, &call_info);
2223 } else {
2224 /* Make compiler happy */
2225 call_info.role = PJSIP_ROLE_UAC;
2226 call_info.state = PJSIP_INV_STATE_DISCONNECTED;
2227 }
2228
2229 if (current_call == -1 ||
2230 call_info.role != PJSIP_ROLE_UAS ||
2231 call_info.state >= PJSIP_INV_STATE_CONNECTING)
2232 {
2233 puts("No pending incoming call");
2234 fflush(stdout);
2235 continue;
2236
2237 } else {
Benny Prijono20d36722007-02-22 14:52:24 +00002238 int st_code;
2239 char contact[120];
2240 pj_str_t hname = { "Contact", 7 };
2241 pj_str_t hvalue;
2242 pjsip_generic_string_hdr hcontact;
2243 pjsua_msg_data msg_data;
2244
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002245 if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
2246 continue;
2247
Benny Prijono20d36722007-02-22 14:52:24 +00002248 st_code = my_atoi(buf);
2249 if (st_code < 100)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002250 continue;
2251
Benny Prijono20d36722007-02-22 14:52:24 +00002252 pjsua_msg_data_init(&msg_data);
2253
2254 if (st_code/100 == 3) {
2255 if (!simple_input("Enter URL to be put in Contact",
2256 contact, sizeof(contact)))
2257 continue;
2258 hvalue = pj_str(contact);
2259 pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
2260
2261 pj_list_push_back(&msg_data.hdr_list, &hcontact);
2262 }
2263
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002264 /*
2265 * Must check again!
2266 * Call may have been disconnected while we're waiting for
2267 * keyboard input.
2268 */
2269 if (current_call == -1) {
2270 puts("Call has been disconnected");
2271 fflush(stdout);
2272 continue;
2273 }
2274
Benny Prijono20d36722007-02-22 14:52:24 +00002275 pjsua_call_answer(current_call, st_code, NULL, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002276 }
2277
2278 break;
2279
2280
2281 case 'h':
2282
2283 if (current_call == -1) {
2284 puts("No current call");
2285 fflush(stdout);
2286 continue;
2287
2288 } else if (menuin[1] == 'a') {
2289
2290 /* Hangup all calls */
2291 pjsua_call_hangup_all();
2292
2293 } else {
2294
2295 /* Hangup current calls */
2296 pjsua_call_hangup(current_call, 0, NULL, NULL);
2297 }
2298 break;
2299
2300 case ']':
2301 case '[':
2302 /*
2303 * Cycle next/prev dialog.
2304 */
2305 if (menuin[0] == ']') {
2306 find_next_call();
2307
2308 } else {
2309 find_prev_call();
2310 }
2311
2312 if (current_call != -1) {
2313
2314 pjsua_call_get_info(current_call, &call_info);
2315 PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s",
2316 (int)call_info.remote_info.slen,
2317 call_info.remote_info.ptr));
2318
2319 } else {
2320 PJ_LOG(3,(THIS_FILE,"No current dialog"));
2321 }
2322 break;
2323
2324
2325 case '>':
2326 case '<':
2327 if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
2328 break;
2329
2330 i = my_atoi(buf);
2331 if (pjsua_acc_is_valid(i)) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00002332 pjsua_acc_set_default(i);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002333 PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
2334 } else {
2335 PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
2336 }
2337 break;
2338
2339
2340 case '+':
2341 if (menuin[1] == 'b') {
2342
2343 pjsua_buddy_config buddy_cfg;
2344 pjsua_buddy_id buddy_id;
2345 pj_status_t status;
2346
2347 if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
2348 break;
2349
2350 if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) {
2351 printf("Invalid SIP URI '%s'\n", buf);
2352 break;
2353 }
2354
Benny Prijonoac623b32006-07-03 15:19:31 +00002355 pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002356
2357 buddy_cfg.uri = pj_str(buf);
2358 buddy_cfg.subscribe = PJ_TRUE;
2359
2360 status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
2361 if (status == PJ_SUCCESS) {
2362 printf("New buddy '%s' added at index %d\n",
2363 buf, buddy_id+1);
2364 }
2365
2366 } else if (menuin[1] == 'a') {
2367
2368 printf("Sorry, this command is not supported yet\n");
2369
2370 } else {
2371 printf("Invalid input %s\n", menuin);
2372 }
2373 break;
2374
2375 case '-':
2376 if (menuin[1] == 'b') {
2377 if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
2378 break;
2379
2380 i = my_atoi(buf) - 1;
2381
2382 if (!pjsua_buddy_is_valid(i)) {
2383 printf("Invalid buddy id %d\n", i);
2384 } else {
2385 pjsua_buddy_del(i);
2386 printf("Buddy %d deleted\n", i);
2387 }
2388
2389 } else if (menuin[1] == 'a') {
2390
2391 if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
2392 break;
2393
2394 i = my_atoi(buf);
2395
2396 if (!pjsua_acc_is_valid(i)) {
2397 printf("Invalid account id %d\n", i);
2398 } else {
2399 pjsua_acc_del(i);
2400 printf("Account %d deleted\n", i);
2401 }
2402
2403 } else {
2404 printf("Invalid input %s\n", menuin);
2405 }
2406 break;
2407
2408 case 'H':
2409 /*
2410 * Hold call.
2411 */
2412 if (current_call != -1) {
2413
2414 pjsua_call_set_hold(current_call, NULL);
2415
2416 } else {
2417 PJ_LOG(3,(THIS_FILE, "No current call"));
2418 }
2419 break;
2420
2421 case 'v':
2422 /*
2423 * Send re-INVITE (to release hold, etc).
2424 */
2425 if (current_call != -1) {
2426
2427 pjsua_call_reinvite(current_call, PJ_TRUE, NULL);
2428
2429 } else {
2430 PJ_LOG(3,(THIS_FILE, "No current call"));
2431 }
2432 break;
2433
2434 case 'x':
2435 /*
2436 * Transfer call.
2437 */
2438 if (current_call == -1) {
2439
2440 PJ_LOG(3,(THIS_FILE, "No current call"));
2441
2442 } else {
2443 int call = current_call;
Benny Prijonod524e822006-09-22 12:48:18 +00002444 pjsua_msg_data msg_data;
2445 pjsip_generic_string_hdr refer_sub;
2446 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2447 pj_str_t STR_FALSE = { "false", 5 };
Benny Prijonof7b1c392006-11-11 16:46:34 +00002448 pjsua_call_info ci;
2449
2450 pjsua_call_get_info(current_call, &ci);
2451 printf("Transfering current call [%d] %.*s\n",
2452 current_call,
2453 (int)ci.remote_info.slen, ci.remote_info.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002454
2455 ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
2456
2457 /* Check if call is still there. */
2458
2459 if (call != current_call) {
2460 puts("Call has been disconnected");
2461 continue;
2462 }
2463
Benny Prijonod524e822006-09-22 12:48:18 +00002464 pjsua_msg_data_init(&msg_data);
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002465 if (app_config.no_refersub) {
2466 /* Add Refer-Sub: false in outgoing REFER request */
2467 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2468 &STR_FALSE);
2469 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2470 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002471 if (result.nb_result != NO_NB) {
2472 if (result.nb_result == -1 || result.nb_result == 0)
2473 puts("You can't do that with transfer call!");
2474 else {
2475 pjsua_buddy_info binfo;
2476 pjsua_buddy_get_info(result.nb_result-1, &binfo);
Benny Prijonod524e822006-09-22 12:48:18 +00002477 pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002478 }
2479
2480 } else if (result.uri_result) {
2481 pj_str_t tmp;
2482 tmp = pj_str(result.uri_result);
Benny Prijonod524e822006-09-22 12:48:18 +00002483 pjsua_call_xfer( current_call, &tmp, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002484 }
2485 }
2486 break;
2487
Benny Prijonof7b1c392006-11-11 16:46:34 +00002488 case 'X':
2489 /*
2490 * Transfer call with replaces.
2491 */
2492 if (current_call == -1) {
2493
2494 PJ_LOG(3,(THIS_FILE, "No current call"));
2495
2496 } else {
2497 int call = current_call;
2498 int dst_call;
2499 pjsua_msg_data msg_data;
2500 pjsip_generic_string_hdr refer_sub;
2501 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2502 pj_str_t STR_FALSE = { "false", 5 };
2503 pjsua_call_id ids[PJSUA_MAX_CALLS];
2504 pjsua_call_info ci;
2505 unsigned i, count;
2506
2507 count = PJ_ARRAY_SIZE(ids);
2508 pjsua_enum_calls(ids, &count);
2509
2510 if (count <= 1) {
2511 puts("There are no other calls");
2512 continue;
2513 }
2514
2515 pjsua_call_get_info(current_call, &ci);
2516 printf("Transfer call [%d] %.*s to one of the following:\n",
2517 current_call,
2518 (int)ci.remote_info.slen, ci.remote_info.ptr);
2519
2520 for (i=0; i<count; ++i) {
2521 pjsua_call_info call_info;
2522
2523 if (ids[i] == call)
2524 continue;
2525
2526 pjsua_call_get_info(ids[i], &call_info);
2527 printf("%d %.*s [%.*s]\n",
2528 ids[i],
2529 (int)call_info.remote_info.slen,
2530 call_info.remote_info.ptr,
2531 (int)call_info.state_text.slen,
2532 call_info.state_text.ptr);
2533 }
2534
2535 if (!simple_input("Enter call number to be replaced",
2536 buf, sizeof(buf)))
2537 continue;
2538
2539 dst_call = my_atoi(buf);
2540
2541 /* Check if call is still there. */
2542
2543 if (call != current_call) {
2544 puts("Call has been disconnected");
2545 continue;
2546 }
2547
2548 /* Check that destination call is valid. */
2549 if (dst_call == call) {
2550 puts("Destination call number must not be the same "
2551 "as the call being transfered");
2552 continue;
2553 }
2554 if (dst_call >= PJSUA_MAX_CALLS) {
2555 puts("Invalid destination call number");
2556 continue;
2557 }
2558 if (!pjsua_call_is_active(dst_call)) {
2559 puts("Invalid destination call number");
2560 continue;
2561 }
2562
2563 pjsua_msg_data_init(&msg_data);
2564 if (app_config.no_refersub) {
2565 /* Add Refer-Sub: false in outgoing REFER request */
2566 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2567 &STR_FALSE);
2568 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2569 }
2570
2571 pjsua_call_xfer_replaces(call, dst_call, 0, &msg_data);
2572 }
2573 break;
2574
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002575 case '#':
2576 /*
2577 * Send DTMF strings.
2578 */
2579 if (current_call == -1) {
2580
2581 PJ_LOG(3,(THIS_FILE, "No current call"));
2582
2583 } else if (!pjsua_call_has_media(current_call)) {
2584
2585 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
2586
2587 } else {
2588 pj_str_t digits;
2589 int call = current_call;
2590 pj_status_t status;
2591
2592 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
2593 sizeof(buf)))
2594 {
2595 break;
2596 }
2597
2598 if (call != current_call) {
2599 puts("Call has been disconnected");
2600 continue;
2601 }
2602
2603 digits = pj_str(buf);
2604 status = pjsua_call_dial_dtmf(current_call, &digits);
2605 if (status != PJ_SUCCESS) {
2606 pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
2607 } else {
2608 puts("DTMF digits enqueued for transmission");
2609 }
2610 }
2611 break;
2612
Benny Prijono56315612006-07-18 14:39:40 +00002613 case 'S':
2614 /*
2615 * Send arbitrary request
2616 */
2617 if (pjsua_acc_get_count() == 0) {
2618 puts("Sorry, need at least one account configured");
2619 break;
2620 }
2621
2622 puts("Send arbitrary request to remote host");
2623
2624 /* Input METHOD */
2625 if (!simple_input("Request method:",text,sizeof(text)))
2626 break;
2627
2628 /* Input destination URI */
2629 uri = NULL;
2630 ui_input_url("Destination URI", buf, sizeof(buf), &result);
2631 if (result.nb_result != NO_NB) {
2632
2633 if (result.nb_result == -1 || result.nb_result == 0) {
2634 puts("Sorry you can't do that!");
2635 continue;
2636 } else {
2637 pjsua_buddy_info binfo;
2638 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2639 uri = binfo.uri.ptr;
2640 }
2641
2642 } else if (result.uri_result) {
2643 uri = result.uri_result;
2644 }
2645
2646 tmp = pj_str(uri);
2647
2648 send_request(text, &tmp);
2649 break;
2650
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002651 case 's':
Benny Prijono990042e2007-01-21 19:36:00 +00002652 if (pj_ansi_strnicmp(menuin, "sleep", 5)==0) {
2653 pj_str_t tmp;
2654 int delay;
2655
2656 tmp.ptr = menuin+6;
2657 tmp.slen = pj_ansi_strlen(menuin)-7;
2658
2659 if (tmp.slen < 1) {
2660 puts("Usage: sleep MSEC");
2661 break;
2662 }
2663
2664 delay = pj_strtoul(&tmp);
2665 if (delay < 0) delay = 0;
2666 pj_thread_sleep(delay);
2667 break;
2668 }
2669 /* Continue below */
2670
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002671 case 'u':
2672 /*
2673 * Subscribe/unsubscribe presence.
2674 */
2675 ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
2676 if (result.nb_result != NO_NB) {
2677 if (result.nb_result == -1) {
2678 int i, count;
2679 count = pjsua_get_buddy_count();
2680 for (i=0; i<count; ++i)
2681 pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
2682 } else if (result.nb_result == 0) {
2683 puts("Sorry, can only subscribe to buddy's presence, "
2684 "not from existing call");
2685 } else {
2686 pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
2687 }
2688
2689 } else if (result.uri_result) {
2690 puts("Sorry, can only subscribe to buddy's presence, "
2691 "not arbitrary URL (for now)");
2692 }
2693
2694 break;
2695
2696 case 'r':
2697 switch (menuin[1]) {
2698 case 'r':
2699 /*
2700 * Re-Register.
2701 */
2702 pjsua_acc_set_registration(current_acc, PJ_TRUE);
2703 break;
2704 case 'u':
2705 /*
2706 * Unregister
2707 */
2708 pjsua_acc_set_registration(current_acc, PJ_FALSE);
2709 break;
2710 }
2711 break;
2712
2713 case 't':
2714 pjsua_acc_get_info(current_acc, &acc_info);
2715 acc_info.online_status = !acc_info.online_status;
2716 pjsua_acc_set_online_status(current_acc, acc_info.online_status);
2717 printf("Setting %s online status to %s\n",
2718 acc_info.acc_uri.ptr,
2719 (acc_info.online_status?"online":"offline"));
2720 break;
2721
2722 case 'c':
2723 switch (menuin[1]) {
2724 case 'l':
2725 conf_list();
2726 break;
2727 case 'c':
2728 case 'd':
2729 {
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002730 char tmp[10], src_port[10], dst_port[10];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002731 pj_status_t status;
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002732 int cnt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002733 const char *src_title, *dst_title;
2734
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002735 cnt = sscanf(menuin, "%s %s %s", tmp, src_port, dst_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002736
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002737 if (cnt != 3) {
2738 conf_list();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002739
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002740 src_title = (menuin[1]=='c'?
2741 "Connect src port #":
2742 "Disconnect src port #");
2743 dst_title = (menuin[1]=='c'?
2744 "To dst port #":
2745 "From dst port #");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002746
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002747 if (!simple_input(src_title, src_port, sizeof(src_port)))
2748 break;
2749
2750 if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
2751 break;
2752 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002753
2754 if (menuin[1]=='c') {
2755 status = pjsua_conf_connect(my_atoi(src_port),
2756 my_atoi(dst_port));
2757 } else {
2758 status = pjsua_conf_disconnect(my_atoi(src_port),
2759 my_atoi(dst_port));
2760 }
2761 if (status == PJ_SUCCESS) {
2762 puts("Success");
2763 } else {
2764 puts("ERROR!!");
2765 }
2766 }
2767 break;
2768 }
2769 break;
2770
Benny Prijono6dd967c2006-12-26 02:27:14 +00002771 case 'V':
2772 /* Adjust audio volume */
2773 sprintf(buf, "Adjust mic level: [%4.1fx] ", app_config.mic_level);
2774 if (simple_input(buf,text,sizeof(text))) {
2775 char *err;
2776 app_config.mic_level = (float)strtod(text, &err);
2777 pjsua_conf_adjust_rx_level(0, app_config.mic_level);
2778 }
2779 sprintf(buf, "Adjust speaker level: [%4.1fx] ",
2780 app_config.speaker_level);
2781 if (simple_input(buf,text,sizeof(text))) {
2782 char *err;
2783 app_config.speaker_level = (float)strtod(text, &err);
2784 pjsua_conf_adjust_tx_level(0, app_config.speaker_level);
2785 }
2786
2787 break;
2788
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002789 case 'd':
2790 if (menuin[1] == 'c') {
2791 char settings[2000];
2792 int len;
2793
2794 len = write_settings(&app_config, settings, sizeof(settings));
2795 if (len < 1)
2796 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2797 else
2798 PJ_LOG(3,(THIS_FILE,
2799 "Dumping configuration (%d bytes):\n%s\n",
2800 len, settings));
Benny Prijono819506c2006-06-22 22:29:51 +00002801
2802 } else if (menuin[1] == 'q') {
2803
2804 if (current_call != PJSUA_INVALID_ID) {
2805 char buf[1024];
2806 pjsua_call_dump(current_call, PJ_TRUE, buf,
2807 sizeof(buf), " ");
2808 PJ_LOG(3,(THIS_FILE, "\n%s", buf));
2809 } else {
2810 PJ_LOG(3,(THIS_FILE, "No current call"));
2811 }
2812
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002813 } else {
2814 app_dump(menuin[1]=='d');
2815 }
2816 break;
2817
2818
2819 case 'f':
2820 if (simple_input("Enter output filename", buf, sizeof(buf))) {
2821 char settings[2000];
2822 int len;
2823
2824 len = write_settings(&app_config, settings, sizeof(settings));
2825 if (len < 1)
2826 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2827 else {
2828 pj_oshandle_t fd;
2829 pj_status_t status;
2830
2831 status = pj_file_open(app_config.pool, buf,
2832 PJ_O_WRONLY, &fd);
2833 if (status != PJ_SUCCESS) {
2834 pjsua_perror(THIS_FILE, "Unable to open file", status);
2835 } else {
2836 pj_ssize_t size = len;
2837 pj_file_write(fd, settings, &size);
2838 pj_file_close(fd);
2839
2840 printf("Settings successfully written to '%s'\n", buf);
2841 }
2842 }
2843
2844 }
2845 break;
2846
2847
2848 case 'q':
2849 goto on_exit;
2850
2851
2852 default:
2853 if (menuin[0] != '\n' && menuin[0] != '\r') {
2854 printf("Invalid input %s", menuin);
2855 }
2856 keystroke_help();
2857 break;
2858 }
2859 }
2860
2861on_exit:
2862 ;
2863}
2864
2865
2866/*****************************************************************************
2867 * Public API
2868 */
2869
2870pj_status_t app_init(int argc, char *argv[])
2871{
Benny Prijonoe93e2872006-06-28 16:46:49 +00002872 pjsua_transport_id transport_id = -1;
Benny Prijonoe347cb02007-02-14 14:36:13 +00002873 pjsua_transport_config tcp_cfg;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002874 unsigned i;
2875 pj_status_t status;
2876
2877 /* Create pjsua */
2878 status = pjsua_create();
2879 if (status != PJ_SUCCESS)
2880 return status;
2881
2882 /* Create pool for application */
2883 app_config.pool = pjsua_pool_create("pjsua", 4000, 4000);
2884
2885 /* Initialize default config */
2886 default_config(&app_config);
2887
2888 /* Parse the arguments */
2889 status = parse_args(argc, argv, &app_config, &uri_arg);
2890 if (status != PJ_SUCCESS)
2891 return status;
2892
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002893 /* Initialize application callbacks */
2894 app_config.cfg.cb.on_call_state = &on_call_state;
2895 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
2896 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
Benny Prijono0875ae82006-12-26 00:11:48 +00002897 app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002898 app_config.cfg.cb.on_reg_state = &on_reg_state;
2899 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
2900 app_config.cfg.cb.on_pager = &on_pager;
2901 app_config.cfg.cb.on_typing = &on_typing;
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002902 app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
Benny Prijonof7b1c392006-11-11 16:46:34 +00002903 app_config.cfg.cb.on_call_replaced = &on_call_replaced;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002904
2905 /* Initialize pjsua */
2906 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
2907 &app_config.media_cfg);
2908 if (status != PJ_SUCCESS)
2909 return status;
2910
Benny Prijonoe909eac2006-07-27 22:04:56 +00002911#ifdef STEREO_DEMO
2912 stereo_demo();
2913#endif
2914
Benny Prijono804ff0a2006-09-14 11:17:48 +00002915 /* Initialize calls data */
2916 for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
2917 app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
2918 app_config.call_data[i].timer.cb = &call_timeout_callback;
2919 }
2920
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002921 /* Optionally registers WAV file */
Benny Prijono32e4f492007-01-24 00:44:26 +00002922 for (i=0; i<app_config.wav_count; ++i) {
2923 pjsua_player_id wav_id;
2924
2925 status = pjsua_player_create(&app_config.wav_files[i], 0,
2926 &wav_id);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002927 if (status != PJ_SUCCESS)
2928 goto on_error;
Benny Prijono22a300a2006-06-14 20:04:55 +00002929
Benny Prijono72c04d22007-02-17 22:20:58 +00002930 if (app_config.wav_id == PJSUA_INVALID_ID) {
Benny Prijono32e4f492007-01-24 00:44:26 +00002931 app_config.wav_id = wav_id;
2932 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
2933 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002934 }
2935
Benny Prijono4af234b2007-01-24 02:02:09 +00002936 /* Optionally registers tone players */
2937 for (i=0; i<app_config.tone_count; ++i) {
2938 pjmedia_port *tport;
2939 char name[80];
2940 pj_str_t label;
2941 pj_status_t status;
2942
2943 pj_ansi_snprintf(name, sizeof(name), "tone-%d,%d",
2944 app_config.tones[i].freq1,
2945 app_config.tones[i].freq2);
2946 label = pj_str(name);
2947 status = pjmedia_tonegen_create2(app_config.pool, &label,
2948 8000, 1, 160, 16,
2949 PJMEDIA_TONEGEN_LOOP, &tport);
2950 if (status != PJ_SUCCESS) {
2951 pjsua_perror(THIS_FILE, "Unable to create tone generator", status);
2952 goto on_error;
2953 }
2954
2955 status = pjsua_conf_add_port(app_config.pool, tport,
2956 &app_config.tone_slots[i]);
2957 pj_assert(status == PJ_SUCCESS);
2958
2959 status = pjmedia_tonegen_play(tport, 1, &app_config.tones[i], 0);
2960 pj_assert(status == PJ_SUCCESS);
2961 }
2962
Benny Prijono1ebd6142006-10-19 15:48:02 +00002963 /* Optionally create recorder file, if any. */
2964 if (app_config.rec_file.slen) {
2965 status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
2966 &app_config.rec_id);
2967 if (status != PJ_SUCCESS)
2968 goto on_error;
2969
2970 app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
2971 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002972
Benny Prijonoe347cb02007-02-14 14:36:13 +00002973 pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
2974
Benny Prijono87ef89a2007-01-14 00:39:45 +00002975 /* Add UDP transport unless it's disabled. */
2976 if (!app_config.no_udp) {
2977 pjsua_acc_id aid;
2978
2979 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
2980 &app_config.udp_cfg,
2981 &transport_id);
2982 if (status != PJ_SUCCESS)
2983 goto on_error;
2984
2985 /* Add local account */
2986 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
2987 //pjsua_acc_set_transport(aid, transport_id);
2988 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
Benny Prijonoe347cb02007-02-14 14:36:13 +00002989
2990 if (app_config.udp_cfg.port == 0) {
2991 pjsua_transport_info ti;
2992 pj_sockaddr_in *a;
2993
2994 pjsua_transport_get_info(transport_id, &ti);
2995 a = (pj_sockaddr_in*)&ti.local_addr;
2996
2997 tcp_cfg.port = pj_ntohs(a->sin_port);
2998 }
Benny Prijono87ef89a2007-01-14 00:39:45 +00002999 }
3000
Benny Prijonoe93e2872006-06-28 16:46:49 +00003001 /* Add TCP transport unless it's disabled */
3002 if (!app_config.no_tcp) {
3003 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
Benny Prijonoe347cb02007-02-14 14:36:13 +00003004 &tcp_cfg,
Benny Prijonoe93e2872006-06-28 16:46:49 +00003005 &transport_id);
3006 if (status != PJ_SUCCESS)
3007 goto on_error;
3008
3009 /* Add local account */
Benny Prijono21b9ad92006-08-15 13:11:22 +00003010 pjsua_acc_add_local(transport_id, PJ_TRUE, NULL);
Benny Prijonoe93e2872006-06-28 16:46:49 +00003011 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
3012
3013 }
3014
Benny Prijonoe93e2872006-06-28 16:46:49 +00003015
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003016#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
3017 /* Add TLS transport when application wants one */
3018 if (app_config.use_tls) {
Benny Prijonof3bbc132006-12-25 06:43:59 +00003019
3020 pjsua_acc_id acc_id;
3021
3022 /* Set TLS port as TCP port+1 */
Benny Prijonoafc47be2007-02-14 14:44:55 +00003023 tcp_cfg.port++;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003024 status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
Benny Prijonoafc47be2007-02-14 14:44:55 +00003025 &tcp_cfg,
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003026 &transport_id);
Benny Prijonoafc47be2007-02-14 14:44:55 +00003027 tcp_cfg.port--;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003028 if (status != PJ_SUCCESS)
3029 goto on_error;
Benny Prijonof3bbc132006-12-25 06:43:59 +00003030
3031 /* Add local account */
3032 pjsua_acc_add_local(transport_id, PJ_FALSE, &acc_id);
3033 pjsua_acc_set_online_status(acc_id, PJ_TRUE);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003034 }
3035#endif
3036
Benny Prijonoe93e2872006-06-28 16:46:49 +00003037 if (transport_id == -1) {
3038 PJ_LOG(3,(THIS_FILE, "Error: no transport is configured"));
3039 status = -1;
3040 goto on_error;
3041 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003042
3043
3044 /* Add accounts */
3045 for (i=0; i<app_config.acc_cnt; ++i) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00003046 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003047 if (status != PJ_SUCCESS)
3048 goto on_error;
3049 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
3050 }
3051
3052 /* Add buddies */
3053 for (i=0; i<app_config.buddy_cnt; ++i) {
3054 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
3055 if (status != PJ_SUCCESS)
3056 goto on_error;
3057 }
3058
3059 /* Optionally set codec orders */
3060 for (i=0; i<app_config.codec_cnt; ++i) {
3061 pjsua_codec_set_priority(&app_config.codec_arg[i],
3062 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
3063 }
3064
3065 /* Add RTP transports */
3066 status = pjsua_media_transports_create(&app_config.rtp_cfg);
3067 if (status != PJ_SUCCESS)
3068 goto on_error;
3069
Benny Prijono22a300a2006-06-14 20:04:55 +00003070 /* Use null sound device? */
Benny Prijonoe909eac2006-07-27 22:04:56 +00003071#ifndef STEREO_DEMO
Benny Prijono22a300a2006-06-14 20:04:55 +00003072 if (app_config.null_audio) {
3073 status = pjsua_set_null_snd_dev();
3074 if (status != PJ_SUCCESS)
3075 return status;
3076 }
Benny Prijonoe909eac2006-07-27 22:04:56 +00003077#endif
Benny Prijono22a300a2006-06-14 20:04:55 +00003078
Benny Prijono4e5d5512007-03-06 18:11:30 +00003079 if (app_config.capture_dev != PJSUA_INVALID_ID
3080 || app_config.playback_dev != PJSUA_INVALID_ID) {
3081 status
3082 = pjsua_set_snd_dev(app_config.capture_dev, app_config.playback_dev);
3083 if (status != PJ_SUCCESS)
3084 goto on_error;
3085 }
3086
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003087 return PJ_SUCCESS;
3088
3089on_error:
Benny Prijonoad2e0ca2007-04-29 12:31:51 +00003090 app_destroy();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003091 return status;
3092}
3093
3094
3095pj_status_t app_main(void)
3096{
3097 pj_status_t status;
3098
3099 /* Start pjsua */
3100 status = pjsua_start();
3101 if (status != PJ_SUCCESS) {
Benny Prijonoad2e0ca2007-04-29 12:31:51 +00003102 app_destroy();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003103 return status;
3104 }
3105
3106 console_app_main(&uri_arg);
3107
3108 return PJ_SUCCESS;
3109}
3110
3111pj_status_t app_destroy(void)
3112{
Benny Prijonof762ee72006-12-01 11:14:37 +00003113 pj_status_t status;
Benny Prijono4af234b2007-01-24 02:02:09 +00003114 unsigned i;
Benny Prijonof762ee72006-12-01 11:14:37 +00003115
Benny Prijonoe909eac2006-07-27 22:04:56 +00003116#ifdef STEREO_DEMO
3117 if (app_config.snd) {
3118 pjmedia_snd_port_destroy(app_config.snd);
3119 app_config.snd = NULL;
3120 }
3121#endif
3122
Benny Prijono4af234b2007-01-24 02:02:09 +00003123 /* Close tone generators */
3124 for (i=0; i<app_config.tone_count; ++i) {
3125 pjsua_conf_remove_port(app_config.tone_slots[i]);
3126 }
3127
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003128 if (app_config.pool) {
3129 pj_pool_release(app_config.pool);
3130 app_config.pool = NULL;
3131 }
3132
Benny Prijonof762ee72006-12-01 11:14:37 +00003133 status = pjsua_destroy();
3134
3135 pj_bzero(&app_config, sizeof(app_config));
3136
3137 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003138}
Benny Prijonoe909eac2006-07-27 22:04:56 +00003139
3140
3141#ifdef STEREO_DEMO
3142static void stereo_demo()
3143{
3144 pjmedia_port *conf, *splitter, *ch1;
Benny Prijonoe909eac2006-07-27 22:04:56 +00003145 pj_status_t status;
3146
3147 /* Disable existing sound device */
3148 conf = pjsua_set_no_snd_dev();
3149
Benny Prijonoe909eac2006-07-27 22:04:56 +00003150 /* Create stereo-mono splitter/combiner */
3151 status = pjmedia_splitcomb_create(app_config.pool,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003152 conf->info.clock_rate /* clock rate */,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003153 2 /* stereo */,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003154 2 * conf->info.samples_per_frame,
3155 conf->info.bits_per_sample,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003156 0 /* options */,
3157 &splitter);
3158 pj_assert(status == PJ_SUCCESS);
3159
3160 /* Connect channel0 (left channel?) to conference port slot0 */
3161 status = pjmedia_splitcomb_set_channel(splitter, 0 /* ch0 */,
3162 0 /*options*/,
3163 conf);
3164 pj_assert(status == PJ_SUCCESS);
3165
3166 /* Create reverse channel for channel1 (right channel?)... */
3167 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
3168 splitter,
3169 1 /* ch1 */,
3170 0 /* options */,
3171 &ch1);
3172 pj_assert(status == PJ_SUCCESS);
3173
3174 /* .. and register it to conference bridge (it would be slot1
3175 * if there's no other devices connected to the bridge)
3176 */
3177 status = pjsua_conf_add_port(app_config.pool, ch1, NULL);
3178 pj_assert(status == PJ_SUCCESS);
3179
3180 /* Create sound device */
3181 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003182 conf->info.clock_rate,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003183 2 /* stereo */,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003184 2 * conf->info.samples_per_frame,
3185 conf->info.bits_per_sample,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003186 0, &app_config.snd);
3187 pj_assert(status == PJ_SUCCESS);
3188
3189
3190 /* Connect the splitter to the sound device */
3191 status = pjmedia_snd_port_connect(app_config.snd, splitter);
3192 pj_assert(status == PJ_SUCCESS);
3193
3194}
3195#endif
3196