blob: 4b2b001bbb68c469dcbc33208d0d0a53ced7938f [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
Benny Prijonoa771a512007-02-19 01:13:53 +00003 * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
Benny Prijonoeebe9af2006-06-13 22:57:13 +00004 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsua-lib/pjsua.h>
20
21
22#define THIS_FILE "pjsua.c"
Benny Prijono804ff0a2006-09-14 11:17:48 +000023#define NO_LIMIT (int)0x7FFFFFFF
Benny Prijonoeebe9af2006-06-13 22:57:13 +000024
Benny Prijonoe909eac2006-07-27 22:04:56 +000025//#define STEREO_DEMO
Benny Prijonoeebe9af2006-06-13 22:57:13 +000026
Benny Prijono804ff0a2006-09-14 11:17:48 +000027/* Call specific data */
28struct call_data
29{
30 pj_timer_entry timer;
31};
32
Benny Prijonoeebe9af2006-06-13 22:57:13 +000033
34/* Pjsua application data */
35static struct app_config
36{
37 pjsua_config cfg;
38 pjsua_logging_config log_cfg;
39 pjsua_media_config media_cfg;
Benny Prijono4ddad2c2006-10-18 17:16:34 +000040 pj_bool_t no_refersub;
Benny Prijonoe93e2872006-06-28 16:46:49 +000041 pj_bool_t no_tcp;
42 pj_bool_t no_udp;
Benny Prijono6e0e54b2006-12-08 21:58:31 +000043 pj_bool_t use_tls;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000044 pjsua_transport_config udp_cfg;
45 pjsua_transport_config rtp_cfg;
46
47 unsigned acc_cnt;
48 pjsua_acc_config acc_cfg[PJSUA_MAX_ACC];
49
50 unsigned buddy_cnt;
51 pjsua_buddy_config buddy_cfg[PJSUA_MAX_BUDDIES];
52
Benny Prijono804ff0a2006-09-14 11:17:48 +000053 struct call_data call_data[PJSUA_MAX_CALLS];
54
Benny Prijonoeebe9af2006-06-13 22:57:13 +000055 pj_pool_t *pool;
56 /* Compatibility with older pjsua */
57
58 unsigned codec_cnt;
59 pj_str_t codec_arg[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +000060 pj_bool_t null_audio;
Benny Prijono32e4f492007-01-24 00:44:26 +000061 unsigned wav_count;
62 pj_str_t wav_files[32];
Benny Prijono4af234b2007-01-24 02:02:09 +000063 unsigned tone_count;
64 pjmedia_tone_desc tones[32];
65 pjsua_conf_port_id tone_slots[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +000066 pjsua_player_id wav_id;
67 pjsua_conf_port_id wav_port;
68 pj_bool_t auto_play;
69 pj_bool_t auto_loop;
Benny Prijono7ca96da2006-08-07 12:11:40 +000070 pj_bool_t auto_conf;
Benny Prijono1ebd6142006-10-19 15:48:02 +000071 pj_str_t rec_file;
72 pj_bool_t auto_rec;
73 pjsua_recorder_id rec_id;
74 pjsua_conf_port_id rec_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000075 unsigned auto_answer;
76 unsigned duration;
Benny Prijonoe909eac2006-07-27 22:04:56 +000077
78#ifdef STEREO_DEMO
79 pjmedia_snd_port *snd;
80#endif
81
Benny Prijono6dd967c2006-12-26 02:27:14 +000082 float mic_level,
83 speaker_level;
84
Benny Prijono4e5d5512007-03-06 18:11:30 +000085 int capture_dev, playback_dev;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000086} app_config;
87
88
Benny Prijono21b9ad92006-08-15 13:11:22 +000089//static pjsua_acc_id current_acc;
90#define current_acc pjsua_acc_get_default()
Benny Prijonof7b1c392006-11-11 16:46:34 +000091static pjsua_call_id current_call = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000092static pj_str_t uri_arg;
93
Benny Prijono594e4c52006-09-14 18:51:01 +000094#ifdef STEREO_DEMO
Benny Prijonoe909eac2006-07-27 22:04:56 +000095static void stereo_demo();
Benny Prijono594e4c52006-09-14 18:51:01 +000096#endif
Benny Prijonoad2e0ca2007-04-29 12:31:51 +000097pj_status_t app_destroy(void);
Benny Prijonoe909eac2006-07-27 22:04:56 +000098
Benny Prijonoeebe9af2006-06-13 22:57:13 +000099/*****************************************************************************
100 * Configuration manipulation
101 */
102
103/* Show usage */
104static void usage(void)
105{
106 puts ("Usage:");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000107 puts (" pjsua [options] [SIP URL to call]");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000108 puts ("");
109 puts ("General options:");
Benny Prijono804ff0a2006-09-14 11:17:48 +0000110 puts (" --config-file=file Read the config/arguments from file.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000111 puts (" --help Display this help screen");
112 puts (" --version Display version info");
113 puts ("");
114 puts ("Logging options:");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000115 puts (" --log-file=fname Log to filename (default stderr)");
116 puts (" --log-level=N Set log max level to N (0(none) to 6(trace)) (default=5)");
117 puts (" --app-log-level=N Set log max level for stdout display (default=4)");
118 puts ("");
119 puts ("SIP Account options:");
120 puts (" --registrar=url Set the URL of registrar server");
121 puts (" --id=url Set the URL of local ID (used in From header)");
122 puts (" --contact=url Optionally override the Contact information");
123 puts (" --proxy=url Optional URL of proxy server to visit");
124 puts (" May be specified multiple times");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000125 puts (" --reg-timeout=SEC Optional registration interval (default 55)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000126 puts (" --realm=string Set realm");
127 puts (" --username=string Set authentication username");
128 puts (" --password=string Set authentication password");
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000129 puts (" --publish Send presence PUBLISH for this account");
Benny Prijonodcfc0ba2007-09-30 16:50:27 +0000130 puts (" --use-100rel Require reliable provisional response (100rel)");
Benny Prijonob67eaac2007-02-18 20:56:00 +0000131 puts (" --next-cred Add another credentials");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000132 puts ("");
133 puts ("SIP Account Control:");
134 puts (" --next-account Add more account");
135 puts ("");
136 puts ("Transport Options:");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000137 puts (" --local-port=port Set TCP/UDP port. This implicitly enables both ");
138 puts (" TCP and UDP transports on the specified port, unless");
139 puts (" if TCP or UDP is disabled.");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000140 puts (" --ip-addr=IP Use the specifed address as SIP and RTP addresses.");
141 puts (" (Hint: the IP may be the public IP of the NAT/router)");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000142 puts (" --no-tcp Disable TCP transport.");
143 puts (" --no-udp Disable UDP transport.");
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000144 puts (" --nameserver=NS Add the specified nameserver to enable SRV resolution");
145 puts (" This option can be specified multiple times.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000146 puts (" --outbound=url Set the URL of global outbound proxy server");
147 puts (" May be specified multiple times");
Benny Prijonoc97608e2007-03-23 16:34:20 +0000148 puts (" --stun-srv=name Set STUN server host or domain");
Benny Prijonof3bbc132006-12-25 06:43:59 +0000149 puts ("");
150 puts ("TLS Options:");
Benny Prijonob67eaac2007-02-18 20:56:00 +0000151 puts (" --use-tls Enable TLS transport (default=no)");
Benny Prijonof3bbc132006-12-25 06:43:59 +0000152 puts (" --tls-ca-file Specify TLS CA file (default=none)");
153 puts (" --tls-cert-file Specify TLS certificate file (default=none)");
154 puts (" --tls-privkey-file Specify TLS private key file (default=none)");
155 puts (" --tls-password Specify TLS password to private key file (default=none)");
156 puts (" --tls-verify-server Verify server's certificate (default=no)");
157 puts (" --tls-verify-client Verify client's certificate (default=no)");
158 puts (" --tls-neg-timeout Specify TLS negotiation timeout (default=no)");
159
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000160 puts ("");
161 puts ("Media Options:");
Benny Prijonoc97608e2007-03-23 16:34:20 +0000162 puts (" --use-ice Enable ICE (default:no)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000163 puts (" --add-codec=name Manually add codec (default is to enable all)");
164 puts (" --clock-rate=N Override sound device clock rate");
165 puts (" --null-audio Use NULL audio device");
Benny Prijono4af234b2007-01-24 02:02:09 +0000166 puts (" --play-file=file Register WAV file in conference bridge.");
167 puts (" This can be specified multiple times.");
Benny Prijonob67eaac2007-02-18 20:56:00 +0000168 puts (" --play-tone=FORMAT Register tone to the conference bridge.");
169 puts (" FORMAT is 'F1,F2,ON,OFF', where F1,F2 are");
170 puts (" frequencies, and ON,OFF=on/off duration in msec.");
Benny Prijono4af234b2007-01-24 02:02:09 +0000171 puts (" This can be specified multiple times.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000172 puts (" --auto-play Automatically play the file (to incoming calls only)");
173 puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP");
Benny Prijono7ca96da2006-08-07 12:11:40 +0000174 puts (" --auto-conf Automatically put calls in conference with others");
Benny Prijono1ebd6142006-10-19 15:48:02 +0000175 puts (" --rec-file=file Open file recorder (extension can be .wav or .mp3");
176 puts (" --auto-rec Automatically record conversation");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000177 puts (" --rtp-port=N Base port to try for RTP (default=4000)");
Benny Prijono00cae612006-07-31 15:19:36 +0000178 puts (" --quality=N Specify media quality (0-10, default=6)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000179 puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)");
Benny Prijono0a12f002006-07-26 17:05:39 +0000180 puts (" --no-vad Disable VAD/silence detector (default=vad enabled)");
Benny Prijonod79f25c2006-08-02 19:41:37 +0000181 puts (" --ec-tail=MSEC Set echo canceller tail length (default=256)");
Benny Prijono00cae612006-07-31 15:19:36 +0000182 puts (" --ilbc-mode=MODE Set iLBC codec mode (20 or 30, default is 20)");
183 puts (" --rx-drop-pct=PCT Drop PCT percent of RX RTP (for pkt lost sim, default: 0)");
184 puts (" --tx-drop-pct=PCT Drop PCT percent of TX RTP (for pkt lost sim, default: 0)");
Benny Prijono4e5d5512007-03-06 18:11:30 +0000185 puts (" --capture-dev=id Audio capture device ID (default=-1)");
186 puts (" --playback-dev=id Audio playback device ID (default=-1)");
Benny Prijono00cae612006-07-31 15:19:36 +0000187
Benny Prijono0a12f002006-07-26 17:05:39 +0000188
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000189 puts ("");
190 puts ("Buddy List (can be more than one):");
191 puts (" --add-buddy url Add the specified URL to the buddy list.");
192 puts ("");
193 puts ("User Agent options:");
194 puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)");
195 puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)");
Benny Prijonof521eb02006-08-06 23:07:25 +0000196 puts (" --thread-cnt=N Number of worker threads (default:1)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000197 puts (" --duration=SEC Set maximum call duration (default:no limit)");
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000198 puts (" --norefersub Suppress event subscription when transfering calls");
Benny Prijono804ff0a2006-09-14 11:17:48 +0000199
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000200 puts ("");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000201 puts ("When URL is specified, pjsua will immediately initiate call to that URL");
202 puts ("");
203
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000204 fflush(stdout);
205}
206
207
208/* Set default config. */
209static void default_config(struct app_config *cfg)
210{
Benny Prijono56315612006-07-18 14:39:40 +0000211 char tmp[80];
Benny Prijono1febfdf2007-02-18 01:35:04 +0000212 unsigned i;
Benny Prijono56315612006-07-18 14:39:40 +0000213
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000214 pjsua_config_default(&cfg->cfg);
Benny Prijono106f5b72007-08-30 13:49:33 +0000215 pj_ansi_sprintf(tmp, "PJSUA v%s/%s", pj_get_version(), PJ_OS_NAME);
Benny Prijono56315612006-07-18 14:39:40 +0000216 pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
217
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000218 pjsua_logging_config_default(&cfg->log_cfg);
219 pjsua_media_config_default(&cfg->media_cfg);
220 pjsua_transport_config_default(&cfg->udp_cfg);
221 cfg->udp_cfg.port = 5060;
222 pjsua_transport_config_default(&cfg->rtp_cfg);
223 cfg->rtp_cfg.port = 4000;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000224 cfg->duration = NO_LIMIT;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000225 cfg->wav_id = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000226 cfg->rec_id = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000227 cfg->wav_port = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000228 cfg->rec_port = PJSUA_INVALID_ID;
Benny Prijono6dd967c2006-12-26 02:27:14 +0000229 cfg->mic_level = cfg->speaker_level = 1.0;
Benny Prijono4e5d5512007-03-06 18:11:30 +0000230 cfg->capture_dev = PJSUA_INVALID_ID;
231 cfg->playback_dev = PJSUA_INVALID_ID;
Benny Prijono1febfdf2007-02-18 01:35:04 +0000232
233 for (i=0; i<PJ_ARRAY_SIZE(cfg->acc_cfg); ++i)
234 pjsua_acc_config_default(&cfg->acc_cfg[i]);
235
236 for (i=0; i<PJ_ARRAY_SIZE(cfg->buddy_cfg); ++i)
237 pjsua_buddy_config_default(&cfg->buddy_cfg[i]);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000238}
239
240
241/*
242 * Read command arguments from config file.
243 */
244static int read_config_file(pj_pool_t *pool, const char *filename,
245 int *app_argc, char ***app_argv)
246{
247 int i;
248 FILE *fhnd;
249 char line[200];
250 int argc = 0;
251 char **argv;
252 enum { MAX_ARGS = 64 };
253
254 /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
255 argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
256 argv[argc++] = *app_argv[0];
257
258 /* Open config file. */
259 fhnd = fopen(filename, "rt");
260 if (!fhnd) {
261 PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
262 fflush(stdout);
263 return -1;
264 }
265
266 /* Scan tokens in the file. */
267 while (argc < MAX_ARGS && !feof(fhnd)) {
Benny Prijonobf5b4692007-06-28 03:20:17 +0000268 char *token;
269 char *p;
270 const char *whitespace = " \t\r\n";
271 char cDelimiter;
272 int len, token_len;
273
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000274 if (fgets(line, sizeof(line), fhnd) == NULL) break;
Benny Prijonobf5b4692007-06-28 03:20:17 +0000275
276 // Trim ending newlines
277 len = strlen(line);
278 if (line[len-1]=='\n')
279 line[--len] = '\0';
280 if (line[len-1]=='\r')
281 line[--len] = '\0';
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000282
Benny Prijonobf5b4692007-06-28 03:20:17 +0000283 if (len==0) continue;
284
285 for (p = line; *p != '\0' && argc < MAX_ARGS; p++) {
286 // first, scan whitespaces
287 while (*p != '\0' && strchr(whitespace, *p) != NULL) p++;
288
289 if (*p == '\0') // are we done yet?
290 break;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000291
Benny Prijonobf5b4692007-06-28 03:20:17 +0000292 if (*p == '"' || *p == '\'') { // is token a quoted string
293 cDelimiter = *p++; // save quote delimiter
294 token = p;
295
296 while (*p != '\0' && *p != cDelimiter) p++;
297
298 if (*p == '\0') // found end of the line, but,
299 cDelimiter = '\0'; // didn't find a matching quote
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000300
Benny Prijonobf5b4692007-06-28 03:20:17 +0000301 } else { // token's not a quoted string
302 token = p;
303
304 while (*p != '\0' && strchr(whitespace, *p) == NULL) p++;
305
306 cDelimiter = *p;
307 }
308
309 *p = '\0';
310 token_len = p-token;
311
312 if (token_len > 0) {
313 if (*token == '#')
314 break; // ignore remainder of line
315
316 argv[argc] = pj_pool_alloc(pool, token_len + 1);
317 pj_memcpy(argv[argc], token, token_len + 1);
318 ++argc;
319 }
320
321 *p = cDelimiter;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000322 }
323 }
324
325 /* Copy arguments from command line */
326 for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
327 argv[argc++] = (*app_argv)[i];
328
329 if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
330 PJ_LOG(1,(THIS_FILE,
331 "Too many arguments specified in cmd line/config file"));
332 fflush(stdout);
333 fclose(fhnd);
334 return -1;
335 }
336
337 fclose(fhnd);
338
339 /* Assign the new command line back to the original command line. */
340 *app_argc = argc;
341 *app_argv = argv;
342 return 0;
343
344}
345
346static int my_atoi(const char *cs)
347{
348 pj_str_t s;
Benny Prijono1e2dbe62007-06-15 04:15:16 +0000349
350 pj_cstr(&s, cs);
351 if (cs[0] == '-') {
352 s.ptr++, s.slen--;
353 return 0 - (int)pj_strtoul(&s);
354 } else if (cs[0] == '+') {
355 s.ptr++, s.slen--;
356 return pj_strtoul(&s);
357 } else {
358 return pj_strtoul(&s);
359 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000360}
361
362
363/* Parse arguments. */
364static pj_status_t parse_args(int argc, char *argv[],
365 struct app_config *cfg,
366 pj_str_t *uri_to_call)
367{
368 int c;
369 int option_index;
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000370 enum { OPT_CONFIG_FILE=127, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000371 OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
Benny Prijono0a5cad82006-09-26 13:21:02 +0000372 OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY,
373 OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
Benny Prijonodcfc0ba2007-09-30 16:50:27 +0000374 OPT_100REL, OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
Benny Prijonoebbf6892007-03-24 17:37:25 +0000375 OPT_NAMESERVER, OPT_STUN_DOMAIN, OPT_STUN_SRV,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000376 OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
377 OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
Benny Prijonoc97608e2007-03-23 16:34:20 +0000378 OPT_AUTO_CONF, OPT_CLOCK_RATE, OPT_USE_ICE,
Benny Prijono4af234b2007-01-24 02:02:09 +0000379 OPT_PLAY_FILE, OPT_PLAY_TONE, OPT_RTP_PORT, OPT_ADD_CODEC,
380 OPT_ILBC_MODE, OPT_REC_FILE, OPT_AUTO_REC,
Benny Prijono0a12f002006-07-26 17:05:39 +0000381 OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
Benny Prijonod79f25c2006-08-02 19:41:37 +0000382 OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL,
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000383 OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
Benny Prijonof521eb02006-08-06 23:07:25 +0000384 OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000385 OPT_NOREFERSUB,
Benny Prijonof3bbc132006-12-25 06:43:59 +0000386 OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_CERT_FILE, OPT_TLS_PRIV_FILE,
387 OPT_TLS_PASSWORD, OPT_TLS_VERIFY_SERVER, OPT_TLS_VERIFY_CLIENT,
388 OPT_TLS_NEG_TIMEOUT,
Benny Prijono4e5d5512007-03-06 18:11:30 +0000389 OPT_CAPTURE_DEV, OPT_PLAYBACK_DEV,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000390 };
391 struct pj_getopt_option long_options[] = {
392 { "config-file",1, 0, OPT_CONFIG_FILE},
393 { "log-file", 1, 0, OPT_LOG_FILE},
394 { "log-level", 1, 0, OPT_LOG_LEVEL},
395 { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
396 { "help", 0, 0, OPT_HELP},
397 { "version", 0, 0, OPT_VERSION},
398 { "clock-rate", 1, 0, OPT_CLOCK_RATE},
399 { "null-audio", 0, 0, OPT_NULL_AUDIO},
400 { "local-port", 1, 0, OPT_LOCAL_PORT},
Benny Prijono0a5cad82006-09-26 13:21:02 +0000401 { "ip-addr", 1, 0, OPT_IP_ADDR},
Benny Prijonoe93e2872006-06-28 16:46:49 +0000402 { "no-tcp", 0, 0, OPT_NO_TCP},
403 { "no-udp", 0, 0, OPT_NO_UDP},
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000404 { "norefersub", 0, 0, OPT_NOREFERSUB},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000405 { "proxy", 1, 0, OPT_PROXY},
406 { "outbound", 1, 0, OPT_OUTBOUND_PROXY},
407 { "registrar", 1, 0, OPT_REGISTRAR},
408 { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000409 { "publish", 0, 0, OPT_PUBLISH},
Benny Prijonodcfc0ba2007-09-30 16:50:27 +0000410 { "use-100rel", 0, 0, OPT_100REL},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000411 { "id", 1, 0, OPT_ID},
412 { "contact", 1, 0, OPT_CONTACT},
413 { "realm", 1, 0, OPT_REALM},
414 { "username", 1, 0, OPT_USERNAME},
415 { "password", 1, 0, OPT_PASSWORD},
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000416 { "nameserver", 1, 0, OPT_NAMESERVER},
Benny Prijonoebbf6892007-03-24 17:37:25 +0000417 { "stun-domain",1, 0, OPT_STUN_DOMAIN},
Benny Prijonoc97608e2007-03-23 16:34:20 +0000418 { "stun-srv", 1, 0, OPT_STUN_SRV},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000419 { "add-buddy", 1, 0, OPT_ADD_BUDDY},
420 { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
421 { "no-presence", 0, 0, OPT_NO_PRESENCE},
422 { "auto-answer",1, 0, OPT_AUTO_ANSWER},
423 { "auto-hangup",1, 0, OPT_AUTO_HANGUP},
424 { "auto-play", 0, 0, OPT_AUTO_PLAY},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000425 { "auto-rec", 0, 0, OPT_AUTO_REC},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000426 { "auto-loop", 0, 0, OPT_AUTO_LOOP},
427 { "auto-conf", 0, 0, OPT_AUTO_CONF},
428 { "play-file", 1, 0, OPT_PLAY_FILE},
Benny Prijono4af234b2007-01-24 02:02:09 +0000429 { "play-tone", 1, 0, OPT_PLAY_TONE},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000430 { "rec-file", 1, 0, OPT_REC_FILE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000431 { "rtp-port", 1, 0, OPT_RTP_PORT},
Benny Prijonoc97608e2007-03-23 16:34:20 +0000432 { "use-ice", 0, 0, OPT_USE_ICE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000433 { "add-codec", 1, 0, OPT_ADD_CODEC},
434 { "complexity", 1, 0, OPT_COMPLEXITY},
435 { "quality", 1, 0, OPT_QUALITY},
436 { "ptime", 1, 0, OPT_PTIME},
Benny Prijono0a12f002006-07-26 17:05:39 +0000437 { "no-vad", 0, 0, OPT_NO_VAD},
Benny Prijonod79f25c2006-08-02 19:41:37 +0000438 { "ec-tail", 1, 0, OPT_EC_TAIL},
Benny Prijono00cae612006-07-31 15:19:36 +0000439 { "ilbc-mode", 1, 0, OPT_ILBC_MODE},
440 { "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
441 { "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000442 { "next-account",0,0, OPT_NEXT_ACCOUNT},
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000443 { "next-cred", 0, 0, OPT_NEXT_CRED},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000444 { "max-calls", 1, 0, OPT_MAX_CALLS},
Benny Prijonof521eb02006-08-06 23:07:25 +0000445 { "duration", 1, 0, OPT_DURATION},
446 { "thread-cnt", 1, 0, OPT_THREAD_CNT},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000447 { "use-tls", 0, 0, OPT_USE_TLS},
448 { "tls-ca-file",1, 0, OPT_TLS_CA_FILE},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000449 { "tls-cert-file",1,0, OPT_TLS_CERT_FILE},
450 { "tls-privkey-file",1,0, OPT_TLS_PRIV_FILE},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000451 { "tls-password",1,0, OPT_TLS_PASSWORD},
Benny Prijonof3bbc132006-12-25 06:43:59 +0000452 { "tls-verify-server", 0, 0, OPT_TLS_VERIFY_SERVER},
453 { "tls-verify-client", 0, 0, OPT_TLS_VERIFY_CLIENT},
454 { "tls-neg-timeout", 1, 0, OPT_TLS_NEG_TIMEOUT},
Benny Prijono4e5d5512007-03-06 18:11:30 +0000455 { "capture-dev", 1, 0, OPT_CAPTURE_DEV},
456 { "playback-dev", 1, 0, OPT_PLAYBACK_DEV},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000457 { NULL, 0, 0, 0}
458 };
459 pj_status_t status;
460 pjsua_acc_config *cur_acc;
461 char *config_file = NULL;
462 unsigned i;
463
464 /* Run pj_getopt once to see if user specifies config file to read. */
Benny Prijonof762ee72006-12-01 11:14:37 +0000465 pj_optind = 0;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000466 while ((c=pj_getopt_long(argc, argv, "", long_options,
467 &option_index)) != -1)
468 {
469 switch (c) {
470 case OPT_CONFIG_FILE:
471 config_file = pj_optarg;
472 break;
473 }
474 if (config_file)
475 break;
476 }
477
478 if (config_file) {
479 status = read_config_file(app_config.pool, config_file, &argc, &argv);
480 if (status != 0)
481 return status;
482 }
483
484 cfg->acc_cnt = 0;
485 cur_acc = &cfg->acc_cfg[0];
486
487
488 /* Reinitialize and re-run pj_getopt again, possibly with new arguments
489 * read from config file.
490 */
491 pj_optind = 0;
492 while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000493 pj_str_t tmp;
494 long lval;
495
496 switch (c) {
497
Benny Prijono6f137482006-06-15 11:31:36 +0000498 case OPT_CONFIG_FILE:
499 /* Ignore as this has been processed before */
500 break;
501
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000502 case OPT_LOG_FILE:
503 cfg->log_cfg.log_filename = pj_str(pj_optarg);
504 break;
505
506 case OPT_LOG_LEVEL:
507 c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
508 if (c < 0 || c > 6) {
509 PJ_LOG(1,(THIS_FILE,
510 "Error: expecting integer value 0-6 "
511 "for --log-level"));
512 return PJ_EINVAL;
513 }
514 cfg->log_cfg.level = c;
515 pj_log_set_level( c );
516 break;
517
518 case OPT_APP_LOG_LEVEL:
519 cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
520 if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
521 PJ_LOG(1,(THIS_FILE,
522 "Error: expecting integer value 0-6 "
523 "for --app-log-level"));
524 return PJ_EINVAL;
525 }
526 break;
527
528 case OPT_HELP:
529 usage();
530 return PJ_EINVAL;
531
532 case OPT_VERSION: /* version */
533 pj_dump_config();
534 return PJ_EINVAL;
535
536 case OPT_NULL_AUDIO:
537 cfg->null_audio = PJ_TRUE;
538 break;
539
540 case OPT_CLOCK_RATE:
541 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
542 if (lval < 8000 || lval > 48000) {
543 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
544 "8000-48000 for clock rate"));
545 return PJ_EINVAL;
546 }
547 cfg->media_cfg.clock_rate = lval;
548 break;
549
550 case OPT_LOCAL_PORT: /* local-port */
551 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
Benny Prijonoe347cb02007-02-14 14:36:13 +0000552 if (lval < 0 || lval > 65535) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000553 PJ_LOG(1,(THIS_FILE,
554 "Error: expecting integer value for "
555 "--local-port"));
556 return PJ_EINVAL;
557 }
558 cfg->udp_cfg.port = (pj_uint16_t)lval;
559 break;
560
Benny Prijono0a5cad82006-09-26 13:21:02 +0000561 case OPT_IP_ADDR: /* ip-addr */
562 cfg->udp_cfg.public_addr = pj_str(pj_optarg);
563 cfg->rtp_cfg.public_addr = pj_str(pj_optarg);
564 break;
565
Benny Prijonoe93e2872006-06-28 16:46:49 +0000566 case OPT_NO_UDP: /* no-udp */
567 if (cfg->no_tcp) {
568 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
569 return PJ_EINVAL;
570 }
571
572 cfg->no_udp = PJ_TRUE;
573 break;
574
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000575 case OPT_NOREFERSUB: /* norefersub */
576 cfg->no_refersub = PJ_TRUE;
577 break;
578
Benny Prijonoe93e2872006-06-28 16:46:49 +0000579 case OPT_NO_TCP: /* no-tcp */
580 if (cfg->no_udp) {
581 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
582 return PJ_EINVAL;
583 }
584
585 cfg->no_tcp = PJ_TRUE;
586 break;
587
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000588 case OPT_PROXY: /* proxy */
589 if (pjsua_verify_sip_url(pj_optarg) != 0) {
590 PJ_LOG(1,(THIS_FILE,
591 "Error: invalid SIP URL '%s' "
592 "in proxy argument", pj_optarg));
593 return PJ_EINVAL;
594 }
595 cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
596 break;
597
598 case OPT_OUTBOUND_PROXY: /* outbound proxy */
599 if (pjsua_verify_sip_url(pj_optarg) != 0) {
600 PJ_LOG(1,(THIS_FILE,
601 "Error: invalid SIP URL '%s' "
602 "in outbound proxy argument", pj_optarg));
603 return PJ_EINVAL;
604 }
605 cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
606 break;
607
608 case OPT_REGISTRAR: /* registrar */
609 if (pjsua_verify_sip_url(pj_optarg) != 0) {
610 PJ_LOG(1,(THIS_FILE,
611 "Error: invalid SIP URL '%s' in "
612 "registrar argument", pj_optarg));
613 return PJ_EINVAL;
614 }
615 cur_acc->reg_uri = pj_str(pj_optarg);
616 break;
617
618 case OPT_REG_TIMEOUT: /* reg-timeout */
619 cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
620 if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
621 PJ_LOG(1,(THIS_FILE,
622 "Error: invalid value for --reg-timeout "
623 "(expecting 1-3600)"));
624 return PJ_EINVAL;
625 }
626 break;
627
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000628 case OPT_PUBLISH: /* publish */
629 cur_acc->publish_enabled = PJ_TRUE;
630 break;
631
Benny Prijonodcfc0ba2007-09-30 16:50:27 +0000632 case OPT_100REL: /** 100rel */
633 cur_acc->require_100rel = PJ_TRUE;
634 cfg->cfg.require_100rel = PJ_TRUE;
635 break;
636
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000637 case OPT_ID: /* id */
638 if (pjsua_verify_sip_url(pj_optarg) != 0) {
639 PJ_LOG(1,(THIS_FILE,
640 "Error: invalid SIP URL '%s' "
641 "in local id argument", pj_optarg));
642 return PJ_EINVAL;
643 }
644 cur_acc->id = pj_str(pj_optarg);
645 break;
646
647 case OPT_CONTACT: /* contact */
648 if (pjsua_verify_sip_url(pj_optarg) != 0) {
649 PJ_LOG(1,(THIS_FILE,
650 "Error: invalid SIP URL '%s' "
651 "in contact argument", pj_optarg));
652 return PJ_EINVAL;
653 }
Benny Prijonob4a17c92006-07-10 14:40:21 +0000654 cur_acc->force_contact = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000655 break;
656
657 case OPT_NEXT_ACCOUNT: /* Add more account. */
658 cfg->acc_cnt++;
Benny Prijono56315612006-07-18 14:39:40 +0000659 cur_acc = &cfg->acc_cfg[cfg->acc_cnt];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000660 break;
661
662 case OPT_USERNAME: /* Default authentication user */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000663 cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
664 cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("digest");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000665 break;
666
667 case OPT_REALM: /* Default authentication realm. */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000668 cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000669 break;
670
671 case OPT_PASSWORD: /* authentication password */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000672 cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
673 cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
674 break;
675
676 case OPT_NEXT_CRED: /* next credential */
677 cur_acc->cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000678 break;
679
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000680 case OPT_NAMESERVER: /* nameserver */
681 cfg->cfg.nameserver[cfg->cfg.nameserver_count++] = pj_str(pj_optarg);
682 if (cfg->cfg.nameserver_count > PJ_ARRAY_SIZE(cfg->cfg.nameserver)) {
683 PJ_LOG(1,(THIS_FILE, "Error: too many nameservers"));
684 return PJ_ETOOMANY;
685 }
686 break;
687
Benny Prijonoebbf6892007-03-24 17:37:25 +0000688 case OPT_STUN_DOMAIN: /* STUN domain */
689 cfg->cfg.stun_domain = pj_str(pj_optarg);
690 break;
691
Benny Prijonoc97608e2007-03-23 16:34:20 +0000692 case OPT_STUN_SRV: /* STUN server */
Benny Prijonoebbf6892007-03-24 17:37:25 +0000693 cfg->cfg.stun_host = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000694 break;
695
696 case OPT_ADD_BUDDY: /* Add to buddy list. */
697 if (pjsua_verify_sip_url(pj_optarg) != 0) {
698 PJ_LOG(1,(THIS_FILE,
699 "Error: invalid URL '%s' in "
700 "--add-buddy option", pj_optarg));
701 return -1;
702 }
703 if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
704 PJ_LOG(1,(THIS_FILE,
705 "Error: too many buddies in buddy list."));
706 return -1;
707 }
708 cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
709 cfg->buddy_cnt++;
710 break;
711
712 case OPT_AUTO_PLAY:
713 cfg->auto_play = 1;
714 break;
715
Benny Prijono1ebd6142006-10-19 15:48:02 +0000716 case OPT_AUTO_REC:
717 cfg->auto_rec = 1;
718 break;
719
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000720 case OPT_AUTO_LOOP:
721 cfg->auto_loop = 1;
722 break;
723
Benny Prijono7ca96da2006-08-07 12:11:40 +0000724 case OPT_AUTO_CONF:
725 cfg->auto_conf = 1;
726 break;
727
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000728 case OPT_PLAY_FILE:
Benny Prijono32e4f492007-01-24 00:44:26 +0000729 cfg->wav_files[cfg->wav_count++] = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000730 break;
731
Benny Prijono4af234b2007-01-24 02:02:09 +0000732 case OPT_PLAY_TONE:
733 {
734 int f1, f2, on, off;
735 int n;
736
737 n = sscanf(pj_optarg, "%d,%d,%d,%d", &f1, &f2, &on, &off);
738 if (n != 4) {
739 puts("Expecting f1,f2,on,off in --play-tone");
740 return -1;
741 }
742
743 cfg->tones[cfg->tone_count].freq1 = (short)f1;
744 cfg->tones[cfg->tone_count].freq2 = (short)f2;
745 cfg->tones[cfg->tone_count].on_msec = (short)on;
746 cfg->tones[cfg->tone_count].off_msec = (short)off;
747 ++cfg->tone_count;
748 }
749 break;
750
Benny Prijono1ebd6142006-10-19 15:48:02 +0000751 case OPT_REC_FILE:
752 cfg->rec_file = pj_str(pj_optarg);
753 break;
754
Benny Prijonoc97608e2007-03-23 16:34:20 +0000755 case OPT_USE_ICE:
756 cfg->media_cfg.enable_ice = PJ_TRUE;
757 break;
758
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000759 case OPT_RTP_PORT:
760 cfg->rtp_cfg.port = my_atoi(pj_optarg);
Benny Prijono5583a802007-06-26 12:20:37 +0000761 if (cfg->rtp_cfg.port == 0) {
762 enum { START_PORT=4000 };
763 unsigned range;
764
765 range = (65535-START_PORT-PJSUA_MAX_CALLS*2);
766 cfg->rtp_cfg.port = START_PORT +
767 ((pj_rand() % range) & 0xFFFE);
768 }
769
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000770 if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
771 PJ_LOG(1,(THIS_FILE,
772 "Error: rtp-port argument value "
773 "(expecting 1-65535"));
774 return -1;
775 }
776 break;
777
778 case OPT_ADD_CODEC:
779 cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
780 break;
781
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000782 /* These options were no longer valid after new pjsua */
783 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000784 case OPT_COMPLEXITY:
785 cfg->complexity = my_atoi(pj_optarg);
786 if (cfg->complexity < 0 || cfg->complexity > 10) {
787 PJ_LOG(1,(THIS_FILE,
788 "Error: invalid --complexity (expecting 0-10"));
789 return -1;
790 }
791 break;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000792 */
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000793
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000794 case OPT_DURATION:
795 cfg->duration = my_atoi(pj_optarg);
796 break;
797
Benny Prijonof521eb02006-08-06 23:07:25 +0000798 case OPT_THREAD_CNT:
799 cfg->cfg.thread_cnt = my_atoi(pj_optarg);
800 if (cfg->cfg.thread_cnt > 128) {
801 PJ_LOG(1,(THIS_FILE,
802 "Error: invalid --thread-cnt option"));
803 return -1;
804 }
805 break;
806
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000807 case OPT_PTIME:
Benny Prijono0a12f002006-07-26 17:05:39 +0000808 cfg->media_cfg.ptime = my_atoi(pj_optarg);
809 if (cfg->media_cfg.ptime < 10 || cfg->media_cfg.ptime > 1000) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000810 PJ_LOG(1,(THIS_FILE,
811 "Error: invalid --ptime option"));
812 return -1;
813 }
814 break;
815
Benny Prijono0a12f002006-07-26 17:05:39 +0000816 case OPT_NO_VAD:
817 cfg->media_cfg.no_vad = PJ_TRUE;
818 break;
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000819
Benny Prijonod79f25c2006-08-02 19:41:37 +0000820 case OPT_EC_TAIL:
821 cfg->media_cfg.ec_tail_len = my_atoi(pj_optarg);
822 if (cfg->media_cfg.ec_tail_len > 1000) {
823 PJ_LOG(1,(THIS_FILE, "I think the ec-tail length setting "
824 "is too big"));
825 return -1;
826 }
827 break;
828
Benny Prijono0498d902006-06-19 14:49:14 +0000829 case OPT_QUALITY:
830 cfg->media_cfg.quality = my_atoi(pj_optarg);
831 if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
832 PJ_LOG(1,(THIS_FILE,
833 "Error: invalid --quality (expecting 0-10"));
834 return -1;
835 }
836 break;
837
Benny Prijono00cae612006-07-31 15:19:36 +0000838 case OPT_ILBC_MODE:
839 cfg->media_cfg.ilbc_mode = my_atoi(pj_optarg);
840 if (cfg->media_cfg.ilbc_mode!=20 && cfg->media_cfg.ilbc_mode!=30) {
841 PJ_LOG(1,(THIS_FILE,
842 "Error: invalid --ilbc-mode (expecting 20 or 30"));
843 return -1;
844 }
845 break;
846
847 case OPT_RX_DROP_PCT:
848 cfg->media_cfg.rx_drop_pct = my_atoi(pj_optarg);
849 if (cfg->media_cfg.rx_drop_pct > 100) {
850 PJ_LOG(1,(THIS_FILE,
851 "Error: invalid --rx-drop-pct (expecting <= 100"));
852 return -1;
853 }
854 break;
855
856 case OPT_TX_DROP_PCT:
857 cfg->media_cfg.tx_drop_pct = my_atoi(pj_optarg);
858 if (cfg->media_cfg.tx_drop_pct > 100) {
859 PJ_LOG(1,(THIS_FILE,
860 "Error: invalid --tx-drop-pct (expecting <= 100"));
861 return -1;
862 }
863 break;
864
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000865 case OPT_AUTO_ANSWER:
866 cfg->auto_answer = my_atoi(pj_optarg);
867 if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
868 PJ_LOG(1,(THIS_FILE,
869 "Error: invalid code in --auto-answer "
870 "(expecting 100-699"));
871 return -1;
872 }
873 break;
874
875 case OPT_MAX_CALLS:
876 cfg->cfg.max_calls = my_atoi(pj_optarg);
Benny Prijono48af79c2006-07-22 12:49:17 +0000877 if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > PJSUA_MAX_CALLS) {
Benny Prijono804ff0a2006-09-14 11:17:48 +0000878 PJ_LOG(1,(THIS_FILE,"Error: maximum call setting exceeds "
879 "compile time limit (PJSUA_MAX_CALLS=%d)",
Benny Prijono48af79c2006-07-22 12:49:17 +0000880 PJSUA_MAX_CALLS));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000881 return -1;
882 }
883 break;
884
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000885 case OPT_USE_TLS:
886 cfg->use_tls = PJ_TRUE;
Benny Prijonof3bbc132006-12-25 06:43:59 +0000887#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
888 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
889 return -1;
890#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000891 break;
892
893 case OPT_TLS_CA_FILE:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000894 cfg->udp_cfg.tls_setting.ca_list_file = pj_str(pj_optarg);
895#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
896 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
897 return -1;
898#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000899 break;
900
Benny Prijonof3bbc132006-12-25 06:43:59 +0000901 case OPT_TLS_CERT_FILE:
902 cfg->udp_cfg.tls_setting.cert_file = pj_str(pj_optarg);
903#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
904 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
905 return -1;
906#endif
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000907 break;
908
Benny Prijonof3bbc132006-12-25 06:43:59 +0000909 case OPT_TLS_PRIV_FILE:
910 cfg->udp_cfg.tls_setting.privkey_file = pj_str(pj_optarg);
911 break;
912
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000913 case OPT_TLS_PASSWORD:
Benny Prijonof3bbc132006-12-25 06:43:59 +0000914 cfg->udp_cfg.tls_setting.password = pj_str(pj_optarg);
915#if !defined(PJSIP_HAS_TLS_TRANSPORT) || PJSIP_HAS_TLS_TRANSPORT==0
916 PJ_LOG(1,(THIS_FILE, "Error: TLS support is not configured"));
917 return -1;
918#endif
919 break;
920
921 case OPT_TLS_VERIFY_SERVER:
922 cfg->udp_cfg.tls_setting.verify_server = PJ_TRUE;
923 break;
924
925 case OPT_TLS_VERIFY_CLIENT:
926 cfg->udp_cfg.tls_setting.verify_client = PJ_TRUE;
927 break;
928
929 case OPT_TLS_NEG_TIMEOUT:
930 cfg->udp_cfg.tls_setting.timeout.sec = atoi(pj_optarg);
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000931 break;
932
Benny Prijono4e5d5512007-03-06 18:11:30 +0000933 case OPT_CAPTURE_DEV:
934 cfg->capture_dev = atoi(pj_optarg);
935 break;
936
937 case OPT_PLAYBACK_DEV:
938 cfg->playback_dev = atoi(pj_optarg);
939 break;
940
Benny Prijono22a300a2006-06-14 20:04:55 +0000941 default:
Benny Prijono787b8692006-06-19 12:40:03 +0000942 PJ_LOG(1,(THIS_FILE,
Benny Prijonod6388ac2006-09-09 13:23:09 +0000943 "Argument \"%s\" is not valid. Use --help to see help",
Benny Prijonoaf09dc32007-04-22 12:48:30 +0000944 argv[pj_optind-1]));
Benny Prijono22a300a2006-06-14 20:04:55 +0000945 return -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000946 }
947 }
948
949 if (pj_optind != argc) {
950 pj_str_t uri_arg;
951
952 if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
953 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
954 return -1;
955 }
956 uri_arg = pj_str(argv[pj_optind]);
957 if (uri_to_call)
958 *uri_to_call = uri_arg;
959 pj_optind++;
960
961 /* Add URI to call to buddy list if it's not already there */
962 for (i=0; i<cfg->buddy_cnt; ++i) {
963 if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
964 break;
965 }
966 if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
967 cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
968 }
969
970 } else {
971 if (uri_to_call)
972 uri_to_call->slen = 0;
973 }
974
975 if (pj_optind != argc) {
976 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
977 return PJ_EINVAL;
978 }
979
Benny Prijono56315612006-07-18 14:39:40 +0000980 if (cfg->acc_cfg[cfg->acc_cnt].id.slen)
981 cfg->acc_cnt++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000982
983 for (i=0; i<cfg->acc_cnt; ++i) {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000984 if (cfg->acc_cfg[i].cred_info[cfg->acc_cfg[i].cred_count].username.slen)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000985 {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000986 cfg->acc_cfg[i].cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000987 }
988 }
989
990
991 return PJ_SUCCESS;
992}
993
994
995/*
996 * Save account settings
997 */
998static void write_account_settings(int acc_index, pj_str_t *result)
999{
1000 unsigned i;
1001 char line[128];
1002 pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
1003
1004
1005 pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
1006 pj_strcat2(result, line);
1007
1008
1009 /* Identity */
1010 if (acc_cfg->id.slen) {
1011 pj_ansi_sprintf(line, "--id %.*s\n",
1012 (int)acc_cfg->id.slen,
1013 acc_cfg->id.ptr);
1014 pj_strcat2(result, line);
1015 }
1016
1017 /* Registrar server */
1018 if (acc_cfg->reg_uri.slen) {
1019 pj_ansi_sprintf(line, "--registrar %.*s\n",
1020 (int)acc_cfg->reg_uri.slen,
1021 acc_cfg->reg_uri.ptr);
1022 pj_strcat2(result, line);
1023
1024 pj_ansi_sprintf(line, "--reg-timeout %u\n",
1025 acc_cfg->reg_timeout);
1026 pj_strcat2(result, line);
1027 }
1028
1029 /* Contact */
Benny Prijonob4a17c92006-07-10 14:40:21 +00001030 if (acc_cfg->force_contact.slen) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001031 pj_ansi_sprintf(line, "--contact %.*s\n",
Benny Prijonob4a17c92006-07-10 14:40:21 +00001032 (int)acc_cfg->force_contact.slen,
1033 acc_cfg->force_contact.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001034 pj_strcat2(result, line);
1035 }
1036
1037 /* Proxy */
1038 for (i=0; i<acc_cfg->proxy_cnt; ++i) {
1039 pj_ansi_sprintf(line, "--proxy %.*s\n",
1040 (int)acc_cfg->proxy[i].slen,
1041 acc_cfg->proxy[i].ptr);
1042 pj_strcat2(result, line);
1043 }
1044
1045 /* Credentials */
1046 for (i=0; i<acc_cfg->cred_count; ++i) {
1047 if (acc_cfg->cred_info[i].realm.slen) {
1048 pj_ansi_sprintf(line, "--realm %.*s\n",
1049 (int)acc_cfg->cred_info[i].realm.slen,
1050 acc_cfg->cred_info[i].realm.ptr);
1051 pj_strcat2(result, line);
1052 }
1053
1054 if (acc_cfg->cred_info[i].username.slen) {
1055 pj_ansi_sprintf(line, "--username %.*s\n",
1056 (int)acc_cfg->cred_info[i].username.slen,
1057 acc_cfg->cred_info[i].username.ptr);
1058 pj_strcat2(result, line);
1059 }
1060
1061 if (acc_cfg->cred_info[i].data.slen) {
1062 pj_ansi_sprintf(line, "--password %.*s\n",
1063 (int)acc_cfg->cred_info[i].data.slen,
1064 acc_cfg->cred_info[i].data.ptr);
1065 pj_strcat2(result, line);
1066 }
Benny Prijonoeef4f8c2006-06-19 12:07:44 +00001067
1068 if (i != acc_cfg->cred_count - 1)
1069 pj_strcat2(result, "--next-cred\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001070 }
1071
1072}
1073
1074
1075/*
1076 * Write settings.
1077 */
1078static int write_settings(const struct app_config *config,
1079 char *buf, pj_size_t max)
1080{
1081 unsigned acc_index;
1082 unsigned i;
1083 pj_str_t cfg;
1084 char line[128];
1085
1086 PJ_UNUSED_ARG(max);
1087
1088 cfg.ptr = buf;
1089 cfg.slen = 0;
1090
1091 /* Logging. */
1092 pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
1093 pj_ansi_sprintf(line, "--log-level %d\n",
1094 config->log_cfg.level);
1095 pj_strcat2(&cfg, line);
1096
1097 pj_ansi_sprintf(line, "--app-log-level %d\n",
1098 config->log_cfg.console_level);
1099 pj_strcat2(&cfg, line);
1100
1101 if (config->log_cfg.log_filename.slen) {
1102 pj_ansi_sprintf(line, "--log-file %.*s\n",
1103 (int)config->log_cfg.log_filename.slen,
1104 config->log_cfg.log_filename.ptr);
1105 pj_strcat2(&cfg, line);
1106 }
1107
1108
1109 /* Save account settings. */
1110 for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
1111
1112 write_account_settings(acc_index, &cfg);
1113
1114 if (acc_index < config->acc_cnt-1)
1115 pj_strcat2(&cfg, "--next-account\n");
1116 }
1117
1118
1119 pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
1120
1121 /* Outbound proxy */
1122 for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
1123 pj_ansi_sprintf(line, "--outbound %.*s\n",
1124 (int)config->cfg.outbound_proxy[i].slen,
1125 config->cfg.outbound_proxy[i].ptr);
1126 pj_strcat2(&cfg, line);
1127 }
1128
1129
1130 /* UDP Transport. */
1131 pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
1132 pj_strcat2(&cfg, line);
1133
Benny Prijono0a5cad82006-09-26 13:21:02 +00001134 /* IP address, if any. */
1135 if (config->udp_cfg.public_addr.slen) {
1136 pj_ansi_sprintf(line, "--ip-addr %.*s\n",
1137 (int)config->udp_cfg.public_addr.slen,
1138 config->udp_cfg.public_addr.ptr);
1139 pj_strcat2(&cfg, line);
1140 }
1141
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001142 /* No TCP ? */
1143 if (config->no_tcp) {
1144 pj_strcat2(&cfg, "--no-tcp\n");
1145 }
1146
1147 /* No UDP ? */
1148 if (config->no_udp) {
1149 pj_strcat2(&cfg, "--no-udp\n");
1150 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001151
1152 /* STUN */
Benny Prijonoebbf6892007-03-24 17:37:25 +00001153 if (config->cfg.stun_domain.slen) {
1154 pj_ansi_sprintf(line, "--stun-domain %.*s\n",
1155 (int)config->cfg.stun_domain.slen,
1156 config->cfg.stun_domain.ptr);
1157 pj_strcat2(&cfg, line);
1158 }
1159 if (config->cfg.stun_host.slen) {
Benny Prijonoc97608e2007-03-23 16:34:20 +00001160 pj_ansi_sprintf(line, "--stun-srv %.*s\n",
Benny Prijonoebbf6892007-03-24 17:37:25 +00001161 (int)config->cfg.stun_host.slen,
1162 config->cfg.stun_host.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001163 pj_strcat2(&cfg, line);
1164 }
1165
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001166
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001167 /* TLS */
1168 if (config->use_tls)
1169 pj_strcat2(&cfg, "--use-tls\n");
Benny Prijonof3bbc132006-12-25 06:43:59 +00001170 if (config->udp_cfg.tls_setting.ca_list_file.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001171 pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001172 (int)config->udp_cfg.tls_setting.ca_list_file.slen,
1173 config->udp_cfg.tls_setting.ca_list_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001174 pj_strcat2(&cfg, line);
1175 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001176 if (config->udp_cfg.tls_setting.cert_file.slen) {
1177 pj_ansi_sprintf(line, "--tls-cert-file %.*s\n",
1178 (int)config->udp_cfg.tls_setting.cert_file.slen,
1179 config->udp_cfg.tls_setting.cert_file.ptr);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001180 pj_strcat2(&cfg, line);
1181 }
Benny Prijonof3bbc132006-12-25 06:43:59 +00001182 if (config->udp_cfg.tls_setting.privkey_file.slen) {
1183 pj_ansi_sprintf(line, "--tls-privkey-file %.*s\n",
1184 (int)config->udp_cfg.tls_setting.privkey_file.slen,
1185 config->udp_cfg.tls_setting.privkey_file.ptr);
1186 pj_strcat2(&cfg, line);
1187 }
1188
1189 if (config->udp_cfg.tls_setting.password.slen) {
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001190 pj_ansi_sprintf(line, "--tls-password %.*s\n",
Benny Prijonof3bbc132006-12-25 06:43:59 +00001191 (int)config->udp_cfg.tls_setting.password.slen,
1192 config->udp_cfg.tls_setting.password.ptr);
1193 pj_strcat2(&cfg, line);
1194 }
1195
1196 if (config->udp_cfg.tls_setting.verify_server)
1197 pj_strcat2(&cfg, "--tls-verify-server\n");
1198
1199 if (config->udp_cfg.tls_setting.verify_client)
1200 pj_strcat2(&cfg, "--tls-verify-client\n");
1201
1202 if (config->udp_cfg.tls_setting.timeout.sec) {
1203 pj_ansi_sprintf(line, "--tls-neg-timeout %d\n",
Benny Prijonoe960bb52007-01-21 17:53:39 +00001204 (int)config->udp_cfg.tls_setting.timeout.sec);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001205 pj_strcat2(&cfg, line);
1206 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001207
1208 pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
1209
1210
1211 /* Media */
Benny Prijonoc97608e2007-03-23 16:34:20 +00001212 if (config->media_cfg.enable_ice)
1213 pj_strcat2(&cfg, "--use-ice\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001214 if (config->null_audio)
1215 pj_strcat2(&cfg, "--null-audio\n");
1216 if (config->auto_play)
1217 pj_strcat2(&cfg, "--auto-play\n");
1218 if (config->auto_loop)
1219 pj_strcat2(&cfg, "--auto-loop\n");
Benny Prijono7ca96da2006-08-07 12:11:40 +00001220 if (config->auto_conf)
1221 pj_strcat2(&cfg, "--auto-conf\n");
Benny Prijono32e4f492007-01-24 00:44:26 +00001222 for (i=0; i<config->wav_count; ++i) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001223 pj_ansi_sprintf(line, "--play-file %s\n",
Benny Prijono32e4f492007-01-24 00:44:26 +00001224 config->wav_files[i].ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001225 pj_strcat2(&cfg, line);
1226 }
Benny Prijono4af234b2007-01-24 02:02:09 +00001227 for (i=0; i<config->tone_count; ++i) {
1228 pj_ansi_sprintf(line, "--play-tone %d,%d,%d,%d\n",
1229 config->tones[i].freq1, config->tones[i].freq2,
1230 config->tones[i].on_msec, config->tones[i].off_msec);
1231 pj_strcat2(&cfg, line);
1232 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001233 if (config->rec_file.slen) {
1234 pj_ansi_sprintf(line, "--rec-file %s\n",
1235 config->rec_file.ptr);
1236 pj_strcat2(&cfg, line);
1237 }
1238 if (config->auto_rec)
1239 pj_strcat2(&cfg, "--auto-rec\n");
Benny Prijono4e5d5512007-03-06 18:11:30 +00001240 if (config->capture_dev != PJSUA_INVALID_ID) {
1241 pj_ansi_sprintf(line, "--capture-dev %d\n", config->capture_dev);
1242 pj_strcat2(&cfg, line);
1243 }
1244 if (config->playback_dev != PJSUA_INVALID_ID) {
1245 pj_ansi_sprintf(line, "--playback-dev %d\n", config->playback_dev);
1246 pj_strcat2(&cfg, line);
1247 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001248
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001249 /* Media clock rate. */
Benny Prijono70972992006-08-05 11:13:58 +00001250 if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001251 pj_ansi_sprintf(line, "--clock-rate %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001252 config->media_cfg.clock_rate);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001253 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001254 } else {
1255 pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
1256 config->media_cfg.clock_rate);
1257 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001258 }
Benny Prijono70972992006-08-05 11:13:58 +00001259
1260 /* quality */
1261 if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001262 pj_ansi_sprintf(line, "--quality %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001263 config->media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001264 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001265 } else {
1266 pj_ansi_sprintf(line, "#using default --quality %d\n",
1267 config->media_cfg.quality);
1268 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001269 }
Benny Prijono0498d902006-06-19 14:49:14 +00001270
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001271
1272 /* ptime */
Benny Prijono2adfe292007-05-11 10:36:08 +00001273 if (config->media_cfg.ptime) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001274 pj_ansi_sprintf(line, "--ptime %d\n",
Benny Prijono2adfe292007-05-11 10:36:08 +00001275 config->media_cfg.ptime);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001276 pj_strcat2(&cfg, line);
1277 }
1278
Benny Prijono70972992006-08-05 11:13:58 +00001279 /* no-vad */
1280 if (config->media_cfg.no_vad) {
1281 pj_strcat2(&cfg, "--no-vad\n");
1282 }
1283
1284 /* ec-tail */
1285 if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
1286 pj_ansi_sprintf(line, "--ec-tail %d\n",
1287 config->media_cfg.ec_tail_len);
1288 pj_strcat2(&cfg, line);
1289 } else {
1290 pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
1291 config->media_cfg.ec_tail_len);
1292 pj_strcat2(&cfg, line);
1293 }
1294
1295
1296 /* ilbc-mode */
1297 if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
1298 pj_ansi_sprintf(line, "--ilbc-mode %d\n",
1299 config->media_cfg.ilbc_mode);
1300 pj_strcat2(&cfg, line);
1301 } else {
1302 pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
1303 config->media_cfg.ilbc_mode);
1304 pj_strcat2(&cfg, line);
1305 }
1306
1307 /* RTP drop */
1308 if (config->media_cfg.tx_drop_pct) {
1309 pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
1310 config->media_cfg.tx_drop_pct);
1311 pj_strcat2(&cfg, line);
1312
1313 }
1314 if (config->media_cfg.rx_drop_pct) {
1315 pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
1316 config->media_cfg.rx_drop_pct);
1317 pj_strcat2(&cfg, line);
1318
1319 }
1320
1321
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001322 /* Start RTP port. */
1323 pj_ansi_sprintf(line, "--rtp-port %d\n",
1324 config->rtp_cfg.port);
1325 pj_strcat2(&cfg, line);
1326
1327 /* Add codec. */
1328 for (i=0; i<config->codec_cnt; ++i) {
1329 pj_ansi_sprintf(line, "--add-codec %s\n",
1330 config->codec_arg[i].ptr);
1331 pj_strcat2(&cfg, line);
1332 }
1333
1334 pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
1335
1336 /* Auto-answer. */
1337 if (config->auto_answer != 0) {
1338 pj_ansi_sprintf(line, "--auto-answer %d\n",
1339 config->auto_answer);
1340 pj_strcat2(&cfg, line);
1341 }
1342
1343 /* Max calls. */
1344 pj_ansi_sprintf(line, "--max-calls %d\n",
1345 config->cfg.max_calls);
1346 pj_strcat2(&cfg, line);
1347
1348 /* Uas-duration. */
Benny Prijono804ff0a2006-09-14 11:17:48 +00001349 if (config->duration != NO_LIMIT) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001350 pj_ansi_sprintf(line, "--duration %d\n",
1351 config->duration);
1352 pj_strcat2(&cfg, line);
1353 }
1354
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001355 /* norefersub ? */
1356 if (config->no_refersub) {
1357 pj_strcat2(&cfg, "--norefersub\n");
1358 }
1359
1360
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001361 pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
1362
1363 /* Add buddies. */
1364 for (i=0; i<config->buddy_cnt; ++i) {
1365 pj_ansi_sprintf(line, "--add-buddy %.*s\n",
1366 (int)config->buddy_cfg[i].uri.slen,
1367 config->buddy_cfg[i].uri.ptr);
1368 pj_strcat2(&cfg, line);
1369 }
1370
1371
1372 *(cfg.ptr + cfg.slen) = '\0';
1373 return cfg.slen;
1374}
1375
1376
1377/*
1378 * Dump application states.
1379 */
1380static void app_dump(pj_bool_t detail)
1381{
Benny Prijonoda9785b2007-04-02 20:43:06 +00001382 pjsua_dump(detail);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001383}
1384
1385
1386/*****************************************************************************
1387 * Console application
1388 */
1389
1390/*
1391 * Find next call when current call is disconnected or when user
1392 * press ']'
1393 */
1394static pj_bool_t find_next_call(void)
1395{
1396 int i, max;
1397
1398 max = pjsua_call_get_max_count();
1399 for (i=current_call+1; i<max; ++i) {
1400 if (pjsua_call_is_active(i)) {
1401 current_call = i;
1402 return PJ_TRUE;
1403 }
1404 }
1405
1406 for (i=0; i<current_call; ++i) {
1407 if (pjsua_call_is_active(i)) {
1408 current_call = i;
1409 return PJ_TRUE;
1410 }
1411 }
1412
1413 current_call = PJSUA_INVALID_ID;
1414 return PJ_FALSE;
1415}
1416
1417
1418/*
1419 * Find previous call when user press '['
1420 */
1421static pj_bool_t find_prev_call(void)
1422{
1423 int i, max;
1424
1425 max = pjsua_call_get_max_count();
1426 for (i=current_call-1; i>=0; --i) {
1427 if (pjsua_call_is_active(i)) {
1428 current_call = i;
1429 return PJ_TRUE;
1430 }
1431 }
1432
1433 for (i=max-1; i>current_call; --i) {
1434 if (pjsua_call_is_active(i)) {
1435 current_call = i;
1436 return PJ_TRUE;
1437 }
1438 }
1439
1440 current_call = PJSUA_INVALID_ID;
1441 return PJ_FALSE;
1442}
1443
1444
Benny Prijono804ff0a2006-09-14 11:17:48 +00001445/* Callback from timer when the maximum call duration has been
1446 * exceeded.
1447 */
1448static void call_timeout_callback(pj_timer_heap_t *timer_heap,
1449 struct pj_timer_entry *entry)
1450{
1451 pjsua_call_id call_id = entry->id;
1452 pjsua_msg_data msg_data;
1453 pjsip_generic_string_hdr warn;
1454 pj_str_t hname = pj_str("Warning");
1455 pj_str_t hvalue = pj_str("399 pjsua \"Call duration exceeded\"");
1456
1457 PJ_UNUSED_ARG(timer_heap);
1458
Benny Prijono148c9dd2006-09-19 13:37:53 +00001459 if (call_id == PJSUA_INVALID_ID) {
1460 PJ_LOG(1,(THIS_FILE, "Invalid call ID in timer callback"));
1461 return;
1462 }
1463
Benny Prijono804ff0a2006-09-14 11:17:48 +00001464 /* Add warning header */
1465 pjsua_msg_data_init(&msg_data);
1466 pjsip_generic_string_hdr_init2(&warn, &hname, &hvalue);
1467 pj_list_push_back(&msg_data.hdr_list, &warn);
1468
1469 /* Call duration has been exceeded; disconnect the call */
1470 PJ_LOG(3,(THIS_FILE, "Duration (%d seconds) has been exceeded "
1471 "for call %d, disconnecting the call",
1472 app_config.duration, call_id));
1473 entry->id = PJSUA_INVALID_ID;
1474 pjsua_call_hangup(call_id, 200, NULL, &msg_data);
1475}
1476
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001477
1478/*
1479 * Handler when invite state has changed.
1480 */
1481static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
1482{
1483 pjsua_call_info call_info;
1484
1485 PJ_UNUSED_ARG(e);
1486
1487 pjsua_call_get_info(call_id, &call_info);
1488
1489 if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
1490
Benny Prijono804ff0a2006-09-14 11:17:48 +00001491 /* Cancel duration timer, if any */
1492 if (app_config.call_data[call_id].timer.id != PJSUA_INVALID_ID) {
1493 struct call_data *cd = &app_config.call_data[call_id];
1494 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1495
1496 cd->timer.id = PJSUA_INVALID_ID;
1497 pjsip_endpt_cancel_timer(endpt, &cd->timer);
1498 }
1499
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001500 PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",
1501 call_id,
1502 call_info.last_status,
1503 call_info.last_status_text.ptr));
1504
1505 if (call_id == current_call) {
1506 find_next_call();
1507 }
1508
Benny Prijono4be63b52006-11-25 14:50:25 +00001509 /* Dump media state upon disconnected */
1510 if (1) {
1511 char buf[1024];
1512 pjsua_call_dump(call_id, PJ_TRUE, buf,
1513 sizeof(buf), " ");
1514 PJ_LOG(5,(THIS_FILE,
1515 "Call %d disconnected, dumping media stats\n%s",
1516 call_id, buf));
1517 }
1518
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001519 } else {
1520
Benny Prijono804ff0a2006-09-14 11:17:48 +00001521 if (app_config.duration!=NO_LIMIT &&
1522 call_info.state == PJSIP_INV_STATE_CONFIRMED)
1523 {
1524 /* Schedule timer to hangup call after the specified duration */
1525 struct call_data *cd = &app_config.call_data[call_id];
1526 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1527 pj_time_val delay;
1528
1529 cd->timer.id = call_id;
1530 delay.sec = app_config.duration;
1531 delay.msec = 0;
1532 pjsip_endpt_schedule_timer(endpt, &cd->timer, &delay);
1533 }
1534
Benny Prijono4be63b52006-11-25 14:50:25 +00001535 if (call_info.state == PJSIP_INV_STATE_EARLY) {
1536 int code;
1537 pj_str_t reason;
1538 pjsip_msg *msg;
1539
1540 /* This can only occur because of TX or RX message */
1541 pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
1542
1543 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
1544 msg = e->body.tsx_state.src.rdata->msg_info.msg;
1545 } else {
1546 msg = e->body.tsx_state.src.tdata->msg;
1547 }
1548
1549 code = msg->line.status.code;
1550 reason = msg->line.status.reason;
1551
1552 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)",
1553 call_id, call_info.state_text.ptr,
1554 code, (int)reason.slen, reason.ptr));
1555 } else {
1556 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s",
1557 call_id,
1558 call_info.state_text.ptr));
1559 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001560
1561 if (current_call==PJSUA_INVALID_ID)
1562 current_call = call_id;
1563
1564 }
1565}
1566
1567
1568/**
1569 * Handler when there is incoming call.
1570 */
1571static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
1572 pjsip_rx_data *rdata)
1573{
1574 pjsua_call_info call_info;
1575
1576 PJ_UNUSED_ARG(acc_id);
1577 PJ_UNUSED_ARG(rdata);
1578
1579 pjsua_call_get_info(call_id, &call_info);
1580
1581 if (app_config.auto_answer > 0) {
1582 pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL);
1583 }
1584
1585 if (app_config.auto_answer < 200) {
1586 PJ_LOG(3,(THIS_FILE,
1587 "Incoming call for account %d!\n"
1588 "From: %s\n"
1589 "To: %s\n"
1590 "Press a to answer or h to reject call",
1591 acc_id,
1592 call_info.remote_info.ptr,
1593 call_info.local_info.ptr));
1594 }
1595}
1596
1597
1598/*
Benny Prijonofeb69f42007-10-05 09:12:26 +00001599 * Handler when a transaction within a call has changed state.
1600 */
1601static void on_call_tsx_state(pjsua_call_id call_id,
1602 pjsip_transaction *tsx,
1603 pjsip_event *e)
1604{
1605 const pjsip_method info_method =
1606 {
1607 PJSIP_OTHER_METHOD,
1608 { "INFO", 4 }
1609 };
1610
1611 if (pjsip_method_cmp(&tsx->method, &info_method)==0) {
1612 /*
1613 * Handle INFO method.
1614 */
1615 if (tsx->role == PJSIP_ROLE_UAC &&
1616 (tsx->state == PJSIP_TSX_STATE_COMPLETED ||
1617 tsx->state == PJSIP_TSX_STATE_TERMINATED &&
1618 e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED))
1619 {
1620 /* Status of outgoing INFO request */
1621 if (tsx->status_code >= 200 && tsx->status_code < 300) {
1622 PJ_LOG(4,(THIS_FILE,
1623 "Call %d: DTMF sent successfully with INFO",
1624 call_id));
1625 } else if (tsx->status_code >= 300) {
1626 PJ_LOG(4,(THIS_FILE,
1627 "Call %d: Failed to send DTMF with INFO: %d/%.*s",
1628 call_id,
1629 tsx->status_code,
1630 (int)tsx->status_text.slen,
1631 tsx->status_text.ptr));
1632 }
1633 } else if (tsx->role == PJSIP_ROLE_UAS &&
1634 tsx->state == PJSIP_TSX_STATE_TRYING)
1635 {
1636 /* Answer incoming INFO with 200/OK */
1637 pjsip_rx_data *rdata;
1638 pjsip_tx_data *tdata;
1639 pj_status_t status;
1640
1641 rdata = e->body.tsx_state.src.rdata;
1642
1643 if (rdata->msg_info.msg->body) {
1644 status = pjsip_endpt_create_response(tsx->endpt, rdata,
1645 200, NULL, &tdata);
1646 if (status == PJ_SUCCESS)
1647 status = pjsip_tsx_send_msg(tsx, tdata);
1648
1649 PJ_LOG(3,(THIS_FILE, "Call %d: incoming INFO:\n%.*s",
1650 call_id,
1651 (int)rdata->msg_info.msg->body->len,
1652 rdata->msg_info.msg->body->data));
1653 } else {
1654 status = pjsip_endpt_create_response(tsx->endpt, rdata,
1655 400, NULL, &tdata);
1656 if (status == PJ_SUCCESS)
1657 status = pjsip_tsx_send_msg(tsx, tdata);
1658 }
1659 }
1660 }
1661}
1662
1663
1664/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001665 * Callback on media state changed event.
1666 * The action may connect the call to sound device, to file, or
1667 * to loop the call.
1668 */
1669static void on_call_media_state(pjsua_call_id call_id)
1670{
1671 pjsua_call_info call_info;
1672
1673 pjsua_call_get_info(call_id, &call_info);
1674
1675 if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
1676 pj_bool_t connect_sound = PJ_TRUE;
1677
1678 /* Loopback sound, if desired */
1679 if (app_config.auto_loop) {
1680 pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
1681 connect_sound = PJ_FALSE;
Benny Prijono1ebd6142006-10-19 15:48:02 +00001682
1683 /* Automatically record conversation, if desired */
1684 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1685 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1686 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001687 }
1688
1689 /* Stream a file, if desired */
1690 if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) {
1691 pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
1692 connect_sound = PJ_FALSE;
1693 }
1694
Benny Prijono7ca96da2006-08-07 12:11:40 +00001695 /* Put call in conference with other calls, if desired */
1696 if (app_config.auto_conf) {
1697 pjsua_call_id call_ids[PJSUA_MAX_CALLS];
Benny Prijono10861bc2006-08-07 13:22:43 +00001698 unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
Benny Prijono7ca96da2006-08-07 12:11:40 +00001699 unsigned i;
1700
1701 /* Get all calls, and establish media connection between
1702 * this call and other calls.
1703 */
1704 pjsua_enum_calls(call_ids, &call_cnt);
Benny Prijono10861bc2006-08-07 13:22:43 +00001705
Benny Prijono7ca96da2006-08-07 12:11:40 +00001706 for (i=0; i<call_cnt; ++i) {
1707 if (call_ids[i] == call_id)
1708 continue;
1709
1710 if (!pjsua_call_has_media(call_ids[i]))
1711 continue;
1712
1713 pjsua_conf_connect(call_info.conf_slot,
1714 pjsua_call_get_conf_port(call_ids[i]));
1715 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1716 call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001717
1718 /* Automatically record conversation, if desired */
1719 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1720 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1721 app_config.rec_port);
1722 }
1723
Benny Prijono7ca96da2006-08-07 12:11:40 +00001724 }
1725
1726 /* Also connect call to local sound device */
1727 connect_sound = PJ_TRUE;
1728 }
1729
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001730 /* Otherwise connect to sound device */
1731 if (connect_sound) {
1732 pjsua_conf_connect(call_info.conf_slot, 0);
1733 pjsua_conf_connect(0, call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001734
1735 /* Automatically record conversation, if desired */
1736 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1737 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1738 pjsua_conf_connect(0, app_config.rec_port);
1739 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001740 }
1741
1742 PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
1743
1744 } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) {
1745 PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
1746 call_id));
1747 } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) {
1748 PJ_LOG(3,(THIS_FILE,
1749 "Media for call %d is suspended (hold) by remote",
1750 call_id));
Benny Prijono096c56c2007-09-15 08:30:16 +00001751 } else if (call_info.media_status == PJSUA_CALL_MEDIA_ERROR) {
1752 pj_str_t reason = pj_str("ICE negotiation failed");
1753
1754 PJ_LOG(1,(THIS_FILE,
1755 "Media has reported error, disconnecting call"));
1756
1757 pjsua_call_hangup(call_id, 500, &reason, NULL);
1758
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001759 } else {
1760 PJ_LOG(3,(THIS_FILE,
1761 "Media for call %d is inactive",
1762 call_id));
1763 }
1764}
1765
Benny Prijono0875ae82006-12-26 00:11:48 +00001766/*
1767 * DTMF callback.
1768 */
1769static void call_on_dtmf_callback(pjsua_call_id call_id, int dtmf)
1770{
1771 PJ_LOG(3,(THIS_FILE, "Incoming DTMF on call %d: %c", call_id, dtmf));
1772}
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001773
1774/*
1775 * Handler registration status has changed.
1776 */
1777static void on_reg_state(pjsua_acc_id acc_id)
1778{
1779 PJ_UNUSED_ARG(acc_id);
1780
1781 // Log already written.
1782}
1783
1784
1785/*
1786 * Handler on buddy state changed.
1787 */
1788static void on_buddy_state(pjsua_buddy_id buddy_id)
1789{
1790 pjsua_buddy_info info;
1791 pjsua_buddy_get_info(buddy_id, &info);
1792
1793 PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
1794 (int)info.uri.slen,
1795 info.uri.ptr,
1796 (int)info.status_text.slen,
1797 info.status_text.ptr));
1798}
1799
1800
1801/**
1802 * Incoming IM message (i.e. MESSAGE request)!
1803 */
1804static void on_pager(pjsua_call_id call_id, const pj_str_t *from,
1805 const pj_str_t *to, const pj_str_t *contact,
1806 const pj_str_t *mime_type, const pj_str_t *text)
1807{
1808 /* Note: call index may be -1 */
1809 PJ_UNUSED_ARG(call_id);
1810 PJ_UNUSED_ARG(to);
1811 PJ_UNUSED_ARG(contact);
1812 PJ_UNUSED_ARG(mime_type);
1813
Benny Prijonof4b538d2007-05-14 16:45:20 +00001814 PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s (%.*s)",
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001815 (int)from->slen, from->ptr,
Benny Prijonof4b538d2007-05-14 16:45:20 +00001816 (int)text->slen, text->ptr,
1817 (int)mime_type->slen, mime_type->ptr));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001818}
1819
1820
1821/**
1822 * Received typing indication
1823 */
1824static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
1825 const pj_str_t *to, const pj_str_t *contact,
1826 pj_bool_t is_typing)
1827{
1828 PJ_UNUSED_ARG(call_id);
1829 PJ_UNUSED_ARG(to);
1830 PJ_UNUSED_ARG(contact);
1831
1832 PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
1833 (int)from->slen, from->ptr,
1834 (is_typing?"is typing..":"has stopped typing")));
1835}
1836
1837
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001838/**
1839 * Call transfer request status.
1840 */
1841static void on_call_transfer_status(pjsua_call_id call_id,
1842 int status_code,
1843 const pj_str_t *status_text,
1844 pj_bool_t final,
1845 pj_bool_t *p_cont)
1846{
1847 PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s",
1848 call_id, status_code,
1849 (int)status_text->slen, status_text->ptr,
1850 (final ? "[final]" : "")));
1851
1852 if (status_code/100 == 2) {
1853 PJ_LOG(3,(THIS_FILE,
1854 "Call %d: call transfered successfully, disconnecting call",
1855 call_id));
1856 pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL);
1857 *p_cont = PJ_FALSE;
1858 }
1859}
1860
1861
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001862/*
Benny Prijonof7b1c392006-11-11 16:46:34 +00001863 * Notification that call is being replaced.
1864 */
1865static void on_call_replaced(pjsua_call_id old_call_id,
1866 pjsua_call_id new_call_id)
1867{
1868 pjsua_call_info old_ci, new_ci;
1869
1870 pjsua_call_get_info(old_call_id, &old_ci);
1871 pjsua_call_get_info(new_call_id, &new_ci);
1872
1873 PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by "
1874 "call %d with %.*s",
1875 old_call_id,
1876 (int)old_ci.remote_info.slen, old_ci.remote_info.ptr,
1877 new_call_id,
1878 (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
1879}
1880
1881
1882/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001883 * Print buddy list.
1884 */
1885static void print_buddy_list(void)
1886{
1887 pjsua_buddy_id ids[64];
1888 int i;
1889 unsigned count = PJ_ARRAY_SIZE(ids);
1890
1891 puts("Buddy list:");
1892
1893 pjsua_enum_buddies(ids, &count);
1894
1895 if (count == 0)
1896 puts(" -none-");
1897 else {
1898 for (i=0; i<(int)count; ++i) {
1899 pjsua_buddy_info info;
1900
1901 if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
1902 continue;
1903
Benny Prijono4461c7d2007-08-25 13:36:15 +00001904 printf(" [%2d] <%.*s> %.*s\n",
1905 ids[i]+1,
1906 (int)info.status_text.slen,
1907 info.status_text.ptr,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001908 (int)info.uri.slen,
1909 info.uri.ptr);
1910 }
1911 }
1912 puts("");
1913}
1914
1915
1916/*
1917 * Print account status.
1918 */
1919static void print_acc_status(int acc_id)
1920{
1921 char buf[80];
1922 pjsua_acc_info info;
1923
1924 pjsua_acc_get_info(acc_id, &info);
1925
1926 if (!info.has_registration) {
1927 pj_ansi_snprintf(buf, sizeof(buf), "%.*s",
1928 (int)info.status_text.slen,
1929 info.status_text.ptr);
1930
1931 } else {
1932 pj_ansi_snprintf(buf, sizeof(buf),
1933 "%d/%.*s (expires=%d)",
1934 info.status,
1935 (int)info.status_text.slen,
1936 info.status_text.ptr,
1937 info.expires);
1938
1939 }
1940
1941 printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
1942 acc_id, (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
Benny Prijono4461c7d2007-08-25 13:36:15 +00001943 printf(" Online status: %.*s\n",
1944 (int)info.online_status_text.slen,
1945 info.online_status_text.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001946}
1947
1948
1949/*
1950 * Show a bit of help.
1951 */
1952static void keystroke_help(void)
1953{
1954 pjsua_acc_id acc_ids[16];
1955 unsigned count = PJ_ARRAY_SIZE(acc_ids);
1956 int i;
1957
1958 printf(">>>>\n");
1959
1960 pjsua_enum_accs(acc_ids, &count);
1961
1962 printf("Account list:\n");
1963 for (i=0; i<(int)count; ++i)
1964 print_acc_status(acc_ids[i]);
1965
1966 print_buddy_list();
1967
1968 //puts("Commands:");
1969 puts("+=============================================================================+");
1970 puts("| Call Commands: | Buddy, IM & Presence: | Account: |");
1971 puts("| | | |");
1972 puts("| m Make new call | +b Add new buddy .| +a Add new accnt |");
1973 puts("| M Make multiple calls | -b Delete buddy | -a Delete accnt. |");
Benny Prijono4461c7d2007-08-25 13:36:15 +00001974 puts("| a Answer call | i Send IM | !a Modify accnt. |");
1975 puts("| h Hangup call (ha=all) | s Subscribe presence | rr (Re-)register |");
1976 puts("| H Hold call | u Unsubscribe presence | ru Unregister |");
1977 puts("| v re-inVite (release hold) | t ToGgle Online status | > Cycle next ac.|");
Benny Prijonoc08682e2007-10-04 06:17:58 +00001978 puts("| U send UPDATE | T Set online status | < Cycle prev ac.|");
1979 puts("| ],[ Select next/prev call +--------------------------+-------------------+");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001980 puts("| x Xfer call | Media Commands: | Status & Config: |");
Benny Prijonof7b1c392006-11-11 16:46:34 +00001981 puts("| X Xfer with Replaces | | |");
Benny Prijonoc08682e2007-10-04 06:17:58 +00001982 puts("| # Send RFC 2833 DTMF | cl List ports | d Dump status |");
1983 puts("| * Send DTMF with INFO | cc Connect port | dd Dump detailed |");
1984 puts("| dq Dump curr. call quality | cd Disconnect port | dc Dump config |");
1985 puts("| | V Adjust audio Volume | f Save config |");
1986 puts("| S Send arbitrary REQUEST | Cp Codec priorities | f Save config |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001987 puts("+------------------------------+--------------------------+-------------------+");
Benny Prijono990042e2007-01-21 19:36:00 +00001988 puts("| q QUIT sleep N: console sleep for N ms |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001989 puts("+=============================================================================+");
Benny Prijono48af79c2006-07-22 12:49:17 +00001990
1991 i = pjsua_call_get_count();
1992 printf("You have %d active call%s\n", i, (i>1?"s":""));
Benny Prijonof7b1c392006-11-11 16:46:34 +00001993
1994 if (current_call != PJSUA_INVALID_ID) {
1995 pjsua_call_info ci;
1996 if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS)
1997 printf("Current call id=%d to %.*s [%.*s]\n", current_call,
1998 (int)ci.remote_info.slen, ci.remote_info.ptr,
1999 (int)ci.state_text.slen, ci.state_text.ptr);
2000 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002001}
2002
2003
2004/*
2005 * Input simple string
2006 */
2007static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
2008{
2009 char *p;
2010
2011 printf("%s (empty to cancel): ", title); fflush(stdout);
2012 fgets(buf, len, stdin);
2013
2014 /* Remove trailing newlines. */
2015 for (p=buf; ; ++p) {
2016 if (*p=='\r' || *p=='\n') *p='\0';
2017 else if (!*p) break;
2018 }
2019
2020 if (!*buf)
2021 return PJ_FALSE;
2022
2023 return PJ_TRUE;
2024}
2025
2026
2027#define NO_NB -2
2028struct input_result
2029{
2030 int nb_result;
2031 char *uri_result;
2032};
2033
2034
2035/*
2036 * Input URL.
2037 */
2038static void ui_input_url(const char *title, char *buf, int len,
2039 struct input_result *result)
2040{
2041 result->nb_result = NO_NB;
2042 result->uri_result = NULL;
2043
2044 print_buddy_list();
2045
2046 printf("Choices:\n"
2047 " 0 For current dialog.\n"
2048 " -1 All %d buddies in buddy list\n"
2049 " [1 -%2d] Select from buddy list\n"
2050 " URL An URL\n"
2051 " <Enter> Empty input (or 'q') to cancel\n"
2052 , pjsua_get_buddy_count(), pjsua_get_buddy_count());
2053 printf("%s: ", title);
2054
2055 fflush(stdout);
2056 fgets(buf, len, stdin);
2057 len = strlen(buf);
2058
2059 /* Left trim */
2060 while (pj_isspace(*buf)) {
2061 ++buf;
2062 --len;
2063 }
2064
2065 /* Remove trailing newlines */
2066 while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
2067 buf[--len] = '\0';
2068
2069 if (len == 0 || buf[0]=='q')
2070 return;
2071
2072 if (pj_isdigit(*buf) || *buf=='-') {
2073
2074 int i;
2075
2076 if (*buf=='-')
2077 i = 1;
2078 else
2079 i = 0;
2080
2081 for (; i<len; ++i) {
2082 if (!pj_isdigit(buf[i])) {
2083 puts("Invalid input");
2084 return;
2085 }
2086 }
2087
2088 result->nb_result = my_atoi(buf);
2089
2090 if (result->nb_result >= 0 &&
2091 result->nb_result <= (int)pjsua_get_buddy_count())
2092 {
2093 return;
2094 }
2095 if (result->nb_result == -1)
2096 return;
2097
2098 puts("Invalid input");
2099 result->nb_result = NO_NB;
2100 return;
2101
2102 } else {
2103 pj_status_t status;
2104
2105 if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
2106 pjsua_perror(THIS_FILE, "Invalid URL", status);
2107 return;
2108 }
2109
2110 result->uri_result = buf;
2111 }
2112}
2113
2114/*
2115 * List the ports in conference bridge
2116 */
2117static void conf_list(void)
2118{
2119 unsigned i, count;
2120 pjsua_conf_port_id id[PJSUA_MAX_CALLS];
2121
2122 printf("Conference ports:\n");
2123
2124 count = PJ_ARRAY_SIZE(id);
2125 pjsua_enum_conf_ports(id, &count);
2126
2127 for (i=0; i<count; ++i) {
2128 char txlist[PJSUA_MAX_CALLS*4+10];
2129 unsigned j;
2130 pjsua_conf_port_info info;
2131
2132 pjsua_conf_get_port_info(id[i], &info);
2133
2134 txlist[0] = '\0';
2135 for (j=0; j<info.listener_cnt; ++j) {
2136 char s[10];
2137 pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
2138 pj_ansi_strcat(txlist, s);
2139 }
2140 printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n",
2141 info.slot_id,
2142 info.clock_rate/1000,
2143 info.samples_per_frame * 1000 / info.clock_rate,
2144 (int)info.name.slen,
2145 info.name.ptr,
2146 txlist);
2147
2148 }
2149 puts("");
2150}
2151
2152
2153/*
Benny Prijono56315612006-07-18 14:39:40 +00002154 * Send arbitrary request to remote host
2155 */
2156static void send_request(char *cstr_method, const pj_str_t *dst_uri)
2157{
2158 pj_str_t str_method;
2159 pjsip_method method;
2160 pjsip_tx_data *tdata;
Benny Prijono56315612006-07-18 14:39:40 +00002161 pjsip_endpoint *endpt;
2162 pj_status_t status;
2163
2164 endpt = pjsua_get_pjsip_endpt();
2165
2166 str_method = pj_str(cstr_method);
2167 pjsip_method_init_np(&method, &str_method);
2168
Benny Prijonofff245c2007-04-02 11:44:47 +00002169 status = pjsua_acc_create_request(current_acc, &method, dst_uri, &tdata);
Benny Prijono56315612006-07-18 14:39:40 +00002170
2171 status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
2172 if (status != PJ_SUCCESS) {
2173 pjsua_perror(THIS_FILE, "Unable to send request", status);
Benny Prijono56315612006-07-18 14:39:40 +00002174 return;
2175 }
2176}
2177
2178
2179/*
Benny Prijono4461c7d2007-08-25 13:36:15 +00002180 * Change extended online status.
2181 */
2182static void change_online_status(void)
2183{
2184 char menuin[32];
2185 pj_bool_t online_status;
2186 pjrpid_element elem;
2187 int i, choice;
2188
2189 enum {
2190 AVAILABLE, BUSY, OTP, IDLE, AWAY, BRB, OFFLINE, OPT_MAX
2191 };
2192
2193 struct opt {
2194 int id;
2195 char *name;
2196 } opts[] = {
2197 { AVAILABLE, "Available" },
2198 { BUSY, "Busy"},
2199 { OTP, "On the phone"},
2200 { IDLE, "Idle"},
2201 { AWAY, "Away"},
2202 { BRB, "Be right back"},
2203 { OFFLINE, "Offline"}
2204 };
2205
2206 printf("\n"
2207 "Choices:\n");
2208 for (i=0; i<PJ_ARRAY_SIZE(opts); ++i) {
2209 printf(" %d %s\n", opts[i].id+1, opts[i].name);
2210 }
2211
2212 if (!simple_input("Select status", menuin, sizeof(menuin)))
2213 return;
2214
2215 choice = atoi(menuin) - 1;
2216 if (choice < 0 || choice >= OPT_MAX) {
2217 puts("Invalid selection");
2218 return;
2219 }
2220
2221 pj_bzero(&elem, sizeof(elem));
2222 elem.type = PJRPID_ELEMENT_TYPE_PERSON;
2223
2224 online_status = PJ_TRUE;
2225
2226 switch (choice) {
2227 case AVAILABLE:
2228 break;
2229 case BUSY:
2230 elem.activity = PJRPID_ACTIVITY_BUSY;
2231 elem.note = pj_str("Busy");
2232 break;
2233 case OTP:
2234 elem.activity = PJRPID_ACTIVITY_BUSY;
2235 elem.note = pj_str("On the phone");
2236 break;
2237 case IDLE:
2238 elem.activity = PJRPID_ACTIVITY_UNKNOWN;
2239 elem.note = pj_str("Idle");
2240 break;
2241 case AWAY:
2242 elem.activity = PJRPID_ACTIVITY_AWAY;
2243 elem.note = pj_str("Away");
2244 break;
2245 case BRB:
2246 elem.activity = PJRPID_ACTIVITY_UNKNOWN;
2247 elem.note = pj_str("Be right back");
2248 break;
2249 case OFFLINE:
2250 online_status = PJ_FALSE;
2251 break;
2252 }
2253
2254 pjsua_acc_set_online_status2(current_acc, online_status, &elem);
2255}
2256
2257
2258/*
Benny Prijonoc08682e2007-10-04 06:17:58 +00002259 * Change codec priorities.
2260 */
2261static void manage_codec_prio(void)
2262{
2263 pjsua_codec_info c[32];
2264 unsigned i, count = PJ_ARRAY_SIZE(c);
2265 char input[32];
2266 char *codec, *prio;
2267 pj_str_t id;
2268 int new_prio;
2269 pj_status_t status;
2270
2271 printf("List of codecs:\n");
2272
2273 pjsua_enum_codecs(c, &count);
2274 for (i=0; i<count; ++i) {
2275 printf(" %d\t%.*s\n", c[i].priority, (int)c[i].codec_id.slen,
2276 c[i].codec_id.ptr);
2277 }
2278
2279 puts("");
2280 puts("Enter codec name and its new priority (e.g. \"speex/16000 200\"), empty to cancel:");
2281
2282 printf("Codec name and priority: ");
2283 fgets(input, sizeof(input), stdin);
2284 if (input[0]=='\r' || input[0]=='\n') {
2285 puts("Done");
2286 return;
2287 }
2288
2289 codec = strtok(input, " \t\r\n");
2290 prio = strtok(NULL, " \r\n");
2291
2292 if (!codec || !prio) {
2293 puts("Invalid input");
2294 return;
2295 }
2296
2297 new_prio = atoi(prio);
2298 if (new_prio < 0)
2299 new_prio = 0;
2300 else if (new_prio > PJMEDIA_CODEC_PRIO_HIGHEST)
2301 new_prio = PJMEDIA_CODEC_PRIO_HIGHEST;
2302
2303 status = pjsua_codec_set_priority(pj_cstr(&id, codec),
2304 (pj_uint8_t)new_prio);
2305 if (status != PJ_SUCCESS)
2306 pjsua_perror(THIS_FILE, "Error setting codec priority", status);
2307}
2308
2309
2310/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002311 * Main "user interface" loop.
2312 */
2313void console_app_main(const pj_str_t *uri_to_call)
2314{
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002315 char menuin[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002316 char buf[128];
2317 char text[128];
2318 int i, count;
2319 char *uri;
2320 pj_str_t tmp;
2321 struct input_result result;
2322 pjsua_call_info call_info;
2323 pjsua_acc_info acc_info;
2324
2325
2326 /* If user specifies URI to call, then call the URI */
2327 if (uri_to_call->slen) {
2328 pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, NULL);
2329 }
2330
2331 keystroke_help();
2332
2333 for (;;) {
2334
2335 printf(">>> ");
2336 fflush(stdout);
2337
Benny Prijono990042e2007-01-21 19:36:00 +00002338 if (fgets(menuin, sizeof(menuin), stdin) == NULL) {
2339 /*
2340 * Be friendly to users who redirect commands into
2341 * program, when file ends, resume with kbd.
2342 * If exit is desired end script with q for quit
2343 */
2344 /* Reopen stdin/stdout/stderr to /dev/console */
2345#if defined(PJ_WIN32) && PJ_WIN32!=0
2346 if (freopen ("CONIN$", "r", stdin) == NULL) {
2347#else
2348 if (1) {
2349#endif
2350 puts("Cannot switch back to console from file redirection");
2351 menuin[0] = 'q';
2352 menuin[1] = '\0';
2353 } else {
2354 puts("Switched back to console from file redirection");
2355 continue;
2356 }
2357 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002358
2359 switch (menuin[0]) {
2360
2361 case 'm':
2362 /* Make call! : */
2363 printf("(You currently have %d calls)\n",
2364 pjsua_call_get_count());
2365
2366 uri = NULL;
2367 ui_input_url("Make call", buf, sizeof(buf), &result);
2368 if (result.nb_result != NO_NB) {
2369
2370 if (result.nb_result == -1 || result.nb_result == 0) {
2371 puts("You can't do that with make call!");
2372 continue;
2373 } else {
2374 pjsua_buddy_info binfo;
2375 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2376 uri = binfo.uri.ptr;
2377 }
2378
2379 } else if (result.uri_result) {
2380 uri = result.uri_result;
2381 }
2382
2383 tmp = pj_str(uri);
2384 pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL);
2385 break;
2386
2387 case 'M':
2388 /* Make multiple calls! : */
2389 printf("(You currently have %d calls)\n",
2390 pjsua_call_get_count());
2391
2392 if (!simple_input("Number of calls", menuin, sizeof(menuin)))
2393 continue;
2394
2395 count = my_atoi(menuin);
2396 if (count < 1)
2397 continue;
2398
2399 ui_input_url("Make call", buf, sizeof(buf), &result);
2400 if (result.nb_result != NO_NB) {
2401 pjsua_buddy_info binfo;
2402 if (result.nb_result == -1 || result.nb_result == 0) {
2403 puts("You can't do that with make call!");
2404 continue;
2405 }
2406 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2407 uri = binfo.uri.ptr;
2408 } else {
2409 uri = result.uri_result;
2410 }
2411
2412 for (i=0; i<my_atoi(menuin); ++i) {
2413 pj_status_t status;
2414
2415 tmp = pj_str(uri);
2416 status = pjsua_call_make_call(current_acc, &tmp, 0, NULL,
2417 NULL, NULL);
2418 if (status != PJ_SUCCESS)
2419 break;
2420 }
2421 break;
2422
2423 case 'i':
2424 /* Send instant messaeg */
2425
2426 /* i is for call index to send message, if any */
2427 i = -1;
2428
2429 /* Make compiler happy. */
2430 uri = NULL;
2431
2432 /* Input destination. */
2433 ui_input_url("Send IM to", buf, sizeof(buf), &result);
2434 if (result.nb_result != NO_NB) {
2435
2436 if (result.nb_result == -1) {
2437 puts("You can't send broadcast IM like that!");
2438 continue;
2439
2440 } else if (result.nb_result == 0) {
2441
2442 i = current_call;
2443
2444 } else {
2445 pjsua_buddy_info binfo;
2446 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2447 uri = binfo.uri.ptr;
2448 }
2449
2450 } else if (result.uri_result) {
2451 uri = result.uri_result;
2452 }
2453
2454
2455 /* Send typing indication. */
2456 if (i != -1)
2457 pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
2458 else {
2459 pj_str_t tmp_uri = pj_str(uri);
2460 pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
2461 }
2462
2463 /* Input the IM . */
2464 if (!simple_input("Message", text, sizeof(text))) {
2465 /*
2466 * Cancelled.
2467 * Send typing notification too, saying we're not typing.
2468 */
2469 if (i != -1)
2470 pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
2471 else {
2472 pj_str_t tmp_uri = pj_str(uri);
2473 pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
2474 }
2475 continue;
2476 }
2477
2478 tmp = pj_str(text);
2479
2480 /* Send the IM */
2481 if (i != -1)
2482 pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
2483 else {
2484 pj_str_t tmp_uri = pj_str(uri);
2485 pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
2486 }
2487
2488 break;
2489
2490 case 'a':
2491
2492 if (current_call != -1) {
2493 pjsua_call_get_info(current_call, &call_info);
2494 } else {
2495 /* Make compiler happy */
2496 call_info.role = PJSIP_ROLE_UAC;
2497 call_info.state = PJSIP_INV_STATE_DISCONNECTED;
2498 }
2499
2500 if (current_call == -1 ||
2501 call_info.role != PJSIP_ROLE_UAS ||
2502 call_info.state >= PJSIP_INV_STATE_CONNECTING)
2503 {
2504 puts("No pending incoming call");
2505 fflush(stdout);
2506 continue;
2507
2508 } else {
Benny Prijono20d36722007-02-22 14:52:24 +00002509 int st_code;
2510 char contact[120];
2511 pj_str_t hname = { "Contact", 7 };
2512 pj_str_t hvalue;
2513 pjsip_generic_string_hdr hcontact;
2514 pjsua_msg_data msg_data;
2515
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002516 if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
2517 continue;
2518
Benny Prijono20d36722007-02-22 14:52:24 +00002519 st_code = my_atoi(buf);
2520 if (st_code < 100)
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002521 continue;
2522
Benny Prijono20d36722007-02-22 14:52:24 +00002523 pjsua_msg_data_init(&msg_data);
2524
2525 if (st_code/100 == 3) {
2526 if (!simple_input("Enter URL to be put in Contact",
2527 contact, sizeof(contact)))
2528 continue;
2529 hvalue = pj_str(contact);
2530 pjsip_generic_string_hdr_init2(&hcontact, &hname, &hvalue);
2531
2532 pj_list_push_back(&msg_data.hdr_list, &hcontact);
2533 }
2534
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002535 /*
2536 * Must check again!
2537 * Call may have been disconnected while we're waiting for
2538 * keyboard input.
2539 */
2540 if (current_call == -1) {
2541 puts("Call has been disconnected");
2542 fflush(stdout);
2543 continue;
2544 }
2545
Benny Prijono20d36722007-02-22 14:52:24 +00002546 pjsua_call_answer(current_call, st_code, NULL, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002547 }
2548
2549 break;
2550
2551
2552 case 'h':
2553
2554 if (current_call == -1) {
2555 puts("No current call");
2556 fflush(stdout);
2557 continue;
2558
2559 } else if (menuin[1] == 'a') {
2560
2561 /* Hangup all calls */
2562 pjsua_call_hangup_all();
2563
2564 } else {
2565
2566 /* Hangup current calls */
2567 pjsua_call_hangup(current_call, 0, NULL, NULL);
2568 }
2569 break;
2570
2571 case ']':
2572 case '[':
2573 /*
2574 * Cycle next/prev dialog.
2575 */
2576 if (menuin[0] == ']') {
2577 find_next_call();
2578
2579 } else {
2580 find_prev_call();
2581 }
2582
2583 if (current_call != -1) {
2584
2585 pjsua_call_get_info(current_call, &call_info);
2586 PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s",
2587 (int)call_info.remote_info.slen,
2588 call_info.remote_info.ptr));
2589
2590 } else {
2591 PJ_LOG(3,(THIS_FILE,"No current dialog"));
2592 }
2593 break;
2594
2595
2596 case '>':
2597 case '<':
2598 if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
2599 break;
2600
2601 i = my_atoi(buf);
2602 if (pjsua_acc_is_valid(i)) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00002603 pjsua_acc_set_default(i);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002604 PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
2605 } else {
2606 PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
2607 }
2608 break;
2609
2610
2611 case '+':
2612 if (menuin[1] == 'b') {
2613
2614 pjsua_buddy_config buddy_cfg;
2615 pjsua_buddy_id buddy_id;
2616 pj_status_t status;
2617
2618 if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
2619 break;
2620
2621 if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) {
2622 printf("Invalid SIP URI '%s'\n", buf);
2623 break;
2624 }
2625
Benny Prijonoac623b32006-07-03 15:19:31 +00002626 pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002627
2628 buddy_cfg.uri = pj_str(buf);
2629 buddy_cfg.subscribe = PJ_TRUE;
2630
2631 status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
2632 if (status == PJ_SUCCESS) {
2633 printf("New buddy '%s' added at index %d\n",
2634 buf, buddy_id+1);
2635 }
2636
2637 } else if (menuin[1] == 'a') {
2638
Benny Prijonofb2b3652007-06-28 07:15:03 +00002639 char id[80], registrar[80], realm[80], uname[80], passwd[30];
2640 pjsua_acc_config acc_cfg;
2641 pj_status_t status;
2642
2643 if (!simple_input("Your SIP URL:", id, sizeof(id)))
2644 break;
2645 if (!simple_input("URL of the registrar:", registrar, sizeof(registrar)))
2646 break;
2647 if (!simple_input("Auth Realm:", realm, sizeof(realm)))
2648 break;
2649 if (!simple_input("Auth Username:", uname, sizeof(uname)))
2650 break;
2651 if (!simple_input("Auth Password:", passwd, sizeof(passwd)))
2652 break;
2653
2654 pjsua_acc_config_default(&acc_cfg);
2655 acc_cfg.id = pj_str(id);
2656 acc_cfg.reg_uri = pj_str(registrar);
2657 acc_cfg.cred_count = 1;
2658 acc_cfg.cred_info[0].scheme = pj_str("digest");
2659 acc_cfg.cred_info[0].realm = pj_str(realm);
2660 acc_cfg.cred_info[0].username = pj_str(uname);
2661 acc_cfg.cred_info[0].data_type = 0;
2662 acc_cfg.cred_info[0].data = pj_str(passwd);
2663
2664 status = pjsua_acc_add(&acc_cfg, PJ_TRUE, NULL);
2665 if (status != PJ_SUCCESS) {
2666 pjsua_perror(THIS_FILE, "Error adding new account", status);
2667 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002668
2669 } else {
2670 printf("Invalid input %s\n", menuin);
2671 }
2672 break;
2673
2674 case '-':
2675 if (menuin[1] == 'b') {
2676 if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
2677 break;
2678
2679 i = my_atoi(buf) - 1;
2680
2681 if (!pjsua_buddy_is_valid(i)) {
2682 printf("Invalid buddy id %d\n", i);
2683 } else {
2684 pjsua_buddy_del(i);
2685 printf("Buddy %d deleted\n", i);
2686 }
2687
2688 } else if (menuin[1] == 'a') {
2689
2690 if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
2691 break;
2692
2693 i = my_atoi(buf);
2694
2695 if (!pjsua_acc_is_valid(i)) {
2696 printf("Invalid account id %d\n", i);
2697 } else {
2698 pjsua_acc_del(i);
2699 printf("Account %d deleted\n", i);
2700 }
2701
2702 } else {
2703 printf("Invalid input %s\n", menuin);
2704 }
2705 break;
2706
2707 case 'H':
2708 /*
2709 * Hold call.
2710 */
2711 if (current_call != -1) {
2712
2713 pjsua_call_set_hold(current_call, NULL);
2714
2715 } else {
2716 PJ_LOG(3,(THIS_FILE, "No current call"));
2717 }
2718 break;
2719
2720 case 'v':
2721 /*
2722 * Send re-INVITE (to release hold, etc).
2723 */
2724 if (current_call != -1) {
2725
2726 pjsua_call_reinvite(current_call, PJ_TRUE, NULL);
2727
2728 } else {
2729 PJ_LOG(3,(THIS_FILE, "No current call"));
2730 }
2731 break;
2732
Benny Prijonoc08682e2007-10-04 06:17:58 +00002733 case 'U':
2734 /*
2735 * Send UPDATE
2736 */
2737 if (current_call != -1) {
2738
2739 pjsua_call_update(current_call, 0, NULL);
2740
2741 } else {
2742 PJ_LOG(3,(THIS_FILE, "No current call"));
2743 }
2744 break;
2745
2746 case 'C':
2747 if (menuin[1] == 'p') {
2748 manage_codec_prio();
2749 }
2750 break;
2751
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002752 case 'x':
2753 /*
2754 * Transfer call.
2755 */
2756 if (current_call == -1) {
2757
2758 PJ_LOG(3,(THIS_FILE, "No current call"));
2759
2760 } else {
2761 int call = current_call;
Benny Prijonod524e822006-09-22 12:48:18 +00002762 pjsua_msg_data msg_data;
2763 pjsip_generic_string_hdr refer_sub;
2764 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2765 pj_str_t STR_FALSE = { "false", 5 };
Benny Prijonof7b1c392006-11-11 16:46:34 +00002766 pjsua_call_info ci;
2767
2768 pjsua_call_get_info(current_call, &ci);
2769 printf("Transfering current call [%d] %.*s\n",
2770 current_call,
2771 (int)ci.remote_info.slen, ci.remote_info.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002772
2773 ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
2774
2775 /* Check if call is still there. */
2776
2777 if (call != current_call) {
2778 puts("Call has been disconnected");
2779 continue;
2780 }
2781
Benny Prijonod524e822006-09-22 12:48:18 +00002782 pjsua_msg_data_init(&msg_data);
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002783 if (app_config.no_refersub) {
2784 /* Add Refer-Sub: false in outgoing REFER request */
2785 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2786 &STR_FALSE);
2787 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2788 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002789 if (result.nb_result != NO_NB) {
2790 if (result.nb_result == -1 || result.nb_result == 0)
2791 puts("You can't do that with transfer call!");
2792 else {
2793 pjsua_buddy_info binfo;
2794 pjsua_buddy_get_info(result.nb_result-1, &binfo);
Benny Prijonod524e822006-09-22 12:48:18 +00002795 pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002796 }
2797
2798 } else if (result.uri_result) {
2799 pj_str_t tmp;
2800 tmp = pj_str(result.uri_result);
Benny Prijonod524e822006-09-22 12:48:18 +00002801 pjsua_call_xfer( current_call, &tmp, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002802 }
2803 }
2804 break;
2805
Benny Prijonof7b1c392006-11-11 16:46:34 +00002806 case 'X':
2807 /*
2808 * Transfer call with replaces.
2809 */
2810 if (current_call == -1) {
2811
2812 PJ_LOG(3,(THIS_FILE, "No current call"));
2813
2814 } else {
2815 int call = current_call;
2816 int dst_call;
2817 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 };
2821 pjsua_call_id ids[PJSUA_MAX_CALLS];
2822 pjsua_call_info ci;
2823 unsigned i, count;
2824
2825 count = PJ_ARRAY_SIZE(ids);
2826 pjsua_enum_calls(ids, &count);
2827
2828 if (count <= 1) {
2829 puts("There are no other calls");
2830 continue;
2831 }
2832
2833 pjsua_call_get_info(current_call, &ci);
2834 printf("Transfer call [%d] %.*s to one of the following:\n",
2835 current_call,
2836 (int)ci.remote_info.slen, ci.remote_info.ptr);
2837
2838 for (i=0; i<count; ++i) {
2839 pjsua_call_info call_info;
2840
2841 if (ids[i] == call)
2842 continue;
2843
2844 pjsua_call_get_info(ids[i], &call_info);
2845 printf("%d %.*s [%.*s]\n",
2846 ids[i],
2847 (int)call_info.remote_info.slen,
2848 call_info.remote_info.ptr,
2849 (int)call_info.state_text.slen,
2850 call_info.state_text.ptr);
2851 }
2852
2853 if (!simple_input("Enter call number to be replaced",
2854 buf, sizeof(buf)))
2855 continue;
2856
2857 dst_call = my_atoi(buf);
2858
2859 /* Check if call is still there. */
2860
2861 if (call != current_call) {
2862 puts("Call has been disconnected");
2863 continue;
2864 }
2865
2866 /* Check that destination call is valid. */
2867 if (dst_call == call) {
2868 puts("Destination call number must not be the same "
2869 "as the call being transfered");
2870 continue;
2871 }
2872 if (dst_call >= PJSUA_MAX_CALLS) {
2873 puts("Invalid destination call number");
2874 continue;
2875 }
2876 if (!pjsua_call_is_active(dst_call)) {
2877 puts("Invalid destination call number");
2878 continue;
2879 }
2880
2881 pjsua_msg_data_init(&msg_data);
2882 if (app_config.no_refersub) {
2883 /* Add Refer-Sub: false in outgoing REFER request */
2884 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2885 &STR_FALSE);
2886 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2887 }
2888
2889 pjsua_call_xfer_replaces(call, dst_call, 0, &msg_data);
2890 }
2891 break;
2892
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002893 case '#':
2894 /*
2895 * Send DTMF strings.
2896 */
2897 if (current_call == -1) {
2898
2899 PJ_LOG(3,(THIS_FILE, "No current call"));
2900
2901 } else if (!pjsua_call_has_media(current_call)) {
2902
2903 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
2904
2905 } else {
2906 pj_str_t digits;
2907 int call = current_call;
2908 pj_status_t status;
2909
2910 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
2911 sizeof(buf)))
2912 {
2913 break;
2914 }
2915
2916 if (call != current_call) {
2917 puts("Call has been disconnected");
2918 continue;
2919 }
2920
2921 digits = pj_str(buf);
2922 status = pjsua_call_dial_dtmf(current_call, &digits);
2923 if (status != PJ_SUCCESS) {
2924 pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
2925 } else {
2926 puts("DTMF digits enqueued for transmission");
2927 }
2928 }
2929 break;
2930
Benny Prijonofeb69f42007-10-05 09:12:26 +00002931 case '*':
2932 /* Send DTMF with INFO */
2933 if (current_call == -1) {
2934
2935 PJ_LOG(3,(THIS_FILE, "No current call"));
2936
2937 } else {
2938 const pj_str_t SIP_INFO = pj_str("INFO");
2939 pj_str_t digits;
2940 int call = current_call;
2941 int i;
2942 pj_status_t status;
2943
2944 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
2945 sizeof(buf)))
2946 {
2947 break;
2948 }
2949
2950 if (call != current_call) {
2951 puts("Call has been disconnected");
2952 continue;
2953 }
2954
2955 digits = pj_str(buf);
2956 for (i=0; i<digits.slen; ++i) {
2957 pjsua_msg_data msg_data;
2958 char body[80];
2959
2960 pjsua_msg_data_init(&msg_data);
2961 msg_data.content_type = pj_str("application/dtmf-relay");
2962
2963 pj_ansi_snprintf(body, sizeof(body),
2964 "Signal=%c\r\n"
2965 "Duration=160",
2966 buf[i]);
2967 msg_data.msg_body = pj_str(body);
2968
2969 status = pjsua_call_send_request(current_call, &SIP_INFO,
2970 &msg_data);
2971 if (status != PJ_SUCCESS) {
2972 break;
2973 }
2974 }
2975 }
2976 break;
2977
Benny Prijono56315612006-07-18 14:39:40 +00002978 case 'S':
2979 /*
2980 * Send arbitrary request
2981 */
2982 if (pjsua_acc_get_count() == 0) {
2983 puts("Sorry, need at least one account configured");
2984 break;
2985 }
2986
2987 puts("Send arbitrary request to remote host");
2988
2989 /* Input METHOD */
2990 if (!simple_input("Request method:",text,sizeof(text)))
2991 break;
2992
2993 /* Input destination URI */
2994 uri = NULL;
2995 ui_input_url("Destination URI", buf, sizeof(buf), &result);
2996 if (result.nb_result != NO_NB) {
2997
2998 if (result.nb_result == -1 || result.nb_result == 0) {
2999 puts("Sorry you can't do that!");
3000 continue;
3001 } else {
3002 pjsua_buddy_info binfo;
3003 pjsua_buddy_get_info(result.nb_result-1, &binfo);
3004 uri = binfo.uri.ptr;
3005 }
3006
3007 } else if (result.uri_result) {
3008 uri = result.uri_result;
3009 }
3010
3011 tmp = pj_str(uri);
3012
3013 send_request(text, &tmp);
3014 break;
3015
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003016 case 's':
Benny Prijono990042e2007-01-21 19:36:00 +00003017 if (pj_ansi_strnicmp(menuin, "sleep", 5)==0) {
3018 pj_str_t tmp;
3019 int delay;
3020
3021 tmp.ptr = menuin+6;
3022 tmp.slen = pj_ansi_strlen(menuin)-7;
3023
3024 if (tmp.slen < 1) {
3025 puts("Usage: sleep MSEC");
3026 break;
3027 }
3028
3029 delay = pj_strtoul(&tmp);
3030 if (delay < 0) delay = 0;
3031 pj_thread_sleep(delay);
3032 break;
3033 }
3034 /* Continue below */
3035
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003036 case 'u':
3037 /*
3038 * Subscribe/unsubscribe presence.
3039 */
3040 ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
3041 if (result.nb_result != NO_NB) {
3042 if (result.nb_result == -1) {
3043 int i, count;
3044 count = pjsua_get_buddy_count();
3045 for (i=0; i<count; ++i)
3046 pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
3047 } else if (result.nb_result == 0) {
3048 puts("Sorry, can only subscribe to buddy's presence, "
3049 "not from existing call");
3050 } else {
3051 pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
3052 }
3053
3054 } else if (result.uri_result) {
3055 puts("Sorry, can only subscribe to buddy's presence, "
3056 "not arbitrary URL (for now)");
3057 }
3058
3059 break;
3060
3061 case 'r':
3062 switch (menuin[1]) {
3063 case 'r':
3064 /*
3065 * Re-Register.
3066 */
3067 pjsua_acc_set_registration(current_acc, PJ_TRUE);
3068 break;
3069 case 'u':
3070 /*
3071 * Unregister
3072 */
3073 pjsua_acc_set_registration(current_acc, PJ_FALSE);
3074 break;
3075 }
3076 break;
3077
3078 case 't':
3079 pjsua_acc_get_info(current_acc, &acc_info);
3080 acc_info.online_status = !acc_info.online_status;
3081 pjsua_acc_set_online_status(current_acc, acc_info.online_status);
3082 printf("Setting %s online status to %s\n",
3083 acc_info.acc_uri.ptr,
3084 (acc_info.online_status?"online":"offline"));
3085 break;
3086
Benny Prijono4461c7d2007-08-25 13:36:15 +00003087 case 'T':
3088 change_online_status();
3089 break;
3090
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003091 case 'c':
3092 switch (menuin[1]) {
3093 case 'l':
3094 conf_list();
3095 break;
3096 case 'c':
3097 case 'd':
3098 {
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003099 char tmp[10], src_port[10], dst_port[10];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003100 pj_status_t status;
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003101 int cnt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003102 const char *src_title, *dst_title;
3103
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003104 cnt = sscanf(menuin, "%s %s %s", tmp, src_port, dst_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003105
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003106 if (cnt != 3) {
3107 conf_list();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003108
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003109 src_title = (menuin[1]=='c'?
3110 "Connect src port #":
3111 "Disconnect src port #");
3112 dst_title = (menuin[1]=='c'?
3113 "To dst port #":
3114 "From dst port #");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003115
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00003116 if (!simple_input(src_title, src_port, sizeof(src_port)))
3117 break;
3118
3119 if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
3120 break;
3121 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003122
3123 if (menuin[1]=='c') {
3124 status = pjsua_conf_connect(my_atoi(src_port),
3125 my_atoi(dst_port));
3126 } else {
3127 status = pjsua_conf_disconnect(my_atoi(src_port),
3128 my_atoi(dst_port));
3129 }
3130 if (status == PJ_SUCCESS) {
3131 puts("Success");
3132 } else {
3133 puts("ERROR!!");
3134 }
3135 }
3136 break;
3137 }
3138 break;
3139
Benny Prijono6dd967c2006-12-26 02:27:14 +00003140 case 'V':
3141 /* Adjust audio volume */
3142 sprintf(buf, "Adjust mic level: [%4.1fx] ", app_config.mic_level);
3143 if (simple_input(buf,text,sizeof(text))) {
3144 char *err;
3145 app_config.mic_level = (float)strtod(text, &err);
3146 pjsua_conf_adjust_rx_level(0, app_config.mic_level);
3147 }
3148 sprintf(buf, "Adjust speaker level: [%4.1fx] ",
3149 app_config.speaker_level);
3150 if (simple_input(buf,text,sizeof(text))) {
3151 char *err;
3152 app_config.speaker_level = (float)strtod(text, &err);
3153 pjsua_conf_adjust_tx_level(0, app_config.speaker_level);
3154 }
3155
3156 break;
3157
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003158 case 'd':
3159 if (menuin[1] == 'c') {
3160 char settings[2000];
3161 int len;
3162
3163 len = write_settings(&app_config, settings, sizeof(settings));
3164 if (len < 1)
3165 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
3166 else
3167 PJ_LOG(3,(THIS_FILE,
3168 "Dumping configuration (%d bytes):\n%s\n",
3169 len, settings));
Benny Prijono819506c2006-06-22 22:29:51 +00003170
3171 } else if (menuin[1] == 'q') {
3172
3173 if (current_call != PJSUA_INVALID_ID) {
3174 char buf[1024];
3175 pjsua_call_dump(current_call, PJ_TRUE, buf,
3176 sizeof(buf), " ");
3177 PJ_LOG(3,(THIS_FILE, "\n%s", buf));
3178 } else {
3179 PJ_LOG(3,(THIS_FILE, "No current call"));
3180 }
3181
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003182 } else {
3183 app_dump(menuin[1]=='d');
3184 }
3185 break;
3186
3187
3188 case 'f':
3189 if (simple_input("Enter output filename", buf, sizeof(buf))) {
3190 char settings[2000];
3191 int len;
3192
3193 len = write_settings(&app_config, settings, sizeof(settings));
3194 if (len < 1)
3195 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
3196 else {
3197 pj_oshandle_t fd;
3198 pj_status_t status;
3199
3200 status = pj_file_open(app_config.pool, buf,
3201 PJ_O_WRONLY, &fd);
3202 if (status != PJ_SUCCESS) {
3203 pjsua_perror(THIS_FILE, "Unable to open file", status);
3204 } else {
3205 pj_ssize_t size = len;
3206 pj_file_write(fd, settings, &size);
3207 pj_file_close(fd);
3208
3209 printf("Settings successfully written to '%s'\n", buf);
3210 }
3211 }
3212
3213 }
3214 break;
3215
3216
3217 case 'q':
3218 goto on_exit;
3219
3220
3221 default:
3222 if (menuin[0] != '\n' && menuin[0] != '\r') {
3223 printf("Invalid input %s", menuin);
3224 }
3225 keystroke_help();
3226 break;
3227 }
3228 }
3229
3230on_exit:
3231 ;
3232}
3233
3234
3235/*****************************************************************************
3236 * Public API
3237 */
3238
3239pj_status_t app_init(int argc, char *argv[])
3240{
Benny Prijonoe93e2872006-06-28 16:46:49 +00003241 pjsua_transport_id transport_id = -1;
Benny Prijonoe347cb02007-02-14 14:36:13 +00003242 pjsua_transport_config tcp_cfg;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003243 unsigned i;
3244 pj_status_t status;
3245
3246 /* Create pjsua */
3247 status = pjsua_create();
3248 if (status != PJ_SUCCESS)
3249 return status;
3250
3251 /* Create pool for application */
Benny Prijonod5696da2007-07-17 16:25:45 +00003252 app_config.pool = pjsua_pool_create("pjsua", 1000, 1000);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003253
3254 /* Initialize default config */
3255 default_config(&app_config);
3256
3257 /* Parse the arguments */
3258 status = parse_args(argc, argv, &app_config, &uri_arg);
3259 if (status != PJ_SUCCESS)
3260 return status;
3261
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003262 /* Initialize application callbacks */
3263 app_config.cfg.cb.on_call_state = &on_call_state;
3264 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
3265 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
Benny Prijonofeb69f42007-10-05 09:12:26 +00003266 app_config.cfg.cb.on_call_tsx_state = &on_call_tsx_state;
Benny Prijono0875ae82006-12-26 00:11:48 +00003267 app_config.cfg.cb.on_dtmf_digit = &call_on_dtmf_callback;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003268 app_config.cfg.cb.on_reg_state = &on_reg_state;
3269 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
3270 app_config.cfg.cb.on_pager = &on_pager;
3271 app_config.cfg.cb.on_typing = &on_typing;
Benny Prijono4ddad2c2006-10-18 17:16:34 +00003272 app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
Benny Prijonof7b1c392006-11-11 16:46:34 +00003273 app_config.cfg.cb.on_call_replaced = &on_call_replaced;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003274
3275 /* Initialize pjsua */
3276 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
3277 &app_config.media_cfg);
3278 if (status != PJ_SUCCESS)
3279 return status;
3280
Benny Prijonoe909eac2006-07-27 22:04:56 +00003281#ifdef STEREO_DEMO
3282 stereo_demo();
3283#endif
3284
Benny Prijono804ff0a2006-09-14 11:17:48 +00003285 /* Initialize calls data */
3286 for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
3287 app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
3288 app_config.call_data[i].timer.cb = &call_timeout_callback;
3289 }
3290
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003291 /* Optionally registers WAV file */
Benny Prijono32e4f492007-01-24 00:44:26 +00003292 for (i=0; i<app_config.wav_count; ++i) {
3293 pjsua_player_id wav_id;
3294
3295 status = pjsua_player_create(&app_config.wav_files[i], 0,
3296 &wav_id);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003297 if (status != PJ_SUCCESS)
3298 goto on_error;
Benny Prijono22a300a2006-06-14 20:04:55 +00003299
Benny Prijono72c04d22007-02-17 22:20:58 +00003300 if (app_config.wav_id == PJSUA_INVALID_ID) {
Benny Prijono32e4f492007-01-24 00:44:26 +00003301 app_config.wav_id = wav_id;
3302 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
3303 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003304 }
3305
Benny Prijono4af234b2007-01-24 02:02:09 +00003306 /* Optionally registers tone players */
3307 for (i=0; i<app_config.tone_count; ++i) {
3308 pjmedia_port *tport;
3309 char name[80];
3310 pj_str_t label;
3311 pj_status_t status;
3312
3313 pj_ansi_snprintf(name, sizeof(name), "tone-%d,%d",
3314 app_config.tones[i].freq1,
3315 app_config.tones[i].freq2);
3316 label = pj_str(name);
3317 status = pjmedia_tonegen_create2(app_config.pool, &label,
3318 8000, 1, 160, 16,
3319 PJMEDIA_TONEGEN_LOOP, &tport);
3320 if (status != PJ_SUCCESS) {
3321 pjsua_perror(THIS_FILE, "Unable to create tone generator", status);
3322 goto on_error;
3323 }
3324
3325 status = pjsua_conf_add_port(app_config.pool, tport,
3326 &app_config.tone_slots[i]);
3327 pj_assert(status == PJ_SUCCESS);
3328
3329 status = pjmedia_tonegen_play(tport, 1, &app_config.tones[i], 0);
3330 pj_assert(status == PJ_SUCCESS);
3331 }
3332
Benny Prijono1ebd6142006-10-19 15:48:02 +00003333 /* Optionally create recorder file, if any. */
3334 if (app_config.rec_file.slen) {
3335 status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
3336 &app_config.rec_id);
3337 if (status != PJ_SUCCESS)
3338 goto on_error;
3339
3340 app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
3341 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003342
Benny Prijonoe347cb02007-02-14 14:36:13 +00003343 pj_memcpy(&tcp_cfg, &app_config.udp_cfg, sizeof(tcp_cfg));
3344
Benny Prijono87ef89a2007-01-14 00:39:45 +00003345 /* Add UDP transport unless it's disabled. */
3346 if (!app_config.no_udp) {
3347 pjsua_acc_id aid;
3348
3349 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
3350 &app_config.udp_cfg,
3351 &transport_id);
3352 if (status != PJ_SUCCESS)
3353 goto on_error;
3354
3355 /* Add local account */
3356 pjsua_acc_add_local(transport_id, PJ_TRUE, &aid);
3357 //pjsua_acc_set_transport(aid, transport_id);
3358 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
Benny Prijonoe347cb02007-02-14 14:36:13 +00003359
3360 if (app_config.udp_cfg.port == 0) {
3361 pjsua_transport_info ti;
3362 pj_sockaddr_in *a;
3363
3364 pjsua_transport_get_info(transport_id, &ti);
3365 a = (pj_sockaddr_in*)&ti.local_addr;
3366
3367 tcp_cfg.port = pj_ntohs(a->sin_port);
3368 }
Benny Prijono87ef89a2007-01-14 00:39:45 +00003369 }
3370
Benny Prijonoe93e2872006-06-28 16:46:49 +00003371 /* Add TCP transport unless it's disabled */
3372 if (!app_config.no_tcp) {
3373 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
Benny Prijonoe347cb02007-02-14 14:36:13 +00003374 &tcp_cfg,
Benny Prijonoe93e2872006-06-28 16:46:49 +00003375 &transport_id);
3376 if (status != PJ_SUCCESS)
3377 goto on_error;
3378
3379 /* Add local account */
Benny Prijono21b9ad92006-08-15 13:11:22 +00003380 pjsua_acc_add_local(transport_id, PJ_TRUE, NULL);
Benny Prijonoe93e2872006-06-28 16:46:49 +00003381 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
3382
3383 }
3384
Benny Prijonoe93e2872006-06-28 16:46:49 +00003385
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003386#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
3387 /* Add TLS transport when application wants one */
3388 if (app_config.use_tls) {
Benny Prijonof3bbc132006-12-25 06:43:59 +00003389
3390 pjsua_acc_id acc_id;
3391
3392 /* Set TLS port as TCP port+1 */
Benny Prijonoafc47be2007-02-14 14:44:55 +00003393 tcp_cfg.port++;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003394 status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
Benny Prijonoafc47be2007-02-14 14:44:55 +00003395 &tcp_cfg,
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003396 &transport_id);
Benny Prijonoafc47be2007-02-14 14:44:55 +00003397 tcp_cfg.port--;
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003398 if (status != PJ_SUCCESS)
3399 goto on_error;
Benny Prijonof3bbc132006-12-25 06:43:59 +00003400
3401 /* Add local account */
3402 pjsua_acc_add_local(transport_id, PJ_FALSE, &acc_id);
3403 pjsua_acc_set_online_status(acc_id, PJ_TRUE);
Benny Prijono6e0e54b2006-12-08 21:58:31 +00003404 }
3405#endif
3406
Benny Prijonoe93e2872006-06-28 16:46:49 +00003407 if (transport_id == -1) {
3408 PJ_LOG(3,(THIS_FILE, "Error: no transport is configured"));
3409 status = -1;
3410 goto on_error;
3411 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003412
3413
3414 /* Add accounts */
3415 for (i=0; i<app_config.acc_cnt; ++i) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00003416 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003417 if (status != PJ_SUCCESS)
3418 goto on_error;
3419 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
3420 }
3421
3422 /* Add buddies */
3423 for (i=0; i<app_config.buddy_cnt; ++i) {
3424 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
3425 if (status != PJ_SUCCESS)
3426 goto on_error;
3427 }
3428
3429 /* Optionally set codec orders */
3430 for (i=0; i<app_config.codec_cnt; ++i) {
3431 pjsua_codec_set_priority(&app_config.codec_arg[i],
3432 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
3433 }
3434
3435 /* Add RTP transports */
3436 status = pjsua_media_transports_create(&app_config.rtp_cfg);
3437 if (status != PJ_SUCCESS)
3438 goto on_error;
3439
Benny Prijono22a300a2006-06-14 20:04:55 +00003440 /* Use null sound device? */
Benny Prijonoe909eac2006-07-27 22:04:56 +00003441#ifndef STEREO_DEMO
Benny Prijono22a300a2006-06-14 20:04:55 +00003442 if (app_config.null_audio) {
3443 status = pjsua_set_null_snd_dev();
3444 if (status != PJ_SUCCESS)
3445 return status;
3446 }
Benny Prijonoe909eac2006-07-27 22:04:56 +00003447#endif
Benny Prijono22a300a2006-06-14 20:04:55 +00003448
Benny Prijono4e5d5512007-03-06 18:11:30 +00003449 if (app_config.capture_dev != PJSUA_INVALID_ID
3450 || app_config.playback_dev != PJSUA_INVALID_ID) {
3451 status
3452 = pjsua_set_snd_dev(app_config.capture_dev, app_config.playback_dev);
3453 if (status != PJ_SUCCESS)
3454 goto on_error;
3455 }
3456
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003457 return PJ_SUCCESS;
3458
3459on_error:
Benny Prijonoad2e0ca2007-04-29 12:31:51 +00003460 app_destroy();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003461 return status;
3462}
3463
3464
3465pj_status_t app_main(void)
3466{
3467 pj_status_t status;
3468
3469 /* Start pjsua */
3470 status = pjsua_start();
3471 if (status != PJ_SUCCESS) {
Benny Prijonoad2e0ca2007-04-29 12:31:51 +00003472 app_destroy();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003473 return status;
3474 }
3475
3476 console_app_main(&uri_arg);
3477
3478 return PJ_SUCCESS;
3479}
3480
3481pj_status_t app_destroy(void)
3482{
Benny Prijonof762ee72006-12-01 11:14:37 +00003483 pj_status_t status;
Benny Prijono4af234b2007-01-24 02:02:09 +00003484 unsigned i;
Benny Prijonof762ee72006-12-01 11:14:37 +00003485
Benny Prijonoe909eac2006-07-27 22:04:56 +00003486#ifdef STEREO_DEMO
3487 if (app_config.snd) {
3488 pjmedia_snd_port_destroy(app_config.snd);
3489 app_config.snd = NULL;
3490 }
3491#endif
3492
Benny Prijono4af234b2007-01-24 02:02:09 +00003493 /* Close tone generators */
3494 for (i=0; i<app_config.tone_count; ++i) {
3495 pjsua_conf_remove_port(app_config.tone_slots[i]);
3496 }
3497
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003498 if (app_config.pool) {
3499 pj_pool_release(app_config.pool);
3500 app_config.pool = NULL;
3501 }
3502
Benny Prijonof762ee72006-12-01 11:14:37 +00003503 status = pjsua_destroy();
3504
3505 pj_bzero(&app_config, sizeof(app_config));
3506
3507 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00003508}
Benny Prijonoe909eac2006-07-27 22:04:56 +00003509
3510
3511#ifdef STEREO_DEMO
3512static void stereo_demo()
3513{
3514 pjmedia_port *conf, *splitter, *ch1;
Benny Prijonoe909eac2006-07-27 22:04:56 +00003515 pj_status_t status;
3516
3517 /* Disable existing sound device */
3518 conf = pjsua_set_no_snd_dev();
3519
Benny Prijonoe909eac2006-07-27 22:04:56 +00003520 /* Create stereo-mono splitter/combiner */
3521 status = pjmedia_splitcomb_create(app_config.pool,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003522 conf->info.clock_rate /* clock rate */,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003523 2 /* stereo */,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003524 2 * conf->info.samples_per_frame,
3525 conf->info.bits_per_sample,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003526 0 /* options */,
3527 &splitter);
3528 pj_assert(status == PJ_SUCCESS);
3529
3530 /* Connect channel0 (left channel?) to conference port slot0 */
3531 status = pjmedia_splitcomb_set_channel(splitter, 0 /* ch0 */,
3532 0 /*options*/,
3533 conf);
3534 pj_assert(status == PJ_SUCCESS);
3535
3536 /* Create reverse channel for channel1 (right channel?)... */
3537 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
3538 splitter,
3539 1 /* ch1 */,
3540 0 /* options */,
3541 &ch1);
3542 pj_assert(status == PJ_SUCCESS);
3543
3544 /* .. and register it to conference bridge (it would be slot1
3545 * if there's no other devices connected to the bridge)
3546 */
3547 status = pjsua_conf_add_port(app_config.pool, ch1, NULL);
3548 pj_assert(status == PJ_SUCCESS);
3549
3550 /* Create sound device */
3551 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003552 conf->info.clock_rate,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003553 2 /* stereo */,
Benny Prijono478cf7c2007-06-02 00:12:29 +00003554 2 * conf->info.samples_per_frame,
3555 conf->info.bits_per_sample,
Benny Prijonoe909eac2006-07-27 22:04:56 +00003556 0, &app_config.snd);
3557 pj_assert(status == PJ_SUCCESS);
3558
3559
3560 /* Connect the splitter to the sound device */
3561 status = pjmedia_snd_port_connect(app_config.snd, splitter);
3562 pj_assert(status == PJ_SUCCESS);
3563
3564}
3565#endif
3566