blob: a3156e386b2e53c9d5338c205d6b5c4bb2c75aac [file] [log] [blame]
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsua-lib/pjsua.h>
20
21
22#define THIS_FILE "pjsua.c"
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;
61 pj_str_t wav_file;
62 pjsua_player_id wav_id;
63 pjsua_conf_port_id wav_port;
64 pj_bool_t auto_play;
65 pj_bool_t auto_loop;
Benny Prijono7ca96da2006-08-07 12:11:40 +000066 pj_bool_t auto_conf;
Benny Prijono1ebd6142006-10-19 15:48:02 +000067 pj_str_t rec_file;
68 pj_bool_t auto_rec;
69 pjsua_recorder_id rec_id;
70 pjsua_conf_port_id rec_port;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000071 unsigned ptime;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000072 unsigned auto_answer;
73 unsigned duration;
Benny Prijonoe909eac2006-07-27 22:04:56 +000074
75#ifdef STEREO_DEMO
76 pjmedia_snd_port *snd;
77#endif
78
Benny Prijonoeebe9af2006-06-13 22:57:13 +000079} app_config;
80
81
Benny Prijono21b9ad92006-08-15 13:11:22 +000082//static pjsua_acc_id current_acc;
83#define current_acc pjsua_acc_get_default()
Benny Prijonof7b1c392006-11-11 16:46:34 +000084static pjsua_call_id current_call = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +000085static pj_str_t uri_arg;
86
Benny Prijono594e4c52006-09-14 18:51:01 +000087#ifdef STEREO_DEMO
Benny Prijonoe909eac2006-07-27 22:04:56 +000088static void stereo_demo();
Benny Prijono594e4c52006-09-14 18:51:01 +000089#endif
Benny Prijonoe909eac2006-07-27 22:04:56 +000090
Benny Prijonoeebe9af2006-06-13 22:57:13 +000091/*****************************************************************************
92 * Configuration manipulation
93 */
94
95/* Show usage */
96static void usage(void)
97{
98 puts ("Usage:");
Benny Prijono0a5cad82006-09-26 13:21:02 +000099 puts (" pjsua [options] [SIP URL to call]");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000100 puts ("");
101 puts ("General options:");
Benny Prijono804ff0a2006-09-14 11:17:48 +0000102 puts (" --config-file=file Read the config/arguments from file.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000103 puts (" --help Display this help screen");
104 puts (" --version Display version info");
105 puts ("");
106 puts ("Logging options:");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000107 puts (" --log-file=fname Log to filename (default stderr)");
108 puts (" --log-level=N Set log max level to N (0(none) to 6(trace)) (default=5)");
109 puts (" --app-log-level=N Set log max level for stdout display (default=4)");
110 puts ("");
111 puts ("SIP Account options:");
112 puts (" --registrar=url Set the URL of registrar server");
113 puts (" --id=url Set the URL of local ID (used in From header)");
114 puts (" --contact=url Optionally override the Contact information");
115 puts (" --proxy=url Optional URL of proxy server to visit");
116 puts (" May be specified multiple times");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000117 puts (" --reg-timeout=SEC Optional registration interval (default 55)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000118 puts (" --realm=string Set realm");
119 puts (" --username=string Set authentication username");
120 puts (" --password=string Set authentication password");
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000121 puts (" --publish Send presence PUBLISH for this account");
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000122 puts (" --next-cred Add another credentials");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000123 puts ("");
124 puts ("SIP Account Control:");
125 puts (" --next-account Add more account");
126 puts ("");
127 puts ("Transport Options:");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000128 puts (" --local-port=port Set TCP/UDP port. This implicitly enables both ");
129 puts (" TCP and UDP transports on the specified port, unless");
130 puts (" if TCP or UDP is disabled.");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000131 puts (" --ip-addr=IP Use the specifed address as SIP and RTP addresses.");
132 puts (" (Hint: the IP may be the public IP of the NAT/router)");
Benny Prijonoe93e2872006-06-28 16:46:49 +0000133 puts (" --no-tcp Disable TCP transport.");
134 puts (" --no-udp Disable UDP transport.");
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000135 puts (" --nameserver=NS Add the specified nameserver to enable SRV resolution");
136 puts (" This option can be specified multiple times.");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000137 puts (" --outbound=url Set the URL of global outbound proxy server");
138 puts (" May be specified multiple times");
139 puts (" --use-stun1=host[:port]");
140 puts (" --use-stun2=host[:port] Resolve local IP with the specified STUN servers");
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000141#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
142 puts (" --use-tls Enable TLS transport");
143 puts (" --tls-ca-file Specify TLS CA file");
144 puts (" --tls-key-file Specify TLS client key file");
145 puts (" --tls-password Specify TLS password");
146#endif
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000147 puts ("");
148 puts ("Media Options:");
149 puts (" --add-codec=name Manually add codec (default is to enable all)");
150 puts (" --clock-rate=N Override sound device clock rate");
151 puts (" --null-audio Use NULL audio device");
Benny Prijono1ebd6142006-10-19 15:48:02 +0000152 puts (" --play-file=file Register WAV file in conference bridge");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000153 puts (" --auto-play Automatically play the file (to incoming calls only)");
154 puts (" --auto-loop Automatically loop incoming RTP to outgoing RTP");
Benny Prijono7ca96da2006-08-07 12:11:40 +0000155 puts (" --auto-conf Automatically put calls in conference with others");
Benny Prijono1ebd6142006-10-19 15:48:02 +0000156 puts (" --rec-file=file Open file recorder (extension can be .wav or .mp3");
157 puts (" --auto-rec Automatically record conversation");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000158 puts (" --rtp-port=N Base port to try for RTP (default=4000)");
Benny Prijono00cae612006-07-31 15:19:36 +0000159 puts (" --quality=N Specify media quality (0-10, default=6)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000160 puts (" --ptime=MSEC Override codec ptime to MSEC (default=specific)");
Benny Prijono0a12f002006-07-26 17:05:39 +0000161 puts (" --no-vad Disable VAD/silence detector (default=vad enabled)");
Benny Prijonod79f25c2006-08-02 19:41:37 +0000162 puts (" --ec-tail=MSEC Set echo canceller tail length (default=256)");
Benny Prijono00cae612006-07-31 15:19:36 +0000163 puts (" --ilbc-mode=MODE Set iLBC codec mode (20 or 30, default is 20)");
164 puts (" --rx-drop-pct=PCT Drop PCT percent of RX RTP (for pkt lost sim, default: 0)");
165 puts (" --tx-drop-pct=PCT Drop PCT percent of TX RTP (for pkt lost sim, default: 0)");
166
Benny Prijono0a12f002006-07-26 17:05:39 +0000167
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000168 puts ("");
169 puts ("Buddy List (can be more than one):");
170 puts (" --add-buddy url Add the specified URL to the buddy list.");
171 puts ("");
172 puts ("User Agent options:");
173 puts (" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)");
174 puts (" --max-calls=N Maximum number of concurrent calls (default:4, max:255)");
Benny Prijonof521eb02006-08-06 23:07:25 +0000175 puts (" --thread-cnt=N Number of worker threads (default:1)");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000176 puts (" --duration=SEC Set maximum call duration (default:no limit)");
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000177 puts (" --norefersub Suppress event subscription when transfering calls");
Benny Prijono804ff0a2006-09-14 11:17:48 +0000178
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000179 puts ("");
Benny Prijono0a5cad82006-09-26 13:21:02 +0000180 puts ("When URL is specified, pjsua will immediately initiate call to that URL");
181 puts ("");
182
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000183 fflush(stdout);
184}
185
186
187/* Set default config. */
188static void default_config(struct app_config *cfg)
189{
Benny Prijono56315612006-07-18 14:39:40 +0000190 char tmp[80];
191
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000192 pjsua_config_default(&cfg->cfg);
Benny Prijono56315612006-07-18 14:39:40 +0000193 pj_ansi_sprintf(tmp, "PJSUA v%s/%s", PJ_VERSION, PJ_OS_NAME);
194 pj_strdup2_with_null(app_config.pool, &cfg->cfg.user_agent, tmp);
195
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000196 pjsua_logging_config_default(&cfg->log_cfg);
197 pjsua_media_config_default(&cfg->media_cfg);
198 pjsua_transport_config_default(&cfg->udp_cfg);
199 cfg->udp_cfg.port = 5060;
200 pjsua_transport_config_default(&cfg->rtp_cfg);
201 cfg->rtp_cfg.port = 4000;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000202 cfg->duration = NO_LIMIT;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000203 cfg->wav_id = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000204 cfg->rec_id = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000205 cfg->wav_port = PJSUA_INVALID_ID;
Benny Prijono1ebd6142006-10-19 15:48:02 +0000206 cfg->rec_port = PJSUA_INVALID_ID;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000207}
208
209
210/*
211 * Read command arguments from config file.
212 */
213static int read_config_file(pj_pool_t *pool, const char *filename,
214 int *app_argc, char ***app_argv)
215{
216 int i;
217 FILE *fhnd;
218 char line[200];
219 int argc = 0;
220 char **argv;
221 enum { MAX_ARGS = 64 };
222
223 /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */
224 argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*));
225 argv[argc++] = *app_argv[0];
226
227 /* Open config file. */
228 fhnd = fopen(filename, "rt");
229 if (!fhnd) {
230 PJ_LOG(1,(THIS_FILE, "Unable to open config file %s", filename));
231 fflush(stdout);
232 return -1;
233 }
234
235 /* Scan tokens in the file. */
236 while (argc < MAX_ARGS && !feof(fhnd)) {
237 char *token, *p = line;
238
239 if (fgets(line, sizeof(line), fhnd) == NULL) break;
240
241 for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS;
242 token = strtok(NULL, " \t\r\n"))
243 {
244 int token_len;
245
246 if (!token) break;
247 if (*token == '#') break;
248
249 token_len = strlen(token);
250 if (!token_len)
251 continue;
252 argv[argc] = pj_pool_alloc(pool, token_len+1);
253 pj_memcpy(argv[argc], token, token_len+1);
254 ++argc;
255 }
256 }
257
258 /* Copy arguments from command line */
259 for (i=1; i<*app_argc && argc < MAX_ARGS; ++i)
260 argv[argc++] = (*app_argv)[i];
261
262 if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) {
263 PJ_LOG(1,(THIS_FILE,
264 "Too many arguments specified in cmd line/config file"));
265 fflush(stdout);
266 fclose(fhnd);
267 return -1;
268 }
269
270 fclose(fhnd);
271
272 /* Assign the new command line back to the original command line. */
273 *app_argc = argc;
274 *app_argv = argv;
275 return 0;
276
277}
278
279static int my_atoi(const char *cs)
280{
281 pj_str_t s;
282 return pj_strtoul(pj_cstr(&s, cs));
283}
284
285
286/* Parse arguments. */
287static pj_status_t parse_args(int argc, char *argv[],
288 struct app_config *cfg,
289 pj_str_t *uri_to_call)
290{
291 int c;
292 int option_index;
293 enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL,
294 OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO,
Benny Prijono0a5cad82006-09-26 13:21:02 +0000295 OPT_LOCAL_PORT, OPT_IP_ADDR, OPT_PROXY, OPT_OUTBOUND_PROXY,
296 OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000297 OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000298 OPT_NAMESERVER, OPT_USE_STUN1, OPT_USE_STUN2,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000299 OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
300 OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP,
301 OPT_AUTO_CONF, OPT_CLOCK_RATE,
Benny Prijono00cae612006-07-31 15:19:36 +0000302 OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC, OPT_ILBC_MODE,
Benny Prijono1ebd6142006-10-19 15:48:02 +0000303 OPT_REC_FILE, OPT_AUTO_REC,
Benny Prijono0a12f002006-07-26 17:05:39 +0000304 OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NO_VAD,
Benny Prijonod79f25c2006-08-02 19:41:37 +0000305 OPT_RX_DROP_PCT, OPT_TX_DROP_PCT, OPT_EC_TAIL,
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000306 OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS,
Benny Prijonof521eb02006-08-06 23:07:25 +0000307 OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, OPT_THREAD_CNT,
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000308 OPT_NOREFERSUB,
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000309 OPT_USE_TLS, OPT_TLS_CA_FILE, OPT_TLS_KEY_FILE, OPT_TLS_PASSWORD,
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000310 };
311 struct pj_getopt_option long_options[] = {
312 { "config-file",1, 0, OPT_CONFIG_FILE},
313 { "log-file", 1, 0, OPT_LOG_FILE},
314 { "log-level", 1, 0, OPT_LOG_LEVEL},
315 { "app-log-level",1,0,OPT_APP_LOG_LEVEL},
316 { "help", 0, 0, OPT_HELP},
317 { "version", 0, 0, OPT_VERSION},
318 { "clock-rate", 1, 0, OPT_CLOCK_RATE},
319 { "null-audio", 0, 0, OPT_NULL_AUDIO},
320 { "local-port", 1, 0, OPT_LOCAL_PORT},
Benny Prijono0a5cad82006-09-26 13:21:02 +0000321 { "ip-addr", 1, 0, OPT_IP_ADDR},
Benny Prijonoe93e2872006-06-28 16:46:49 +0000322 { "no-tcp", 0, 0, OPT_NO_TCP},
323 { "no-udp", 0, 0, OPT_NO_UDP},
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000324 { "norefersub", 0, 0, OPT_NOREFERSUB},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000325 { "proxy", 1, 0, OPT_PROXY},
326 { "outbound", 1, 0, OPT_OUTBOUND_PROXY},
327 { "registrar", 1, 0, OPT_REGISTRAR},
328 { "reg-timeout",1, 0, OPT_REG_TIMEOUT},
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000329 { "publish", 0, 0, OPT_PUBLISH},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000330 { "id", 1, 0, OPT_ID},
331 { "contact", 1, 0, OPT_CONTACT},
332 { "realm", 1, 0, OPT_REALM},
333 { "username", 1, 0, OPT_USERNAME},
334 { "password", 1, 0, OPT_PASSWORD},
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000335 { "nameserver", 1, 0, OPT_NAMESERVER},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000336 { "use-stun1", 1, 0, OPT_USE_STUN1},
337 { "use-stun2", 1, 0, OPT_USE_STUN2},
338 { "add-buddy", 1, 0, OPT_ADD_BUDDY},
339 { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG},
340 { "no-presence", 0, 0, OPT_NO_PRESENCE},
341 { "auto-answer",1, 0, OPT_AUTO_ANSWER},
342 { "auto-hangup",1, 0, OPT_AUTO_HANGUP},
343 { "auto-play", 0, 0, OPT_AUTO_PLAY},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000344 { "auto-rec", 0, 0, OPT_AUTO_REC},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000345 { "auto-loop", 0, 0, OPT_AUTO_LOOP},
346 { "auto-conf", 0, 0, OPT_AUTO_CONF},
347 { "play-file", 1, 0, OPT_PLAY_FILE},
Benny Prijono1ebd6142006-10-19 15:48:02 +0000348 { "rec-file", 1, 0, OPT_REC_FILE},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000349 { "rtp-port", 1, 0, OPT_RTP_PORT},
350 { "add-codec", 1, 0, OPT_ADD_CODEC},
351 { "complexity", 1, 0, OPT_COMPLEXITY},
352 { "quality", 1, 0, OPT_QUALITY},
353 { "ptime", 1, 0, OPT_PTIME},
Benny Prijono0a12f002006-07-26 17:05:39 +0000354 { "no-vad", 0, 0, OPT_NO_VAD},
Benny Prijonod79f25c2006-08-02 19:41:37 +0000355 { "ec-tail", 1, 0, OPT_EC_TAIL},
Benny Prijono00cae612006-07-31 15:19:36 +0000356 { "ilbc-mode", 1, 0, OPT_ILBC_MODE},
357 { "rx-drop-pct",1, 0, OPT_RX_DROP_PCT},
358 { "tx-drop-pct",1, 0, OPT_TX_DROP_PCT},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000359 { "next-account",0,0, OPT_NEXT_ACCOUNT},
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000360 { "next-cred", 0, 0, OPT_NEXT_CRED},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000361 { "max-calls", 1, 0, OPT_MAX_CALLS},
Benny Prijonof521eb02006-08-06 23:07:25 +0000362 { "duration", 1, 0, OPT_DURATION},
363 { "thread-cnt", 1, 0, OPT_THREAD_CNT},
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000364 { "use-tls", 0, 0, OPT_USE_TLS},
365 { "tls-ca-file",1, 0, OPT_TLS_CA_FILE},
366 { "tls-key-file",1,0, OPT_TLS_KEY_FILE},
367 { "tls-password",1,0, OPT_TLS_PASSWORD},
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000368 { NULL, 0, 0, 0}
369 };
370 pj_status_t status;
371 pjsua_acc_config *cur_acc;
372 char *config_file = NULL;
373 unsigned i;
374
375 /* Run pj_getopt once to see if user specifies config file to read. */
Benny Prijonof762ee72006-12-01 11:14:37 +0000376 pj_optind = 0;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000377 while ((c=pj_getopt_long(argc, argv, "", long_options,
378 &option_index)) != -1)
379 {
380 switch (c) {
381 case OPT_CONFIG_FILE:
382 config_file = pj_optarg;
383 break;
384 }
385 if (config_file)
386 break;
387 }
388
389 if (config_file) {
390 status = read_config_file(app_config.pool, config_file, &argc, &argv);
391 if (status != 0)
392 return status;
393 }
394
395 cfg->acc_cnt = 0;
396 cur_acc = &cfg->acc_cfg[0];
397
398
399 /* Reinitialize and re-run pj_getopt again, possibly with new arguments
400 * read from config file.
401 */
402 pj_optind = 0;
403 while((c=pj_getopt_long(argc,argv, "", long_options,&option_index))!=-1) {
404 char *p;
405 pj_str_t tmp;
406 long lval;
407
408 switch (c) {
409
Benny Prijono6f137482006-06-15 11:31:36 +0000410 case OPT_CONFIG_FILE:
411 /* Ignore as this has been processed before */
412 break;
413
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000414 case OPT_LOG_FILE:
415 cfg->log_cfg.log_filename = pj_str(pj_optarg);
416 break;
417
418 case OPT_LOG_LEVEL:
419 c = pj_strtoul(pj_cstr(&tmp, pj_optarg));
420 if (c < 0 || c > 6) {
421 PJ_LOG(1,(THIS_FILE,
422 "Error: expecting integer value 0-6 "
423 "for --log-level"));
424 return PJ_EINVAL;
425 }
426 cfg->log_cfg.level = c;
427 pj_log_set_level( c );
428 break;
429
430 case OPT_APP_LOG_LEVEL:
431 cfg->log_cfg.console_level = pj_strtoul(pj_cstr(&tmp, pj_optarg));
432 if (cfg->log_cfg.console_level < 0 || cfg->log_cfg.console_level > 6) {
433 PJ_LOG(1,(THIS_FILE,
434 "Error: expecting integer value 0-6 "
435 "for --app-log-level"));
436 return PJ_EINVAL;
437 }
438 break;
439
440 case OPT_HELP:
441 usage();
442 return PJ_EINVAL;
443
444 case OPT_VERSION: /* version */
445 pj_dump_config();
446 return PJ_EINVAL;
447
448 case OPT_NULL_AUDIO:
449 cfg->null_audio = PJ_TRUE;
450 break;
451
452 case OPT_CLOCK_RATE:
453 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
454 if (lval < 8000 || lval > 48000) {
455 PJ_LOG(1,(THIS_FILE, "Error: expecting value between "
456 "8000-48000 for clock rate"));
457 return PJ_EINVAL;
458 }
459 cfg->media_cfg.clock_rate = lval;
460 break;
461
462 case OPT_LOCAL_PORT: /* local-port */
463 lval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
464 if (lval < 1 || lval > 65535) {
465 PJ_LOG(1,(THIS_FILE,
466 "Error: expecting integer value for "
467 "--local-port"));
468 return PJ_EINVAL;
469 }
470 cfg->udp_cfg.port = (pj_uint16_t)lval;
471 break;
472
Benny Prijono0a5cad82006-09-26 13:21:02 +0000473 case OPT_IP_ADDR: /* ip-addr */
474 cfg->udp_cfg.public_addr = pj_str(pj_optarg);
475 cfg->rtp_cfg.public_addr = pj_str(pj_optarg);
476 break;
477
Benny Prijonoe93e2872006-06-28 16:46:49 +0000478 case OPT_NO_UDP: /* no-udp */
479 if (cfg->no_tcp) {
480 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
481 return PJ_EINVAL;
482 }
483
484 cfg->no_udp = PJ_TRUE;
485 break;
486
Benny Prijono4ddad2c2006-10-18 17:16:34 +0000487 case OPT_NOREFERSUB: /* norefersub */
488 cfg->no_refersub = PJ_TRUE;
489 break;
490
Benny Prijonoe93e2872006-06-28 16:46:49 +0000491 case OPT_NO_TCP: /* no-tcp */
492 if (cfg->no_udp) {
493 PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP"));
494 return PJ_EINVAL;
495 }
496
497 cfg->no_tcp = PJ_TRUE;
498 break;
499
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000500 case OPT_PROXY: /* proxy */
501 if (pjsua_verify_sip_url(pj_optarg) != 0) {
502 PJ_LOG(1,(THIS_FILE,
503 "Error: invalid SIP URL '%s' "
504 "in proxy argument", pj_optarg));
505 return PJ_EINVAL;
506 }
507 cur_acc->proxy[cur_acc->proxy_cnt++] = pj_str(pj_optarg);
508 break;
509
510 case OPT_OUTBOUND_PROXY: /* outbound proxy */
511 if (pjsua_verify_sip_url(pj_optarg) != 0) {
512 PJ_LOG(1,(THIS_FILE,
513 "Error: invalid SIP URL '%s' "
514 "in outbound proxy argument", pj_optarg));
515 return PJ_EINVAL;
516 }
517 cfg->cfg.outbound_proxy[cfg->cfg.outbound_proxy_cnt++] = pj_str(pj_optarg);
518 break;
519
520 case OPT_REGISTRAR: /* registrar */
521 if (pjsua_verify_sip_url(pj_optarg) != 0) {
522 PJ_LOG(1,(THIS_FILE,
523 "Error: invalid SIP URL '%s' in "
524 "registrar argument", pj_optarg));
525 return PJ_EINVAL;
526 }
527 cur_acc->reg_uri = pj_str(pj_optarg);
528 break;
529
530 case OPT_REG_TIMEOUT: /* reg-timeout */
531 cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,pj_optarg));
532 if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) {
533 PJ_LOG(1,(THIS_FILE,
534 "Error: invalid value for --reg-timeout "
535 "(expecting 1-3600)"));
536 return PJ_EINVAL;
537 }
538 break;
539
Benny Prijono3a5e1ab2006-08-15 20:26:34 +0000540 case OPT_PUBLISH: /* publish */
541 cur_acc->publish_enabled = PJ_TRUE;
542 break;
543
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000544 case OPT_ID: /* id */
545 if (pjsua_verify_sip_url(pj_optarg) != 0) {
546 PJ_LOG(1,(THIS_FILE,
547 "Error: invalid SIP URL '%s' "
548 "in local id argument", pj_optarg));
549 return PJ_EINVAL;
550 }
551 cur_acc->id = pj_str(pj_optarg);
552 break;
553
554 case OPT_CONTACT: /* contact */
555 if (pjsua_verify_sip_url(pj_optarg) != 0) {
556 PJ_LOG(1,(THIS_FILE,
557 "Error: invalid SIP URL '%s' "
558 "in contact argument", pj_optarg));
559 return PJ_EINVAL;
560 }
Benny Prijonob4a17c92006-07-10 14:40:21 +0000561 cur_acc->force_contact = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000562 break;
563
564 case OPT_NEXT_ACCOUNT: /* Add more account. */
565 cfg->acc_cnt++;
Benny Prijono56315612006-07-18 14:39:40 +0000566 cur_acc = &cfg->acc_cfg[cfg->acc_cnt];
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000567 break;
568
569 case OPT_USERNAME: /* Default authentication user */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000570 cur_acc->cred_info[cur_acc->cred_count].username = pj_str(pj_optarg);
571 cur_acc->cred_info[cur_acc->cred_count].scheme = pj_str("digest");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000572 break;
573
574 case OPT_REALM: /* Default authentication realm. */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000575 cur_acc->cred_info[cur_acc->cred_count].realm = pj_str(pj_optarg);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000576 break;
577
578 case OPT_PASSWORD: /* authentication password */
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000579 cur_acc->cred_info[cur_acc->cred_count].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
580 cur_acc->cred_info[cur_acc->cred_count].data = pj_str(pj_optarg);
581 break;
582
583 case OPT_NEXT_CRED: /* next credential */
584 cur_acc->cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000585 break;
586
Benny Prijonofa9e5b12006-10-08 12:39:34 +0000587 case OPT_NAMESERVER: /* nameserver */
588 cfg->cfg.nameserver[cfg->cfg.nameserver_count++] = pj_str(pj_optarg);
589 if (cfg->cfg.nameserver_count > PJ_ARRAY_SIZE(cfg->cfg.nameserver)) {
590 PJ_LOG(1,(THIS_FILE, "Error: too many nameservers"));
591 return PJ_ETOOMANY;
592 }
593 break;
594
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000595 case OPT_USE_STUN1: /* STUN server 1 */
596 p = pj_ansi_strchr(pj_optarg, ':');
597 if (p) {
598 *p = '\0';
599 cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
600 cfg->udp_cfg.stun_config.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1));
601 if (cfg->udp_cfg.stun_config.stun_port1 < 1 || cfg->udp_cfg.stun_config.stun_port1 > 65535) {
602 PJ_LOG(1,(THIS_FILE,
603 "Error: expecting port number with "
604 "option --use-stun1"));
605 return PJ_EINVAL;
606 }
607 } else {
608 cfg->udp_cfg.stun_config.stun_port1 = 3478;
609 cfg->udp_cfg.stun_config.stun_srv1 = pj_str(pj_optarg);
610 }
611 cfg->udp_cfg.use_stun = PJ_TRUE;
612 break;
613
614 case OPT_USE_STUN2: /* STUN server 2 */
615 p = pj_ansi_strchr(pj_optarg, ':');
616 if (p) {
617 *p = '\0';
618 cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
619 cfg->udp_cfg.stun_config.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1));
620 if (cfg->udp_cfg.stun_config.stun_port2 < 1 || cfg->udp_cfg.stun_config.stun_port2 > 65535) {
621 PJ_LOG(1,(THIS_FILE,
622 "Error: expecting port number with "
623 "option --use-stun2"));
624 return PJ_EINVAL;
625 }
626 } else {
627 cfg->udp_cfg.stun_config.stun_port2 = 3478;
628 cfg->udp_cfg.stun_config.stun_srv2 = pj_str(pj_optarg);
629 }
630 break;
631
632 case OPT_ADD_BUDDY: /* Add to buddy list. */
633 if (pjsua_verify_sip_url(pj_optarg) != 0) {
634 PJ_LOG(1,(THIS_FILE,
635 "Error: invalid URL '%s' in "
636 "--add-buddy option", pj_optarg));
637 return -1;
638 }
639 if (cfg->buddy_cnt == PJ_ARRAY_SIZE(cfg->buddy_cfg)) {
640 PJ_LOG(1,(THIS_FILE,
641 "Error: too many buddies in buddy list."));
642 return -1;
643 }
644 cfg->buddy_cfg[cfg->buddy_cnt].uri = pj_str(pj_optarg);
645 cfg->buddy_cnt++;
646 break;
647
648 case OPT_AUTO_PLAY:
649 cfg->auto_play = 1;
650 break;
651
Benny Prijono1ebd6142006-10-19 15:48:02 +0000652 case OPT_AUTO_REC:
653 cfg->auto_rec = 1;
654 break;
655
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000656 case OPT_AUTO_LOOP:
657 cfg->auto_loop = 1;
658 break;
659
Benny Prijono7ca96da2006-08-07 12:11:40 +0000660 case OPT_AUTO_CONF:
661 cfg->auto_conf = 1;
662 break;
663
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000664 case OPT_PLAY_FILE:
665 cfg->wav_file = pj_str(pj_optarg);
666 break;
667
Benny Prijono1ebd6142006-10-19 15:48:02 +0000668 case OPT_REC_FILE:
669 cfg->rec_file = pj_str(pj_optarg);
670 break;
671
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000672 case OPT_RTP_PORT:
673 cfg->rtp_cfg.port = my_atoi(pj_optarg);
674 if (cfg->rtp_cfg.port < 1 || cfg->rtp_cfg.port > 65535) {
675 PJ_LOG(1,(THIS_FILE,
676 "Error: rtp-port argument value "
677 "(expecting 1-65535"));
678 return -1;
679 }
680 break;
681
682 case OPT_ADD_CODEC:
683 cfg->codec_arg[cfg->codec_cnt++] = pj_str(pj_optarg);
684 break;
685
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000686 /* These options were no longer valid after new pjsua */
687 /*
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000688 case OPT_COMPLEXITY:
689 cfg->complexity = my_atoi(pj_optarg);
690 if (cfg->complexity < 0 || cfg->complexity > 10) {
691 PJ_LOG(1,(THIS_FILE,
692 "Error: invalid --complexity (expecting 0-10"));
693 return -1;
694 }
695 break;
Benny Prijono804ff0a2006-09-14 11:17:48 +0000696 */
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000697
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000698 case OPT_DURATION:
699 cfg->duration = my_atoi(pj_optarg);
700 break;
701
Benny Prijonof521eb02006-08-06 23:07:25 +0000702 case OPT_THREAD_CNT:
703 cfg->cfg.thread_cnt = my_atoi(pj_optarg);
704 if (cfg->cfg.thread_cnt > 128) {
705 PJ_LOG(1,(THIS_FILE,
706 "Error: invalid --thread-cnt option"));
707 return -1;
708 }
709 break;
710
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000711 case OPT_PTIME:
Benny Prijono0a12f002006-07-26 17:05:39 +0000712 cfg->media_cfg.ptime = my_atoi(pj_optarg);
713 if (cfg->media_cfg.ptime < 10 || cfg->media_cfg.ptime > 1000) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000714 PJ_LOG(1,(THIS_FILE,
715 "Error: invalid --ptime option"));
716 return -1;
717 }
718 break;
719
Benny Prijono0a12f002006-07-26 17:05:39 +0000720 case OPT_NO_VAD:
721 cfg->media_cfg.no_vad = PJ_TRUE;
722 break;
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000723
Benny Prijonod79f25c2006-08-02 19:41:37 +0000724 case OPT_EC_TAIL:
725 cfg->media_cfg.ec_tail_len = my_atoi(pj_optarg);
726 if (cfg->media_cfg.ec_tail_len > 1000) {
727 PJ_LOG(1,(THIS_FILE, "I think the ec-tail length setting "
728 "is too big"));
729 return -1;
730 }
731 break;
732
Benny Prijono0498d902006-06-19 14:49:14 +0000733 case OPT_QUALITY:
734 cfg->media_cfg.quality = my_atoi(pj_optarg);
735 if (cfg->media_cfg.quality < 0 || cfg->media_cfg.quality > 10) {
736 PJ_LOG(1,(THIS_FILE,
737 "Error: invalid --quality (expecting 0-10"));
738 return -1;
739 }
740 break;
741
Benny Prijono00cae612006-07-31 15:19:36 +0000742 case OPT_ILBC_MODE:
743 cfg->media_cfg.ilbc_mode = my_atoi(pj_optarg);
744 if (cfg->media_cfg.ilbc_mode!=20 && cfg->media_cfg.ilbc_mode!=30) {
745 PJ_LOG(1,(THIS_FILE,
746 "Error: invalid --ilbc-mode (expecting 20 or 30"));
747 return -1;
748 }
749 break;
750
751 case OPT_RX_DROP_PCT:
752 cfg->media_cfg.rx_drop_pct = my_atoi(pj_optarg);
753 if (cfg->media_cfg.rx_drop_pct > 100) {
754 PJ_LOG(1,(THIS_FILE,
755 "Error: invalid --rx-drop-pct (expecting <= 100"));
756 return -1;
757 }
758 break;
759
760 case OPT_TX_DROP_PCT:
761 cfg->media_cfg.tx_drop_pct = my_atoi(pj_optarg);
762 if (cfg->media_cfg.tx_drop_pct > 100) {
763 PJ_LOG(1,(THIS_FILE,
764 "Error: invalid --tx-drop-pct (expecting <= 100"));
765 return -1;
766 }
767 break;
768
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000769 case OPT_AUTO_ANSWER:
770 cfg->auto_answer = my_atoi(pj_optarg);
771 if (cfg->auto_answer < 100 || cfg->auto_answer > 699) {
772 PJ_LOG(1,(THIS_FILE,
773 "Error: invalid code in --auto-answer "
774 "(expecting 100-699"));
775 return -1;
776 }
777 break;
778
779 case OPT_MAX_CALLS:
780 cfg->cfg.max_calls = my_atoi(pj_optarg);
Benny Prijono48af79c2006-07-22 12:49:17 +0000781 if (cfg->cfg.max_calls < 1 || cfg->cfg.max_calls > PJSUA_MAX_CALLS) {
Benny Prijono804ff0a2006-09-14 11:17:48 +0000782 PJ_LOG(1,(THIS_FILE,"Error: maximum call setting exceeds "
783 "compile time limit (PJSUA_MAX_CALLS=%d)",
Benny Prijono48af79c2006-07-22 12:49:17 +0000784 PJSUA_MAX_CALLS));
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000785 return -1;
786 }
787 break;
788
Benny Prijono6e0e54b2006-12-08 21:58:31 +0000789 case OPT_USE_TLS:
790 cfg->use_tls = PJ_TRUE;
791 break;
792
793 case OPT_TLS_CA_FILE:
794 cfg->udp_cfg.tls_ca_file = pj_str(pj_optarg);
795 break;
796
797 case OPT_TLS_KEY_FILE:
798 cfg->udp_cfg.tls_key_file = pj_str(pj_optarg);
799 break;
800
801 case OPT_TLS_PASSWORD:
802 cfg->udp_cfg.tls_password = pj_str(pj_optarg);
803 break;
804
Benny Prijono22a300a2006-06-14 20:04:55 +0000805 default:
Benny Prijono787b8692006-06-19 12:40:03 +0000806 PJ_LOG(1,(THIS_FILE,
Benny Prijonod6388ac2006-09-09 13:23:09 +0000807 "Argument \"%s\" is not valid. Use --help to see help",
808 argv[pj_optind-1]));
Benny Prijono22a300a2006-06-14 20:04:55 +0000809 return -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000810 }
811 }
812
813 if (pj_optind != argc) {
814 pj_str_t uri_arg;
815
816 if (pjsua_verify_sip_url(argv[pj_optind]) != PJ_SUCCESS) {
817 PJ_LOG(1,(THIS_FILE, "Invalid SIP URI %s", argv[pj_optind]));
818 return -1;
819 }
820 uri_arg = pj_str(argv[pj_optind]);
821 if (uri_to_call)
822 *uri_to_call = uri_arg;
823 pj_optind++;
824
825 /* Add URI to call to buddy list if it's not already there */
826 for (i=0; i<cfg->buddy_cnt; ++i) {
827 if (pj_stricmp(&cfg->buddy_cfg[i].uri, &uri_arg)==0)
828 break;
829 }
830 if (i == cfg->buddy_cnt && cfg->buddy_cnt < PJSUA_MAX_BUDDIES) {
831 cfg->buddy_cfg[cfg->buddy_cnt++].uri = uri_arg;
832 }
833
834 } else {
835 if (uri_to_call)
836 uri_to_call->slen = 0;
837 }
838
839 if (pj_optind != argc) {
840 PJ_LOG(1,(THIS_FILE, "Error: unknown options %s", argv[pj_optind]));
841 return PJ_EINVAL;
842 }
843
Benny Prijono56315612006-07-18 14:39:40 +0000844 if (cfg->acc_cfg[cfg->acc_cnt].id.slen)
845 cfg->acc_cnt++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000846
847 for (i=0; i<cfg->acc_cnt; ++i) {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000848 if (cfg->acc_cfg[i].cred_info[cfg->acc_cfg[i].cred_count].username.slen)
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000849 {
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000850 cfg->acc_cfg[i].cred_count++;
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000851 }
852 }
853
854
855 return PJ_SUCCESS;
856}
857
858
859/*
860 * Save account settings
861 */
862static void write_account_settings(int acc_index, pj_str_t *result)
863{
864 unsigned i;
865 char line[128];
866 pjsua_acc_config *acc_cfg = &app_config.acc_cfg[acc_index];
867
868
869 pj_ansi_sprintf(line, "\n#\n# Account %d:\n#\n", acc_index);
870 pj_strcat2(result, line);
871
872
873 /* Identity */
874 if (acc_cfg->id.slen) {
875 pj_ansi_sprintf(line, "--id %.*s\n",
876 (int)acc_cfg->id.slen,
877 acc_cfg->id.ptr);
878 pj_strcat2(result, line);
879 }
880
881 /* Registrar server */
882 if (acc_cfg->reg_uri.slen) {
883 pj_ansi_sprintf(line, "--registrar %.*s\n",
884 (int)acc_cfg->reg_uri.slen,
885 acc_cfg->reg_uri.ptr);
886 pj_strcat2(result, line);
887
888 pj_ansi_sprintf(line, "--reg-timeout %u\n",
889 acc_cfg->reg_timeout);
890 pj_strcat2(result, line);
891 }
892
893 /* Contact */
Benny Prijonob4a17c92006-07-10 14:40:21 +0000894 if (acc_cfg->force_contact.slen) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000895 pj_ansi_sprintf(line, "--contact %.*s\n",
Benny Prijonob4a17c92006-07-10 14:40:21 +0000896 (int)acc_cfg->force_contact.slen,
897 acc_cfg->force_contact.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000898 pj_strcat2(result, line);
899 }
900
901 /* Proxy */
902 for (i=0; i<acc_cfg->proxy_cnt; ++i) {
903 pj_ansi_sprintf(line, "--proxy %.*s\n",
904 (int)acc_cfg->proxy[i].slen,
905 acc_cfg->proxy[i].ptr);
906 pj_strcat2(result, line);
907 }
908
909 /* Credentials */
910 for (i=0; i<acc_cfg->cred_count; ++i) {
911 if (acc_cfg->cred_info[i].realm.slen) {
912 pj_ansi_sprintf(line, "--realm %.*s\n",
913 (int)acc_cfg->cred_info[i].realm.slen,
914 acc_cfg->cred_info[i].realm.ptr);
915 pj_strcat2(result, line);
916 }
917
918 if (acc_cfg->cred_info[i].username.slen) {
919 pj_ansi_sprintf(line, "--username %.*s\n",
920 (int)acc_cfg->cred_info[i].username.slen,
921 acc_cfg->cred_info[i].username.ptr);
922 pj_strcat2(result, line);
923 }
924
925 if (acc_cfg->cred_info[i].data.slen) {
926 pj_ansi_sprintf(line, "--password %.*s\n",
927 (int)acc_cfg->cred_info[i].data.slen,
928 acc_cfg->cred_info[i].data.ptr);
929 pj_strcat2(result, line);
930 }
Benny Prijonoeef4f8c2006-06-19 12:07:44 +0000931
932 if (i != acc_cfg->cred_count - 1)
933 pj_strcat2(result, "--next-cred\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +0000934 }
935
936}
937
938
939/*
940 * Write settings.
941 */
942static int write_settings(const struct app_config *config,
943 char *buf, pj_size_t max)
944{
945 unsigned acc_index;
946 unsigned i;
947 pj_str_t cfg;
948 char line[128];
949
950 PJ_UNUSED_ARG(max);
951
952 cfg.ptr = buf;
953 cfg.slen = 0;
954
955 /* Logging. */
956 pj_strcat2(&cfg, "#\n# Logging options:\n#\n");
957 pj_ansi_sprintf(line, "--log-level %d\n",
958 config->log_cfg.level);
959 pj_strcat2(&cfg, line);
960
961 pj_ansi_sprintf(line, "--app-log-level %d\n",
962 config->log_cfg.console_level);
963 pj_strcat2(&cfg, line);
964
965 if (config->log_cfg.log_filename.slen) {
966 pj_ansi_sprintf(line, "--log-file %.*s\n",
967 (int)config->log_cfg.log_filename.slen,
968 config->log_cfg.log_filename.ptr);
969 pj_strcat2(&cfg, line);
970 }
971
972
973 /* Save account settings. */
974 for (acc_index=0; acc_index < config->acc_cnt; ++acc_index) {
975
976 write_account_settings(acc_index, &cfg);
977
978 if (acc_index < config->acc_cnt-1)
979 pj_strcat2(&cfg, "--next-account\n");
980 }
981
982
983 pj_strcat2(&cfg, "\n#\n# Network settings:\n#\n");
984
985 /* Outbound proxy */
986 for (i=0; i<config->cfg.outbound_proxy_cnt; ++i) {
987 pj_ansi_sprintf(line, "--outbound %.*s\n",
988 (int)config->cfg.outbound_proxy[i].slen,
989 config->cfg.outbound_proxy[i].ptr);
990 pj_strcat2(&cfg, line);
991 }
992
993
994 /* UDP Transport. */
995 pj_ansi_sprintf(line, "--local-port %d\n", config->udp_cfg.port);
996 pj_strcat2(&cfg, line);
997
Benny Prijono0a5cad82006-09-26 13:21:02 +0000998 /* IP address, if any. */
999 if (config->udp_cfg.public_addr.slen) {
1000 pj_ansi_sprintf(line, "--ip-addr %.*s\n",
1001 (int)config->udp_cfg.public_addr.slen,
1002 config->udp_cfg.public_addr.ptr);
1003 pj_strcat2(&cfg, line);
1004 }
1005
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001006 /* No TCP ? */
1007 if (config->no_tcp) {
1008 pj_strcat2(&cfg, "--no-tcp\n");
1009 }
1010
1011 /* No UDP ? */
1012 if (config->no_udp) {
1013 pj_strcat2(&cfg, "--no-udp\n");
1014 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001015
1016 /* STUN */
1017 if (config->udp_cfg.stun_config.stun_port1) {
1018 pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n",
1019 (int)config->udp_cfg.stun_config.stun_srv1.slen,
1020 config->udp_cfg.stun_config.stun_srv1.ptr,
1021 config->udp_cfg.stun_config.stun_port1);
1022 pj_strcat2(&cfg, line);
1023 }
1024
1025 if (config->udp_cfg.stun_config.stun_port2) {
1026 pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n",
1027 (int)config->udp_cfg.stun_config.stun_srv2.slen,
1028 config->udp_cfg.stun_config.stun_srv2.ptr,
1029 config->udp_cfg.stun_config.stun_port2);
1030 pj_strcat2(&cfg, line);
1031 }
1032
Benny Prijono6e0e54b2006-12-08 21:58:31 +00001033 /* TLS */
1034 if (config->use_tls)
1035 pj_strcat2(&cfg, "--use-tls\n");
1036 if (config->udp_cfg.tls_ca_file.slen) {
1037 pj_ansi_sprintf(line, "--tls-ca-file %.*s\n",
1038 (int)config->udp_cfg.tls_ca_file.slen,
1039 config->udp_cfg.tls_ca_file.ptr);
1040 pj_strcat2(&cfg, line);
1041 }
1042 if (config->udp_cfg.tls_key_file.slen) {
1043 pj_ansi_sprintf(line, "--tls-key-file %.*s\n",
1044 (int)config->udp_cfg.tls_key_file.slen,
1045 config->udp_cfg.tls_key_file.ptr);
1046 pj_strcat2(&cfg, line);
1047 }
1048 if (config->udp_cfg.tls_password.slen) {
1049 pj_ansi_sprintf(line, "--tls-password %.*s\n",
1050 (int)config->udp_cfg.tls_password.slen,
1051 config->udp_cfg.tls_password.ptr);
1052 pj_strcat2(&cfg, line);
1053 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001054
1055 pj_strcat2(&cfg, "\n#\n# Media settings:\n#\n");
1056
1057
1058 /* Media */
1059 if (config->null_audio)
1060 pj_strcat2(&cfg, "--null-audio\n");
1061 if (config->auto_play)
1062 pj_strcat2(&cfg, "--auto-play\n");
1063 if (config->auto_loop)
1064 pj_strcat2(&cfg, "--auto-loop\n");
Benny Prijono7ca96da2006-08-07 12:11:40 +00001065 if (config->auto_conf)
1066 pj_strcat2(&cfg, "--auto-conf\n");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001067 if (config->wav_file.slen) {
1068 pj_ansi_sprintf(line, "--play-file %s\n",
1069 config->wav_file.ptr);
1070 pj_strcat2(&cfg, line);
1071 }
Benny Prijono1ebd6142006-10-19 15:48:02 +00001072 if (config->rec_file.slen) {
1073 pj_ansi_sprintf(line, "--rec-file %s\n",
1074 config->rec_file.ptr);
1075 pj_strcat2(&cfg, line);
1076 }
1077 if (config->auto_rec)
1078 pj_strcat2(&cfg, "--auto-rec\n");
1079
1080
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001081 /* Media clock rate. */
Benny Prijono70972992006-08-05 11:13:58 +00001082 if (config->media_cfg.clock_rate != PJSUA_DEFAULT_CLOCK_RATE) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001083 pj_ansi_sprintf(line, "--clock-rate %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001084 config->media_cfg.clock_rate);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001085 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001086 } else {
1087 pj_ansi_sprintf(line, "#using default --clock-rate %d\n",
1088 config->media_cfg.clock_rate);
1089 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001090 }
Benny Prijono70972992006-08-05 11:13:58 +00001091
1092 /* quality */
1093 if (config->media_cfg.quality != PJSUA_DEFAULT_CODEC_QUALITY) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001094 pj_ansi_sprintf(line, "--quality %d\n",
Benny Prijono0498d902006-06-19 14:49:14 +00001095 config->media_cfg.quality);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001096 pj_strcat2(&cfg, line);
Benny Prijono70972992006-08-05 11:13:58 +00001097 } else {
1098 pj_ansi_sprintf(line, "#using default --quality %d\n",
1099 config->media_cfg.quality);
1100 pj_strcat2(&cfg, line);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001101 }
Benny Prijono0498d902006-06-19 14:49:14 +00001102
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001103
1104 /* ptime */
1105 if (config->ptime) {
1106 pj_ansi_sprintf(line, "--ptime %d\n",
1107 config->ptime);
1108 pj_strcat2(&cfg, line);
1109 }
1110
Benny Prijono70972992006-08-05 11:13:58 +00001111 /* no-vad */
1112 if (config->media_cfg.no_vad) {
1113 pj_strcat2(&cfg, "--no-vad\n");
1114 }
1115
1116 /* ec-tail */
1117 if (config->media_cfg.ec_tail_len != PJSUA_DEFAULT_EC_TAIL_LEN) {
1118 pj_ansi_sprintf(line, "--ec-tail %d\n",
1119 config->media_cfg.ec_tail_len);
1120 pj_strcat2(&cfg, line);
1121 } else {
1122 pj_ansi_sprintf(line, "#using default --ec-tail %d\n",
1123 config->media_cfg.ec_tail_len);
1124 pj_strcat2(&cfg, line);
1125 }
1126
1127
1128 /* ilbc-mode */
1129 if (config->media_cfg.ilbc_mode != PJSUA_DEFAULT_ILBC_MODE) {
1130 pj_ansi_sprintf(line, "--ilbc-mode %d\n",
1131 config->media_cfg.ilbc_mode);
1132 pj_strcat2(&cfg, line);
1133 } else {
1134 pj_ansi_sprintf(line, "#using default --ilbc-mode %d\n",
1135 config->media_cfg.ilbc_mode);
1136 pj_strcat2(&cfg, line);
1137 }
1138
1139 /* RTP drop */
1140 if (config->media_cfg.tx_drop_pct) {
1141 pj_ansi_sprintf(line, "--tx-drop-pct %d\n",
1142 config->media_cfg.tx_drop_pct);
1143 pj_strcat2(&cfg, line);
1144
1145 }
1146 if (config->media_cfg.rx_drop_pct) {
1147 pj_ansi_sprintf(line, "--rx-drop-pct %d\n",
1148 config->media_cfg.rx_drop_pct);
1149 pj_strcat2(&cfg, line);
1150
1151 }
1152
1153
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001154 /* Start RTP port. */
1155 pj_ansi_sprintf(line, "--rtp-port %d\n",
1156 config->rtp_cfg.port);
1157 pj_strcat2(&cfg, line);
1158
1159 /* Add codec. */
1160 for (i=0; i<config->codec_cnt; ++i) {
1161 pj_ansi_sprintf(line, "--add-codec %s\n",
1162 config->codec_arg[i].ptr);
1163 pj_strcat2(&cfg, line);
1164 }
1165
1166 pj_strcat2(&cfg, "\n#\n# User agent:\n#\n");
1167
1168 /* Auto-answer. */
1169 if (config->auto_answer != 0) {
1170 pj_ansi_sprintf(line, "--auto-answer %d\n",
1171 config->auto_answer);
1172 pj_strcat2(&cfg, line);
1173 }
1174
1175 /* Max calls. */
1176 pj_ansi_sprintf(line, "--max-calls %d\n",
1177 config->cfg.max_calls);
1178 pj_strcat2(&cfg, line);
1179
1180 /* Uas-duration. */
Benny Prijono804ff0a2006-09-14 11:17:48 +00001181 if (config->duration != NO_LIMIT) {
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001182 pj_ansi_sprintf(line, "--duration %d\n",
1183 config->duration);
1184 pj_strcat2(&cfg, line);
1185 }
1186
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001187 /* norefersub ? */
1188 if (config->no_refersub) {
1189 pj_strcat2(&cfg, "--norefersub\n");
1190 }
1191
1192
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001193 pj_strcat2(&cfg, "\n#\n# Buddies:\n#\n");
1194
1195 /* Add buddies. */
1196 for (i=0; i<config->buddy_cnt; ++i) {
1197 pj_ansi_sprintf(line, "--add-buddy %.*s\n",
1198 (int)config->buddy_cfg[i].uri.slen,
1199 config->buddy_cfg[i].uri.ptr);
1200 pj_strcat2(&cfg, line);
1201 }
1202
1203
1204 *(cfg.ptr + cfg.slen) = '\0';
1205 return cfg.slen;
1206}
1207
1208
1209/*
1210 * Dump application states.
1211 */
1212static void app_dump(pj_bool_t detail)
1213{
1214 unsigned old_decor;
1215 char buf[1024];
1216
1217 PJ_LOG(3,(THIS_FILE, "Start dumping application states:"));
1218
1219 old_decor = pj_log_get_decor();
1220 pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR));
1221
1222 if (detail)
1223 pj_dump_config();
1224
1225 pjsip_endpt_dump(pjsua_get_pjsip_endpt(), detail);
1226 pjmedia_endpt_dump(pjsua_get_pjmedia_endpt());
1227 pjsip_tsx_layer_dump(detail);
1228 pjsip_ua_dump(detail);
1229
1230
1231 /* Dump all invite sessions: */
1232 PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:"));
1233
1234 if (pjsua_call_get_count() == 0) {
1235
1236 PJ_LOG(3,(THIS_FILE, " - no sessions -"));
1237
1238 } else {
1239 unsigned i;
1240
1241 for (i=0; i<app_config.cfg.max_calls; ++i) {
1242 if (pjsua_call_is_active(i)) {
1243 pjsua_call_dump(i, detail, buf, sizeof(buf), " ");
1244 PJ_LOG(3,(THIS_FILE, "%s", buf));
1245 }
1246 }
1247 }
1248
1249 /* Dump presence status */
1250 pjsua_pres_dump(detail);
1251
1252 pj_log_set_decor(old_decor);
1253 PJ_LOG(3,(THIS_FILE, "Dump complete"));
1254}
1255
1256
1257/*****************************************************************************
1258 * Console application
1259 */
1260
1261/*
1262 * Find next call when current call is disconnected or when user
1263 * press ']'
1264 */
1265static pj_bool_t find_next_call(void)
1266{
1267 int i, max;
1268
1269 max = pjsua_call_get_max_count();
1270 for (i=current_call+1; i<max; ++i) {
1271 if (pjsua_call_is_active(i)) {
1272 current_call = i;
1273 return PJ_TRUE;
1274 }
1275 }
1276
1277 for (i=0; i<current_call; ++i) {
1278 if (pjsua_call_is_active(i)) {
1279 current_call = i;
1280 return PJ_TRUE;
1281 }
1282 }
1283
1284 current_call = PJSUA_INVALID_ID;
1285 return PJ_FALSE;
1286}
1287
1288
1289/*
1290 * Find previous call when user press '['
1291 */
1292static pj_bool_t find_prev_call(void)
1293{
1294 int i, max;
1295
1296 max = pjsua_call_get_max_count();
1297 for (i=current_call-1; i>=0; --i) {
1298 if (pjsua_call_is_active(i)) {
1299 current_call = i;
1300 return PJ_TRUE;
1301 }
1302 }
1303
1304 for (i=max-1; i>current_call; --i) {
1305 if (pjsua_call_is_active(i)) {
1306 current_call = i;
1307 return PJ_TRUE;
1308 }
1309 }
1310
1311 current_call = PJSUA_INVALID_ID;
1312 return PJ_FALSE;
1313}
1314
1315
Benny Prijono804ff0a2006-09-14 11:17:48 +00001316/* Callback from timer when the maximum call duration has been
1317 * exceeded.
1318 */
1319static void call_timeout_callback(pj_timer_heap_t *timer_heap,
1320 struct pj_timer_entry *entry)
1321{
1322 pjsua_call_id call_id = entry->id;
1323 pjsua_msg_data msg_data;
1324 pjsip_generic_string_hdr warn;
1325 pj_str_t hname = pj_str("Warning");
1326 pj_str_t hvalue = pj_str("399 pjsua \"Call duration exceeded\"");
1327
1328 PJ_UNUSED_ARG(timer_heap);
1329
Benny Prijono148c9dd2006-09-19 13:37:53 +00001330 if (call_id == PJSUA_INVALID_ID) {
1331 PJ_LOG(1,(THIS_FILE, "Invalid call ID in timer callback"));
1332 return;
1333 }
1334
Benny Prijono804ff0a2006-09-14 11:17:48 +00001335 /* Add warning header */
1336 pjsua_msg_data_init(&msg_data);
1337 pjsip_generic_string_hdr_init2(&warn, &hname, &hvalue);
1338 pj_list_push_back(&msg_data.hdr_list, &warn);
1339
1340 /* Call duration has been exceeded; disconnect the call */
1341 PJ_LOG(3,(THIS_FILE, "Duration (%d seconds) has been exceeded "
1342 "for call %d, disconnecting the call",
1343 app_config.duration, call_id));
1344 entry->id = PJSUA_INVALID_ID;
1345 pjsua_call_hangup(call_id, 200, NULL, &msg_data);
1346}
1347
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001348
1349/*
1350 * Handler when invite state has changed.
1351 */
1352static void on_call_state(pjsua_call_id call_id, pjsip_event *e)
1353{
1354 pjsua_call_info call_info;
1355
1356 PJ_UNUSED_ARG(e);
1357
1358 pjsua_call_get_info(call_id, &call_info);
1359
1360 if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) {
1361
Benny Prijono804ff0a2006-09-14 11:17:48 +00001362 /* Cancel duration timer, if any */
1363 if (app_config.call_data[call_id].timer.id != PJSUA_INVALID_ID) {
1364 struct call_data *cd = &app_config.call_data[call_id];
1365 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1366
1367 cd->timer.id = PJSUA_INVALID_ID;
1368 pjsip_endpt_cancel_timer(endpt, &cd->timer);
1369 }
1370
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001371 PJ_LOG(3,(THIS_FILE, "Call %d is DISCONNECTED [reason=%d (%s)]",
1372 call_id,
1373 call_info.last_status,
1374 call_info.last_status_text.ptr));
1375
1376 if (call_id == current_call) {
1377 find_next_call();
1378 }
1379
Benny Prijono4be63b52006-11-25 14:50:25 +00001380 /* Dump media state upon disconnected */
1381 if (1) {
1382 char buf[1024];
1383 pjsua_call_dump(call_id, PJ_TRUE, buf,
1384 sizeof(buf), " ");
1385 PJ_LOG(5,(THIS_FILE,
1386 "Call %d disconnected, dumping media stats\n%s",
1387 call_id, buf));
1388 }
1389
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001390 } else {
1391
Benny Prijono804ff0a2006-09-14 11:17:48 +00001392 if (app_config.duration!=NO_LIMIT &&
1393 call_info.state == PJSIP_INV_STATE_CONFIRMED)
1394 {
1395 /* Schedule timer to hangup call after the specified duration */
1396 struct call_data *cd = &app_config.call_data[call_id];
1397 pjsip_endpoint *endpt = pjsua_get_pjsip_endpt();
1398 pj_time_val delay;
1399
1400 cd->timer.id = call_id;
1401 delay.sec = app_config.duration;
1402 delay.msec = 0;
1403 pjsip_endpt_schedule_timer(endpt, &cd->timer, &delay);
1404 }
1405
Benny Prijono4be63b52006-11-25 14:50:25 +00001406 if (call_info.state == PJSIP_INV_STATE_EARLY) {
1407 int code;
1408 pj_str_t reason;
1409 pjsip_msg *msg;
1410
1411 /* This can only occur because of TX or RX message */
1412 pj_assert(e->type == PJSIP_EVENT_TSX_STATE);
1413
1414 if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
1415 msg = e->body.tsx_state.src.rdata->msg_info.msg;
1416 } else {
1417 msg = e->body.tsx_state.src.tdata->msg;
1418 }
1419
1420 code = msg->line.status.code;
1421 reason = msg->line.status.reason;
1422
1423 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s (%d %.*s)",
1424 call_id, call_info.state_text.ptr,
1425 code, (int)reason.slen, reason.ptr));
1426 } else {
1427 PJ_LOG(3,(THIS_FILE, "Call %d state changed to %s",
1428 call_id,
1429 call_info.state_text.ptr));
1430 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001431
1432 if (current_call==PJSUA_INVALID_ID)
1433 current_call = call_id;
1434
1435 }
1436}
1437
1438
1439/**
1440 * Handler when there is incoming call.
1441 */
1442static void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id,
1443 pjsip_rx_data *rdata)
1444{
1445 pjsua_call_info call_info;
1446
1447 PJ_UNUSED_ARG(acc_id);
1448 PJ_UNUSED_ARG(rdata);
1449
1450 pjsua_call_get_info(call_id, &call_info);
1451
1452 if (app_config.auto_answer > 0) {
1453 pjsua_call_answer(call_id, app_config.auto_answer, NULL, NULL);
1454 }
1455
1456 if (app_config.auto_answer < 200) {
1457 PJ_LOG(3,(THIS_FILE,
1458 "Incoming call for account %d!\n"
1459 "From: %s\n"
1460 "To: %s\n"
1461 "Press a to answer or h to reject call",
1462 acc_id,
1463 call_info.remote_info.ptr,
1464 call_info.local_info.ptr));
1465 }
1466}
1467
1468
1469/*
1470 * Callback on media state changed event.
1471 * The action may connect the call to sound device, to file, or
1472 * to loop the call.
1473 */
1474static void on_call_media_state(pjsua_call_id call_id)
1475{
1476 pjsua_call_info call_info;
1477
1478 pjsua_call_get_info(call_id, &call_info);
1479
1480 if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
1481 pj_bool_t connect_sound = PJ_TRUE;
1482
1483 /* Loopback sound, if desired */
1484 if (app_config.auto_loop) {
1485 pjsua_conf_connect(call_info.conf_slot, call_info.conf_slot);
1486 connect_sound = PJ_FALSE;
Benny Prijono1ebd6142006-10-19 15:48:02 +00001487
1488 /* Automatically record conversation, if desired */
1489 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1490 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1491 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001492 }
1493
1494 /* Stream a file, if desired */
1495 if (app_config.auto_play && app_config.wav_port != PJSUA_INVALID_ID) {
1496 pjsua_conf_connect(app_config.wav_port, call_info.conf_slot);
1497 connect_sound = PJ_FALSE;
1498 }
1499
Benny Prijono7ca96da2006-08-07 12:11:40 +00001500 /* Put call in conference with other calls, if desired */
1501 if (app_config.auto_conf) {
1502 pjsua_call_id call_ids[PJSUA_MAX_CALLS];
Benny Prijono10861bc2006-08-07 13:22:43 +00001503 unsigned call_cnt=PJ_ARRAY_SIZE(call_ids);
Benny Prijono7ca96da2006-08-07 12:11:40 +00001504 unsigned i;
1505
1506 /* Get all calls, and establish media connection between
1507 * this call and other calls.
1508 */
1509 pjsua_enum_calls(call_ids, &call_cnt);
Benny Prijono10861bc2006-08-07 13:22:43 +00001510
Benny Prijono7ca96da2006-08-07 12:11:40 +00001511 for (i=0; i<call_cnt; ++i) {
1512 if (call_ids[i] == call_id)
1513 continue;
1514
1515 if (!pjsua_call_has_media(call_ids[i]))
1516 continue;
1517
1518 pjsua_conf_connect(call_info.conf_slot,
1519 pjsua_call_get_conf_port(call_ids[i]));
1520 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1521 call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001522
1523 /* Automatically record conversation, if desired */
1524 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1525 pjsua_conf_connect(pjsua_call_get_conf_port(call_ids[i]),
1526 app_config.rec_port);
1527 }
1528
Benny Prijono7ca96da2006-08-07 12:11:40 +00001529 }
1530
1531 /* Also connect call to local sound device */
1532 connect_sound = PJ_TRUE;
1533 }
1534
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001535 /* Otherwise connect to sound device */
1536 if (connect_sound) {
1537 pjsua_conf_connect(call_info.conf_slot, 0);
1538 pjsua_conf_connect(0, call_info.conf_slot);
Benny Prijono1ebd6142006-10-19 15:48:02 +00001539
1540 /* Automatically record conversation, if desired */
1541 if (app_config.auto_rec && app_config.rec_port != PJSUA_INVALID_ID) {
1542 pjsua_conf_connect(call_info.conf_slot, app_config.rec_port);
1543 pjsua_conf_connect(0, app_config.rec_port);
1544 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001545 }
1546
1547 PJ_LOG(3,(THIS_FILE, "Media for call %d is active", call_id));
1548
1549 } else if (call_info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD) {
1550 PJ_LOG(3,(THIS_FILE, "Media for call %d is suspended (hold) by local",
1551 call_id));
1552 } else if (call_info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD) {
1553 PJ_LOG(3,(THIS_FILE,
1554 "Media for call %d is suspended (hold) by remote",
1555 call_id));
1556 } else {
1557 PJ_LOG(3,(THIS_FILE,
1558 "Media for call %d is inactive",
1559 call_id));
1560 }
1561}
1562
1563
1564/*
1565 * Handler registration status has changed.
1566 */
1567static void on_reg_state(pjsua_acc_id acc_id)
1568{
1569 PJ_UNUSED_ARG(acc_id);
1570
1571 // Log already written.
1572}
1573
1574
1575/*
1576 * Handler on buddy state changed.
1577 */
1578static void on_buddy_state(pjsua_buddy_id buddy_id)
1579{
1580 pjsua_buddy_info info;
1581 pjsua_buddy_get_info(buddy_id, &info);
1582
1583 PJ_LOG(3,(THIS_FILE, "%.*s status is %.*s",
1584 (int)info.uri.slen,
1585 info.uri.ptr,
1586 (int)info.status_text.slen,
1587 info.status_text.ptr));
1588}
1589
1590
1591/**
1592 * Incoming IM message (i.e. MESSAGE request)!
1593 */
1594static void on_pager(pjsua_call_id call_id, const pj_str_t *from,
1595 const pj_str_t *to, const pj_str_t *contact,
1596 const pj_str_t *mime_type, const pj_str_t *text)
1597{
1598 /* Note: call index may be -1 */
1599 PJ_UNUSED_ARG(call_id);
1600 PJ_UNUSED_ARG(to);
1601 PJ_UNUSED_ARG(contact);
1602 PJ_UNUSED_ARG(mime_type);
1603
1604 PJ_LOG(3,(THIS_FILE,"MESSAGE from %.*s: %.*s",
1605 (int)from->slen, from->ptr,
1606 (int)text->slen, text->ptr));
1607}
1608
1609
1610/**
1611 * Received typing indication
1612 */
1613static void on_typing(pjsua_call_id call_id, const pj_str_t *from,
1614 const pj_str_t *to, const pj_str_t *contact,
1615 pj_bool_t is_typing)
1616{
1617 PJ_UNUSED_ARG(call_id);
1618 PJ_UNUSED_ARG(to);
1619 PJ_UNUSED_ARG(contact);
1620
1621 PJ_LOG(3,(THIS_FILE, "IM indication: %.*s %s",
1622 (int)from->slen, from->ptr,
1623 (is_typing?"is typing..":"has stopped typing")));
1624}
1625
1626
Benny Prijono4ddad2c2006-10-18 17:16:34 +00001627/**
1628 * Call transfer request status.
1629 */
1630static void on_call_transfer_status(pjsua_call_id call_id,
1631 int status_code,
1632 const pj_str_t *status_text,
1633 pj_bool_t final,
1634 pj_bool_t *p_cont)
1635{
1636 PJ_LOG(3,(THIS_FILE, "Call %d: transfer status=%d (%.*s) %s",
1637 call_id, status_code,
1638 (int)status_text->slen, status_text->ptr,
1639 (final ? "[final]" : "")));
1640
1641 if (status_code/100 == 2) {
1642 PJ_LOG(3,(THIS_FILE,
1643 "Call %d: call transfered successfully, disconnecting call",
1644 call_id));
1645 pjsua_call_hangup(call_id, PJSIP_SC_GONE, NULL, NULL);
1646 *p_cont = PJ_FALSE;
1647 }
1648}
1649
1650
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001651/*
Benny Prijonof7b1c392006-11-11 16:46:34 +00001652 * Notification that call is being replaced.
1653 */
1654static void on_call_replaced(pjsua_call_id old_call_id,
1655 pjsua_call_id new_call_id)
1656{
1657 pjsua_call_info old_ci, new_ci;
1658
1659 pjsua_call_get_info(old_call_id, &old_ci);
1660 pjsua_call_get_info(new_call_id, &new_ci);
1661
1662 PJ_LOG(3,(THIS_FILE, "Call %d with %.*s is being replaced by "
1663 "call %d with %.*s",
1664 old_call_id,
1665 (int)old_ci.remote_info.slen, old_ci.remote_info.ptr,
1666 new_call_id,
1667 (int)new_ci.remote_info.slen, new_ci.remote_info.ptr));
1668}
1669
1670
1671/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001672 * Print buddy list.
1673 */
1674static void print_buddy_list(void)
1675{
1676 pjsua_buddy_id ids[64];
1677 int i;
1678 unsigned count = PJ_ARRAY_SIZE(ids);
1679
1680 puts("Buddy list:");
1681
1682 pjsua_enum_buddies(ids, &count);
1683
1684 if (count == 0)
1685 puts(" -none-");
1686 else {
1687 for (i=0; i<(int)count; ++i) {
1688 pjsua_buddy_info info;
1689
1690 if (pjsua_buddy_get_info(ids[i], &info) != PJ_SUCCESS)
1691 continue;
1692
1693 printf(" [%2d] <%7s> %.*s\n",
1694 ids[i]+1, info.status_text.ptr,
1695 (int)info.uri.slen,
1696 info.uri.ptr);
1697 }
1698 }
1699 puts("");
1700}
1701
1702
1703/*
1704 * Print account status.
1705 */
1706static void print_acc_status(int acc_id)
1707{
1708 char buf[80];
1709 pjsua_acc_info info;
1710
1711 pjsua_acc_get_info(acc_id, &info);
1712
1713 if (!info.has_registration) {
1714 pj_ansi_snprintf(buf, sizeof(buf), "%.*s",
1715 (int)info.status_text.slen,
1716 info.status_text.ptr);
1717
1718 } else {
1719 pj_ansi_snprintf(buf, sizeof(buf),
1720 "%d/%.*s (expires=%d)",
1721 info.status,
1722 (int)info.status_text.slen,
1723 info.status_text.ptr,
1724 info.expires);
1725
1726 }
1727
1728 printf(" %c[%2d] %.*s: %s\n", (acc_id==current_acc?'*':' '),
1729 acc_id, (int)info.acc_uri.slen, info.acc_uri.ptr, buf);
1730 printf(" Online status: %s\n",
1731 (info.online_status ? "Online" : "Invisible"));
1732}
1733
1734
1735/*
1736 * Show a bit of help.
1737 */
1738static void keystroke_help(void)
1739{
1740 pjsua_acc_id acc_ids[16];
1741 unsigned count = PJ_ARRAY_SIZE(acc_ids);
1742 int i;
1743
1744 printf(">>>>\n");
1745
1746 pjsua_enum_accs(acc_ids, &count);
1747
1748 printf("Account list:\n");
1749 for (i=0; i<(int)count; ++i)
1750 print_acc_status(acc_ids[i]);
1751
1752 print_buddy_list();
1753
1754 //puts("Commands:");
1755 puts("+=============================================================================+");
1756 puts("| Call Commands: | Buddy, IM & Presence: | Account: |");
1757 puts("| | | |");
1758 puts("| m Make new call | +b Add new buddy .| +a Add new accnt |");
1759 puts("| M Make multiple calls | -b Delete buddy | -a Delete accnt. |");
1760 puts("| a Answer call | !b Modify buddy | !a Modify accnt. |");
1761 puts("| h Hangup call (ha=all) | i Send IM | rr (Re-)register |");
1762 puts("| H Hold call | s Subscribe presence | ru Unregister |");
1763 puts("| v re-inVite (release hold) | u Unsubscribe presence | > Cycle next ac.|");
1764 puts("| ] Select next dialog | t ToGgle Online status | < Cycle prev ac.|");
1765 puts("| [ Select previous dialog +--------------------------+-------------------+");
1766 puts("| x Xfer call | Media Commands: | Status & Config: |");
Benny Prijonof7b1c392006-11-11 16:46:34 +00001767 puts("| X Xfer with Replaces | | |");
1768 puts("| # Send DTMF string | cl List ports | d Dump status |");
1769 puts("| dq Dump curr. call quality | cc Connect port | dd Dump detailed |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001770 puts("| | cd Disconnect port | dc Dump config |");
Benny Prijono56315612006-07-18 14:39:40 +00001771 puts("| S Send arbitrary REQUEST | | f Save config |");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001772 puts("+------------------------------+--------------------------+-------------------+");
1773 puts("| q QUIT |");
1774 puts("+=============================================================================+");
Benny Prijono48af79c2006-07-22 12:49:17 +00001775
1776 i = pjsua_call_get_count();
1777 printf("You have %d active call%s\n", i, (i>1?"s":""));
Benny Prijonof7b1c392006-11-11 16:46:34 +00001778
1779 if (current_call != PJSUA_INVALID_ID) {
1780 pjsua_call_info ci;
1781 if (pjsua_call_get_info(current_call, &ci)==PJ_SUCCESS)
1782 printf("Current call id=%d to %.*s [%.*s]\n", current_call,
1783 (int)ci.remote_info.slen, ci.remote_info.ptr,
1784 (int)ci.state_text.slen, ci.state_text.ptr);
1785 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001786}
1787
1788
1789/*
1790 * Input simple string
1791 */
1792static pj_bool_t simple_input(const char *title, char *buf, pj_size_t len)
1793{
1794 char *p;
1795
1796 printf("%s (empty to cancel): ", title); fflush(stdout);
1797 fgets(buf, len, stdin);
1798
1799 /* Remove trailing newlines. */
1800 for (p=buf; ; ++p) {
1801 if (*p=='\r' || *p=='\n') *p='\0';
1802 else if (!*p) break;
1803 }
1804
1805 if (!*buf)
1806 return PJ_FALSE;
1807
1808 return PJ_TRUE;
1809}
1810
1811
1812#define NO_NB -2
1813struct input_result
1814{
1815 int nb_result;
1816 char *uri_result;
1817};
1818
1819
1820/*
1821 * Input URL.
1822 */
1823static void ui_input_url(const char *title, char *buf, int len,
1824 struct input_result *result)
1825{
1826 result->nb_result = NO_NB;
1827 result->uri_result = NULL;
1828
1829 print_buddy_list();
1830
1831 printf("Choices:\n"
1832 " 0 For current dialog.\n"
1833 " -1 All %d buddies in buddy list\n"
1834 " [1 -%2d] Select from buddy list\n"
1835 " URL An URL\n"
1836 " <Enter> Empty input (or 'q') to cancel\n"
1837 , pjsua_get_buddy_count(), pjsua_get_buddy_count());
1838 printf("%s: ", title);
1839
1840 fflush(stdout);
1841 fgets(buf, len, stdin);
1842 len = strlen(buf);
1843
1844 /* Left trim */
1845 while (pj_isspace(*buf)) {
1846 ++buf;
1847 --len;
1848 }
1849
1850 /* Remove trailing newlines */
1851 while (len && (buf[len-1] == '\r' || buf[len-1] == '\n'))
1852 buf[--len] = '\0';
1853
1854 if (len == 0 || buf[0]=='q')
1855 return;
1856
1857 if (pj_isdigit(*buf) || *buf=='-') {
1858
1859 int i;
1860
1861 if (*buf=='-')
1862 i = 1;
1863 else
1864 i = 0;
1865
1866 for (; i<len; ++i) {
1867 if (!pj_isdigit(buf[i])) {
1868 puts("Invalid input");
1869 return;
1870 }
1871 }
1872
1873 result->nb_result = my_atoi(buf);
1874
1875 if (result->nb_result >= 0 &&
1876 result->nb_result <= (int)pjsua_get_buddy_count())
1877 {
1878 return;
1879 }
1880 if (result->nb_result == -1)
1881 return;
1882
1883 puts("Invalid input");
1884 result->nb_result = NO_NB;
1885 return;
1886
1887 } else {
1888 pj_status_t status;
1889
1890 if ((status=pjsua_verify_sip_url(buf)) != PJ_SUCCESS) {
1891 pjsua_perror(THIS_FILE, "Invalid URL", status);
1892 return;
1893 }
1894
1895 result->uri_result = buf;
1896 }
1897}
1898
1899/*
1900 * List the ports in conference bridge
1901 */
1902static void conf_list(void)
1903{
1904 unsigned i, count;
1905 pjsua_conf_port_id id[PJSUA_MAX_CALLS];
1906
1907 printf("Conference ports:\n");
1908
1909 count = PJ_ARRAY_SIZE(id);
1910 pjsua_enum_conf_ports(id, &count);
1911
1912 for (i=0; i<count; ++i) {
1913 char txlist[PJSUA_MAX_CALLS*4+10];
1914 unsigned j;
1915 pjsua_conf_port_info info;
1916
1917 pjsua_conf_get_port_info(id[i], &info);
1918
1919 txlist[0] = '\0';
1920 for (j=0; j<info.listener_cnt; ++j) {
1921 char s[10];
1922 pj_ansi_sprintf(s, "#%d ", info.listeners[j]);
1923 pj_ansi_strcat(txlist, s);
1924 }
1925 printf("Port #%02d[%2dKHz/%dms] %20.*s transmitting to: %s\n",
1926 info.slot_id,
1927 info.clock_rate/1000,
1928 info.samples_per_frame * 1000 / info.clock_rate,
1929 (int)info.name.slen,
1930 info.name.ptr,
1931 txlist);
1932
1933 }
1934 puts("");
1935}
1936
1937
1938/*
Benny Prijono56315612006-07-18 14:39:40 +00001939 * Send arbitrary request to remote host
1940 */
1941static void send_request(char *cstr_method, const pj_str_t *dst_uri)
1942{
1943 pj_str_t str_method;
1944 pjsip_method method;
1945 pjsip_tx_data *tdata;
1946 pjsua_acc_info acc_info;
1947 pjsip_endpoint *endpt;
1948 pj_status_t status;
1949
1950 endpt = pjsua_get_pjsip_endpt();
1951
1952 str_method = pj_str(cstr_method);
1953 pjsip_method_init_np(&method, &str_method);
1954
1955 pjsua_acc_get_info(current_acc, &acc_info);
1956
1957 status = pjsip_endpt_create_request(endpt, &method, dst_uri,
1958 &acc_info.acc_uri, dst_uri,
1959 NULL, NULL, -1, NULL, &tdata);
1960 if (status != PJ_SUCCESS) {
1961 pjsua_perror(THIS_FILE, "Unable to create request", status);
1962 return;
1963 }
1964
1965 status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL);
1966 if (status != PJ_SUCCESS) {
1967 pjsua_perror(THIS_FILE, "Unable to send request", status);
Benny Prijono56315612006-07-18 14:39:40 +00001968 return;
1969 }
1970}
1971
1972
1973/*
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001974 * Main "user interface" loop.
1975 */
1976void console_app_main(const pj_str_t *uri_to_call)
1977{
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00001978 char menuin[32];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00001979 char buf[128];
1980 char text[128];
1981 int i, count;
1982 char *uri;
1983 pj_str_t tmp;
1984 struct input_result result;
1985 pjsua_call_info call_info;
1986 pjsua_acc_info acc_info;
1987
1988
1989 /* If user specifies URI to call, then call the URI */
1990 if (uri_to_call->slen) {
1991 pjsua_call_make_call( current_acc, uri_to_call, 0, NULL, NULL, NULL);
1992 }
1993
1994 keystroke_help();
1995
1996 for (;;) {
1997
1998 printf(">>> ");
1999 fflush(stdout);
2000
2001 fgets(menuin, sizeof(menuin), stdin);
2002
2003 switch (menuin[0]) {
2004
2005 case 'm':
2006 /* Make call! : */
2007 printf("(You currently have %d calls)\n",
2008 pjsua_call_get_count());
2009
2010 uri = NULL;
2011 ui_input_url("Make call", buf, sizeof(buf), &result);
2012 if (result.nb_result != NO_NB) {
2013
2014 if (result.nb_result == -1 || result.nb_result == 0) {
2015 puts("You can't do that with make call!");
2016 continue;
2017 } else {
2018 pjsua_buddy_info binfo;
2019 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2020 uri = binfo.uri.ptr;
2021 }
2022
2023 } else if (result.uri_result) {
2024 uri = result.uri_result;
2025 }
2026
2027 tmp = pj_str(uri);
2028 pjsua_call_make_call( current_acc, &tmp, 0, NULL, NULL, NULL);
2029 break;
2030
2031 case 'M':
2032 /* Make multiple calls! : */
2033 printf("(You currently have %d calls)\n",
2034 pjsua_call_get_count());
2035
2036 if (!simple_input("Number of calls", menuin, sizeof(menuin)))
2037 continue;
2038
2039 count = my_atoi(menuin);
2040 if (count < 1)
2041 continue;
2042
2043 ui_input_url("Make call", buf, sizeof(buf), &result);
2044 if (result.nb_result != NO_NB) {
2045 pjsua_buddy_info binfo;
2046 if (result.nb_result == -1 || result.nb_result == 0) {
2047 puts("You can't do that with make call!");
2048 continue;
2049 }
2050 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2051 uri = binfo.uri.ptr;
2052 } else {
2053 uri = result.uri_result;
2054 }
2055
2056 for (i=0; i<my_atoi(menuin); ++i) {
2057 pj_status_t status;
2058
2059 tmp = pj_str(uri);
2060 status = pjsua_call_make_call(current_acc, &tmp, 0, NULL,
2061 NULL, NULL);
2062 if (status != PJ_SUCCESS)
2063 break;
2064 }
2065 break;
2066
2067 case 'i':
2068 /* Send instant messaeg */
2069
2070 /* i is for call index to send message, if any */
2071 i = -1;
2072
2073 /* Make compiler happy. */
2074 uri = NULL;
2075
2076 /* Input destination. */
2077 ui_input_url("Send IM to", buf, sizeof(buf), &result);
2078 if (result.nb_result != NO_NB) {
2079
2080 if (result.nb_result == -1) {
2081 puts("You can't send broadcast IM like that!");
2082 continue;
2083
2084 } else if (result.nb_result == 0) {
2085
2086 i = current_call;
2087
2088 } else {
2089 pjsua_buddy_info binfo;
2090 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2091 uri = binfo.uri.ptr;
2092 }
2093
2094 } else if (result.uri_result) {
2095 uri = result.uri_result;
2096 }
2097
2098
2099 /* Send typing indication. */
2100 if (i != -1)
2101 pjsua_call_send_typing_ind(i, PJ_TRUE, NULL);
2102 else {
2103 pj_str_t tmp_uri = pj_str(uri);
2104 pjsua_im_typing(current_acc, &tmp_uri, PJ_TRUE, NULL);
2105 }
2106
2107 /* Input the IM . */
2108 if (!simple_input("Message", text, sizeof(text))) {
2109 /*
2110 * Cancelled.
2111 * Send typing notification too, saying we're not typing.
2112 */
2113 if (i != -1)
2114 pjsua_call_send_typing_ind(i, PJ_FALSE, NULL);
2115 else {
2116 pj_str_t tmp_uri = pj_str(uri);
2117 pjsua_im_typing(current_acc, &tmp_uri, PJ_FALSE, NULL);
2118 }
2119 continue;
2120 }
2121
2122 tmp = pj_str(text);
2123
2124 /* Send the IM */
2125 if (i != -1)
2126 pjsua_call_send_im(i, NULL, &tmp, NULL, NULL);
2127 else {
2128 pj_str_t tmp_uri = pj_str(uri);
2129 pjsua_im_send(current_acc, &tmp_uri, NULL, &tmp, NULL, NULL);
2130 }
2131
2132 break;
2133
2134 case 'a':
2135
2136 if (current_call != -1) {
2137 pjsua_call_get_info(current_call, &call_info);
2138 } else {
2139 /* Make compiler happy */
2140 call_info.role = PJSIP_ROLE_UAC;
2141 call_info.state = PJSIP_INV_STATE_DISCONNECTED;
2142 }
2143
2144 if (current_call == -1 ||
2145 call_info.role != PJSIP_ROLE_UAS ||
2146 call_info.state >= PJSIP_INV_STATE_CONNECTING)
2147 {
2148 puts("No pending incoming call");
2149 fflush(stdout);
2150 continue;
2151
2152 } else {
2153 if (!simple_input("Answer with code (100-699)", buf, sizeof(buf)))
2154 continue;
2155
2156 if (my_atoi(buf) < 100)
2157 continue;
2158
2159 /*
2160 * Must check again!
2161 * Call may have been disconnected while we're waiting for
2162 * keyboard input.
2163 */
2164 if (current_call == -1) {
2165 puts("Call has been disconnected");
2166 fflush(stdout);
2167 continue;
2168 }
2169
2170 pjsua_call_answer(current_call, my_atoi(buf), NULL, NULL);
2171 }
2172
2173 break;
2174
2175
2176 case 'h':
2177
2178 if (current_call == -1) {
2179 puts("No current call");
2180 fflush(stdout);
2181 continue;
2182
2183 } else if (menuin[1] == 'a') {
2184
2185 /* Hangup all calls */
2186 pjsua_call_hangup_all();
2187
2188 } else {
2189
2190 /* Hangup current calls */
2191 pjsua_call_hangup(current_call, 0, NULL, NULL);
2192 }
2193 break;
2194
2195 case ']':
2196 case '[':
2197 /*
2198 * Cycle next/prev dialog.
2199 */
2200 if (menuin[0] == ']') {
2201 find_next_call();
2202
2203 } else {
2204 find_prev_call();
2205 }
2206
2207 if (current_call != -1) {
2208
2209 pjsua_call_get_info(current_call, &call_info);
2210 PJ_LOG(3,(THIS_FILE,"Current dialog: %.*s",
2211 (int)call_info.remote_info.slen,
2212 call_info.remote_info.ptr));
2213
2214 } else {
2215 PJ_LOG(3,(THIS_FILE,"No current dialog"));
2216 }
2217 break;
2218
2219
2220 case '>':
2221 case '<':
2222 if (!simple_input("Enter account ID to select", buf, sizeof(buf)))
2223 break;
2224
2225 i = my_atoi(buf);
2226 if (pjsua_acc_is_valid(i)) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00002227 pjsua_acc_set_default(i);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002228 PJ_LOG(3,(THIS_FILE, "Current account changed to %d", i));
2229 } else {
2230 PJ_LOG(3,(THIS_FILE, "Invalid account id %d", i));
2231 }
2232 break;
2233
2234
2235 case '+':
2236 if (menuin[1] == 'b') {
2237
2238 pjsua_buddy_config buddy_cfg;
2239 pjsua_buddy_id buddy_id;
2240 pj_status_t status;
2241
2242 if (!simple_input("Enter buddy's URI:", buf, sizeof(buf)))
2243 break;
2244
2245 if (pjsua_verify_sip_url(buf) != PJ_SUCCESS) {
2246 printf("Invalid SIP URI '%s'\n", buf);
2247 break;
2248 }
2249
Benny Prijonoac623b32006-07-03 15:19:31 +00002250 pj_bzero(&buddy_cfg, sizeof(pjsua_buddy_config));
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002251
2252 buddy_cfg.uri = pj_str(buf);
2253 buddy_cfg.subscribe = PJ_TRUE;
2254
2255 status = pjsua_buddy_add(&buddy_cfg, &buddy_id);
2256 if (status == PJ_SUCCESS) {
2257 printf("New buddy '%s' added at index %d\n",
2258 buf, buddy_id+1);
2259 }
2260
2261 } else if (menuin[1] == 'a') {
2262
2263 printf("Sorry, this command is not supported yet\n");
2264
2265 } else {
2266 printf("Invalid input %s\n", menuin);
2267 }
2268 break;
2269
2270 case '-':
2271 if (menuin[1] == 'b') {
2272 if (!simple_input("Enter buddy ID to delete",buf,sizeof(buf)))
2273 break;
2274
2275 i = my_atoi(buf) - 1;
2276
2277 if (!pjsua_buddy_is_valid(i)) {
2278 printf("Invalid buddy id %d\n", i);
2279 } else {
2280 pjsua_buddy_del(i);
2281 printf("Buddy %d deleted\n", i);
2282 }
2283
2284 } else if (menuin[1] == 'a') {
2285
2286 if (!simple_input("Enter account ID to delete",buf,sizeof(buf)))
2287 break;
2288
2289 i = my_atoi(buf);
2290
2291 if (!pjsua_acc_is_valid(i)) {
2292 printf("Invalid account id %d\n", i);
2293 } else {
2294 pjsua_acc_del(i);
2295 printf("Account %d deleted\n", i);
2296 }
2297
2298 } else {
2299 printf("Invalid input %s\n", menuin);
2300 }
2301 break;
2302
2303 case 'H':
2304 /*
2305 * Hold call.
2306 */
2307 if (current_call != -1) {
2308
2309 pjsua_call_set_hold(current_call, NULL);
2310
2311 } else {
2312 PJ_LOG(3,(THIS_FILE, "No current call"));
2313 }
2314 break;
2315
2316 case 'v':
2317 /*
2318 * Send re-INVITE (to release hold, etc).
2319 */
2320 if (current_call != -1) {
2321
2322 pjsua_call_reinvite(current_call, PJ_TRUE, NULL);
2323
2324 } else {
2325 PJ_LOG(3,(THIS_FILE, "No current call"));
2326 }
2327 break;
2328
2329 case 'x':
2330 /*
2331 * Transfer call.
2332 */
2333 if (current_call == -1) {
2334
2335 PJ_LOG(3,(THIS_FILE, "No current call"));
2336
2337 } else {
2338 int call = current_call;
Benny Prijonod524e822006-09-22 12:48:18 +00002339 pjsua_msg_data msg_data;
2340 pjsip_generic_string_hdr refer_sub;
2341 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2342 pj_str_t STR_FALSE = { "false", 5 };
Benny Prijonof7b1c392006-11-11 16:46:34 +00002343 pjsua_call_info ci;
2344
2345 pjsua_call_get_info(current_call, &ci);
2346 printf("Transfering current call [%d] %.*s\n",
2347 current_call,
2348 (int)ci.remote_info.slen, ci.remote_info.ptr);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002349
2350 ui_input_url("Transfer to URL", buf, sizeof(buf), &result);
2351
2352 /* Check if call is still there. */
2353
2354 if (call != current_call) {
2355 puts("Call has been disconnected");
2356 continue;
2357 }
2358
Benny Prijonod524e822006-09-22 12:48:18 +00002359 pjsua_msg_data_init(&msg_data);
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002360 if (app_config.no_refersub) {
2361 /* Add Refer-Sub: false in outgoing REFER request */
2362 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2363 &STR_FALSE);
2364 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2365 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002366 if (result.nb_result != NO_NB) {
2367 if (result.nb_result == -1 || result.nb_result == 0)
2368 puts("You can't do that with transfer call!");
2369 else {
2370 pjsua_buddy_info binfo;
2371 pjsua_buddy_get_info(result.nb_result-1, &binfo);
Benny Prijonod524e822006-09-22 12:48:18 +00002372 pjsua_call_xfer( current_call, &binfo.uri, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002373 }
2374
2375 } else if (result.uri_result) {
2376 pj_str_t tmp;
2377 tmp = pj_str(result.uri_result);
Benny Prijonod524e822006-09-22 12:48:18 +00002378 pjsua_call_xfer( current_call, &tmp, &msg_data);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002379 }
2380 }
2381 break;
2382
Benny Prijonof7b1c392006-11-11 16:46:34 +00002383 case 'X':
2384 /*
2385 * Transfer call with replaces.
2386 */
2387 if (current_call == -1) {
2388
2389 PJ_LOG(3,(THIS_FILE, "No current call"));
2390
2391 } else {
2392 int call = current_call;
2393 int dst_call;
2394 pjsua_msg_data msg_data;
2395 pjsip_generic_string_hdr refer_sub;
2396 pj_str_t STR_REFER_SUB = { "Refer-Sub", 9 };
2397 pj_str_t STR_FALSE = { "false", 5 };
2398 pjsua_call_id ids[PJSUA_MAX_CALLS];
2399 pjsua_call_info ci;
2400 unsigned i, count;
2401
2402 count = PJ_ARRAY_SIZE(ids);
2403 pjsua_enum_calls(ids, &count);
2404
2405 if (count <= 1) {
2406 puts("There are no other calls");
2407 continue;
2408 }
2409
2410 pjsua_call_get_info(current_call, &ci);
2411 printf("Transfer call [%d] %.*s to one of the following:\n",
2412 current_call,
2413 (int)ci.remote_info.slen, ci.remote_info.ptr);
2414
2415 for (i=0; i<count; ++i) {
2416 pjsua_call_info call_info;
2417
2418 if (ids[i] == call)
2419 continue;
2420
2421 pjsua_call_get_info(ids[i], &call_info);
2422 printf("%d %.*s [%.*s]\n",
2423 ids[i],
2424 (int)call_info.remote_info.slen,
2425 call_info.remote_info.ptr,
2426 (int)call_info.state_text.slen,
2427 call_info.state_text.ptr);
2428 }
2429
2430 if (!simple_input("Enter call number to be replaced",
2431 buf, sizeof(buf)))
2432 continue;
2433
2434 dst_call = my_atoi(buf);
2435
2436 /* Check if call is still there. */
2437
2438 if (call != current_call) {
2439 puts("Call has been disconnected");
2440 continue;
2441 }
2442
2443 /* Check that destination call is valid. */
2444 if (dst_call == call) {
2445 puts("Destination call number must not be the same "
2446 "as the call being transfered");
2447 continue;
2448 }
2449 if (dst_call >= PJSUA_MAX_CALLS) {
2450 puts("Invalid destination call number");
2451 continue;
2452 }
2453 if (!pjsua_call_is_active(dst_call)) {
2454 puts("Invalid destination call number");
2455 continue;
2456 }
2457
2458 pjsua_msg_data_init(&msg_data);
2459 if (app_config.no_refersub) {
2460 /* Add Refer-Sub: false in outgoing REFER request */
2461 pjsip_generic_string_hdr_init2(&refer_sub, &STR_REFER_SUB,
2462 &STR_FALSE);
2463 pj_list_push_back(&msg_data.hdr_list, &refer_sub);
2464 }
2465
2466 pjsua_call_xfer_replaces(call, dst_call, 0, &msg_data);
2467 }
2468 break;
2469
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002470 case '#':
2471 /*
2472 * Send DTMF strings.
2473 */
2474 if (current_call == -1) {
2475
2476 PJ_LOG(3,(THIS_FILE, "No current call"));
2477
2478 } else if (!pjsua_call_has_media(current_call)) {
2479
2480 PJ_LOG(3,(THIS_FILE, "Media is not established yet!"));
2481
2482 } else {
2483 pj_str_t digits;
2484 int call = current_call;
2485 pj_status_t status;
2486
2487 if (!simple_input("DTMF strings to send (0-9*#A-B)", buf,
2488 sizeof(buf)))
2489 {
2490 break;
2491 }
2492
2493 if (call != current_call) {
2494 puts("Call has been disconnected");
2495 continue;
2496 }
2497
2498 digits = pj_str(buf);
2499 status = pjsua_call_dial_dtmf(current_call, &digits);
2500 if (status != PJ_SUCCESS) {
2501 pjsua_perror(THIS_FILE, "Unable to send DTMF", status);
2502 } else {
2503 puts("DTMF digits enqueued for transmission");
2504 }
2505 }
2506 break;
2507
Benny Prijono56315612006-07-18 14:39:40 +00002508 case 'S':
2509 /*
2510 * Send arbitrary request
2511 */
2512 if (pjsua_acc_get_count() == 0) {
2513 puts("Sorry, need at least one account configured");
2514 break;
2515 }
2516
2517 puts("Send arbitrary request to remote host");
2518
2519 /* Input METHOD */
2520 if (!simple_input("Request method:",text,sizeof(text)))
2521 break;
2522
2523 /* Input destination URI */
2524 uri = NULL;
2525 ui_input_url("Destination URI", buf, sizeof(buf), &result);
2526 if (result.nb_result != NO_NB) {
2527
2528 if (result.nb_result == -1 || result.nb_result == 0) {
2529 puts("Sorry you can't do that!");
2530 continue;
2531 } else {
2532 pjsua_buddy_info binfo;
2533 pjsua_buddy_get_info(result.nb_result-1, &binfo);
2534 uri = binfo.uri.ptr;
2535 }
2536
2537 } else if (result.uri_result) {
2538 uri = result.uri_result;
2539 }
2540
2541 tmp = pj_str(uri);
2542
2543 send_request(text, &tmp);
2544 break;
2545
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002546 case 's':
2547 case 'u':
2548 /*
2549 * Subscribe/unsubscribe presence.
2550 */
2551 ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result);
2552 if (result.nb_result != NO_NB) {
2553 if (result.nb_result == -1) {
2554 int i, count;
2555 count = pjsua_get_buddy_count();
2556 for (i=0; i<count; ++i)
2557 pjsua_buddy_subscribe_pres(i, menuin[0]=='s');
2558 } else if (result.nb_result == 0) {
2559 puts("Sorry, can only subscribe to buddy's presence, "
2560 "not from existing call");
2561 } else {
2562 pjsua_buddy_subscribe_pres(result.nb_result-1, (menuin[0]=='s'));
2563 }
2564
2565 } else if (result.uri_result) {
2566 puts("Sorry, can only subscribe to buddy's presence, "
2567 "not arbitrary URL (for now)");
2568 }
2569
2570 break;
2571
2572 case 'r':
2573 switch (menuin[1]) {
2574 case 'r':
2575 /*
2576 * Re-Register.
2577 */
2578 pjsua_acc_set_registration(current_acc, PJ_TRUE);
2579 break;
2580 case 'u':
2581 /*
2582 * Unregister
2583 */
2584 pjsua_acc_set_registration(current_acc, PJ_FALSE);
2585 break;
2586 }
2587 break;
2588
2589 case 't':
2590 pjsua_acc_get_info(current_acc, &acc_info);
2591 acc_info.online_status = !acc_info.online_status;
2592 pjsua_acc_set_online_status(current_acc, acc_info.online_status);
2593 printf("Setting %s online status to %s\n",
2594 acc_info.acc_uri.ptr,
2595 (acc_info.online_status?"online":"offline"));
2596 break;
2597
2598 case 'c':
2599 switch (menuin[1]) {
2600 case 'l':
2601 conf_list();
2602 break;
2603 case 'c':
2604 case 'd':
2605 {
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002606 char tmp[10], src_port[10], dst_port[10];
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002607 pj_status_t status;
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002608 int cnt;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002609 const char *src_title, *dst_title;
2610
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002611 cnt = sscanf(menuin, "%s %s %s", tmp, src_port, dst_port);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002612
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002613 if (cnt != 3) {
2614 conf_list();
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002615
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002616 src_title = (menuin[1]=='c'?
2617 "Connect src port #":
2618 "Disconnect src port #");
2619 dst_title = (menuin[1]=='c'?
2620 "To dst port #":
2621 "From dst port #");
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002622
Benny Prijono2b6ec1a2006-11-26 10:49:58 +00002623 if (!simple_input(src_title, src_port, sizeof(src_port)))
2624 break;
2625
2626 if (!simple_input(dst_title, dst_port, sizeof(dst_port)))
2627 break;
2628 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002629
2630 if (menuin[1]=='c') {
2631 status = pjsua_conf_connect(my_atoi(src_port),
2632 my_atoi(dst_port));
2633 } else {
2634 status = pjsua_conf_disconnect(my_atoi(src_port),
2635 my_atoi(dst_port));
2636 }
2637 if (status == PJ_SUCCESS) {
2638 puts("Success");
2639 } else {
2640 puts("ERROR!!");
2641 }
2642 }
2643 break;
2644 }
2645 break;
2646
2647 case 'd':
2648 if (menuin[1] == 'c') {
2649 char settings[2000];
2650 int len;
2651
2652 len = write_settings(&app_config, settings, sizeof(settings));
2653 if (len < 1)
2654 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2655 else
2656 PJ_LOG(3,(THIS_FILE,
2657 "Dumping configuration (%d bytes):\n%s\n",
2658 len, settings));
Benny Prijono819506c2006-06-22 22:29:51 +00002659
2660 } else if (menuin[1] == 'q') {
2661
2662 if (current_call != PJSUA_INVALID_ID) {
2663 char buf[1024];
2664 pjsua_call_dump(current_call, PJ_TRUE, buf,
2665 sizeof(buf), " ");
2666 PJ_LOG(3,(THIS_FILE, "\n%s", buf));
2667 } else {
2668 PJ_LOG(3,(THIS_FILE, "No current call"));
2669 }
2670
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002671 } else {
2672 app_dump(menuin[1]=='d');
2673 }
2674 break;
2675
2676
2677 case 'f':
2678 if (simple_input("Enter output filename", buf, sizeof(buf))) {
2679 char settings[2000];
2680 int len;
2681
2682 len = write_settings(&app_config, settings, sizeof(settings));
2683 if (len < 1)
2684 PJ_LOG(3,(THIS_FILE, "Error: not enough buffer"));
2685 else {
2686 pj_oshandle_t fd;
2687 pj_status_t status;
2688
2689 status = pj_file_open(app_config.pool, buf,
2690 PJ_O_WRONLY, &fd);
2691 if (status != PJ_SUCCESS) {
2692 pjsua_perror(THIS_FILE, "Unable to open file", status);
2693 } else {
2694 pj_ssize_t size = len;
2695 pj_file_write(fd, settings, &size);
2696 pj_file_close(fd);
2697
2698 printf("Settings successfully written to '%s'\n", buf);
2699 }
2700 }
2701
2702 }
2703 break;
2704
2705
2706 case 'q':
2707 goto on_exit;
2708
2709
2710 default:
2711 if (menuin[0] != '\n' && menuin[0] != '\r') {
2712 printf("Invalid input %s", menuin);
2713 }
2714 keystroke_help();
2715 break;
2716 }
2717 }
2718
2719on_exit:
2720 ;
2721}
2722
2723
2724/*****************************************************************************
2725 * Public API
2726 */
2727
2728pj_status_t app_init(int argc, char *argv[])
2729{
Benny Prijonoe93e2872006-06-28 16:46:49 +00002730 pjsua_transport_id transport_id = -1;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002731 unsigned i;
2732 pj_status_t status;
2733
2734 /* Create pjsua */
2735 status = pjsua_create();
2736 if (status != PJ_SUCCESS)
2737 return status;
2738
2739 /* Create pool for application */
2740 app_config.pool = pjsua_pool_create("pjsua", 4000, 4000);
2741
2742 /* Initialize default config */
2743 default_config(&app_config);
2744
2745 /* Parse the arguments */
2746 status = parse_args(argc, argv, &app_config, &uri_arg);
2747 if (status != PJ_SUCCESS)
2748 return status;
2749
2750 /* Copy udp_cfg STUN config to rtp_cfg */
2751 app_config.rtp_cfg.use_stun = app_config.udp_cfg.use_stun;
2752 app_config.rtp_cfg.stun_config = app_config.udp_cfg.stun_config;
2753
2754
2755 /* Initialize application callbacks */
2756 app_config.cfg.cb.on_call_state = &on_call_state;
2757 app_config.cfg.cb.on_call_media_state = &on_call_media_state;
2758 app_config.cfg.cb.on_incoming_call = &on_incoming_call;
2759 app_config.cfg.cb.on_reg_state = &on_reg_state;
2760 app_config.cfg.cb.on_buddy_state = &on_buddy_state;
2761 app_config.cfg.cb.on_pager = &on_pager;
2762 app_config.cfg.cb.on_typing = &on_typing;
Benny Prijono4ddad2c2006-10-18 17:16:34 +00002763 app_config.cfg.cb.on_call_transfer_status = &on_call_transfer_status;
Benny Prijonof7b1c392006-11-11 16:46:34 +00002764 app_config.cfg.cb.on_call_replaced = &on_call_replaced;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002765
2766 /* Initialize pjsua */
2767 status = pjsua_init(&app_config.cfg, &app_config.log_cfg,
2768 &app_config.media_cfg);
2769 if (status != PJ_SUCCESS)
2770 return status;
2771
Benny Prijonoe909eac2006-07-27 22:04:56 +00002772#ifdef STEREO_DEMO
2773 stereo_demo();
2774#endif
2775
Benny Prijono804ff0a2006-09-14 11:17:48 +00002776 /* Initialize calls data */
2777 for (i=0; i<PJ_ARRAY_SIZE(app_config.call_data); ++i) {
2778 app_config.call_data[i].timer.id = PJSUA_INVALID_ID;
2779 app_config.call_data[i].timer.cb = &call_timeout_callback;
2780 }
2781
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002782 /* Optionally registers WAV file */
2783 if (app_config.wav_file.slen) {
Benny Prijono6fd4b8f2006-06-22 18:51:50 +00002784 status = pjsua_player_create(&app_config.wav_file, 0,
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002785 &app_config.wav_id);
2786 if (status != PJ_SUCCESS)
2787 goto on_error;
Benny Prijono22a300a2006-06-14 20:04:55 +00002788
2789 app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002790 }
2791
Benny Prijono1ebd6142006-10-19 15:48:02 +00002792 /* Optionally create recorder file, if any. */
2793 if (app_config.rec_file.slen) {
2794 status = pjsua_recorder_create(&app_config.rec_file, 0, NULL, 0, 0,
2795 &app_config.rec_id);
2796 if (status != PJ_SUCCESS)
2797 goto on_error;
2798
2799 app_config.rec_port = pjsua_recorder_get_conf_port(app_config.rec_id);
2800 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002801
Benny Prijonoe93e2872006-06-28 16:46:49 +00002802 /* Add TCP transport unless it's disabled */
2803 if (!app_config.no_tcp) {
2804 status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
2805 &app_config.udp_cfg,
2806 &transport_id);
2807 if (status != PJ_SUCCESS)
2808 goto on_error;
2809
2810 /* Add local account */
Benny Prijono21b9ad92006-08-15 13:11:22 +00002811 pjsua_acc_add_local(transport_id, PJ_TRUE, NULL);
Benny Prijonoe93e2872006-06-28 16:46:49 +00002812 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2813
2814 }
2815
Benny Prijonoe93e2872006-06-28 16:46:49 +00002816 /* Add UDP transport unless it's disabled. */
2817 if (!app_config.no_udp) {
2818 status = pjsua_transport_create(PJSIP_TRANSPORT_UDP,
2819 &app_config.udp_cfg,
2820 &transport_id);
2821 if (status != PJ_SUCCESS)
2822 goto on_error;
2823
2824 /* Add local account */
Benny Prijono21b9ad92006-08-15 13:11:22 +00002825 pjsua_acc_add_local(transport_id, PJ_TRUE, NULL);
Benny Prijonoe93e2872006-06-28 16:46:49 +00002826 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2827 }
2828
Benny Prijono6e0e54b2006-12-08 21:58:31 +00002829#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
2830 /* Add TLS transport when application wants one */
2831 if (app_config.use_tls) {
2832 status = pjsua_transport_create(PJSIP_TRANSPORT_TLS,
2833 &app_config.udp_cfg,
2834 &transport_id);
2835 if (status != PJ_SUCCESS)
2836 goto on_error;
2837 }
2838#endif
2839
Benny Prijonoe93e2872006-06-28 16:46:49 +00002840 if (transport_id == -1) {
2841 PJ_LOG(3,(THIS_FILE, "Error: no transport is configured"));
2842 status = -1;
2843 goto on_error;
2844 }
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002845
2846
2847 /* Add accounts */
2848 for (i=0; i<app_config.acc_cnt; ++i) {
Benny Prijono21b9ad92006-08-15 13:11:22 +00002849 status = pjsua_acc_add(&app_config.acc_cfg[i], PJ_TRUE, NULL);
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002850 if (status != PJ_SUCCESS)
2851 goto on_error;
2852 pjsua_acc_set_online_status(current_acc, PJ_TRUE);
2853 }
2854
2855 /* Add buddies */
2856 for (i=0; i<app_config.buddy_cnt; ++i) {
2857 status = pjsua_buddy_add(&app_config.buddy_cfg[i], NULL);
2858 if (status != PJ_SUCCESS)
2859 goto on_error;
2860 }
2861
2862 /* Optionally set codec orders */
2863 for (i=0; i<app_config.codec_cnt; ++i) {
2864 pjsua_codec_set_priority(&app_config.codec_arg[i],
2865 (pj_uint8_t)(PJMEDIA_CODEC_PRIO_NORMAL+i+9));
2866 }
2867
2868 /* Add RTP transports */
2869 status = pjsua_media_transports_create(&app_config.rtp_cfg);
2870 if (status != PJ_SUCCESS)
2871 goto on_error;
2872
Benny Prijono22a300a2006-06-14 20:04:55 +00002873 /* Use null sound device? */
Benny Prijonoe909eac2006-07-27 22:04:56 +00002874#ifndef STEREO_DEMO
Benny Prijono22a300a2006-06-14 20:04:55 +00002875 if (app_config.null_audio) {
2876 status = pjsua_set_null_snd_dev();
2877 if (status != PJ_SUCCESS)
2878 return status;
2879 }
Benny Prijonoe909eac2006-07-27 22:04:56 +00002880#endif
Benny Prijono22a300a2006-06-14 20:04:55 +00002881
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002882 return PJ_SUCCESS;
2883
2884on_error:
2885 pjsua_destroy();
2886 return status;
2887}
2888
2889
2890pj_status_t app_main(void)
2891{
2892 pj_status_t status;
2893
2894 /* Start pjsua */
2895 status = pjsua_start();
2896 if (status != PJ_SUCCESS) {
2897 pjsua_destroy();
2898 return status;
2899 }
2900
2901 console_app_main(&uri_arg);
2902
2903 return PJ_SUCCESS;
2904}
2905
2906pj_status_t app_destroy(void)
2907{
Benny Prijonof762ee72006-12-01 11:14:37 +00002908 pj_status_t status;
2909
Benny Prijonoe909eac2006-07-27 22:04:56 +00002910#ifdef STEREO_DEMO
2911 if (app_config.snd) {
2912 pjmedia_snd_port_destroy(app_config.snd);
2913 app_config.snd = NULL;
2914 }
2915#endif
2916
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002917 if (app_config.pool) {
2918 pj_pool_release(app_config.pool);
2919 app_config.pool = NULL;
2920 }
2921
Benny Prijonof762ee72006-12-01 11:14:37 +00002922 status = pjsua_destroy();
2923
2924 pj_bzero(&app_config, sizeof(app_config));
2925
2926 return status;
Benny Prijonoeebe9af2006-06-13 22:57:13 +00002927}
Benny Prijonoe909eac2006-07-27 22:04:56 +00002928
2929
2930#ifdef STEREO_DEMO
2931static void stereo_demo()
2932{
2933 pjmedia_port *conf, *splitter, *ch1;
2934 unsigned clock;
2935 pj_status_t status;
2936
2937 /* Disable existing sound device */
2938 conf = pjsua_set_no_snd_dev();
2939
2940 clock = app_config.media_cfg.clock_rate;
2941
2942 /* Create stereo-mono splitter/combiner */
2943 status = pjmedia_splitcomb_create(app_config.pool,
2944 clock /* clock rate */,
2945 2 /* stereo */,
2946 clock*2*10/1000/* 10ms samples * 2ch */,
2947 16 /* bits */,
2948 0 /* options */,
2949 &splitter);
2950 pj_assert(status == PJ_SUCCESS);
2951
2952 /* Connect channel0 (left channel?) to conference port slot0 */
2953 status = pjmedia_splitcomb_set_channel(splitter, 0 /* ch0 */,
2954 0 /*options*/,
2955 conf);
2956 pj_assert(status == PJ_SUCCESS);
2957
2958 /* Create reverse channel for channel1 (right channel?)... */
2959 status = pjmedia_splitcomb_create_rev_channel(app_config.pool,
2960 splitter,
2961 1 /* ch1 */,
2962 0 /* options */,
2963 &ch1);
2964 pj_assert(status == PJ_SUCCESS);
2965
2966 /* .. and register it to conference bridge (it would be slot1
2967 * if there's no other devices connected to the bridge)
2968 */
2969 status = pjsua_conf_add_port(app_config.pool, ch1, NULL);
2970 pj_assert(status == PJ_SUCCESS);
2971
2972 /* Create sound device */
2973 status = pjmedia_snd_port_create(app_config.pool, -1, -1,
2974 clock /* clock rate */,
2975 2 /* stereo */,
2976 clock*2*10/1000 /* 10 ms samples * 2ch */,
2977 16 /* bits */,
2978 0, &app_config.snd);
2979 pj_assert(status == PJ_SUCCESS);
2980
2981
2982 /* Connect the splitter to the sound device */
2983 status = pjmedia_snd_port_connect(app_config.snd, splitter);
2984 pj_assert(status == PJ_SUCCESS);
2985
2986}
2987#endif
2988