blob: fe92fa2d6ac7214263f6feeae461d9136159900b [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
Benny Prijono4ab9fbb2007-10-12 12:14:27 +000022#define THIS_FILE "pjsua_app.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:");
Benny Prijono48ab2b72007-11-08 09:24:30 +0000120 puts (" --use-ims Enable 3GPP/IMS related settings on this account");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000121 puts (" --registrar=url Set the URL of registrar server");
122 puts (" --id=url Set the URL of local ID (used in From header)");
123 puts (" --contact=url Optionally override the Contact information");
124 puts (" --proxy=url Optional URL of proxy server to visit");
125 puts (" May be specified multiple times");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000126 puts (" --reg-timeout=SEC Optional registration interval (default 55)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000127 puts (" --realm=string Set realm");
128 puts (" --username=string Set authentication username");
129 puts (" --password=string Set authentication password");
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000130 puts (" --publish Send presence PUBLISH for this account");
Benny Prijonodcfc0ba2007-09-30 16:50:27 +0000131 puts (" --use-100rel Require reliable provisional response (100rel)");
Benny Prijonob67eaac2007-02-18 20:56:00 +0000132 puts (" --next-cred Add another credentials");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000133 puts ("");
134 puts ("SIP Account Control:");
135 puts (" --next-account Add more account");
136 puts ("");
137 puts ("Transport Options:");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000138 puts (" --local-port=port Set TCP/UDP port. This implicitly enables both ");
139 puts (" TCP and UDP transports on the specified port, unless");
140 puts (" if TCP or UDP is disabled.");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000141 puts (" --ip-addr=IP Use the specifed address as SIP and RTP addresses.");
142 puts (" (Hint: the IP may be the public IP of the NAT/router)");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000143 puts (" --no-tcp Disable TCP transport.");
144 puts (" --no-udp Disable UDP transport.");
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000145 puts (" --nameserver=NS Add the specified nameserver to enable SRV resolution");
146 puts (" This option can be specified multiple times.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000147 puts (" --outbound=url Set the URL of global outbound proxy server");
148 puts (" May be specified multiple times");
Benny Prijonoc97608e2007-03-23 16:34:20 +0000149 puts (" --stun-srv=name Set STUN server host or domain");
Benny Prijonof3bbc132006-12-25 06:43:59 +0000150 puts ("");
151 puts ("TLS Options:");
Benny Prijonob67eaac2007-02-18 20:56:00 +0000152 puts (" --use-tls Enable TLS transport (default=no)");
Benny Prijonof3bbc132006-12-25 06:43:59 +0000153 puts (" --tls-ca-file Specify TLS CA file (default=none)");
154 puts (" --tls-cert-file Specify TLS certificate file (default=none)");
155 puts (" --tls-privkey-file Specify TLS private key file (default=none)");
156 puts (" --tls-password Specify TLS password to private key file (default=none)");
157 puts (" --tls-verify-server Verify server's certificate (default=no)");
158 puts (" --tls-verify-client Verify client's certificate (default=no)");
159 puts (" --tls-neg-timeout Specify TLS negotiation timeout (default=no)");
160
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000161 puts ("");
162 puts ("Media Options:");
Benny Prijonoc97608e2007-03-23 16:34:20 +0000163 puts (" --use-ice Enable ICE (default:no)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000164 puts (" --add-codec=name Manually add codec (default is to enable all)");
165 puts (" --clock-rate=N Override sound device clock rate");
166 puts (" --null-audio Use NULL audio device");
Benny Prijono4af234b2007-01-24 02:02:09 +0000167 puts (" --play-file=file Register WAV file in conference bridge.");
168 puts (" This can be specified multiple times.");
Benny Prijonob67eaac2007-02-18 20:56:00 +0000169 puts (" --play-tone=FORMAT Register tone to the conference bridge.");
170 puts (" FORMAT is 'F1,F2,ON,OFF', where F1,F2 are");
171 puts (" frequencies, and ON,OFF=on/off duration in msec.");
Benny Prijono4af234b2007-01-24 02:02:09 +0000172 puts (" This can be specified multiple times.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000173 puts (" --auto-play Automatically play the file (to incoming calls only)");
174 puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP");
Benny Prijono7ca96da2006-08-07 12:11:40 +0000175 puts (" --auto-conf Automatically put calls in conference with others");
Benny Prijono1ebd6142006-10-19 15:48:02 +0000176 puts (" --rec-file=file Open file recorder (extension can be .wav or .mp3");
177 puts (" --auto-rec Automatically record conversation");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000178 puts (" --rtp-port=N Base port to try for RTP (default=4000)");
Benny Prijono00cae612006-07-31 15:19:36 +0000179 puts (" --quality=N Specify media quality (0-10, default=6)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000180 puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)");
Benny Prijono0a12f002006-07-26 17:05:39 +0000181 puts (" --no-vad Disable VAD/silence detector (default=vad enabled)");
Benny Prijonod79f25c2006-08-02 19:41:37 +0000182 puts (" --ec-tail=MSEC Set echo canceller tail length (default=256)");
Benny Prijono00cae612006-07-31 15:19:36 +0000183 puts (" --ilbc-mode=MODE Set iLBC codec mode (20 or 30, default is 20)");
184 puts (" --rx-drop-pct=PCT Drop PCT percent of RX RTP (for pkt lost sim, default: 0)");
185 puts (" --tx-drop-pct=PCT Drop PCT percent of TX RTP (for pkt lost sim, default: 0)");
Benny Prijono4e5d5512007-03-06 18:11:30 +0000186 puts (" --capture-dev=id Audio capture device ID (default=-1)");
187 puts (" --playback-dev=id Audio playback device ID (default=-1)");
Benny Prijono00cae612006-07-31 15:19:36 +0000188
Benny Prijono0a12f002006-07-26 17:05:39 +0000189
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000190 puts ("");
191 puts ("Buddy List (can be more than one):");
192 puts (" --add-buddy url Add the specified URL to the buddy list.");
193 puts ("");
194 puts ("User Agent options:");
195 puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)");
196 puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)");
Benny Prijonof521eb02006-08-06 23:07:25 +0000197 puts (" --thread-cnt=N Number of worker threads (default:1)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000198 puts (" --duration=SEC Set maximum call duration (default:no limit)");
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000199 puts (" --norefersub Suppress event subscription when transfering calls");
Benny Prijono804ff0a2006-09-14 11:17:48 +0000200
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000201 puts ("");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000202 puts ("When URL is specified, pjsua will immediately initiate call to that URL");
203 puts ("");
204
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000205 fflush(stdout);
206}
207
208
209/* Set default config. */
210static void default_config(struct app_config *cfg)
211{
Benny Prijono56315612006-07-18 14:39:40 +0000212 char tmp[80];
Benny Prijono1febfdf2007-02-18 01:35:04 +0000213 unsigned i;
Benny Prijono56315612006-07-18 14:39:40 +0000214
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000215 pjsua_config_default(&cfg->cfg);
Benny Prijono106f5b72007-08-30 13:49:33 +0000216 pj_ansi_sprintf(tmp, "PJSUA v%s/%s", pj_get_version(), PJ_OS_NAME);
Benny Prijono56315612006-07-18 14:39:40 +0000217 pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
218
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000219 pjsua_logging_config_default(&cfg->log_cfg);
220 pjsua_media_config_default(&cfg->media_cfg);
221 pjsua_transport_config_default(&cfg->udp_cfg);
222 cfg->udp_cfg.port = 5060;
223 pjsua_transport_config_default(&cfg->rtp_cfg);
224 cfg->rtp_cfg.port = 4000;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000225 cfg->duration = NO_LIMIT;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000226 cfg->wav_id = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000227 cfg->rec_id = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000228 cfg->wav_port = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000229 cfg->rec_port = PJSUA_INVALID_ID;
Benny Prijono6dd967c2006-12-26 02:27:14 +0000230 cfg->mic_level = cfg->speaker_level = 1.0;
Benny Prijono4e5d5512007-03-06 18:11:30 +0000231 cfg->capture_dev = PJSUA_INVALID_ID;
232 cfg->playback_dev = PJSUA_INVALID_ID;
Benny Prijono1febfdf2007-02-18 01:35:04 +0000233
234 for (i=0; i<PJ_ARRAY_SIZE(cfg->acc_cfg); ++i)
235 pjsua_acc_config_default(&cfg->acc_cfg[i]);
236
237 for (i=0; i<PJ_ARRAY_SIZE(cfg->buddy_cfg); ++i)
238 pjsua_buddy_config_default(&cfg->buddy_cfg[i]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000239}
240
241
242/*
243 * Read command arguments from config file.
244 */
245static int read_config_file(pj_pool_t *pool, const char *filename,
246 int *app_argc, char ***app_argv)
247{
248 int i;
249 FILE *fhnd;
250 char line[200];
251 int argc = 0;
252 char **argv;
253 enum { MAX_ARGS = 64 };
254
255 /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
256 argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
257 argv[argc++] = *app_argv[0];
258
259 /* Open config file. */
260 fhnd = fopen(filename, "rt");
261 if (!fhnd) {
262 PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
263 fflush(stdout);
264 return -1;
265 }
266
267 /* Scan tokens in the file. */
268 while (argc < MAX_ARGS && !feof(fhnd)) {
Benny Prijonobf5b4692007-06-28 03:20:17 +0000269 char *token;
270 char *p;
271 const char *whitespace = " \t\r\n";
272 char cDelimiter;
273 int len, token_len;
274
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000275 if (fgets(line, sizeof(line), fhnd) == NULL) break;
Benny Prijonobf5b4692007-06-28 03:20:17 +0000276
277 // Trim ending newlines
278 len = strlen(line);
279 if (line[len-1]=='\n')
280 line[--len] = '\0';
281 if (line[len-1]=='\r')
282 line[--len] = '\0';
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000283
Benny Prijonobf5b4692007-06-28 03:20:17 +0000284 if (len==0) continue;
285
286 for (p = line; *p != '\0' && argc < MAX_ARGS; p++) {
287 // first, scan whitespaces
288 while (*p != '\0' && strchr(whitespace, *p) != NULL) p++;
289
290 if (*p == '\0') // are we done yet?
291 break;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000292
Benny Prijonobf5b4692007-06-28 03:20:17 +0000293 if (*p == '"' || *p == '\'') { // is token a quoted string
294 cDelimiter = *p++; // save quote delimiter
295 token = p;
296
297 while (*p != '\0' && *p != cDelimiter) p++;
298
299 if (*p == '\0') // found end of the line, but,
300 cDelimiter = '\0'; // didn't find a matching quote
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000301
Benny Prijonobf5b4692007-06-28 03:20:17 +0000302 } else { // token's not a quoted string
303 token = p;
304
305 while (*p != '\0' && strchr(whitespace, *p) == NULL) p++;
306
307 cDelimiter = *p;
308 }
309
310 *p = '\0';
311 token_len = p-token;
312
313 if (token_len > 0) {
314 if (*token == '#')
315 break; // ignore remainder of line
316
317 argv[argc] = pj_pool_alloc(pool, token_len + 1);
318 pj_memcpy(argv[argc], token, token_len + 1);
319 ++argc;
320 }
321
322 *p = cDelimiter;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000323 }
324 }
325
326 /* Copy arguments from command line */
327 for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
328 argv[argc++] = (*app_argv)[i];
329
330 if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
331 PJ_LOG(1,(THIS_FILE,
332 "Too many arguments specified in cmd line/config file"));
333 fflush(stdout);
334 fclose(fhnd);
335 return -1;
336 }
337
338 fclose(fhnd);
339
340 /* Assign the new command line back to the original command line. */
341 *app_argc = argc;
342 *app_argv = argv;
343 return 0;
344
345}
346
347static int my_atoi(const char *cs)
348{
349 pj_str_t s;
Benny Prijono1e2dbe62007-06-15 04:15:16 +0000350
351 pj_cstr(&s, cs);
352 if (cs[0] == '-') {
353 s.ptr++, s.slen--;
354 return 0 - (int)pj_strtoul(&s);
355 } else if (cs[0] == '+') {
356 s.ptr++, s.slen--;
357 return pj_strtoul(&s);
358 } else {
359 return pj_strtoul(&s);
360 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000361}
362
363
364/* Parse arguments. */
365static pj_status_t parse_args(int argc, char *argv[],
366 struct app_config *cfg,
367 pj_str_t *uri_to_call)
368{
369 int c;
370 int option_index;
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000371 enum { OPT_CONFIG_FILE=127, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000372 OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
Benny Prijono0a5cad82006-09-26 13:21:02 +0000373 OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY,
374 OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
Benny Prijono48ab2b72007-11-08 09:24:30 +0000375 OPT_100REL, OPT_USE_IMS, OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
Benny Prijonoebbf6892007-03-24 17:37:25 +0000376 OPT_NAMESERVER, OPT_STUN_DOMAIN, OPT_STUN_SRV,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000377 OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
378 OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000379 OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_USE_ICE,
Benny Prijono4af234b2007-01-24 02:02:09 +0000380 OPT_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC,
381 OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC,
Benny Prijono0a12f002006-07-26 17:05:39 +0000382 OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
Benny Prijonod79f25c2006-08-02 19:41:37 +0000383 OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL,
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000384 OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
Benny Prijonof521eb02006-08-06 23:07:25 +0000385 OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000386 OPT_NOREFERSUB,
Benny Prijonof3bbc132006-12-25 06:43:59 +0000387 OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_CERT_FILE, OPT_TLS_PRIV_FILE,
388 OPT_TLS_PASSWORD, OPT_TLS_VERIFY_SERVER, OPT_TLS_VERIFY_CLIENT,
389 OPT_TLS_NEG_TIMEOUT,
Benny Prijono4e5d5512007-03-06 18:11:30 +0000390 OPT_CAPTURE_DEV, OPT_PLAYBACK_DEV,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000391 };
392 struct pj_getopt_option long_options[] = {
393 { "config-file",1, 0, OPT_CONFIG_FILE},
394 { "log-file", 1, 0, OPT_LOG_FILE},
395 { "log-level", 1, 0, OPT_LOG_LEVEL},
396 { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
397 { "help", 0, 0, OPT_HELP},
398 { "version", 0, 0, OPT_VERSION},
399 { "clock-rate", 1, 0, OPT_CLOCK_RATE},
400 { "null-audio", 0, 0, OPT_NULL_AUDIO},
401 { "local-port", 1, 0, OPT_LOCAL_PORT},
Benny Prijono0a5cad82006-09-26 13:21:02 +0000402 { "ip-addr", 1, 0, OPT_IP_ADDR},
Benny Prijonoe93e2872006-06-28 16:46:49 +0000403 { "no-tcp", 0, 0, OPT_NO_TCP},
404 { "no-udp", 0, 0, OPT_NO_UDP},
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000405 { "norefersub", 0, 0, OPT_NOREFERSUB},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000406 { "proxy", 1, 0, OPT_PROXY},
407 { "outbound", 1, 0, OPT_OUTBOUND_PROXY},
408 { "registrar", 1, 0, OPT_REGISTRAR},
409 { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000410 { "publish", 0, 0, OPT_PUBLISH},
Benny Prijonodcfc0ba2007-09-30 16:50:27 +0000411 { "use-100rel", 0, 0, OPT_100REL},
Benny Prijono48ab2b72007-11-08 09:24:30 +0000412 { "use-ims", 0, 0, OPT_USE_IMS},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000413 { "id", 1, 0, OPT_ID},
414 { "contact", 1, 0, OPT_CONTACT},
415 { "realm", 1, 0, OPT_REALM},
416 { "username", 1, 0, OPT_USERNAME},
417 { "password", 1, 0, OPT_PASSWORD},
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000418 { "nameserver", 1, 0, OPT_NAMESERVER},
Benny Prijonoebbf6892007-03-24 17:37:25 +0000419 { "stun-domain",1, 0, OPT_STUN_DOMAIN},
Benny Prijonoc97608e2007-03-23 16:34:20 +0000420 { "stun-srv", 1, 0, OPT_STUN_SRV},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000421 { "add-buddy", 1, 0, OPT_ADD_BUDDY},
422 { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
423 { "no-presence", 0, 0, OPT_NO_PRESENCE},
424 { "auto-answer",1, 0, OPT_AUTO_ANSWER},
425 { "auto-hangup",1, 0, OPT_AUTO_HANGUP},
426 { "auto-play", 0, 0, OPT_AUTO_PLAY},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000427 { "auto-rec", 0, 0, OPT_AUTO_REC},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000428 { "auto-loop", 0, 0, OPT_AUTO_LOOP},
429 { "auto-conf", 0, 0, OPT_AUTO_CONF},
430 { "play-file", 1, 0, OPT_PLAY_FILE},
Benny Prijono4af234b2007-01-24 02:02:09 +0000431 { "play-tone", 1, 0, OPT_PLAY_TONE},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000432 { "rec-file", 1, 0, OPT_REC_FILE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000433 { "rtp-port", 1, 0, OPT_RTP_PORT},
Benny Prijonoc97608e2007-03-23 16:34:20 +0000434 { "use-ice", 0, 0, OPT_USE_ICE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000435 { "add-codec", 1, 0, OPT_ADD_CODEC},
436 { "complexity", 1, 0, OPT_COMPLEXITY},
437 { "quality", 1, 0, OPT_QUALITY},
438 { "ptime", 1, 0, OPT_PTIME},
Benny Prijono0a12f002006-07-26 17:05:39 +0000439 { "no-vad", 0, 0, OPT_NO_VAD},
Benny Prijonod79f25c2006-08-02 19:41:37 +0000440 { "ec-tail", 1, 0, OPT_EC_TAIL},
Benny Prijono00cae612006-07-31 15:19:36 +0000441 { "ilbc-mode", 1, 0, OPT_ILBC_MODE},
442 { "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
443 { "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000444 { "next-account",0,0, OPT_NEXT_ACCOUNT},
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000445 { "next-cred", 0, 0, OPT_NEXT_CRED},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000446 { "max-calls", 1, 0, OPT_MAX_CALLS},
Benny Prijonof521eb02006-08-06 23:07:25 +0000447 { "duration", 1, 0, OPT_DURATION},
448 { "thread-cnt", 1, 0, OPT_THREAD_CNT},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000449 { "use-tls", 0, 0, OPT_USE_TLS},
450 { "tls-ca-file",1, 0, OPT_TLS_CA_FILE},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000451 { "tls-cert-file",1,0, OPT_TLS_CERT_FILE},
452 { "tls-privkey-file",1,0, OPT_TLS_PRIV_FILE},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000453 { "tls-password",1,0, OPT_TLS_PASSWORD},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000454 { "tls-verify-server", 0, 0, OPT_TLS_VERIFY_SERVER},
455 { "tls-verify-client", 0, 0, OPT_TLS_VERIFY_CLIENT},
456 { "tls-neg-timeout", 1, 0, OPT_TLS_NEG_TIMEOUT},
Benny Prijono4e5d5512007-03-06 18:11:30 +0000457 { "capture-dev", 1, 0, OPT_CAPTURE_DEV},
458 { "playback-dev", 1, 0, OPT_PLAYBACK_DEV},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000459 { NULL, 0, 0, 0}
460 };
461 pj_status_t status;
462 pjsua_acc_config *cur_acc;
463 char *config_file = NULL;
464 unsigned i;
465
466 /* Run pj_getopt once to see if user specifies config file to read. */
Benny Prijonof762ee72006-12-01 11:14:37 +0000467 pj_optind = 0;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000468 while ((c=pj_getopt_long(argc, argv, "", long_options,
469 &option_index)) != -1)
470 {
471 switch (c) {
472 case OPT_CONFIG_FILE:
473 config_file = pj_optarg;
474 break;
475 }
476 if (config_file)
477 break;
478 }
479
480 if (config_file) {
481 status = read_config_file(app_config.pool, config_file, &argc, &argv);
482 if (status != 0)
483 return status;
484 }
485
486 cfg->acc_cnt = 0;
487 cur_acc = &cfg->acc_cfg[0];
488
489
490 /* Reinitialize and re-run pj_getopt again, possibly with new arguments
491 * read from config file.
492 */
493 pj_optind = 0;
494 while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000495 pj_str_t tmp;
496 long lval;
497
498 switch (c) {
499
Benny Prijono6f137482006-06-15 11:31:36 +0000500 case OPT_CONFIG_FILE:
501 /* Ignore as this has been processed before */
502 break;
503
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000504 case OPT_LOG_FILE:
505 cfg->log_cfg.log_filename = pj_str(pj_optarg);
506 break;
507
508 case OPT_LOG_LEVEL:
509 c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
510 if (c < 0 || c > 6) {
511 PJ_LOG(1,(THIS_FILE,
512 "Error: expecting integer value 0-6 "
513 "for --log-level"));
514 return PJ_EINVAL;
515 }
516 cfg->log_cfg.level = c;
517 pj_log_set_level( c );
518 break;
519
520 case OPT_APP_LOG_LEVEL:
521 cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
522 if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
523 PJ_LOG(1,(THIS_FILE,
524 "Error: expecting integer value 0-6 "
525 "for --app-log-level"));
526 return PJ_EINVAL;
527 }
528 break;
529
530 case OPT_HELP:
531 usage();
532 return PJ_EINVAL;
533
534 case OPT_VERSION: /* version */
535 pj_dump_config();
536 return PJ_EINVAL;
537
538 case OPT_NULL_AUDIO:
539 cfg->null_audio = PJ_TRUE;
540 break;
541
542 case OPT_CLOCK_RATE:
543 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
544 if (lval < 8000 || lval > 48000) {
545 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
546 "8000-48000 for clock rate"));
547 return PJ_EINVAL;
548 }
549 cfg->media_cfg.clock_rate = lval;
550 break;
551
552 case OPT_LOCAL_PORT: /* local-port */
553 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
Benny Prijonoe347cb02007-02-14 14:36:13 +0000554 if (lval < 0 || lval > 65535) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000555 PJ_LOG(1,(THIS_FILE,
556 "Error: expecting integer value for "
557 "--local-port"));
558 return PJ_EINVAL;
559 }
560 cfg->udp_cfg.port = (pj_uint16_t)lval;
561 break;
562
Benny Prijono0a5cad82006-09-26 13:21:02 +0000563 case OPT_IP_ADDR: /* ip-addr */
564 cfg->udp_cfg.public_addr = pj_str(pj_optarg);
565 cfg->rtp_cfg.public_addr = pj_str(pj_optarg);
566 break;
567
Benny Prijonoe93e2872006-06-28 16:46:49 +0000568 case OPT_NO_UDP: /* no-udp */
569 if (cfg->no_tcp) {
Benny Prijonob988d762007-12-05 04:30:04 +0000570 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
571 return PJ_EINVAL;
Benny Prijonoe93e2872006-06-28 16:46:49 +0000572 }
573
574 cfg->no_udp = PJ_TRUE;
575 break;
576
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000577 case OPT_NOREFERSUB: /* norefersub */
578 cfg->no_refersub = PJ_TRUE;
579 break;
580
Benny Prijonoe93e2872006-06-28 16:46:49 +0000581 case OPT_NO_TCP: /* no-tcp */
582 if (cfg->no_udp) {
Benny Prijonob988d762007-12-05 04:30:04 +0000583 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
584 return PJ_EINVAL;
Benny Prijonoe93e2872006-06-28 16:46:49 +0000585 }
586
587 cfg->no_tcp = PJ_TRUE;
588 break;
589
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000590 case OPT_PROXY: /* proxy */
591 if (pjsua_verify_sip_url(pj_optarg) != 0) {
592 PJ_LOG(1,(THIS_FILE,
593 "Error: invalid SIP URL '%s' "
594 "in proxy argument", pj_optarg));
595 return PJ_EINVAL;
596 }
597 cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
598 break;
599
600 case OPT_OUTBOUND_PROXY: /* outbound proxy */
601 if (pjsua_verify_sip_url(pj_optarg) != 0) {
602 PJ_LOG(1,(THIS_FILE,
603 "Error: invalid SIP URL '%s' "
604 "in outbound proxy argument", pj_optarg));
605 return PJ_EINVAL;
606 }
607 cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
608 break;
609
610 case OPT_REGISTRAR: /* registrar */
611 if (pjsua_verify_sip_url(pj_optarg) != 0) {
612 PJ_LOG(1,(THIS_FILE,
613 "Error: invalid SIP URL '%s' in "
614 "registrar argument", pj_optarg));
615 return PJ_EINVAL;
616 }
617 cur_acc->reg_uri = pj_str(pj_optarg);
618 break;
619
620 case OPT_REG_TIMEOUT: /* reg-timeout */
621 cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
622 if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
623 PJ_LOG(1,(THIS_FILE,
624 "Error: invalid value for --reg-timeout "
625 "(expecting 1-3600)"));
626 return PJ_EINVAL;
627 }
628 break;
629
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000630 case OPT_PUBLISH: /* publish */
631 cur_acc->publish_enabled = PJ_TRUE;
632 break;
633
Benny Prijonodcfc0ba2007-09-30 16:50:27 +0000634 case OPT_100REL: /** 100rel */
635 cur_acc->require_100rel = PJ_TRUE;
636 cfg->cfg.require_100rel = PJ_TRUE;
637 break;
638
Benny Prijono48ab2b72007-11-08 09:24:30 +0000639 case OPT_USE_IMS: /* Activate IMS settings */
640 cur_acc->auth_pref.initial_auth = PJ_TRUE;
Benny Prijono2a67ea42007-10-25 02:51:33 +0000641 break;
642
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000643 case OPT_ID: /* id */
644 if (pjsua_verify_sip_url(pj_optarg) != 0) {
645 PJ_LOG(1,(THIS_FILE,
646 "Error: invalid SIP URL '%s' "
647 "in local id argument", pj_optarg));
648 return PJ_EINVAL;
649 }
650 cur_acc->id = pj_str(pj_optarg);
651 break;
652
653 case OPT_CONTACT: /* contact */
654 if (pjsua_verify_sip_url(pj_optarg) != 0) {
655 PJ_LOG(1,(THIS_FILE,
656 "Error: invalid SIP URL '%s' "
657 "in contact argument", pj_optarg));
658 return PJ_EINVAL;
659 }
Benny Prijonob4a17c92006-07-10 14:40:21 +0000660 cur_acc->force_contact = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000661 break;
662
663 case OPT_NEXT_ACCOUNT: /* Add more account. */
664 cfg->acc_cnt++;
Benny Prijono56315612006-07-18 14:39:40 +0000665 cur_acc = &cfg->acc_cfg[cfg->acc_cnt];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000666 break;
667
668 case OPT_USERNAME: /* Default authentication user */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000669 cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
Benny Prijono48ab2b72007-11-08 09:24:30 +0000670 cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("Digest");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000671 break;
672
673 case OPT_REALM: /* Default authentication realm. */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000674 cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000675 break;
676
677 case OPT_PASSWORD: /* authentication password */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000678 cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
679 cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
Benny Prijono28f673a2007-10-15 07:04:59 +0000680#if PJSIP_HAS_DIGEST_AKA_AUTH
681 cur_acc->cred_info[cur_acc->cred_count].data_type |= PJSIP_CRED_DATA_EXT_AKA;
682 cur_acc->cred_info[cur_acc->cred_count].ext.aka.k = pj_str(pj_optarg);
683 cur_acc->cred_info[cur_acc->cred_count].ext.aka.cb = &pjsip_auth_create_aka_response;
684#endif
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000685 break;
686
687 case OPT_NEXT_CRED: /* next credential */
688 cur_acc->cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000689 break;
690
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000691 case OPT_NAMESERVER: /* nameserver */
692 cfg->cfg.nameserver[cfg->cfg.nameserver_count++] = pj_str(pj_optarg);
693 if (cfg->cfg.nameserver_count > PJ_ARRAY_SIZE(cfg->cfg.nameserver)) {
694 PJ_LOG(1,(THIS_FILE, "Error: too many nameservers"));
695 return PJ_ETOOMANY;
696 }
697 break;
698
Benny Prijonoebbf6892007-03-24 17:37:25 +0000699 case OPT_STUN_DOMAIN: /* STUN domain */
700 cfg->cfg.stun_domain = pj_str(pj_optarg);
701 break;
702
Benny Prijonoc97608e2007-03-23 16:34:20 +0000703 case OPT_STUN_SRV: /* STUN server */
Benny Prijonoebbf6892007-03-24 17:37:25 +0000704 cfg->cfg.stun_host = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000705 break;
706
707 case OPT_ADD_BUDDY: /* Add to buddy list. */
708 if (pjsua_verify_sip_url(pj_optarg) != 0) {
709 PJ_LOG(1,(THIS_FILE,
710 "Error: invalid URL '%s' in "
711 "--add-buddy option", pj_optarg));
712 return -1;
713 }
714 if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
715 PJ_LOG(1,(THIS_FILE,
716 "Error: too many buddies in buddy list."));
717 return -1;
718 }
719 cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
720 cfg->buddy_cnt++;
721 break;
722
723 case OPT_AUTO_PLAY:
724 cfg->auto_play = 1;
725 break;
726
Benny Prijono1ebd6142006-10-19 15:48:02 +0000727 case OPT_AUTO_REC:
728 cfg->auto_rec = 1;
729 break;
730
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000731 case OPT_AUTO_LOOP:
732 cfg->auto_loop = 1;
733 break;
734
Benny Prijono7ca96da2006-08-07 12:11:40 +0000735 case OPT_AUTO_CONF:
736 cfg->auto_conf = 1;
737 break;
738
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000739 case OPT_PLAY_FILE:
Benny Prijono32e4f492007-01-24 00:44:26 +0000740 cfg->wav_files[cfg->wav_count++] = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000741 break;
742
Benny Prijono4af234b2007-01-24 02:02:09 +0000743 case OPT_PLAY_TONE:
744 {
745 int f1, f2, on, off;
746 int n;
747
748 n = sscanf(pj_optarg, "%d,%d,%d,%d", &f1, &f2, &on, &off);
749 if (n != 4) {
750 puts("Expecting f1,f2,on,off in --play-tone");
751 return -1;
752 }
753
754 cfg->tones[cfg->tone_count].freq1 = (short)f1;
755 cfg->tones[cfg->tone_count].freq2 = (short)f2;
756 cfg->tones[cfg->tone_count].on_msec = (short)on;
757 cfg->tones[cfg->tone_count].off_msec = (short)off;
758 ++cfg->tone_count;
759 }
760 break;
761
Benny Prijono1ebd6142006-10-19 15:48:02 +0000762 case OPT_REC_FILE:
763 cfg->rec_file = pj_str(pj_optarg);
764 break;
765
Benny Prijonoc97608e2007-03-23 16:34:20 +0000766 case OPT_USE_ICE:
767 cfg->media_cfg.enable_ice = PJ_TRUE;
768 break;
769
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000770 case OPT_RTP_PORT:
771 cfg->rtp_cfg.port = my_atoi(pj_optarg);
Benny Prijono5583a802007-06-26 12:20:37 +0000772 if (cfg->rtp_cfg.port == 0) {
773 enum { START_PORT=4000 };
774 unsigned range;
775
776 range = (65535-START_PORT-PJSUA_MAX_CALLS*2);
777 cfg->rtp_cfg.port = START_PORT +
778 ((pj_rand() % range) & 0xFFFE);
779 }
780
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000781 if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
782 PJ_LOG(1,(THIS_FILE,
783 "Error: rtp-port argument value "
784 "(expecting 1-65535"));
785 return -1;
786 }
787 break;
788
789 case OPT_ADD_CODEC:
790 cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
791 break;
792
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000793 /* These options were no longer valid after new pjsua */
794 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000795 case OPT_COMPLEXITY:
796 cfg->complexity = my_atoi(pj_optarg);
797 if (cfg->complexity < 0 || cfg->complexity > 10) {
798 PJ_LOG(1,(THIS_FILE,
799 "Error: invalid --complexity (expecting 0-10"));
800 return -1;
801 }
802 break;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000803 */
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000804
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000805 case OPT_DURATION:
806 cfg->duration = my_atoi(pj_optarg);
807 break;
808
Benny Prijonof521eb02006-08-06 23:07:25 +0000809 case OPT_THREAD_CNT:
810 cfg->cfg.thread_cnt = my_atoi(pj_optarg);
811 if (cfg->cfg.thread_cnt > 128) {
812 PJ_LOG(1,(THIS_FILE,
813 "Error: invalid --thread-cnt option"));
814 return -1;
815 }
816 break;
817
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000818 case OPT_PTIME:
Benny Prijono0a12f002006-07-26 17:05:39 +0000819 cfg->media_cfg.ptime = my_atoi(pj_optarg);
820 if (cfg->media_cfg.ptime < 10 || cfg->media_cfg.ptime > 1000) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000821 PJ_LOG(1,(THIS_FILE,
822 "Error: invalid --ptime option"));
823 return -1;
824 }
825 break;
826
Benny Prijono0a12f002006-07-26 17:05:39 +0000827 case OPT_NO_VAD:
828 cfg->media_cfg.no_vad = PJ_TRUE;
829 break;
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000830
Benny Prijonod79f25c2006-08-02 19:41:37 +0000831 case OPT_EC_TAIL:
832 cfg->media_cfg.ec_tail_len = my_atoi(pj_optarg);
833 if (cfg->media_cfg.ec_tail_len > 1000) {
834 PJ_LOG(1,(THIS_FILE, "I think the ec-tail length setting "
835 "is too big"));
836 return -1;
837 }
838 break;
839
Benny Prijono0498d902006-06-19 14:49:14 +0000840 case OPT_QUALITY:
841 cfg->media_cfg.quality = my_atoi(pj_optarg);
842 if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
843 PJ_LOG(1,(THIS_FILE,
844 "Error: invalid --quality (expecting 0-10"));
845 return -1;
846 }
847 break;
848
Benny Prijono00cae612006-07-31 15:19:36 +0000849 case OPT_ILBC_MODE:
850 cfg->media_cfg.ilbc_mode = my_atoi(pj_optarg);
851 if (cfg->media_cfg.ilbc_mode!=20 && cfg->media_cfg.ilbc_mode!=30) {
852 PJ_LOG(1,(THIS_FILE,
853 "Error: invalid --ilbc-mode (expecting 20 or 30"));
854 return -1;
855 }
856 break;
857
858 case OPT_RX_DROP_PCT:
859 cfg->media_cfg.rx_drop_pct = my_atoi(pj_optarg);
860 if (cfg->media_cfg.rx_drop_pct > 100) {
861 PJ_LOG(1,(THIS_FILE,
862 "Error: invalid --rx-drop-pct (expecting <= 100"));
863 return -1;
864 }
865 break;
866
867 case OPT_TX_DROP_PCT:
868 cfg->media_cfg.tx_drop_pct = my_atoi(pj_optarg);
869 if (cfg->media_cfg.tx_drop_pct > 100) {
870 PJ_LOG(1,(THIS_FILE,
871 "Error: invalid --tx-drop-pct (expecting <= 100"));
872 return -1;
873 }
874 break;
875
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000876 case OPT_AUTO_ANSWER:
877 cfg->auto_answer = my_atoi(pj_optarg);
878 if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
879 PJ_LOG(1,(THIS_FILE,
880 "Error: invalid code in --auto-answer "
881 "(expecting 100-699"));
882 return -1;
883 }
884 break;
885
886 case OPT_MAX_CALLS:
887 cfg->cfg.max_calls = my_atoi(pj_optarg);
Benny Prijono48af79c2006-07-22 12:49:17 +0000888 if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > PJSUA_MAX_CALLS) {
Benny Prijono804ff0a2006-09-14 11:17:48 +0000889 PJ_LOG(1,(THIS_FILE,"Error: maximum call setting exceeds "
890 "compile time limit (PJSUA_MAX_CALLS=%d)",
Benny Prijono48af79c2006-07-22 12:49:17 +0000891 PJSUA_MAX_CALLS));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000892 return -1;
893 }
894 break;
895
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000896 case OPT_USE_TLS:
897 cfg->use_tls = PJ_TRUE;
Benny Prijonof3bbc132006-12-25 06:43:59 +0000898#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
899 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
900 return -1;
901#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000902 break;
903
904 case OPT_TLS_CA_FILE:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000905 cfg->udp_cfg.tls_setting.ca_list_file = pj_str(pj_optarg);
906#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
907 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
908 return -1;
909#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000910 break;
911
Benny Prijonof3bbc132006-12-25 06:43:59 +0000912 case OPT_TLS_CERT_FILE:
913 cfg->udp_cfg.tls_setting.cert_file = pj_str(pj_optarg);
914#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
915 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
916 return -1;
917#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000918 break;
919
Benny Prijonof3bbc132006-12-25 06:43:59 +0000920 case OPT_TLS_PRIV_FILE:
921 cfg->udp_cfg.tls_setting.privkey_file = pj_str(pj_optarg);
922 break;
923
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000924 case OPT_TLS_PASSWORD:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000925 cfg->udp_cfg.tls_setting.password = pj_str(pj_optarg);
926#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
927 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
928 return -1;
929#endif
930 break;
931
932 case OPT_TLS_VERIFY_SERVER:
933 cfg->udp_cfg.tls_setting.verify_server = PJ_TRUE;
934 break;
935
936 case OPT_TLS_VERIFY_CLIENT:
937 cfg->udp_cfg.tls_setting.verify_client = PJ_TRUE;
938 break;
939
940 case OPT_TLS_NEG_TIMEOUT:
941 cfg->udp_cfg.tls_setting.timeout.sec = atoi(pj_optarg);
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000942 break;
943
Benny Prijono4e5d5512007-03-06 18:11:30 +0000944 case OPT_CAPTURE_DEV:
945 cfg->capture_dev = atoi(pj_optarg);
946 break;
947
948 case OPT_PLAYBACK_DEV:
949 cfg->playback_dev = atoi(pj_optarg);
950 break;
951
Benny Prijono22a300a2006-06-14 20:04:55 +0000952 default:
Benny Prijono787b8692006-06-19 12:40:03 +0000953 PJ_LOG(1,(THIS_FILE,
Benny Prijonod6388ac2006-09-09 13:23:09 +0000954 "Argument \"%s\" is not valid. Use --help to see help",
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000955 argv[pj_optind-1]));
Benny Prijono22a300a2006-06-14 20:04:55 +0000956 return -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000957 }
958 }
959
960 if (pj_optind != argc) {
961 pj_str_t uri_arg;
962
963 if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
964 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
965 return -1;
966 }
967 uri_arg = pj_str(argv[pj_optind]);
968 if (uri_to_call)
969 *uri_to_call = uri_arg;
970 pj_optind++;
971
972 /* Add URI to call to buddy list if it's not already there */
973 for (i=0; i<cfg->buddy_cnt; ++i) {
974 if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
975 break;
976 }
977 if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
978 cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
979 }
980
981 } else {
982 if (uri_to_call)
983 uri_to_call->slen = 0;
984 }
985
986 if (pj_optind != argc) {
987 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
988 return PJ_EINVAL;
989 }
990
Benny Prijono56315612006-07-18 14:39:40 +0000991 if (cfg->acc_cfg[cfg->acc_cnt].id.slen)
992 cfg->acc_cnt++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000993
994 for (i=0; i<cfg->acc_cnt; ++i) {
Benny Prijono48ab2b72007-11-08 09:24:30 +0000995 pjsua_acc_config *acfg = &cfg->acc_cfg[i];
996
997 if (acfg->cred_info[acfg->cred_count].username.slen)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000998 {
Benny Prijono48ab2b72007-11-08 09:24:30 +0000999 acfg->cred_count++;
1000 }
1001
1002 /* When IMS mode is enabled for the account, verify that settings
1003 * are okay.
1004 */
1005 /* For now we check if IMS mode is activated by looking if
1006 * initial_auth is set.
1007 */
1008 if (acfg->auth_pref.initial_auth && acfg->cred_count) {
1009 /* Realm must point to the real domain */
1010 if (*acfg->cred_info[0].realm.ptr=='*') {
1011 PJ_LOG(1,(THIS_FILE,
1012 "Error: cannot use '*' as realm with IMS"));
1013 return PJ_EINVAL;
1014 }
1015
1016 /* Username for authentication must be in a@b format */
1017 if (strchr(acfg->cred_info[0].username.ptr, '@')==0) {
1018 PJ_LOG(1,(THIS_FILE,
1019 "Error: Username for authentication must "
1020 "be in user@domain format with IMS"));
1021 return PJ_EINVAL;
1022 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001023 }
1024 }
1025
1026
1027 return PJ_SUCCESS;
1028}
1029
1030
1031/*
1032 * Save account settings
1033 */
1034static void write_account_settings(int acc_index, pj_str_t *result)
1035{
1036 unsigned i;
1037 char line[128];
1038 pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
1039
1040
1041 pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
1042 pj_strcat2(result, line);
1043
1044
1045 /* Identity */
1046 if (acc_cfg->id.slen) {
1047 pj_ansi_sprintf(line, "--id %.*s\n",
1048 (int)acc_cfg->id.slen,
1049 acc_cfg->id.ptr);
1050 pj_strcat2(result, line);
1051 }
1052
1053 /* Registrar server */
1054 if (acc_cfg->reg_uri.slen) {
1055 pj_ansi_sprintf(line, "--registrar %.*s\n",
1056 (int)acc_cfg->reg_uri.slen,
1057 acc_cfg->reg_uri.ptr);
1058 pj_strcat2(result, line);
1059
1060 pj_ansi_sprintf(line, "--reg-timeout %u\n",
1061 acc_cfg->reg_timeout);
1062 pj_strcat2(result, line);
1063 }
1064
1065 /* Contact */
Benny Prijonob4a17c92006-07-10 14:40:21 +00001066 if (acc_cfg->force_contact.slen) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001067 pj_ansi_sprintf(line, "--contact %.*s\n",
Benny Prijonob4a17c92006-07-10 14:40:21 +00001068 (int)acc_cfg->force_contact.slen,
1069 acc_cfg->force_contact.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001070 pj_strcat2(result, line);
1071 }
1072
1073 /* Proxy */
1074 for (i=0; i<acc_cfg->proxy_cnt; ++i) {
1075 pj_ansi_sprintf(line, "--proxy %.*s\n",
1076 (int)acc_cfg->proxy[i].slen,
1077 acc_cfg->proxy[i].ptr);
1078 pj_strcat2(result, line);
1079 }
1080
1081 /* Credentials */
1082 for (i=0; i<acc_cfg->cred_count; ++i) {
1083 if (acc_cfg->cred_info[i].realm.slen) {
1084 pj_ansi_sprintf(line, "--realm %.*s\n",
1085 (int)acc_cfg->cred_info[i].realm.slen,
1086 acc_cfg->cred_info[i].realm.ptr);
1087 pj_strcat2(result, line);
1088 }
1089
1090 if (acc_cfg->cred_info[i].username.slen) {
1091 pj_ansi_sprintf(line, "--username %.*s\n",
1092 (int)acc_cfg->cred_info[i].username.slen,
1093 acc_cfg->cred_info[i].username.ptr);
1094 pj_strcat2(result, line);
1095 }
1096
1097 if (acc_cfg->cred_info[i].data.slen) {
1098 pj_ansi_sprintf(line, "--password %.*s\n",
1099 (int)acc_cfg->cred_info[i].data.slen,
1100 acc_cfg->cred_info[i].data.ptr);
1101 pj_strcat2(result, line);
1102 }
Benny Prijonoeef4f8c2006-06-19 12:07:44 +00001103
1104 if (i != acc_cfg->cred_count - 1)
1105 pj_strcat2(result, "--next-cred\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001106 }
1107
1108}
1109
1110
1111/*
1112 * Write settings.
1113 */
1114static int write_settings(const struct app_config *config,
1115 char *buf, pj_size_t max)
1116{
1117 unsigned acc_index;
1118 unsigned i;
1119 pj_str_t cfg;
1120 char line[128];
1121
1122 PJ_UNUSED_ARG(max);
1123
1124 cfg.ptr = buf;
1125 cfg.slen = 0;
1126
1127 /* Logging. */
1128 pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
1129 pj_ansi_sprintf(line, "--log-level %d\n",
1130 config->log_cfg.level);
1131 pj_strcat2(&cfg, line);
1132
1133 pj_ansi_sprintf(line, "--app-log-level %d\n",
1134 config->log_cfg.console_level);
1135 pj_strcat2(&cfg, line);
1136
1137 if (config->log_cfg.log_filename.slen) {
1138 pj_ansi_sprintf(line, "--log-file %.*s\n",
1139 (int)config->log_cfg.log_filename.slen,
1140 config->log_cfg.log_filename.ptr);
1141 pj_strcat2(&cfg, line);
1142 }
1143
1144
1145 /* Save account settings. */
1146 for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
1147
1148 write_account_settings(acc_index, &cfg);
1149
1150 if (acc_index < config->acc_cnt-1)
1151 pj_strcat2(&cfg, "--next-account\n");
1152 }
1153
1154
1155 pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
1156
1157 /* Outbound proxy */
1158 for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
1159 pj_ansi_sprintf(line, "--outbound %.*s\n",
1160 (int)config->cfg.outbound_proxy[i].slen,
1161 config->cfg.outbound_proxy[i].ptr);
1162 pj_strcat2(&cfg, line);
1163 }
1164
1165
1166 /* UDP Transport. */
1167 pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
1168 pj_strcat2(&cfg, line);
1169
Benny Prijono0a5cad82006-09-26 13:21:02 +00001170 /* IP address, if any. */
1171 if (config->udp_cfg.public_addr.slen) {
1172 pj_ansi_sprintf(line, "--ip-addr %.*s\n",
1173 (int)config->udp_cfg.public_addr.slen,
1174 config->udp_cfg.public_addr.ptr);
1175 pj_strcat2(&cfg, line);
1176 }
1177
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001178 /* No TCP ? */
1179 if (config->no_tcp) {
1180 pj_strcat2(&cfg, "--no-tcp\n");
1181 }
1182
1183 /* No UDP ? */
1184 if (config->no_udp) {
1185 pj_strcat2(&cfg, "--no-udp\n");
1186 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001187
1188 /* STUN */
Benny Prijonoebbf6892007-03-24 17:37:25 +00001189 if (config->cfg.stun_domain.slen) {
1190 pj_ansi_sprintf(line, "--stun-domain %.*s\n",
1191 (int)config->cfg.stun_domain.slen,
1192 config->cfg.stun_domain.ptr);
1193 pj_strcat2(&cfg, line);
1194 }
1195 if (config->cfg.stun_host.slen) {
Benny Prijonoc97608e2007-03-23 16:34:20 +00001196 pj_ansi_sprintf(line, "--stun-srv %.*s\n",
Benny Prijonoebbf6892007-03-24 17:37:25 +00001197 (int)config->cfg.stun_host.slen,
1198 config->cfg.stun_host.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001199 pj_strcat2(&cfg, line);
1200 }
1201
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001202
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001203 /* TLS */
1204 if (config->use_tls)
1205 pj_strcat2(&cfg, "--use-tls\n");
Benny Prijonof3bbc132006-12-25 06:43:59 +00001206 if (config->udp_cfg.tls_setting.ca_list_file.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001207 pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001208 (int)config->udp_cfg.tls_setting.ca_list_file.slen,
1209 config->udp_cfg.tls_setting.ca_list_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001210 pj_strcat2(&cfg, line);
1211 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001212 if (config->udp_cfg.tls_setting.cert_file.slen) {
1213 pj_ansi_sprintf(line, "--tls-cert-file %.*s\n",
1214 (int)config->udp_cfg.tls_setting.cert_file.slen,
1215 config->udp_cfg.tls_setting.cert_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001216 pj_strcat2(&cfg, line);
1217 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001218 if (config->udp_cfg.tls_setting.privkey_file.slen) {
1219 pj_ansi_sprintf(line, "--tls-privkey-file %.*s\n",
1220 (int)config->udp_cfg.tls_setting.privkey_file.slen,
1221 config->udp_cfg.tls_setting.privkey_file.ptr);
1222 pj_strcat2(&cfg, line);
1223 }
1224
1225 if (config->udp_cfg.tls_setting.password.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001226 pj_ansi_sprintf(line, "--tls-password %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001227 (int)config->udp_cfg.tls_setting.password.slen,
1228 config->udp_cfg.tls_setting.password.ptr);
1229 pj_strcat2(&cfg, line);
1230 }
1231
1232 if (config->udp_cfg.tls_setting.verify_server)
1233 pj_strcat2(&cfg, "--tls-verify-server\n");
1234
1235 if (config->udp_cfg.tls_setting.verify_client)
1236 pj_strcat2(&cfg, "--tls-verify-client\n");
1237
1238 if (config->udp_cfg.tls_setting.timeout.sec) {
1239 pj_ansi_sprintf(line, "--tls-neg-timeout %d\n",
Benny Prijonoe960bb52007-01-21 17:53:39 +00001240 (int)config->udp_cfg.tls_setting.timeout.sec);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001241 pj_strcat2(&cfg, line);
1242 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001243
1244 pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
1245
1246
1247 /* Media */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001248 if (config->media_cfg.enable_ice)
1249 pj_strcat2(&cfg, "--use-ice\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001250 if (config->null_audio)
1251 pj_strcat2(&cfg, "--null-audio\n");
1252 if (config->auto_play)
1253 pj_strcat2(&cfg, "--auto-play\n");
1254 if (config->auto_loop)
1255 pj_strcat2(&cfg, "--auto-loop\n");
Benny Prijono7ca96da2006-08-07 12:11:40 +00001256 if (config->auto_conf)
1257 pj_strcat2(&cfg, "--auto-conf\n");
Benny Prijono32e4f492007-01-24 00:44:26 +00001258 for (i=0; i<config->wav_count; ++i) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001259 pj_ansi_sprintf(line, "--play-file %s\n",
Benny Prijono32e4f492007-01-24 00:44:26 +00001260 config->wav_files[i].ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001261 pj_strcat2(&cfg, line);
1262 }
Benny Prijono4af234b2007-01-24 02:02:09 +00001263 for (i=0; i<config->tone_count; ++i) {
1264 pj_ansi_sprintf(line, "--play-tone %d,%d,%d,%d\n",
1265 config->tones[i].freq1, config->tones[i].freq2,
1266 config->tones[i].on_msec, config->tones[i].off_msec);
1267 pj_strcat2(&cfg, line);
1268 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001269 if (config->rec_file.slen) {
1270 pj_ansi_sprintf(line, "--rec-file %s\n",
1271 config->rec_file.ptr);
1272 pj_strcat2(&cfg, line);
1273 }
1274 if (config->auto_rec)
1275 pj_strcat2(&cfg, "--auto-rec\n");
Benny Prijono4e5d5512007-03-06 18:11:30 +00001276 if (config->capture_dev != PJSUA_INVALID_ID) {
1277 pj_ansi_sprintf(line, "--capture-dev %d\n", config->capture_dev);
1278 pj_strcat2(&cfg, line);
1279 }
1280 if (config->playback_dev != PJSUA_INVALID_ID) {
1281 pj_ansi_sprintf(line, "--playback-dev %d\n", config->playback_dev);
1282 pj_strcat2(&cfg, line);
1283 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001284
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001285 /* Media clock rate. */
Benny Prijono70972992006-08-05 11:13:58 +00001286 if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001287 pj_ansi_sprintf(line, "--clock-rate %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001288 config->media_cfg.clock_rate);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001289 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001290 } else {
1291 pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
1292 config->media_cfg.clock_rate);
1293 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001294 }
Benny Prijono70972992006-08-05 11:13:58 +00001295
1296 /* quality */
1297 if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001298 pj_ansi_sprintf(line, "--quality %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001299 config->media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001300 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001301 } else {
1302 pj_ansi_sprintf(line, "#using default --quality %d\n",
1303 config->media_cfg.quality);
1304 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001305 }
Benny Prijono0498d902006-06-19 14:49:14 +00001306
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001307
1308 /* ptime */
Benny Prijono2adfe292007-05-11 10:36:08 +00001309 if (config->media_cfg.ptime) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001310 pj_ansi_sprintf(line, "--ptime %d\n",
Benny Prijono2adfe292007-05-11 10:36:08 +00001311 config->media_cfg.ptime);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001312 pj_strcat2(&cfg, line);
1313 }
1314
Benny Prijono70972992006-08-05 11:13:58 +00001315 /* no-vad */
1316 if (config->media_cfg.no_vad) {
1317 pj_strcat2(&cfg, "--no-vad\n");
1318 }
1319
1320 /* ec-tail */
1321 if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
1322 pj_ansi_sprintf(line, "--ec-tail %d\n",
1323 config->media_cfg.ec_tail_len);
1324 pj_strcat2(&cfg, line);
1325 } else {
1326 pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
1327 config->media_cfg.ec_tail_len);
1328 pj_strcat2(&cfg, line);
1329 }
1330
1331
1332 /* ilbc-mode */
1333 if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
1334 pj_ansi_sprintf(line, "--ilbc-mode %d\n",
1335 config->media_cfg.ilbc_mode);
1336 pj_strcat2(&cfg, line);
1337 } else {
1338 pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
1339 config->media_cfg.ilbc_mode);
1340 pj_strcat2(&cfg, line);
1341 }
1342
1343 /* RTP drop */
1344 if (config->media_cfg.tx_drop_pct) {
1345 pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
1346 config->media_cfg.tx_drop_pct);
1347 pj_strcat2(&cfg, line);
1348
1349 }
1350 if (config->media_cfg.rx_drop_pct) {
1351 pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
1352 config->media_cfg.rx_drop_pct);
1353 pj_strcat2(&cfg, line);
1354
1355 }
1356
1357
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001358 /* Start RTP port. */
1359 pj_ansi_sprintf(line, "--rtp-port %d\n",
1360 config->rtp_cfg.port);
1361 pj_strcat2(&cfg, line);
1362
1363 /* Add codec. */
1364 for (i=0; i<config->codec_cnt; ++i) {
1365 pj_ansi_sprintf(line, "--add-codec %s\n",
1366 config->codec_arg[i].ptr);
1367 pj_strcat2(&cfg, line);
1368 }
1369
1370 pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
1371
1372 /* Auto-answer. */
1373 if (config->auto_answer != 0) {
1374 pj_ansi_sprintf(line, "--auto-answer %d\n",
1375 config->auto_answer);
1376 pj_strcat2(&cfg, line);
1377 }
1378
1379 /* Max calls. */
1380 pj_ansi_sprintf(line, "--max-calls %d\n",
1381 config->cfg.max_calls);
1382 pj_strcat2(&cfg, line);
1383
1384 /* Uas-duration. */
Benny Prijono804ff0a2006-09-14 11:17:48 +00001385 if (config->duration != NO_LIMIT) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001386 pj_ansi_sprintf(line, "--duration %d\n",
1387 config->duration);
1388 pj_strcat2(&cfg, line);
1389 }
1390
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001391 /* norefersub ? */
1392 if (config->no_refersub) {
1393 pj_strcat2(&cfg, "--norefersub\n");
1394 }
1395
1396
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001397 pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
1398
1399 /* Add buddies. */
1400 for (i=0; i<config->buddy_cnt; ++i) {
1401 pj_ansi_sprintf(line, "--add-buddy %.*s\n",
1402 (int)config->buddy_cfg[i].uri.slen,
1403 config->buddy_cfg[i].uri.ptr);
1404 pj_strcat2(&cfg, line);
1405 }
1406
1407
1408 *(cfg.ptr + cfg.slen) = '\0';
1409 return cfg.slen;
1410}
1411
1412
1413/*
1414 * Dump application states.
1415 */
1416static void app_dump(pj_bool_t detail)
1417{
Benny Prijonoda9785b2007-04-02 20:43:06 +00001418 pjsua_dump(detail);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001419}
1420
1421
1422/*****************************************************************************
1423 * Console application
1424 */
1425
1426/*
1427 * Find next call when current call is disconnected or when user
1428 * press ']'
1429 */
1430static pj_bool_t find_next_call(void)
1431{
1432 int i, max;
1433
1434 max = pjsua_call_get_max_count();
1435 for (i=current_call+1; i<max; ++i) {
1436 if (pjsua_call_is_active(i)) {
1437 current_call = i;
1438 return PJ_TRUE;
1439 }
1440 }
1441
1442 for (i=0; i<current_call; ++i) {
1443 if (pjsua_call_is_active(i)) {
1444 current_call = i;
1445 return PJ_TRUE;
1446 }
1447 }
1448
1449 current_call = PJSUA_INVALID_ID;
1450 return PJ_FALSE;
1451}
1452
1453
1454/*
1455 * Find previous call when user press '['
1456 */
1457static pj_bool_t find_prev_call(void)
1458{
1459 int i, max;
1460
1461 max = pjsua_call_get_max_count();
1462 for (i=current_call-1; i>=0; --i) {
1463 if (pjsua_call_is_active(i)) {
1464 current_call = i;
1465 return PJ_TRUE;
1466 }
1467 }
1468
1469 for (i=max-1; i>current_call; --i) {
1470 if (pjsua_call_is_active(i)) {
1471 current_call = i;
1472 return PJ_TRUE;
1473 }
1474 }
1475
1476 current_call = PJSUA_INVALID_ID;
1477 return PJ_FALSE;
1478}
1479
1480
Benny Prijono804ff0a2006-09-14 11:17:48 +00001481/* Callback from timer when the maximum call duration has been
1482 * exceeded.
1483 */
1484static void call_timeout_callback(pj_timer_heap_t *timer_heap,
1485 struct pj_timer_entry *entry)
1486{
1487 pjsua_call_id call_id = entry->id;
1488 pjsua_msg_data msg_data;
1489 pjsip_generic_string_hdr warn;
1490 pj_str_t hname = pj_str("Warning");
1491 pj_str_t hvalue = pj_str("399 pjsua \"Call duration exceeded\"");
1492
1493 PJ_UNUSED_ARG(timer_heap);
1494
Benny Prijono148c9dd2006-09-19 13:37:53 +00001495 if (call_id == PJSUA_INVALID_ID) {
1496 PJ_LOG(1,(THIS_FILE, "Invalid call ID in timer callback"));
1497 return;
1498 }
1499
Benny Prijono804ff0a2006-09-14 11:17:48 +00001500 /* Add warning header */
1501 pjsua_msg_data_init(&msg_data);
1502 pjsip_generic_string_hdr_init2(&warn, &hname, &hvalue);
1503 pj_list_push_back(&msg_data.hdr_list, &warn);
1504
1505 /* Call duration has been exceeded; disconnect the call */
1506 PJ_LOG(3,(THIS_FILE, "Duration (%d seconds) has been exceeded "
1507 "for call %d, disconnecting the call",
1508 app_config.duration, call_id));
1509 entry->id = PJSUA_INVALID_ID;
1510 pjsua_call_hangup(call_id, 200, NULL, &msg_data);
1511}
1512
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001513
1514/*
1515 * Handler when invite state has changed.
1516 */
1517static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
1518{
1519 pjsua_call_info call_info;
1520
1521 PJ_UNUSED_ARG(e);
1522
1523 pjsua_call_get_info(call_id, &call_info);
1524
1525 if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
1526
Benny Prijono804ff0a2006-09-14 11:17:48 +00001527 /* Cancel duration timer, if any */
1528 if (app_config.call_data[call_id].timer.id != PJSUA_INVALID_ID) {
1529 struct call_data *cd = &app_config.call_data[call_id];
1530 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1531
1532 cd->timer.id = PJSUA_INVALID_ID;
1533 pjsip_endpt_cancel_timer(endpt, &cd->timer);
1534 }
1535
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001536 PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",
1537 call_id,
1538 call_info.last_status,
1539 call_info.last_status_text.ptr));
1540
1541 if (call_id == current_call) {
1542 find_next_call();
1543 }
1544
Benny Prijono4be63b52006-11-25 14:50:25 +00001545 /* Dump media state upon disconnected */
1546 if (1) {
1547 char buf[1024];
1548 pjsua_call_dump(call_id, PJ_TRUE, buf,
1549 sizeof(buf), " ");
1550 PJ_LOG(5,(THIS_FILE,
1551 "Call %d disconnected, dumping media stats\n%s",
1552 call_id, buf));
1553 }
1554
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001555 } else {
1556
Benny Prijono804ff0a2006-09-14 11:17:48 +00001557 if (app_config.duration!=NO_LIMIT &&
1558 call_info.state == PJSIP_INV_STATE_CONFIRMED)
1559 {
1560 /* Schedule timer to hangup call after the specified duration */
1561 struct call_data *cd = &app_config.call_data[call_id];
1562 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1563 pj_time_val delay;
1564
1565 cd->timer.id = call_id;
1566 delay.sec = app_config.duration;
1567 delay.msec = 0;
1568 pjsip_endpt_schedule_timer(endpt, &cd->timer, &delay);
1569 }
1570
Benny Prijono4be63b52006-11-25 14:50:25 +00001571 if (call_info.state == PJSIP_INV_STATE_EARLY) {
1572 int code;
1573 pj_str_t reason;
1574 pjsip_msg *msg;
1575
1576 /* This can only occur because of TX or RX message */
1577 pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
1578
1579 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
1580 msg = e->body.tsx_state.src.rdata->msg_info.msg;
1581 } else {
1582 msg = e->body.tsx_state.src.tdata->msg;
1583 }
1584
1585 code = msg->line.status.code;
1586 reason = msg->line.status.reason;
1587
1588 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)",
1589 call_id, call_info.state_text.ptr,
1590 code, (int)reason.slen, reason.ptr));
1591 } else {
1592 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s",
1593 call_id,
1594 call_info.state_text.ptr));
1595 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001596
1597 if (current_call==PJSUA_INVALID_ID)
1598 current_call = call_id;
1599
1600 }
1601}
1602
1603
1604/**
1605 * Handler when there is incoming call.
1606 */
1607static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
1608 pjsip_rx_data *rdata)
1609{
1610 pjsua_call_info call_info;
1611
1612 PJ_UNUSED_ARG(acc_id);
1613 PJ_UNUSED_ARG(rdata);
1614
1615 pjsua_call_get_info(call_id, &call_info);
1616
1617 if (app_config.auto_answer > 0) {
1618 pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL);
1619 }
1620
1621 if (app_config.auto_answer < 200) {
1622 PJ_LOG(3,(THIS_FILE,
1623 "Incoming call for account %d!\n"
1624 "From: %s\n"
1625 "To: %s\n"
1626 "Press a to answer or h to reject call",
1627 acc_id,
1628 call_info.remote_info.ptr,
1629 call_info.local_info.ptr));
1630 }
1631}
1632
1633
1634/*
Benny Prijonofeb69f42007-10-05 09:12:26 +00001635 * Handler when a transaction within a call has changed state.
1636 */
1637static void on_call_tsx_state(pjsua_call_id call_id,
1638 pjsip_transaction *tsx,
1639 pjsip_event *e)
1640{
1641 const pjsip_method info_method =
1642 {
1643 PJSIP_OTHER_METHOD,
1644 { "INFO", 4 }
1645 };
1646
1647 if (pjsip_method_cmp(&tsx->method, &info_method)==0) {
1648 /*
1649 * Handle INFO method.
1650 */
1651 if (tsx->role == PJSIP_ROLE_UAC &&
1652 (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
Benny Prijonob071a782007-10-10 13:12:37 +00001653 (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
1654 e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED)))
Benny Prijonofeb69f42007-10-05 09:12:26 +00001655 {
1656 /* Status of outgoing INFO request */
1657 if (tsx->status_code >= 200 && tsx->status_code < 300) {
1658 PJ_LOG(4,(THIS_FILE,
1659 "Call %d: DTMF sent successfully with INFO",
1660 call_id));
1661 } else if (tsx->status_code >= 300) {
1662 PJ_LOG(4,(THIS_FILE,
1663 "Call %d: Failed to send DTMF with INFO: %d/%.*s",
1664 call_id,
1665 tsx->status_code,
1666 (int)tsx->status_text.slen,
1667 tsx->status_text.ptr));
1668 }
1669 } else if (tsx->role == PJSIP_ROLE_UAS &&
1670 tsx->state == PJSIP_TSX_STATE_TRYING)
1671 {
1672 /* Answer incoming INFO with 200/OK */
1673 pjsip_rx_data *rdata;
1674 pjsip_tx_data *tdata;
1675 pj_status_t status;
1676
1677 rdata = e->body.tsx_state.src.rdata;
1678
1679 if (rdata->msg_info.msg->body) {
1680 status = pjsip_endpt_create_response(tsx->endpt, rdata,
1681 200, NULL, &tdata);
1682 if (status == PJ_SUCCESS)
1683 status = pjsip_tsx_send_msg(tsx, tdata);
1684
1685 PJ_LOG(3,(THIS_FILE, "Call %d: incoming INFO:\n%.*s",
1686 call_id,
1687 (int)rdata->msg_info.msg->body->len,
1688 rdata->msg_info.msg->body->data));
1689 } else {
1690 status = pjsip_endpt_create_response(tsx->endpt, rdata,
1691 400, NULL, &tdata);
1692 if (status == PJ_SUCCESS)
1693 status = pjsip_tsx_send_msg(tsx, tdata);
1694 }
1695 }
1696 }
1697}
1698
1699
1700/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001701 * Callback on media state changed event.
1702 * The action may connect the call to sound device, to file, or
1703 * to loop the call.
1704 */
1705static void on_call_media_state(pjsua_call_id call_id)
1706{
1707 pjsua_call_info call_info;
1708
1709 pjsua_call_get_info(call_id, &call_info);
1710
1711 if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
1712 pj_bool_t connect_sound = PJ_TRUE;
1713
1714 /* Loopback sound, if desired */
1715 if (app_config.auto_loop) {
1716 pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
1717 connect_sound = PJ_FALSE;
Benny Prijono1ebd6142006-10-19 15:48:02 +00001718
1719 /* Automatically record conversation, if desired */
1720 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1721 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1722 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001723 }
1724
1725 /* Stream a file, if desired */
1726 if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) {
1727 pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
1728 connect_sound = PJ_FALSE;
1729 }
1730
Benny Prijono7ca96da2006-08-07 12:11:40 +00001731 /* Put call in conference with other calls, if desired */
1732 if (app_config.auto_conf) {
1733 pjsua_call_id call_ids[PJSUA_MAX_CALLS];
Benny Prijono10861bc2006-08-07 13:22:43 +00001734 unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
Benny Prijono7ca96da2006-08-07 12:11:40 +00001735 unsigned i;
1736
1737 /* Get all calls, and establish media connection between
1738 * this call and other calls.
1739 */
1740 pjsua_enum_calls(call_ids, &call_cnt);
Benny Prijono10861bc2006-08-07 13:22:43 +00001741
Benny Prijono7ca96da2006-08-07 12:11:40 +00001742 for (i=0; i<call_cnt; ++i) {
1743 if (call_ids[i] == call_id)
1744 continue;
1745
1746 if (!pjsua_call_has_media(call_ids[i]))
1747 continue;
1748
1749 pjsua_conf_connect(call_info.conf_slot,
1750 pjsua_call_get_conf_port(call_ids[i]));
1751 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1752 call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001753
1754 /* Automatically record conversation, if desired */
1755 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1756 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1757 app_config.rec_port);
1758 }
1759
Benny Prijono7ca96da2006-08-07 12:11:40 +00001760 }
1761
1762 /* Also connect call to local sound device */
1763 connect_sound = PJ_TRUE;
1764 }
1765
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001766 /* Otherwise connect to sound device */
1767 if (connect_sound) {
1768 pjsua_conf_connect(call_info.conf_slot, 0);
1769 pjsua_conf_connect(0, call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001770
1771 /* Automatically record conversation, if desired */
1772 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1773 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1774 pjsua_conf_connect(0, app_config.rec_port);
1775 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001776 }
1777
1778 PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
1779
1780 } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) {
1781 PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
1782 call_id));
1783 } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) {
1784 PJ_LOG(3,(THIS_FILE,
1785 "Media for call %d is suspended (hold) by remote",
1786 call_id));
Benny Prijono096c56c2007-09-15 08:30:16 +00001787 } else if (call_info.media_status == PJSUA_CALL_MEDIA_ERROR) {
1788 pj_str_t reason = pj_str("ICE negotiation failed");
1789
1790 PJ_LOG(1,(THIS_FILE,
1791 "Media has reported error, disconnecting call"));
1792
1793 pjsua_call_hangup(call_id, 500, &reason, NULL);
1794
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001795 } else {
1796 PJ_LOG(3,(THIS_FILE,
1797 "Media for call %d is inactive",
1798 call_id));
1799 }
1800}
1801
Benny Prijono0875ae82006-12-26 00:11:48 +00001802/*
1803 * DTMF callback.
1804 */
1805static void call_on_dtmf_callback(pjsua_call_id call_id, int dtmf)
1806{
1807 PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, dtmf));
1808}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001809
1810/*
1811 * Handler registration status has changed.
1812 */
1813static void on_reg_state(pjsua_acc_id acc_id)
1814{
1815 PJ_UNUSED_ARG(acc_id);
1816
1817 // Log already written.
1818}
1819
1820
1821/*
1822 * Handler on buddy state changed.
1823 */
1824static void on_buddy_state(pjsua_buddy_id buddy_id)
1825{
1826 pjsua_buddy_info info;
1827 pjsua_buddy_get_info(buddy_id, &info);
1828
1829 PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
1830 (int)info.uri.slen,
1831 info.uri.ptr,
1832 (int)info.status_text.slen,
1833 info.status_text.ptr));
1834}
1835
1836
1837/**
1838 * Incoming IM message (i.e. MESSAGE request)!
1839 */
1840static void on_pager(pjsua_call_id call_id, const pj_str_t *from,
1841 const pj_str_t *to, const pj_str_t *contact,
1842 const pj_str_t *mime_type, const pj_str_t *text)
1843{
1844 /* Note: call index may be -1 */
1845 PJ_UNUSED_ARG(call_id);
1846 PJ_UNUSED_ARG(to);
1847 PJ_UNUSED_ARG(contact);
1848 PJ_UNUSED_ARG(mime_type);
1849
Benny Prijonof4b538d2007-05-14 16:45:20 +00001850 PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s (%.*s)",
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001851 (int)from->slen, from->ptr,
Benny Prijonof4b538d2007-05-14 16:45:20 +00001852 (int)text->slen, text->ptr,
1853 (int)mime_type->slen, mime_type->ptr));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001854}
1855
1856
1857/**
1858 * Received typing indication
1859 */
1860static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
1861 const pj_str_t *to, const pj_str_t *contact,
1862 pj_bool_t is_typing)
1863{
1864 PJ_UNUSED_ARG(call_id);
1865 PJ_UNUSED_ARG(to);
1866 PJ_UNUSED_ARG(contact);
1867
1868 PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
1869 (int)from->slen, from->ptr,
1870 (is_typing?"is typing..":"has stopped typing")));
1871}
1872
1873
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001874/**
1875 * Call transfer request status.
1876 */
1877static void on_call_transfer_status(pjsua_call_id call_id,
1878 int status_code,
1879 const pj_str_t *status_text,
1880 pj_bool_t final,
1881 pj_bool_t *p_cont)
1882{
1883 PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s",
1884 call_id, status_code,
1885 (int)status_text->slen, status_text->ptr,
1886 (final ? "[final]" : "")));
1887
1888 if (status_code/100 == 2) {
1889 PJ_LOG(3,(THIS_FILE,
1890 "Call %d: call transfered successfully, disconnecting call",
1891 call_id));
1892 pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL);
1893 *p_cont = PJ_FALSE;
1894 }
1895}
1896
1897
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001898/*
Benny Prijonof7b1c392006-11-11 16:46:34 +00001899 * Notification that call is being replaced.
1900 */
1901static void on_call_replaced(pjsua_call_id old_call_id,
1902 pjsua_call_id new_call_id)
1903{
1904 pjsua_call_info old_ci, new_ci;
1905
1906 pjsua_call_get_info(old_call_id, &old_ci);
1907 pjsua_call_get_info(new_call_id, &new_ci);
1908
1909 PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by "
1910 "call %d with %.*s",
1911 old_call_id,
1912 (int)old_ci.remote_info.slen, old_ci.remote_info.ptr,
1913 new_call_id,
1914 (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
1915}
1916
1917
1918/*
Benny Prijono6ba8c542007-10-16 01:34:14 +00001919 * NAT type detection callback.
1920 */
1921static void on_nat_detect(const pj_stun_nat_detect_result *res)
1922{
1923 if (res->status != PJ_SUCCESS) {
1924 pjsua_perror(THIS_FILE, "NAT detection failed", res->status);
1925 } else {
1926 PJ_LOG(3, (THIS_FILE, "NAT detected as %s", res->nat_type_name));
1927 }
1928}
1929
1930
1931/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001932 * Print buddy list.
1933 */
1934static void print_buddy_list(void)
1935{
1936 pjsua_buddy_id ids[64];
1937 int i;
1938 unsigned count = PJ_ARRAY_SIZE(ids);
1939
1940 puts("Buddy list:");
1941
1942 pjsua_enum_buddies(ids, &count);
1943
1944 if (count == 0)
1945 puts(" -none-");
1946 else {
1947 for (i=0; i<(int)count; ++i) {
1948 pjsua_buddy_info info;
1949
1950 if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
1951 continue;
1952
Benny Prijono4461c7d2007-08-25 13:36:15 +00001953 printf(" [%2d] <%.*s> %.*s\n",
1954 ids[i]+1,
1955 (int)info.status_text.slen,
1956 info.status_text.ptr,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001957 (int)info.uri.slen,
1958 info.uri.ptr);
1959 }
1960 }
1961 puts("");
1962}
1963
1964
1965/*
1966 * Print account status.
1967 */
1968static void print_acc_status(int acc_id)
1969{
1970 char buf[80];
1971 pjsua_acc_info info;
1972
1973 pjsua_acc_get_info(acc_id, &info);
1974
1975 if (!info.has_registration) {
1976 pj_ansi_snprintf(buf, sizeof(buf), "%.*s",
1977 (int)info.status_text.slen,
1978 info.status_text.ptr);
1979
1980 } else {
1981 pj_ansi_snprintf(buf, sizeof(buf),
1982 "%d/%.*s (expires=%d)",
1983 info.status,
1984 (int)info.status_text.slen,
1985 info.status_text.ptr,
1986 info.expires);
1987
1988 }
1989
1990 printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
1991 acc_id, (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
Benny Prijono4461c7d2007-08-25 13:36:15 +00001992 printf(" Online status: %.*s\n",
1993 (int)info.online_status_text.slen,
1994 info.online_status_text.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001995}
1996
1997
1998/*
1999 * Show a bit of help.
2000 */
2001static void keystroke_help(void)
2002{
2003 pjsua_acc_id acc_ids[16];
2004 unsigned count = PJ_ARRAY_SIZE(acc_ids);
2005 int i;
2006
2007 printf(">>>>\n");
2008
2009 pjsua_enum_accs(acc_ids, &count);
2010
2011 printf("Account list:\n");
2012 for (i=0; i<(int)count; ++i)
2013 print_acc_status(acc_ids[i]);
2014
2015 print_buddy_list();
2016
2017 //puts("Commands:");
2018 puts("+=============================================================================+");
2019 puts("| Call Commands: | Buddy, IM & Presence: | Account: |");
2020 puts("| | | |");
2021 puts("| m Make new call | +b Add new buddy .| +a Add new accnt |");
2022 puts("| M Make multiple calls | -b Delete buddy | -a Delete accnt. |");
Benny Prijono4461c7d2007-08-25 13:36:15 +00002023 puts("| a Answer call | i Send IM | !a Modify accnt. |");
2024 puts("| h Hangup call (ha=all) | s Subscribe presence | rr (Re-)register |");
2025 puts("| H Hold call | u Unsubscribe presence | ru Unregister |");
2026 puts("| v re-inVite (release hold) | t ToGgle Online status | > Cycle next ac.|");
Benny Prijonoc08682e2007-10-04 06:17:58 +00002027 puts("| U send UPDATE | T Set online status | < Cycle prev ac.|");
2028 puts("| ],[ Select next/prev call +--------------------------+-------------------+");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002029 puts("| x Xfer call | Media Commands: | Status & Config: |");
Benny Prijonof7b1c392006-11-11 16:46:34 +00002030 puts("| X Xfer with Replaces | | |");
Benny Prijonoc08682e2007-10-04 06:17:58 +00002031 puts("| # Send RFC 2833 DTMF | cl List ports | d Dump status |");
2032 puts("| * Send DTMF with INFO | cc Connect port | dd Dump detailed |");
2033 puts("| dq Dump curr. call quality | cd Disconnect port | dc Dump config |");
2034 puts("| | V Adjust audio Volume | f Save config |");
2035 puts("| S Send arbitrary REQUEST | Cp Codec priorities | f Save config |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002036 puts("+------------------------------+--------------------------+-------------------+");
Benny Prijono438e65b2007-11-03 21:42:10 +00002037 puts("| q QUIT sleep N: console sleep for N ms n: detect NAT type |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002038 puts("+=============================================================================+");
Benny Prijono48af79c2006-07-22 12:49:17 +00002039
2040 i = pjsua_call_get_count();
2041 printf("You have %d active call%s\n", i, (i>1?"s":""));
Benny Prijonof7b1c392006-11-11 16:46:34 +00002042
2043 if (current_call != PJSUA_INVALID_ID) {
2044 pjsua_call_info ci;
2045 if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS)
2046 printf("Current call id=%d to %.*s [%.*s]\n", current_call,
2047 (int)ci.remote_info.slen, ci.remote_info.ptr,
2048 (int)ci.state_text.slen, ci.state_text.ptr);
2049 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002050}
2051
2052
2053/*
2054 * Input simple string
2055 */
2056static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
2057{
2058 char *p;
2059
2060 printf("%s (empty to cancel): ", title); fflush(stdout);
2061 fgets(buf, len, stdin);
2062
2063 /* Remove trailing newlines. */
2064 for (p=buf; ; ++p) {
2065 if (*p=='\r' || *p=='\n') *p='\0';
2066 else if (!*p) break;
2067 }
2068
2069 if (!*buf)
2070 return PJ_FALSE;
2071
2072 return PJ_TRUE;
2073}
2074
2075
2076#define NO_NB -2
2077struct input_result
2078{
2079 int nb_result;
2080 char *uri_result;
2081};
2082
2083
2084/*
2085 * Input URL.
2086 */
2087static void ui_input_url(const char *title, char *buf, int len,
2088 struct input_result *result)
2089{
2090 result->nb_result = NO_NB;
2091 result->uri_result = NULL;
2092
2093 print_buddy_list();
2094
2095 printf("Choices:\n"
2096 " 0 For current dialog.\n"
2097 " -1 All %d buddies in buddy list\n"
2098 " [1 -%2d] Select from buddy list\n"
2099 " URL An URL\n"
2100 " <Enter> Empty input (or 'q') to cancel\n"
2101 , pjsua_get_buddy_count(), pjsua_get_buddy_count());
2102 printf("%s: ", title);
2103
2104 fflush(stdout);
2105 fgets(buf, len, stdin);
2106 len = strlen(buf);
2107
2108 /* Left trim */
2109 while (pj_isspace(*buf)) {
2110 ++buf;
2111 --len;
2112 }
2113
2114 /* Remove trailing newlines */
2115 while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
2116 buf[--len] = '\0';
2117
2118 if (len == 0 || buf[0]=='q')
2119 return;
2120
2121 if (pj_isdigit(*buf) || *buf=='-') {
2122
2123 int i;
2124
2125 if (*buf=='-')
2126 i = 1;
2127 else
2128 i = 0;
2129
2130 for (; i<len; ++i) {
2131 if (!pj_isdigit(buf[i])) {
2132 puts("Invalid input");
2133 return;
2134 }
2135 }
2136
2137 result->nb_result = my_atoi(buf);
2138
2139 if (result->nb_result >= 0 &&
2140 result->nb_result <= (int)pjsua_get_buddy_count())
2141 {
2142 return;
2143 }
2144 if (result->nb_result == -1)
2145 return;
2146
2147 puts("Invalid input");
2148 result->nb_result = NO_NB;
2149 return;
2150
2151 } else {
2152 pj_status_t status;
2153
2154 if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
2155 pjsua_perror(THIS_FILE, "Invalid URL", status);
2156 return;
2157 }
2158
2159 result->uri_result = buf;
2160 }
2161}
2162
2163/*
2164 * List the ports in conference bridge
2165 */
2166static void conf_list(void)
2167{
2168 unsigned i, count;
2169 pjsua_conf_port_id id[PJSUA_MAX_CALLS];
2170
2171 printf("Conference ports:\n");
2172
2173 count = PJ_ARRAY_SIZE(id);
2174 pjsua_enum_conf_ports(id, &count);
2175
2176 for (i=0; i<count; ++i) {
2177 char txlist[PJSUA_MAX_CALLS*4+10];
2178 unsigned j;
2179 pjsua_conf_port_info info;
2180
2181 pjsua_conf_get_port_info(id[i], &info);
2182
2183 txlist[0] = '\0';
2184 for (j=0; j<info.listener_cnt; ++j) {
2185 char s[10];
2186 pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
2187 pj_ansi_strcat(txlist, s);
2188 }
2189 printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n",
2190 info.slot_id,
2191 info.clock_rate/1000,
2192 info.samples_per_frame * 1000 / info.clock_rate,
2193 (int)info.name.slen,
2194 info.name.ptr,
2195 txlist);
2196
2197 }
2198 puts("");
2199}
2200
2201
2202/*
Benny Prijono56315612006-07-18 14:39:40 +00002203 * Send arbitrary request to remote host
2204 */
2205static void send_request(char *cstr_method, const pj_str_t *dst_uri)
2206{
2207 pj_str_t str_method;
2208 pjsip_method method;
2209 pjsip_tx_data *tdata;
Benny Prijono56315612006-07-18 14:39:40 +00002210 pjsip_endpoint *endpt;
2211 pj_status_t status;
2212
2213 endpt = pjsua_get_pjsip_endpt();
2214
2215 str_method = pj_str(cstr_method);
2216 pjsip_method_init_np(&method, &str_method);
2217
Benny Prijonofff245c2007-04-02 11:44:47 +00002218 status = pjsua_acc_create_request(current_acc, &method, dst_uri, &tdata);
Benny Prijono56315612006-07-18 14:39:40 +00002219
Benny Prijonob988d762007-12-05 04:30:04 +00002220 status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
Benny Prijono56315612006-07-18 14:39:40 +00002221 if (status != PJ_SUCCESS) {
Benny Prijonob988d762007-12-05 04:30:04 +00002222 pjsua_perror(THIS_FILE, "Unable to send request", status);
Benny Prijono56315612006-07-18 14:39:40 +00002223 return;
2224 }
2225}
2226
2227
2228/*
Benny Prijono4461c7d2007-08-25 13:36:15 +00002229 * Change extended online status.
2230 */
2231static void change_online_status(void)
2232{
2233 char menuin[32];
2234 pj_bool_t online_status;
2235 pjrpid_element elem;
2236 int i, choice;
2237
2238 enum {
2239 AVAILABLE, BUSY, OTP, IDLE, AWAY, BRB, OFFLINE, OPT_MAX
2240 };
2241
2242 struct opt {
2243 int id;
2244 char *name;
2245 } opts[] = {
2246 { AVAILABLE, "Available" },
2247 { BUSY, "Busy"},
2248 { OTP, "On the phone"},
2249 { IDLE, "Idle"},
2250 { AWAY, "Away"},
2251 { BRB, "Be right back"},
2252 { OFFLINE, "Offline"}
2253 };
2254
2255 printf("\n"
2256 "Choices:\n");
2257 for (i=0; i<PJ_ARRAY_SIZE(opts); ++i) {
2258 printf(" %d %s\n", opts[i].id+1, opts[i].name);
2259 }
2260
2261 if (!simple_input("Select status", menuin, sizeof(menuin)))
2262 return;
2263
2264 choice = atoi(menuin) - 1;
2265 if (choice < 0 || choice >= OPT_MAX) {
2266 puts("Invalid selection");
2267 return;
2268 }
2269
2270 pj_bzero(&elem, sizeof(elem));
2271 elem.type = PJRPID_ELEMENT_TYPE_PERSON;
2272
2273 online_status = PJ_TRUE;
2274
2275 switch (choice) {
2276 case AVAILABLE:
2277 break;
2278 case BUSY:
2279 elem.activity = PJRPID_ACTIVITY_BUSY;
2280 elem.note = pj_str("Busy");
2281 break;
2282 case OTP:
2283 elem.activity = PJRPID_ACTIVITY_BUSY;
2284 elem.note = pj_str("On the phone");
2285 break;
2286 case IDLE:
2287 elem.activity = PJRPID_ACTIVITY_UNKNOWN;
2288 elem.note = pj_str("Idle");
2289 break;
2290 case AWAY:
2291 elem.activity = PJRPID_ACTIVITY_AWAY;
2292 elem.note = pj_str("Away");
2293 break;
2294 case BRB:
2295 elem.activity = PJRPID_ACTIVITY_UNKNOWN;
2296 elem.note = pj_str("Be right back");
2297 break;
2298 case OFFLINE:
2299 online_status = PJ_FALSE;
2300 break;
2301 }
2302
2303 pjsua_acc_set_online_status2(current_acc, online_status, &elem);
2304}
2305
2306
2307/*
Benny Prijonoc08682e2007-10-04 06:17:58 +00002308 * Change codec priorities.
2309 */
2310static void manage_codec_prio(void)
2311{
2312 pjsua_codec_info c[32];
2313 unsigned i, count = PJ_ARRAY_SIZE(c);
2314 char input[32];
2315 char *codec, *prio;
2316 pj_str_t id;
2317 int new_prio;
2318 pj_status_t status;
2319
2320 printf("List of codecs:\n");
2321
2322 pjsua_enum_codecs(c, &count);
2323 for (i=0; i<count; ++i) {
2324 printf(" %d\t%.*s\n", c[i].priority, (int)c[i].codec_id.slen,
2325 c[i].codec_id.ptr);
2326 }
2327
2328 puts("");
2329 puts("Enter codec name and its new priority (e.g. \"speex/16000 200\"), empty to cancel:");
2330
2331 printf("Codec name and priority: ");
2332 fgets(input, sizeof(input), stdin);
2333 if (input[0]=='\r' || input[0]=='\n') {
2334 puts("Done");
2335 return;
2336 }
2337
2338 codec = strtok(input, " \t\r\n");
2339 prio = strtok(NULL, " \r\n");
2340
2341 if (!codec || !prio) {
2342 puts("Invalid input");
2343 return;
2344 }
2345
2346 new_prio = atoi(prio);
2347 if (new_prio < 0)
2348 new_prio = 0;
2349 else if (new_prio > PJMEDIA_CODEC_PRIO_HIGHEST)
2350 new_prio = PJMEDIA_CODEC_PRIO_HIGHEST;
2351
2352 status = pjsua_codec_set_priority(pj_cstr(&id, codec),
2353 (pj_uint8_t)new_prio);
2354 if (status != PJ_SUCCESS)
2355 pjsua_perror(THIS_FILE, "Error setting codec priority", status);
2356}
2357
2358
2359/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002360 * Main "user interface" loop.
2361 */
2362void console_app_main(const pj_str_t *uri_to_call)
2363{
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002364 char menuin[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002365 char buf[128];
2366 char text[128];
2367 int i, count;
2368 char *uri;
2369 pj_str_t tmp;
2370 struct input_result result;
2371 pjsua_call_info call_info;
2372 pjsua_acc_info acc_info;
2373
2374
2375 /* If user specifies URI to call, then call the URI */
2376 if (uri_to_call->slen) {
2377 pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, NULL);
2378 }
2379
2380 keystroke_help();
2381
2382 for (;;) {
2383
2384 printf(">>> ");
2385 fflush(stdout);
2386
Benny Prijono990042e2007-01-21 19:36:00 +00002387 if (fgets(menuin, sizeof(menuin), stdin) == NULL) {
2388 /*
2389 * Be friendly to users who redirect commands into
2390 * program, when file ends, resume with kbd.
2391 * If exit is desired end script with q for quit
2392 */
2393 /* Reopen stdin/stdout/stderr to /dev/console */
2394#if defined(PJ_WIN32) && PJ_WIN32!=0
2395 if (freopen ("CONIN$", "r", stdin) == NULL) {
2396#else
2397 if (1) {
2398#endif
2399 puts("Cannot switch back to console from file redirection");
2400 menuin[0] = 'q';
2401 menuin[1] = '\0';
2402 } else {
2403 puts("Switched back to console from file redirection");
2404 continue;
2405 }
2406 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002407
2408 switch (menuin[0]) {
2409
2410 case 'm':
2411 /* Make call! : */
2412 printf("(You currently have %d calls)\n",
2413 pjsua_call_get_count());
2414
2415 uri = NULL;
2416 ui_input_url("Make call", buf, sizeof(buf), &result);
2417 if (result.nb_result != NO_NB) {
2418
2419 if (result.nb_result == -1 || result.nb_result == 0) {
2420 puts("You can't do that with make call!");
2421 continue;
2422 } else {
2423 pjsua_buddy_info binfo;
2424 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2425 uri = binfo.uri.ptr;
2426 }
2427
2428 } else if (result.uri_result) {
2429 uri = result.uri_result;
2430 }
2431
2432 tmp = pj_str(uri);
2433 pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL);
2434 break;
2435
2436 case 'M':
2437 /* Make multiple calls! : */
2438 printf("(You currently have %d calls)\n",
2439 pjsua_call_get_count());
2440
2441 if (!simple_input("Number of calls", menuin, sizeof(menuin)))
2442 continue;
2443
2444 count = my_atoi(menuin);
2445 if (count < 1)
2446 continue;
2447
2448 ui_input_url("Make call", buf, sizeof(buf), &result);
2449 if (result.nb_result != NO_NB) {
2450 pjsua_buddy_info binfo;
2451 if (result.nb_result == -1 || result.nb_result == 0) {
2452 puts("You can't do that with make call!");
2453 continue;
2454 }
2455 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2456 uri = binfo.uri.ptr;
2457 } else {
2458 uri = result.uri_result;
2459 }
2460
2461 for (i=0; i<my_atoi(menuin); ++i) {
2462 pj_status_t status;
2463
2464 tmp = pj_str(uri);
2465 status = pjsua_call_make_call(current_acc, &tmp, 0, NULL,
2466 NULL, NULL);
2467 if (status != PJ_SUCCESS)
2468 break;
2469 }
2470 break;
2471
Benny Prijono4ab9fbb2007-10-12 12:14:27 +00002472 case 'n':
Benny Prijono438e65b2007-11-03 21:42:10 +00002473 i = pjsua_detect_nat_type();
2474 if (i != PJ_SUCCESS)
2475 pjsua_perror(THIS_FILE, "Error", i);
Benny Prijono4ab9fbb2007-10-12 12:14:27 +00002476 break;
2477
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002478 case 'i':
2479 /* Send instant messaeg */
2480
2481 /* i is for call index to send message, if any */
2482 i = -1;
2483
2484 /* Make compiler happy. */
2485 uri = NULL;
2486
2487 /* Input destination. */
2488 ui_input_url("Send IM to", buf, sizeof(buf), &result);
2489 if (result.nb_result != NO_NB) {
2490
2491 if (result.nb_result == -1) {
2492 puts("You can't send broadcast IM like that!");
2493 continue;
2494
2495 } else if (result.nb_result == 0) {
2496
2497 i = current_call;
2498
2499 } else {
2500 pjsua_buddy_info binfo;
2501 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2502 uri = binfo.uri.ptr;
2503 }
2504
2505 } else if (result.uri_result) {
2506 uri = result.uri_result;
2507 }
2508
2509
2510 /* Send typing indication. */
2511 if (i != -1)
2512 pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
2513 else {
2514 pj_str_t tmp_uri = pj_str(uri);
2515 pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
2516 }
2517
2518 /* Input the IM . */
2519 if (!simple_input("Message", text, sizeof(text))) {
2520 /*
2521 * Cancelled.
2522 * Send typing notification too, saying we're not typing.
2523 */
2524 if (i != -1)
2525 pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
2526 else {
2527 pj_str_t tmp_uri = pj_str(uri);
2528 pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
2529 }
2530 continue;
2531 }
2532
2533 tmp = pj_str(text);
2534
2535 /* Send the IM */
2536 if (i != -1)
2537 pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
2538 else {
2539 pj_str_t tmp_uri = pj_str(uri);
2540 pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
2541 }
2542
2543 break;
2544
2545 case 'a':
2546
2547 if (current_call != -1) {
2548 pjsua_call_get_info(current_call, &call_info);
2549 } else {
2550 /* Make compiler happy */
2551 call_info.role = PJSIP_ROLE_UAC;
2552 call_info.state = PJSIP_INV_STATE_DISCONNECTED;
2553 }
2554
2555 if (current_call == -1 ||
2556 call_info.role != PJSIP_ROLE_UAS ||
2557 call_info.state >= PJSIP_INV_STATE_CONNECTING)
2558 {
2559 puts("No pending incoming call");
2560 fflush(stdout);
2561 continue;
2562
2563 } else {
Benny Prijono20d36722007-02-22 14:52:24 +00002564 int st_code;
2565 char contact[120];
2566 pj_str_t hname = { "Contact", 7 };
2567 pj_str_t hvalue;
2568 pjsip_generic_string_hdr hcontact;
2569 pjsua_msg_data msg_data;
2570
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002571 if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
2572 continue;
2573
Benny Prijono20d36722007-02-22 14:52:24 +00002574 st_code = my_atoi(buf);
2575 if (st_code < 100)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002576 continue;
2577
Benny Prijono20d36722007-02-22 14:52:24 +00002578 pjsua_msg_data_init(&msg_data);
2579
2580 if (st_code/100 == 3) {
2581 if (!simple_input("Enter URL to be put in Contact",
2582 contact, sizeof(contact)))
2583 continue;
2584 hvalue = pj_str(contact);
2585 pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
2586
2587 pj_list_push_back(&msg_data.hdr_list, &hcontact);
2588 }
2589
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002590 /*
2591 * Must check again!
2592 * Call may have been disconnected while we're waiting for
2593 * keyboard input.
2594 */
2595 if (current_call == -1) {
2596 puts("Call has been disconnected");
2597 fflush(stdout);
2598 continue;
2599 }
2600
Benny Prijono20d36722007-02-22 14:52:24 +00002601 pjsua_call_answer(current_call, st_code, NULL, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002602 }
2603
2604 break;
2605
2606
2607 case 'h':
2608
2609 if (current_call == -1) {
2610 puts("No current call");
2611 fflush(stdout);
2612 continue;
2613
2614 } else if (menuin[1] == 'a') {
2615
2616 /* Hangup all calls */
2617 pjsua_call_hangup_all();
2618
2619 } else {
2620
2621 /* Hangup current calls */
2622 pjsua_call_hangup(current_call, 0, NULL, NULL);
2623 }
2624 break;
2625
2626 case ']':
2627 case '[':
2628 /*
2629 * Cycle next/prev dialog.
2630 */
2631 if (menuin[0] == ']') {
2632 find_next_call();
2633
2634 } else {
2635 find_prev_call();
2636 }
2637
2638 if (current_call != -1) {
2639
2640 pjsua_call_get_info(current_call, &call_info);
2641 PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s",
2642 (int)call_info.remote_info.slen,
2643 call_info.remote_info.ptr));
2644
2645 } else {
2646 PJ_LOG(3,(THIS_FILE,"No current dialog"));
2647 }
2648 break;
2649
2650
2651 case '>':
2652 case '<':
2653 if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
2654 break;
2655
2656 i = my_atoi(buf);
2657 if (pjsua_acc_is_valid(i)) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00002658 pjsua_acc_set_default(i);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002659 PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
2660 } else {
2661 PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
2662 }
2663 break;
2664
2665
2666 case '+':
2667 if (menuin[1] == 'b') {
2668
2669 pjsua_buddy_config buddy_cfg;
2670 pjsua_buddy_id buddy_id;
2671 pj_status_t status;
2672
2673 if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
2674 break;
2675
2676 if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) {
2677 printf("Invalid SIP URI '%s'\n", buf);
2678 break;
2679 }
2680
Benny Prijonoac623b32006-07-03 15:19:31 +00002681 pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002682
2683 buddy_cfg.uri = pj_str(buf);
2684 buddy_cfg.subscribe = PJ_TRUE;
2685
2686 status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
2687 if (status == PJ_SUCCESS) {
2688 printf("New buddy '%s' added at index %d\n",
2689 buf, buddy_id+1);
2690 }
2691
2692 } else if (menuin[1] == 'a') {
2693
Benny Prijonofb2b3652007-06-28 07:15:03 +00002694 char id[80], registrar[80], realm[80], uname[80], passwd[30];
2695 pjsua_acc_config acc_cfg;
2696 pj_status_t status;
2697
2698 if (!simple_input("Your SIP URL:", id, sizeof(id)))
2699 break;
2700 if (!simple_input("URL of the registrar:", registrar, sizeof(registrar)))
2701 break;
2702 if (!simple_input("Auth Realm:", realm, sizeof(realm)))
2703 break;
2704 if (!simple_input("Auth Username:", uname, sizeof(uname)))
2705 break;
2706 if (!simple_input("Auth Password:", passwd, sizeof(passwd)))
2707 break;
2708
2709 pjsua_acc_config_default(&acc_cfg);
2710 acc_cfg.id = pj_str(id);
2711 acc_cfg.reg_uri = pj_str(registrar);
2712 acc_cfg.cred_count = 1;
Benny Prijono48ab2b72007-11-08 09:24:30 +00002713 acc_cfg.cred_info[0].scheme = pj_str("Digest");
Benny Prijonofb2b3652007-06-28 07:15:03 +00002714 acc_cfg.cred_info[0].realm = pj_str(realm);
2715 acc_cfg.cred_info[0].username = pj_str(uname);
2716 acc_cfg.cred_info[0].data_type = 0;
2717 acc_cfg.cred_info[0].data = pj_str(passwd);
2718
2719 status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
2720 if (status != PJ_SUCCESS) {
2721 pjsua_perror(THIS_FILE, "Error adding new account", status);
2722 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002723
2724 } else {
2725 printf("Invalid input %s\n", menuin);
2726 }
2727 break;
2728
2729 case '-':
2730 if (menuin[1] == 'b') {
2731 if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
2732 break;
2733
2734 i = my_atoi(buf) - 1;
2735
2736 if (!pjsua_buddy_is_valid(i)) {
2737 printf("Invalid buddy id %d\n", i);
2738 } else {
2739 pjsua_buddy_del(i);
2740 printf("Buddy %d deleted\n", i);
2741 }
2742
2743 } else if (menuin[1] == 'a') {
2744
2745 if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
2746 break;
2747
2748 i = my_atoi(buf);
2749
2750 if (!pjsua_acc_is_valid(i)) {
2751 printf("Invalid account id %d\n", i);
2752 } else {
2753 pjsua_acc_del(i);
2754 printf("Account %d deleted\n", i);
2755 }
2756
2757 } else {
2758 printf("Invalid input %s\n", menuin);
2759 }
2760 break;
2761
2762 case 'H':
2763 /*
2764 * Hold call.
2765 */
2766 if (current_call != -1) {
2767
2768 pjsua_call_set_hold(current_call, NULL);
2769
2770 } else {
2771 PJ_LOG(3,(THIS_FILE, "No current call"));
2772 }
2773 break;
2774
2775 case 'v':
2776 /*
2777 * Send re-INVITE (to release hold, etc).
2778 */
2779 if (current_call != -1) {
2780
2781 pjsua_call_reinvite(current_call, PJ_TRUE, NULL);
2782
2783 } else {
2784 PJ_LOG(3,(THIS_FILE, "No current call"));
2785 }
2786 break;
2787
Benny Prijonoc08682e2007-10-04 06:17:58 +00002788 case 'U':
2789 /*
2790 * Send UPDATE
2791 */
2792 if (current_call != -1) {
2793
2794 pjsua_call_update(current_call, 0, NULL);
2795
2796 } else {
2797 PJ_LOG(3,(THIS_FILE, "No current call"));
2798 }
2799 break;
2800
2801 case 'C':
2802 if (menuin[1] == 'p') {
2803 manage_codec_prio();
2804 }
2805 break;
2806
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002807 case 'x':
2808 /*
2809 * Transfer call.
2810 */
2811 if (current_call == -1) {
2812
2813 PJ_LOG(3,(THIS_FILE, "No current call"));
2814
2815 } else {
2816 int call = current_call;
Benny Prijonod524e822006-09-22 12:48:18 +00002817 pjsua_msg_data msg_data;
2818 pjsip_generic_string_hdr refer_sub;
2819 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2820 pj_str_t STR_FALSE = { "false", 5 };
Benny Prijonof7b1c392006-11-11 16:46:34 +00002821 pjsua_call_info ci;
2822
2823 pjsua_call_get_info(current_call, &ci);
2824 printf("Transfering current call [%d] %.*s\n",
2825 current_call,
2826 (int)ci.remote_info.slen, ci.remote_info.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002827
2828 ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
2829
2830 /* Check if call is still there. */
2831
2832 if (call != current_call) {
2833 puts("Call has been disconnected");
2834 continue;
2835 }
2836
Benny Prijonod524e822006-09-22 12:48:18 +00002837 pjsua_msg_data_init(&msg_data);
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002838 if (app_config.no_refersub) {
2839 /* Add Refer-Sub: false in outgoing REFER request */
2840 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2841 &STR_FALSE);
2842 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2843 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002844 if (result.nb_result != NO_NB) {
2845 if (result.nb_result == -1 || result.nb_result == 0)
2846 puts("You can't do that with transfer call!");
2847 else {
2848 pjsua_buddy_info binfo;
2849 pjsua_buddy_get_info(result.nb_result-1, &binfo);
Benny Prijonod524e822006-09-22 12:48:18 +00002850 pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002851 }
2852
2853 } else if (result.uri_result) {
2854 pj_str_t tmp;
2855 tmp = pj_str(result.uri_result);
Benny Prijonod524e822006-09-22 12:48:18 +00002856 pjsua_call_xfer( current_call, &tmp, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002857 }
2858 }
2859 break;
2860
Benny Prijonof7b1c392006-11-11 16:46:34 +00002861 case 'X':
2862 /*
2863 * Transfer call with replaces.
2864 */
2865 if (current_call == -1) {
2866
2867 PJ_LOG(3,(THIS_FILE, "No current call"));
2868
2869 } else {
2870 int call = current_call;
2871 int dst_call;
2872 pjsua_msg_data msg_data;
2873 pjsip_generic_string_hdr refer_sub;
2874 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2875 pj_str_t STR_FALSE = { "false", 5 };
2876 pjsua_call_id ids[PJSUA_MAX_CALLS];
2877 pjsua_call_info ci;
2878 unsigned i, count;
2879
2880 count = PJ_ARRAY_SIZE(ids);
2881 pjsua_enum_calls(ids, &count);
2882
2883 if (count <= 1) {
2884 puts("There are no other calls");
2885 continue;
2886 }
2887
2888 pjsua_call_get_info(current_call, &ci);
2889 printf("Transfer call [%d] %.*s to one of the following:\n",
2890 current_call,
2891 (int)ci.remote_info.slen, ci.remote_info.ptr);
2892
2893 for (i=0; i<count; ++i) {
2894 pjsua_call_info call_info;
2895
2896 if (ids[i] == call)
2897 continue;
2898
2899 pjsua_call_get_info(ids[i], &call_info);
2900 printf("%d %.*s [%.*s]\n",
2901 ids[i],
2902 (int)call_info.remote_info.slen,
2903 call_info.remote_info.ptr,
2904 (int)call_info.state_text.slen,
2905 call_info.state_text.ptr);
2906 }
2907
2908 if (!simple_input("Enter call number to be replaced",
2909 buf, sizeof(buf)))
2910 continue;
2911
2912 dst_call = my_atoi(buf);
2913
2914 /* Check if call is still there. */
2915
2916 if (call != current_call) {
2917 puts("Call has been disconnected");
2918 continue;
2919 }
2920
2921 /* Check that destination call is valid. */
2922 if (dst_call == call) {
2923 puts("Destination call number must not be the same "
2924 "as the call being transfered");
2925 continue;
2926 }
2927 if (dst_call >= PJSUA_MAX_CALLS) {
2928 puts("Invalid destination call number");
2929 continue;
2930 }
2931 if (!pjsua_call_is_active(dst_call)) {
2932 puts("Invalid destination call number");
2933 continue;
2934 }
2935
2936 pjsua_msg_data_init(&msg_data);
2937 if (app_config.no_refersub) {
2938 /* Add Refer-Sub: false in outgoing REFER request */
2939 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2940 &STR_FALSE);
2941 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2942 }
2943
2944 pjsua_call_xfer_replaces(call, dst_call, 0, &msg_data);
2945 }
2946 break;
2947
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002948 case '#':
2949 /*
2950 * Send DTMF strings.
2951 */
2952 if (current_call == -1) {
2953
2954 PJ_LOG(3,(THIS_FILE, "No current call"));
2955
2956 } else if (!pjsua_call_has_media(current_call)) {
2957
2958 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
2959
2960 } else {
2961 pj_str_t digits;
2962 int call = current_call;
2963 pj_status_t status;
2964
2965 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
2966 sizeof(buf)))
2967 {
2968 break;
2969 }
2970
2971 if (call != current_call) {
2972 puts("Call has been disconnected");
2973 continue;
2974 }
2975
2976 digits = pj_str(buf);
2977 status = pjsua_call_dial_dtmf(current_call, &digits);
2978 if (status != PJ_SUCCESS) {
2979 pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
2980 } else {
2981 puts("DTMF digits enqueued for transmission");
2982 }
2983 }
2984 break;
2985
Benny Prijonofeb69f42007-10-05 09:12:26 +00002986 case '*':
2987 /* Send DTMF with INFO */
2988 if (current_call == -1) {
2989
2990 PJ_LOG(3,(THIS_FILE, "No current call"));
2991
2992 } else {
2993 const pj_str_t SIP_INFO = pj_str("INFO");
2994 pj_str_t digits;
2995 int call = current_call;
2996 int i;
2997 pj_status_t status;
2998
2999 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
3000 sizeof(buf)))
3001 {
3002 break;
3003 }
3004
3005 if (call != current_call) {
3006 puts("Call has been disconnected");
3007 continue;
3008 }
3009
3010 digits = pj_str(buf);
3011 for (i=0; i<digits.slen; ++i) {
3012 pjsua_msg_data msg_data;
3013 char body[80];
3014
3015 pjsua_msg_data_init(&msg_data);
3016 msg_data.content_type = pj_str("application/dtmf-relay");
3017
3018 pj_ansi_snprintf(body, sizeof(body),
3019 "Signal=%c\r\n"
3020 "Duration=160",
3021 buf[i]);
3022 msg_data.msg_body = pj_str(body);
3023
3024 status = pjsua_call_send_request(current_call, &SIP_INFO,
3025 &msg_data);
3026 if (status != PJ_SUCCESS) {
3027 break;
3028 }
3029 }
3030 }
3031 break;
3032
Benny Prijono56315612006-07-18 14:39:40 +00003033 case 'S':
3034 /*
3035 * Send arbitrary request
3036 */
3037 if (pjsua_acc_get_count() == 0) {
3038 puts("Sorry, need at least one account configured");
3039 break;
3040 }
3041
3042 puts("Send arbitrary request to remote host");
3043
3044 /* Input METHOD */
3045 if (!simple_input("Request method:",text,sizeof(text)))
3046 break;
3047
3048 /* Input destination URI */
3049 uri = NULL;
3050 ui_input_url("Destination URI", buf, sizeof(buf), &result);
3051 if (result.nb_result != NO_NB) {
3052
3053 if (result.nb_result == -1 || result.nb_result == 0) {
3054 puts("Sorry you can't do that!");
3055 continue;
3056 } else {
3057 pjsua_buddy_info binfo;
3058 pjsua_buddy_get_info(result.nb_result-1, &binfo);
3059 uri = binfo.uri.ptr;
3060 }
3061
3062 } else if (result.uri_result) {
3063 uri = result.uri_result;
3064 }
3065
3066 tmp = pj_str(uri);
3067
3068 send_request(text, &tmp);
3069 break;
3070
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003071 case 's':
Benny Prijono990042e2007-01-21 19:36:00 +00003072 if (pj_ansi_strnicmp(menuin, "sleep", 5)==0) {
3073 pj_str_t tmp;
3074 int delay;
3075
3076 tmp.ptr = menuin+6;
3077 tmp.slen = pj_ansi_strlen(menuin)-7;
3078
3079 if (tmp.slen < 1) {
3080 puts("Usage: sleep MSEC");
3081 break;
3082 }
3083
3084 delay = pj_strtoul(&tmp);
3085 if (delay < 0) delay = 0;
3086 pj_thread_sleep(delay);
3087 break;
3088 }
3089 /* Continue below */
3090
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003091 case 'u':
3092 /*
3093 * Subscribe/unsubscribe presence.
3094 */
3095 ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
3096 if (result.nb_result != NO_NB) {
3097 if (result.nb_result == -1) {
3098 int i, count;
3099 count = pjsua_get_buddy_count();
3100 for (i=0; i<count; ++i)
3101 pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
3102 } else if (result.nb_result == 0) {
3103 puts("Sorry, can only subscribe to buddy's presence, "
3104 "not from existing call");
3105 } else {
3106 pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
3107 }
3108
3109 } else if (result.uri_result) {
3110 puts("Sorry, can only subscribe to buddy's presence, "
3111 "not arbitrary URL (for now)");
3112 }
3113
3114 break;
3115
3116 case 'r':
3117 switch (menuin[1]) {
3118 case 'r':
3119 /*
3120 * Re-Register.
3121 */
3122 pjsua_acc_set_registration(current_acc, PJ_TRUE);
3123 break;
3124 case 'u':
3125 /*
3126 * Unregister
3127 */
3128 pjsua_acc_set_registration(current_acc, PJ_FALSE);
3129 break;
3130 }
3131 break;
3132
3133 case 't':
3134 pjsua_acc_get_info(current_acc, &acc_info);
3135 acc_info.online_status = !acc_info.online_status;
3136 pjsua_acc_set_online_status(current_acc, acc_info.online_status);
3137 printf("Setting %s online status to %s\n",
3138 acc_info.acc_uri.ptr,
3139 (acc_info.online_status?"online":"offline"));
3140 break;
3141
Benny Prijono4461c7d2007-08-25 13:36:15 +00003142 case 'T':
3143 change_online_status();
3144 break;
3145
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003146 case 'c':
3147 switch (menuin[1]) {
3148 case 'l':
3149 conf_list();
3150 break;
3151 case 'c':
3152 case 'd':
3153 {
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003154 char tmp[10], src_port[10], dst_port[10];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003155 pj_status_t status;
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003156 int cnt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003157 const char *src_title, *dst_title;
3158
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003159 cnt = sscanf(menuin, "%s %s %s", tmp, src_port, dst_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003160
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003161 if (cnt != 3) {
3162 conf_list();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003163
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003164 src_title = (menuin[1]=='c'?
3165 "Connect src port #":
3166 "Disconnect src port #");
3167 dst_title = (menuin[1]=='c'?
3168 "To dst port #":
3169 "From dst port #");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003170
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003171 if (!simple_input(src_title, src_port, sizeof(src_port)))
3172 break;
3173
3174 if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
3175 break;
3176 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003177
3178 if (menuin[1]=='c') {
3179 status = pjsua_conf_connect(my_atoi(src_port),
3180 my_atoi(dst_port));
3181 } else {
3182 status = pjsua_conf_disconnect(my_atoi(src_port),
3183 my_atoi(dst_port));
3184 }
3185 if (status == PJ_SUCCESS) {
3186 puts("Success");
3187 } else {
3188 puts("ERROR!!");
3189 }
3190 }
3191 break;
3192 }
3193 break;
3194
Benny Prijono6dd967c2006-12-26 02:27:14 +00003195 case 'V':
3196 /* Adjust audio volume */
3197 sprintf(buf, "Adjust mic level: [%4.1fx] ", app_config.mic_level);
3198 if (simple_input(buf,text,sizeof(text))) {
3199 char *err;
3200 app_config.mic_level = (float)strtod(text, &err);
3201 pjsua_conf_adjust_rx_level(0, app_config.mic_level);
3202 }
3203 sprintf(buf, "Adjust speaker level: [%4.1fx] ",
3204 app_config.speaker_level);
3205 if (simple_input(buf,text,sizeof(text))) {
3206 char *err;
3207 app_config.speaker_level = (float)strtod(text, &err);
3208 pjsua_conf_adjust_tx_level(0, app_config.speaker_level);
3209 }
3210
3211 break;
3212
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003213 case 'd':
3214 if (menuin[1] == 'c') {
3215 char settings[2000];
3216 int len;
3217
3218 len = write_settings(&app_config, settings, sizeof(settings));
3219 if (len < 1)
3220 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
3221 else
3222 PJ_LOG(3,(THIS_FILE,
3223 "Dumping configuration (%d bytes):\n%s\n",
3224 len, settings));
Benny Prijono819506c2006-06-22 22:29:51 +00003225
3226 } else if (menuin[1] == 'q') {
3227
3228 if (current_call != PJSUA_INVALID_ID) {
3229 char buf[1024];
3230 pjsua_call_dump(current_call, PJ_TRUE, buf,
3231 sizeof(buf), " ");
3232 PJ_LOG(3,(THIS_FILE, "\n%s", buf));
3233 } else {
3234 PJ_LOG(3,(THIS_FILE, "No current call"));
3235 }
3236
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003237 } else {
3238 app_dump(menuin[1]=='d');
3239 }
3240 break;
3241
3242
3243 case 'f':
3244 if (simple_input("Enter output filename", buf, sizeof(buf))) {
3245 char settings[2000];
3246 int len;
3247
3248 len = write_settings(&app_config, settings, sizeof(settings));
3249 if (len < 1)
3250 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
3251 else {
3252 pj_oshandle_t fd;
3253 pj_status_t status;
3254
3255 status = pj_file_open(app_config.pool, buf,
3256 PJ_O_WRONLY, &fd);
3257 if (status != PJ_SUCCESS) {
3258 pjsua_perror(THIS_FILE, "Unable to open file", status);
3259 } else {
3260 pj_ssize_t size = len;
3261 pj_file_write(fd, settings, &size);
3262 pj_file_close(fd);
3263
3264 printf("Settings successfully written to '%s'\n", buf);
3265 }
3266 }
3267
3268 }
3269 break;
3270
3271
3272 case 'q':
3273 goto on_exit;
3274
3275
3276 default:
3277 if (menuin[0] != '\n' && menuin[0] != '\r') {
3278 printf("Invalid input %s", menuin);
3279 }
3280 keystroke_help();
3281 break;
3282 }
3283 }
3284
3285on_exit:
3286 ;
3287}
3288
3289
3290/*****************************************************************************
3291 * Public API
3292 */
3293
3294pj_status_t app_init(int argc, char *argv[])
3295{
Benny Prijonoe93e2872006-06-28 16:46:49 +00003296 pjsua_transport_id transport_id = -1;
Benny Prijonoe347cb02007-02-14 14:36:13 +00003297 pjsua_transport_config tcp_cfg;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003298 unsigned i;
3299 pj_status_t status;
3300
3301 /* Create pjsua */
3302 status = pjsua_create();
3303 if (status != PJ_SUCCESS)
3304 return status;
3305
3306 /* Create pool for application */
Benny Prijonod5696da2007-07-17 16:25:45 +00003307 app_config.pool = pjsua_pool_create("pjsua", 1000, 1000);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003308
3309 /* Initialize default config */
3310 default_config(&app_config);
3311
3312 /* Parse the arguments */
3313 status = parse_args(argc, argv, &app_config, &uri_arg);
3314 if (status != PJ_SUCCESS)
3315 return status;
3316
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003317 /* Initialize application callbacks */
3318 app_config.cfg.cb.on_call_state = &on_call_state;
3319 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
3320 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
Benny Prijonofeb69f42007-10-05 09:12:26 +00003321 app_config.cfg.cb.on_call_tsx_state = &on_call_tsx_state;
Benny Prijono0875ae82006-12-26 00:11:48 +00003322 app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003323 app_config.cfg.cb.on_reg_state = &on_reg_state;
3324 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
3325 app_config.cfg.cb.on_pager = &on_pager;
3326 app_config.cfg.cb.on_typing = &on_typing;
Benny Prijono4ddad2c2006-10-18 17:16:34 +00003327 app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
Benny Prijonof7b1c392006-11-11 16:46:34 +00003328 app_config.cfg.cb.on_call_replaced = &on_call_replaced;
Benny Prijono6ba8c542007-10-16 01:34:14 +00003329 app_config.cfg.cb.on_nat_detect = &on_nat_detect;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003330
3331 /* Initialize pjsua */
3332 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
3333 &app_config.media_cfg);
3334 if (status != PJ_SUCCESS)
3335 return status;
3336
Benny Prijonoe909eac2006-07-27 22:04:56 +00003337#ifdef STEREO_DEMO
3338 stereo_demo();
3339#endif
3340
Benny Prijono804ff0a2006-09-14 11:17:48 +00003341 /* Initialize calls data */
3342 for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
3343 app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
3344 app_config.call_data[i].timer.cb = &call_timeout_callback;
3345 }
3346
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003347 /* Optionally registers WAV file */
Benny Prijono32e4f492007-01-24 00:44:26 +00003348 for (i=0; i<app_config.wav_count; ++i) {
3349 pjsua_player_id wav_id;
3350
3351 status = pjsua_player_create(&app_config.wav_files[i], 0,
3352 &wav_id);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003353 if (status != PJ_SUCCESS)
3354 goto on_error;
Benny Prijono22a300a2006-06-14 20:04:55 +00003355
Benny Prijono72c04d22007-02-17 22:20:58 +00003356 if (app_config.wav_id == PJSUA_INVALID_ID) {
Benny Prijono32e4f492007-01-24 00:44:26 +00003357 app_config.wav_id = wav_id;
3358 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
3359 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003360 }
3361
Benny Prijono4af234b2007-01-24 02:02:09 +00003362 /* Optionally registers tone players */
3363 for (i=0; i<app_config.tone_count; ++i) {
3364 pjmedia_port *tport;
3365 char name[80];
3366 pj_str_t label;
3367 pj_status_t status;
3368
3369 pj_ansi_snprintf(name, sizeof(name), "tone-%d,%d",
3370 app_config.tones[i].freq1,
3371 app_config.tones[i].freq2);
3372 label = pj_str(name);
3373 status = pjmedia_tonegen_create2(app_config.pool, &label,
3374 8000, 1, 160, 16,
3375 PJMEDIA_TONEGEN_LOOP, &tport);
3376 if (status != PJ_SUCCESS) {
3377 pjsua_perror(THIS_FILE, "Unable to create tone generator", status);
3378 goto on_error;
3379 }
3380
3381 status = pjsua_conf_add_port(app_config.pool, tport,
3382 &app_config.tone_slots[i]);
3383 pj_assert(status == PJ_SUCCESS);
3384
3385 status = pjmedia_tonegen_play(tport, 1, &app_config.tones[i], 0);
3386 pj_assert(status == PJ_SUCCESS);
3387 }
3388
Benny Prijono1ebd6142006-10-19 15:48:02 +00003389 /* Optionally create recorder file, if any. */
3390 if (app_config.rec_file.slen) {
3391 status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
3392 &app_config.rec_id);
3393 if (status != PJ_SUCCESS)
3394 goto on_error;
3395
3396 app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
3397 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003398
Benny Prijonoe347cb02007-02-14 14:36:13 +00003399 pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
3400
Benny Prijono87ef89a2007-01-14 00:39:45 +00003401 /* Add UDP transport unless it's disabled. */
3402 if (!app_config.no_udp) {
3403 pjsua_acc_id aid;
3404
3405 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
3406 &app_config.udp_cfg,
3407 &transport_id);
3408 if (status != PJ_SUCCESS)
3409 goto on_error;
3410
3411 /* Add local account */
3412 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
3413 //pjsua_acc_set_transport(aid, transport_id);
3414 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
Benny Prijonoe347cb02007-02-14 14:36:13 +00003415
3416 if (app_config.udp_cfg.port == 0) {
3417 pjsua_transport_info ti;
3418 pj_sockaddr_in *a;
3419
3420 pjsua_transport_get_info(transport_id, &ti);
3421 a = (pj_sockaddr_in*)&ti.local_addr;
3422
3423 tcp_cfg.port = pj_ntohs(a->sin_port);
3424 }
Benny Prijono87ef89a2007-01-14 00:39:45 +00003425 }
3426
Benny Prijonoe93e2872006-06-28 16:46:49 +00003427 /* Add TCP transport unless it's disabled */
3428 if (!app_config.no_tcp) {
3429 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
Benny Prijonoe347cb02007-02-14 14:36:13 +00003430 &tcp_cfg,
Benny Prijonoe93e2872006-06-28 16:46:49 +00003431 &transport_id);
3432 if (status != PJ_SUCCESS)
3433 goto on_error;
3434
3435 /* Add local account */
Benny Prijono21b9ad92006-08-15 13:11:22 +00003436 pjsua_acc_add_local(transport_id, PJ_TRUE, NULL);
Benny Prijonoe93e2872006-06-28 16:46:49 +00003437 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
3438
3439 }
3440
Benny Prijonoe93e2872006-06-28 16:46:49 +00003441
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003442#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
3443 /* Add TLS transport when application wants one */
3444 if (app_config.use_tls) {
Benny Prijonof3bbc132006-12-25 06:43:59 +00003445
3446 pjsua_acc_id acc_id;
3447
3448 /* Set TLS port as TCP port+1 */
Benny Prijonoafc47be2007-02-14 14:44:55 +00003449 tcp_cfg.port++;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003450 status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
Benny Prijonoafc47be2007-02-14 14:44:55 +00003451 &tcp_cfg,
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003452 &transport_id);
Benny Prijonoafc47be2007-02-14 14:44:55 +00003453 tcp_cfg.port--;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003454 if (status != PJ_SUCCESS)
3455 goto on_error;
Benny Prijonof3bbc132006-12-25 06:43:59 +00003456
3457 /* Add local account */
3458 pjsua_acc_add_local(transport_id, PJ_FALSE, &acc_id);
3459 pjsua_acc_set_online_status(acc_id, PJ_TRUE);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003460 }
3461#endif
3462
Benny Prijonoe93e2872006-06-28 16:46:49 +00003463 if (transport_id == -1) {
3464 PJ_LOG(3,(THIS_FILE, "Error: no transport is configured"));
3465 status = -1;
3466 goto on_error;
3467 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003468
3469
3470 /* Add accounts */
3471 for (i=0; i<app_config.acc_cnt; ++i) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00003472 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003473 if (status != PJ_SUCCESS)
3474 goto on_error;
3475 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
3476 }
3477
3478 /* Add buddies */
3479 for (i=0; i<app_config.buddy_cnt; ++i) {
3480 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
3481 if (status != PJ_SUCCESS)
3482 goto on_error;
3483 }
3484
3485 /* Optionally set codec orders */
3486 for (i=0; i<app_config.codec_cnt; ++i) {
3487 pjsua_codec_set_priority(&app_config.codec_arg[i],
3488 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
3489 }
3490
3491 /* Add RTP transports */
3492 status = pjsua_media_transports_create(&app_config.rtp_cfg);
3493 if (status != PJ_SUCCESS)
3494 goto on_error;
3495
Benny Prijono22a300a2006-06-14 20:04:55 +00003496 /* Use null sound device? */
Benny Prijonoe909eac2006-07-27 22:04:56 +00003497#ifndef STEREO_DEMO
Benny Prijono22a300a2006-06-14 20:04:55 +00003498 if (app_config.null_audio) {
3499 status = pjsua_set_null_snd_dev();
3500 if (status != PJ_SUCCESS)
3501 return status;
3502 }
Benny Prijonoe909eac2006-07-27 22:04:56 +00003503#endif
Benny Prijono22a300a2006-06-14 20:04:55 +00003504
Benny Prijono4e5d5512007-03-06 18:11:30 +00003505 if (app_config.capture_dev != PJSUA_INVALID_ID
3506 || app_config.playback_dev != PJSUA_INVALID_ID) {
3507 status
3508 = pjsua_set_snd_dev(app_config.capture_dev, app_config.playback_dev);
3509 if (status != PJ_SUCCESS)
3510 goto on_error;
3511 }
3512
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003513 return PJ_SUCCESS;
3514
3515on_error:
Benny Prijonoad2e0ca2007-04-29 12:31:51 +00003516 app_destroy();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003517 return status;
3518}
3519
3520
3521pj_status_t app_main(void)
3522{
3523 pj_status_t status;
3524
3525 /* Start pjsua */
3526 status = pjsua_start();
3527 if (status != PJ_SUCCESS) {
Benny Prijonoad2e0ca2007-04-29 12:31:51 +00003528 app_destroy();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003529 return status;
3530 }
3531
3532 console_app_main(&uri_arg);
3533
3534 return PJ_SUCCESS;
3535}
3536
3537pj_status_t app_destroy(void)
3538{
Benny Prijonof762ee72006-12-01 11:14:37 +00003539 pj_status_t status;
Benny Prijono4af234b2007-01-24 02:02:09 +00003540 unsigned i;
Benny Prijonof762ee72006-12-01 11:14:37 +00003541
Benny Prijonoe909eac2006-07-27 22:04:56 +00003542#ifdef STEREO_DEMO
3543 if (app_config.snd) {
3544 pjmedia_snd_port_destroy(app_config.snd);
3545 app_config.snd = NULL;
3546 }
3547#endif
3548
Benny Prijono4af234b2007-01-24 02:02:09 +00003549 /* Close tone generators */
3550 for (i=0; i<app_config.tone_count; ++i) {
3551 pjsua_conf_remove_port(app_config.tone_slots[i]);
3552 }
3553
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003554 if (app_config.pool) {
3555 pj_pool_release(app_config.pool);
3556 app_config.pool = NULL;
3557 }
3558
Benny Prijonof762ee72006-12-01 11:14:37 +00003559 status = pjsua_destroy();
3560
3561 pj_bzero(&app_config, sizeof(app_config));
3562
3563 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003564}
Benny Prijonoe909eac2006-07-27 22:04:56 +00003565
3566
3567#ifdef STEREO_DEMO
3568static void stereo_demo()
3569{
3570 pjmedia_port *conf, *splitter, *ch1;
Benny Prijonoe909eac2006-07-27 22:04:56 +00003571 pj_status_t status;
3572
3573 /* Disable existing sound device */
3574 conf = pjsua_set_no_snd_dev();
3575
Benny Prijonoe909eac2006-07-27 22:04:56 +00003576 /* Create stereo-mono splitter/combiner */
3577 status = pjmedia_splitcomb_create(app_config.pool,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003578 conf->info.clock_rate /* clock rate */,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003579 2 /* stereo */,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003580 2 * conf->info.samples_per_frame,
3581 conf->info.bits_per_sample,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003582 0 /* options */,
3583 &splitter);
3584 pj_assert(status == PJ_SUCCESS);
3585
3586 /* Connect channel0 (left channel?) to conference port slot0 */
3587 status = pjmedia_splitcomb_set_channel(splitter, 0 /* ch0 */,
3588 0 /*options*/,
3589 conf);
3590 pj_assert(status == PJ_SUCCESS);
3591
3592 /* Create reverse channel for channel1 (right channel?)... */
3593 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
3594 splitter,
3595 1 /* ch1 */,
3596 0 /* options */,
3597 &ch1);
3598 pj_assert(status == PJ_SUCCESS);
3599
3600 /* .. and register it to conference bridge (it would be slot1
3601 * if there's no other devices connected to the bridge)
3602 */
3603 status = pjsua_conf_add_port(app_config.pool, ch1, NULL);
3604 pj_assert(status == PJ_SUCCESS);
3605
3606 /* Create sound device */
3607 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003608 conf->info.clock_rate,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003609 2 /* stereo */,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003610 2 * conf->info.samples_per_frame,
3611 conf->info.bits_per_sample,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003612 0, &app_config.snd);
3613 pj_assert(status == PJ_SUCCESS);
3614
3615
3616 /* Connect the splitter to the sound device */
3617 status = pjmedia_snd_port_connect(app_config.snd, splitter);
3618 pj_assert(status == PJ_SUCCESS);
3619
3620}
3621#endif
3622